use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct FluxWeakening<S: ControlScalar> {
pub l_d: S,
pub psi_f: S,
pub v_s_max: S,
pub omega_base: S,
pub id_min: S,
id_ref: S,
}
impl<S: ControlScalar> FluxWeakening<S> {
pub fn new(l_d: S, psi_f: S, v_s_max: S, omega_base: S, id_min: S) -> Self {
Self {
l_d,
psi_f,
v_s_max,
omega_base,
id_min,
id_ref: S::ZERO,
}
}
pub fn update(&mut self, omega_e: S) -> S {
let omega_abs = omega_e.abs();
if omega_abs <= self.omega_base || omega_abs < S::EPSILON {
self.id_ref = S::ZERO;
return S::ZERO;
}
let flux_limit = self.v_s_max / omega_abs;
let id = (flux_limit - self.psi_f) / self.l_d;
self.id_ref = id.clamp_val(self.id_min, S::ZERO);
self.id_ref
}
pub fn id_ref(&self) -> S {
self.id_ref
}
pub fn reset(&mut self) {
self.id_ref = S::ZERO;
}
pub fn is_active(&self) -> bool {
self.id_ref < S::ZERO
}
}
#[cfg(test)]
mod tests {
use super::*;
fn build_fw() -> FluxWeakening<f64> {
FluxWeakening::new(0.001, 0.05, 10.0, 200.0, -20.0)
}
#[test]
fn below_base_speed_no_fw() {
let mut fw = build_fw();
let id = fw.update(100.0); assert_eq!(id, 0.0);
assert!(!fw.is_active());
}
#[test]
fn above_base_speed_activates() {
let mut fw = build_fw();
let id = fw.update(400.0);
assert!(id < 0.0, "id={:.4}", id);
assert!(fw.is_active());
}
#[test]
fn clamps_to_id_min() {
let mut fw = build_fw();
let id = fw.update(10000.0); assert!(id >= -20.0);
}
#[test]
fn negative_speed_works() {
let mut fw = build_fw();
let id_pos = fw.update(400.0);
let id_neg = fw.update(-400.0);
assert!((id_pos - id_neg).abs() < 1e-10, "Should be symmetric");
}
#[test]
fn reset_clears_state() {
let mut fw = build_fw();
fw.update(500.0);
fw.reset();
assert_eq!(fw.id_ref(), 0.0);
}
}