use std::collections::HashMap;
use serde::Deserialize;
use super::branch_flow::Terminal;
use super::types::{Bus, BusType, Line, Line3Ph, Transformer, Transformer3PhSeq, ZipKind, ZipTerm};
use super::network::{
half_open_branch_shunt,
source_impedance_pu, source_impedance_pu_seq, transformer_tap, transformer_admittances,
transformer_admittances_ex, transformer_seq_params, tap_ratio_from_voltages, three_winding_star_params,
ShuntAdm, ShuntAdm3Ph,
};
use num_complex::Complex;
#[derive(Deserialize)]
pub struct PgmInput {
pub data: PgmData,
}
#[derive(Deserialize)]
pub struct PgmData {
pub node: Vec<PgmNode>,
#[serde(default)]
pub line: Vec<PgmLine>,
pub source: Vec<PgmSource>,
#[serde(default)]
pub sym_load: Vec<PgmSymLoad>,
#[serde(default)]
pub asym_load: Vec<PgmAsymLoad>,
#[serde(default)]
pub sym_gen: Vec<PgmSymGen>,
#[serde(default)]
pub asym_gen: Vec<PgmAsymGen>,
#[serde(default)]
pub shunt: Vec<PgmShunt>,
#[serde(default)]
pub transformer: Vec<PgmTransformer>,
#[serde(default)]
pub three_winding_transformer: Vec<PgmThreeWindingTransformer>,
#[serde(default)]
pub voltage_regulator: Vec<PgmVoltageRegulator>,
#[serde(default)]
pub link: Vec<PgmLink>,
#[serde(default)]
pub sym_voltage_sensor: Vec<PgmSymVoltageSensor>,
#[serde(default)]
pub sym_power_sensor: Vec<PgmSymPowerSensor>,
#[serde(default)]
pub asym_voltage_sensor: Vec<PgmAsymVoltageSensor>,
#[serde(default)]
pub asym_power_sensor: Vec<PgmAsymPowerSensor>,
#[serde(default)]
pub sym_current_sensor: Vec<PgmSymCurrentSensor>,
#[serde(default)]
pub asym_current_sensor: Vec<PgmAsymCurrentSensor>,
}
#[derive(Deserialize)]
pub struct PgmNode {
pub id: u64,
pub u_rated: f64,
}
#[derive(Deserialize)]
pub struct PgmLine {
pub id: u64,
pub from_node: u64,
pub to_node: u64,
pub from_status: u8,
pub to_status: u8,
pub r1: f64,
pub x1: f64,
pub c1: f64,
#[serde(default)]
pub tan1: f64,
#[serde(default = "nan")]
pub r0: f64,
#[serde(default = "nan")]
pub x0: f64,
#[serde(default = "nan")]
pub c0: f64,
#[serde(default)]
pub tan0: f64,
}
pub const PGM_LINK_Y: Complex<f64> = Complex::new(1e8, 1e8);
pub use crate::topology::IDEAL_CONNECTION_Y as LINK_Y;
fn one() -> f64 { 1.0 }
fn nan() -> f64 { f64::NAN }
fn default_sk() -> f64 { 1e10 }
fn default_rx_ratio() -> f64 { 0.1 }
fn nan3() -> [f64; 3] { [f64::NAN; 3] }
fn de_f64_or_inf<'de, D>(d: D) -> Result<f64, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::{Error, Unexpected};
#[derive(Deserialize)]
#[serde(untagged)]
enum Raw {
Num(f64),
Str(String),
}
match Option::<Raw>::deserialize(d)? {
None => Ok(f64::NAN),
Some(Raw::Num(v)) => Ok(v),
Some(Raw::Str(s)) => match s.trim() {
"inf" | "+inf" | "Infinity" => Ok(f64::INFINITY),
"-inf" | "-Infinity" => Ok(f64::NEG_INFINITY),
"nan" | "NaN" => Ok(f64::NAN),
other => Err(D::Error::invalid_value(
Unexpected::Str(other),
&"a number or the string \"inf\"",
)),
},
}
}
#[derive(Deserialize)]
pub struct PgmSource {
pub id: u64,
pub node: u64,
pub status: u8,
#[serde(default = "one")]
pub u_ref: f64,
#[serde(default = "default_sk")]
pub sk: f64,
#[serde(default = "default_rx_ratio")]
pub rx_ratio: f64,
#[serde(default = "one")]
pub z01_ratio: f64,
}
fn default_load_type() -> u8 { 0 }
#[derive(Deserialize)]
pub struct PgmSymLoad {
pub id: u64,
pub node: u64,
pub status: u8,
#[serde(rename = "type", default = "default_load_type")]
pub load_type: u8,
#[serde(default = "nan")]
pub p_specified: f64,
#[serde(default = "nan")]
pub q_specified: f64,
}
#[derive(Deserialize)]
pub struct PgmAsymLoad {
pub id: u64,
pub node: u64,
pub status: u8,
#[serde(rename = "type", default = "default_load_type")]
pub load_type: u8,
#[serde(default = "nan3")]
pub p_specified: [f64; 3],
#[serde(default = "nan3")]
pub q_specified: [f64; 3],
}
#[derive(Deserialize)]
pub struct PgmSymGen {
pub id: u64,
pub node: u64,
pub status: u8,
#[serde(rename = "type", default = "default_load_type")]
pub load_type: u8,
#[serde(default = "nan")]
pub p_specified: f64,
#[serde(default = "nan")]
pub q_specified: f64,
}
#[derive(Deserialize)]
pub struct PgmVoltageRegulator {
pub id: u64,
pub regulated_object: u64,
pub status: u8,
pub u_ref: f64,
#[serde(default = "nan")]
pub q_min: f64,
#[serde(default = "nan")]
pub q_max: f64,
}
#[derive(Deserialize)]
pub struct PgmAsymGen {
pub id: u64,
pub node: u64,
pub status: u8,
#[serde(rename = "type", default = "default_load_type")]
pub load_type: u8,
#[serde(default = "nan3")]
pub p_specified: [f64; 3],
#[serde(default = "nan3")]
pub q_specified: [f64; 3],
}
#[derive(Deserialize)]
pub struct PgmShunt {
pub id: u64,
pub node: u64,
pub status: u8,
pub g1: f64,
pub b1: f64,
#[serde(default = "nan")]
pub g0: f64,
#[serde(default = "nan")]
pub b0: f64,
}
#[derive(Deserialize)]
pub struct PgmThreeWindingTransformer {
pub id: u64,
pub node_1: u64,
pub node_2: u64,
pub node_3: u64,
pub status_1: u8,
pub status_2: u8,
pub status_3: u8,
pub u1: f64,
pub u2: f64,
pub u3: f64,
pub sn_1: f64,
pub sn_2: f64,
pub sn_3: f64,
pub uk_12: f64,
pub uk_13: f64,
pub uk_23: f64,
pub pk_12: f64,
pub pk_13: f64,
pub pk_23: f64,
pub i0: f64,
pub p0: f64,
pub clock_12: i32,
pub clock_13: i32,
pub tap_side: u8,
pub tap_pos: i32,
pub tap_min: i32,
pub tap_max: i32,
pub tap_nom: i32,
pub tap_size: f64,
}
#[derive(Deserialize, Clone)]
pub struct PgmTransformer {
pub id: u64,
pub from_node: u64,
pub to_node: u64,
pub from_status: u8,
pub to_status: u8,
pub u1: f64,
pub u2: f64,
pub sn: f64,
pub uk: f64,
pub pk: f64,
pub i0: f64,
pub p0: f64,
#[serde(default)]
pub winding_from: u8,
#[serde(default)]
pub winding_to: u8,
pub clock: i32,
pub tap_side: u8,
pub tap_pos: i32,
pub tap_min: i32,
pub tap_max: i32,
pub tap_nom: i32,
pub tap_size: f64,
}
#[derive(Deserialize)]
pub struct PgmSymVoltageSensor {
pub id: u64,
pub measured_object: u64,
#[serde(default = "nan")]
pub u_measured: f64,
#[serde(default = "nan")]
pub u_angle_measured: f64,
#[serde(default = "nan")]
pub u_sigma: f64,
#[serde(default = "nan")]
pub u_angle_sigma: f64,
}
#[derive(Deserialize)]
pub struct PgmSymPowerSensor {
pub id: u64,
pub measured_object: u64,
pub measured_terminal_type: u8,
#[serde(default = "nan", deserialize_with = "de_f64_or_inf")]
pub p_measured: f64,
#[serde(default = "nan", deserialize_with = "de_f64_or_inf")]
pub q_measured: f64,
#[serde(default = "nan", deserialize_with = "de_f64_or_inf")]
pub power_sigma: f64,
#[serde(default = "nan", deserialize_with = "de_f64_or_inf")]
pub p_sigma: f64,
#[serde(default = "nan", deserialize_with = "de_f64_or_inf")]
pub q_sigma: f64,
}
#[derive(Deserialize)]
pub struct PgmAsymVoltageSensor {
pub id: u64,
pub measured_object: u64,
#[serde(default = "nan3")]
pub u_measured: [f64; 3],
#[serde(default = "nan3")]
pub u_angle_measured: [f64; 3],
#[serde(default = "nan", deserialize_with = "de_f64_or_inf")]
pub u_sigma: f64,
}
#[derive(Deserialize)]
pub struct PgmAsymPowerSensor {
pub id: u64,
pub measured_object: u64,
pub measured_terminal_type: u8,
#[serde(default = "nan3")]
pub p_measured: [f64; 3],
#[serde(default = "nan3")]
pub q_measured: [f64; 3],
#[serde(default = "nan", deserialize_with = "de_f64_or_inf")]
pub power_sigma: f64,
#[serde(default = "nan3")]
pub p_sigma: [f64; 3],
#[serde(default = "nan3")]
pub q_sigma: [f64; 3],
}
#[derive(Deserialize)]
pub struct PgmSymCurrentSensor {
pub id: u64,
pub measured_object: u64,
pub measured_terminal_type: u8,
#[serde(default)]
pub angle_measurement_type: u8,
#[serde(default = "nan", deserialize_with = "de_f64_or_inf")]
pub i_measured: f64,
#[serde(default = "nan", deserialize_with = "de_f64_or_inf")]
pub i_angle_measured: f64,
#[serde(default = "nan", deserialize_with = "de_f64_or_inf")]
pub i_sigma: f64,
#[serde(default = "nan", deserialize_with = "de_f64_or_inf")]
pub i_angle_sigma: f64,
}
#[derive(Deserialize)]
pub struct PgmAsymCurrentSensor {
pub id: u64,
pub measured_object: u64,
pub measured_terminal_type: u8,
#[serde(default)]
pub angle_measurement_type: u8,
#[serde(default = "nan3")]
pub i_measured: [f64; 3],
#[serde(default = "nan3")]
pub i_angle_measured: [f64; 3],
#[serde(default = "nan", deserialize_with = "de_f64_or_inf")]
pub i_sigma: f64,
#[serde(default = "nan", deserialize_with = "de_f64_or_inf")]
pub i_angle_sigma: f64,
}
#[derive(Deserialize)]
pub struct PgmOutput<N> {
pub data: PgmOutputData<N>,
}
#[derive(Deserialize)]
pub struct PgmOutputData<N> {
pub node: Vec<N>,
}
#[derive(Deserialize)]
pub struct PgmBatchOutput<N> {
pub data: Vec<PgmOutputData<N>>,
}
#[derive(Deserialize)]
pub struct PgmNodeOutput {
pub id: u64,
pub u_pu: f64,
pub u_angle: f64,
#[serde(default)]
pub u: f64,
}
#[derive(Debug, Deserialize)]
pub struct PgmLineOutput {
pub id: u64,
#[serde(default)]
pub energized: u8,
pub p_from: f64,
pub q_from: f64,
pub p_to: f64,
pub q_to: f64,
}
#[derive(Debug, Deserialize)]
pub struct PgmNodeAsymOutput {
pub id: u64,
pub u_pu: [f64; 3],
pub u_angle: [f64; 3],
}
#[derive(Deserialize)]
pub struct PgmLink {
pub id: u64,
pub from_node: u64,
pub to_node: u64,
pub from_status: u8,
pub to_status: u8,
}
fn line_y_shunt(ln: &PgmLine, omega: f64, z_base: f64) -> Complex<f64> {
Complex::new(ln.tan1, 1.0) * (omega * ln.c1 * z_base)
}
fn half_open_line(ln: &PgmLine, omega: f64, z_base: f64, idx: usize) -> Line {
let y_shunt = line_y_shunt(ln, omega, z_base);
let (r, x) = crate::topology::clamp_branch_impedance(ln.r1 / z_base, ln.x1 / z_base);
let y_series = Complex::new(1.0, 0.0) / Complex::new(r, x);
let y_eq = half_open_branch_shunt(y_series, y_shunt);
Line { from: idx, to: idx, r: 0.0, x: 0.0, b_shunt: y_eq.im, g_shunt: y_eq.re }
}
pub fn node_id_to_idx(input: &PgmInput) -> HashMap<u64, usize> {
let mut ids: Vec<u64> = input.data.node.iter().map(|n| n.id).collect();
ids.sort_unstable();
ids.into_iter().enumerate().map(|(idx, id)| (id, idx)).collect()
}
pub fn pgm_shunts_1ph(
input: &PgmInput,
id_to_idx: &HashMap<u64, usize>,
s_base_va: f64,
) -> Vec<ShuntAdm> {
let id_to_u_rated: HashMap<u64, f64> = input.data.node.iter().map(|n| (n.id, n.u_rated)).collect();
input.data.shunt.iter()
.filter(|s| s.status != 0)
.map(|s| {
let u_rated = id_to_u_rated[&s.node];
let base_y = s_base_va / (u_rated * u_rated);
ShuntAdm { at: id_to_idx[&s.node], y: Complex::new(s.g1, s.b1) / base_y }
})
.collect()
}
pub fn pgm_shunts_3ph(
input: &PgmInput,
id_to_idx: &HashMap<u64, usize>,
s_base_va: f64,
) -> Vec<ShuntAdm3Ph> {
let id_to_u_rated: HashMap<u64, f64> = input.data.node.iter().map(|n| (n.id, n.u_rated)).collect();
input.data.shunt.iter()
.filter(|s| s.status != 0)
.map(|s| {
let u_rated = id_to_u_rated[&s.node];
let base_y = s_base_va / (u_rated * u_rated);
ShuntAdm3Ph {
at: id_to_idx[&s.node],
y1: Complex::new(s.g1, s.b1) / base_y,
y0: Complex::new(s.g0, s.b0) / base_y,
}
})
.collect()
}
pub fn pgm_transformers_3ph(
input: &PgmInput,
id_to_idx: &HashMap<u64, usize>,
s_base_va: f64,
) -> Vec<Transformer3PhSeq> {
let id_to_u_rated: HashMap<u64, f64> =
input.data.node.iter().map(|n| (n.id, n.u_rated)).collect();
input.data.transformer.iter()
.map(|t| {
let tap = transformer_tap(
t.u1, t.u2, t.tap_side, t.tap_pos, t.tap_min, t.tap_max, t.tap_nom, t.tap_size,
t.clock, id_to_u_rated[&t.from_node], id_to_u_rated[&t.to_node],
);
let (y_series, y_shunt) = transformer_admittances(t.u2, t.sn, t.uk, t.pk, t.i0, t.p0, s_base_va);
let (y0, y1, y2) = transformer_seq_params(
y_series, y_shunt, tap, t.from_status, t.to_status,
t.winding_from, t.winding_to, t.sn, t.uk, s_base_va, t.clock,
);
Transformer3PhSeq {
from: id_to_idx[&t.from_node],
to: id_to_idx[&t.to_node],
y0, y1, y2,
}
})
.collect()
}
#[derive(Clone, Debug)]
pub struct PgmNetwork {
pub buses: Vec<Bus>,
pub lines: Vec<Line>,
pub transformers: Vec<Transformer>,
pub node_idx: HashMap<u64, usize>,
pub branch_idx: HashMap<u64, usize>,
pub three_winding_branch_idx: HashMap<u64, [usize; 3]>,
pub source_branch_idx: HashMap<u64, usize>,
pub appliance_bus: HashMap<u64, usize>,
pub zero_injection: Vec<bool>,
pub half_open_terminal: HashMap<u64, Terminal>,
}
impl PgmNetwork {
pub fn resolve_terminal(&self, id: u64, terminal: Terminal) -> Option<(usize, Terminal)> {
let &branch = self.branch_idx.get(&id)?;
match self.half_open_terminal.get(&id) {
Some(&live) if live == terminal => Some((branch, Terminal::From)),
Some(_) => None,
None => Some((branch, terminal)),
}
}
}
pub fn pgm_to_network(
input: PgmInput,
s_base_va: f64,
freq_hz: f64,
) -> PgmNetwork {
let id_to_idx = node_id_to_idx(&input);
let id_to_u_rated: HashMap<u64, f64> = input.data.node.iter()
.map(|n| (n.id, n.u_rated))
.collect();
let mut p_inj: HashMap<u64, f64> = HashMap::new();
let mut q_inj: HashMap<u64, f64> = HashMap::new();
let mut zip_map: HashMap<u64, Vec<ZipTerm>> = HashMap::new();
let mut accumulate = |node: u64, load_type: u8, p: f64, q: f64, sign: f64| {
if !p.is_finite() && !q.is_finite() {
return;
}
let p = if p.is_finite() { p } else { 0.0 };
let q = if q.is_finite() { q } else { 0.0 };
let s = Complex::new(p, q) * sign / s_base_va;
match load_type {
1 => zip_map.entry(node).or_default().push(ZipTerm { s_const: s, kind: ZipKind::ConstImpedance }),
2 => zip_map.entry(node).or_default().push(ZipTerm { s_const: s, kind: ZipKind::ConstCurrent }),
_ => {
*p_inj.entry(node).or_insert(0.0) += s.re;
*q_inj.entry(node).or_insert(0.0) += s.im;
}
}
};
for load in &input.data.sym_load {
if load.status == 0 { continue; }
accumulate(load.node, load.load_type, load.p_specified, load.q_specified, -1.0);
}
for sgen in &input.data.sym_gen {
if sgen.status == 0 { continue; }
accumulate(sgen.node, sgen.load_type, sgen.p_specified, sgen.q_specified, 1.0);
}
for load in &input.data.asym_load {
if load.status == 0 { continue; }
let p: f64 = load.p_specified.iter().sum();
let q: f64 = load.q_specified.iter().sum();
accumulate(load.node, load.load_type, p, q, -1.0);
}
for agen in &input.data.asym_gen {
if agen.status == 0 { continue; }
let p: f64 = agen.p_specified.iter().sum();
let q: f64 = agen.q_specified.iter().sum();
accumulate(agen.node, agen.load_type, p, q, 1.0);
}
let n_nodes = input.data.node.len();
let mut sorted_ids: Vec<u64> = input.data.node.iter().map(|n| n.id).collect();
sorted_ids.sort_unstable();
let mut opt_buses = vec![None::<Bus>; n_nodes];
for id in &sorted_ids {
let idx = id_to_idx[id];
opt_buses[idx] = Some(Bus {
idx,
bus_type: BusType::PQ,
voltage_mag: 1.0,
voltage_ang: 0.0,
p_spec: *p_inj.get(id).unwrap_or(&0.0),
q_spec: *q_inj.get(id).unwrap_or(&0.0),
q_min: -f64::INFINITY,
q_max: f64::INFINITY,
u_rated: id_to_u_rated[id],
zip_terms: zip_map.remove(id).unwrap_or_default(),
});
}
let mut buses: Vec<Bus> = opt_buses.into_iter().map(|b| b.unwrap()).collect();
let sym_gen_node: HashMap<u64, u64> = input.data.sym_gen.iter().map(|g| (g.id, g.node)).collect();
for vr in &input.data.voltage_regulator {
if vr.status == 0 { continue; }
let Some(&node) = sym_gen_node.get(&vr.regulated_object) else { continue };
let idx = id_to_idx[&node];
if buses[idx].bus_type == BusType::PQ {
buses[idx].bus_type = BusType::PV;
buses[idx].voltage_mag = vr.u_ref;
buses[idx].q_min = if vr.q_min.is_nan() { -f64::INFINITY } else { vr.q_min / s_base_va };
buses[idx].q_max = if vr.q_max.is_nan() { f64::INFINITY } else { vr.q_max / s_base_va };
}
}
let omega = 2.0 * std::f64::consts::PI * freq_hz;
let mut lines: Vec<Line> = Vec::new();
let mut branch_idx: HashMap<u64, usize> = HashMap::new();
let mut three_winding_branch_idx: HashMap<u64, [usize; 3]> = HashMap::new();
let mut source_branch_idx: HashMap<u64, usize> = HashMap::new();
let mut half_open_terminal: HashMap<u64, Terminal> = HashMap::new();
for ln in &input.data.line {
match (ln.from_status, ln.to_status) {
(1, 1) => {
let z_base = id_to_u_rated[&ln.from_node].powi(2) / s_base_va;
let y_shunt = line_y_shunt(ln, omega, z_base);
let (r, x) = crate::topology::clamp_branch_impedance(
ln.r1 / z_base,
ln.x1 / z_base,
);
branch_idx.insert(ln.id, lines.len());
lines.push(Line {
from: id_to_idx[&ln.from_node],
to: id_to_idx[&ln.to_node],
r,
x,
b_shunt: y_shunt.im,
g_shunt: y_shunt.re,
});
}
(1, 0) => {
let z_base = id_to_u_rated[&ln.from_node].powi(2) / s_base_va;
let idx = id_to_idx[&ln.from_node];
branch_idx.insert(ln.id, lines.len());
half_open_terminal.insert(ln.id, Terminal::From);
lines.push(half_open_line(ln, omega, z_base, idx));
}
(0, 1) => {
let z_base = id_to_u_rated[&ln.to_node].powi(2) / s_base_va;
let idx = id_to_idx[&ln.to_node];
branch_idx.insert(ln.id, lines.len());
half_open_terminal.insert(ln.id, Terminal::To);
lines.push(half_open_line(ln, omega, z_base, idx));
}
_ => {}
}
}
let mut transformers: Vec<Transformer> = Vec::new();
let mut transformer_pos: HashMap<u64, usize> = HashMap::new();
let mut three_winding_pos: HashMap<u64, [usize; 3]> = HashMap::new();
for t in &input.data.transformer {
let tap = transformer_tap(
t.u1, t.u2, t.tap_side, t.tap_pos, t.tap_min, t.tap_max, t.tap_nom, t.tap_size, t.clock,
id_to_u_rated[&t.from_node], id_to_u_rated[&t.to_node],
);
let (y_series, y_shunt) = transformer_admittances(t.u2, t.sn, t.uk, t.pk, t.i0, t.p0, s_base_va);
transformer_pos.insert(t.id, transformers.len());
transformers.push(Transformer {
from: id_to_idx[&t.from_node],
to: id_to_idx[&t.to_node],
from_status: t.from_status,
to_status: t.to_status,
y_series,
y_shunt,
tap,
});
}
for ln in &input.data.link {
if ln.from_status == 0 && ln.to_status == 0 {
continue;
}
transformer_pos.insert(ln.id, transformers.len());
transformers.push(Transformer {
from: id_to_idx[&ln.from_node],
to: id_to_idx[&ln.to_node],
from_status: ln.from_status,
to_status: ln.to_status,
y_series: LINK_Y,
y_shunt: Complex::new(0.0, 0.0),
tap: Complex::new(1.0, 0.0),
});
}
for (i, t) in input.data.three_winding_transformer.iter().enumerate() {
let star_idx = n_nodes + i;
let u1_rated = id_to_u_rated[&t.node_1];
let u2_rated = id_to_u_rated[&t.node_2];
let u3_rated = id_to_u_rated[&t.node_3];
let tap_direction = if t.tap_max > t.tap_min { 1.0 } else { -1.0 };
let delta = tap_direction * (t.tap_pos - t.tap_nom) as f64 * t.tap_size;
let (u1_local, u2_local, u3_local) = match t.tap_side {
0 => (t.u1 + delta, t.u2, t.u3),
1 => (t.u1, t.u2 + delta, t.u3),
_ => (t.u1, t.u2, t.u3 + delta),
};
let ((uk_t1, uk_t2, uk_t3), (pk_t1, pk_t2, pk_t3)) = three_winding_star_params(
t.sn_1, t.sn_2, t.sn_3, t.uk_12, t.uk_13, t.uk_23, t.pk_12, t.pk_13, t.pk_23,
);
buses.push(Bus {
idx: star_idx,
bus_type: BusType::PQ,
voltage_mag: 1.0,
voltage_ang: 0.0,
p_spec: 0.0,
q_spec: 0.0,
q_min: -f64::INFINITY,
q_max: f64::INFINITY,
u_rated: u1_rated,
zip_terms: Vec::new(),
});
three_winding_pos.insert(
t.id,
[transformers.len(), transformers.len() + 1, transformers.len() + 2],
);
let t1_tap = tap_ratio_from_voltages(u1_local * u1_rated, u1_local * u1_rated, 0);
let (t1_series, t1_shunt) =
transformer_admittances_ex(u1_local, u1_rated, t.sn_1, uk_t1, pk_t1, t.i0, t.p0, s_base_va);
transformers.push(Transformer {
from: id_to_idx[&t.node_1], to: star_idx,
from_status: t.status_1, to_status: 1,
y_series: t1_series, y_shunt: t1_shunt, tap: t1_tap,
});
let t2_tap = tap_ratio_from_voltages(u2_local * u1_rated, u1_local * u2_rated, 12 - t.clock_12);
let (t2_series, t2_shunt) =
transformer_admittances_ex(u1_local, u1_rated, t.sn_2, uk_t2, pk_t2, 0.0, 0.0, s_base_va);
transformers.push(Transformer {
from: id_to_idx[&t.node_2], to: star_idx,
from_status: t.status_2, to_status: 1,
y_series: t2_series, y_shunt: t2_shunt, tap: t2_tap,
});
let t3_tap = tap_ratio_from_voltages(u3_local * u1_rated, u1_local * u3_rated, 12 - t.clock_13);
let (t3_series, t3_shunt) =
transformer_admittances_ex(u1_local, u1_rated, t.sn_3, uk_t3, pk_t3, 0.0, 0.0, s_base_va);
transformers.push(Transformer {
from: id_to_idx[&t.node_3], to: star_idx,
from_status: t.status_3, to_status: 1,
y_series: t3_series, y_shunt: t3_shunt, tap: t3_tap,
});
}
let n_3wdg = input.data.three_winding_transformer.len();
for (i, src) in input.data.source.iter().filter(|s| s.status != 0).enumerate() {
let virtual_idx = n_nodes + n_3wdg + i;
let (r_s, x_s) = source_impedance_pu(src.sk, src.rx_ratio, s_base_va);
buses.push(Bus {
idx: virtual_idx,
bus_type: BusType::Slack,
voltage_mag: src.u_ref,
voltage_ang: 0.0,
p_spec: 0.0,
q_spec: 0.0,
q_min: -f64::INFINITY,
q_max: f64::INFINITY,
u_rated: id_to_u_rated[&src.node],
zip_terms: Vec::new(),
});
source_branch_idx.insert(src.id, lines.len());
lines.push(Line { from: virtual_idx, to: id_to_idx[&src.node], r: r_s, x: x_s, b_shunt: 0.0, g_shunt: 0.0 });
}
let n_lines = lines.len();
branch_idx.extend(transformer_pos.into_iter().map(|(id, pos)| (id, n_lines + pos)));
three_winding_branch_idx.extend(
three_winding_pos
.into_iter()
.map(|(id, legs)| (id, legs.map(|pos| n_lines + pos))),
);
let mut appliance_bus: HashMap<u64, usize> = HashMap::new();
for (id, node) in input.data.sym_load.iter().map(|a| (a.id, a.node))
.chain(input.data.asym_load.iter().map(|a| (a.id, a.node)))
.chain(input.data.sym_gen.iter().map(|a| (a.id, a.node)))
.chain(input.data.asym_gen.iter().map(|a| (a.id, a.node)))
.chain(input.data.shunt.iter().map(|a| (a.id, a.node)))
.chain(input.data.source.iter().map(|a| (a.id, a.node)))
{
if let Some(&idx) = id_to_idx.get(&node) {
appliance_bus.insert(id, idx);
}
}
let mut zero_injection = vec![true; buses.len()];
for (node, status) in input.data.sym_load.iter().map(|a| (a.node, a.status))
.chain(input.data.asym_load.iter().map(|a| (a.node, a.status)))
.chain(input.data.sym_gen.iter().map(|a| (a.node, a.status)))
.chain(input.data.asym_gen.iter().map(|a| (a.node, a.status)))
{
if status != 0 {
if let Some(&idx) = id_to_idx.get(&node) {
zero_injection[idx] = false;
}
}
}
for &branch in source_branch_idx.values() {
zero_injection[lines[branch].from] = false;
}
PgmNetwork {
buses,
lines,
transformers,
zero_injection,
node_idx: id_to_idx,
branch_idx,
three_winding_branch_idx,
source_branch_idx,
appliance_bus,
half_open_terminal,
}
}
pub fn pgm_to_buses_and_branches(
input: PgmInput,
s_base_va: f64,
freq_hz: f64,
) -> (Vec<Bus>, Vec<Line>, Vec<Transformer>) {
let net = pgm_to_network(input, s_base_va, freq_hz);
(net.buses, net.lines, net.transformers)
}
pub struct PgmNetwork3Ph {
pub node_idx: HashMap<u64, usize>,
pub branch_idx: HashMap<u64, usize>,
pub source_branch_idx: HashMap<u64, usize>,
pub appliance_bus: HashMap<u64, usize>,
pub zero_injection: Vec<bool>,
pub half_open_terminal: HashMap<u64, Terminal>,
pub n_nodes: usize,
}
pub fn pgm_3ph_maps(input: &PgmInput) -> Result<PgmNetwork3Ph, Unsupported3Ph> {
if !input.data.three_winding_transformer.is_empty() {
return Err(Unsupported3Ph::ThreeWindingTransformer);
}
if !input.data.voltage_regulator.is_empty() {
return Err(Unsupported3Ph::VoltageRegulator);
}
if !input.data.link.is_empty() {
return Err(Unsupported3Ph::Link);
}
if let Some(t) = input
.data
.transformer
.iter()
.find(|t| !matches!((t.winding_from, t.winding_to), (2, 1) | (1, 1)))
{
return Err(Unsupported3Ph::WindingPair {
from: t.winding_from,
to: t.winding_to,
});
}
let node_idx = node_id_to_idx(input);
let n_physical = input.data.node.len();
let mut branch_idx = HashMap::new();
let mut half_open_terminal = HashMap::new();
let mut next = 0usize;
for ln in &input.data.line {
match (ln.from_status, ln.to_status) {
(1, 1) => {}
(1, 0) => {
half_open_terminal.insert(ln.id, Terminal::From);
}
(0, 1) => {
half_open_terminal.insert(ln.id, Terminal::To);
}
_ => continue,
}
branch_idx.insert(ln.id, next);
next += 1;
}
let mut source_branch_idx = HashMap::new();
for src in input.data.source.iter().filter(|s| s.status != 0) {
source_branch_idx.insert(src.id, next);
next += 1;
}
for t in &input.data.transformer {
branch_idx.insert(t.id, next);
next += 1;
}
let mut appliance_bus = HashMap::new();
for (id, node) in input.data.sym_load.iter().map(|a| (a.id, a.node))
.chain(input.data.asym_load.iter().map(|a| (a.id, a.node)))
.chain(input.data.sym_gen.iter().map(|a| (a.id, a.node)))
.chain(input.data.asym_gen.iter().map(|a| (a.id, a.node)))
.chain(input.data.shunt.iter().map(|a| (a.id, a.node)))
.chain(input.data.source.iter().map(|a| (a.id, a.node)))
{
if let Some(&k) = node_idx.get(&node) {
appliance_bus.insert(id, k);
}
}
let n_sources = input.data.source.iter().filter(|s| s.status != 0).count();
let mut zero_injection = vec![true; n_physical + n_sources];
for k in n_physical..zero_injection.len() {
zero_injection[k] = false;
}
for node in input.data.sym_load.iter().map(|a| a.node)
.chain(input.data.asym_load.iter().map(|a| a.node))
.chain(input.data.sym_gen.iter().map(|a| a.node))
.chain(input.data.asym_gen.iter().map(|a| a.node))
{
if let Some(&k) = node_idx.get(&node) {
zero_injection[k] = false;
}
}
Ok(PgmNetwork3Ph {
node_idx,
branch_idx,
source_branch_idx,
appliance_bus,
zero_injection,
half_open_terminal,
n_nodes: n_physical + n_sources,
})
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Unsupported3Ph {
ThreeWindingTransformer,
VoltageRegulator,
Link,
WindingPair { from: u8, to: u8 },
}
impl std::fmt::Display for Unsupported3Ph {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let what = match self {
Self::ThreeWindingTransformer => "three_winding_transformer",
Self::VoltageRegulator => "voltage_regulator",
Self::Link => "link",
Self::WindingPair { from, to } => {
return write!(
f,
"the three-phase conversion derives zero-sequence parameters only for Dyn \
and YNyn windings, not for the pair ({from}, {to})"
)
}
};
write!(f, "the three-phase conversion does not model `{what}`")
}
}
impl std::error::Error for Unsupported3Ph {}
pub fn pgm_to_3ph_network(
input: PgmInput,
s_base_va: f64,
freq_hz: f64,
) -> (Vec<Bus>, Vec<Line3Ph>, HashMap<u64, usize>) {
let id_to_idx = node_id_to_idx(&input);
let id_to_u_rated: HashMap<u64, f64> =
input.data.node.iter().map(|n| (n.id, n.u_rated)).collect();
let n_nodes = input.data.node.len();
let two_pi_f = 2.0 * std::f64::consts::PI * freq_hz;
let phase_ang = [0.0_f64, -2.0 * std::f64::consts::PI / 3.0, 2.0 * std::f64::consts::PI / 3.0];
let mut p_inj: HashMap<u64, [f64; 3]> = HashMap::new();
let mut q_inj: HashMap<u64, [f64; 3]> = HashMap::new();
let mut zip_map: HashMap<u64, [Vec<ZipTerm>; 3]> = HashMap::new();
let s_base_1ph = s_base_va / 3.0;
let mut accumulate3 = |node: u64, load_type: u8, p: [f64; 3], q: [f64; 3], sign: f64, base: f64| {
match load_type {
1 | 2 => {
let kind = if load_type == 1 { ZipKind::ConstImpedance } else { ZipKind::ConstCurrent };
let entry = zip_map.entry(node).or_insert_with(|| [Vec::new(), Vec::new(), Vec::new()]);
for ph in 0..3 {
entry[ph].push(ZipTerm { s_const: Complex::new(p[ph], q[ph]) * sign / base, kind });
}
}
_ => {
let pe = p_inj.entry(node).or_insert([0.0; 3]);
let qe = q_inj.entry(node).or_insert([0.0; 3]);
for ph in 0..3 {
pe[ph] += sign * p[ph] / base;
qe[ph] += sign * q[ph] / base;
}
}
}
};
for load in &input.data.asym_load {
if load.status == 0 { continue; }
accumulate3(load.node, load.load_type, load.p_specified, load.q_specified, -1.0, s_base_1ph);
}
for agen in &input.data.asym_gen {
if agen.status == 0 { continue; }
accumulate3(agen.node, agen.load_type, agen.p_specified, agen.q_specified, 1.0, s_base_1ph);
}
for load in &input.data.sym_load {
if load.status == 0 { continue; }
let p = [load.p_specified; 3];
let q = [load.q_specified; 3];
accumulate3(load.node, load.load_type, p, q, -1.0, s_base_va);
}
for sgen in &input.data.sym_gen {
if sgen.status == 0 { continue; }
let p = [sgen.p_specified; 3];
let q = [sgen.q_specified; 3];
accumulate3(sgen.node, sgen.load_type, p, q, 1.0, s_base_va);
}
let mut sorted_ids: Vec<u64> = input.data.node.iter().map(|n| n.id).collect();
sorted_ids.sort_unstable();
let mut buses: Vec<Bus> = Vec::with_capacity(3 * n_nodes);
for _ in 0..3 * n_nodes {
buses.push(Bus {
idx: 0,
bus_type: BusType::PQ,
voltage_mag: 1.0,
voltage_ang: 0.0,
p_spec: 0.0,
q_spec: 0.0,
q_min: -f64::INFINITY,
q_max: f64::INFINITY,
u_rated: 0.0,
zip_terms: Vec::new(),
});
}
for id in &sorted_ids {
let phys = id_to_idx[id];
let p_arr = p_inj.get(id).copied().unwrap_or([0.0; 3]);
let q_arr = q_inj.get(id).copied().unwrap_or([0.0; 3]);
let mut zip_arr = zip_map.remove(id).unwrap_or_default();
for ph in 0..3 {
let bus_idx = 3 * phys + ph;
buses[bus_idx] = Bus {
idx: bus_idx,
bus_type: BusType::PQ,
voltage_mag: 1.0,
voltage_ang: phase_ang[ph],
p_spec: p_arr[ph],
q_spec: q_arr[ph],
q_min: -f64::INFINITY,
q_max: f64::INFINITY,
u_rated: id_to_u_rated[id],
zip_terms: std::mem::take(&mut zip_arr[ph]),
};
}
}
let mut lines: Vec<Line3Ph> = Vec::new();
for ln in &input.data.line {
let u_rated_from = id_to_u_rated[&ln.from_node];
let z_base = u_rated_from * u_rated_from / s_base_va;
let b1 = two_pi_f * ln.c1 * z_base;
let b0 = two_pi_f * ln.c0 * z_base;
let r1_pu = ln.r1 / z_base;
let x1_pu = ln.x1 / z_base;
let r0_pu = ln.r0 / z_base;
let x0_pu = ln.x0 / z_base;
let from_phys = id_to_idx[&ln.from_node];
let to_phys = id_to_idx[&ln.to_node];
match (ln.from_status, ln.to_status) {
(1, 1) => {
lines.push(Line3Ph {
from: from_phys,
to: to_phys,
r1: r1_pu, x1: x1_pu, b1,
r0: r0_pu, x0: x0_pu, b0,
});
}
(1, 0) => {
lines.push(Line3Ph {
from: from_phys, to: from_phys,
r1: 0.0, x1: 0.0, b1,
r0: 0.0, x0: 0.0, b0,
});
}
(0, 1) => {
lines.push(Line3Ph {
from: to_phys, to: to_phys,
r1: 0.0, x1: 0.0, b1,
r0: 0.0, x0: 0.0, b0,
});
}
_ => {}
}
}
for (i, src) in input.data.source.iter().filter(|s| s.status != 0).enumerate() {
let virtual_phys = n_nodes + i;
let (r1_s, x1_s, r0_s, x0_s) =
source_impedance_pu_seq(src.sk, src.rx_ratio, src.z01_ratio, s_base_va);
for ph in 0..3 {
let bus_idx = 3 * virtual_phys + ph;
buses.push(Bus {
idx: bus_idx,
bus_type: BusType::Slack,
voltage_mag: src.u_ref,
voltage_ang: phase_ang[ph],
p_spec: 0.0,
q_spec: 0.0,
q_min: -f64::INFINITY,
q_max: f64::INFINITY,
u_rated: id_to_u_rated[&src.node],
zip_terms: Vec::new(),
});
}
lines.push(Line3Ph {
from: virtual_phys,
to: id_to_idx[&src.node],
r1: r1_s, x1: x1_s, b1: 0.0,
r0: r0_s, x0: x0_s, b0: 0.0,
});
}
(buses, lines, id_to_idx)
}