use num_complex::Complex;
use super::{DynamicModel, InitError, ModelJacobian};
#[derive(Clone, Debug)]
pub struct ZipLoad {
fractions: [f64; 3],
cutoff: f64,
v0: f64,
s_conj: [Complex<f64>; 3],
}
impl ZipLoad {
pub fn new(z: f64, i: f64, p: f64, cutoff: f64) -> Result<Self, InitError> {
let sum = z + i + p;
if (sum - 1.0).abs() > 1e-9 {
return Err(InitError::OutsideLimits {
name: "zip fractions sum",
value: sum,
});
}
if cutoff <= 0.0 {
return Err(InitError::NonPositiveParameter {
name: "zip cutoff",
value: cutoff,
});
}
Ok(Self {
fractions: [z, i, p],
cutoff,
v0: 1.0,
s_conj: [Complex::new(0.0, 0.0); 3],
})
}
pub fn constant_power(cutoff: f64) -> Result<Self, InitError> {
Self::new(0.0, 0.0, 1.0, cutoff)
}
fn weights(&self, v_mag: f64) -> ([f64; 3], [f64; 3]) {
let m = v_mag.max(self.cutoff);
let clamped = v_mag <= self.cutoff;
let w = [
1.0 / (self.v0 * self.v0),
1.0 / (self.v0 * m),
1.0 / (m * m),
];
let dw = if clamped {
[0.0, 0.0, 0.0]
} else {
[0.0, -1.0 / (self.v0 * m * m), -2.0 / (m * m * m)]
};
(w, dw)
}
}
const NO_STATES: [&str; 0] = [];
impl DynamicModel for ZipLoad {
fn n_states(&self) -> usize {
0
}
fn state_names(&self) -> &[&'static str] {
&NO_STATES
}
fn norton_admittance(&self) -> Option<Complex<f64>> {
None
}
fn derivatives(&self, _x: &[f64], _v: Complex<f64>, _out: &mut [f64]) {}
fn injection(&self, _x: &[f64], v: Complex<f64>) -> Complex<f64> {
let (w, _) = self.weights(v.norm());
(0..3).map(|k| self.s_conj[k] * v * w[k]).sum()
}
fn jacobian(&self, _x: &[f64], v: Complex<f64>, out: &mut ModelJacobian) {
let v_mag = v.norm();
let (w, dw) = self.weights(v_mag);
let (dm_re, dm_im) = if v_mag > 0.0 {
(v.re / v_mag, v.im / v_mag)
} else {
(0.0, 0.0)
};
let mut d_re = Complex::new(0.0, 0.0);
let mut d_im = Complex::new(0.0, 0.0);
for k in 0..3 {
let c = self.s_conj[k];
d_re += c * (Complex::new(w[k], 0.0) + v * dw[k] * dm_re);
d_im += c * (Complex::new(0.0, w[k]) + v * dw[k] * dm_im);
}
out.didv = [d_re.re, d_im.re, d_re.im, d_im.im];
}
fn initialize(&mut self, v: Complex<f64>, s: Complex<f64>) -> Result<Vec<f64>, InitError> {
let v_mag = v.norm();
if v_mag == 0.0 {
return Err(InitError::ZeroTerminalVoltage);
}
self.v0 = v_mag;
for k in 0..3 {
self.s_conj[k] = (s * self.fractions[k]).conj();
}
Ok(Vec::new())
}
}