use ferrotherm::mppi::{Lqr, Mppi, System};
fn score(m: &Mppi, s: &System, seeds: &[u64]) -> (f64, f64, f64) {
let mut v: Vec<f64> = seeds.iter().map(|&sd| m.run(s, 1.0, 200, sd)).collect();
v.sort_by(|a, b| a.partial_cmp(b).unwrap());
(v[v.len() / 2], v[0], v[v.len() - 1])
}
fn main() {
for (label, s) in [
("stable a=0.9", System { a: 0.9, b: 1.0, q: 1.0, r: 0.5 }),
("unstable a=1.1", System { a: 1.1, b: 1.0, q: 1.0, r: 0.5 }),
] {
let l = Lqr::solve(&s);
let opt = l.cost_to_go(1.0);
println!("\n=== {label} ===");
println!("exact optimum (Riccati): {opt:.4}\n");
let seeds: Vec<u64> = (1..=7).collect();
let mut rows: Vec<(usize, usize, f64, f64)> = Vec::new();
for &k in &[50usize, 200, 800, 3200] {
for &it in &[1usize, 2, 4, 8, 16] {
let m = Mppi { horizon: 5, rollouts: k, sigma: 0.2, lambda: 0.3, iters: it };
let (med, lo, hi) = score(&m, &s, &seeds);
rows.push((k, it, (med - opt) / opt * 100.0, (hi - lo) / opt * 100.0));
}
}
println!("{:>9} {:>6} {:>10} {:>9} {:>12} {:>12}",
"rollouts", "iters", "excess %", "spread %", "total draws", "seq depth");
for &(k, it, ex, sp) in &rows {
println!("{k:>9} {it:>6} {ex:>9.2}% {sp:>8.2}% {:>12} {it:>12}", k * it);
}
println!("\n target min total draws (cfg) min seq depth (cfg)");
for &target in &[25.0f64, 15.0, 10.0] {
let ok: Vec<_> = rows.iter().filter(|r| r.2 <= target).collect();
if ok.is_empty() {
println!(" <={target:>4.0}% not reached in this sweep not reached");
continue;
}
let by_draws = ok.iter().min_by_key(|r| r.0 * r.1).unwrap();
let by_depth = ok.iter().min_by_key(|r| r.1).unwrap();
println!(" <={:>4.0}% {:>8} ({} x {}) {:>3} ({} x {})",
target, by_draws.0 * by_draws.1, by_draws.0, by_draws.1,
by_depth.1, by_depth.0, by_depth.1);
}
println!("\n holding one knob fixed:");
let at = |k: usize, it: usize| rows.iter().find(|r| r.0 == k && r.1 == it).map(|r| r.2).unwrap();
println!(" rollouts 50 -> 3200 at iters=1 : {:>6.2}% -> {:>6.2}% ({:>5.1} pts)",
at(50, 1), at(3200, 1), at(50, 1) - at(3200, 1));
println!(" iters 1 -> 16 at rollouts=50 : {:>6.2}% -> {:>6.2}% ({:>5.1} pts)",
at(50, 1), at(50, 16), at(50, 1) - at(50, 16));
println!(" iters 1 -> 16 at rollouts=3200 : {:>6.2}% -> {:>6.2}% ({:>5.1} pts)",
at(3200, 1), at(3200, 16), at(3200, 1) - at(3200, 16));
}
println!("\nRollouts are independent and divide across lanes. Iters are a chain and do not.");
println!("Whichever knob carries the accuracy is the one that decides what hardware can buy.");
}