use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct HorizonConfig {
pub prediction: usize,
pub control: usize,
pub dt: f64,
}
impl HorizonConfig {
pub fn new(horizon: usize, dt: f64) -> Self {
Self {
prediction: horizon,
control: horizon,
dt,
}
}
pub fn with_control_horizon(prediction: usize, control: usize, dt: f64) -> Self {
let control = control.min(prediction);
Self {
prediction,
control,
dt,
}
}
pub fn prediction_time_s(&self) -> f64 {
self.prediction as f64 * self.dt
}
pub fn control_time_s(&self) -> f64 {
self.control as f64 * self.dt
}
pub fn tail_steps(&self) -> usize {
self.prediction.saturating_sub(self.control)
}
}
#[derive(Debug, Clone, Copy)]
pub struct VariableHorizon {
pub max_horizon: usize,
pub current: usize,
pub shrinking: bool,
pub steps_remaining: usize,
}
impl VariableHorizon {
pub fn receding(horizon: usize) -> Self {
Self {
max_horizon: horizon,
current: horizon,
shrinking: false,
steps_remaining: 0,
}
}
pub fn shrinking(max_horizon: usize, batch_steps: usize) -> Self {
let current = max_horizon.min(batch_steps);
Self {
max_horizon,
current,
shrinking: true,
steps_remaining: batch_steps,
}
}
pub fn step(&mut self) {
if self.shrinking && self.steps_remaining > 0 {
self.steps_remaining -= 1;
self.current = self.max_horizon.min(self.steps_remaining);
}
}
pub fn is_complete(&self) -> bool {
self.shrinking && self.steps_remaining == 0
}
pub fn reset(&mut self, batch_steps: usize) {
self.steps_remaining = batch_steps;
self.current = self.max_horizon.min(batch_steps);
}
}
#[derive(Debug, Clone, Copy)]
pub enum HorizonReference<S: ControlScalar, const N: usize, const H: usize> {
Constant([S; N]),
Step {
before: [S; N],
after: [S; N],
k_step: usize,
},
Linear { start: [S; N], end: [S; N] },
}
impl<S: ControlScalar, const N: usize, const H: usize> HorizonReference<S, N, H> {
pub fn at(&self, k: usize) -> [S; N] {
match *self {
Self::Constant(r) => r,
Self::Step {
before,
after,
k_step,
} => {
if k < k_step {
before
} else {
after
}
}
Self::Linear { start, end } => {
if H == 0 || H == 1 {
return end;
}
let t = S::from_f64(k as f64 / (H - 1) as f64);
core::array::from_fn(|i| start[i] + t * (end[i] - start[i]))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn horizon_config_times() {
let h = HorizonConfig::with_control_horizon(10, 5, 0.1);
assert_eq!(h.prediction, 10);
assert_eq!(h.control, 5);
assert!((h.prediction_time_s() - 1.0).abs() < 1e-10);
assert!((h.control_time_s() - 0.5).abs() < 1e-10);
assert_eq!(h.tail_steps(), 5);
}
#[test]
fn horizon_control_clamped_to_prediction() {
let h = HorizonConfig::with_control_horizon(5, 10, 0.1);
assert_eq!(h.control, 5); }
#[test]
fn receding_horizon_no_change() {
let mut h = VariableHorizon::receding(10);
h.step();
h.step();
assert_eq!(h.current, 10); }
#[test]
fn shrinking_horizon_decreases() {
let mut h = VariableHorizon::shrinking(5, 8);
assert_eq!(h.current, 5);
h.step(); h.step(); h.step(); h.step(); assert_eq!(h.current, 4);
}
#[test]
fn shrinking_horizon_completes() {
let mut h = VariableHorizon::shrinking(3, 3);
for _ in 0..3 {
h.step();
}
assert!(h.is_complete());
}
#[test]
fn reference_constant() {
let r = HorizonReference::<f64, 2, 10>::Constant([1.0, 2.0]);
assert_eq!(r.at(0), [1.0, 2.0]);
assert_eq!(r.at(9), [1.0, 2.0]);
}
#[test]
fn reference_step_change() {
let r = HorizonReference::<f64, 1, 10>::Step {
before: [0.0],
after: [5.0],
k_step: 5,
};
assert_eq!(r.at(4), [0.0]);
assert_eq!(r.at(5), [5.0]);
}
#[test]
fn reference_linear_endpoints() {
let r = HorizonReference::<f64, 1, 11>::Linear {
start: [0.0],
end: [10.0],
};
assert!((r.at(0)[0] - 0.0).abs() < 1e-10);
assert!((r.at(10)[0] - 10.0).abs() < 1e-10);
assert!((r.at(5)[0] - 5.0).abs() < 1e-10);
}
}