use std::collections::BTreeMap;
use std::sync::Arc;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::Error;
pub type Extras = BTreeMap<String, Value>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(transparent)]
pub struct BusId(pub usize);
impl std::fmt::Display for BusId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
#[repr(u8)]
#[non_exhaustive]
pub enum BusType {
Pq = 1,
Pv = 2,
Ref = 3,
Isolated = 4,
}
impl BusType {
pub(crate) fn from_f64(v: f64) -> Self {
match v as i32 {
2 => Self::Pv,
3 => Self::Ref,
4 => Self::Isolated,
_ => Self::Pq,
}
}
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Pq => "PQ",
Self::Pv => "PV",
Self::Ref => "REF",
Self::Isolated => "ISOLATED",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GenCost {
pub model: u8,
pub startup: f64,
pub shutdown: f64,
pub ncost: usize,
pub coeffs: Vec<f64>,
}
impl GenCost {
pub fn quadratic(&self) -> Option<(f64, f64)> {
if self.model != 2 {
return None;
}
if self.coeffs.len() < self.ncost {
return None;
}
match self.ncost {
3 => Some((2.0 * self.coeffs[0], self.coeffs[1])),
2 => Some((0.0, self.coeffs[0])),
1 => Some((0.0, 0.0)),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum SourceFormat {
Matpower,
PowerModelsJson,
EgretJson,
Psse,
PowerWorld,
PandapowerJson,
Pslf,
PowerWorldBinary,
InMemory,
Normalized,
Gridfm,
PypsaCsv,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Network {
pub name: String,
pub base_mva: f64,
pub buses: Vec<Bus>,
pub loads: Vec<Load>,
pub shunts: Vec<Shunt>,
pub branches: Vec<Branch>,
pub generators: Vec<Generator>,
pub storage: Vec<Storage>,
pub hvdc: Vec<Hvdc>,
pub source_format: SourceFormat,
#[serde(skip)]
pub source: Option<Arc<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Bus {
pub id: BusId,
pub kind: BusType,
pub vm: f64,
pub va: f64,
pub base_kv: f64,
pub vmax: f64,
pub vmin: f64,
pub area: usize,
pub zone: usize,
pub name: Option<String>,
pub extras: Extras,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Load {
pub bus: BusId,
pub p: f64,
pub q: f64,
pub in_service: bool,
pub extras: Extras,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Shunt {
pub bus: BusId,
pub g: f64,
pub b: f64,
pub in_service: bool,
pub extras: Extras,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Branch {
pub from: BusId,
pub to: BusId,
pub r: f64,
pub x: f64,
pub b: f64,
pub rate_a: f64,
pub rate_b: f64,
pub rate_c: f64,
pub tap: f64,
pub shift: f64,
pub in_service: bool,
pub angmin: f64,
pub angmax: f64,
pub extras: Extras,
}
impl Branch {
#[must_use]
pub fn effective_tap(&self) -> f64 {
if self.tap == 0.0 { 1.0 } else { self.tap }
}
#[must_use]
pub fn is_transformer(&self) -> bool {
self.tap != 0.0 || self.shift != 0.0
}
#[must_use]
pub fn has_angle_limits(&self) -> bool {
self.angmin > -360.0 || self.angmax < 360.0
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Generator {
pub bus: BusId,
pub pg: f64,
pub qg: f64,
pub pmax: f64,
pub pmin: f64,
pub qmax: f64,
pub qmin: f64,
pub vg: f64,
pub mbase: f64,
pub in_service: bool,
pub cost: Option<GenCost>,
pub caps: GenCaps,
}
impl Generator {
#[must_use]
pub fn has_caps(&self) -> bool {
self.caps.iter().any(Option::is_some)
}
}
pub type GenCaps = [Option<f64>; GEN_EXTRA_KEYS.len()];
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Storage {
pub bus: BusId,
pub ps: f64,
pub qs: f64,
pub energy: f64,
pub energy_rating: f64,
pub charge_rating: f64,
pub discharge_rating: f64,
pub charge_efficiency: f64,
pub discharge_efficiency: f64,
pub thermal_rating: f64,
pub qmin: f64,
pub qmax: f64,
pub r: f64,
pub x: f64,
pub p_loss: f64,
pub q_loss: f64,
pub in_service: bool,
pub extras: Extras,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Hvdc {
pub from: BusId,
pub to: BusId,
pub in_service: bool,
pub pf: f64,
pub pt: f64,
pub qf: f64,
pub qt: f64,
pub vf: f64,
pub vt: f64,
pub pmin: f64,
pub pmax: f64,
pub qminf: f64,
pub qmaxf: f64,
pub qmint: f64,
pub qmaxt: f64,
pub loss0: f64,
pub loss1: f64,
pub extras: Extras,
}
pub(crate) const GEN_EXTRA_KEYS: [&str; 11] = [
"pc1", "pc2", "qc1min", "qc1max", "qc2min", "qc2max", "ramp_agc", "ramp_10", "ramp_30",
"ramp_q", "apf",
];
impl Network {
#[must_use]
pub fn in_memory(
name: impl Into<String>,
base_mva: f64,
buses: Vec<Bus>,
branches: Vec<Branch>,
) -> Network {
Network {
name: name.into(),
base_mva,
buses,
loads: Vec::new(),
shunts: Vec::new(),
branches,
generators: Vec::new(),
storage: Vec::new(),
hvdc: Vec::new(),
source_format: SourceFormat::InMemory,
source: None,
}
}
pub fn to_json(&self) -> crate::Result<String> {
serde_json::to_string(self).map_err(|e| Error::FormatRead {
format: "JSON",
message: e.to_string(),
})
}
#[must_use]
pub fn to_format(&self, format: crate::TargetFormat) -> crate::Conversion {
crate::write_as(self, format)
}
#[must_use]
pub fn to_matpower(&self) -> String {
crate::write_matpower(self)
}
pub fn from_json(text: &str) -> crate::Result<Network> {
let net: Network = serde_json::from_str(text).map_err(|e| Error::FormatRead {
format: "JSON",
message: e.to_string(),
})?;
net.check_references("JSON")?;
if net.buses.is_empty() {
return Err(Error::FormatRead {
format: "JSON",
message: "case has no buses".into(),
});
}
Ok(net)
}
#[must_use]
pub fn is_normalized(&self) -> bool {
self.source_format == SourceFormat::Normalized
}
pub fn check_base_mva(&self) -> crate::Result<()> {
if self.base_mva.is_finite() && self.base_mva > 0.0 {
Ok(())
} else {
Err(crate::Error::InvalidBaseMva {
base: self.base_mva,
})
}
}
pub fn validate(&self) -> crate::Result<()> {
self.check_references("network")
}
pub(crate) fn check_references(&self, format: &'static str) -> crate::Result<()> {
let mut ids = std::collections::HashSet::with_capacity(self.buses.len());
for b in &self.buses {
if !ids.insert(b.id) {
return Err(Error::FormatRead {
format,
message: format!("duplicate bus id {}", b.id),
});
}
}
let check = |bus: BusId, what: &str| -> crate::Result<()> {
if ids.contains(&bus) {
Ok(())
} else {
Err(Error::FormatRead {
format,
message: format!("{what} references unknown bus {bus}"),
})
}
};
for (i, br) in self.branches.iter().enumerate() {
for bus in [br.from, br.to] {
if !ids.contains(&bus) {
return Err(Error::FormatRead {
format,
message: format!("branch {i} references unknown bus {bus}"),
});
}
}
}
for l in &self.loads {
check(l.bus, "load")?;
}
for s in &self.shunts {
check(s.bus, "shunt")?;
}
for g in &self.generators {
check(g.bus, "generator")?;
}
for d in &self.hvdc {
check(d.from, "dcline")?;
check(d.to, "dcline")?;
}
for s in &self.storage {
check(s.bus, "storage")?;
}
Ok(())
}
}