完善流量控制前馈、稳态判定与阀门标定流程
- 前馈冻结改用可配置的 3 秒流量绝对误差滑窗,修复采样抖动造成的重复解冻,并保留下游扰动后的自动更新 - 支持非单调阀特性反解、最小可测面积以下直接全关,以及闭阀端精细扫点与辨识开关 - 调整双压力判稳、最长等待时间、在线入口全开收尾和闭环四联图 - 更新配置、README、阀模型及离线测试,归档本轮实验数据与诊断产物
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
"""flow_control 前馈误差滑窗的离线自测(不连接硬件)。"""
|
||||
|
||||
from collections import deque
|
||||
|
||||
import pytest
|
||||
|
||||
from flow_control import FlowControlLoop
|
||||
|
||||
|
||||
def _pid_threshold_loop():
|
||||
"""只构造测试 FAR/NEAR 阈值计算所需的状态。"""
|
||||
loop = FlowControlLoop.__new__(FlowControlLoop)
|
||||
loop.pid_near_error_min_slm = 1.0
|
||||
loop.pid_near_error_max_slm = 3.0
|
||||
loop.pid_near_error_target_ratio = 0.05
|
||||
loop.pid_far_error_min_slm = 4.0
|
||||
loop.pid_far_error_max_slm = 12.0
|
||||
loop.pid_far_error_target_ratio = 0.20
|
||||
return loop
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("target", "expected_near", "expected_far"),
|
||||
[
|
||||
(10.0, 1.0, 4.0), # 比例值低于下限
|
||||
(30.0, 1.5, 6.0), # 比例值位于上下限之间
|
||||
(60.0, 3.0, 12.0), # 比例值恰好达到上限
|
||||
(300.0, 3.0, 12.0), # 比例值超过上限
|
||||
],
|
||||
)
|
||||
def test_pid_error_thresholds_are_clamped(target, expected_near, expected_far):
|
||||
loop = _pid_threshold_loop()
|
||||
|
||||
near, far = loop._pid_error_thresholds(target)
|
||||
|
||||
assert near == pytest.approx(expected_near)
|
||||
assert far == pytest.approx(expected_far)
|
||||
|
||||
|
||||
def _feedforward_window_loop():
|
||||
"""只构造测试滑窗所需的最小 FlowControlLoop 状态。"""
|
||||
loop = FlowControlLoop.__new__(FlowControlLoop)
|
||||
loop.target_flow_slm = 100.0
|
||||
loop.feedforward_update_window_s = 3.0
|
||||
loop._feedforward_error_window = deque()
|
||||
loop._feedforward_window_ready = False
|
||||
return loop
|
||||
|
||||
|
||||
def test_feedforward_window_stays_ready_despite_sampling_jitter():
|
||||
loop = _feedforward_window_loop()
|
||||
|
||||
# 首个完整 3 s 窗口内误差恒为 2 SLM。
|
||||
sample_times = [0.0, 0.099]
|
||||
sample_times.extend(0.201 + 0.1 * index for index in range(28))
|
||||
sample_times.append(3.0)
|
||||
means = [
|
||||
loop._feedforward_window_mean_abs_error(timestamp, 98.0)
|
||||
for timestamp in sample_times
|
||||
]
|
||||
|
||||
assert all(value is None for value in means[:-1])
|
||||
assert means[-1] == pytest.approx(2.0)
|
||||
assert loop._feedforward_window_ready
|
||||
|
||||
# 下一周期淘汰旧样本后,队列首尾跨度仅 2.899 s。窗口仍应保持
|
||||
# ready,并返回最近 3 s 的误差均值,不能因约 0.1 s 抖动返回 None。
|
||||
mean = loop._feedforward_window_mean_abs_error(3.1, 98.0)
|
||||
|
||||
span = (
|
||||
loop._feedforward_error_window[-1][0]
|
||||
- loop._feedforward_error_window[0][0]
|
||||
)
|
||||
assert span == pytest.approx(2.899)
|
||||
assert mean == pytest.approx(2.0)
|
||||
assert loop._feedforward_window_ready
|
||||
|
||||
|
||||
def test_ready_window_keeps_tracking_recent_error_and_can_reset():
|
||||
loop = _feedforward_window_loop()
|
||||
loop._feedforward_window_mean_abs_error(0.0, 98.0)
|
||||
assert loop._feedforward_window_mean_abs_error(3.0, 98.0) == pytest.approx(2.0)
|
||||
|
||||
# 就绪状态不会冻结均值本身;最近 3 s 都变成 10 SLM 误差后,
|
||||
# 返回的新均值会超过默认 5 SLM 阈值,从而触发上层前馈更新。
|
||||
for index in range(1, 32):
|
||||
mean = loop._feedforward_window_mean_abs_error(
|
||||
3.0 + index * 0.1,
|
||||
90.0,
|
||||
)
|
||||
assert mean == pytest.approx(10.0)
|
||||
|
||||
loop._reset_feedforward_error_window()
|
||||
assert not loop._feedforward_window_ready
|
||||
assert not loop._feedforward_error_window
|
||||
assert loop._feedforward_window_mean_abs_error(10.0, 90.0) is None
|
||||
Reference in New Issue
Block a user