pub mod bad_data;
pub mod batch;
pub mod constraints;
pub mod functional;
pub mod iterative;
pub mod jacobian;
pub mod observability;
pub mod nr;
use num_complex::Complex;
use crate::branch_flow::{branch_params, BranchParams};
use crate::measurement::{Measurement, MeasurementKind, Target};
use crate::network::YBusSparse;
use crate::types::Bus;
pub struct SeNetwork {
pub ybus: YBusSparse,
pub terminals: Vec<[functional::CurrentFunctional; 2]>,
pub shunt_y: Vec<Complex<f64>>,
pub source_branches: Vec<Vec<usize>>,
pub zero_injection: Vec<bool>,
pub energized: Vec<bool>,
}
fn terminals_from_params(params: &[BranchParams]) -> Vec<[functional::CurrentFunctional; 2]> {
use crate::branch_flow::Terminal;
params
.iter()
.map(|b| {
[Terminal::From, Terminal::To].map(|t| {
let (near, far) = b.buses(t);
let (y_self, y_mut) = b.seen_from(t);
functional::CurrentFunctional {
at: near,
coefficients: vec![(near, y_self), (far, y_mut)],
}
})
})
.collect()
}
impl SeNetwork {
pub fn new(
net: &crate::pgm::PgmNetwork,
ybus: YBusSparse,
shunts: &[crate::network::ShuntAdm],
) -> Self {
let n = ybus.n();
let mut shunt_y = vec![Complex::new(0.0, 0.0); n];
for s in shunts {
shunt_y[s.at] += s.y;
}
let terminals = terminals_from_params(&branch_params(&net.lines, &net.transformers));
let mut source_branches = vec![Vec::new(); n];
for (&_id, &branch) in &net.source_branch_idx {
source_branches[terminals[branch][1].at].push(branch);
}
for list in &mut source_branches {
list.sort_unstable();
}
let mut energized = vec![false; n];
for component in crate::network::connected_components(&ybus) {
if component.iter().any(|&i| !source_branches[i].is_empty()) {
for &i in &component {
energized[i] = true;
}
}
}
let zero_injection = net.zero_injection.clone();
Self {
ybus,
terminals,
shunt_y,
source_branches,
zero_injection,
energized,
}
}
pub fn from_3ph(
ybus: YBusSparse,
lines: &[crate::types::Line3Ph],
transformers: &[crate::types::Transformer3PhSeq],
shunts: &[crate::network::ShuntAdm3Ph],
source_branch_idx: &std::collections::HashMap<u64, usize>,
zero_injection_per_node: &[bool],
) -> Self {
let n = ybus.n();
let n_nodes = n / 3;
let mut terminals = Vec::with_capacity(3 * (lines.len() + transformers.len()));
let blocks = lines
.iter()
.map(|ln| (ln.from, ln.to, crate::network::line3ph_blocks(ln)))
.chain(
transformers
.iter()
.map(|t| (t.from, t.to, crate::network::transformer3ph_blocks(t))),
);
for (from, to, [yff, yft, ytf, ytt]) in blocks {
for p in 0..3 {
let coeffs = |near_block: &[[Complex<f64>; 3]; 3],
far_block: &[[Complex<f64>; 3]; 3],
near: usize,
far: usize| {
(0..3)
.map(|q| (3 * near + q, near_block[p][q]))
.chain((0..3).map(|q| (3 * far + q, far_block[p][q])))
.collect::<Vec<_>>()
};
terminals.push([
functional::CurrentFunctional {
at: 3 * from + p,
coefficients: coeffs(&yff, &yft, from, to),
},
functional::CurrentFunctional {
at: 3 * to + p,
coefficients: coeffs(&ytt, &ytf, to, from),
},
]);
}
}
let mut shunt_y = vec![Complex::new(0.0, 0.0); n];
for s in shunts {
let m = crate::network::seq_to_phase_shunt(s.y1, s.y0);
for p in 0..3 {
shunt_y[3 * s.at + p] += m[p][p];
}
}
let mut source_branches = vec![Vec::new(); n];
for &branch in source_branch_idx.values() {
for p in 0..3 {
let phase_branch = 3 * branch + p;
source_branches[terminals[phase_branch][1].at].push(phase_branch);
}
}
for list in &mut source_branches {
list.sort_unstable();
}
let mut zero_injection = vec![false; n];
for (k, &zero) in zero_injection_per_node.iter().enumerate().take(n_nodes) {
for p in 0..3 {
zero_injection[3 * k + p] = zero;
}
}
let mut energized = vec![false; n];
for component in crate::network::connected_components(&ybus) {
if component.iter().any(|&i| !source_branches[i].is_empty()) {
for &i in &component {
energized[i] = true;
}
}
}
Self {
ybus,
terminals,
shunt_y,
source_branches,
zero_injection,
energized,
}
}
pub fn constrained_buses(&self) -> Vec<bool> {
self.zero_injection
.iter()
.zip(&self.energized)
.map(|(&zero, &live)| zero && live)
.collect()
}
}
pub fn measurement_functions(
measurements: &[Measurement],
buses: &[Bus],
net: &SeNetwork,
) -> Vec<f64> {
let model = MeasurementModel::new(measurements, net);
measurement_functions_with(measurements, buses, &model)
}
pub fn measurement_functions_with(
measurements: &[Measurement],
buses: &[Bus],
model: &MeasurementModel,
) -> Vec<f64> {
let v: Vec<Complex<f64>> = buses
.iter()
.map(|b| Complex::from_polar(b.voltage_mag, b.voltage_ang))
.collect();
measurements
.iter()
.enumerate()
.map(|(i, m)| match (m.kind, m.target) {
_ if !model.is_live(i) => 0.0,
(MeasurementKind::VoltageMagnitude, Target::Bus(b)) => buses[b].voltage_mag,
(MeasurementKind::VoltageAngle, Target::Bus(b)) => buses[b].voltage_ang,
_ => match model.functional(i) {
Some(f) => {
let value = match m.target {
Target::BranchTerminalCurrent { frame, .. } => match frame {
crate::measurement::AngleFrame::Global => f.current(&v),
crate::measurement::AngleFrame::Local => f.local_current(&v),
},
_ => f.power(&v),
};
match m.kind {
MeasurementKind::ActivePower | MeasurementKind::CurrentReal => value.re,
_ => value.im,
}
}
None => 0.0,
},
})
.collect()
}
pub struct MeasurementModel {
functionals: Vec<Option<functional::CurrentFunctional>>,
live: Vec<bool>,
}
impl MeasurementModel {
pub fn new(measurements: &[Measurement], net: &SeNetwork) -> Self {
let functionals: Vec<Option<functional::CurrentFunctional>> = measurements
.iter()
.map(|m| match (m.kind, m.target) {
(MeasurementKind::VoltageMagnitude | MeasurementKind::VoltageAngle, Target::Bus(_)) => None,
_ => net.functional(m.target),
})
.collect();
let live = measurements
.iter()
.zip(&functionals)
.map(|(m, f)| match (m.target, f) {
(Target::Bus(b), None) => net.energized[b],
(_, Some(f)) => net.energized[f.at],
(_, None) => false,
})
.collect();
Self { functionals, live }
}
pub fn is_live(&self, measurement: usize) -> bool {
self.live[measurement]
}
pub fn functional(&self, measurement: usize) -> Option<&functional::CurrentFunctional> {
self.functionals[measurement].as_ref()
}
pub fn len(&self) -> usize {
self.functionals.len()
}
pub fn is_empty(&self) -> bool {
self.functionals.is_empty()
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::branch_flow::{terminal_flow, Terminal};
use crate::network::YBus;
use crate::types::BusType;
fn bus(idx: usize, mag: f64, ang: f64) -> Bus {
Bus {
idx,
bus_type: BusType::PQ,
voltage_mag: mag,
voltage_ang: ang,
p_spec: 0.0,
q_spec: 0.0,
q_min: f64::NEG_INFINITY,
q_max: f64::INFINITY,
u_rated: 1.0,
zip_terms: Vec::new(),
}
}
pub(crate) fn two_bus_params() -> BranchParams {
let y_series = Complex::new(1.0, 0.0) / Complex::new(0.05, 0.2);
BranchParams { from: 0, to: 1, y: [y_series, -y_series, -y_series, y_series] }
}
pub(crate) fn two_bus_net() -> (SeNetwork, Vec<Bus>) {
let y_series = Complex::new(1.0, 0.0) / Complex::new(0.05, 0.2);
let y_shunt = Complex::new(0.02, 0.3);
let mut yb = YBus::new(2);
yb.add(0, 0, y_series);
yb.add(1, 1, y_series);
yb.add(0, 1, -y_series);
yb.add(1, 0, -y_series);
yb.add(1, 1, y_shunt);
let net = SeNetwork {
ybus: yb.finish(),
terminals: terminals_from_params(&[two_bus_params()]),
shunt_y: vec![Complex::new(0.0, 0.0), y_shunt],
source_branches: vec![Vec::new(), vec![0]],
zero_injection: vec![false, false],
energized: vec![true, true],
};
(net, vec![bus(0, 1.02, 0.03), bus(1, 0.99, -0.02)])
}
#[test]
fn energization_follows_components_not_sensors() {
let y = Complex::new(1.0, 0.0) / Complex::new(0.05, 0.2);
let mut yb = YBus::new(3);
for (i, j, v) in [(0, 0, y), (1, 1, y), (0, 1, -y), (1, 0, -y)] {
yb.add(i, j, v);
}
yb.add(2, 2, Complex::new(0.0, 0.0));
let net = SeNetwork {
ybus: yb.finish(),
terminals: terminals_from_params(&[BranchParams { from: 0, to: 1, y: [y, -y, -y, y] }]),
shunt_y: vec![Complex::new(0.0, 0.0); 3],
source_branches: vec![Vec::new(), vec![0], Vec::new()],
zero_injection: vec![true, false, true],
energized: vec![true, true, false],
};
let rebuilt: Vec<bool> = crate::network::connected_components(&net.ybus)
.iter()
.fold(vec![false; 3], |mut acc, c| {
if c.iter().any(|&i| !net.source_branches[i].is_empty()) {
for &i in c {
acc[i] = true;
}
}
acc
});
assert_eq!(rebuilt, vec![true, true, false], "bus 2 has no source in its component");
assert_eq!(net.constrained_buses(), vec![true, false, false]);
}
#[test]
fn shunt_injection_is_negated_consumption() {
let (net, buses) = two_bus_net();
let v: Vec<Complex<f64>> = buses
.iter()
.map(|b| Complex::from_polar(b.voltage_mag, b.voltage_ang))
.collect();
let s = net
.functional(Target::ShuntInjection { bus: 1 })
.expect("shunt functional")
.power(&v);
let (p, q) = (s.re, s.im);
let v2 = 0.99_f64 * 0.99;
assert!((p + v2 * 0.02).abs() < 1e-12, "p={p}");
assert!((q - v2 * 0.3).abs() < 1e-12, "q={q}");
assert!(p < 0.0, "a resistive shunt must consume active power");
}
#[test]
fn source_injection_is_negated_to_terminal_flow() {
let (net, buses) = two_bus_net();
let v: Vec<Complex<f64>> = buses
.iter()
.map(|b| Complex::from_polar(b.voltage_mag, b.voltage_ang))
.collect();
let (p_to, q_to) = terminal_flow(&two_bus_params(), Terminal::To, &v);
let s_src = net
.functional(Target::SourceInjection { branch: 0 })
.expect("source functional")
.power(&v);
let (p_src, q_src) = (s_src.re, s_src.im);
assert!((p_src + p_to).abs() < 1e-12);
assert!((q_src + q_to).abs() < 1e-12);
}
#[test]
fn node_injection_is_bus_plus_source_plus_shunt() {
let (net, buses) = two_bus_net();
let ms = |target| Measurement {
kind: MeasurementKind::ActivePower,
target,
value: 0.0,
sigma: 1.0,
};
let measurements = vec![ms(Target::Bus(1)), ms(Target::NodeInjection(1))];
let h = measurement_functions(&measurements, &buses, &net);
let v: Vec<Complex<f64>> = buses
.iter()
.map(|b| Complex::from_polar(b.voltage_mag, b.voltage_ang))
.collect();
let power = |t| net.functional(t).expect("functional").power(&v).re;
let expected = h[0]
+ power(Target::SourceInjection { branch: 0 })
+ power(Target::ShuntInjection { bus: 1 });
assert!((h[1] - expected).abs() < 1e-12, "node={} expected={expected}", h[1]);
assert!((h[1] - h[0]).abs() > 1e-6, "bus and node injection should differ here");
}
}