powerio_dist/pmd/math/mod.rs
1//! The PowerModelsDistribution MATHEMATICAL model as JSON (`pmd-math-json`).
2//!
3//! PMD's `transform_data_model` lowers an ENGINEERING model to the index
4//! based, per unit MATHEMATICAL model its optimization problems are built
5//! on: integer keyed `bus`, `branch`, `switch`, `transformer`, `gen`,
6//! `load`, `shunt`, and `storage` tables, every value per unit on a bus
7//! voltage base and the system power base, angles in radians, and
8//! transformers decomposed into ideal two-winding units joined by a loss
9//! model of virtual buses and branches. The `map` table records which
10//! MATHEMATICAL objects each ENGINEERING object became, and `bus_lookup`
11//! names each bus's index.
12//!
13//! The writer performs that lowering from the typed network,
14//! following `eng2math.jl`, `make_per_unit!`, and `calc_voltage_bases` of PMD
15//! v0.16: the same object names, `source_id`s, virtual element names, and
16//! per unit conventions, so PMD reads the output as its own and the oracle
17//! tests compare the two value by value. The reader inverts the
18//! per unit scaling with the document's own bases and folds the virtual
19//! elements back through `map`, so a MATHEMATICAL document parses to the
20//! same [`MulticonductorNetwork`] its ENGINEERING source would, with the
21//! irreversible parts (winding power ratings, line lengths, Kron reduced
22//! neutrals) restated and reported.
23//!
24//! [`MulticonductorNetwork`]: crate::MulticonductorNetwork
25
26pub(crate) mod read;
27pub(crate) mod write;
28
29use std::collections::BTreeMap;
30
31use serde_json::{Map, Value};
32
33/// The emission policy of the MATHEMATICAL writer.
34#[derive(Clone, Debug, PartialEq, Eq)]
35#[non_exhaustive]
36pub struct PmdMathEmitOptions {
37 /// Kron reduce the grounded neutral before lowering, as PMD's
38 /// `transform_data_model` does by default. `false` keeps every conductor
39 /// (PMD's `kron_reduce=false`).
40 pub kron_reduce: bool,
41}
42
43impl Default for PmdMathEmitOptions {
44 fn default() -> Self {
45 Self { kron_reduce: true }
46 }
47}
48
49/// The ENGINEERING `settings` record that fixes the per unit system: the
50/// power base in `power_scale_factor` units (kVA), the voltage scale factor
51/// (kV), the frequency, and the stated voltage bases per bus (kV, line to
52/// neutral).
53#[derive(Clone, Debug)]
54pub(crate) struct Settings {
55 pub(crate) sbase: f64,
56 pub(crate) voltage_scale_factor: f64,
57 pub(crate) power_scale_factor: f64,
58 pub(crate) base_frequency: f64,
59 /// Stated voltage bases by lowercased bus id, kV line to neutral.
60 pub(crate) vbases_default: BTreeMap<String, f64>,
61 /// The record as stated, for the output's `settings`.
62 pub(crate) raw: Map<String, Value>,
63}
64
65impl Settings {
66 /// The settings an ENGINEERING read retained, else the ones the
67 /// ENGINEERING writer synthesizes for a dss or BMOPF sourced network.
68 pub(crate) fn of(net: &crate::MulticonductorNetwork) -> Self {
69 let raw = match net.extras().get("pmd_settings") {
70 Some(Value::Object(settings)) => settings.clone(),
71 _ => match super::write::synthesized_settings(net) {
72 Value::Object(settings) => settings,
73 _ => Map::new(),
74 },
75 };
76 let number = |key: &str, default: f64| {
77 raw.get(key)
78 .and_then(Value::as_f64)
79 .filter(|v| v.is_finite() && *v > 0.0)
80 .unwrap_or(default)
81 };
82 let vbases_default = raw
83 .get("vbases_default")
84 .and_then(Value::as_object)
85 .map(|table| {
86 table
87 .iter()
88 .filter_map(|(bus, kv)| Some((bus.to_ascii_lowercase(), kv.as_f64()?)))
89 .collect()
90 })
91 .unwrap_or_default();
92 Self {
93 sbase: number("sbase", number("sbase_default", 1.0)),
94 voltage_scale_factor: number("voltage_scale_factor", 1000.0),
95 power_scale_factor: number("power_scale_factor", 1000.0),
96 base_frequency: number("base_frequency", net.base_frequency()),
97 vbases_default,
98 raw,
99 }
100 }
101}
102
103/// The MATHEMATICAL `model` enumeration of a load law.
104pub(crate) const LOAD_MODEL_POWER: &str = "POWER";
105pub(crate) const LOAD_MODEL_IMPEDANCE: &str = "IMPEDANCE";
106pub(crate) const LOAD_MODEL_CURRENT: &str = "CURRENT";
107
108/// PMD's null restoration applies to MATHEMATICAL documents too: an absent
109/// or `null` bound restores by its key's suffix.
110pub(crate) fn restore(key: &str, v: &Value) -> f64 {
111 super::read::restore_null(key, v)
112}