use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct BoxConstraint<S: ControlScalar> {
pub min: S,
pub max: S,
}
impl<S: ControlScalar> BoxConstraint<S> {
pub fn new(min: S, max: S) -> Self {
Self { min, max }
}
pub fn unconstrained() -> Self {
Self {
min: -S::from_f64(f64::MAX / 2.0),
max: S::from_f64(f64::MAX / 2.0),
}
}
pub fn clamp(&self, v: S) -> S {
v.clamp_val(self.min, self.max)
}
pub fn is_satisfied(&self, v: S) -> bool {
v >= self.min && v <= self.max
}
}
#[derive(Debug, Clone, Copy)]
pub struct InputConstraints<S: ControlScalar, const M: usize> {
pub bounds: [BoxConstraint<S>; M],
pub delta_bounds: [BoxConstraint<S>; M],
}
impl<S: ControlScalar, const M: usize> InputConstraints<S, M> {
pub fn new(min: S, max: S, delta_min: S, delta_max: S) -> Self {
Self {
bounds: core::array::from_fn(|_| BoxConstraint::new(min, max)),
delta_bounds: core::array::from_fn(|_| BoxConstraint::new(delta_min, delta_max)),
}
}
pub fn apply(&self, u: &mut [S; M], u_prev: &[S; M]) {
for i in 0..M {
let du = u[i] - u_prev[i];
let du_clamped = self.delta_bounds[i].clamp(du);
u[i] = u_prev[i] + du_clamped;
u[i] = self.bounds[i].clamp(u[i]);
}
}
pub fn all_satisfied(&self, u: &[S; M], u_prev: &[S; M]) -> bool {
for i in 0..M {
if !self.bounds[i].is_satisfied(u[i]) {
return false;
}
let du = u[i] - u_prev[i];
if !self.delta_bounds[i].is_satisfied(du) {
return false;
}
}
true
}
}
#[derive(Debug, Clone, Copy)]
pub struct StateConstraints<S: ControlScalar, const N: usize> {
pub bounds: [BoxConstraint<S>; N],
}
impl<S: ControlScalar, const N: usize> StateConstraints<S, N> {
pub fn new(min: S, max: S) -> Self {
Self {
bounds: core::array::from_fn(|_| BoxConstraint::new(min, max)),
}
}
pub fn is_feasible(&self, x: &[S; N]) -> bool {
x.iter()
.enumerate()
.all(|(i, &xi)| self.bounds[i].is_satisfied(xi))
}
pub fn project(&self, x: &mut [S; N]) {
for (xi, bound) in x.iter_mut().zip(self.bounds.iter()) {
*xi = bound.clamp(*xi);
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct OutputConstraints<S: ControlScalar, const P: usize> {
pub bounds: [BoxConstraint<S>; P],
}
impl<S: ControlScalar, const P: usize> OutputConstraints<S, P> {
pub fn new(min: S, max: S) -> Self {
Self {
bounds: core::array::from_fn(|_| BoxConstraint::new(min, max)),
}
}
pub fn is_feasible(&self, y: &[S; P]) -> bool {
y.iter()
.enumerate()
.all(|(i, &yi)| self.bounds[i].is_satisfied(yi))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn box_constraint_clamp() {
let c = BoxConstraint::new(-1.0_f64, 1.0);
assert_eq!(c.clamp(2.0), 1.0);
assert_eq!(c.clamp(-3.0), -1.0);
assert_eq!(c.clamp(0.5), 0.5);
}
#[test]
fn input_constraints_rate_and_abs() {
let ic = InputConstraints::<f64, 2>::new(-10.0, 10.0, -2.0, 2.0);
let u_prev = [0.0_f64; 2];
let mut u = [5.0_f64, -15.0]; ic.apply(&mut u, &u_prev);
assert!((u[0] - 2.0).abs() < 1e-10, "u[0]={}", u[0]);
assert!((u[1] - (-2.0)).abs() < 1e-10, "u[1]={}", u[1]);
}
#[test]
fn state_constraints_feasibility() {
let sc = StateConstraints::<f64, 3>::new(-5.0, 5.0);
assert!(sc.is_feasible(&[1.0, -2.0, 3.0]));
assert!(!sc.is_feasible(&[6.0, 0.0, 0.0]));
}
#[test]
fn state_projection() {
let sc = StateConstraints::<f64, 2>::new(-1.0, 1.0);
let mut x = [3.0_f64, -4.0];
sc.project(&mut x);
assert_eq!(x[0], 1.0);
assert_eq!(x[1], -1.0);
}
}