use powerio_core::Error;
use powerio_dist::MulticonductorNetwork;
use serde::{Deserialize, Serialize};
use crate::OperatingPoint;
use crate::diagnostics::codes;
use crate::instance::balanced::transform_discarded;
use crate::instance::constraints::MulticonductorActiveConstraints;
use crate::instance::objective::{Objective, ObjectiveTerm};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct PrescribedTerminalPower {
pub load: String,
pub terminals: Vec<String>,
pub p_w: Vec<f64>,
pub q_var: Vec<f64>,
pub voltage_model: powerio_dist::DistLoadVoltageModel,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct PrescribedSourceVoltage {
pub source: String,
pub terminals: Vec<String>,
pub v_magnitude: Vec<f64>,
pub v_angle: Vec<f64>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "kind")]
#[non_exhaustive]
pub enum ActiveControlMode {
RegulatorTap { transformer: String },
CapacitorSteps { capacitor: String },
}
#[derive(Clone, Debug)]
pub struct McAcPfInstance {
network: MulticonductorNetwork,
loads: Vec<PrescribedTerminalPower>,
sources: Vec<PrescribedSourceVoltage>,
isolated_terminals: Vec<(String, String)>,
control_modes: Vec<ActiveControlMode>,
initial_point: Option<OperatingPoint<MulticonductorNetwork>>,
}
impl McAcPfInstance {
pub fn from_network(network: MulticonductorNetwork) -> Result<Self, Error> {
powerio_dist::require_electrical_readiness(&network)?;
if network.sources().is_empty() {
return Err(Error::new(
&codes::BUILD_INSTANCE_SHAPE_MISMATCH,
"the multiconductor network states no voltage source to anchor the calculation",
));
}
let loads = network
.loads()
.iter()
.map(|load| PrescribedTerminalPower {
load: load.name.clone(),
terminals: load.terminal_map.clone(),
p_w: load.p_nom.clone(),
q_var: load.q_nom.clone(),
voltage_model: load.voltage_model.clone(),
})
.collect();
let sources = network
.sources()
.iter()
.map(|source| PrescribedSourceVoltage {
source: source.name.clone(),
terminals: source.terminal_map.clone(),
v_magnitude: source.v_magnitude.clone(),
v_angle: source.v_angle.clone(),
})
.collect();
let controlled_element = |object: &powerio_dist::UntypedObject, key: &str| {
object
.props
.iter()
.find(|(name, _)| name.as_deref() == Some(key))
.map(|(_, value)| value.clone())
};
let mut control_modes = Vec::new();
for object in network.untyped_objects() {
match object.class.to_ascii_lowercase().as_str() {
"regcontrol" => {
if let Some(transformer) = controlled_element(object, "transformer") {
control_modes.push(ActiveControlMode::RegulatorTap { transformer });
}
}
"capcontrol" => {
if let Some(capacitor) = controlled_element(object, "capacitor") {
control_modes.push(ActiveControlMode::CapacitorSteps { capacitor });
}
}
_ => {}
}
}
Ok(Self {
network,
loads,
sources,
isolated_terminals: Vec::new(),
control_modes,
initial_point: None,
})
}
#[must_use]
pub fn with_initial_point(mut self, point: OperatingPoint<MulticonductorNetwork>) -> Self {
self.initial_point = Some(point);
self
}
pub fn with_network(mut self, network: MulticonductorNetwork) -> Result<Self, Error> {
let mut replacement = Self::from_network(network.clone())?;
if let Some(initial) = self.initial_point.take() {
replacement.initial_point = Some(initial.rebind_network(network)?);
}
Ok(replacement)
}
#[must_use]
pub fn network(&self) -> &MulticonductorNetwork {
&self.network
}
#[must_use]
pub fn loads(&self) -> &[PrescribedTerminalPower] {
&self.loads
}
#[must_use]
pub fn sources(&self) -> &[PrescribedSourceVoltage] {
&self.sources
}
#[must_use]
pub fn isolated_terminals(&self) -> &[(String, String)] {
&self.isolated_terminals
}
#[must_use]
pub fn control_modes(&self) -> &[ActiveControlMode] {
&self.control_modes
}
#[must_use]
pub const fn initial_point(&self) -> Option<&OperatingPoint<MulticonductorNetwork>> {
self.initial_point.as_ref()
}
}
#[derive(Clone, Debug)]
pub struct McAcOpfInstance {
network: MulticonductorNetwork,
objective: Objective,
constraints: MulticonductorActiveConstraints,
initial_point: Option<OperatingPoint<MulticonductorNetwork>>,
}
impl McAcOpfInstance {
pub fn from_network(network: MulticonductorNetwork) -> Result<Self, Error> {
powerio_dist::require_electrical_readiness(&network)?;
if network.sources().is_empty() {
return Err(Error::new(
&codes::BUILD_INSTANCE_SHAPE_MISMATCH,
"the multiconductor network states no voltage source to anchor the calculation",
));
}
Ok(Self {
network,
objective: Objective::active_power_dispatch_cost(),
constraints: MulticonductorActiveConstraints::default(),
initial_point: None,
})
}
#[must_use]
pub fn with_objective(mut self, objective: Objective) -> Self {
self.objective = objective;
self
}
#[must_use]
pub fn with_objective_term(mut self, term: ObjectiveTerm) -> Self {
self.objective = std::mem::take(&mut self.objective).with_term(term);
self
}
#[must_use]
pub fn with_constraints(mut self, constraints: MulticonductorActiveConstraints) -> Self {
self.constraints = constraints;
self
}
#[must_use]
pub fn with_initial_point(mut self, point: OperatingPoint<MulticonductorNetwork>) -> Self {
self.initial_point = Some(point);
self
}
pub fn with_network(mut self, network: MulticonductorNetwork) -> Result<Self, Error> {
powerio_dist::require_electrical_readiness(&network)?;
if network.sources().is_empty() {
return Err(Error::new(
&codes::BUILD_INSTANCE_SHAPE_MISMATCH,
"the multiconductor network states no voltage source to anchor the calculation",
));
}
if let Some(initial) = self.initial_point.take() {
self.initial_point = Some(initial.rebind_network(network.clone())?);
}
self.network = network;
Ok(self)
}
#[must_use]
pub fn network(&self) -> &MulticonductorNetwork {
&self.network
}
#[must_use]
pub const fn objective(&self) -> &Objective {
&self.objective
}
#[must_use]
pub const fn constraints(&self) -> &MulticonductorActiveConstraints {
&self.constraints
}
#[must_use]
pub const fn initial_point(&self) -> Option<&OperatingPoint<MulticonductorNetwork>> {
self.initial_point.as_ref()
}
pub fn to_mc_ac_pf(&self) -> Result<(McAcPfInstance, Vec<powerio_core::Diagnostic>), Error> {
let instance = McAcPfInstance::from_network(self.network.clone())?;
Ok((
instance,
vec![transform_discarded(
"the objective and the active constraint selections",
)],
))
}
}