1use powerio_core::Error;
13use powerio_dist::MulticonductorNetwork;
14use serde::{Deserialize, Serialize};
15
16use crate::OperatingPoint;
17use crate::diagnostics::codes;
18use crate::instance::balanced::transform_discarded;
19use crate::instance::constraints::MulticonductorActiveConstraints;
20use crate::instance::objective::{Objective, ObjectiveTerm};
21
22#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
26pub struct PrescribedTerminalPower {
27 pub load: String,
28 pub terminals: Vec<String>,
30 pub p_w: Vec<f64>,
32 pub q_var: Vec<f64>,
34 pub voltage_model: powerio_dist::DistLoadVoltageModel,
36}
37
38#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
41pub struct PrescribedSourceVoltage {
42 pub source: String,
43 pub terminals: Vec<String>,
45 pub v_magnitude: Vec<f64>,
47 pub v_angle: Vec<f64>,
49}
50
51#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
53#[serde(rename_all = "snake_case", tag = "kind")]
54#[non_exhaustive]
55pub enum ActiveControlMode {
56 RegulatorTap { transformer: String },
58 CapacitorSteps { capacitor: String },
60}
61
62#[derive(Clone, Debug)]
64pub struct McAcPfInstance {
65 network: MulticonductorNetwork,
66 loads: Vec<PrescribedTerminalPower>,
67 sources: Vec<PrescribedSourceVoltage>,
68 isolated_terminals: Vec<(String, String)>,
69 control_modes: Vec<ActiveControlMode>,
70 initial_point: Option<OperatingPoint<MulticonductorNetwork>>,
71}
72
73impl McAcPfInstance {
74 pub fn from_network(network: MulticonductorNetwork) -> Result<Self, Error> {
83 powerio_dist::require_electrical_readiness(&network)?;
84 if network.sources().is_empty() {
85 return Err(Error::new(
86 &codes::BUILD_INSTANCE_SHAPE_MISMATCH,
87 "the multiconductor network states no voltage source to anchor the calculation",
88 ));
89 }
90 let loads = network
91 .loads()
92 .iter()
93 .map(|load| PrescribedTerminalPower {
94 load: load.name.clone(),
95 terminals: load.terminal_map.clone(),
96 p_w: load.p_nom.clone(),
97 q_var: load.q_nom.clone(),
98 voltage_model: load.voltage_model.clone(),
99 })
100 .collect();
101 let sources = network
102 .sources()
103 .iter()
104 .map(|source| PrescribedSourceVoltage {
105 source: source.name.clone(),
106 terminals: source.terminal_map.clone(),
107 v_magnitude: source.v_magnitude.clone(),
108 v_angle: source.v_angle.clone(),
109 })
110 .collect();
111 let controlled_element = |object: &powerio_dist::UntypedObject, key: &str| {
115 object
116 .props
117 .iter()
118 .find(|(name, _)| name.as_deref() == Some(key))
119 .map(|(_, value)| value.clone())
120 };
121 let mut control_modes = Vec::new();
122 for object in network.untyped_objects() {
123 match object.class.to_ascii_lowercase().as_str() {
124 "regcontrol" => {
125 if let Some(transformer) = controlled_element(object, "transformer") {
126 control_modes.push(ActiveControlMode::RegulatorTap { transformer });
127 }
128 }
129 "capcontrol" => {
130 if let Some(capacitor) = controlled_element(object, "capacitor") {
131 control_modes.push(ActiveControlMode::CapacitorSteps { capacitor });
132 }
133 }
134 _ => {}
135 }
136 }
137 Ok(Self {
138 network,
139 loads,
140 sources,
141 isolated_terminals: Vec::new(),
142 control_modes,
143 initial_point: None,
144 })
145 }
146
147 #[must_use]
149 pub fn with_initial_point(mut self, point: OperatingPoint<MulticonductorNetwork>) -> Self {
150 self.initial_point = Some(point);
151 self
152 }
153
154 pub fn with_network(mut self, network: MulticonductorNetwork) -> Result<Self, Error> {
158 let mut replacement = Self::from_network(network.clone())?;
159 if let Some(initial) = self.initial_point.take() {
160 replacement.initial_point = Some(initial.rebind_network(network)?);
161 }
162 Ok(replacement)
163 }
164
165 #[must_use]
167 pub fn network(&self) -> &MulticonductorNetwork {
168 &self.network
169 }
170
171 #[must_use]
173 pub fn loads(&self) -> &[PrescribedTerminalPower] {
174 &self.loads
175 }
176
177 #[must_use]
179 pub fn sources(&self) -> &[PrescribedSourceVoltage] {
180 &self.sources
181 }
182
183 #[must_use]
185 pub fn isolated_terminals(&self) -> &[(String, String)] {
186 &self.isolated_terminals
187 }
188
189 #[must_use]
191 pub fn control_modes(&self) -> &[ActiveControlMode] {
192 &self.control_modes
193 }
194
195 #[must_use]
197 pub const fn initial_point(&self) -> Option<&OperatingPoint<MulticonductorNetwork>> {
198 self.initial_point.as_ref()
199 }
200}
201
202#[derive(Clone, Debug)]
206pub struct McAcOpfInstance {
207 network: MulticonductorNetwork,
208 objective: Objective,
209 constraints: MulticonductorActiveConstraints,
210 initial_point: Option<OperatingPoint<MulticonductorNetwork>>,
211}
212
213impl McAcOpfInstance {
214 pub fn from_network(network: MulticonductorNetwork) -> Result<Self, Error> {
220 powerio_dist::require_electrical_readiness(&network)?;
221 if network.sources().is_empty() {
222 return Err(Error::new(
223 &codes::BUILD_INSTANCE_SHAPE_MISMATCH,
224 "the multiconductor network states no voltage source to anchor the calculation",
225 ));
226 }
227 Ok(Self {
228 network,
229 objective: Objective::active_power_dispatch_cost(),
230 constraints: MulticonductorActiveConstraints::default(),
231 initial_point: None,
232 })
233 }
234
235 #[must_use]
238 pub fn with_objective(mut self, objective: Objective) -> Self {
239 self.objective = objective;
240 self
241 }
242
243 #[must_use]
245 pub fn with_objective_term(mut self, term: ObjectiveTerm) -> Self {
246 self.objective = std::mem::take(&mut self.objective).with_term(term);
247 self
248 }
249
250 #[must_use]
252 pub fn with_constraints(mut self, constraints: MulticonductorActiveConstraints) -> Self {
253 self.constraints = constraints;
254 self
255 }
256
257 #[must_use]
259 pub fn with_initial_point(mut self, point: OperatingPoint<MulticonductorNetwork>) -> Self {
260 self.initial_point = Some(point);
261 self
262 }
263
264 pub fn with_network(mut self, network: MulticonductorNetwork) -> Result<Self, Error> {
267 powerio_dist::require_electrical_readiness(&network)?;
268 if network.sources().is_empty() {
269 return Err(Error::new(
270 &codes::BUILD_INSTANCE_SHAPE_MISMATCH,
271 "the multiconductor network states no voltage source to anchor the calculation",
272 ));
273 }
274 if let Some(initial) = self.initial_point.take() {
275 self.initial_point = Some(initial.rebind_network(network.clone())?);
276 }
277 self.network = network;
278 Ok(self)
279 }
280
281 #[must_use]
283 pub fn network(&self) -> &MulticonductorNetwork {
284 &self.network
285 }
286
287 #[must_use]
289 pub const fn objective(&self) -> &Objective {
290 &self.objective
291 }
292
293 #[must_use]
295 pub const fn constraints(&self) -> &MulticonductorActiveConstraints {
296 &self.constraints
297 }
298
299 #[must_use]
301 pub const fn initial_point(&self) -> Option<&OperatingPoint<MulticonductorNetwork>> {
302 self.initial_point.as_ref()
303 }
304
305 pub fn to_mc_ac_pf(&self) -> Result<(McAcPfInstance, Vec<powerio_core::Diagnostic>), Error> {
312 let instance = McAcPfInstance::from_network(self.network.clone())?;
313 Ok((
314 instance,
315 vec![transform_discarded(
316 "the objective and the active constraint selections",
317 )],
318 ))
319 }
320}