完善流量控制前馈、稳态判定与阀门标定流程

- 前馈冻结改用可配置的 3 秒流量绝对误差滑窗,修复采样抖动造成的重复解冻,并保留下游扰动后的自动更新
- 支持非单调阀特性反解、最小可测面积以下直接全关,以及闭阀端精细扫点与辨识开关
- 调整双压力判稳、最长等待时间、在线入口全开收尾和闭环四联图
- 更新配置、README、阀模型及离线测试,归档本轮实验数据与诊断产物
This commit is contained in:
2026-09-02 16:14:42 +08:00
parent 5888b4ccce
commit cfeedcf887
80 changed files with 40473 additions and 171 deletions
+70 -6
View File
@@ -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):