#![allow(clippy::needless_range_loop)]
use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RlsAxis {
D,
Q,
}
#[derive(Debug, Clone, Copy)]
pub struct PmsmIdConfig<S: ControlScalar> {
pub forgetting_factor: S,
pub p_init: S,
pub convergence_threshold: S,
pub min_steps_for_convergence: u32,
}
impl<S: ControlScalar> PmsmIdConfig<S> {
pub fn default_config() -> Self {
Self {
forgetting_factor: S::from_f64(0.97),
p_init: S::from_f64(1.0e4),
convergence_threshold: S::from_f64(1.0e-5),
min_steps_for_convergence: 500,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct PmsmParamIdResult<S: ControlScalar> {
pub rs: S,
pub ld: S,
pub lq: S,
pub lambda_pm: S,
pub d_converged: bool,
pub q_converged: bool,
pub steps: u32,
}
#[derive(Debug, Clone, Copy)]
struct RlsState<S: ControlScalar> {
theta: [S; 3],
p: [[S; 3]; 3],
n_params: usize,
lambda: S,
steps: u32,
conv_threshold: S,
min_steps: u32,
converged: bool,
}
impl<S: ControlScalar> RlsState<S> {
fn new(n_params: usize, lambda: S, p_init: S, conv_threshold: S, min_steps: u32) -> Self {
debug_assert!(n_params == 2 || n_params == 3);
let mut p = [[S::ZERO; 3]; 3];
for i in 0..n_params {
p[i][i] = p_init;
}
Self {
theta: [S::ZERO; 3],
p,
n_params,
lambda,
steps: 0,
conv_threshold,
min_steps,
converged: false,
}
}
fn update(&mut self, phi: &[S; 3], y: S) {
let n = self.n_params;
let mut p_phi = [S::ZERO; 3];
for i in 0..n {
for j in 0..n {
p_phi[i] += self.p[i][j] * phi[j];
}
}
let mut phi_t_p_phi = S::ZERO;
for i in 0..n {
phi_t_p_phi += phi[i] * p_phi[i];
}
let denom = self.lambda + phi_t_p_phi;
let mut k = [S::ZERO; 3];
for i in 0..n {
k[i] = p_phi[i] / denom;
}
let mut y_hat = S::ZERO;
for i in 0..n {
y_hat += phi[i] * self.theta[i];
}
let innovation = y - y_hat;
let mut max_delta = S::ZERO;
for i in 0..n {
let delta = k[i] * innovation;
self.theta[i] += delta;
let abs_delta = if delta < S::ZERO { -delta } else { delta };
if abs_delta > max_delta {
max_delta = abs_delta;
}
}
let mut phi_t_p = [S::ZERO; 3];
for j in 0..n {
for l in 0..n {
phi_t_p[j] += phi[l] * self.p[l][j];
}
}
for i in 0..n {
for j in 0..n {
self.p[i][j] = (self.p[i][j] - k[i] * phi_t_p[j]) / self.lambda;
}
}
self.steps += 1;
if !self.converged && self.steps >= self.min_steps && max_delta < self.conv_threshold {
self.converged = true;
}
}
}
#[derive(Debug, Clone)]
pub struct PmsmParamId<S: ControlScalar> {
d_rls: RlsState<S>,
q_rls: RlsState<S>,
prev_id: S,
prev_iq: S,
dt: S,
config: PmsmIdConfig<S>,
}
impl<S: ControlScalar> PmsmParamId<S> {
pub fn new(dt: S, config: PmsmIdConfig<S>) -> Self {
let lambda = config.forgetting_factor;
let p0 = config.p_init;
let eps = config.convergence_threshold;
let min_steps = config.min_steps_for_convergence;
Self {
d_rls: RlsState::new(2, lambda, p0, eps, min_steps),
q_rls: RlsState::new(3, lambda, p0, eps, min_steps),
prev_id: S::ZERO,
prev_iq: S::ZERO,
dt,
config,
}
}
pub fn with_defaults(dt: S) -> Self {
Self::new(dt, PmsmIdConfig::default_config())
}
pub fn update(&mut self, vd: S, vq: S, id: S, iq: S, omega_e: S) {
let dt_inv = if self.dt > S::ZERO {
S::ONE / self.dt
} else {
S::ZERO
};
let did_dt = (id - self.prev_id) * dt_inv;
let diq_dt = (iq - self.prev_iq) * dt_inv;
let lq_est = self.q_rls.theta[1];
let y_d = vd + omega_e * lq_est * iq;
let mut phi_d = [S::ZERO; 3];
phi_d[0] = id; phi_d[1] = did_dt; self.d_rls.update(&phi_d, y_d);
let ld_est = self.d_rls.theta[1];
let y_q = vq - omega_e * ld_est * id;
let mut phi_q = [S::ZERO; 3];
phi_q[0] = iq; phi_q[1] = diq_dt; phi_q[2] = omega_e; self.q_rls.update(&phi_q, y_q);
self.prev_id = id;
self.prev_iq = iq;
}
pub fn results(&self) -> PmsmParamIdResult<S> {
let rs_d = self.d_rls.theta[0];
let ld = self.d_rls.theta[1];
let rs_q = self.q_rls.theta[0];
let lq = self.q_rls.theta[1];
let lambda_pm = self.q_rls.theta[2];
let rs = if self.d_rls.converged && self.q_rls.converged {
(rs_d + rs_q) * S::HALF
} else if self.d_rls.converged {
rs_d
} else {
rs_q
};
let rs = if rs < S::ZERO { S::ZERO } else { rs };
let ld = if ld < S::ZERO { S::ZERO } else { ld };
let lq = if lq < S::ZERO { S::ZERO } else { lq };
let lambda_pm = if lambda_pm < S::ZERO {
S::ZERO
} else {
lambda_pm
};
PmsmParamIdResult {
rs,
ld,
lq,
lambda_pm,
d_converged: self.d_rls.converged,
q_converged: self.q_rls.converged,
steps: self.d_rls.steps,
}
}
pub fn reset(&mut self) {
let lambda = self.config.forgetting_factor;
let p0 = self.config.p_init;
let eps = self.config.convergence_threshold;
let min_steps = self.config.min_steps_for_convergence;
self.d_rls = RlsState::new(2, lambda, p0, eps, min_steps);
self.q_rls = RlsState::new(3, lambda, p0, eps, min_steps);
self.prev_id = S::ZERO;
self.prev_iq = S::ZERO;
}
pub fn is_converged(&self) -> bool {
self.d_rls.converged && self.q_rls.converged
}
pub fn steps(&self) -> u32 {
self.d_rls.steps
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rls_recovers_d_axis_params() {
let dt = 1e-4_f64;
let config = PmsmIdConfig {
forgetting_factor: 0.99,
p_init: 1.0e6,
convergence_threshold: 1e-7,
min_steps_for_convergence: 200,
};
let mut id_est = PmsmParamId::<f64>::new(dt, config);
let rs_true = 0.5_f64;
let ld_true = 3e-4_f64;
let lq_true = 4e-4_f64;
let lpm_true = 0.05_f64;
let omega_e = 100.0_f64;
let mut id = 0.0_f64;
let mut iq = 0.0_f64;
let mut t = 0.0_f64;
for _step in 0..5000 {
let id_new = 2.0 * libm::sin(50.0 * t);
let iq_new = 3.0 * libm::cos(70.0 * t);
let did_dt = (id_new - id) / dt;
let diq_dt = (iq_new - iq) / dt;
let vd = rs_true * id_new + ld_true * did_dt - omega_e * lq_true * iq_new;
let vq = rs_true * iq_new
+ lq_true * diq_dt
+ omega_e * ld_true * id_new
+ omega_e * lpm_true;
id = id_new;
iq = iq_new;
id_est.update(vd, vq, id, iq, omega_e);
t += dt;
}
let res = id_est.results();
let rel_err = (res.ld - ld_true).abs() / ld_true;
assert!(
rel_err < 0.20,
"Ld relative error {:.2}% too large (estimate={:.6e}, true={:.6e})",
rel_err * 100.0,
res.ld,
ld_true
);
}
#[test]
fn default_config_has_valid_forgetting_factor() {
let cfg = PmsmIdConfig::<f64>::default_config();
assert!(cfg.forgetting_factor > 0.0 && cfg.forgetting_factor <= 1.0);
}
#[test]
fn reset_clears_state() {
let mut est = PmsmParamId::<f32>::with_defaults(1e-4);
for i in 0..100 {
let v = i as f32 * 0.01;
est.update(v, v, v * 0.1, v * 0.2, 50.0);
}
est.reset();
assert_eq!(est.steps(), 0);
let res = est.results();
assert_eq!(res.rs, 0.0_f32);
}
#[test]
fn rls_state_two_params_basic_convergence() {
let mut rls = RlsState::<f64>::new(2, 0.99, 1e4, 1e-8, 100);
for i in 0..2000 {
let x = (i as f64) * 0.01;
let y = 2.0 * x + 0.5 * (i as f64 * 0.7).sin();
let phi = [x, 0.0, 0.0];
rls.update(&phi, y);
}
assert!(
(rls.theta[0] - 2.0).abs() < 0.5,
"theta[0]={:.4} should be near 2.0",
rls.theta[0]
);
}
}