完善流量控制前馈、稳态判定与阀门标定流程
- 前馈冻结改用可配置的 3 秒流量绝对误差滑窗,修复采样抖动造成的重复解冻,并保留下游扰动后的自动更新 - 支持非单调阀特性反解、最小可测面积以下直接全关,以及闭阀端精细扫点与辨识开关 - 调整双压力判稳、最长等待时间、在线入口全开收尾和闭环四联图 - 更新配置、README、阀模型及离线测试,归档本轮实验数据与诊断产物
This commit is contained in:
+70
-6
@@ -42,7 +42,14 @@ class ValveModel:
|
||||
``area_table`` 是按 x 升序的 ``(x, A_eff)`` 列表。
|
||||
"""
|
||||
|
||||
def __init__(self, area_table, motor_open, motor_closed):
|
||||
def __init__(
|
||||
self,
|
||||
area_table,
|
||||
motor_open,
|
||||
motor_closed,
|
||||
*,
|
||||
enforce_monotonic=True,
|
||||
):
|
||||
motor_open = float(motor_open)
|
||||
motor_closed = float(motor_closed)
|
||||
if motor_open >= motor_closed:
|
||||
@@ -69,8 +76,11 @@ class ValveModel:
|
||||
if a < 0.0:
|
||||
raise ValueError(f"A_eff 不得为负: x={x}, A_eff={a}")
|
||||
|
||||
if not isinstance(enforce_monotonic, bool):
|
||||
raise ValueError("enforce_monotonic 必须是布尔值")
|
||||
ys = [a for (_, a) in table]
|
||||
if not _is_monotonic(ys):
|
||||
is_monotonic = _is_monotonic(ys)
|
||||
if enforce_monotonic and not is_monotonic:
|
||||
raise ValueError(
|
||||
"A_eff(x) 非单调:反解无法唯一。请检查扫点数据,"
|
||||
"缩小范围到单调段后重扫。"
|
||||
@@ -79,6 +89,8 @@ class ValveModel:
|
||||
self.area_table = table
|
||||
self.motor_open = motor_open
|
||||
self.motor_closed = motor_closed
|
||||
self.enforce_monotonic = enforce_monotonic
|
||||
self.is_monotonic = is_monotonic
|
||||
self._xs = [x for (x, _) in table]
|
||||
self._ys = ys
|
||||
|
||||
@@ -89,10 +101,14 @@ class ValveModel:
|
||||
return _interp(float(x), self._xs, self._ys)
|
||||
|
||||
def stroke_from_area(self, a):
|
||||
"""反查 x(A_eff);表单调,越界钳位到端点行程。"""
|
||||
"""反查 x(A_eff);低于实测最小面积时直接返回机械全关行程。"""
|
||||
a = float(a)
|
||||
xs = self._xs
|
||||
ys = self._ys
|
||||
|
||||
if not self.is_monotonic:
|
||||
return self._stroke_from_nonmonotonic_area(a)
|
||||
|
||||
increasing = ys[0] <= ys[-1]
|
||||
|
||||
if increasing:
|
||||
@@ -102,7 +118,10 @@ class ValveModel:
|
||||
y_min, y_min_x = ys[-1], xs[-1]
|
||||
y_max, y_max_x = ys[0], xs[0]
|
||||
|
||||
if a <= y_min:
|
||||
if a < y_min:
|
||||
# 小于最小实测有效面积属于不可连续调节区,直接机械全关。
|
||||
return self.motor_closed
|
||||
if a == y_min:
|
||||
return y_min_x
|
||||
if a >= y_max:
|
||||
return y_max_x
|
||||
@@ -117,6 +136,36 @@ class ValveModel:
|
||||
# 理论走不到这里(a 严格落在 y_min/y_max 之间且表单调)。
|
||||
return xs[0]
|
||||
|
||||
def _stroke_from_nonmonotonic_area(self, a):
|
||||
"""非单调表反解:存在多个交点时选择更接近关闭端的较大行程。"""
|
||||
xs = self._xs
|
||||
ys = self._ys
|
||||
y_min = min(ys)
|
||||
y_max = max(ys)
|
||||
y_min_x = max(x for x, y in zip(xs, ys) if y == y_min)
|
||||
y_max_x = min(x for x, y in zip(xs, ys) if y == y_max)
|
||||
|
||||
if a < y_min:
|
||||
return self.motor_closed
|
||||
if a == y_min:
|
||||
return y_min_x
|
||||
if a >= y_max:
|
||||
return y_max_x
|
||||
|
||||
candidates = []
|
||||
for i in range(len(ys) - 1):
|
||||
x0, x1 = xs[i], xs[i + 1]
|
||||
y0, y1 = ys[i], ys[i + 1]
|
||||
if y0 == y1:
|
||||
if a == y0:
|
||||
candidates.append(max(x0, x1))
|
||||
continue
|
||||
if (y0 <= a <= y1) or (y1 <= a <= y0):
|
||||
t = (a - y0) / (y1 - y0)
|
||||
candidates.append(x0 + t * (x1 - x0))
|
||||
|
||||
return max(candidates) if candidates else self.motor_closed
|
||||
|
||||
# ------------------------------------------------------------ 静态特性
|
||||
|
||||
def flow_ss(self, x, p1_kpa, p2_kpa):
|
||||
@@ -151,17 +200,32 @@ class ValveModel:
|
||||
data = {
|
||||
"motor_open": self.motor_open,
|
||||
"motor_closed": self.motor_closed,
|
||||
"enforce_monotonic": self.enforce_monotonic,
|
||||
"area_table": [[x, a] for (x, a) in self.area_table],
|
||||
}
|
||||
with open(path, "w", encoding="utf-8") as file:
|
||||
json.dump(data, file, indent=2, ensure_ascii=False)
|
||||
|
||||
@classmethod
|
||||
def load(cls, path, motor_open, motor_closed):
|
||||
def load(
|
||||
cls,
|
||||
path,
|
||||
motor_open,
|
||||
motor_closed,
|
||||
*,
|
||||
enforce_monotonic=None,
|
||||
):
|
||||
with open(path, "r", encoding="utf-8") as file:
|
||||
data = json.load(file)
|
||||
area_table = [tuple(item) for item in data["area_table"]]
|
||||
return cls(area_table, motor_open, motor_closed)
|
||||
if enforce_monotonic is None:
|
||||
enforce_monotonic = data.get("enforce_monotonic", True)
|
||||
return cls(
|
||||
area_table,
|
||||
motor_open,
|
||||
motor_closed,
|
||||
enforce_monotonic=enforce_monotonic,
|
||||
)
|
||||
|
||||
|
||||
def _is_monotonic(ys):
|
||||
|
||||
Reference in New Issue
Block a user