use std::sync::Arc;
use powerio_core::Error;
use powerio_tx::{BalancedNetwork, BusId};
use crate::diagnostics::codes;
use crate::instance::{AcOpfInstance, AcPfInstance, DcOpfInstance, DcPfInstance};
use crate::solution::{Producer, Residuals, Termination};
use crate::state::row_identity;
#[derive(Clone, Debug, Default, PartialEq)]
#[non_exhaustive]
pub struct GeneratorDispatch {
pub p_mw: Vec<f64>,
pub q_mvar: Vec<f64>,
}
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's table has {expected} rows"),
))
}
}
#[derive(Clone, Debug, Default)]
struct SolutionIndex {
bus: std::collections::BTreeMap<BusId, usize>,
branch: std::collections::BTreeMap<String, usize>,
generator: std::collections::BTreeMap<String, usize>,
}
impl SolutionIndex {
fn build(network: &BalancedNetwork) -> Result<Self, Error> {
let mut index = Self::default();
for (row, bus) in network.buses().iter().enumerate() {
if index.bus.insert(bus.id, row).is_some() {
return Err(duplicate_identity("bus", &bus.id.to_string()));
}
}
for (row, branch) in network.branches().iter().enumerate() {
let identity = row_identity(branch.uid.as_deref(), "branches", row);
if index.branch.insert(identity.clone(), row).is_some() {
return Err(duplicate_identity("branch", &identity));
}
}
for (row, generator) in network.generators().iter().enumerate() {
let identity = row_identity(generator.uid.as_deref(), "generators", row);
if index.generator.insert(identity.clone(), row).is_some() {
return Err(duplicate_identity("generator", &identity));
}
}
Ok(index)
}
}
fn solution_index<I>(instance: &std::sync::Arc<I>) -> Result<SolutionIndex, Error>
where
I: NetworkCarrier,
{
SolutionIndex::build(instance.network())
}
trait NetworkCarrier {
fn network(&self) -> &BalancedNetwork;
}
impl NetworkCarrier for DcPfInstance {
fn network(&self) -> &BalancedNetwork {
DcPfInstance::network(self)
}
}
impl NetworkCarrier for AcPfInstance {
fn network(&self) -> &BalancedNetwork {
AcPfInstance::network(self)
}
}
impl NetworkCarrier for DcOpfInstance {
fn network(&self) -> &BalancedNetwork {
DcOpfInstance::network(self)
}
}
impl NetworkCarrier for AcOpfInstance {
fn network(&self) -> &BalancedNetwork {
AcOpfInstance::network(self)
}
}
fn duplicate_identity(kind: &str, identity: &str) -> Error {
Error::new(
&codes::BUILD_STATE_IDENTITY_UNKNOWN,
format!("{kind}: duplicate element identity `{identity}`"),
)
}
fn bus_position(index: &SolutionIndex, bus: BusId) -> Option<usize> {
index.bus.get(&bus).copied()
}
fn branch_position(index: &SolutionIndex, identity: &str) -> Option<usize> {
index.branch.get(identity).copied()
}
fn generator_position(index: &SolutionIndex, identity: &str) -> Option<usize> {
index.generator.get(identity).copied()
}
macro_rules! shared_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)
}
#[must_use]
pub fn network(&self) -> &BalancedNetwork {
self.instance.network()
}
fn row_index(&self) -> &SolutionIndex {
&self.index
}
#[must_use]
pub fn bus_order(&self) -> Vec<BusId> {
self.network().buses().iter().map(|bus| bus.id).collect()
}
#[must_use]
pub fn branch_order(&self) -> Vec<String> {
self.network()
.branches()
.iter()
.enumerate()
.map(|(row, branch)| row_identity(branch.uid.as_deref(), "branches", row))
.collect()
}
#[must_use]
pub fn generator_order(&self) -> Vec<String> {
self.network()
.generators()
.iter()
.enumerate()
.map(|(row, generator)| row_identity(generator.uid.as_deref(), "generators", row))
.collect()
}
#[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
}
pub fn branch_identity_order(&self) -> impl Iterator<Item = String> + '_ {
self.network()
.branches()
.iter()
.enumerate()
.map(|(row, branch)| row_identity(branch.uid.as_deref(), "branches", row))
}
};
}
macro_rules! optional_dispatch_accessors {
() => {
#[must_use]
pub fn generator_dispatch(&self) -> Option<&GeneratorDispatch> {
self.generator_dispatch.as_ref()
}
pub fn with_generator_dispatch(
mut self,
dispatch: GeneratorDispatch,
) -> Result<Self, Error> {
check_length(
"generator dispatch",
dispatch.p_mw.len(),
self.network().generators().len(),
)?;
if !dispatch.q_mvar.is_empty() {
check_length(
"generator reactive dispatch",
dispatch.q_mvar.len(),
self.network().generators().len(),
)?;
}
self.generator_dispatch = Some(dispatch);
Ok(self)
}
};
}
#[derive(Clone, Debug)]
pub struct DcPfSolution {
instance: Arc<DcPfInstance>,
termination: Termination,
residuals: Residuals,
producer: Producer,
bus_voltage_angle: Vec<f64>,
bus_active_injection: Vec<f64>,
branch_from_active_flow: Vec<f64>,
branch_to_active_flow: Vec<f64>,
generator_dispatch: Option<GeneratorDispatch>,
index: SolutionIndex,
}
impl DcPfSolution {
pub fn new(
instance: Arc<DcPfInstance>,
termination: Termination,
bus_voltage_angle: Vec<f64>,
bus_active_injection: Vec<f64>,
branch_from_active_flow: Vec<f64>,
branch_to_active_flow: Vec<f64>,
) -> Result<Self, Error> {
let buses = instance.network().buses().len();
let branches = instance.network().branches().len();
check_length("bus voltage angles", bus_voltage_angle.len(), buses)?;
check_length("bus active injections", bus_active_injection.len(), buses)?;
check_length(
"branch from-side flows",
branch_from_active_flow.len(),
branches,
)?;
check_length(
"branch to-side flows",
branch_to_active_flow.len(),
branches,
)?;
let index = solution_index(&instance)?;
Ok(Self {
instance,
termination,
residuals: Residuals::default(),
producer: None,
bus_voltage_angle,
bus_active_injection,
branch_from_active_flow,
branch_to_active_flow,
generator_dispatch: None,
index,
})
}
shared_solution_accessors!(DcPfInstance);
optional_dispatch_accessors!();
#[must_use]
pub fn bus_voltage_angle(&self, bus: BusId) -> Option<f64> {
Some(self.bus_voltage_angle[bus_position(self.row_index(), bus)?])
}
#[must_use]
pub fn bus_active_injection(&self, bus: BusId) -> Option<f64> {
Some(self.bus_active_injection[bus_position(self.row_index(), bus)?])
}
#[must_use]
pub fn branch_from_active_flow(&self, identity: &str) -> Option<f64> {
Some(self.branch_from_active_flow[branch_position(self.row_index(), identity)?])
}
#[must_use]
pub fn branch_to_active_flow(&self, identity: &str) -> Option<f64> {
Some(self.branch_to_active_flow[branch_position(self.row_index(), identity)?])
}
#[must_use]
pub fn bus_voltage_angles(&self) -> &[f64] {
&self.bus_voltage_angle
}
#[must_use]
pub fn bus_active_injections(&self) -> &[f64] {
&self.bus_active_injection
}
#[must_use]
pub fn branch_from_active_flows(&self) -> &[f64] {
&self.branch_from_active_flow
}
#[must_use]
pub fn branch_to_active_flows(&self) -> &[f64] {
&self.branch_to_active_flow
}
}
#[derive(Clone, Debug)]
pub struct AcPfSolution {
instance: Arc<AcPfInstance>,
termination: Termination,
residuals: Residuals,
producer: Producer,
bus_voltage_magnitude: Vec<f64>,
bus_voltage_angle: Vec<f64>,
bus_active_injection: Vec<f64>,
bus_reactive_injection: Vec<f64>,
branch_from_active_flow: Vec<f64>,
branch_from_reactive_flow: Vec<f64>,
branch_to_active_flow: Vec<f64>,
branch_to_reactive_flow: Vec<f64>,
generator_dispatch: Option<GeneratorDispatch>,
index: SolutionIndex,
}
impl AcPfSolution {
#[allow(clippy::too_many_arguments)] pub fn new(
instance: Arc<AcPfInstance>,
termination: Termination,
bus_voltage_magnitude: Vec<f64>,
bus_voltage_angle: Vec<f64>,
bus_active_injection: Vec<f64>,
bus_reactive_injection: Vec<f64>,
branch_from_active_flow: Vec<f64>,
branch_from_reactive_flow: Vec<f64>,
branch_to_active_flow: Vec<f64>,
branch_to_reactive_flow: Vec<f64>,
) -> Result<Self, Error> {
let buses = instance.network().buses().len();
let branches = instance.network().branches().len();
check_length("bus voltage magnitudes", bus_voltage_magnitude.len(), buses)?;
check_length("bus voltage angles", bus_voltage_angle.len(), buses)?;
check_length("bus active injections", bus_active_injection.len(), buses)?;
check_length(
"bus reactive injections",
bus_reactive_injection.len(),
buses,
)?;
check_length(
"branch from-side active flows",
branch_from_active_flow.len(),
branches,
)?;
check_length(
"branch from-side reactive flows",
branch_from_reactive_flow.len(),
branches,
)?;
check_length(
"branch to-side active flows",
branch_to_active_flow.len(),
branches,
)?;
check_length(
"branch to-side reactive flows",
branch_to_reactive_flow.len(),
branches,
)?;
let index = solution_index(&instance)?;
Ok(Self {
instance,
termination,
residuals: Residuals::default(),
producer: None,
bus_voltage_magnitude,
bus_voltage_angle,
bus_active_injection,
bus_reactive_injection,
branch_from_active_flow,
branch_from_reactive_flow,
branch_to_active_flow,
branch_to_reactive_flow,
generator_dispatch: None,
index,
})
}
shared_solution_accessors!(AcPfInstance);
optional_dispatch_accessors!();
#[must_use]
pub fn bus_voltage_magnitude(&self, bus: BusId) -> Option<f64> {
Some(self.bus_voltage_magnitude[bus_position(self.row_index(), bus)?])
}
#[must_use]
pub fn bus_voltage_angle(&self, bus: BusId) -> Option<f64> {
Some(self.bus_voltage_angle[bus_position(self.row_index(), bus)?])
}
#[must_use]
pub fn bus_active_injection(&self, bus: BusId) -> Option<f64> {
Some(self.bus_active_injection[bus_position(self.row_index(), bus)?])
}
#[must_use]
pub fn bus_reactive_injection(&self, bus: BusId) -> Option<f64> {
Some(self.bus_reactive_injection[bus_position(self.row_index(), bus)?])
}
#[must_use]
pub fn branch_from_active_flow(&self, identity: &str) -> Option<f64> {
Some(self.branch_from_active_flow[branch_position(self.row_index(), identity)?])
}
#[must_use]
pub fn branch_from_reactive_flow(&self, identity: &str) -> Option<f64> {
Some(self.branch_from_reactive_flow[branch_position(self.row_index(), identity)?])
}
#[must_use]
pub fn branch_to_active_flow(&self, identity: &str) -> Option<f64> {
Some(self.branch_to_active_flow[branch_position(self.row_index(), identity)?])
}
#[must_use]
pub fn branch_to_reactive_flow(&self, identity: &str) -> Option<f64> {
Some(self.branch_to_reactive_flow[branch_position(self.row_index(), identity)?])
}
}
#[derive(Clone, Debug)]
pub struct DcOpfSolution {
instance: Arc<DcOpfInstance>,
termination: Termination,
residuals: Residuals,
producer: Producer,
bus_voltage_angle: Vec<f64>,
bus_active_injection: Vec<f64>,
branch_from_active_flow: Vec<f64>,
branch_to_active_flow: Vec<f64>,
generator_active_power: Vec<f64>,
objective: f64,
index: SolutionIndex,
}
impl DcOpfSolution {
#[allow(clippy::too_many_arguments)] pub fn new(
instance: Arc<DcOpfInstance>,
termination: Termination,
bus_voltage_angle: Vec<f64>,
bus_active_injection: Vec<f64>,
branch_from_active_flow: Vec<f64>,
branch_to_active_flow: Vec<f64>,
generator_active_power: Vec<f64>,
objective: f64,
) -> Result<Self, Error> {
let buses = instance.network().buses().len();
let branches = instance.network().branches().len();
check_length("bus voltage angles", bus_voltage_angle.len(), buses)?;
check_length("bus active injections", bus_active_injection.len(), buses)?;
check_length(
"branch from-side flows",
branch_from_active_flow.len(),
branches,
)?;
check_length(
"branch to-side flows",
branch_to_active_flow.len(),
branches,
)?;
check_length(
"generator active dispatch",
generator_active_power.len(),
instance.network().generators().len(),
)?;
let index = solution_index(&instance)?;
Ok(Self {
instance,
termination,
residuals: Residuals::default(),
producer: None,
bus_voltage_angle,
bus_active_injection,
branch_from_active_flow,
branch_to_active_flow,
generator_active_power,
objective,
index,
})
}
shared_solution_accessors!(DcOpfInstance);
#[must_use]
pub const fn objective(&self) -> f64 {
self.objective
}
#[must_use]
pub fn generator_active_power(&self, identity: &str) -> Option<f64> {
Some(self.generator_active_power[generator_position(self.row_index(), identity)?])
}
#[must_use]
pub fn bus_voltage_angle(&self, bus: BusId) -> Option<f64> {
Some(self.bus_voltage_angle[bus_position(self.row_index(), bus)?])
}
#[must_use]
pub fn bus_active_injection(&self, bus: BusId) -> Option<f64> {
Some(self.bus_active_injection[bus_position(self.row_index(), bus)?])
}
#[must_use]
pub fn branch_from_active_flow(&self, identity: &str) -> Option<f64> {
Some(self.branch_from_active_flow[branch_position(self.row_index(), identity)?])
}
#[must_use]
pub fn branch_to_active_flow(&self, identity: &str) -> Option<f64> {
Some(self.branch_to_active_flow[branch_position(self.row_index(), identity)?])
}
}
#[derive(Clone, Debug)]
pub struct AcOpfSolution {
instance: Arc<AcOpfInstance>,
termination: Termination,
residuals: Residuals,
producer: Producer,
bus_voltage_magnitude: Vec<f64>,
bus_voltage_angle: Vec<f64>,
bus_active_injection: Vec<f64>,
bus_reactive_injection: Vec<f64>,
branch_from_active_flow: Vec<f64>,
branch_from_reactive_flow: Vec<f64>,
branch_to_active_flow: Vec<f64>,
branch_to_reactive_flow: Vec<f64>,
generator_active_power: Vec<f64>,
generator_reactive_power: Vec<f64>,
objective: f64,
index: SolutionIndex,
}
impl AcOpfSolution {
#[allow(clippy::too_many_arguments)] pub fn new(
instance: Arc<AcOpfInstance>,
termination: Termination,
bus_voltage_magnitude: Vec<f64>,
bus_voltage_angle: Vec<f64>,
bus_active_injection: Vec<f64>,
bus_reactive_injection: Vec<f64>,
branch_from_active_flow: Vec<f64>,
branch_from_reactive_flow: Vec<f64>,
branch_to_active_flow: Vec<f64>,
branch_to_reactive_flow: Vec<f64>,
generator_active_power: Vec<f64>,
generator_reactive_power: Vec<f64>,
objective: f64,
) -> Result<Self, Error> {
let buses = instance.network().buses().len();
let branches = instance.network().branches().len();
let generators = instance.network().generators().len();
check_length("bus voltage magnitudes", bus_voltage_magnitude.len(), buses)?;
check_length("bus voltage angles", bus_voltage_angle.len(), buses)?;
check_length("bus active injections", bus_active_injection.len(), buses)?;
check_length(
"bus reactive injections",
bus_reactive_injection.len(),
buses,
)?;
check_length(
"branch from-side active flows",
branch_from_active_flow.len(),
branches,
)?;
check_length(
"branch from-side reactive flows",
branch_from_reactive_flow.len(),
branches,
)?;
check_length(
"branch to-side active flows",
branch_to_active_flow.len(),
branches,
)?;
check_length(
"branch to-side reactive flows",
branch_to_reactive_flow.len(),
branches,
)?;
check_length(
"generator active dispatch",
generator_active_power.len(),
generators,
)?;
check_length(
"generator reactive dispatch",
generator_reactive_power.len(),
generators,
)?;
let index = solution_index(&instance)?;
Ok(Self {
instance,
termination,
residuals: Residuals::default(),
producer: None,
bus_voltage_magnitude,
bus_voltage_angle,
bus_active_injection,
bus_reactive_injection,
branch_from_active_flow,
branch_from_reactive_flow,
branch_to_active_flow,
branch_to_reactive_flow,
generator_active_power,
generator_reactive_power,
objective,
index,
})
}
shared_solution_accessors!(AcOpfInstance);
#[must_use]
pub const fn objective(&self) -> f64 {
self.objective
}
#[must_use]
pub fn generator_active_power(&self, identity: &str) -> Option<f64> {
Some(self.generator_active_power[generator_position(self.row_index(), identity)?])
}
#[must_use]
pub fn generator_reactive_power(&self, identity: &str) -> Option<f64> {
Some(self.generator_reactive_power[generator_position(self.row_index(), identity)?])
}
#[must_use]
pub fn bus_voltage_magnitude(&self, bus: BusId) -> Option<f64> {
Some(self.bus_voltage_magnitude[bus_position(self.row_index(), bus)?])
}
#[must_use]
pub fn bus_voltage_angle(&self, bus: BusId) -> Option<f64> {
Some(self.bus_voltage_angle[bus_position(self.row_index(), bus)?])
}
#[must_use]
pub fn bus_active_injection(&self, bus: BusId) -> Option<f64> {
Some(self.bus_active_injection[bus_position(self.row_index(), bus)?])
}
#[must_use]
pub fn bus_reactive_injection(&self, bus: BusId) -> Option<f64> {
Some(self.bus_reactive_injection[bus_position(self.row_index(), bus)?])
}
#[must_use]
pub fn branch_from_active_flow(&self, identity: &str) -> Option<f64> {
Some(self.branch_from_active_flow[branch_position(self.row_index(), identity)?])
}
#[must_use]
pub fn branch_from_reactive_flow(&self, identity: &str) -> Option<f64> {
Some(self.branch_from_reactive_flow[branch_position(self.row_index(), identity)?])
}
#[must_use]
pub fn branch_to_active_flow(&self, identity: &str) -> Option<f64> {
Some(self.branch_to_active_flow[branch_position(self.row_index(), identity)?])
}
#[must_use]
pub fn branch_to_reactive_flow(&self, identity: &str) -> Option<f64> {
Some(self.branch_to_reactive_flow[branch_position(self.row_index(), identity)?])
}
#[must_use]
pub fn bus_voltage_magnitudes(&self) -> &[f64] {
&self.bus_voltage_magnitude
}
#[must_use]
pub fn bus_voltage_angles(&self) -> &[f64] {
&self.bus_voltage_angle
}
#[must_use]
pub fn bus_active_injections(&self) -> &[f64] {
&self.bus_active_injection
}
#[must_use]
pub fn bus_reactive_injections(&self) -> &[f64] {
&self.bus_reactive_injection
}
#[must_use]
pub fn branch_from_active_flows(&self) -> &[f64] {
&self.branch_from_active_flow
}
#[must_use]
pub fn branch_from_reactive_flows(&self) -> &[f64] {
&self.branch_from_reactive_flow
}
#[must_use]
pub fn branch_to_active_flows(&self) -> &[f64] {
&self.branch_to_active_flow
}
#[must_use]
pub fn branch_to_reactive_flows(&self) -> &[f64] {
&self.branch_to_reactive_flow
}
#[must_use]
pub fn generator_active_powers(&self) -> &[f64] {
&self.generator_active_power
}
#[must_use]
pub fn generator_reactive_powers(&self) -> &[f64] {
&self.generator_reactive_power
}
}