use super::branch_flow::Terminal;
use super::network::{
ShuntAdm, ShuntAdm3Ph, YBusSparse, branch_calc_param, half_open_branch_shunt,
source_impedance_pu, source_impedance_pu_seq, tap_ratio_from_voltages,
three_winding_star_params, transformer_admittances, transformer_admittances_ex,
transformer_seq_params, transformer_tap,
};
use super::types::{Bus, BusType, Line, Line3Ph, Transformer, Transformer3PhSeq, ZipKind, ZipTerm};
use num_complex::Complex;
use serde::Deserialize;
use std::collections::HashMap;
pub mod measurement;
#[cfg(feature = "opf")]
pub mod opf;
pub mod se;
pub mod shortcircuit;
#[derive(Clone, Deserialize)]
pub struct PgmInput {
pub data: PgmData,
}
#[derive(Clone, 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>,
#[serde(default)]
pub fault: Vec<PgmFault>,
}
#[derive(Clone, Deserialize)]
pub struct PgmNode {
pub id: u64,
pub u_rated: f64,
}
#[derive(Clone, 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 wye_n() -> u8 {
crate::network::WYE_N
}
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 one_u8() -> u8 {
1
}
fn default_fault_phase() -> i8 {
-1
}
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(Clone, Deserialize)]
pub struct PgmSource {
pub id: u64,
pub node: u64,
pub status: u8,
#[serde(default = "one")]
pub u_ref: f64,
#[serde(default)]
pub u_ref_angle: 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(Clone, 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(Clone, 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(Clone, 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(Clone, 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(Clone, 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(Clone, Copy, Debug, Deserialize)]
pub struct PgmFault {
pub id: u64,
#[serde(default = "one_u8")]
pub status: u8,
pub fault_type: i8,
#[serde(default = "default_fault_phase")]
pub fault_phase: i8,
pub fault_object: u64,
#[serde(default)]
pub r_f: f64,
#[serde(default)]
pub x_f: f64,
}
#[derive(Clone, 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(Clone, 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,
#[serde(default = "wye_n")]
pub winding_1: u8,
#[serde(default = "wye_n")]
pub winding_2: u8,
#[serde(default = "wye_n")]
pub winding_3: u8,
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(Clone, 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(Clone, 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(Clone, 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(Clone, 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(Clone, 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(Clone, 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(Clone, Deserialize)]
pub struct PgmOutput<N> {
pub data: PgmOutputData<N>,
}
#[derive(Clone, Deserialize)]
pub struct PgmOutputData<N> {
pub node: Vec<N>,
}
#[derive(Clone, Deserialize)]
pub struct PgmBatchOutput<N> {
pub data: Vec<PgmOutputData<N>>,
}
#[derive(Clone, 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(Debug, Deserialize)]
pub struct PgmScOutput {
pub data: PgmScOutputData,
}
#[derive(Debug, Deserialize)]
pub struct PgmScBatchOutput {
pub data: Vec<PgmScOutputData>,
}
#[derive(Debug, Default, Deserialize)]
pub struct PgmScOutputData {
#[serde(default)]
pub node: Vec<PgmScNodeOutput>,
#[serde(default)]
pub fault: Vec<PgmScFaultOutput>,
#[serde(default)]
pub source: Vec<PgmScApplianceOutput>,
#[serde(default)]
pub line: Vec<PgmScBranchOutput>,
#[serde(default)]
pub transformer: Vec<PgmScBranchOutput>,
#[serde(default)]
pub link: Vec<PgmScBranchOutput>,
#[serde(default)]
pub shunt: Vec<PgmScApplianceOutput>,
}
#[derive(Debug, Deserialize)]
pub struct PgmScNodeOutput {
pub id: u64,
#[serde(default)]
pub energized: Option<u8>,
#[serde(default)]
pub u_pu: Option<[f64; 3]>,
#[serde(default)]
pub u_angle: Option<[f64; 3]>,
#[serde(default)]
pub u: Option<[f64; 3]>,
}
#[derive(Debug, Deserialize)]
pub struct PgmScFaultOutput {
pub id: u64,
#[serde(default)]
pub energized: Option<u8>,
#[serde(default)]
pub i_f: Option<[f64; 3]>,
#[serde(default)]
pub i_f_angle: Option<[f64; 3]>,
}
#[derive(Debug, Deserialize)]
pub struct PgmScApplianceOutput {
pub id: u64,
#[serde(default)]
pub energized: Option<u8>,
#[serde(default)]
pub i: Option<[f64; 3]>,
#[serde(default)]
pub i_angle: Option<[f64; 3]>,
}
#[derive(Debug, Deserialize)]
pub struct PgmScBranchOutput {
pub id: u64,
#[serde(default)]
pub energized: Option<u8>,
#[serde(default)]
pub i_from: Option<[f64; 3]>,
#[serde(default)]
pub i_from_angle: Option<[f64; 3]>,
#[serde(default)]
pub i_to: Option<[f64; 3]>,
#[serde(default)]
pub i_to_angle: Option<[f64; 3]>,
}
#[derive(Clone, 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 link_seq_params(lk: &PgmLink, id_to_idx: &HashMap<u64, usize>) -> Option<Transformer3PhSeq> {
if lk.from_status == 0 && lk.to_status == 0 {
return None;
}
let y = branch_calc_param(
LINK_Y,
Complex::new(0.0, 0.0),
Complex::new(1.0, 0.0),
lk.from_status,
lk.to_status,
);
Some(Transformer3PhSeq {
from: id_to_idx[&lk.from_node],
to: id_to_idx[&lk.to_node],
y0: y,
y1: y,
y2: y,
})
}
pub struct ThreeWindingLeg {
pub node: u64,
pub status: u8,
pub y_series: Complex<f64>,
pub y_shunt: Complex<f64>,
pub tap: Complex<f64>,
pub winding_from: u8,
pub winding_to: u8,
pub sn: f64,
pub uk: f64,
pub clock: i32,
}
pub fn three_winding_legs(
t: &PgmThreeWindingTransformer,
id_to_u_rated: &HashMap<u64, f64>,
s_base_va: f64,
) -> [ThreeWindingLeg; 3] {
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,
);
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,
);
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,
);
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,
);
[
ThreeWindingLeg {
node: t.node_1,
status: t.status_1,
y_series: t1_series,
y_shunt: t1_shunt,
tap: tap_ratio_from_voltages(u1_local * u1_rated, u1_local * u1_rated, 0),
winding_from: crate::network::WYE_N,
winding_to: crate::network::WYE_N,
sn: t.sn_1,
uk: uk_t1,
clock: 0,
},
ThreeWindingLeg {
node: t.node_2,
status: t.status_2,
y_series: t2_series,
y_shunt: t2_shunt,
tap: tap_ratio_from_voltages(u2_local * u1_rated, u1_local * u2_rated, 12 - t.clock_12),
winding_from: t.winding_2,
winding_to: t.winding_1,
sn: t.sn_2,
uk: uk_t2,
clock: 12 - t.clock_12,
},
ThreeWindingLeg {
node: t.node_3,
status: t.status_3,
y_series: t3_series,
y_shunt: t3_shunt,
tap: tap_ratio_from_voltages(u3_local * u1_rated, u1_local * u3_rated, 12 - t.clock_13),
winding_from: t.winding_3,
winding_to: t.winding_1,
sn: t.sn_3,
uk: uk_t3,
clock: 12 - t.clock_13,
},
]
}
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();
let n_nodes = input.data.node.len();
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,
}
})
.chain(
input
.data
.link
.iter()
.filter_map(|lk| link_seq_params(lk, id_to_idx)),
)
.chain(
input
.data
.three_winding_transformer
.iter()
.enumerate()
.flat_map(|(i, t)| {
let star = n_nodes + i;
three_winding_legs(t, &id_to_u_rated, s_base_va)
.into_iter()
.map(move |leg| {
let (y0, y1, y2) = crate::network::transformer_seq_params(
leg.y_series,
leg.y_shunt,
leg.tap,
leg.status,
1,
leg.winding_from,
leg.winding_to,
leg.sn,
leg.uk,
s_base_va,
leg.clock,
);
Transformer3PhSeq {
from: id_to_idx[&leg.node],
to: star,
y0,
y1,
y2,
}
})
.collect::<Vec<_>>()
}),
)
.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 se_network(&self, ybus: YBusSparse, shunts: &[ShuntAdm]) -> crate::se::SeNetwork {
crate::se::SeNetwork::new(
ybus,
&self.lines,
&self.transformers,
shunts,
&self.source_branch_idx,
&self.zero_injection,
)
}
}
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 legs = three_winding_legs(t, &id_to_u_rated, s_base_va);
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: id_to_u_rated[&t.node_1],
zip_terms: Vec::new(),
});
three_winding_pos.insert(
t.id,
[
transformers.len(),
transformers.len() + 1,
transformers.len() + 2,
],
);
for leg in &legs {
transformers.push(Transformer {
from: id_to_idx[&leg.node],
to: star_idx,
from_status: leg.status,
to_status: 1,
y_series: leg.y_series,
y_shunt: leg.y_shunt,
tap: leg.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 three_winding_idx: HashMap<u64, [usize; 3]>,
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) -> PgmNetwork3Ph {
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;
}
for lk in &input.data.link {
if lk.from_status == 0 && lk.to_status == 0 {
continue;
}
branch_idx.insert(lk.id, next);
next += 1;
}
let mut three_winding_idx = HashMap::new();
for t in &input.data.three_winding_transformer {
three_winding_idx.insert(t.id, [next, next + 1, next + 2]);
next += 3;
}
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 n_3wdg = input.data.three_winding_transformer.len();
let mut zero_injection = vec![true; n_physical + n_3wdg + n_sources];
for flag in zero_injection.iter_mut().skip(n_physical + n_3wdg) {
*flag = 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;
}
}
PgmNetwork3Ph {
node_idx,
branch_idx,
three_winding_idx,
source_branch_idx,
appliance_bus,
zero_injection,
half_open_terminal,
n_nodes: n_physical + n_3wdg + n_sources,
}
}
pub fn pgm_lines_3ph(
input: &PgmInput,
id_to_idx: &HashMap<u64, usize>,
s_base_va: f64,
freq_hz: f64,
) -> Vec<Line3Ph> {
let id_to_u_rated: HashMap<u64, f64> =
input.data.node.iter().map(|n| (n.id, n.u_rated)).collect();
let two_pi_f = 2.0 * std::f64::consts::PI * freq_hz;
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 g1 = b1 * ln.tan1;
let g0 = b0 * ln.tan0;
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,
g1,
r0: r0_pu,
x0: x0_pu,
b0,
g0,
});
}
(1, 0) => {
lines.push(Line3Ph {
from: from_phys,
to: from_phys,
r1: 0.0,
x1: 0.0,
b1,
g1,
r0: 0.0,
x0: 0.0,
b0,
g0,
});
}
(0, 1) => {
lines.push(Line3Ph {
from: to_phys,
to: to_phys,
r1: 0.0,
x1: 0.0,
b1,
g1,
r0: 0.0,
x0: 0.0,
b0,
g0,
});
}
_ => {}
}
}
lines
}
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 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 = pgm_lines_3ph(&input, &id_to_idx, s_base_va, freq_hz);
for (i, t) in input.data.three_winding_transformer.iter().enumerate() {
let star_phys = n_nodes + i;
for ph in 0..3 {
buses.push(Bus {
idx: 3 * star_phys + ph,
bus_type: BusType::PQ,
voltage_mag: 1.0,
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[&t.node_1],
zip_terms: Vec::new(),
});
}
}
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_phys = n_nodes + n_3wdg + 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,
g1: 0.0,
r0: r0_s,
x0: x0_s,
b0: 0.0,
g0: 0.0,
});
}
(buses, lines, id_to_idx)
}