use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BacksteppingError {
NonPositiveGain,
ZeroInputGain,
NonPositiveDt,
}
impl core::fmt::Display for BacksteppingError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
BacksteppingError::NonPositiveGain => {
f.write_str("all backstepping gains c_i must be positive")
}
BacksteppingError::ZeroInputGain => {
f.write_str("virtual input gain g_i must be non-zero")
}
BacksteppingError::NonPositiveDt => f.write_str("dt must be positive"),
}
}
}
pub struct SecondOrderBackstepping<S: ControlScalar> {
c1: S,
c2: S,
g1: S,
g2: S,
dt: S,
alpha1_prev: S,
has_prev: bool,
}
impl<S: ControlScalar> SecondOrderBackstepping<S> {
pub fn new(c1: S, c2: S, g1: S, g2: S, dt: S) -> Result<Self, BacksteppingError> {
if c1 <= S::ZERO || c2 <= S::ZERO {
return Err(BacksteppingError::NonPositiveGain);
}
if g1 == S::ZERO || g2 == S::ZERO {
return Err(BacksteppingError::ZeroInputGain);
}
if dt <= S::ZERO {
return Err(BacksteppingError::NonPositiveDt);
}
Ok(Self {
c1,
c2,
g1,
g2,
dt,
alpha1_prev: S::ZERO,
has_prev: false,
})
}
pub fn reset(&mut self) {
self.alpha1_prev = S::ZERO;
self.has_prev = false;
}
pub fn virtual_control(&self, x1: S, x1_ref: S, dx1_ref: S, f1: S) -> S {
let z1 = x1 - x1_ref;
(-f1 + dx1_ref - self.c1 * z1) / self.g1
}
pub fn update(&mut self, x1: S, x2: S, x1_ref: S, dx1_ref: S, f1: S, f2: S) -> S {
let alpha1 = self.virtual_control(x1, x1_ref, dx1_ref, f1);
let dalpha1 = if self.has_prev {
(alpha1 - self.alpha1_prev) / self.dt
} else {
S::ZERO
};
self.alpha1_prev = alpha1;
self.has_prev = true;
let z1 = x1 - x1_ref;
let z2 = x2 - alpha1;
(-f2 + dalpha1 - self.c2 * z2 - self.g1 * z1) / self.g2
}
}
pub struct ThirdOrderBackstepping<S: ControlScalar> {
c1: S,
c2: S,
c3: S,
g1: S,
g2: S,
g3: S,
dt: S,
alpha1_prev: S,
alpha2_prev: S,
has_prev: bool,
}
impl<S: ControlScalar> ThirdOrderBackstepping<S> {
#[allow(clippy::too_many_arguments)]
pub fn new(c1: S, c2: S, c3: S, g1: S, g2: S, g3: S, dt: S) -> Result<Self, BacksteppingError> {
if c1 <= S::ZERO || c2 <= S::ZERO || c3 <= S::ZERO {
return Err(BacksteppingError::NonPositiveGain);
}
if g1 == S::ZERO || g2 == S::ZERO || g3 == S::ZERO {
return Err(BacksteppingError::ZeroInputGain);
}
if dt <= S::ZERO {
return Err(BacksteppingError::NonPositiveDt);
}
Ok(Self {
c1,
c2,
c3,
g1,
g2,
g3,
dt,
alpha1_prev: S::ZERO,
alpha2_prev: S::ZERO,
has_prev: false,
})
}
pub fn reset(&mut self) {
self.alpha1_prev = S::ZERO;
self.alpha2_prev = S::ZERO;
self.has_prev = false;
}
#[allow(clippy::too_many_arguments)]
pub fn update(&mut self, x1: S, x2: S, x3: S, x1_ref: S, dx1_ref: S, f1: S, f2: S, f3: S) -> S {
let z1 = x1 - x1_ref;
let alpha1 = (-f1 + dx1_ref - self.c1 * z1) / self.g1;
let z2 = x2 - alpha1;
let dalpha1 = if self.has_prev {
(alpha1 - self.alpha1_prev) / self.dt
} else {
S::ZERO
};
let alpha2 = (-f2 + dalpha1 - self.c2 * z2 - self.g1 * z1) / self.g2;
let z3 = x3 - alpha2;
let dalpha2 = if self.has_prev {
(alpha2 - self.alpha2_prev) / self.dt
} else {
S::ZERO
};
let u = (-f3 + dalpha2 - self.c3 * z3 - self.g2 * z2) / self.g3;
self.alpha1_prev = alpha1;
self.alpha2_prev = alpha2;
self.has_prev = true;
u
}
}
#[derive(Debug, Clone, Copy)]
pub struct IntegratorChainBackstepping<S: ControlScalar, const N: usize> {
gains: [S; N],
}
impl<S: ControlScalar, const N: usize> IntegratorChainBackstepping<S, N> {
pub fn new(gains: [S; N]) -> Result<Self, BacksteppingError> {
for &g in &gains {
if g <= S::ZERO {
return Err(BacksteppingError::NonPositiveGain);
}
}
Ok(Self { gains })
}
}
impl<S: ControlScalar> IntegratorChainBackstepping<S, 2> {
pub fn control(&self, x: &[S; 2], x1_ref: S, dx1_ref: S) -> S {
let c1 = self.gains[0];
let c2 = self.gains[1];
let z1 = x[0] - x1_ref;
let alpha1 = dx1_ref - c1 * z1;
let z2 = x[1] - alpha1;
-c2 * z2 - z1
}
}
impl<S: ControlScalar> IntegratorChainBackstepping<S, 3> {
pub fn control(&self, x: &[S; 3], x1_ref: S, dx1_ref: S, alpha1_dot: S) -> S {
let c1 = self.gains[0];
let c2 = self.gains[1];
let c3 = self.gains[2];
let z1 = x[0] - x1_ref;
let alpha1 = dx1_ref - c1 * z1;
let z2 = x[1] - alpha1;
let alpha2 = alpha1_dot - c2 * z2 - z1;
let z3 = x[2] - alpha2;
-c3 * z3 - z2
}
}
#[cfg(test)]
mod tests {
use super::*;
const DT: f64 = 0.001;
fn step_double_integrator(state: [f64; 2], u: f64) -> [f64; 2] {
[state[0] + DT * state[1], state[1] + DT * u]
}
fn step_triple_integrator(state: [f64; 3], u: f64) -> [f64; 3] {
[
state[0] + DT * state[1],
state[1] + DT * state[2],
state[2] + DT * u,
]
}
#[test]
fn backstepping_2nd_order_invalid_gains() {
assert!(SecondOrderBackstepping::<f64>::new(0.0, 2.0, 1.0, 1.0, DT).is_err());
assert!(SecondOrderBackstepping::<f64>::new(1.0, 0.0, 1.0, 1.0, DT).is_err());
assert!(SecondOrderBackstepping::<f64>::new(1.0, 2.0, 0.0, 1.0, DT).is_err());
assert!(SecondOrderBackstepping::<f64>::new(1.0, 2.0, 1.0, 0.0, DT).is_err());
}
#[test]
fn backstepping_2nd_order_stabilises_double_integrator() {
let mut ctrl =
SecondOrderBackstepping::<f64>::new(5.0, 5.0, 1.0, 1.0, DT).expect("valid params");
let r = 1.0_f64;
let mut state = [0.0_f64; 2];
for _ in 0..4000 {
let u = ctrl.update(state[0], state[1], r, 0.0, 0.0, 0.0);
state = step_double_integrator(state, u);
}
assert!(
(state[0] - r).abs() < 0.05,
"x₁={:.4} should converge to r={}",
state[0],
r
);
}
#[test]
fn backstepping_3rd_order_stabilises_triple_integrator() {
let mut ctrl = ThirdOrderBackstepping::<f64>::new(4.0, 4.0, 4.0, 1.0, 1.0, 1.0, DT)
.expect("valid params");
let r = 1.0_f64;
let mut state = [0.0_f64; 3];
for _ in 0..8000 {
let u = ctrl.update(state[0], state[1], state[2], r, 0.0, 0.0, 0.0, 0.0);
state = step_triple_integrator(state, u);
}
assert!(
(state[0] - r).abs() < 0.05,
"x₁={:.4} should converge to r={}",
state[0],
r
);
}
#[test]
fn integrator_chain_2nd_order_converges() {
let ctrl = IntegratorChainBackstepping::<f64, 2>::new([3.0, 5.0]).expect("valid gains");
let r = 2.0_f64;
let mut state = [0.0_f64; 2];
for _ in 0..5000 {
let u = ctrl.control(&state, r, 0.0);
state = step_double_integrator(state, u);
}
assert!(
(state[0] - r).abs() < 0.1,
"x₁={:.4} should track r={}",
state[0],
r
);
}
#[test]
fn integrator_chain_3rd_order_converges() {
let ctrl =
IntegratorChainBackstepping::<f64, 3>::new([3.0, 4.0, 5.0]).expect("valid gains");
let r = 1.5_f64;
let mut state = [0.0_f64; 3];
let mut alpha1_prev = 0.0_f64;
for _ in 0..10000 {
let c1 = 3.0_f64;
let z1 = state[0] - r;
let alpha1 = -c1 * z1; let alpha1_dot = (alpha1 - alpha1_prev) / DT;
alpha1_prev = alpha1;
let u = ctrl.control(&state, r, 0.0, alpha1_dot);
state = step_triple_integrator(state, u);
}
assert!(
(state[0] - r).abs() < 0.1,
"x₁={:.4} should track r={}",
state[0],
r
);
}
#[test]
fn integrator_chain_zero_gain_rejected() {
let res = IntegratorChainBackstepping::<f64, 2>::new([3.0, 0.0]);
assert!(res.is_err());
}
#[test]
fn third_order_backstepping_invalid() {
assert!(ThirdOrderBackstepping::<f64>::new(-1.0, 2.0, 2.0, 1.0, 1.0, 1.0, DT).is_err());
assert!(ThirdOrderBackstepping::<f64>::new(1.0, 2.0, 2.0, 0.0, 1.0, 1.0, DT).is_err());
}
#[test]
fn second_order_backstepping_reset() {
let mut ctrl = SecondOrderBackstepping::<f64>::new(2.0, 3.0, 1.0, 1.0, DT).expect("valid");
for _ in 0..10 {
let _ = ctrl.update(0.5, 0.1, 1.0, 0.0, 0.0, 0.0);
}
ctrl.reset();
assert!(!ctrl.has_prev);
}
}