use std::sync::Arc;
use powerio_core::{Error, TimePoint, TimeSeries};
use powerio_dist::MulticonductorNetwork;
use super::{
OperatingPointColumns, OperatingPointFlags, OperatingPointValues, QuantityLayout,
SharedColumns, dense_quantity, sparse_quantity,
};
use crate::diagnostics::codes;
const TERMINAL_VOLTAGE_MAGNITUDE: &str = "terminal_voltage_magnitude";
const TERMINAL_VOLTAGE_ANGLE: &str = "terminal_voltage_angle";
pub(crate) const LOAD_ACTIVE_POWER: &str = "load_active_power";
pub(crate) const LOAD_REACTIVE_POWER: &str = "load_reactive_power";
pub(crate) const GENERATOR_ACTIVE_POWER: &str = "generator_active_power";
pub(crate) const GENERATOR_REACTIVE_POWER: &str = "generator_reactive_power";
pub(crate) const SWITCH_CLOSED: &str = "switch_closed";
const TRANSFORMER_TAP: &str = "transformer_tap";
const CAPACITOR_STEPS: &str = "capacitor_steps";
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum MulticonductorOperatingPointQuantity {
TerminalVoltageMagnitude,
TerminalVoltageAngle,
LoadActivePower,
LoadReactivePower,
GeneratorActivePower,
GeneratorReactivePower,
TransformerTap,
CapacitorSteps,
}
impl MulticonductorOperatingPointQuantity {
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::TerminalVoltageMagnitude => TERMINAL_VOLTAGE_MAGNITUDE,
Self::TerminalVoltageAngle => TERMINAL_VOLTAGE_ANGLE,
Self::LoadActivePower => LOAD_ACTIVE_POWER,
Self::LoadReactivePower => LOAD_REACTIVE_POWER,
Self::GeneratorActivePower => GENERATOR_ACTIVE_POWER,
Self::GeneratorReactivePower => GENERATOR_REACTIVE_POWER,
Self::TransformerTap => TRANSFORMER_TAP,
Self::CapacitorSteps => CAPACITOR_STEPS,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum MulticonductorOperatingPointFlag {
SwitchClosed,
}
impl MulticonductorOperatingPointFlag {
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::SwitchClosed => SWITCH_CLOSED,
}
}
}
pub use super::OperatingPoint;
impl OperatingPoint<MulticonductorNetwork> {
pub(crate) fn rebind_network(mut self, network: MulticonductorNetwork) -> Result<Self, Error> {
let layout = MulticonductorOperatingPointBuilder::new(network.clone(), Vec::new());
for quantity in self.columns.quantities.keys() {
let expected: Vec<String> = layout
.layout_for(quantity)?
.order()
.map(str::to_owned)
.collect();
let actual: Vec<&str> = self
.identity_order(quantity)
.expect("the quantity came from this point")
.collect();
if actual.len() != expected.len()
|| actual
.iter()
.zip(&expected)
.any(|(left, right)| *left != right)
{
return Err(Error::new(
&codes::BUILD_OPERATING_POINT_SHAPE_MISMATCH,
format!(
"{quantity}: edited network changes the initial point's component identity order"
),
));
}
}
self.network = network;
Ok(self)
}
#[must_use]
pub fn values(
&self,
quantity: MulticonductorOperatingPointQuantity,
) -> Option<OperatingPointValues<'_>> {
self.iter_values(quantity.name())
}
#[must_use]
pub fn flags(
&self,
quantity: MulticonductorOperatingPointFlag,
) -> Option<OperatingPointFlags<'_>> {
self.iter_flags(quantity.name())
}
#[must_use]
pub fn terminal_voltage_magnitude(&self, bus: &str, terminal: &str) -> Option<f64> {
self.value_pair(TERMINAL_VOLTAGE_MAGNITUDE, bus, terminal)
}
#[must_use]
pub fn terminal_voltage_angle(&self, bus: &str, terminal: &str) -> Option<f64> {
self.value_pair(TERMINAL_VOLTAGE_ANGLE, bus, terminal)
}
#[must_use]
pub fn load_active_power(&self, load: &str, terminal: &str) -> Option<f64> {
self.value_pair(LOAD_ACTIVE_POWER, load, terminal)
}
#[must_use]
pub fn load_reactive_power(&self, load: &str, terminal: &str) -> Option<f64> {
self.value_pair(LOAD_REACTIVE_POWER, load, terminal)
}
#[must_use]
pub fn generator_active_power(&self, generator: &str, terminal: &str) -> Option<f64> {
self.value_pair(GENERATOR_ACTIVE_POWER, generator, terminal)
}
#[must_use]
pub fn generator_reactive_power(&self, generator: &str, terminal: &str) -> Option<f64> {
self.value_pair(GENERATOR_REACTIVE_POWER, generator, terminal)
}
#[must_use]
pub fn switch_closed(&self, switch: &str) -> Option<bool> {
self.value_single(SWITCH_CLOSED, switch)
.map(|value| value != 0.0)
}
#[must_use]
pub fn transformer_tap(&self, transformer: &str) -> Option<f64> {
self.value_single(TRANSFORMER_TAP, transformer)
}
#[must_use]
pub fn capacitor_steps(&self, capacitor: &str) -> Option<f64> {
self.value_single(CAPACITOR_STEPS, capacitor)
}
fn value_pair(&self, quantity: &'static str, element: &str, terminal: &str) -> Option<f64> {
self.columns
.quantities
.get(quantity)?
.value(self.index, &format!("{element}/{terminal}"))
}
fn value_single(&self, quantity: &'static str, element: &str) -> Option<f64> {
self.columns
.quantities
.get(quantity)?
.value(self.index, element)
}
}
#[derive(Debug)]
pub struct MulticonductorOperatingPointBuilder {
network: MulticonductorNetwork,
time_points: Vec<TimePoint>,
quantities: Vec<(&'static str, ColumnsInput)>,
}
#[derive(Debug)]
enum ColumnsInput {
Dense(Vec<f64>),
Sparse {
base: Vec<f64>,
changes: Vec<Vec<(String, f64)>>,
},
}
impl MulticonductorOperatingPointBuilder {
#[must_use]
pub fn new(network: MulticonductorNetwork, time_points: Vec<TimePoint>) -> Self {
Self {
network,
time_points,
quantities: Vec::new(),
}
}
#[must_use]
pub fn for_point(network: MulticonductorNetwork) -> Self {
Self::new(network, Vec::new())
}
fn dense(mut self, quantity: &'static str, values: Vec<f64>) -> Self {
self.quantities
.push((quantity, ColumnsInput::Dense(values)));
self
}
fn sparse(
mut self,
quantity: &'static str,
base: Vec<f64>,
changes: Vec<Vec<(String, f64)>>,
) -> Self {
self.quantities
.push((quantity, ColumnsInput::Sparse { base, changes }));
self
}
fn sparse_flags(
self,
quantity: &'static str,
base: Vec<bool>,
changes: Vec<Vec<(String, bool)>>,
) -> Self {
self.sparse(
quantity,
encode_flags(base),
changes
.into_iter()
.map(|point| {
point
.into_iter()
.map(|(identity, value)| (identity, encode_flag(value)))
.collect()
})
.collect(),
)
}
#[must_use]
pub fn terminal_voltage_magnitudes(self, values: Vec<f64>) -> Self {
self.dense(TERMINAL_VOLTAGE_MAGNITUDE, values)
}
#[must_use]
pub fn terminal_voltage_angles(self, values: Vec<f64>) -> Self {
self.dense(TERMINAL_VOLTAGE_ANGLE, values)
}
#[must_use]
pub fn load_active_powers(self, values: Vec<f64>) -> Self {
self.dense(LOAD_ACTIVE_POWER, values)
}
#[must_use]
pub fn load_reactive_powers(self, values: Vec<f64>) -> Self {
self.dense(LOAD_REACTIVE_POWER, values)
}
#[must_use]
pub fn generator_active_powers(self, values: Vec<f64>) -> Self {
self.dense(GENERATOR_ACTIVE_POWER, values)
}
#[must_use]
pub fn generator_reactive_powers(self, values: Vec<f64>) -> Self {
self.dense(GENERATOR_REACTIVE_POWER, values)
}
#[must_use]
pub fn switch_closed(self, values: Vec<bool>) -> Self {
self.dense(SWITCH_CLOSED, encode_flags(values))
}
#[must_use]
pub fn transformer_taps(self, values: Vec<f64>) -> Self {
self.dense(TRANSFORMER_TAP, values)
}
#[must_use]
pub fn capacitor_steps(self, values: Vec<f64>) -> Self {
self.dense(CAPACITOR_STEPS, values)
}
#[must_use]
pub fn sparse_terminal_voltage_magnitudes(
self,
base: Vec<f64>,
changes: Vec<Vec<(String, f64)>>,
) -> Self {
self.sparse(TERMINAL_VOLTAGE_MAGNITUDE, base, changes)
}
#[must_use]
pub fn sparse_terminal_voltage_angles(
self,
base: Vec<f64>,
changes: Vec<Vec<(String, f64)>>,
) -> Self {
self.sparse(TERMINAL_VOLTAGE_ANGLE, base, changes)
}
#[must_use]
pub fn sparse_load_active_powers(
self,
base: Vec<f64>,
changes: Vec<Vec<(String, f64)>>,
) -> Self {
self.sparse(LOAD_ACTIVE_POWER, base, changes)
}
#[must_use]
pub fn sparse_load_reactive_powers(
self,
base: Vec<f64>,
changes: Vec<Vec<(String, f64)>>,
) -> Self {
self.sparse(LOAD_REACTIVE_POWER, base, changes)
}
#[must_use]
pub fn sparse_generator_active_powers(
self,
base: Vec<f64>,
changes: Vec<Vec<(String, f64)>>,
) -> Self {
self.sparse(GENERATOR_ACTIVE_POWER, base, changes)
}
#[must_use]
pub fn sparse_generator_reactive_powers(
self,
base: Vec<f64>,
changes: Vec<Vec<(String, f64)>>,
) -> Self {
self.sparse(GENERATOR_REACTIVE_POWER, base, changes)
}
#[must_use]
pub fn sparse_switch_closed(self, base: Vec<bool>, changes: Vec<Vec<(String, bool)>>) -> Self {
self.sparse_flags(SWITCH_CLOSED, base, changes)
}
#[must_use]
pub fn sparse_transformer_taps(self, base: Vec<f64>, changes: Vec<Vec<(String, f64)>>) -> Self {
self.sparse(TRANSFORMER_TAP, base, changes)
}
#[must_use]
pub fn sparse_capacitor_steps(self, base: Vec<f64>, changes: Vec<Vec<(String, f64)>>) -> Self {
self.sparse(CAPACITOR_STEPS, base, changes)
}
fn layout_for(&self, quantity: &'static str) -> Result<QuantityLayout, Error> {
let network = &self.network;
match quantity {
TERMINAL_VOLTAGE_MAGNITUDE | TERMINAL_VOLTAGE_ANGLE => QuantityLayout::from_order(
quantity,
network.buses().iter().flat_map(|bus| {
bus.terminals
.iter()
.map(move |terminal| format!("{}/{terminal}", bus.id))
}),
),
LOAD_ACTIVE_POWER | LOAD_REACTIVE_POWER => QuantityLayout::from_order(
quantity,
network.loads().iter().flat_map(|load| {
load.terminal_map
.iter()
.map(move |terminal| format!("{}/{terminal}", load.name))
}),
),
GENERATOR_ACTIVE_POWER | GENERATOR_REACTIVE_POWER => QuantityLayout::from_order(
quantity,
network.generators().iter().flat_map(|generator| {
generator
.terminal_map
.iter()
.map(move |terminal| format!("{}/{terminal}", generator.name))
}),
),
SWITCH_CLOSED => QuantityLayout::from_order(
quantity,
network.switches().iter().map(|s| s.name.clone()),
),
TRANSFORMER_TAP => QuantityLayout::from_order(
quantity,
network.transformers().iter().map(|t| t.name.clone()),
),
CAPACITOR_STEPS => QuantityLayout::from_order(
quantity,
network.capacitors().iter().map(|c| c.name.clone()),
),
_ => unreachable!("builder methods name registered quantities"),
}
}
fn build_points(
&self,
point_count: usize,
) -> Result<Vec<OperatingPoint<MulticonductorNetwork>>, Error> {
let mut quantities = std::collections::HashMap::new();
for (quantity, input) in &self.quantities {
let layout = self.layout_for(quantity)?;
let built = match input {
ColumnsInput::Dense(values) => {
dense_quantity(quantity, layout, point_count, values.clone())?
}
ColumnsInput::Sparse { base, changes } => {
sparse_quantity(quantity, layout, point_count, base.clone(), changes.clone())?
}
};
if quantities.insert(*quantity, built).is_some() {
return Err(Error::new(
&codes::BUILD_OPERATING_POINT_SHAPE_MISMATCH,
format!("{quantity} was supplied twice"),
));
}
}
let columns: SharedColumns = Arc::new(OperatingPointColumns {
point_count,
quantities,
});
Ok((0..point_count)
.map(|index| OperatingPoint {
network: self.network.clone(),
columns: Arc::clone(&columns),
index,
})
.collect())
}
pub fn build(self) -> Result<TimeSeries<OperatingPoint<MulticonductorNetwork>>, Error> {
let point_count = self.time_points.len();
if point_count == 0 {
return Err(Error::new(
&codes::BUILD_OPERATING_POINT_SHAPE_MISMATCH,
"an operating point series needs at least one time point",
));
}
let points = self.build_points(point_count)?;
TimeSeries::new(self.time_points, points)
}
pub fn build_point(self) -> Result<OperatingPoint<MulticonductorNetwork>, Error> {
if self.time_points.len() > 1 {
return Err(Error::new(
&codes::BUILD_OPERATING_POINT_SHAPE_MISMATCH,
"a scalar operating point builder cannot contain several time points",
));
}
self.build_points(1)?.pop().ok_or_else(|| {
Error::new(
&codes::BUILD_OPERATING_POINT_SHAPE_MISMATCH,
"the scalar operating point builder produced no point",
)
})
}
}
fn encode_flags(values: Vec<bool>) -> Vec<f64> {
values.into_iter().map(encode_flag).collect()
}
fn encode_flag(value: bool) -> f64 {
if value { 1.0 } else { 0.0 }
}