#![allow(clippy::excessive_precision)]
use crate::core::scalar::ControlScalar;
pub fn max_linear_voltage<S: ControlScalar>(v_dc: S) -> S {
let inv_sqrt3 = S::from_f64(0.5773502692);
v_dc * inv_sqrt3
}
pub fn max_six_step_voltage<S: ControlScalar>(v_dc: S) -> S {
let two_over_pi = S::from_f64(core::f64::consts::FRAC_2_PI);
v_dc * two_over_pi
}
pub fn overmodulate_mode1<S: ControlScalar>(alpha: S, beta: S, v_dc: S) -> (S, S) {
let v_max = max_linear_voltage(v_dc);
let mag = (alpha * alpha + beta * beta).sqrt();
if mag <= v_max || mag <= S::EPSILON {
(alpha, beta)
} else {
let scale = v_max / mag;
(alpha * scale, beta * scale)
}
}
pub fn overmodulate_mode2<S: ControlScalar>(alpha: S, beta: S, v_dc: S, mi: S) -> (S, S) {
let v_half = v_dc * S::HALF;
let mi_max = S::from_f64(4.0 / core::f64::consts::PI);
let mi_clamped = mi.clamp_val(S::ONE, mi_max);
let blend = (mi_clamped - S::ONE) / (mi_max - S::ONE);
let theta = beta.atan2(alpha);
let pi_over_3 = S::PI / S::from_f64(3.0);
let two_pi = S::TWO * S::PI;
let theta_pos = if theta < S::ZERO {
theta + two_pi
} else {
theta
};
let sector_f = theta_pos / pi_over_3;
let sector = sector_f.to_f64() as usize % 6;
let theta_v = S::from_f64((sector as f64 + S::HALF.to_f64()) * 60.0_f64.to_radians());
let mut delta = theta_v - theta_pos;
while delta.to_f64() > core::f64::consts::PI {
delta -= two_pi;
}
while delta.to_f64() < -core::f64::consts::PI {
delta += two_pi;
}
let theta_out = theta_pos + blend * delta;
let sqrt3_over2 = S::from_f64(3.0_f64.sqrt() / 2.0);
let theta_sector = theta_out - theta_v; let cos_sector = theta_sector.cos().abs().max(S::EPSILON);
let r_hex = v_half * sqrt3_over2 / cos_sector;
(r_hex * theta_out.cos(), r_hex * theta_out.sin())
}
pub fn overmodulate<S: ControlScalar>(alpha: S, beta: S, v_dc: S, mi: S) -> (S, S) {
let mi_max = S::from_f64(4.0 / core::f64::consts::PI);
let mi_clamped = mi.clamp_val(S::ZERO, mi_max);
if mi_clamped <= S::ONE {
overmodulate_mode1(alpha, beta, v_dc)
} else {
overmodulate_mode2(alpha, beta, v_dc, mi_clamped)
}
}
#[derive(Debug, Clone, Copy)]
pub struct OvermodulationController<S: ControlScalar> {
pub v_dc: S,
pub mi_threshold: S,
}
impl<S: ControlScalar> OvermodulationController<S> {
pub fn new(v_dc: S) -> Self {
Self {
v_dc,
mi_threshold: S::ONE,
}
}
pub fn modulation_index(&self, alpha: S, beta: S) -> S {
let mag = (alpha * alpha + beta * beta).sqrt();
let v_max = max_linear_voltage(self.v_dc);
if v_max > S::EPSILON {
mag / v_max
} else {
S::ZERO
}
}
pub fn apply(&self, alpha: S, beta: S) -> (S, S) {
let mi = self.modulation_index(alpha, beta);
overmodulate(alpha, beta, self.v_dc, mi)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_max_linear_voltage() {
let v = max_linear_voltage(400.0_f32);
let expected = 400.0_f32 / 3.0_f32.sqrt();
assert!((v - expected).abs() < 0.01, "v={v}, expected={expected}");
}
#[test]
fn test_mode1_clamping() {
let v_dc = 400.0_f32;
let v_max = max_linear_voltage(v_dc);
let (a1, b1) = overmodulate_mode1(10.0_f32, 10.0_f32, v_dc);
let mag1 = (a1 * a1 + b1 * b1).sqrt();
assert!(mag1 <= (10.0_f32 * 2.0_f32.sqrt() + 0.01));
let big = v_max * 2.0;
let (a2, b2) = overmodulate_mode1(big, 0.0_f32, v_dc);
let mag2 = (a2 * a2 + b2 * b2).sqrt();
assert!((mag2 - v_max).abs() < 0.01, "mag2={mag2}, v_max={v_max}");
}
#[test]
fn test_overmodulation_controller_identity_below_threshold() {
let ctrl = OvermodulationController::<f32>::new(400.0);
let alpha = 100.0_f32;
let beta = 0.0_f32;
let mi = ctrl.modulation_index(alpha, beta);
assert!(mi < 1.0, "Expected MI < 1, got {mi}");
let (ao, bo) = ctrl.apply(alpha, beta);
assert!((ao - alpha).abs() < 0.01);
assert!((bo - beta).abs() < 0.01);
}
#[test]
fn test_max_six_step_voltage() {
let v = max_six_step_voltage(400.0_f32);
let expected = 2.0_f32 / core::f32::consts::PI * 400.0;
assert!((v - expected).abs() < 0.01, "v={v}");
}
}