跳转到内容

三容水箱 ​

本例使用三容水箱的液位与流量数据学习过程模型,再构建 MPC 或残差 PID 控制器,使末端液位跟踪 15 cm 目标,并抑制负载扰动。

1. 任务背景与目标 ​

连通水箱为什么存在延迟与耦合 ​

三容水箱用于模拟相连容器的液位控制。三个水箱按 1 → 3 → 2 连通,连通管的水流由两端水头差决定。泵 1 向水箱 1 注水,控制目标却是末端水箱 2 的液位,因此动作需要通过中间水箱逐级传递。

当末端液位偏低时,增加泵 1 流量并不会立即抬高末端液位;如果在响应尚未到达时持续加大泵量,随后可能出现超调。泵 2 对末端施加独立负载,又会使相同泵 1 指令产生不同结果。这要求控制器同时考虑当前三箱液位、负载变化和过程延迟。

三个液位共同描述水的储存与传递状态。只记录末端液位,会丢失上游已经积累了多少水的信息;遗漏泵 2 负载,则会把负载变化误当作模型误差。后面的数据字段和决策流图据此保留 levels、action 和 load。

液位目标与动作范围 ​

水箱按 1、3、2 的顺序连通,泵1流量为控制量,泵2负载直接进入末端水箱2,目标液位 h2 为15厘米

图:装置连接与变量对应关系,图形未按比例绘制;连通管流向取决于水头差。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 的当前奖励为:

text
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)

数据直接可用于训练,无需另行派生下一状态或跨轨迹拼接。 采样策略的比例增益与设定值偏置逐轨迹重抽,叠加动作抖动并插入随机固定泵量的阶跃段。 这使日志不等同于一个固定、无激励控制器的表现,也不保证已覆盖所有控制工况。

已有数据时直接复用。只有缺失数据且允许仿真采集时,才在源码仓库根目录执行以下可选步骤:

bash
cd examples/three_tank
python prepare_data.py

prepare_data.py 会调用数学仿真器,并覆盖输出 NPZ,不是只读预处理。 纯离线条件下应取得已有文件并跳过该命令。

数据生成参数 ​

参数默认值
--trajectories40
--steps300
--seed20260822

3. 建模与配置 ​

下面先展示 examples/three_tank/config.yaml 的配置,再结合本任务逐块解释。训练命令使用同一文件。

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.yamlvenv.revive_pcontroller.mpc默认完整路线;世界模型 500 epoch,控制器验证与导出
examples/three_tank/configs/bc_residual_pid.yamlvenv.bccontroller.residual_pid对照路线;世界模型 500 epoch,最多 27 个增益组合
examples/three_tank/config.min.yamlvenv.revive_pcontroller.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.yamlconfigs/bc_residual_pid.yaml
tune.modevalidate_onlytwo_stage
tune.max_trials—27
tune.top_k—5
tune.fast_eval_segments812
tune.full_eval_segments1224
tune.eval_horizon100100

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 目录执行,假定已有数据。先预检,再运行完整默认路线:

bash
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,使用以下替代训练命令:

bash
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
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. 模型使用与评估 ​

先固定候选,分别计算三类结果:

bash
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 为准;当前汇总脚本按等长轨迹使用第一个回合长度归一, 自有变长轨迹不能直接照搬这项逐步均值。

评估协议 ​

参数默认值
--episodes20
--steps300
--seed20260901

基线与候选使用相同 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 + 残差 PIDREVIVE-P + MPC
平均每步奖励-4.179-2.026-0.653-0.820
跟踪 MAE(cm)4.0091.8710.4950.658
稳态 MAE(cm)3.9821.8050.3800.669

两种学习控制器均降低了相对于固定比例基线的液位误差。残差 PID 的全程与稳态误差更小;MPC 则根据模型和未来负载规划一段泵量序列。两份方案同时涉及模型与控制器差异,应作为完整方案比较。

稳态误差按每集末 20% 步数计算。若全程误差明显大于稳态误差,可检查初始调节速度和负载切换后的恢复过程;若稳态误差仍高,则应检查积分作用、动作限制以及模型对小流量变化的敏感性。

采集行为带有随机增益和激励,固定基线没有这些激励。控制方案之间的结论以共同测试条件下的基线与候选为准。

7. 迁移到实际业务 ​

迁移到实际液位或过程控制 ​

先确认流量、液位、控制周期、测点与动作范围,再确认未来 q2 的可用来源。 默认只控制 q1,不是双泵解耦控制;缺少中间液位测点时应重新检查可观测性。

分别验收跟踪、超调、溢流、动作速率、积分状态隔离和推理时延。 数学模型测试见 tests/unit/test_three_tank_env.py; 工程回归门槛见 tests/perf/baselines.json,两者都不替代现场验收。

阅读任务页约定,区分数据准备、训练验证和独立评估。