use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct WarmStart<S: ControlScalar, const M: usize, const H: usize> {
u_prev: [[S; M]; H],
pub has_solution: bool,
pub strategy: WarmStartStrategy,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WarmStartStrategy {
HoldLast,
ZeroFill,
Extrapolate,
}
impl<S: ControlScalar, const M: usize, const H: usize> WarmStart<S, M, H> {
pub fn new(strategy: WarmStartStrategy) -> Self {
Self {
u_prev: [[S::ZERO; M]; H],
has_solution: false,
strategy,
}
}
pub fn record(&mut self, u_opt: &[[S; M]; H]) {
self.u_prev = *u_opt;
self.has_solution = true;
}
pub fn shifted(&self) -> [[S; M]; H] {
if !self.has_solution {
return [[S::ZERO; M]; H];
}
let mut u_init = [[S::ZERO; M]; H];
let n = H.saturating_sub(1);
u_init[..n].copy_from_slice(&self.u_prev[1..n + 1]);
if H > 0 {
let tail = H - 1;
match self.strategy {
WarmStartStrategy::HoldLast => {
u_init[tail] = self.u_prev[H - 1];
}
WarmStartStrategy::ZeroFill => {
u_init[tail] = [S::ZERO; M];
}
WarmStartStrategy::Extrapolate => {
if H >= 2 {
u_init[tail] = core::array::from_fn(|i| {
S::TWO * self.u_prev[H - 1][i] - self.u_prev[H - 2][i]
});
} else {
u_init[tail] = self.u_prev[H - 1];
}
}
}
}
u_init
}
pub fn reset(&mut self) {
self.u_prev = [[S::ZERO; M]; H];
self.has_solution = false;
}
pub fn clip_to_bounds(u_init: &mut [[S; M]; H], u_min: &[S; M], u_max: &[S; M]) {
for uk in u_init.iter_mut() {
for (i, ui) in uk.iter_mut().enumerate() {
*ui = ui.clamp_val(u_min[i], u_max[i]);
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct MultiSolutionCache<S: ControlScalar, const M: usize, const H: usize, const C: usize> {
solutions: [[[S; M]; H]; C],
costs: [S; C],
n_stored: usize,
}
impl<S: ControlScalar, const M: usize, const H: usize, const C: usize>
MultiSolutionCache<S, M, H, C>
{
pub fn new() -> Self {
Self {
solutions: [[[S::ZERO; M]; H]; C],
costs: [S::from_f64(f64::MAX / 2.0); C],
n_stored: 0,
}
}
pub fn store(&mut self, u_seq: [[S; M]; H], cost: S) {
if self.n_stored < C {
let idx = self.n_stored;
self.solutions[idx] = u_seq;
self.costs[idx] = cost;
self.n_stored += 1;
} else {
let mut worst_idx = 0;
let mut worst_cost = self.costs[0];
for (i, &c) in self.costs.iter().enumerate().skip(1) {
if c > worst_cost {
worst_cost = c;
worst_idx = i;
}
}
if cost < worst_cost {
self.solutions[worst_idx] = u_seq;
self.costs[worst_idx] = cost;
}
}
}
pub fn best(&self) -> Option<([[S; M]; H], S)> {
if self.n_stored == 0 {
return None;
}
let mut best_idx = 0;
let mut best_cost = self.costs[0];
for (i, &c) in self.costs[..self.n_stored].iter().enumerate().skip(1) {
if c < best_cost {
best_cost = c;
best_idx = i;
}
}
Some((self.solutions[best_idx], best_cost))
}
pub fn clear(&mut self) {
self.n_stored = 0;
self.costs = [S::from_f64(f64::MAX / 2.0); C];
}
}
impl<S: ControlScalar, const M: usize, const H: usize, const C: usize> Default
for MultiSolutionCache<S, M, H, C>
{
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn warm_start_shift_hold_last() {
let mut ws = WarmStart::<f64, 1, 4>::new(WarmStartStrategy::HoldLast);
let u_opt = [[1.0], [2.0], [3.0], [4.0]];
ws.record(&u_opt);
let shifted = ws.shifted();
assert_eq!(shifted[0], [2.0]);
assert_eq!(shifted[1], [3.0]);
assert_eq!(shifted[2], [4.0]);
assert_eq!(shifted[3], [4.0]); }
#[test]
fn warm_start_shift_zero_fill() {
let mut ws = WarmStart::<f64, 1, 3>::new(WarmStartStrategy::ZeroFill);
ws.record(&[[1.0], [2.0], [3.0]]);
let shifted = ws.shifted();
assert_eq!(shifted[0], [2.0]);
assert_eq!(shifted[1], [3.0]);
assert_eq!(shifted[2], [0.0]); }
#[test]
fn warm_start_extrapolate() {
let mut ws = WarmStart::<f64, 1, 4>::new(WarmStartStrategy::Extrapolate);
ws.record(&[[1.0], [2.0], [3.0], [4.0]]);
let shifted = ws.shifted();
assert!(
(shifted[3][0] - 5.0).abs() < 1e-10,
"tail={:.4}",
shifted[3][0]
);
}
#[test]
fn warm_start_no_solution_returns_zeros() {
let ws = WarmStart::<f64, 2, 3>::new(WarmStartStrategy::HoldLast);
let shifted = ws.shifted();
for row in &shifted {
assert_eq!(*row, [0.0, 0.0]);
}
}
#[test]
fn multi_cache_best_solution() {
let mut cache = MultiSolutionCache::<f64, 1, 2, 3>::new();
cache.store([[1.0], [2.0]], 10.0);
cache.store([[3.0], [4.0]], 5.0);
cache.store([[5.0], [6.0]], 15.0);
let (sol, cost) = cache.best().unwrap();
assert!((cost - 5.0).abs() < 1e-10);
assert_eq!(sol[0], [3.0]);
}
#[test]
fn multi_cache_replaces_worst() {
let mut cache = MultiSolutionCache::<f64, 1, 1, 2>::new();
cache.store([[0.0]], 100.0);
cache.store([[1.0]], 50.0);
cache.store([[2.0]], 1.0); let (_, cost) = cache.best().unwrap();
assert!((cost - 1.0).abs() < 1e-10);
}
#[test]
fn clip_to_bounds() {
let mut u = [[5.0_f64], [-5.0]];
WarmStart::<f64, 1, 2>::clip_to_bounds(&mut u, &[-2.0], &[2.0]);
assert_eq!(u[0], [2.0]);
assert_eq!(u[1], [-2.0]);
}
}