use std::sync::Arc;
use powerio_core::Error;
use powerio_dist::MulticonductorNetwork;
use crate::diagnostics::codes;
use crate::instance::{McAcOpfInstance, McAcPfInstance};
use crate::solution::{Producer, Residuals, Termination};
fn check_length(what: &'static str, got: usize, expected: usize) -> Result<(), Error> {
if got == expected {
Ok(())
} else {
Err(Error::new(
&codes::BUILD_SOLUTION_SHAPE_MISMATCH,
format!("{what} carries {got} values; the instance resolves {expected} terminals"),
))
}
}
fn terminal_count(network: &MulticonductorNetwork) -> usize {
network.buses().iter().map(|bus| bus.terminals.len()).sum()
}
fn source_terminal_count(network: &MulticonductorNetwork) -> usize {
network
.sources()
.iter()
.map(|source| source.terminal_map.len())
.sum()
}
#[derive(Clone, Debug, Default)]
struct TerminalIndex {
position: std::collections::BTreeMap<(String, String), usize>,
}
impl TerminalIndex {
fn build(network: &MulticonductorNetwork) -> Result<Self, Error> {
let mut position = std::collections::BTreeMap::new();
let mut offset = 0usize;
for row in network.buses() {
for (column, terminal) in row.terminals.iter().enumerate() {
if position
.insert((row.id.clone(), terminal.clone()), offset + column)
.is_some()
{
return Err(Error::new(
&codes::BUILD_STATE_IDENTITY_UNKNOWN,
format!("bus `{}`: duplicate terminal identity `{terminal}`", row.id),
));
}
}
offset += row.terminals.len();
}
Ok(Self { position })
}
}
fn terminal_position(index: &TerminalIndex, bus: &str, terminal: &str) -> Option<usize> {
index
.position
.get(&(bus.to_owned(), terminal.to_owned()))
.copied()
}
macro_rules! shared_mc_solution_accessors {
($instance_type:ty) => {
#[must_use]
pub fn instance(&self) -> &$instance_type {
&self.instance
}
#[must_use]
pub fn shared_instance(&self) -> Arc<$instance_type> {
Arc::clone(&self.instance)
}
fn terminal_index(&self) -> &TerminalIndex {
&self.index
}
#[must_use]
pub fn network(&self) -> &MulticonductorNetwork {
self.instance.network()
}
#[must_use]
pub fn termination(&self) -> &Termination {
&self.termination
}
#[must_use]
pub fn residuals(&self) -> &Residuals {
&self.residuals
}
#[must_use]
pub fn producer(&self) -> Option<&str> {
self.producer.as_deref()
}
#[must_use]
pub fn with_producer(mut self, producer: impl Into<String>) -> Self {
self.producer = Some(producer.into());
self
}
#[must_use]
pub fn with_residuals(mut self, residuals: Residuals) -> Self {
self.residuals = residuals;
self
}
#[must_use]
pub fn terminal_voltage_magnitude(&self, bus: &str, terminal: &str) -> Option<f64> {
Some(
self.terminal_voltage_magnitude
[terminal_position(self.terminal_index(), bus, terminal)?],
)
}
#[must_use]
pub fn terminal_voltage_angle(&self, bus: &str, terminal: &str) -> Option<f64> {
Some(
self.terminal_voltage_angle
[terminal_position(self.terminal_index(), bus, terminal)?],
)
}
#[must_use]
pub fn terminal_current_magnitude(&self, bus: &str, terminal: &str) -> Option<f64> {
let values = self.terminal_current_magnitude.as_ref()?;
Some(values[terminal_position(self.terminal_index(), bus, terminal)?])
}
#[must_use]
pub fn terminal_active_power(&self, bus: &str, terminal: &str) -> Option<f64> {
let values = self.terminal_active_power.as_ref()?;
Some(values[terminal_position(self.terminal_index(), bus, terminal)?])
}
pub fn with_terminal_currents(mut self, values: Vec<f64>) -> Result<Self, Error> {
check_length(
"terminal current magnitudes",
values.len(),
terminal_count(self.network()),
)?;
self.terminal_current_magnitude = Some(values);
Ok(self)
}
pub fn with_terminal_powers(mut self, values: Vec<f64>) -> Result<Self, Error> {
check_length(
"terminal active powers",
values.len(),
terminal_count(self.network()),
)?;
self.terminal_active_power = Some(values);
Ok(self)
}
#[must_use]
pub fn source_active_injections(&self) -> &[f64] {
&self.source_active_injection
}
};
}
#[derive(Clone, Debug)]
pub struct McAcPfSolution {
instance: Arc<McAcPfInstance>,
termination: Termination,
residuals: Residuals,
producer: Producer,
terminal_voltage_magnitude: Vec<f64>,
terminal_voltage_angle: Vec<f64>,
terminal_current_magnitude: Option<Vec<f64>>,
terminal_active_power: Option<Vec<f64>>,
source_active_injection: Vec<f64>,
index: TerminalIndex,
}
impl McAcPfSolution {
pub fn new(
instance: Arc<McAcPfInstance>,
termination: Termination,
terminal_voltage_magnitude: Vec<f64>,
terminal_voltage_angle: Vec<f64>,
source_active_injection: Vec<f64>,
) -> Result<Self, Error> {
let terminals = terminal_count(instance.network());
check_length(
"terminal voltage magnitudes",
terminal_voltage_magnitude.len(),
terminals,
)?;
check_length(
"terminal voltage angles",
terminal_voltage_angle.len(),
terminals,
)?;
check_length(
"source active injections",
source_active_injection.len(),
source_terminal_count(instance.network()),
)?;
let index = TerminalIndex::build(instance.network())?;
Ok(Self {
instance,
termination,
residuals: Residuals::default(),
producer: None,
terminal_voltage_magnitude,
terminal_voltage_angle,
terminal_current_magnitude: None,
terminal_active_power: None,
source_active_injection,
index,
})
}
shared_mc_solution_accessors!(McAcPfInstance);
}
#[derive(Clone, Debug)]
pub struct McAcOpfSolution {
instance: Arc<McAcOpfInstance>,
termination: Termination,
residuals: Residuals,
producer: Producer,
terminal_voltage_magnitude: Vec<f64>,
terminal_voltage_angle: Vec<f64>,
terminal_current_magnitude: Option<Vec<f64>>,
terminal_active_power: Option<Vec<f64>>,
source_active_injection: Vec<f64>,
generator_active_power: Vec<f64>,
objective: f64,
index: TerminalIndex,
}
impl McAcOpfSolution {
pub fn new(
instance: Arc<McAcOpfInstance>,
termination: Termination,
terminal_voltage_magnitude: Vec<f64>,
terminal_voltage_angle: Vec<f64>,
source_active_injection: Vec<f64>,
generator_active_power: Vec<f64>,
objective: f64,
) -> Result<Self, Error> {
let terminals = terminal_count(instance.network());
check_length(
"terminal voltage magnitudes",
terminal_voltage_magnitude.len(),
terminals,
)?;
check_length(
"terminal voltage angles",
terminal_voltage_angle.len(),
terminals,
)?;
check_length(
"source active injections",
source_active_injection.len(),
source_terminal_count(instance.network()),
)?;
let generator_conductors: usize = instance
.network()
.generators()
.iter()
.map(|generator| generator.terminal_map.len())
.sum();
check_length(
"per phase generator dispatch",
generator_active_power.len(),
generator_conductors,
)?;
let index = TerminalIndex::build(instance.network())?;
Ok(Self {
instance,
termination,
residuals: Residuals::default(),
producer: None,
terminal_voltage_magnitude,
terminal_voltage_angle,
terminal_current_magnitude: None,
terminal_active_power: None,
source_active_injection,
generator_active_power,
objective,
index,
})
}
shared_mc_solution_accessors!(McAcOpfInstance);
#[must_use]
pub const fn objective(&self) -> f64 {
self.objective
}
#[must_use]
pub fn generator_active_powers(&self) -> &[f64] {
&self.generator_active_power
}
}