use oxictl::optimal::{ControlConstraints, SingleShootingProblem};
fn dynamics(x: &[f64; 2], u: &[f64; 1]) -> [f64; 2] {
[x[1], u[0]]
}
fn stage_cost(_x: &[f64; 2], u: &[f64; 1]) -> f64 {
u[0] * u[0]
}
fn terminal_cost(x: &[f64; 2]) -> f64 {
50.0 * (x[0] * x[0] + x[1] * x[1])
}
fn main() {
println!("=== Optimal Trajectory: Minimum-Energy Double Integrator ===\n");
const M: usize = 20;
let x0 = [2.0_f64, 0.0];
let constraints = ControlConstraints::box_input([-3.0_f64], [3.0_f64]);
let mut prob: SingleShootingProblem<f64, 2, 1, M> = SingleShootingProblem::new(
dynamics,
stage_cost,
terminal_cost,
0.1_f64, constraints,
);
prob.max_iter = 600;
prob.step_size = 0.02;
prob.tol = 1e-6;
prob.ode_steps = 4;
prob.armijo_beta = 0.5;
prob.armijo_c1 = 1e-4;
let u_init = [[0.0_f64]; M];
let j_init = prob
.cost(&x0, &u_init)
.expect("Initial cost evaluation should succeed");
println!("Initial cost (u=0): {:.4}", j_init);
println!("Initial state: x = [{:.3}, {:.3}]", x0[0], x0[1]);
println!("Solving...");
let (u_opt, j_opt) = prob
.solve(&x0, &u_init)
.expect("Single shooting solver should converge");
println!("Optimal cost: {:.4}", j_opt);
println!(
"Cost reduction: {:.2}%\n",
100.0 * (j_init - j_opt) / j_init
);
let traj = prob
.trajectory(&x0, &u_opt)
.expect("Trajectory computation should succeed");
println!(
"{:>6} {:>8} {:>12} {:>12} {:>12}",
"Step", "Time(s)", "Position", "Velocity", "Control"
);
println!("{}", "-".repeat(58));
println!(
"{:>6} {:>8.2} {:>12.5} {:>12.5} {:>12.5}",
0, 0.0, x0[0], x0[1], 0.0
);
for k in 0..M {
let t = (k + 1) as f64 * 0.1;
let xk = traj[k];
let uk = u_opt[k][0];
println!(
"{:>6} {:>8.2} {:>12.5} {:>12.5} {:>12.5}",
k + 1,
t,
xk[0],
xk[1],
uk
);
}
let u_max = u_opt.iter().map(|u| u[0].abs()).fold(0.0_f64, f64::max);
let u_rms = (u_opt.iter().map(|u| u[0] * u[0]).sum::<f64>() / M as f64).sqrt();
let energy = u_opt.iter().map(|u| u[0] * u[0] * 0.1).sum::<f64>();
let final_state = traj[M - 1];
let final_norm = (final_state[0].powi(2) + final_state[1].powi(2)).sqrt();
println!("\n=== Trajectory Analysis ===");
println!(
"Final state: x = [{:.5}, {:.5}]",
final_state[0], final_state[1]
);
println!("Final state norm: {:.5}", final_norm);
println!("Max control: |u_max| = {:.4}", u_max);
println!("RMS control: {:.4}", u_rms);
println!("Total energy: ∫u² dt = {:.4}", energy);
println!("\n=== Verification ===");
if j_opt < j_init {
println!("[PASS] Optimised cost < initial cost");
} else {
println!("[FAIL] Optimised cost >= initial cost");
}
if u_max <= 3.0 + 1e-9 {
println!("[PASS] Control within bounds [-3, 3]");
} else {
println!("[FAIL] Control violates bounds: |u_max|={:.4}", u_max);
}
if final_norm < 1.0 {
println!(
"[PASS] Final state norm {:.4} < 1.0 (near origin)",
final_norm
);
} else {
println!(
"[WARN] Final state norm {:.4} >= 1.0 (consider more iterations)",
final_norm
);
}
println!("\nDone. Minimum-energy double integrator trajectory computed.");
}