use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SuperTwistingError {
InvalidGain,
InvalidDt,
}
impl core::fmt::Display for SuperTwistingError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
SuperTwistingError::InvalidGain => {
f.write_str("super-twisting gains k1 and k2 must be strictly positive")
}
SuperTwistingError::InvalidDt => {
f.write_str("sampling period dt must be strictly positive")
}
}
}
}
#[inline]
fn sign<S: ControlScalar>(x: S) -> S {
if x > S::ZERO {
S::ONE
} else if x < S::ZERO {
-S::ONE
} else {
S::ZERO
}
}
#[derive(Debug, Clone, Copy)]
pub struct SuperTwistingController<S: ControlScalar> {
k1: S,
k2: S,
v: S,
dt: S,
sigma_prev: S,
}
impl<S: ControlScalar> SuperTwistingController<S> {
pub fn new(k1: S, k2: S, dt: S) -> Result<Self, SuperTwistingError> {
if k1 <= S::ZERO || k2 <= S::ZERO {
return Err(SuperTwistingError::InvalidGain);
}
if dt <= S::ZERO {
return Err(SuperTwistingError::InvalidDt);
}
Ok(Self {
k1,
k2,
v: S::ZERO,
dt,
sigma_prev: S::ZERO,
})
}
pub fn update(&mut self, sigma: S) -> Result<S, SuperTwistingError> {
let half = S::from_f64(0.5);
let abs_sigma = sigma.abs();
let sqrt_sigma = abs_sigma.powf(half);
let u = -self.k1 * sqrt_sigma * sign(sigma) + self.v;
self.v -= self.k2 * sign(sigma) * self.dt;
self.sigma_prev = sigma;
Ok(u)
}
pub fn reset(&mut self) {
self.v = S::ZERO;
self.sigma_prev = S::ZERO;
}
pub fn integral_state(&self) -> S {
self.v
}
pub fn sigma_prev(&self) -> S {
self.sigma_prev
}
}
#[derive(Debug, Clone, Copy)]
pub struct AdaptiveSuperTwisting<S: ControlScalar> {
base: SuperTwistingController<S>,
k1_adapt: S,
k1_dot: S,
epsilon: S,
k1_min: S,
}
impl<S: ControlScalar> AdaptiveSuperTwisting<S> {
pub fn new(
k1_init: S,
k2: S,
k1_dot: S,
epsilon: S,
dt: S,
) -> Result<Self, SuperTwistingError> {
if k1_init <= S::ZERO || k2 <= S::ZERO || k1_dot <= S::ZERO || epsilon <= S::ZERO {
return Err(SuperTwistingError::InvalidGain);
}
if dt <= S::ZERO {
return Err(SuperTwistingError::InvalidDt);
}
let base = SuperTwistingController::new(k1_init, k2, dt)?;
Ok(Self {
base,
k1_adapt: k1_init,
k1_dot,
epsilon,
k1_min: k1_init,
})
}
pub fn update(&mut self, sigma: S) -> Result<S, SuperTwistingError> {
if sigma.abs() > self.epsilon {
self.k1_adapt += self.k1_dot * self.base.dt;
} else {
let candidate = self.k1_adapt - self.k1_dot * self.base.dt;
self.k1_adapt = if candidate > self.k1_min {
candidate
} else {
self.k1_min
};
}
self.base.k1 = self.k1_adapt;
self.base.update(sigma)
}
pub fn adaptive_gain(&self) -> S {
self.k1_adapt
}
pub fn reset(&mut self) {
self.base.reset();
self.k1_adapt = self.k1_min;
self.base.k1 = self.k1_min;
}
}
#[cfg(test)]
mod tests {
use super::*;
const DT: f64 = 0.001;
#[test]
fn zero_sigma_zero_output() {
let mut ctrl = SuperTwistingController::<f64>::new(2.0, 5.0, DT).expect("valid params");
let u = ctrl.update(0.0).expect("update ok");
assert!(u.abs() < 1e-14, "u should be zero for sigma=0, got {}", u);
}
#[test]
fn nonzero_sigma_nonzero_output() {
let mut ctrl = SuperTwistingController::<f64>::new(2.0, 5.0, DT).expect("valid params");
let u = ctrl.update(1.0).expect("update ok");
assert!(
u.abs() > 1e-10,
"u should be non-zero for sigma=1, got {}",
u
);
}
#[test]
fn invalid_k1_returns_error() {
let res = SuperTwistingController::<f64>::new(0.0, 5.0, DT);
assert!(
matches!(res, Err(SuperTwistingError::InvalidGain)),
"expected InvalidGain, got {:?}",
res.err()
);
}
#[test]
fn invalid_dt_returns_error() {
let res = SuperTwistingController::<f64>::new(2.0, 5.0, 0.0);
assert!(
matches!(res, Err(SuperTwistingError::InvalidDt)),
"expected InvalidDt, got {:?}",
res.err()
);
}
#[test]
fn integral_state_decreases_for_positive_sigma() {
let mut ctrl = SuperTwistingController::<f64>::new(2.0, 5.0, DT).expect("valid params");
let v0 = ctrl.integral_state();
ctrl.update(1.0).expect("update ok");
let v1 = ctrl.integral_state();
assert!(
v1 < v0,
"v should decrease for positive sigma: v0={}, v1={}",
v0,
v1
);
}
#[test]
fn formula_verification_unit_sigma() {
let k1 = 3.0_f64;
let k2 = 5.0_f64;
let mut ctrl = SuperTwistingController::<f64>::new(k1, k2, DT).expect("valid params");
let u = ctrl.update(1.0).expect("update ok");
let expected = -k1; assert!(
(u - expected).abs() < 1e-12,
"expected u={}, got u={}",
expected,
u
);
}
#[test]
fn adaptive_gain_grows_with_large_sigma() {
let mut ctrl =
AdaptiveSuperTwisting::<f64>::new(1.0, 5.0, 0.5, 0.1, DT).expect("valid params");
let g0 = ctrl.adaptive_gain();
for _ in 0..100 {
ctrl.update(10.0).expect("update ok");
}
let g1 = ctrl.adaptive_gain();
assert!(
g1 > g0,
"gain should grow with large sigma: g0={}, g1={}",
g0,
g1
);
}
#[test]
fn sta_drives_sigma_to_zero() {
let k1 = 3.0_f64;
let k2 = 5.0_f64;
let c = 5.0_f64;
let mut ctrl = SuperTwistingController::<f64>::new(k1, k2, DT).expect("valid params");
let mut x = 1.0_f64;
let mut xdot = 0.0_f64;
let sigma_init = xdot + c * x;
for _ in 0..5000 {
let sigma = xdot + c * x;
let u = ctrl.update(sigma).expect("update ok");
x += DT * xdot;
xdot += DT * u;
}
let sigma_final = xdot + c * x;
assert!(
sigma_final.abs() < sigma_init.abs(),
"sigma should decrease: sigma_init={:.4}, sigma_final={:.4}",
sigma_init,
sigma_final
);
}
}