use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct InductionMotor<S: ControlScalar> {
pub rs: S,
pub rr: S,
pub ls: S,
pub lr: S,
pub lm: S,
pub pole_pairs: S,
pub j: S,
pub b_friction: S,
i_alpha: S,
i_beta: S,
lambda_alpha_r: S,
lambda_beta_r: S,
omega_r: S,
theta_r: S,
}
impl<S: ControlScalar> InductionMotor<S> {
#[allow(clippy::too_many_arguments)]
pub fn new(rs: S, rr: S, ls: S, lr: S, lm: S, pole_pairs: S, j: S, b_friction: S) -> Self {
Self {
rs,
rr,
ls,
lr,
lm,
pole_pairs,
j,
b_friction,
i_alpha: S::ZERO,
i_beta: S::ZERO,
lambda_alpha_r: S::ZERO,
lambda_beta_r: S::ZERO,
omega_r: S::ZERO,
theta_r: S::ZERO,
}
}
pub fn typical_1kw() -> Self {
Self::new(
S::from_f64(4.0), S::from_f64(2.5), S::from_f64(0.3), S::from_f64(0.3), S::from_f64(0.28), S::from_f64(2.0), S::from_f64(0.01), S::from_f64(0.001), )
}
pub fn step(&mut self, v_alpha: S, v_beta: S, t_load: S, dt: S) {
let sigma = S::ONE - self.lm * self.lm / (self.ls * self.lr);
let sigma_ls = sigma * self.ls;
let rr_lr = self.rr / self.lr;
let lm_lr = self.lm / self.lr;
let lm_rr_lr2 = self.lm * self.rr / (self.lr * self.lr);
let e_omega = self.pole_pairs * self.omega_r;
let d_ia = (sigma_ls).recip()
* (-self.rs * self.i_alpha
+ lm_rr_lr2 * self.lambda_alpha_r
+ lm_lr * e_omega * self.lambda_beta_r
+ v_alpha);
let d_ib = (sigma_ls).recip()
* (-self.rs * self.i_beta + lm_rr_lr2 * self.lambda_beta_r
- lm_lr * e_omega * self.lambda_alpha_r
+ v_beta);
let d_lar = self.lm * rr_lr * self.i_alpha
- rr_lr * self.lambda_alpha_r
- e_omega * self.lambda_beta_r;
let d_lbr = self.lm * rr_lr * self.i_beta - rr_lr * self.lambda_beta_r
+ e_omega * self.lambda_alpha_r;
let te = self.pole_pairs
* lm_lr
* (self.i_beta * self.lambda_alpha_r - self.i_alpha * self.lambda_beta_r);
let d_omega = (te - self.b_friction * self.omega_r - t_load) / self.j;
self.i_alpha += d_ia * dt;
self.i_beta += d_ib * dt;
self.lambda_alpha_r += d_lar * dt;
self.lambda_beta_r += d_lbr * dt;
self.omega_r += d_omega * dt;
self.theta_r += self.omega_r * dt;
}
pub fn i_alpha(&self) -> S {
self.i_alpha
}
pub fn i_beta(&self) -> S {
self.i_beta
}
pub fn omega_r(&self) -> S {
self.omega_r
}
pub fn theta_r(&self) -> S {
self.theta_r
}
pub fn lambda_alpha_r(&self) -> S {
self.lambda_alpha_r
}
pub fn lambda_beta_r(&self) -> S {
self.lambda_beta_r
}
pub fn flux_magnitude(&self) -> S {
(self.lambda_alpha_r * self.lambda_alpha_r + self.lambda_beta_r * self.lambda_beta_r).sqrt()
}
pub fn torque(&self) -> S {
let lm_lr = self.lm / self.lr;
self.pole_pairs
* lm_lr
* (self.i_beta * self.lambda_alpha_r - self.i_alpha * self.lambda_beta_r)
}
pub fn reset(&mut self) {
self.i_alpha = S::ZERO;
self.i_beta = S::ZERO;
self.lambda_alpha_r = S::ZERO;
self.lambda_beta_r = S::ZERO;
self.omega_r = S::ZERO;
self.theta_r = S::ZERO;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn motor_starts_from_rest() {
let mut motor = InductionMotor::<f64>::typical_1kw();
let dt = 1e-5_f64;
for _ in 0..1000 {
motor.step(100.0, 0.0, 0.0, dt);
}
assert!(motor.i_alpha().abs() > 0.1, "ia should grow");
}
#[test]
fn torque_is_zero_at_rest_no_flux() {
let motor = InductionMotor::<f64>::typical_1kw();
assert_eq!(motor.torque(), 0.0);
}
#[test]
fn flux_builds_up_with_voltage() {
let mut motor = InductionMotor::<f64>::typical_1kw();
let dt = 1e-4_f64;
for _ in 0..5000 {
motor.step(50.0, 0.0, 0.0, dt);
}
assert!(motor.flux_magnitude() > 0.01, "flux should build up");
}
#[test]
fn motor_accelerates_under_torque_producing_voltage() {
let mut motor = InductionMotor::<f64>::typical_1kw();
let dt = 1e-4_f64;
let omega_s = 2.0 * core::f64::consts::PI * 50.0;
let v_peak = 100.0_f64;
let mut t = 0.0_f64;
for _ in 0..100_000 {
let va = v_peak * (omega_s * t).cos();
let vb = v_peak * (omega_s * t - core::f64::consts::PI / 2.0).cos();
motor.step(va, vb, 0.0, dt);
t += dt;
}
assert!(
motor.omega_r().abs() > 1.0,
"motor should accelerate, ω={:.2}",
motor.omega_r()
);
}
}