use num_complex::Complex;
use serde::{Deserialize, Serialize};
use super::{DynamicModel, InitError};
#[derive(Clone, Debug)]
pub struct MachineJacobian {
pub dfdx: Vec<f64>,
pub dfdv: Vec<f64>,
pub dfde: Vec<f64>,
pub dfdp: Vec<f64>,
pub didx: Vec<f64>,
pub didv: [f64; 4],
}
impl MachineJacobian {
pub fn zeros(n: usize) -> Self {
Self {
dfdx: vec![0.0; n * n],
dfdv: vec![0.0; n * 2],
dfde: vec![0.0; n],
dfdp: vec![0.0; n],
didx: vec![0.0; 2 * n],
didv: [0.0; 4],
}
}
pub fn clear(&mut self) {
self.dfdx.fill(0.0);
self.dfdv.fill(0.0);
self.dfde.fill(0.0);
self.dfdp.fill(0.0);
self.didx.fill(0.0);
self.didv = [0.0; 4];
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct MachineInit {
pub states: Vec<f64>,
pub e_fd: f64,
pub p_m: f64,
}
macro_rules! tunable_machine {
($machine:ty) => {
fn tunable(&self) -> &'static [&'static str] {
&["h", "d"]
}
fn parameter(&self, name: &str) -> Option<f64> {
match name {
"h" => Some(self.params.h),
"d" => Some(self.params.d),
_ => None,
}
}
fn with_parameter(&self, name: &str, value: f64) -> Option<Box<dyn Machine>> {
let mut params = self.params;
match name {
"h" => params.h = value,
"d" => params.d = value,
_ => return None,
}
let mut rebuilt = <$machine>::new(params, self.s_base, self.f_nom)
.ok()?
.with_speed_voltages(self.speed_voltages);
rebuilt.carry_latched_from(self);
Some(Box::new(rebuilt))
}
};
}
pub trait Machine: std::fmt::Debug {
fn n_states(&self) -> usize;
fn state_names(&self) -> &[&'static str];
fn omega_index(&self) -> usize;
fn tunable(&self) -> &'static [&'static str] {
&[]
}
fn parameter(&self, _name: &str) -> Option<f64> {
None
}
fn with_parameter(&self, _name: &str, _value: f64) -> Option<Box<dyn Machine>> {
None
}
fn internal_emf(&self) -> f64 {
0.0
}
fn carry_latched_from(&mut self, _other: &dyn Machine) {}
fn angle_index(&self) -> usize {
0
}
fn norton_admittance(&self) -> Complex<f64>;
fn derivatives(&self, x: &[f64], v: Complex<f64>, e_fd: f64, p_m: f64, out: &mut [f64]);
fn injection(&self, x: &[f64], v: Complex<f64>) -> Complex<f64>;
fn jacobian(&self, x: &[f64], v: Complex<f64>, e_fd: f64, p_m: f64, out: &mut MachineJacobian);
fn initialize(&mut self, v: Complex<f64>, s: Complex<f64>) -> Result<MachineInit, InitError>;
}
fn base_ratio(mbase: f64, s_base: f64) -> Result<f64, InitError> {
if mbase <= 0.0 {
return Err(InitError::NonPositiveParameter {
name: "mbase",
value: mbase,
});
}
Ok(mbase / s_base)
}
fn require_positive(name: &'static str, value: f64) -> Result<f64, InitError> {
if value <= 0.0 {
return Err(InitError::NonPositiveParameter { name, value });
}
Ok(value)
}
#[derive(Clone, Debug)]
pub struct GenCls {
params: GenClsParams,
s_base: f64,
f_nom: f64,
h: f64,
d: f64,
y: Complex<f64>,
z: Complex<f64>,
omega_base: f64,
e_mag: f64,
speed_voltages: bool,
ra: f64,
xdp: f64,
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct GenClsParams {
pub h: f64,
pub d: f64,
pub ra: f64,
pub xdp: f64,
pub mbase: f64,
}
const GENCLS_STATES: [&str; 2] = ["delta", "omega"];
impl GenCls {
pub fn new(params: GenClsParams, s_base: f64, f_nom: f64) -> Result<Self, InitError> {
require_positive("h", params.h)?;
require_positive("xdp", params.xdp)?;
let k = base_ratio(params.mbase, s_base)?;
let (ra, xdp) = (params.ra / k, params.xdp / k);
let z = Complex::new(ra, xdp);
Ok(Self {
params,
s_base,
f_nom,
h: params.h * k,
d: params.d * k,
y: Complex::new(1.0, 0.0) / z,
z,
omega_base: std::f64::consts::TAU * f_nom,
e_mag: 0.0,
speed_voltages: false,
ra,
xdp,
})
}
pub fn with_speed_voltages(mut self, on: bool) -> Self {
self.speed_voltages = on;
self
}
pub fn e_mag(&self) -> f64 {
self.e_mag
}
pub fn h(&self) -> f64 {
self.h
}
pub fn omega_base(&self) -> f64 {
self.omega_base
}
fn emf(&self, delta: f64) -> Complex<f64> {
Complex::from_polar(self.e_mag, delta)
}
fn speed(&self, omega: f64) -> (f64, f64) {
if self.speed_voltages {
(omega, 1.0)
} else {
(1.0, 0.0)
}
}
fn stator(&self, e: Complex<f64>, v: Complex<f64>, omega: f64) -> Stator {
let (w, dw) = self.speed(omega);
let y = Complex::new(1.0, 0.0) / Complex::new(self.ra, w * self.xdp);
let current = (e * w - v) * y;
let dy = -Complex::new(0.0, 1.0) * self.xdp * y * y * dw;
Stator {
y,
current,
di_domega: e * dw * y + (e * w - v) * dy,
w,
}
}
fn torque(e: Complex<f64>, current: Complex<f64>) -> f64 {
(e * current.conj()).re
}
fn mechanical(&self, p_m: f64, omega: f64) -> (f64, f64) {
if self.speed_voltages {
(p_m / omega, -p_m / (omega * omega))
} else {
(p_m, 0.0)
}
}
}
struct Stator {
y: Complex<f64>,
current: Complex<f64>,
di_domega: Complex<f64>,
w: f64,
}
impl Machine for GenCls {
tunable_machine!(GenCls);
fn internal_emf(&self) -> f64 {
self.e_mag
}
fn carry_latched_from(&mut self, other: &dyn Machine) {
self.e_mag = other.internal_emf();
}
fn n_states(&self) -> usize {
2
}
fn state_names(&self) -> &[&'static str] {
&GENCLS_STATES
}
fn omega_index(&self) -> usize {
1
}
fn norton_admittance(&self) -> Complex<f64> {
self.y
}
fn derivatives(&self, x: &[f64], v: Complex<f64>, _e_fd: f64, p_m: f64, out: &mut [f64]) {
let (delta, omega) = (x[0], x[1]);
let e = self.emf(delta);
let c_e = Self::torque(e, self.stator(e, v, omega).current);
let (mech, _) = self.mechanical(p_m, omega);
out[0] = self.omega_base * (omega - 1.0);
out[1] = (mech - c_e - self.d * (omega - 1.0)) / (2.0 * self.h);
}
fn injection(&self, x: &[f64], v: Complex<f64>) -> Complex<f64> {
self.stator(self.emf(x[0]), v, x[1]).current + self.y * v
}
fn jacobian(
&self,
x: &[f64],
v: Complex<f64>,
_e_fd: f64,
p_m: f64,
out: &mut MachineJacobian,
) {
let (delta, omega) = (x[0], x[1]);
let e = self.emf(delta);
let stator = self.stator(e, v, omega);
let two_h = 2.0 * self.h;
let (_, dmech) = self.mechanical(p_m, omega);
let i = stator.current;
let di_ddelta = Complex::new(0.0, 1.0) * e * stator.w * stator.y;
let di_dvre = -stator.y;
let di_dvim = -Complex::new(0.0, 1.0) * stator.y;
let de_ddelta = Complex::new(0.0, 1.0) * e;
let dce_ddelta = (de_ddelta * i.conj()).re + (e * di_ddelta.conj()).re;
let dce_domega = (e * stator.di_domega.conj()).re;
let dce_dvre = (e * di_dvre.conj()).re;
let dce_dvim = (e * di_dvim.conj()).re;
out.dfdx[0] = 0.0;
out.dfdx[1] = self.omega_base;
out.dfdx[2] = -dce_ddelta / two_h;
out.dfdx[3] = (dmech - dce_domega - self.d) / two_h;
out.dfdv[0] = 0.0;
out.dfdv[1] = 0.0;
out.dfdv[2] = -dce_dvre / two_h;
out.dfdv[3] = -dce_dvim / two_h;
out.dfde[0] = 0.0;
out.dfde[1] = 0.0;
out.dfdp[0] = 0.0;
out.dfdp[1] = if self.speed_voltages {
1.0 / (omega * two_h)
} else {
1.0 / two_h
};
out.didx[0] = di_ddelta.re;
out.didx[1] = stator.di_domega.re;
out.didx[2] = di_ddelta.im;
out.didx[3] = stator.di_domega.im;
let d_re = di_dvre + self.y;
let d_im = di_dvim + Complex::new(0.0, 1.0) * self.y;
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<MachineInit, InitError> {
if v.norm() == 0.0 {
return Err(InitError::ZeroTerminalVoltage);
}
let i = (s / v).conj();
let e = v + self.z * i;
self.e_mag = e.norm();
Ok(MachineInit {
states: vec![e.arg(), 1.0],
e_fd: self.e_mag,
p_m: (e * i.conj()).re,
})
}
}
#[derive(Clone, Debug)]
pub struct GenTransient {
params: GenTransientParams,
s_base: f64,
f_nom: f64,
h: f64,
d: f64,
ra: f64,
xd: f64,
xdp: f64,
xq: f64,
xqp: f64,
td0p: f64,
tq0p: f64,
omega_base: f64,
y: Complex<f64>,
speed_voltages: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct GenTransientParams {
pub h: f64,
pub d: f64,
pub ra: f64,
pub xd: f64,
pub xq: f64,
pub xdp: f64,
pub xqp: f64,
pub td0p: f64,
pub tq0p: f64,
pub mbase: f64,
}
const GENTRANSIENT_STATES: [&str; 4] = ["delta", "omega", "eqp", "edp"];
impl GenTransient {
pub fn new(params: GenTransientParams, s_base: f64, f_nom: f64) -> Result<Self, InitError> {
require_positive("h", params.h)?;
require_positive("xdp", params.xdp)?;
require_positive("xqp", params.xqp)?;
require_positive("td0p", params.td0p)?;
require_positive("tq0p", params.tq0p)?;
if params.xd <= params.xdp {
return Err(InitError::NonPositiveParameter {
name: "xd - xdp",
value: params.xd - params.xdp,
});
}
if params.xq < params.xqp {
return Err(InitError::NonPositiveParameter {
name: "xq - xqp",
value: params.xq - params.xqp,
});
}
let k = base_ratio(params.mbase, s_base)?;
let (ra, xdp, xqp) = (params.ra / k, params.xdp / k, params.xqp / k);
let z_norton = Complex::new(ra, 0.5 * (xdp + xqp));
Ok(Self {
params,
s_base,
f_nom,
h: params.h * k,
d: params.d * k,
ra,
xd: params.xd / k,
xdp,
xq: params.xq / k,
xqp,
td0p: params.td0p,
tq0p: params.tq0p,
omega_base: std::f64::consts::TAU * f_nom,
y: Complex::new(1.0, 0.0) / z_norton,
speed_voltages: false,
})
}
pub fn with_speed_voltages(mut self, on: bool) -> Self {
self.speed_voltages = on;
self
}
pub fn h(&self) -> f64 {
self.h
}
fn speed(&self, omega: f64) -> (f64, f64) {
if self.speed_voltages {
(omega, 1.0)
} else {
(1.0, 0.0)
}
}
fn mechanical(&self, p_m: f64, omega: f64) -> (f64, f64) {
if self.speed_voltages {
(p_m / omega, -p_m / (omega * omega))
} else {
(p_m, 0.0)
}
}
pub(crate) fn to_dq(delta: f64, v: Complex<f64>) -> (f64, f64) {
let (sd, cd) = delta.sin_cos();
(v.re * sd - v.im * cd, v.re * cd + v.im * sd)
}
pub(crate) fn from_dq(delta: f64, i_d: f64, i_q: f64) -> Complex<f64> {
let (sd, cd) = delta.sin_cos();
Complex::new(i_d * sd + i_q * cd, -i_d * cd + i_q * sd)
}
fn det(&self, w: f64) -> f64 {
self.ra * self.ra + w * w * self.xdp * self.xqp
}
fn currents(&self, eqp: f64, edp: f64, v_d: f64, v_q: f64, w: f64) -> (f64, f64, f64, f64) {
let (a, b) = (w * edp - v_d, w * eqp - v_q);
let det = self.det(w);
(
(self.ra * a + w * self.xqp * b) / det,
(-w * self.xdp * a + self.ra * b) / det,
a,
b,
)
}
fn air_gap_power(&self, eqp: f64, edp: f64, i_d: f64, i_q: f64) -> f64 {
edp * i_d + eqp * i_q + (self.xqp - self.xdp) * i_d * i_q
}
}
impl Machine for GenTransient {
tunable_machine!(GenTransient);
fn n_states(&self) -> usize {
4
}
fn state_names(&self) -> &[&'static str] {
&GENTRANSIENT_STATES
}
fn omega_index(&self) -> usize {
1
}
fn norton_admittance(&self) -> Complex<f64> {
self.y
}
fn derivatives(&self, x: &[f64], v: Complex<f64>, e_fd: f64, p_m: f64, out: &mut [f64]) {
let (delta, omega, eqp, edp) = (x[0], x[1], x[2], x[3]);
let (w, _) = self.speed(omega);
let (v_d, v_q) = Self::to_dq(delta, v);
let (i_d, i_q, _, _) = self.currents(eqp, edp, v_d, v_q, w);
let p_e = self.air_gap_power(eqp, edp, i_d, i_q);
let (mech, _) = self.mechanical(p_m, omega);
out[0] = self.omega_base * (omega - 1.0);
out[1] = (mech - p_e - self.d * (omega - 1.0)) / (2.0 * self.h);
out[2] = (-eqp - (self.xd - self.xdp) * i_d + e_fd) / self.td0p;
out[3] = (-edp + (self.xq - self.xqp) * i_q) / self.tq0p;
}
fn injection(&self, x: &[f64], v: Complex<f64>) -> Complex<f64> {
let (delta, omega, eqp, edp) = (x[0], x[1], x[2], x[3]);
let (w, _) = self.speed(omega);
let (v_d, v_q) = Self::to_dq(delta, v);
let (i_d, i_q, _, _) = self.currents(eqp, edp, v_d, v_q, w);
Self::from_dq(delta, i_d, i_q) + self.y * v
}
fn jacobian(
&self,
x: &[f64],
v: Complex<f64>,
_e_fd: f64,
p_m: f64,
out: &mut MachineJacobian,
) {
let (delta, omega, eqp, edp) = (x[0], x[1], x[2], x[3]);
let (w, dw) = self.speed(omega);
let (_, dmech) = self.mechanical(p_m, omega);
let (sd, cd) = delta.sin_cos();
let (v_d, v_q) = Self::to_dq(delta, v);
let (i_d, i_q, a, b) = self.currents(eqp, edp, v_d, v_q, w);
let det = self.det(w);
let two_h = 2.0 * self.h;
let dab: [(f64, f64); 6] = [
(-v_q, v_d), (0.0, 0.0), (0.0, w), (w, 0.0), (-sd, -cd), (cd, -sd), ];
let mut di_d = [0.0; 6];
let mut di_q = [0.0; 6];
for (k, (da, db)) in dab.iter().enumerate() {
di_d[k] = (self.ra * da + w * self.xqp * db) / det;
di_q[k] = (-w * self.xdp * da + self.ra * db) / det;
}
let ddet = 2.0 * w * dw * self.xdp * self.xqp;
di_d[1] = (self.ra * dw * edp + dw * self.xqp * b + w * self.xqp * dw * eqp) / det
- i_d * ddet / det;
di_q[1] = (-dw * self.xdp * a - w * self.xdp * dw * edp + self.ra * dw * eqp) / det
- i_q * ddet / det;
let sal = self.xqp - self.xdp;
let mut dpe = [0.0; 6];
for (k, dpe_k) in dpe.iter_mut().enumerate() {
*dpe_k = edp * di_d[k] + eqp * di_q[k] + sal * (i_q * di_d[k] + i_d * di_q[k]);
}
dpe[2] += i_q; dpe[3] += i_d;
let n = 4;
out.dfdx[1] = self.omega_base;
for k in 0..n {
out.dfdx[n + k] = -dpe[k] / two_h;
}
out.dfdx[n + 1] += (dmech - self.d) / two_h;
for k in 0..n {
out.dfdx[2 * n + k] = -(self.xd - self.xdp) * di_d[k] / self.td0p;
out.dfdx[3 * n + k] = (self.xq - self.xqp) * di_q[k] / self.tq0p;
}
out.dfdx[2 * n + 2] -= 1.0 / self.td0p;
out.dfdx[3 * n + 3] -= 1.0 / self.tq0p;
for (col, k) in [(0usize, 4usize), (1, 5)] {
out.dfdv[2 + col] = -dpe[k] / two_h;
out.dfdv[4 + col] = -(self.xd - self.xdp) * di_d[k] / self.td0p;
out.dfdv[6 + col] = (self.xq - self.xqp) * di_q[k] / self.tq0p;
}
out.dfde[2] = 1.0 / self.td0p;
out.dfdp[1] = if self.speed_voltages {
1.0 / (omega * two_h)
} else {
1.0 / two_h
};
for k in 0..4 {
let (mut d_re, mut d_im) = (di_d[k] * sd + di_q[k] * cd, -di_d[k] * cd + di_q[k] * sd);
if k == 0 {
d_re += i_d * cd - i_q * sd;
d_im += i_d * sd + i_q * cd;
}
out.didx[k] = d_re;
out.didx[n + k] = d_im;
}
let (g, b) = (self.y.re, self.y.im);
for (col, k) in [(0usize, 4usize), (1, 5)] {
out.didv[col] = di_d[k] * sd + di_q[k] * cd;
out.didv[2 + col] = -di_d[k] * cd + di_q[k] * sd;
}
out.didv[0] += g;
out.didv[1] += -b;
out.didv[2] += b;
out.didv[3] += g;
}
fn initialize(&mut self, v: Complex<f64>, s: Complex<f64>) -> Result<MachineInit, InitError> {
if v.norm() == 0.0 {
return Err(InitError::ZeroTerminalVoltage);
}
let i = (s / v).conj();
let e_q = v + Complex::new(self.ra, self.xq) * i;
let delta = e_q.arg();
let (v_d, v_q) = Self::to_dq(delta, v);
let (i_d, i_q) = Self::to_dq(delta, i);
let eqp = v_q + self.ra * i_q + self.xdp * i_d;
let edp = v_d + self.ra * i_d - self.xqp * i_q;
let e_fd = eqp + (self.xd - self.xdp) * i_d;
let p_m = self.air_gap_power(eqp, edp, i_d, i_q);
Ok(MachineInit {
states: vec![delta, 1.0, eqp, edp],
e_fd,
p_m,
})
}
}
pub fn bare(machine: Box<dyn Machine>) -> Box<dyn DynamicModel> {
Box::new(super::unit::GeneratingUnit::machine_only(machine))
}
#[derive(Clone, Debug)]
pub struct GenRound {
params: GenRoundParams,
s_base: f64,
f_nom: f64,
h: f64,
d: f64,
ra: f64,
xd: f64,
xdp: f64,
xdpp: f64,
xq: f64,
xqp: f64,
xqpp: f64,
xl: f64,
td0p: f64,
tq0p: f64,
td0pp: f64,
tq0pp: f64,
ad: f64,
bd: f64,
aq: f64,
bq: f64,
kd: f64,
kq: f64,
omega_base: f64,
y: Complex<f64>,
speed_voltages: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct GenRoundParams {
pub h: f64,
pub d: f64,
pub ra: f64,
pub xd: f64,
pub xq: f64,
pub xdp: f64,
pub xqp: f64,
pub xdpp: f64,
pub xqpp: f64,
pub xl: f64,
pub td0p: f64,
pub tq0p: f64,
pub td0pp: f64,
pub tq0pp: f64,
pub mbase: f64,
}
const GENROUND_STATES: [&str; 6] = ["delta", "omega", "eqp", "edp", "psi1d", "psi2q"];
impl GenRound {
pub fn new(params: GenRoundParams, s_base: f64, f_nom: f64) -> Result<Self, InitError> {
require_positive("h", params.h)?;
require_positive("td0p", params.td0p)?;
require_positive("tq0p", params.tq0p)?;
require_positive("td0pp", params.td0pp)?;
require_positive("tq0pp", params.tq0pp)?;
for (name, lo, hi) in [
("xdpp - xl", params.xl, params.xdpp),
("xdp - xdpp", params.xdpp, params.xdp),
("xd - xdp", params.xdp, params.xd),
("xqpp - xl", params.xl, params.xqpp),
("xqp - xqpp", params.xqpp, params.xqp),
("xq - xqp", params.xqp, params.xq),
] {
if hi <= lo {
return Err(InitError::NonPositiveParameter {
name,
value: hi - lo,
});
}
}
let k = base_ratio(params.mbase, s_base)?;
let (xd, xdp, xdpp) = (params.xd / k, params.xdp / k, params.xdpp / k);
let (xq, xqp, xqpp) = (params.xq / k, params.xqp / k, params.xqpp / k);
let (ra, xl) = (params.ra / k, params.xl / k);
let bd = (xdp - xdpp) / (xdp - xl);
let bq = (xqp - xqpp) / (xqp - xl);
let z_norton = Complex::new(ra, 0.5 * (xdpp + xqpp));
Ok(Self {
params,
s_base,
f_nom,
h: params.h * k,
d: params.d * k,
ra,
xd,
xdp,
xdpp,
xq,
xqp,
xqpp,
xl,
td0p: params.td0p,
tq0p: params.tq0p,
td0pp: params.td0pp,
tq0pp: params.tq0pp,
ad: 1.0 - bd,
bd,
aq: 1.0 - bq,
bq,
kd: bd / (xdp - xl),
kq: bq / (xqp - xl),
omega_base: std::f64::consts::TAU * f_nom,
y: Complex::new(1.0, 0.0) / z_norton,
speed_voltages: false,
})
}
pub fn with_speed_voltages(mut self, on: bool) -> Self {
self.speed_voltages = on;
self
}
pub fn h(&self) -> f64 {
self.h
}
fn speed(&self, omega: f64) -> (f64, f64) {
if self.speed_voltages {
(omega, 1.0)
} else {
(1.0, 0.0)
}
}
fn mechanical(&self, p_m: f64, omega: f64) -> (f64, f64) {
if self.speed_voltages {
(p_m / omega, -p_m / (omega * omega))
} else {
(p_m, 0.0)
}
}
pub fn subtransient_impedance(&self) -> Complex<f64> {
Complex::new(self.ra, 0.5 * (self.xdpp + self.xqpp))
}
fn subtransient_emf(&self, eqp: f64, edp: f64, psi1d: f64, psi2q: f64) -> (f64, f64) {
(
self.ad * eqp + self.bd * psi1d,
self.aq * edp + self.bq * psi2q,
)
}
fn det(&self, w: f64) -> f64 {
self.ra * self.ra + w * w * self.xdpp * self.xqpp
}
fn currents(&self, eqpp: f64, edpp: f64, v_d: f64, v_q: f64, w: f64) -> (f64, f64, f64, f64) {
let (a, b) = (w * edpp - v_d, w * eqpp - v_q);
let det = self.det(w);
(
(self.ra * a + w * self.xqpp * b) / det,
(-w * self.xdpp * a + self.ra * b) / det,
a,
b,
)
}
}
impl Machine for GenRound {
tunable_machine!(GenRound);
fn n_states(&self) -> usize {
6
}
fn state_names(&self) -> &[&'static str] {
&GENROUND_STATES
}
fn omega_index(&self) -> usize {
1
}
fn norton_admittance(&self) -> Complex<f64> {
self.y
}
fn derivatives(&self, x: &[f64], v: Complex<f64>, e_fd: f64, p_m: f64, out: &mut [f64]) {
let (delta, omega, eqp, edp, psi1d, psi2q) = (x[0], x[1], x[2], x[3], x[4], x[5]);
let (w, _) = self.speed(omega);
let (v_d, v_q) = GenTransient::to_dq(delta, v);
let (eqpp, edpp) = self.subtransient_emf(eqp, edp, psi1d, psi2q);
let (i_d, i_q, _, _) = self.currents(eqpp, edpp, v_d, v_q, w);
let (mech, _) = self.mechanical(p_m, omega);
let p_e = edpp * i_d + eqpp * i_q + (self.xqpp - self.xdpp) * i_d * i_q;
let delta_d = psi1d - eqp + (self.xdp - self.xl) * i_d;
let delta_q = psi2q - edp - (self.xqp - self.xl) * i_q;
out[0] = self.omega_base * (omega - 1.0);
out[1] = (mech - p_e - self.d * (omega - 1.0)) / (2.0 * self.h);
out[2] = (-eqp - (self.xd - self.xdp) * (i_d - self.kd * delta_d) + e_fd) / self.td0p;
out[3] = (-edp + (self.xq - self.xqp) * (i_q + self.kq * delta_q)) / self.tq0p;
out[4] = -delta_d / self.td0pp;
out[5] = -delta_q / self.tq0pp;
}
fn injection(&self, x: &[f64], v: Complex<f64>) -> Complex<f64> {
let (delta, omega, eqp, edp, psi1d, psi2q) = (x[0], x[1], x[2], x[3], x[4], x[5]);
let (w, _) = self.speed(omega);
let (v_d, v_q) = GenTransient::to_dq(delta, v);
let (eqpp, edpp) = self.subtransient_emf(eqp, edp, psi1d, psi2q);
let (i_d, i_q, _, _) = self.currents(eqpp, edpp, v_d, v_q, w);
GenTransient::from_dq(delta, i_d, i_q) + self.y * v
}
fn jacobian(
&self,
x: &[f64],
v: Complex<f64>,
_e_fd: f64,
p_m: f64,
out: &mut MachineJacobian,
) {
let (delta, omega, eqp, edp, psi1d, psi2q) = (x[0], x[1], x[2], x[3], x[4], x[5]);
let (w, dw) = self.speed(omega);
let (_, dmech) = self.mechanical(p_m, omega);
let (sd, cd) = delta.sin_cos();
let (v_d, v_q) = GenTransient::to_dq(delta, v);
let (eqpp, edpp) = self.subtransient_emf(eqp, edp, psi1d, psi2q);
let (i_d, i_q, a, b) = self.currents(eqpp, edpp, v_d, v_q, w);
let det = self.det(w);
let two_h = 2.0 * self.h;
let n = 6;
let dab: [(f64, f64); 8] = [
(-v_q, v_d), (0.0, 0.0), (0.0, w * self.ad), (w * self.aq, 0.0), (0.0, w * self.bd), (w * self.bq, 0.0), (-sd, -cd), (cd, -sd), ];
let deqpp = [0.0, 0.0, self.ad, 0.0, self.bd, 0.0, 0.0, 0.0];
let dedpp = [0.0, 0.0, 0.0, self.aq, 0.0, self.bq, 0.0, 0.0];
let mut di_d = [0.0; 8];
let mut di_q = [0.0; 8];
for (k, (da, db)) in dab.iter().enumerate() {
di_d[k] = (self.ra * da + w * self.xqpp * db) / det;
di_q[k] = (-w * self.xdpp * da + self.ra * db) / det;
}
let ddet = 2.0 * w * dw * self.xdpp * self.xqpp;
di_d[1] = (self.ra * dw * edpp + dw * self.xqpp * b + w * self.xqpp * dw * eqpp) / det
- i_d * ddet / det;
di_q[1] = (-dw * self.xdpp * a - w * self.xdpp * dw * edpp + self.ra * dw * eqpp) / det
- i_q * ddet / det;
let sal = self.xqpp - self.xdpp;
let (xdd, xqq) = (self.xdp - self.xl, self.xqp - self.xl);
let mut dpe = [0.0; 8];
let mut d_delta_d = [0.0; 8];
let mut d_delta_q = [0.0; 8];
for k in 0..8 {
dpe[k] = edpp * di_d[k]
+ eqpp * di_q[k]
+ sal * (i_q * di_d[k] + i_d * di_q[k])
+ i_d * dedpp[k]
+ i_q * deqpp[k];
d_delta_d[k] = xdd * di_d[k];
d_delta_q[k] = -xqq * di_q[k];
}
d_delta_d[4] += 1.0; d_delta_d[2] -= 1.0; d_delta_q[5] += 1.0; d_delta_q[3] -= 1.0;
out.dfdx[1] = self.omega_base;
for k in 0..n {
out.dfdx[n + k] = -dpe[k] / two_h;
out.dfdx[2 * n + k] =
-(self.xd - self.xdp) * (di_d[k] - self.kd * d_delta_d[k]) / self.td0p;
out.dfdx[3 * n + k] =
(self.xq - self.xqp) * (di_q[k] + self.kq * d_delta_q[k]) / self.tq0p;
out.dfdx[4 * n + k] = -d_delta_d[k] / self.td0pp;
out.dfdx[5 * n + k] = -d_delta_q[k] / self.tq0pp;
}
out.dfdx[n + 1] += (dmech - self.d) / two_h;
out.dfdx[2 * n + 2] -= 1.0 / self.td0p;
out.dfdx[3 * n + 3] -= 1.0 / self.tq0p;
for (col, k) in [(0usize, 6usize), (1, 7)] {
out.dfdv[2 + col] = -dpe[k] / two_h;
out.dfdv[4 + col] =
-(self.xd - self.xdp) * (di_d[k] - self.kd * d_delta_d[k]) / self.td0p;
out.dfdv[6 + col] =
(self.xq - self.xqp) * (di_q[k] + self.kq * d_delta_q[k]) / self.tq0p;
out.dfdv[8 + col] = -d_delta_d[k] / self.td0pp;
out.dfdv[10 + col] = -d_delta_q[k] / self.tq0pp;
}
out.dfde[2] = 1.0 / self.td0p;
out.dfdp[1] = if self.speed_voltages {
1.0 / (omega * two_h)
} else {
1.0 / two_h
};
for k in 0..n {
let (mut d_re, mut d_im) = (di_d[k] * sd + di_q[k] * cd, -di_d[k] * cd + di_q[k] * sd);
if k == 0 {
d_re += i_d * cd - i_q * sd;
d_im += i_d * sd + i_q * cd;
}
out.didx[k] = d_re;
out.didx[n + k] = d_im;
}
let (g, b) = (self.y.re, self.y.im);
for (col, k) in [(0usize, 6usize), (1, 7)] {
out.didv[col] = di_d[k] * sd + di_q[k] * cd;
out.didv[2 + col] = -di_d[k] * cd + di_q[k] * sd;
}
out.didv[0] += g;
out.didv[1] += -b;
out.didv[2] += b;
out.didv[3] += g;
}
fn initialize(&mut self, v: Complex<f64>, s: Complex<f64>) -> Result<MachineInit, InitError> {
if v.norm() == 0.0 {
return Err(InitError::ZeroTerminalVoltage);
}
let i = (s / v).conj();
let e_q = v + Complex::new(self.ra, self.xq) * i;
let delta = e_q.arg();
let (v_d, v_q) = GenTransient::to_dq(delta, v);
let (i_d, i_q) = GenTransient::to_dq(delta, i);
let eqpp = v_q + self.ra * i_q + self.xdpp * i_d;
let edpp = v_d + self.ra * i_d - self.xqpp * i_q;
let edp = (self.xq - self.xqp) * i_q;
let psi2q = edp + (self.xqp - self.xl) * i_q;
let eqp = eqpp + (self.xdp - self.xdpp) * i_d;
let psi1d = eqp - (self.xdp - self.xl) * i_d;
let e_fd = eqp + (self.xd - self.xdp) * i_d;
let p_m = edpp * i_d + eqpp * i_q + (self.xqpp - self.xdpp) * i_d * i_q;
Ok(MachineInit {
states: vec![delta, 1.0, eqp, edp, psi1d, psi2q],
e_fd,
p_m,
})
}
}
#[derive(Clone, Debug)]
pub struct GenSalient {
params: GenSalientParams,
s_base: f64,
f_nom: f64,
h: f64,
d: f64,
ra: f64,
xd: f64,
xdp: f64,
xdpp: f64,
xq: f64,
xqpp: f64,
xl: f64,
td0p: f64,
td0pp: f64,
tq0pp: f64,
ad: f64,
bd: f64,
kd: f64,
omega_base: f64,
y: Complex<f64>,
speed_voltages: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct GenSalientParams {
pub h: f64,
pub d: f64,
pub ra: f64,
pub xd: f64,
pub xq: f64,
pub xdp: f64,
pub xdpp: f64,
pub xqpp: f64,
pub xl: f64,
pub td0p: f64,
pub td0pp: f64,
pub tq0pp: f64,
pub mbase: f64,
}
const GENSALIENT_STATES: [&str; 5] = ["delta", "omega", "eqp", "psi1d", "edpp"];
impl GenSalient {
pub fn new(params: GenSalientParams, s_base: f64, f_nom: f64) -> Result<Self, InitError> {
require_positive("h", params.h)?;
require_positive("td0p", params.td0p)?;
require_positive("td0pp", params.td0pp)?;
require_positive("tq0pp", params.tq0pp)?;
for (name, lo, hi) in [
("xdpp - xl", params.xl, params.xdpp),
("xdp - xdpp", params.xdpp, params.xdp),
("xd - xdp", params.xdp, params.xd),
("xq - xqpp", params.xqpp, params.xq),
] {
if hi <= lo {
return Err(InitError::NonPositiveParameter {
name,
value: hi - lo,
});
}
}
let k = base_ratio(params.mbase, s_base)?;
let (xdp, xdpp, xl) = (params.xdp / k, params.xdpp / k, params.xl / k);
let (ra, xqpp) = (params.ra / k, params.xqpp / k);
let bd = (xdp - xdpp) / (xdp - xl);
let z_norton = Complex::new(ra, 0.5 * (xdpp + xqpp));
Ok(Self {
params,
s_base,
f_nom,
h: params.h * k,
d: params.d * k,
ra,
xd: params.xd / k,
xdp,
xdpp,
xq: params.xq / k,
xqpp,
xl,
td0p: params.td0p,
td0pp: params.td0pp,
tq0pp: params.tq0pp,
ad: 1.0 - bd,
bd,
kd: bd / (xdp - xl),
omega_base: std::f64::consts::TAU * f_nom,
y: Complex::new(1.0, 0.0) / z_norton,
speed_voltages: false,
})
}
pub fn with_speed_voltages(mut self, on: bool) -> Self {
self.speed_voltages = on;
self
}
pub fn h(&self) -> f64 {
self.h
}
fn speed(&self, omega: f64) -> (f64, f64) {
if self.speed_voltages {
(omega, 1.0)
} else {
(1.0, 0.0)
}
}
fn mechanical(&self, p_m: f64, omega: f64) -> (f64, f64) {
if self.speed_voltages {
(p_m / omega, -p_m / (omega * omega))
} else {
(p_m, 0.0)
}
}
fn det(&self, w: f64) -> f64 {
self.ra * self.ra + w * w * self.xdpp * self.xqpp
}
fn currents(&self, eqpp: f64, edpp: f64, v_d: f64, v_q: f64, w: f64) -> (f64, f64, f64, f64) {
let (a, b) = (w * edpp - v_d, w * eqpp - v_q);
let det = self.det(w);
(
(self.ra * a + w * self.xqpp * b) / det,
(-w * self.xdpp * a + self.ra * b) / det,
a,
b,
)
}
}
impl Machine for GenSalient {
tunable_machine!(GenSalient);
fn n_states(&self) -> usize {
5
}
fn state_names(&self) -> &[&'static str] {
&GENSALIENT_STATES
}
fn omega_index(&self) -> usize {
1
}
fn norton_admittance(&self) -> Complex<f64> {
self.y
}
fn derivatives(&self, x: &[f64], v: Complex<f64>, e_fd: f64, p_m: f64, out: &mut [f64]) {
let (delta, omega, eqp, psi1d, edpp) = (x[0], x[1], x[2], x[3], x[4]);
let (w, _) = self.speed(omega);
let (v_d, v_q) = GenTransient::to_dq(delta, v);
let eqpp = self.ad * eqp + self.bd * psi1d;
let (i_d, i_q, _, _) = self.currents(eqpp, edpp, v_d, v_q, w);
let (mech, _) = self.mechanical(p_m, omega);
let c_e = edpp * i_d + eqpp * i_q + (self.xqpp - self.xdpp) * i_d * i_q;
let delta_d = psi1d - eqp + (self.xdp - self.xl) * i_d;
out[0] = self.omega_base * (omega - 1.0);
out[1] = (mech - c_e - self.d * (omega - 1.0)) / (2.0 * self.h);
out[2] = (-eqp - (self.xd - self.xdp) * (i_d - self.kd * delta_d) + e_fd) / self.td0p;
out[3] = -delta_d / self.td0pp;
out[4] = (-edpp + (self.xq - self.xqpp) * i_q) / self.tq0pp;
}
fn injection(&self, x: &[f64], v: Complex<f64>) -> Complex<f64> {
let (delta, omega, eqp, psi1d, edpp) = (x[0], x[1], x[2], x[3], x[4]);
let (w, _) = self.speed(omega);
let (v_d, v_q) = GenTransient::to_dq(delta, v);
let eqpp = self.ad * eqp + self.bd * psi1d;
let (i_d, i_q, _, _) = self.currents(eqpp, edpp, v_d, v_q, w);
GenTransient::from_dq(delta, i_d, i_q) + self.y * v
}
fn jacobian(
&self,
x: &[f64],
v: Complex<f64>,
_e_fd: f64,
p_m: f64,
out: &mut MachineJacobian,
) {
let (delta, omega, eqp, psi1d, edpp) = (x[0], x[1], x[2], x[3], x[4]);
let (w, dw) = self.speed(omega);
let (_, dmech) = self.mechanical(p_m, omega);
let (sd, cd) = delta.sin_cos();
let (v_d, v_q) = GenTransient::to_dq(delta, v);
let eqpp = self.ad * eqp + self.bd * psi1d;
let (i_d, i_q, a, b) = self.currents(eqpp, edpp, v_d, v_q, w);
let det = self.det(w);
let two_h = 2.0 * self.h;
let n = 5;
let dab: [(f64, f64); 7] = [
(-v_q, v_d),
(0.0, 0.0),
(0.0, w * self.ad),
(0.0, w * self.bd),
(w, 0.0),
(-sd, -cd),
(cd, -sd),
];
let deqpp = [0.0, 0.0, self.ad, self.bd, 0.0, 0.0, 0.0];
let dedpp = [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0];
let mut di_d = [0.0; 7];
let mut di_q = [0.0; 7];
for (k, (da, db)) in dab.iter().enumerate() {
di_d[k] = (self.ra * da + w * self.xqpp * db) / det;
di_q[k] = (-w * self.xdpp * da + self.ra * db) / det;
}
let ddet = 2.0 * w * dw * self.xdpp * self.xqpp;
di_d[1] = (self.ra * dw * edpp + dw * self.xqpp * b + w * self.xqpp * dw * eqpp) / det
- i_d * ddet / det;
di_q[1] = (-dw * self.xdpp * a - w * self.xdpp * dw * edpp + self.ra * dw * eqpp) / det
- i_q * ddet / det;
let sal = self.xqpp - self.xdpp;
let xdd = self.xdp - self.xl;
let mut dce = [0.0; 7];
let mut d_delta_d = [0.0; 7];
for k in 0..7 {
dce[k] = edpp * di_d[k]
+ eqpp * di_q[k]
+ sal * (i_q * di_d[k] + i_d * di_q[k])
+ i_d * dedpp[k]
+ i_q * deqpp[k];
d_delta_d[k] = xdd * di_d[k];
}
d_delta_d[3] += 1.0; d_delta_d[2] -= 1.0;
out.dfdx[1] = self.omega_base;
for k in 0..n {
out.dfdx[n + k] = -dce[k] / two_h;
out.dfdx[2 * n + k] =
-(self.xd - self.xdp) * (di_d[k] - self.kd * d_delta_d[k]) / self.td0p;
out.dfdx[3 * n + k] = -d_delta_d[k] / self.td0pp;
out.dfdx[4 * n + k] = (self.xq - self.xqpp) * di_q[k] / self.tq0pp;
}
out.dfdx[n + 1] += (dmech - self.d) / two_h;
out.dfdx[2 * n + 2] -= 1.0 / self.td0p;
out.dfdx[4 * n + 4] -= 1.0 / self.tq0pp;
for (col, k) in [(0usize, 5usize), (1, 6)] {
out.dfdv[2 + col] = -dce[k] / two_h;
out.dfdv[4 + col] =
-(self.xd - self.xdp) * (di_d[k] - self.kd * d_delta_d[k]) / self.td0p;
out.dfdv[6 + col] = -d_delta_d[k] / self.td0pp;
out.dfdv[8 + col] = (self.xq - self.xqpp) * di_q[k] / self.tq0pp;
}
out.dfde[2] = 1.0 / self.td0p;
out.dfdp[1] = if self.speed_voltages {
1.0 / (omega * two_h)
} else {
1.0 / two_h
};
for k in 0..n {
let (mut d_re, mut d_im) = (di_d[k] * sd + di_q[k] * cd, -di_d[k] * cd + di_q[k] * sd);
if k == 0 {
d_re += i_d * cd - i_q * sd;
d_im += i_d * sd + i_q * cd;
}
out.didx[k] = d_re;
out.didx[n + k] = d_im;
}
let (g, b_y) = (self.y.re, self.y.im);
for (col, k) in [(0usize, 5usize), (1, 6)] {
out.didv[col] = di_d[k] * sd + di_q[k] * cd;
out.didv[2 + col] = -di_d[k] * cd + di_q[k] * sd;
}
out.didv[0] += g;
out.didv[1] += -b_y;
out.didv[2] += b_y;
out.didv[3] += g;
}
fn initialize(&mut self, v: Complex<f64>, s: Complex<f64>) -> Result<MachineInit, InitError> {
if v.norm() == 0.0 {
return Err(InitError::ZeroTerminalVoltage);
}
let i = (s / v).conj();
let e_q = v + Complex::new(self.ra, self.xq) * i;
let delta = e_q.arg();
let (_, v_q) = GenTransient::to_dq(delta, v);
let (i_d, i_q) = GenTransient::to_dq(delta, i);
let eqpp = v_q + self.ra * i_q + self.xdpp * i_d;
let edpp = (self.xq - self.xqpp) * i_q;
let eqp = eqpp + (self.xdp - self.xdpp) * i_d;
let psi1d = eqp - (self.xdp - self.xl) * i_d;
let e_fd = eqp + (self.xd - self.xdp) * i_d;
let p_m = edpp * i_d + eqpp * i_q + (self.xqpp - self.xdpp) * i_d * i_q;
Ok(MachineInit {
states: vec![delta, 1.0, eqp, psi1d, edpp],
e_fd,
p_m,
})
}
}