1use std::sync::Arc;
9
10use powerio_core::Error;
11use powerio_dist::MulticonductorNetwork;
12
13use crate::diagnostics::codes;
14use crate::instance::{McAcOpfInstance, McAcPfInstance};
15use crate::solution::{Producer, Residuals, Termination};
16
17fn check_length(what: &'static str, got: usize, expected: usize) -> Result<(), Error> {
18 if got == expected {
19 Ok(())
20 } else {
21 Err(Error::new(
22 &codes::BUILD_SOLUTION_SHAPE_MISMATCH,
23 format!("{what} carries {got} values; the instance resolves {expected} terminals"),
24 ))
25 }
26}
27
28fn terminal_count(network: &MulticonductorNetwork) -> usize {
29 network.buses().iter().map(|bus| bus.terminals.len()).sum()
30}
31
32fn source_terminal_count(network: &MulticonductorNetwork) -> usize {
33 network
34 .sources()
35 .iter()
36 .map(|source| source.terminal_map.len())
37 .sum()
38}
39
40#[derive(Clone, Debug, Default)]
43struct TerminalIndex {
44 position: std::collections::BTreeMap<(String, String), usize>,
45}
46
47impl TerminalIndex {
48 fn build(network: &MulticonductorNetwork) -> Result<Self, Error> {
49 let mut position = std::collections::BTreeMap::new();
50 let mut offset = 0usize;
51 for row in network.buses() {
52 for (column, terminal) in row.terminals.iter().enumerate() {
53 if position
54 .insert((row.id.clone(), terminal.clone()), offset + column)
55 .is_some()
56 {
57 return Err(Error::new(
58 &codes::BUILD_OPERATING_POINT_IDENTITY_UNKNOWN,
59 format!("bus `{}`: duplicate terminal identity `{terminal}`", row.id),
60 ));
61 }
62 }
63 offset += row.terminals.len();
64 }
65 Ok(Self { position })
66 }
67}
68
69fn terminal_position(index: &TerminalIndex, bus: &str, terminal: &str) -> Option<usize> {
70 index
71 .position
72 .get(&(bus.to_owned(), terminal.to_owned()))
73 .copied()
74}
75
76macro_rules! shared_mc_solution_accessors {
77 ($instance_type:ty) => {
78 #[must_use]
81 pub fn instance(&self) -> &$instance_type {
82 &self.instance
83 }
84
85 #[must_use]
88 pub fn shared_instance(&self) -> Arc<$instance_type> {
89 Arc::clone(&self.instance)
90 }
91
92 fn terminal_index(&self) -> &TerminalIndex {
93 &self.index
94 }
95
96 #[must_use]
98 pub fn network(&self) -> &MulticonductorNetwork {
99 self.instance.network()
100 }
101
102 #[must_use]
104 pub fn termination(&self) -> &Termination {
105 &self.termination
106 }
107
108 #[must_use]
110 pub fn residuals(&self) -> &Residuals {
111 &self.residuals
112 }
113
114 #[must_use]
116 pub fn producer(&self) -> Option<&str> {
117 self.producer.as_deref()
118 }
119
120 #[must_use]
122 pub fn with_producer(mut self, producer: impl Into<String>) -> Self {
123 self.producer = Some(producer.into());
124 self
125 }
126
127 #[must_use]
129 pub fn with_residuals(mut self, residuals: Residuals) -> Self {
130 self.residuals = residuals;
131 self
132 }
133
134 #[must_use]
136 pub fn terminal_voltage_magnitude(&self, bus: &str, terminal: &str) -> Option<f64> {
137 Some(
138 self.terminal_voltage_magnitude
139 [terminal_position(self.terminal_index(), bus, terminal)?],
140 )
141 }
142
143 #[must_use]
145 pub fn terminal_voltage_angle(&self, bus: &str, terminal: &str) -> Option<f64> {
146 Some(
147 self.terminal_voltage_angle
148 [terminal_position(self.terminal_index(), bus, terminal)?],
149 )
150 }
151
152 #[must_use]
155 pub fn terminal_current_magnitude(&self, bus: &str, terminal: &str) -> Option<f64> {
156 let values = self.terminal_current_magnitude.as_ref()?;
157 Some(values[terminal_position(self.terminal_index(), bus, terminal)?])
158 }
159
160 #[must_use]
163 pub fn terminal_active_power(&self, bus: &str, terminal: &str) -> Option<f64> {
164 let values = self.terminal_active_power.as_ref()?;
165 Some(values[terminal_position(self.terminal_index(), bus, terminal)?])
166 }
167
168 pub fn with_terminal_currents(mut self, values: Vec<f64>) -> Result<Self, Error> {
173 check_length(
174 "terminal current magnitudes",
175 values.len(),
176 terminal_count(self.network()),
177 )?;
178 self.terminal_current_magnitude = Some(values);
179 Ok(self)
180 }
181
182 pub fn with_terminal_powers(mut self, values: Vec<f64>) -> Result<Self, Error> {
187 check_length(
188 "terminal active powers",
189 values.len(),
190 terminal_count(self.network()),
191 )?;
192 self.terminal_active_power = Some(values);
193 Ok(self)
194 }
195
196 #[must_use]
199 pub fn source_active_injections(&self) -> &[f64] {
200 &self.source_active_injection
201 }
202 };
203}
204
205#[derive(Clone, Debug)]
209pub struct McAcPfSolution {
210 instance: Arc<McAcPfInstance>,
211 termination: Termination,
212 residuals: Residuals,
213 producer: Producer,
214 terminal_voltage_magnitude: Vec<f64>,
215 terminal_voltage_angle: Vec<f64>,
216 terminal_current_magnitude: Option<Vec<f64>>,
217 terminal_active_power: Option<Vec<f64>>,
218 source_active_injection: Vec<f64>,
219 index: TerminalIndex,
220}
221
222impl McAcPfSolution {
223 pub fn new(
232 instance: Arc<McAcPfInstance>,
233 termination: Termination,
234 terminal_voltage_magnitude: Vec<f64>,
235 terminal_voltage_angle: Vec<f64>,
236 source_active_injection: Vec<f64>,
237 ) -> Result<Self, Error> {
238 let terminals = terminal_count(instance.network());
239 check_length(
240 "terminal voltage magnitudes",
241 terminal_voltage_magnitude.len(),
242 terminals,
243 )?;
244 check_length(
245 "terminal voltage angles",
246 terminal_voltage_angle.len(),
247 terminals,
248 )?;
249 check_length(
250 "source active injections",
251 source_active_injection.len(),
252 source_terminal_count(instance.network()),
253 )?;
254 let index = TerminalIndex::build(instance.network())?;
255 Ok(Self {
256 instance,
257 termination,
258 residuals: Residuals::default(),
259 producer: None,
260 terminal_voltage_magnitude,
261 terminal_voltage_angle,
262 terminal_current_magnitude: None,
263 terminal_active_power: None,
264 source_active_injection,
265 index,
266 })
267 }
268
269 shared_mc_solution_accessors!(McAcPfInstance);
270}
271
272#[derive(Clone, Debug)]
275pub struct McAcOpfSolution {
276 instance: Arc<McAcOpfInstance>,
277 termination: Termination,
278 residuals: Residuals,
279 producer: Producer,
280 terminal_voltage_magnitude: Vec<f64>,
281 terminal_voltage_angle: Vec<f64>,
282 terminal_current_magnitude: Option<Vec<f64>>,
283 terminal_active_power: Option<Vec<f64>>,
284 source_active_injection: Vec<f64>,
285 generator_active_power: Vec<f64>,
288 objective: f64,
289 index: TerminalIndex,
290}
291
292impl McAcOpfSolution {
293 pub fn new(
301 instance: Arc<McAcOpfInstance>,
302 termination: Termination,
303 terminal_voltage_magnitude: Vec<f64>,
304 terminal_voltage_angle: Vec<f64>,
305 source_active_injection: Vec<f64>,
306 generator_active_power: Vec<f64>,
307 objective: f64,
308 ) -> Result<Self, Error> {
309 let terminals = terminal_count(instance.network());
310 check_length(
311 "terminal voltage magnitudes",
312 terminal_voltage_magnitude.len(),
313 terminals,
314 )?;
315 check_length(
316 "terminal voltage angles",
317 terminal_voltage_angle.len(),
318 terminals,
319 )?;
320 check_length(
321 "source active injections",
322 source_active_injection.len(),
323 source_terminal_count(instance.network()),
324 )?;
325 let generator_conductors: usize = instance
326 .network()
327 .generators()
328 .iter()
329 .map(|generator| generator.terminal_map.len())
330 .sum();
331 check_length(
332 "per phase generator dispatch",
333 generator_active_power.len(),
334 generator_conductors,
335 )?;
336 let index = TerminalIndex::build(instance.network())?;
337 Ok(Self {
338 instance,
339 termination,
340 residuals: Residuals::default(),
341 producer: None,
342 terminal_voltage_magnitude,
343 terminal_voltage_angle,
344 terminal_current_magnitude: None,
345 terminal_active_power: None,
346 source_active_injection,
347 generator_active_power,
348 objective,
349 index,
350 })
351 }
352
353 shared_mc_solution_accessors!(McAcOpfInstance);
354
355 #[must_use]
357 pub const fn objective(&self) -> f64 {
358 self.objective
359 }
360
361 #[must_use]
364 pub fn generator_active_powers(&self) -> &[f64] {
365 &self.generator_active_power
366 }
367}