use std::collections::BTreeMap;
use std::sync::Arc;
pub type Extras = BTreeMap<String, serde_json::Value>;
pub type Mat = Vec<Vec<f64>>;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum DistSourceFormat {
Dss,
BmopfJson,
PmdJson,
}
impl DistSourceFormat {
pub fn name(self) -> &'static str {
match self {
DistSourceFormat::Dss => "dss",
DistSourceFormat::PmdJson => "pmd-json",
DistSourceFormat::BmopfJson => "bmopf-json",
}
}
}
#[derive(Clone, Debug, PartialEq, Default)]
pub struct DistBus {
pub id: String,
pub terminals: Vec<String>,
pub grounded: Vec<String>,
pub v_min: Option<f64>,
pub v_max: Option<f64>,
pub vpn_min: Option<Vec<f64>>,
pub vpn_max: Option<Vec<f64>>,
pub vpp_min: Option<Vec<f64>>,
pub vpp_max: Option<Vec<f64>>,
pub vsym_min: Option<Vec<f64>>,
pub vsym_max: Option<Vec<f64>>,
pub extras: Extras,
}
#[derive(Clone, Debug, PartialEq)]
pub struct DistLineCode {
pub name: String,
pub n_conductors: usize,
pub r_series: Mat,
pub x_series: Mat,
pub g_from: Mat,
pub b_from: Mat,
pub g_to: Mat,
pub b_to: Mat,
pub i_max: Option<Vec<f64>>,
pub s_max: Option<Vec<f64>>,
pub extras: Extras,
}
#[derive(Clone, Debug, PartialEq)]
pub struct DistLine {
pub name: String,
pub bus_from: String,
pub bus_to: String,
pub terminal_map_from: Vec<String>,
pub terminal_map_to: Vec<String>,
pub linecode: String,
pub length: f64,
pub extras: Extras,
}
#[derive(Clone, Debug, PartialEq)]
pub struct DistSwitch {
pub name: String,
pub bus_from: String,
pub bus_to: String,
pub terminal_map_from: Vec<String>,
pub terminal_map_to: Vec<String>,
pub open: bool,
pub i_max: Option<Vec<f64>>,
pub extras: Extras,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Configuration {
Wye,
Delta,
SinglePhase,
}
#[derive(Clone, Debug, PartialEq)]
pub struct DistLoad {
pub name: String,
pub bus: String,
pub terminal_map: Vec<String>,
pub configuration: Configuration,
pub p_nom: Vec<f64>,
pub q_nom: Vec<f64>,
pub extras: Extras,
}
#[derive(Clone, Debug, PartialEq)]
pub struct DistGenerator {
pub name: String,
pub bus: String,
pub terminal_map: Vec<String>,
pub configuration: Configuration,
pub p_nom: Vec<f64>,
pub q_nom: Vec<f64>,
pub p_min: Option<Vec<f64>>,
pub p_max: Option<Vec<f64>>,
pub q_min: Option<Vec<f64>>,
pub q_max: Option<Vec<f64>>,
pub cost: Option<f64>,
pub extras: Extras,
}
#[derive(Clone, Debug, PartialEq)]
pub struct DistShunt {
pub name: String,
pub bus: String,
pub terminal_map: Vec<String>,
pub g: Mat,
pub b: Mat,
pub extras: Extras,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum WindingConn {
Wye,
Delta,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Winding {
pub bus: String,
pub terminal_map: Vec<String>,
pub conn: WindingConn,
pub v_ref: f64,
pub s_rating: f64,
pub r_pct: f64,
pub tap: f64,
}
#[derive(Clone, Debug, PartialEq)]
pub struct DistTransformer {
pub name: String,
pub windings: Vec<Winding>,
pub xsc_pct: Vec<f64>,
pub phases: usize,
pub extras: Extras,
}
#[derive(Clone, Debug, PartialEq)]
pub struct VoltageSource {
pub name: String,
pub bus: String,
pub terminal_map: Vec<String>,
pub v_magnitude: Vec<f64>,
pub v_angle: Vec<f64>,
pub extras: Extras,
}
#[derive(Clone, Debug, PartialEq)]
pub struct UntypedObject {
pub class: String,
pub name: String,
pub props: Vec<(Option<String>, String)>,
}
#[derive(Clone, Debug)]
pub struct DistNetwork {
pub name: Option<String>,
pub base_frequency: f64,
pub buses: Vec<DistBus>,
pub linecodes: Vec<DistLineCode>,
pub lines: Vec<DistLine>,
pub switches: Vec<DistSwitch>,
pub transformers: Vec<DistTransformer>,
pub loads: Vec<DistLoad>,
pub generators: Vec<DistGenerator>,
pub shunts: Vec<DistShunt>,
pub sources: Vec<VoltageSource>,
pub untyped: Vec<UntypedObject>,
pub commands: Vec<(String, String)>,
pub options: Vec<(String, String)>,
pub defaulted: BTreeMap<String, Vec<&'static str>>,
pub warnings: Vec<String>,
pub source: Option<Arc<String>>,
pub source_format: Option<DistSourceFormat>,
pub extras: Extras,
}
impl Default for DistNetwork {
fn default() -> Self {
DistNetwork {
name: None,
base_frequency: crate::dss::defaults::BASE_FREQUENCY,
buses: Vec::new(),
linecodes: Vec::new(),
lines: Vec::new(),
switches: Vec::new(),
transformers: Vec::new(),
loads: Vec::new(),
generators: Vec::new(),
shunts: Vec::new(),
sources: Vec::new(),
untyped: Vec::new(),
commands: Vec::new(),
options: Vec::new(),
defaulted: BTreeMap::new(),
warnings: Vec::new(),
source: None,
source_format: None,
extras: Extras::new(),
}
}
}
impl DistNetwork {
pub fn bus(&self, id: &str) -> Option<&DistBus> {
self.buses.iter().find(|b| b.id.eq_ignore_ascii_case(id))
}
pub fn linecode(&self, name: &str) -> Option<&DistLineCode> {
self.linecodes
.iter()
.find(|c| c.name.eq_ignore_ascii_case(name))
}
}
pub(crate) fn square_from_rows(rows: &[Vec<f64>], n: usize) -> Option<Mat> {
let mut m = vec![vec![0.0; n]; n];
if rows.len() != n {
return None;
}
let lower = rows.iter().enumerate().all(|(i, r)| r.len() == i + 1);
let full = rows.iter().all(|r| r.len() == n);
if lower {
for (i, row) in rows.iter().enumerate() {
for (j, &v) in row.iter().enumerate() {
m[i][j] = v;
m[j][i] = v;
}
}
} else if full {
for (i, row) in rows.iter().enumerate() {
m[i].clone_from_slice(&row[..n]);
}
} else {
return None;
}
Some(m)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[allow(clippy::float_cmp)]
fn lower_triangle_completes_symmetrically() {
let rows = vec![vec![1.0], vec![0.5, 2.0], vec![0.3, 0.4, 3.0]];
let m = square_from_rows(&rows, 3).unwrap();
assert_eq!(m[0][1], 0.5);
assert_eq!(m[1][0], 0.5);
assert_eq!(m[2][2], 3.0);
assert_eq!(m[0][2], 0.3);
}
#[test]
#[allow(clippy::float_cmp)]
fn full_rows_pass_through() {
let rows = vec![vec![1.0, 9.0], vec![8.0, 2.0]];
let m = square_from_rows(&rows, 2).unwrap();
assert_eq!(m[0][1], 9.0);
assert_eq!(m[1][0], 8.0);
}
#[test]
fn wrong_shape_is_rejected() {
assert!(square_from_rows(&[vec![1.0], vec![2.0]], 2).is_none());
assert!(square_from_rows(&[vec![1.0, 2.0]], 2).is_none());
}
}