feat: 添加了自动超参数整定

This commit is contained in:
Horldsence
2026-09-23 00:42:26 +08:00
parent 7d87987e41
commit 680adae339
56 changed files with 13664 additions and 245 deletions
+3
View File
@@ -11,6 +11,9 @@ control_period_ms = 1
command_timeout_ms = 200
# 电机反馈离线判定(毫秒),离线后强制 0 电流。
feedback_timeout_ms = 100
# 驱动层命令租约(毫秒):控制循环超过该时长未刷新电机命令 → 强制 0 电流
# (防控制任务死亡后旧指令持续输出)。必须 >= control_period_ms。
motor_command_timeout_ms = 50
[mecanum]
# 轮轴线到底盘中心的纵向 / 横向半距(米)与轮半径(米)。
+334
View File
@@ -0,0 +1,334 @@
# chassis-tune 超参数预设
#
# 一次整定的结果不只取决于被控对象与种子,还取决于"搜什么区间、用哪个场景、
# 什么算好、什么算达标、搜多细"。这份文件把这些**实验条件**固化下来:
# 评审一次、长期复用、任何机器上都能复现同一组默认行为。
#
# 用法:
# chassis-tune presets # 列出本文件全部预设
# chassis-tune sweep --preset config/tune.toml # 用 default_preset
# chassis-tune sweep --preset config/tune.toml --preset-name bench_loaded
# chassis-tune sweep --preset-name bench_loaded # 缺省路径即本文件
# chassis-tune sweep --preset config/tune.toml --samples 128 # CLI 覆盖预设的单项
#
# 优先级:内置默认 < 本文件的预设 < 显式命令行参数(逐字段覆盖)。
# 本文件里没写的项一律沿用内置默认,因此预设只需写"与默认不同"的部分——
# 但为了让评审者一眼看全工况,下面每个预设都写全了关键项。
#
# **安全边界**:current_limit / direction / gear_ratio / 电调 ID / 串口设备名
# 不属于超参数,本文件**不接受**这些键(写了会直接报错)。它们只来自
# chassis.toml 或显式 CLI——否则预设文件就成了绕过限流的口子。
#
# schema:见 crates/chassis-tune/src/preset.rs(version = 1,未知键报错)。
version = 1
default_preset = "sim_bench_model"
# ---------------------------------------------------------------- 离线:台架量级模型
# 默认预设。评审、CI 冒烟、改完代码回归都用它:不碰硬件,结果可复现。
[preset.sim_bench_model]
description = "离线 · 台架量级模型(静/库仑摩擦 + 2 拍延迟):全量搜索,评审与回归的基准"
[preset.sim_bench_model.space]
# kp 覆盖手调基线 2 与仿真最优 8 两侧;对数刻度保证小增益也被采样到。
kp = { min = 0.5, max = 30.0, scale = "log" }
# ki 允许精确取 0(纯比例在某些工况已经够用)。
ki = { min = 0.1, max = 100.0, scale = "log0" }
# kd 半概率取 0:速度环加微分对量化噪声敏感,先让搜索自己决定要不要。
kd = { min = 0.01, max = 3.0, scale = "log0" }
# 前馈上限 2.5 覆盖带载所需的 ≈1.77(raw/rpm)。
feedforward = { min = 0.01, max = 2.5, scale = "log0" }
[preset.sim_bench_model.scenario]
profile = "ramp-and-hold"
target_rpm = 1500.0
ramp_ms = 200.0
hold_ms = 1800.0
dt_us = 1000.0
[preset.sim_bench_model.cost]
# 跟得上、不过冲最重要;电流只作次要惩罚(省电流不是目的,避免烧电调才是)。
settle = 1.0
overshoot = 1.0
iae = 0.5
effort = 0.1
[preset.sim_bench_model.acceptance]
max_overshoot_pct = 20.0
max_ss_error_pct = 5.0
[preset.sim_bench_model.search]
seed = 20260101
samples = 512
top_k = 8
solver = "nelder-mead"
max_iters = 200
particles = 24
max_evals = 4000
threads = 0
[preset.sim_bench_model.sim]
plant = "gm3508-bench"
# 域随机化:单点模型调出来的增益对增益/时间常数/延迟高度敏感,
# 因此默认在 8 个成员的集成上做鲁棒优化,而不是对着一条理想曲线最优化。
ensemble = 8
robust_weight = 0.5
gain_pct = 0.2
tau_pct = 0.3
delay_ticks_max = 3
friction_pct = 0.5
[preset.sim_bench_model.bench]
# 离线预设不用台架,但安全包络仍写全,方便临时切 --backend bench 预演。
max_rpm = 3000.0
max_temp_c = 70
trial_timeout_ms = 6000
between_trials_ms = 500
startup_timeout_ms = 5000
continue_on_abort = false
validate_runs = 3
# ---------------------------------------------------------------- 离线:理想模型
# 无摩擦、无延迟的一阶模型:跑得快,适合改完控制逻辑后的秒级回归,
# **不要**拿它的结果直接上车(模型里没有摩擦与延迟,最优解会偏激进)。
[preset.sim_ideal]
description = "离线 · 理想一阶模型(无摩擦无延迟):秒级回归用,结果不可直接上车"
[preset.sim_ideal.space]
kp = { min = 0.5, max = 30.0, scale = "log" }
ki = { min = 0.1, max = 100.0, scale = "log0" }
kd = { min = 0.01, max = 3.0, scale = "log0" }
feedforward = { min = 0.01, max = 2.5, scale = "log0" }
[preset.sim_ideal.scenario]
profile = "ramp-and-hold"
target_rpm = 1500.0
ramp_ms = 200.0
hold_ms = 1800.0
dt_us = 1000.0
[preset.sim_ideal.cost]
settle = 1.0
overshoot = 1.0
iae = 0.5
effort = 0.1
[preset.sim_ideal.acceptance]
max_overshoot_pct = 20.0
max_ss_error_pct = 5.0
[preset.sim_ideal.search]
seed = 20260101
samples = 128
top_k = 4
solver = "nelder-mead"
max_iters = 100
particles = 16
max_evals = 1000
threads = 0
[preset.sim_ideal.sim]
plant = "gm3508"
# 理想模型本身就是确定的,单成员即可。
ensemble = 1
robust_weight = 0.0
gain_pct = 0.0
tau_pct = 0.0
delay_ticks_max = 0
friction_pct = 0.0
[preset.sim_ideal.bench]
max_rpm = 3000.0
max_temp_c = 70
trial_timeout_ms = 6000
between_trials_ms = 500
startup_timeout_ms = 5000
continue_on_abort = false
validate_runs = 3
# ---------------------------------------------------------------- 台架:空载
# 轮子架空(无地面载荷)。摩擦小、静差靠积分压制,前馈**必须为 0**:
# 空载加前馈会把轮子直接推到最高转速。目标转速也压低,便于观察与急停。
[preset.bench_noload]
description = "真机台架 · 空载(轮子架空):小样本局部搜索,禁用前馈,目标 800 rpm"
# 真机后端:仍然需要 --i-know-the-wheel-spins 才会真的驱动电机。
backend = "bench"
[preset.bench_noload.space]
# 空载好解在手调基线(kp≈2, ki≈10)附近,区间收紧以提高样本密度。
kp = { min = 0.2, max = 12.0, scale = "log" }
ki = { min = 0.1, max = 50.0, scale = "log0" }
kd = { min = 0.01, max = 1.0, scale = "log0" }
# 固定为 0:单点区间 + 线性刻度 = 搜索时该轴恒定。
feedforward = { min = 0.0, max = 0.0, scale = "linear" }
[preset.bench_noload.scenario]
profile = "ramp-and-hold"
target_rpm = 800.0
ramp_ms = 300.0
hold_ms = 1700.0
dt_us = 1000.0
[preset.bench_noload.cost]
settle = 1.0
overshoot = 1.0
iae = 0.5
effort = 0.1
[preset.bench_noload.acceptance]
# 空载量化噪声相对目标更大,放宽到 25%,避免"为了 0.5% 的稳态误差过冲"。
max_overshoot_pct = 25.0
max_ss_error_pct = 8.0
[preset.bench_noload.search]
seed = 20260101
# 只有一条总线,每次试验 2s,24 次 ≈ 1 分钟:够做局部精修,不够铺满空间。
samples = 24
top_k = 3
solver = "nelder-mead"
max_iters = 40
particles = 12
max_evals = 40
threads = 1
[preset.bench_noload.sim]
# 真机后端不用这些,但保留"最接近台架的模型",方便先离线预演再上车。
plant = "gm3508-bench"
ensemble = 8
robust_weight = 0.5
gain_pct = 0.2
tau_pct = 0.3
delay_ticks_max = 3
friction_pct = 0.5
[preset.bench_noload.bench]
# 目标是 800,过速保护留足裕量但远低于电调上限。
max_rpm = 2000.0
max_temp_c = 60
trial_timeout_ms = 6000
between_trials_ms = 500
startup_timeout_ms = 5000
continue_on_abort = false
# 同一组增益跑 3 次看重复性;离散度大说明参数贴着噪声,不该上车。
validate_runs = 3
# ---------------------------------------------------------------- 台架:带载
# 整车落地 / 配重(有地面摩擦与惯量)。静差明显,前馈开始有意义(覆盖 ≈1.77)。
[preset.bench_loaded]
description = "真机台架 · 带载(整车落地/配重):前馈区间上调,目标 1200 rpm,超调收紧"
backend = "bench"
[preset.bench_loaded.space]
kp = { min = 0.5, max = 30.0, scale = "log" }
ki = { min = 0.5, max = 100.0, scale = "log0" }
kd = { min = 0.01, max = 3.0, scale = "log0" }
# 带载需要前馈抵消摩擦:下限不为 0,让搜索必须考虑它。
feedforward = { min = 0.5, max = 2.5, scale = "log" }
[preset.bench_loaded.scenario]
profile = "ramp-and-hold"
target_rpm = 1200.0
ramp_ms = 300.0
hold_ms = 2200.0
dt_us = 1000.0
[preset.bench_loaded.cost]
settle = 1.0
overshoot = 1.0
iae = 0.5
effort = 0.1
[preset.bench_loaded.acceptance]
# 带载下超调意味着实际位移过冲,收紧到 15%。
max_overshoot_pct = 15.0
max_ss_error_pct = 5.0
[preset.bench_loaded.search]
seed = 20260101
samples = 32
top_k = 4
solver = "nelder-mead"
max_iters = 60
particles = 16
max_evals = 80
threads = 1
[preset.bench_loaded.sim]
plant = "gm3508-bench"
ensemble = 8
robust_weight = 0.5
gain_pct = 0.2
tau_pct = 0.3
delay_ticks_max = 3
friction_pct = 0.5
[preset.bench_loaded.bench]
max_rpm = 2500.0
max_temp_c = 70
trial_timeout_ms = 8000
between_trials_ms = 800
startup_timeout_ms = 5000
continue_on_abort = false
validate_runs = 3
# ---------------------------------------------------------------- 实战验收:双向
# 上车前的最后一道关:升速 → 保持 → 降速 → 保持(回零),验的是双向跟随与
# 回零能力,比单向斜坡更接近实战。样本很少(围绕现有增益做局部复算),
# 因为此时**不该**再大改增益,只想确认现有一组在两个方向上都不出问题。
[preset.field_updown]
description = "真机实战验收 · 升降速双向(回零):小样本复验,超调与稳态误差收紧"
backend = "bench"
[preset.field_updown.space]
kp = { min = 0.5, max = 30.0, scale = "log" }
ki = { min = 0.5, max = 100.0, scale = "log0" }
kd = { min = 0.01, max = 3.0, scale = "log0" }
feedforward = { min = 0.5, max = 2.5, scale = "log" }
[preset.field_updown.scenario]
profile = "up-down"
target_rpm = 1200.0
ramp_ms = 400.0
hold_ms = 800.0
dt_us = 1000.0
[preset.field_updown.cost]
# 双向场景里稳态误差(尤其回零)比调节时间更重要。
settle = 1.0
overshoot = 1.0
iae = 1.0
effort = 0.1
[preset.field_updown.acceptance]
max_overshoot_pct = 12.0
max_ss_error_pct = 3.0
[preset.field_updown.search]
seed = 20260101
samples = 16
top_k = 2
solver = "nelder-mead"
max_iters = 30
particles = 12
max_evals = 40
threads = 1
[preset.field_updown.sim]
plant = "gm3508-bench"
ensemble = 8
robust_weight = 0.5
gain_pct = 0.2
tau_pct = 0.3
delay_ticks_max = 3
friction_pct = 0.5
[preset.field_updown.bench]
max_rpm = 2500.0
max_temp_c = 70
# 双向场景总时长翻倍,超时也翻倍。
trial_timeout_ms = 10000
between_trials_ms = 1000
startup_timeout_ms = 5000
continue_on_abort = false
# 验收阶段多跑几次,重复性不过关就不签字。
validate_runs = 5