use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DetailedGenParams {
pub h: f64,
pub d: f64,
pub x_d: f64,
pub x_d_prime: f64,
pub x_q: f64,
pub x_q_prime: f64,
pub t_d0_prime: f64,
pub t_q0_prime: f64,
pub freq_hz: f64,
pub ra: f64,
}
impl DetailedGenParams {
pub fn omega_s(&self) -> f64 {
2.0 * std::f64::consts::PI * self.freq_hz
}
pub fn m(&self) -> f64 {
2.0 * self.h / self.omega_s()
}
pub fn steam_round_rotor() -> Self {
Self {
h: 6.0,
d: 2.0,
x_d: 1.81,
x_d_prime: 0.30,
x_q: 1.76,
x_q_prime: 0.65,
t_d0_prime: 8.0,
t_q0_prime: 1.0,
freq_hz: 60.0,
ra: 0.003,
}
}
pub fn hydro_salient_pole() -> Self {
Self {
h: 4.0,
d: 1.5,
x_d: 1.02,
x_d_prime: 0.32,
x_q: 0.72,
x_q_prime: 0.72, t_d0_prime: 6.5,
t_q0_prime: 0.5,
freq_hz: 60.0,
ra: 0.0025,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct DetailedGenState {
pub e_q_prime: f64,
pub e_d_prime: f64,
pub delta: f64,
pub omega: f64,
pub t_e: f64,
pub time_s: f64,
}
#[derive(Debug, Clone, Copy)]
pub struct GenAlgebraic {
pub i_d: f64,
pub i_q: f64,
}
pub struct DetailedGenerator {
pub params: DetailedGenParams,
pub state: DetailedGenState,
pub e_fd: f64,
pub t_m: f64,
}
impl DetailedGenerator {
pub fn new(
params: DetailedGenParams,
delta0: f64,
omega0: f64,
e_q0: f64,
e_d0: f64,
e_fd0: f64,
t_m0: f64,
) -> Self {
Self {
state: DetailedGenState {
e_q_prime: e_q0,
e_d_prime: e_d0,
delta: delta0,
omega: omega0,
t_e: t_m0,
time_s: 0.0,
},
params,
e_fd: e_fd0,
t_m: t_m0,
}
}
pub fn compute_currents(&self, vt_re: f64, vt_im: f64) -> GenAlgebraic {
let p = &self.params;
let s = &self.state;
let v_d = vt_re * (s.delta).sin() - vt_im * (s.delta).cos();
let v_q = vt_re * (s.delta).cos() + vt_im * (s.delta).sin();
let denom = p.ra * p.ra + p.x_d_prime * p.x_q_prime;
let i_d = if denom > 1e-12 {
(p.ra * (v_d - s.e_d_prime) - p.x_q_prime * (v_q - s.e_q_prime)) / denom
} else {
(v_d - s.e_d_prime) / p.x_d_prime.max(1e-6)
};
let i_q = if denom > 1e-12 {
(p.ra * (v_q - s.e_q_prime) + p.x_d_prime * (v_d - s.e_d_prime)) / denom
} else {
(v_q - s.e_q_prime) / p.x_q_prime.max(1e-6)
};
GenAlgebraic { i_d, i_q }
}
pub fn electrical_torque(&self, alg: &GenAlgebraic) -> f64 {
let s = &self.state;
let p = &self.params;
s.e_q_prime * alg.i_q
+ s.e_d_prime * alg.i_d
+ (p.x_d_prime - p.x_q_prime) * alg.i_d * alg.i_q
}
pub fn step_smib_rk4(&mut self, v_inf: f64, x_net: f64, dt: f64) {
let p = &self.params;
let omega_s = p.omega_s();
let m = p.m();
let step = |s: &DetailedGenState, e_fd: f64, t_m: f64| -> DetailedGenState {
let i_q = (s.e_q_prime - v_inf * s.delta.cos()) / (x_net + p.x_d_prime);
let i_d = v_inf * s.delta.sin() / (x_net + p.x_q_prime);
let t_e =
s.e_q_prime * i_q + s.e_d_prime * i_d + (p.x_d_prime - p.x_q_prime) * i_d * i_q;
let de_q_prime = (e_fd - s.e_q_prime - (p.x_d - p.x_d_prime) * i_d) / p.t_d0_prime;
let de_d_prime = (-s.e_d_prime + (p.x_q - p.x_q_prime) * i_q) / p.t_q0_prime;
let d_omega = (t_m - t_e - p.d * (s.omega - 1.0)) / m;
let d_delta = omega_s * (s.omega - 1.0);
DetailedGenState {
e_q_prime: de_q_prime,
e_d_prime: de_d_prime,
delta: d_delta,
omega: d_omega,
t_e,
time_s: 0.0,
}
};
let s = self.state;
let k1 = step(&s, self.e_fd, self.t_m);
let s2 = DetailedGenState {
e_q_prime: s.e_q_prime + 0.5 * dt * k1.e_q_prime,
e_d_prime: s.e_d_prime + 0.5 * dt * k1.e_d_prime,
delta: s.delta + 0.5 * dt * k1.delta,
omega: s.omega + 0.5 * dt * k1.omega,
t_e: s.t_e,
time_s: s.time_s,
};
let k2 = step(&s2, self.e_fd, self.t_m);
let s3 = DetailedGenState {
e_q_prime: s.e_q_prime + 0.5 * dt * k2.e_q_prime,
e_d_prime: s.e_d_prime + 0.5 * dt * k2.e_d_prime,
delta: s.delta + 0.5 * dt * k2.delta,
omega: s.omega + 0.5 * dt * k2.omega,
t_e: s.t_e,
time_s: s.time_s,
};
let k3 = step(&s3, self.e_fd, self.t_m);
let s4 = DetailedGenState {
e_q_prime: s.e_q_prime + dt * k3.e_q_prime,
e_d_prime: s.e_d_prime + dt * k3.e_d_prime,
delta: s.delta + dt * k3.delta,
omega: s.omega + dt * k3.omega,
t_e: s.t_e,
time_s: s.time_s,
};
let k4 = step(&s4, self.e_fd, self.t_m);
let t_e_mid = k2.t_e; self.state = DetailedGenState {
e_q_prime: s.e_q_prime
+ dt / 6.0
* (k1.e_q_prime + 2.0 * k2.e_q_prime + 2.0 * k3.e_q_prime + k4.e_q_prime),
e_d_prime: s.e_d_prime
+ dt / 6.0
* (k1.e_d_prime + 2.0 * k2.e_d_prime + 2.0 * k3.e_d_prime + k4.e_d_prime),
delta: s.delta + dt / 6.0 * (k1.delta + 2.0 * k2.delta + 2.0 * k3.delta + k4.delta),
omega: s.omega + dt / 6.0 * (k1.omega + 2.0 * k2.omega + 2.0 * k3.omega + k4.omega),
t_e: t_e_mid,
time_s: s.time_s + dt,
};
}
pub fn set_e_fd(&mut self, e_fd: f64) {
self.e_fd = e_fd.max(0.0);
}
pub fn set_t_m(&mut self, t_m: f64) {
self.t_m = t_m.max(0.0);
}
pub fn power_factor_angle(&self, alg: &GenAlgebraic) -> f64 {
let p_e = self.state.e_q_prime * alg.i_q;
let q_e = self.state.e_q_prime * alg.i_d;
q_e.atan2(p_e)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_gen() -> DetailedGenerator {
let p = DetailedGenParams::steam_round_rotor();
DetailedGenerator::new(p, 30f64.to_radians(), 1.0, 1.2, 0.0, 1.2, 0.8)
}
#[test]
fn test_params_m_positive() {
let p = DetailedGenParams::steam_round_rotor();
assert!(p.m() > 0.0);
assert!(p.omega_s() > 300.0); }
#[test]
fn test_initial_state_finite() {
let gen = make_gen();
let s = &gen.state;
assert!(s.e_q_prime.is_finite());
assert!(s.delta.is_finite());
assert!(s.omega.is_finite());
}
#[test]
fn test_compute_currents_finite() {
let gen = make_gen();
let alg = gen.compute_currents(1.0, 0.0);
assert!(alg.i_d.is_finite());
assert!(alg.i_q.is_finite());
}
#[test]
fn test_electrical_torque_positive_at_load() {
let gen = make_gen();
let alg = gen.compute_currents(1.0, 0.0);
let te = gen.electrical_torque(&alg);
assert!(te.is_finite());
}
#[test]
fn test_step_smib_stays_finite() {
let mut gen = make_gen();
for _ in 0..100 {
gen.step_smib_rk4(1.0, 0.3, 0.01);
}
assert!(gen.state.delta.is_finite());
assert!(gen.state.omega.is_finite());
assert!(gen.state.e_q_prime.is_finite());
}
#[test]
fn test_hydro_salient_pole_params() {
let p = DetailedGenParams::hydro_salient_pole();
assert!(p.x_q_prime >= p.x_q_prime); assert!(p.t_d0_prime > p.t_q0_prime);
}
#[test]
fn test_set_efd_and_tm() {
let mut gen = make_gen();
gen.set_e_fd(1.5);
gen.set_t_m(0.9);
assert!((gen.e_fd - 1.5).abs() < 1e-10);
assert!((gen.t_m - 0.9).abs() < 1e-10);
}
#[test]
fn test_freq_hz_60() {
let p = DetailedGenParams::steam_round_rotor();
assert!((p.freq_hz - 60.0).abs() < 1e-10);
}
}