use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct InductionIdConfig<S: ControlScalar> {
pub ls: S,
pub lr: S,
pub lm: S,
pub rs_init: S,
pub tr_init: S,
pub gamma_tr: S,
pub gamma_rs: S,
pub slip_filter_alpha: S,
pub convergence_threshold: S,
pub min_steps_for_convergence: u32,
}
impl<S: ControlScalar> InductionIdConfig<S> {
pub fn default_config() -> Self {
Self {
ls: S::from_f64(0.175),
lr: S::from_f64(0.170),
lm: S::from_f64(0.165),
rs_init: S::from_f64(1.5),
tr_init: S::from_f64(0.14),
gamma_tr: S::from_f64(50.0),
gamma_rs: S::from_f64(5.0),
slip_filter_alpha: S::from_f64(0.05),
convergence_threshold: S::from_f64(1e-5),
min_steps_for_convergence: 400,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct InductionParamIdResult<S: ControlScalar> {
pub tr: S,
pub rr: S,
pub rs: S,
pub omega_slip: S,
pub psi_r_alpha: S,
pub psi_r_beta: S,
pub converged: bool,
pub steps: u32,
}
#[derive(Debug, Clone)]
pub struct InductionParamId<S: ControlScalar> {
config: InductionIdConfig<S>,
psi_ref_alpha: S,
psi_ref_beta: S,
psi_adj_alpha: S,
psi_adj_beta: S,
rs_hat: S,
tr_hat: S,
omega_slip_filtered: S,
prev_i_alpha: S,
prev_i_beta: S,
dt: S,
steps: u32,
converged: bool,
}
impl<S: ControlScalar> InductionParamId<S> {
pub fn new(dt: S, config: InductionIdConfig<S>) -> Self {
let rs_hat = config.rs_init;
let tr_hat = config.tr_init;
Self {
config,
psi_ref_alpha: S::ZERO,
psi_ref_beta: S::ZERO,
psi_adj_alpha: S::ZERO,
psi_adj_beta: S::ZERO,
rs_hat,
tr_hat,
omega_slip_filtered: S::ZERO,
prev_i_alpha: S::ZERO,
prev_i_beta: S::ZERO,
dt,
steps: 0,
converged: false,
}
}
pub fn with_defaults(dt: S) -> Self {
Self::new(dt, InductionIdConfig::default_config())
}
pub fn update(&mut self, v_alpha: S, v_beta: S, i_alpha: S, i_beta: S, omega_r: S) {
let dt_inv = if self.dt > S::ZERO {
S::ONE / self.dt
} else {
S::ZERO
};
let ls = self.config.ls;
let lr = self.config.lr;
let lm = self.config.lm;
let lm_sq = lm * lm;
let sigma_ls_lr = ls * lr - lm_sq; let sigma_ls = sigma_ls_lr / lr;
let di_alpha_dt = (i_alpha - self.prev_i_alpha) * dt_inv;
let di_beta_dt = (i_beta - self.prev_i_beta) * dt_inv;
let lr_over_lm = lr / lm;
let back_emf_alpha = v_alpha - self.rs_hat * i_alpha - sigma_ls * di_alpha_dt;
let back_emf_beta = v_beta - self.rs_hat * i_beta - sigma_ls * di_beta_dt;
let dpsi_ref_alpha = lr_over_lm * back_emf_alpha;
let dpsi_ref_beta = lr_over_lm * back_emf_beta;
self.psi_ref_alpha += dpsi_ref_alpha * self.dt;
self.psi_ref_beta += dpsi_ref_beta * self.dt;
let tr_safe = if self.tr_hat > S::from_f64(1e-6) {
self.tr_hat
} else {
S::from_f64(1e-6)
};
let inv_tr = S::ONE / tr_safe;
let lm_inv_tr = lm * inv_tr;
let dpsi_adj_alpha =
-self.psi_adj_alpha * inv_tr + lm_inv_tr * i_alpha - omega_r * self.psi_adj_beta;
let dpsi_adj_beta =
-self.psi_adj_beta * inv_tr + lm_inv_tr * i_beta + omega_r * self.psi_adj_alpha;
self.psi_adj_alpha += dpsi_adj_alpha * self.dt;
self.psi_adj_beta += dpsi_adj_beta * self.dt;
let e_alpha = self.psi_ref_alpha - self.psi_adj_alpha;
let e_beta = self.psi_ref_beta - self.psi_adj_beta;
let mras_signal = e_alpha * self.psi_adj_alpha + e_beta * self.psi_adj_beta;
let dtr = self.config.gamma_tr * mras_signal;
let tr_prev = self.tr_hat;
self.tr_hat += dtr * self.dt;
if self.tr_hat < S::from_f64(1e-6) {
self.tr_hat = S::from_f64(1e-6);
}
let rs_correction = -(e_alpha * i_alpha + e_beta * i_beta);
self.rs_hat += self.config.gamma_rs * rs_correction * self.dt;
if self.rs_hat < S::ZERO {
self.rs_hat = S::ZERO;
}
let psi_sq =
self.psi_adj_alpha * self.psi_adj_alpha + self.psi_adj_beta * self.psi_adj_beta;
let raw_slip = if psi_sq > S::from_f64(1e-8) {
lm / (tr_safe * psi_sq) * (self.psi_adj_alpha * i_beta - self.psi_adj_beta * i_alpha)
} else {
S::ZERO
};
let alpha = self.config.slip_filter_alpha;
self.omega_slip_filtered = alpha * raw_slip + (S::ONE - alpha) * self.omega_slip_filtered;
self.steps += 1;
if !self.converged && self.steps >= self.config.min_steps_for_convergence {
let dtr_abs = if dtr < S::ZERO { -dtr } else { dtr };
let tr_change = (self.tr_hat - tr_prev).abs();
let _ = tr_change; if dtr_abs < self.config.convergence_threshold {
self.converged = true;
}
}
self.prev_i_alpha = i_alpha;
self.prev_i_beta = i_beta;
}
pub fn results(&self) -> InductionParamIdResult<S> {
let tr_safe = if self.tr_hat > S::from_f64(1e-9) {
self.tr_hat
} else {
S::from_f64(1e-9)
};
let rr = self.config.lr / tr_safe;
InductionParamIdResult {
tr: self.tr_hat,
rr,
rs: self.rs_hat,
omega_slip: self.omega_slip_filtered,
psi_r_alpha: self.psi_adj_alpha,
psi_r_beta: self.psi_adj_beta,
converged: self.converged,
steps: self.steps,
}
}
pub fn reset(&mut self) {
self.psi_ref_alpha = S::ZERO;
self.psi_ref_beta = S::ZERO;
self.psi_adj_alpha = S::ZERO;
self.psi_adj_beta = S::ZERO;
self.rs_hat = self.config.rs_init;
self.tr_hat = self.config.tr_init;
self.omega_slip_filtered = S::ZERO;
self.prev_i_alpha = S::ZERO;
self.prev_i_beta = S::ZERO;
self.steps = 0;
self.converged = false;
}
pub fn is_converged(&self) -> bool {
self.converged
}
pub fn steps(&self) -> u32 {
self.steps
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_config_sane() {
let cfg = InductionIdConfig::<f64>::default_config();
assert!(cfg.tr_init > 0.0);
assert!(cfg.gamma_tr > 0.0);
assert!(cfg.slip_filter_alpha > 0.0 && cfg.slip_filter_alpha <= 1.0);
}
#[test]
fn update_does_not_panic_on_zero_inputs() {
let mut id = InductionParamId::<f64>::with_defaults(1e-4);
for _ in 0..100 {
id.update(0.0, 0.0, 0.0, 0.0, 0.0);
}
let res = id.results();
assert!(res.tr > 0.0);
assert!(res.rs >= 0.0);
}
#[test]
fn reset_restores_initial_state() {
let mut id = InductionParamId::<f32>::with_defaults(1e-4);
for i in 0..200 {
let v = i as f32 * 0.05;
id.update(v, v * 0.5, v * 0.1, v * 0.05, 100.0);
}
let tr_before_reset = id.results().tr;
id.reset();
let res = id.results();
let init_tr = InductionIdConfig::<f32>::default_config().tr_init;
assert!((res.tr - init_tr).abs() < 1e-6_f32);
assert!((tr_before_reset - init_tr).abs() > 0.0_f32);
}
#[test]
fn slip_remains_bounded_with_normal_excitation() {
let mut id = InductionParamId::<f64>::with_defaults(1e-4);
let omega_r = 314.0_f64; for i in 0..1000 {
let t = i as f64 * 1e-4;
let i_alpha = 5.0 * libm::sin(2.0 * core::f64::consts::PI * 50.0 * t);
let i_beta = 5.0 * libm::cos(2.0 * core::f64::consts::PI * 50.0 * t);
let v_alpha = 100.0 * libm::sin(2.0 * core::f64::consts::PI * 50.0 * t);
let v_beta = 100.0 * libm::cos(2.0 * core::f64::consts::PI * 50.0 * t);
id.update(v_alpha, v_beta, i_alpha, i_beta, omega_r);
}
let res = id.results();
assert!(
res.omega_slip.abs() < 2000.0,
"slip exceeded physical bound: {} rad/s",
res.omega_slip
);
}
}