完善流量控制前馈、稳态判定与阀门标定流程
- 前馈冻结改用可配置的 3 秒流量绝对误差滑窗,修复采样抖动造成的重复解冻,并保留下游扰动后的自动更新 - 支持非单调阀特性反解、最小可测面积以下直接全关,以及闭阀端精细扫点与辨识开关 - 调整双压力判稳、最长等待时间、在线入口全开收尾和闭环四联图 - 更新配置、README、阀模型及离线测试,归档本轮实验数据与诊断产物
This commit is contained in:
@@ -89,6 +89,18 @@ def test_valve_model_roundtrip():
|
||||
assert _close(model.feedforward_stroke(q, 400.0, 300.0), 900.0, rel=0.01)
|
||||
|
||||
|
||||
def test_area_below_measured_minimum_returns_fully_closed_stroke():
|
||||
model = ValveModel(
|
||||
[(800.0, 0.7358347), (980.0, 0.002094)],
|
||||
motor_open=800.0,
|
||||
motor_closed=1000.0,
|
||||
)
|
||||
|
||||
assert model.stroke_from_area(0.002094) == 980.0
|
||||
assert model.stroke_from_area(0.002093) == 1000.0
|
||||
assert model.stroke_from_area(0.0) == 1000.0
|
||||
|
||||
|
||||
def test_non_monotonic_raises():
|
||||
table = [(800.0, 0.5), (900.0, 0.1), (1000.0, 0.3)]
|
||||
try:
|
||||
@@ -98,6 +110,48 @@ def test_non_monotonic_raises():
|
||||
raise AssertionError("非单调表应抛 ValueError")
|
||||
|
||||
|
||||
def test_monotonic_check_can_be_disabled():
|
||||
import identify_valve
|
||||
|
||||
p1 = 400.0
|
||||
denom = p1 + P_ATM # p2=0 时为阻塞流,F=1。
|
||||
steady = [
|
||||
(800.0, 0.5 * denom, p1, 0.0, None),
|
||||
(900.0, 0.1 * denom, p1, 0.0, None),
|
||||
(1000.0, 0.2 * denom, p1, 0.0, None),
|
||||
]
|
||||
|
||||
checked, dropped, _, _ = identify_valve.compute_area_table(
|
||||
steady,
|
||||
check_monotonic=True,
|
||||
)
|
||||
unchecked, unchecked_dropped, _, _ = identify_valve.compute_area_table(
|
||||
steady,
|
||||
check_monotonic=False,
|
||||
)
|
||||
assert len(checked) == 2
|
||||
assert len(dropped) == 1
|
||||
assert len(unchecked) == 3
|
||||
assert not unchecked_dropped
|
||||
|
||||
model = ValveModel(
|
||||
unchecked,
|
||||
motor_open=800.0,
|
||||
motor_closed=1000.0,
|
||||
enforce_monotonic=False,
|
||||
)
|
||||
assert not model.is_monotonic
|
||||
# A_eff=0.15 有两个交点,选择更接近关闭端的 x=950。
|
||||
assert _close(model.stroke_from_area(0.15), 950.0)
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
path = Path(tmp) / "nonmonotonic.json"
|
||||
model.save(path)
|
||||
loaded = ValveModel.load(path, 800.0, 1000.0)
|
||||
assert not loaded.enforce_monotonic
|
||||
assert _close(loaded.stroke_from_area(0.15), 950.0)
|
||||
|
||||
|
||||
def test_identify_end_to_end():
|
||||
import identify_valve
|
||||
|
||||
@@ -170,16 +224,49 @@ def test_resolve_csv_paths_glob():
|
||||
assert [Path(p).name for p in paths] == ["a.csv", "b.csv"], paths
|
||||
|
||||
|
||||
def test_open_loop_appends_fine_closed_end_strokes():
|
||||
import config
|
||||
import open_loop
|
||||
|
||||
points = open_loop.generate_scan_points(0.0, 100.0, 50.0, seed=42)
|
||||
fine_strokes = open_loop.generate_fine_strokes(
|
||||
config.OPEN_LOOP_FINE_STROKE_START,
|
||||
config.MOTOR_CLOSED_POSITION,
|
||||
config.OPEN_LOOP_FINE_STROKE_STEP,
|
||||
)
|
||||
|
||||
assert open_loop.generate_fine_strokes(975.0, 1000.0, 5.0) == [
|
||||
975.0,
|
||||
980.0,
|
||||
985.0,
|
||||
990.0,
|
||||
995.0,
|
||||
1000.0,
|
||||
]
|
||||
fine_points = points[-len(fine_strokes):]
|
||||
assert [position for _, position in fine_points] == fine_strokes
|
||||
for opening, position in fine_points:
|
||||
assert _close(
|
||||
opening,
|
||||
open_loop.motor_position_to_opening(
|
||||
position,
|
||||
config.MOTOR_OPEN_POSITION,
|
||||
config.MOTOR_CLOSED_POSITION,
|
||||
),
|
||||
)
|
||||
if __name__ == "__main__":
|
||||
tests = [
|
||||
test_f_ratio,
|
||||
test_abs_pressure,
|
||||
test_valve_model_roundtrip,
|
||||
test_area_below_measured_minimum_returns_fully_closed_stroke,
|
||||
test_non_monotonic_raises,
|
||||
test_monotonic_check_can_be_disabled,
|
||||
test_identify_end_to_end,
|
||||
test_merge_multiple_csvs,
|
||||
test_direction_tagging,
|
||||
test_resolve_csv_paths_glob,
|
||||
test_open_loop_appends_fine_closed_end_strokes,
|
||||
]
|
||||
for test in tests:
|
||||
test()
|
||||
|
||||
Reference in New Issue
Block a user