#![allow(clippy::excessive_precision)]
use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct SynRmController<S: ControlScalar> {
pub ld: S,
pub lq: S,
pub poles: u8,
pub i_max: S,
}
impl<S: ControlScalar> SynRmController<S> {
pub fn new(ld: S, lq: S, poles: u8, i_max: S) -> Self {
Self {
ld,
lq,
poles,
i_max,
}
}
pub fn mtpa_current_refs(&self, torque_ref: S) -> (S, S) {
let delta_l = self.ld - self.lq;
let p = S::from_f64(self.poles as f64);
let three_halves = S::from_f64(1.5);
let denom = three_halves * p * delta_l;
if denom.abs() < S::EPSILON {
return (S::ZERO, S::ZERO);
}
let torque_abs = torque_ref.abs();
let i_sq = torque_abs / denom;
let i_axis = i_sq.max(S::ZERO).sqrt();
let inv_sqrt2 = S::from_f64(1.0 / 2.0_f64.sqrt());
let i_axis_max = self.i_max * inv_sqrt2;
let i_axis_clamped = i_axis.min(i_axis_max);
let id = i_axis_clamped;
let iq = i_axis_clamped * torque_ref.signum();
(id, iq)
}
pub fn reluctance_torque(&self, id: S, iq: S) -> S {
let p = S::from_f64(self.poles as f64);
let three_halves = S::from_f64(1.5);
three_halves * p * (self.ld - self.lq) * id * iq
}
pub fn flux_references(&self, id: S, iq: S) -> (S, S) {
(self.ld * id, self.lq * iq)
}
pub fn mtpa_angle(&self) -> S {
S::PI / S::from_f64(4.0)
}
pub fn clamp_current(&self, id: S, iq: S) -> (S, S) {
let mag = (id * id + iq * iq).sqrt();
if mag > self.i_max && mag > S::EPSILON {
let scale = self.i_max / mag;
(id * scale, iq * scale)
} else {
(id, iq)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_reluctance_torque_positive() {
let ctrl = SynRmController::<f32>::new(0.03, 0.01, 2, 20.0);
let tau = ctrl.reluctance_torque(5.0, 5.0);
assert!((tau - 1.5_f32).abs() < 1e-4, "tau={tau}");
}
#[test]
fn test_reluctance_torque_zero_at_zero_current() {
let ctrl = SynRmController::<f32>::new(0.03, 0.01, 2, 20.0);
let tau = ctrl.reluctance_torque(0.0, 0.0);
assert_eq!(tau, 0.0_f32);
}
#[test]
fn test_mtpa_angle_is_45_degrees() {
let ctrl = SynRmController::<f32>::new(0.03, 0.01, 2, 20.0);
let angle = ctrl.mtpa_angle();
let expected = core::f32::consts::PI / 4.0;
assert!((angle - expected).abs() < 1e-6, "angle={angle}");
}
#[test]
fn test_mtpa_current_refs_torque_consistency() {
let ctrl = SynRmController::<f32>::new(0.03, 0.01, 2, 20.0);
let torque_ref = 1.0_f32;
let (id, iq) = ctrl.mtpa_current_refs(torque_ref);
let tau = ctrl.reluctance_torque(id, iq);
assert!(
(tau - torque_ref).abs() < 0.01,
"tau={tau}, torque_ref={torque_ref}"
);
}
#[test]
fn test_clamp_current_within_limit() {
let ctrl = SynRmController::<f32>::new(0.03, 0.01, 2, 10.0);
let (id_c, iq_c) = ctrl.clamp_current(10.0, 10.0);
let mag = (id_c * id_c + iq_c * iq_c).sqrt();
assert!((mag - 10.0_f32).abs() < 1e-4, "clamped magnitude={mag}");
assert!(
(id_c - iq_c).abs() < 1e-4,
"angle not preserved: id={id_c}, iq={iq_c}"
);
}
#[test]
fn test_flux_references() {
let ctrl = SynRmController::<f32>::new(0.03, 0.01, 2, 20.0);
let (psi_d, psi_q) = ctrl.flux_references(5.0, 3.0);
assert!((psi_d - 0.15_f32).abs() < 1e-5, "psi_d={psi_d}");
assert!((psi_q - 0.03_f32).abs() < 1e-5, "psi_q={psi_q}");
}
}