三容水箱
本例使用三容水箱的液位与流量数据学习过程模型,再构建 MPC 或残差 PID 控制器,使末端液位跟踪 15 cm 目标,并抑制负载扰动。
1. 任务背景与目标
连通水箱为什么存在延迟与耦合
三容水箱用于模拟相连容器的液位控制。三个水箱按 1 → 3 → 2 连通,连通管的水流由两端水头差决定。泵 1 向水箱 1 注水,控制目标却是末端水箱 2 的液位,因此动作需要通过中间水箱逐级传递。
当末端液位偏低时,增加泵 1 流量并不会立即抬高末端液位;如果在响应尚未到达时持续加大泵量,随后可能出现超调。泵 2 对末端施加独立负载,又会使相同泵 1 指令产生不同结果。这要求控制器同时考虑当前三箱液位、负载变化和过程延迟。
三个液位共同描述水的储存与传递状态。只记录末端液位,会丢失上游已经积累了多少水的信息;遗漏泵 2 负载,则会把负载变化误当作模型误差。后面的数据字段和决策流图据此保留 levels、action 和 load。
液位目标与动作范围
图:装置连接与变量对应关系,图形未按比例绘制;连通管流向取决于水头差。q2 是外生负载。
控制变量是泵 1 流量 q1,范围 [0, 120] ml/s;目标是末端液位 h2 = 15 cm。 泵 2 流量 q2 是外生负载,不是第二个可优化动作。状态列顺序为 [h1, h3, h2], 控制周期为 20 s,液位范围为 [0, 60] cm。这些都是本仿真对象的定义,不能直接当作任意装置的参数。
泵 1 的影响经中间水箱传递,负载却直接作用于末端,因而需要关注多步预测与扰动响应, 而非只看单步误差。examples/three_tank/reward.py 的当前奖励为:
r_t = -|h2_next - 15| - 0.005 × q1回报越接近 0 越好。第二项是流量代价代理,不是实测电费;奖励没有单独的溢流硬约束。 仿真器对液位的夹断不等于控制器已经保证了安全。
2. 数据与准备
每一步同时记录三个水箱的液位、泵 1 指令和泵 2 负载。下一步液位是模型学习目标;负载作为外部输入单独保存,使模型能够区分主动注水和末端扰动。采集时安排液位变化与负载阶跃,便于识别过程的延迟和耦合。
默认数据 data/three_tank.npz 为 40 条轨迹 × 300 步,共 12000 个转移。 每条轨迹约 100 分钟,包含随机初值、负载阶跃,以及带参数变化和激励的比例控制行为。
| Key | 标准 Shape | 含义 |
|---|---|---|
levels | [12000, 3] | 当前液位,顺序 h1、h3、h2 |
action | [12000, 1] | 泵 1 流量 |
load | [12000, 1] | 泵 2 负载流量 |
next_levels | [12000, 3] | 执行动作后的液位 |
index | [40] | 轨迹结束下标(exclusive) |
数据直接可用于训练,无需另行派生下一状态或跨轨迹拼接。 采样策略的比例增益与设定值偏置逐轨迹重抽,叠加动作抖动并插入随机固定泵量的阶跃段。 这使日志不等同于一个固定、无激励控制器的表现,也不保证已覆盖所有控制工况。
已有数据时直接复用。只有缺失数据且允许仿真采集时,才在源码仓库根目录执行以下可选步骤:
cd examples/three_tank
python prepare_data.pyprepare_data.py 会调用数学仿真器,并覆盖输出 NPZ,不是只读预处理。 纯离线条件下应取得已有文件并跳过该命令。
数据生成参数
| 参数 | 默认值 |
|---|---|
--trajectories | 40 |
--steps | 300 |
--seed | 20260822 |
3. 建模与配置
下面先展示 examples/three_tank/config.yaml 的配置,再结合本任务逐块解释。训练命令使用同一文件。
name: three_tank
version: '2.0'
graph:
nodes:
action:
inputs: [levels]
network:
backbone: mlp
hidden_dims: [128, 128]
activation: leakyrelu
output_dist: TanhNormal
delta_levels:
inputs: [levels, action, load]
network:
backbone: mlp
hidden_dims: [256, 256]
activation: leakyrelu
output_dist: TanhNormal
next_levels:
inputs: [levels, delta_levels]
function: builtin.delta_add
transitions: auto
columns:
levels: [h1, h3, h2]
action:
- {name: pump1, min: 0.0, max: 120.0}
load: [q2]
data: {train_ratio: 0.75, batch_size: 256}
training:
device: auto
stages:
- name: venv
algorithm: venv.revive_p
hyperparameters:
epochs: 500
bc:
optimizer: {lr: 0.0003, weight_decay: 1.0e-06}
grad_clip: 50
loss: nll
adversarial:
start_epoch: 0
enabled: true
ood: {d_lr: 0.0006}
rollout: {horizon: 40, batch_size: 512}
validation:
rollout: {enabled: true, interval: 10, horizon: 40, num_trajectories: 8}
selection: {metric: val/rollout/mae, mode: min}
- name: policy
algorithm: controller.mpc
inherit_from: venv
hyperparameters:
method: cem
horizon: 25
num_samples: 128
num_elites: 16
num_iterations: 4
temperature: 1.0
gamma: 0.99
deterministic: true
use_policy_prior: false
warm_start: false
future_exogenous_keys: [load]
seed: 0
action_bounds:
action: [0.0, 120.0]
tune: {enabled: true, mode: validate_only, fast_eval_segments: 8, full_eval_segments: 12, eval_horizon: 100,
seed: 42}
policy_nodes: [action]
reward: {path: reward.py, function: get_reward}
output:
save_freq: 20
tensorboard: true
onnx: {required: true, atol: 0.001}配置入口
| 配置 | 世界模型阶段 | 控制器阶段 | 用途与运行规模 |
|---|---|---|---|
examples/three_tank/config.yaml | venv.revive_p | controller.mpc | 默认完整路线;世界模型 500 epoch,控制器验证与导出 |
examples/three_tank/configs/bc_residual_pid.yaml | venv.bc | controller.residual_pid | 对照路线;世界模型 500 epoch,最多 27 个增益组合 |
examples/three_tank/config.min.yaml | venv.revive_p | controller.mpc | 最小声明,仍有两阶段;未写参数由默认层补齐 |
两条完整路线同时改变世界模型算法和控制器,不是单因素消融。 MPC 也可以基于其他世界模型构建;本例使用 REVIVE-P 不意味着 MPC 必须配对抗训练。
config.min.yaml 未显式保留主配置的规划窗口、未来负载键、调参验证设置与导出容差, 不能当作完整配置方案的等价缩写,更不能误称“只有世界模型”。 主线使用 config.yaml;要理解默认值,可使用 revive validate --show-defaults。
配置逐块讲解
graph:观测、动作与外生负载
action ← levels 是行为动作节点,网络为两层 [128, 128] MLP; delta_levels ← levels, action, load 是动力学节点,网络为 [256, 256] MLP。 两者使用 leakyrelu 与 TanhNormal;next_levels 通过 builtin.delta_add 合成, transitions: auto 建立 levels ← next_levels。
load 没有生成节点,模型推演时从数据回放。遗漏它可能把负载变化混入未解释误差, 但误差方向、程度仍需实测。动作列的物理边界与历史数据的统计范围是不同概念, 见列与物理边界。
data:划分与批量
train_ratio: 0.75、batch_size: 256,其他划分与归一化参数采用默认层。 保留轨迹边界,并核对解析配置与报告里的实际划分;验证窗口不是新增的独立实验。
training.stages[venv]:世界模型
默认 venv.revive_p 训练 500 epoch;监督部分学习率 3e-4、loss: nll、 grad_clip: 50。对抗从第 0 轮启用,推演 horizon 40、batch_size 512。 每 10 轮在 8 个、长度 40 的验证窗口上评估,按 val/rollout/mae 的最小值选模。
BC 对照保留同样的图、数据划分和验证声明,但改为 venv.bc 的监督参数结构。 聚合多步误差有参考价值,不是任意控制动作下精度或收益的保证。
training.stages[policy]:控制器
inherit_from: venv 消费选出的世界模型。默认 MPC 用 CEM 在部署决策时规划动作, 配置为 horizon 25(约 8.3 分钟)、每轮 128 个候选、16 个精英、4 次迭代。 warm_start: false、use_policy_prior: false 保留现有配置方案行为; 虽然图中有动作网络,本配置的 CEM 不使用它作采样先验。
future_exogenous_keys: [load] 要求决策时提供未来负载。当前评估器将仿真负载真值传入规划窗, 末尾不足时重复最后值。这是“未来负载已知”的测试假设,现场只有决策当时可得的排产或预测才能替代它。
残差 PID 对照读取 levels[2],目标 15;未提供专家 baseline,采用零基线,残差本身就是泵指令。 delta_max: 120、rate_limit: 20、i_max: 60 控制输出与积分范围。 sample_dt_h: 1 在该配置方案按一个控制步计算积分/微分,不表示仿真周期是一小时。 增益网格为 kp [4, 8, 12]、ki [0.8, 2, 4]、kd [2, 8, 16]。
控制器评估规模
| 参数 | config.yaml | configs/bc_residual_pid.yaml |
|---|---|---|
tune.mode | validate_only | two_stage |
tune.max_trials | — | 27 |
tune.top_k | — | 5 |
tune.fast_eval_segments | 8 | 12 |
tune.full_eval_segments | 12 | 24 |
tune.eval_horizon | 100 | 100 |
MPC 的 validate_only 不搜索增益;PID 的 two_stage 先快速排序,再对前 top_k 个候选完整评估。 表中 — 表示没有显式配置该字段,不表示无限预算。 这些都是学到的模型中的验证,不能替代独立仿真。 reward.path 相对 YAML 所在目录解析,两条完整路线均调用本目录同一份奖励文件。
output:输出文件与导出
默认 best_only 保留最优模型及同轮续训态,tensorboard: true 记录训练指标;save_freq 仅在 legacy 模式生效。 onnx.required: true 要求导出及一致性校验,onnx.atol: 1e-3 沿用当前配置方案。 容差是否适当要结合输出单位和误差验证,不能仅凭量级断言默认容差必然不可满足。 导出通过不是控制效果验收通过。
4. 训练与选模
以下命令在 examples/three_tank 目录执行,假定已有数据。先预检,再运行完整默认路线:
revive validate --config config.yaml --data data/three_tank.npz
revive train --config config.yaml --train-data data/three_tank.npz \
--run-id full_mpc --log-dir logs --seed 42如果选择 BC + 残差 PID,使用以下替代训练命令:
revive train --config configs/bc_residual_pid.yaml --train-data data/three_tank.npz \
--run-id full_pid --log-dir logs --seed 42按 YAML 顺序执行两个阶段;查看 logs/<run_id>/report.md、config.resolved.yaml、 世界模型验证指标、控制器调参结果及 models/policy.pt。 只有模型内的排序完成,不能据此宣称已在独立对象上超越基线。
若仅检查流程,可在训练命令中加 --profile smoke 并使用新 run ID。 当前 smoke 不压缩控制器的 CEM 采样数、tune 候选数或验证窗口,仍可能耗时; 正式比较控制效果时使用完整训练配置;每次训练使用独立 run ID。
一键路线与文件
bash run_all.sh full_mpc
bash run_all.sh full_pid configs/bc_residual_pid.yaml一键路线替代上述分步操作,不要重复执行同名训练。默认仍为 config.yaml、训练 seed 42; 已有数据则复用,缺失或设置 FORCE_PREPARE 时会重新采集。 它运行一次固定基线评估、两次候选评估,后一次写 logs/<run_id>/flagship.json。 候选两次使用相同协议,不是两组新增独立样本;一键命令不是纯离线命令。 主要脚本为 examples/three_tank/prepare_data.py、examples/three_tank/evaluate.py、examples/three_tank/run_all.sh。
5. 模型使用与评估
先固定候选,分别计算三类结果:
python evaluate.py --dataset --json
python evaluate.py --baseline --episodes 20 --steps 300 --seed 20260901 --json
python evaluate.py --model logs/full_mpc/models/policy.pt \
--episodes 20 --steps 300 --seed 20260901 --json评估 PID 时将模型路径换为 logs/full_pid/models/policy.pt。 --dataset、--baseline、--model 三种模式只能选一个。 数据模式不推进仿真,回合边界以 NPZ 的 index 为准;当前汇总脚本按等长轨迹使用第一个回合长度归一, 自有变长轨迹不能直接照搬这项逐步均值。
评估协议
| 参数 | 默认值 |
|---|---|
--episodes | 20 |
--steps | 300 |
--seed | 20260901 |
基线与候选使用相同 seed 派生的初始液位和负载曲线。固定基线使用比例增益/设定偏置区间中点, 不加抖动或阶跃激励;它与日志采集时的随机混合行为不同。
real_return_mean/std 汇总每集回报;real_reward_mean_per_step 为平均每步奖励; real_tracking_mae 为全程 h2 MAE,real_settle_mae 为末 20% 步数 MAE。 框架的 val/rollout/reward_mean 对应每步量,val/rollout/return_mean 对应整段量; 即使公式一致,也要核对初态、负载、窗口长度和统计方式。
--trace 可打印第一个回合抽样的 h2/q1,用于定位控制行为,不是自动生成完整曲线报告。 外部测试用于最终核验;如果用其结果反复调参,需要另留未参与选择的测试工况。
6. 结果与分析
数据包含 40 条、每条 300 步的轨迹。固定基线和学习控制器在相同初始液位、负载序列下评估 20 集,每集 300 步,起始 seed 为 20260901。
| 指标 | 数据行为 | 固定比例基线 | BC + 残差 PID | REVIVE-P + MPC |
|---|---|---|---|---|
| 平均每步奖励 | -4.179 | -2.026 | -0.653 | -0.820 |
| 跟踪 MAE(cm) | 4.009 | 1.871 | 0.495 | 0.658 |
| 稳态 MAE(cm) | 3.982 | 1.805 | 0.380 | 0.669 |
两种学习控制器均降低了相对于固定比例基线的液位误差。残差 PID 的全程与稳态误差更小;MPC 则根据模型和未来负载规划一段泵量序列。两份方案同时涉及模型与控制器差异,应作为完整方案比较。
稳态误差按每集末 20% 步数计算。若全程误差明显大于稳态误差,可检查初始调节速度和负载切换后的恢复过程;若稳态误差仍高,则应检查积分作用、动作限制以及模型对小流量变化的敏感性。
采集行为带有随机增益和激励,固定基线没有这些激励。控制方案之间的结论以共同测试条件下的基线与候选为准。
7. 迁移到实际业务
迁移到实际液位或过程控制
先确认流量、液位、控制周期、测点与动作范围,再确认未来 q2 的可用来源。 默认只控制 q1,不是双泵解耦控制;缺少中间液位测点时应重新检查可观测性。
分别验收跟踪、超调、溢流、动作速率、积分状态隔离和推理时延。 数学模型测试见 tests/unit/test_three_tank_env.py; 工程回归门槛见 tests/perf/baselines.json,两者都不替代现场验收。
阅读任务页约定,区分数据准备、训练验证和独立评估。