use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct GridFormingInverter<S: ControlScalar> {
pub omega_n: S,
pub v_n: S,
pub inertia: S,
pub d_p: S,
pub d_q: S,
pub omega: S,
pub v_ref: S,
pub p_out: S,
pub q_out: S,
}
impl<S: ControlScalar> GridFormingInverter<S> {
pub fn new(omega_n: S, v_n: S, inertia: S, d_p: S, d_q: S) -> Self {
Self {
omega_n,
v_n,
inertia,
d_p,
d_q,
omega: omega_n,
v_ref: v_n,
p_out: S::ZERO,
q_out: S::ZERO,
}
}
pub fn droop_control(&mut self, p: S, q: S, p_ref: S, q_ref: S) -> (S, S) {
self.p_out = p;
self.q_out = q;
self.omega = self.omega_n - self.d_p * (p - p_ref);
self.v_ref = self.v_n - self.d_q * (q - q_ref);
(self.omega, self.v_ref)
}
pub fn vsm_step(&mut self, p_ref: S, p_meas: S, dt: S) {
self.p_out = p_meas;
let delta_omega = self.omega - self.omega_n;
let dw = (p_ref - p_meas - self.d_p * delta_omega) / (S::TWO * self.inertia);
self.omega += dw * dt;
}
pub fn voltage_droop(&self, q_meas: S, q_ref: S) -> S {
self.v_n - self.d_q * (q_meas - q_ref)
}
pub fn frequency_hz(&self) -> S {
self.omega / (S::TWO * S::PI)
}
pub fn is_synchronized(&self, omega_grid: S, tolerance: S) -> bool {
(self.omega - omega_grid).abs() <= tolerance
}
pub fn reset(&mut self) {
self.omega = self.omega_n;
self.v_ref = self.v_n;
self.p_out = S::ZERO;
self.q_out = S::ZERO;
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::f64::consts::PI;
#[test]
fn droop_reduces_frequency_on_overload() {
let omega_n = 2.0 * PI * 50.0_f64;
let mut inv = GridFormingInverter::new(omega_n, 230.0, 5.0, 1e-3, 1e-4);
let (omega, _) = inv.droop_control(2000.0, 0.0, 1000.0, 0.0);
assert!(omega < omega_n, "omega should drop: {}", omega);
}
#[test]
fn vsm_step_integrates_swing_equation() {
let omega_n = 2.0 * PI * 50.0_f64;
let mut inv = GridFormingInverter::new(omega_n, 230.0, 5.0, 1.0, 0.1);
inv.vsm_step(1500.0, 1000.0, 0.01);
assert!(inv.omega > omega_n, "omega should increase: {}", inv.omega);
}
#[test]
fn voltage_droop_drops_on_inductive_load() {
let omega_n = 2.0 * PI * 50.0_f64;
let inv = GridFormingInverter::new(omega_n, 230.0, 5.0, 1e-3, 1e-3);
let v = inv.voltage_droop(500.0, 0.0);
assert!(v < 230.0, "v_ref should drop: {}", v);
}
#[test]
fn synchronization_check() {
let omega_n = 2.0 * PI * 50.0_f64;
let inv = GridFormingInverter::new(omega_n, 230.0, 5.0, 1e-3, 1e-3);
assert!(inv.is_synchronized(omega_n, 0.1));
assert!(!inv.is_synchronized(omega_n + 1.0, 0.1));
}
}