1use std::sync::Arc;
5
6use powerio_core::{Error, TimePoint, TimeSeries};
7use powerio_tx::{BalancedNetwork, BusId};
8
9use super::{
10 OperatingPoint, OperatingPointColumns, OperatingPointFlags, OperatingPointValues,
11 QuantityLayout, SharedColumns, dense_quantity, row_identity, sparse_quantity,
12};
13use crate::diagnostics::codes;
14
15const BUS_VOLTAGE_MAGNITUDE: &str = "bus_voltage_magnitude";
21const BUS_VOLTAGE_ANGLE: &str = "bus_voltage_angle";
22const BUS_ACTIVE_INJECTION: &str = "bus_active_injection";
23const BUS_REACTIVE_INJECTION: &str = "bus_reactive_injection";
24pub(crate) const GENERATOR_ACTIVE_POWER: &str = "generator_active_power";
25pub(crate) const GENERATOR_REACTIVE_POWER: &str = "generator_reactive_power";
26pub(crate) const GENERATOR_VOLTAGE_SETPOINT: &str = "generator_voltage_setpoint";
27pub(crate) const GENERATOR_IN_SERVICE: &str = "generator_in_service";
28pub(crate) const LOAD_ACTIVE_POWER: &str = "load_active_power";
29pub(crate) const LOAD_REACTIVE_POWER: &str = "load_reactive_power";
30pub(crate) const BRANCH_IN_SERVICE: &str = "branch_in_service";
31pub(crate) const BRANCH_TAP_RATIO: &str = "branch_tap_ratio";
32pub(crate) const BRANCH_PHASE_SHIFT: &str = "branch_phase_shift";
33pub(crate) const SWITCH_CLOSED: &str = "switch_closed";
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
37#[non_exhaustive]
38pub enum BalancedOperatingPointQuantity {
39 BusVoltageMagnitude,
40 BusVoltageAngle,
41 BusActiveInjection,
42 BusReactiveInjection,
43 GeneratorActivePower,
44 GeneratorReactivePower,
45 GeneratorVoltageSetpoint,
46 LoadActivePower,
47 LoadReactivePower,
48 BranchTapRatio,
49 BranchPhaseShift,
50}
51
52impl BalancedOperatingPointQuantity {
53 #[must_use]
55 pub const fn name(self) -> &'static str {
56 match self {
57 Self::BusVoltageMagnitude => BUS_VOLTAGE_MAGNITUDE,
58 Self::BusVoltageAngle => BUS_VOLTAGE_ANGLE,
59 Self::BusActiveInjection => BUS_ACTIVE_INJECTION,
60 Self::BusReactiveInjection => BUS_REACTIVE_INJECTION,
61 Self::GeneratorActivePower => GENERATOR_ACTIVE_POWER,
62 Self::GeneratorReactivePower => GENERATOR_REACTIVE_POWER,
63 Self::GeneratorVoltageSetpoint => GENERATOR_VOLTAGE_SETPOINT,
64 Self::LoadActivePower => LOAD_ACTIVE_POWER,
65 Self::LoadReactivePower => LOAD_REACTIVE_POWER,
66 Self::BranchTapRatio => BRANCH_TAP_RATIO,
67 Self::BranchPhaseShift => BRANCH_PHASE_SHIFT,
68 }
69 }
70}
71
72#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
74#[non_exhaustive]
75pub enum BalancedOperatingPointFlag {
76 GeneratorInService,
77 BranchInService,
78 SwitchClosed,
79}
80
81impl BalancedOperatingPointFlag {
82 #[must_use]
84 pub const fn name(self) -> &'static str {
85 match self {
86 Self::GeneratorInService => GENERATOR_IN_SERVICE,
87 Self::BranchInService => BRANCH_IN_SERVICE,
88 Self::SwitchClosed => SWITCH_CLOSED,
89 }
90 }
91}
92
93impl OperatingPoint<BalancedNetwork> {
94 pub(crate) fn rebind_network(mut self, network: BalancedNetwork) -> Result<Self, Error> {
98 let layout = BalancedOperatingPointBuilder::new(network.clone(), Vec::new());
99 for quantity in self.columns.quantities.keys() {
100 let expected = layout.identity_order(quantity)?;
101 let actual: Vec<&str> = self
102 .identity_order(quantity)
103 .expect("the quantity came from this point")
104 .collect();
105 if actual.len() != expected.len() || actual.iter().zip(&expected).any(|(a, b)| *a != b)
106 {
107 return Err(Error::new(
108 &codes::BUILD_OPERATING_POINT_SHAPE_MISMATCH,
109 format!(
110 "{quantity}: edited network changes the initial point's element identity order"
111 ),
112 ));
113 }
114 }
115 self.network = network;
116 Ok(self)
117 }
118
119 #[must_use]
121 pub fn values(
122 &self,
123 quantity: BalancedOperatingPointQuantity,
124 ) -> Option<OperatingPointValues<'_>> {
125 self.iter_values(quantity.name())
126 }
127
128 #[must_use]
130 pub fn flags(&self, quantity: BalancedOperatingPointFlag) -> Option<OperatingPointFlags<'_>> {
131 self.iter_flags(quantity.name())
132 }
133
134 #[must_use]
137 pub fn bus_voltage_magnitude(&self, bus: BusId) -> Option<f64> {
138 self.value(BUS_VOLTAGE_MAGNITUDE, &bus.0.to_string())
139 }
140
141 #[must_use]
143 pub fn bus_voltage_angle(&self, bus: BusId) -> Option<f64> {
144 self.value(BUS_VOLTAGE_ANGLE, &bus.0.to_string())
145 }
146
147 #[must_use]
149 pub fn bus_active_injection(&self, bus: BusId) -> Option<f64> {
150 self.value(BUS_ACTIVE_INJECTION, &bus.0.to_string())
151 }
152
153 #[must_use]
155 pub fn bus_reactive_injection(&self, bus: BusId) -> Option<f64> {
156 self.value(BUS_REACTIVE_INJECTION, &bus.0.to_string())
157 }
158
159 #[must_use]
161 pub fn generator_active_power(&self, identity: &str) -> Option<f64> {
162 self.value(GENERATOR_ACTIVE_POWER, identity)
163 }
164
165 #[must_use]
167 pub fn generator_reactive_power(&self, identity: &str) -> Option<f64> {
168 self.value(GENERATOR_REACTIVE_POWER, identity)
169 }
170
171 #[must_use]
173 pub fn generator_voltage_setpoint(&self, identity: &str) -> Option<f64> {
174 self.value(GENERATOR_VOLTAGE_SETPOINT, identity)
175 }
176
177 #[must_use]
179 pub fn generator_in_service(&self, identity: &str) -> Option<bool> {
180 self.value(GENERATOR_IN_SERVICE, identity)
181 .map(|value| value != 0.0)
182 }
183
184 #[must_use]
186 pub fn load_active_power(&self, identity: &str) -> Option<f64> {
187 self.value(LOAD_ACTIVE_POWER, identity)
188 }
189
190 #[must_use]
192 pub fn load_reactive_power(&self, identity: &str) -> Option<f64> {
193 self.value(LOAD_REACTIVE_POWER, identity)
194 }
195
196 #[must_use]
198 pub fn branch_in_service(&self, identity: &str) -> Option<bool> {
199 self.value(BRANCH_IN_SERVICE, identity)
200 .map(|value| value != 0.0)
201 }
202
203 #[must_use]
205 pub fn branch_tap_ratio(&self, identity: &str) -> Option<f64> {
206 self.value(BRANCH_TAP_RATIO, identity)
207 }
208
209 #[must_use]
211 pub fn branch_phase_shift(&self, identity: &str) -> Option<f64> {
212 self.value(BRANCH_PHASE_SHIFT, identity)
213 }
214
215 #[must_use]
217 pub fn switch_closed(&self, identity: &str) -> Option<bool> {
218 self.value(SWITCH_CLOSED, identity)
219 .map(|value| value != 0.0)
220 }
221}
222
223#[derive(Debug)]
229pub struct BalancedOperatingPointBuilder {
230 network: BalancedNetwork,
231 time_points: Vec<TimePoint>,
232 quantities: Vec<(&'static str, ColumnsInput)>,
233}
234
235#[derive(Debug)]
236enum ColumnsInput {
237 Dense(Vec<f64>),
238 Sparse {
239 base: Vec<f64>,
240 changes: Vec<Vec<(String, f64)>>,
241 },
242}
243
244#[derive(Clone, Copy, Debug, PartialEq, Eq)]
245enum KeyFamily {
246 Bus,
247 Generator,
248 Load,
249 Branch,
250 Switch,
251}
252
253fn family_of(quantity: &'static str) -> KeyFamily {
254 match quantity {
255 BUS_VOLTAGE_MAGNITUDE
256 | BUS_VOLTAGE_ANGLE
257 | BUS_ACTIVE_INJECTION
258 | BUS_REACTIVE_INJECTION => KeyFamily::Bus,
259 GENERATOR_ACTIVE_POWER
260 | GENERATOR_REACTIVE_POWER
261 | GENERATOR_VOLTAGE_SETPOINT
262 | GENERATOR_IN_SERVICE => KeyFamily::Generator,
263 LOAD_ACTIVE_POWER | LOAD_REACTIVE_POWER => KeyFamily::Load,
264 BRANCH_IN_SERVICE | BRANCH_TAP_RATIO | BRANCH_PHASE_SHIFT => KeyFamily::Branch,
265 SWITCH_CLOSED => KeyFamily::Switch,
266 _ => unreachable!("builder methods name registered quantities"),
267 }
268}
269
270impl BalancedOperatingPointBuilder {
271 #[must_use]
272 pub fn new(mut network: BalancedNetwork, time_points: Vec<TimePoint>) -> Self {
273 network.assign_missing_component_ids();
274 Self {
275 network,
276 time_points,
277 quantities: Vec::new(),
278 }
279 }
280
281 #[must_use]
283 pub fn for_point(network: BalancedNetwork) -> Self {
284 Self::new(network, Vec::new())
285 }
286
287 fn dense(mut self, quantity: &'static str, values: Vec<f64>) -> Self {
288 self.quantities
289 .push((quantity, ColumnsInput::Dense(values)));
290 self
291 }
292
293 fn sparse(
294 mut self,
295 quantity: &'static str,
296 base: Vec<f64>,
297 changes: Vec<Vec<(String, f64)>>,
298 ) -> Self {
299 self.quantities
300 .push((quantity, ColumnsInput::Sparse { base, changes }));
301 self
302 }
303
304 fn sparse_flags(
305 self,
306 quantity: &'static str,
307 base: Vec<bool>,
308 changes: Vec<Vec<(String, bool)>>,
309 ) -> Self {
310 self.sparse(
311 quantity,
312 encode_flags(base),
313 changes
314 .into_iter()
315 .map(|point| {
316 point
317 .into_iter()
318 .map(|(identity, value)| (identity, encode_flag(value)))
319 .collect()
320 })
321 .collect(),
322 )
323 }
324
325 #[must_use]
328 pub fn bus_voltage_magnitudes(self, values: Vec<f64>) -> Self {
329 self.dense(BUS_VOLTAGE_MAGNITUDE, values)
330 }
331
332 #[must_use]
333 pub fn bus_voltage_angles(self, values: Vec<f64>) -> Self {
334 self.dense(BUS_VOLTAGE_ANGLE, values)
335 }
336
337 #[must_use]
338 pub fn bus_active_injections(self, values: Vec<f64>) -> Self {
339 self.dense(BUS_ACTIVE_INJECTION, values)
340 }
341
342 #[must_use]
343 pub fn bus_reactive_injections(self, values: Vec<f64>) -> Self {
344 self.dense(BUS_REACTIVE_INJECTION, values)
345 }
346
347 #[must_use]
348 pub fn generator_active_powers(self, values: Vec<f64>) -> Self {
349 self.dense(GENERATOR_ACTIVE_POWER, values)
350 }
351
352 #[must_use]
353 pub fn generator_reactive_powers(self, values: Vec<f64>) -> Self {
354 self.dense(GENERATOR_REACTIVE_POWER, values)
355 }
356
357 #[must_use]
358 pub fn generator_voltage_setpoints(self, values: Vec<f64>) -> Self {
359 self.dense(GENERATOR_VOLTAGE_SETPOINT, values)
360 }
361
362 #[must_use]
363 pub fn generator_in_service(self, values: Vec<bool>) -> Self {
364 self.dense(GENERATOR_IN_SERVICE, encode_flags(values))
365 }
366
367 #[must_use]
368 pub fn load_active_powers(self, values: Vec<f64>) -> Self {
369 self.dense(LOAD_ACTIVE_POWER, values)
370 }
371
372 #[must_use]
373 pub fn load_reactive_powers(self, values: Vec<f64>) -> Self {
374 self.dense(LOAD_REACTIVE_POWER, values)
375 }
376
377 #[must_use]
378 pub fn branch_in_service(self, values: Vec<bool>) -> Self {
379 self.dense(BRANCH_IN_SERVICE, encode_flags(values))
380 }
381
382 #[must_use]
383 pub fn branch_tap_ratios(self, values: Vec<f64>) -> Self {
384 self.dense(BRANCH_TAP_RATIO, values)
385 }
386
387 #[must_use]
388 pub fn branch_phase_shifts(self, values: Vec<f64>) -> Self {
389 self.dense(BRANCH_PHASE_SHIFT, values)
390 }
391
392 #[must_use]
393 pub fn switch_closed(self, values: Vec<bool>) -> Self {
394 self.dense(SWITCH_CLOSED, encode_flags(values))
395 }
396
397 #[must_use]
400 pub fn sparse_bus_voltage_magnitudes(
401 self,
402 base: Vec<f64>,
403 changes: Vec<Vec<(String, f64)>>,
404 ) -> Self {
405 self.sparse(BUS_VOLTAGE_MAGNITUDE, base, changes)
406 }
407
408 #[must_use]
409 pub fn sparse_bus_voltage_angles(
410 self,
411 base: Vec<f64>,
412 changes: Vec<Vec<(String, f64)>>,
413 ) -> Self {
414 self.sparse(BUS_VOLTAGE_ANGLE, base, changes)
415 }
416
417 #[must_use]
418 pub fn sparse_bus_active_injections(
419 self,
420 base: Vec<f64>,
421 changes: Vec<Vec<(String, f64)>>,
422 ) -> Self {
423 self.sparse(BUS_ACTIVE_INJECTION, base, changes)
424 }
425
426 #[must_use]
427 pub fn sparse_bus_reactive_injections(
428 self,
429 base: Vec<f64>,
430 changes: Vec<Vec<(String, f64)>>,
431 ) -> Self {
432 self.sparse(BUS_REACTIVE_INJECTION, base, changes)
433 }
434
435 #[must_use]
436 pub fn sparse_generator_active_powers(
437 self,
438 base: Vec<f64>,
439 changes: Vec<Vec<(String, f64)>>,
440 ) -> Self {
441 self.sparse(GENERATOR_ACTIVE_POWER, base, changes)
442 }
443
444 #[must_use]
445 pub fn sparse_generator_reactive_powers(
446 self,
447 base: Vec<f64>,
448 changes: Vec<Vec<(String, f64)>>,
449 ) -> Self {
450 self.sparse(GENERATOR_REACTIVE_POWER, base, changes)
451 }
452
453 #[must_use]
454 pub fn sparse_generator_voltage_setpoints(
455 self,
456 base: Vec<f64>,
457 changes: Vec<Vec<(String, f64)>>,
458 ) -> Self {
459 self.sparse(GENERATOR_VOLTAGE_SETPOINT, base, changes)
460 }
461
462 #[must_use]
463 pub fn sparse_generator_in_service(
464 self,
465 base: Vec<bool>,
466 changes: Vec<Vec<(String, bool)>>,
467 ) -> Self {
468 self.sparse_flags(GENERATOR_IN_SERVICE, base, changes)
469 }
470
471 #[must_use]
474 pub fn sparse_load_active_powers(
475 self,
476 base: Vec<f64>,
477 changes: Vec<Vec<(String, f64)>>,
478 ) -> Self {
479 self.sparse(LOAD_ACTIVE_POWER, base, changes)
480 }
481
482 #[must_use]
483 pub fn sparse_load_reactive_powers(
484 self,
485 base: Vec<f64>,
486 changes: Vec<Vec<(String, f64)>>,
487 ) -> Self {
488 self.sparse(LOAD_REACTIVE_POWER, base, changes)
489 }
490
491 #[must_use]
492 pub fn sparse_branch_in_service(
493 self,
494 base: Vec<bool>,
495 changes: Vec<Vec<(String, bool)>>,
496 ) -> Self {
497 self.sparse_flags(BRANCH_IN_SERVICE, base, changes)
498 }
499
500 #[must_use]
501 pub fn sparse_branch_tap_ratios(
502 self,
503 base: Vec<f64>,
504 changes: Vec<Vec<(String, f64)>>,
505 ) -> Self {
506 self.sparse(BRANCH_TAP_RATIO, base, changes)
507 }
508
509 #[must_use]
510 pub fn sparse_branch_phase_shifts(
511 self,
512 base: Vec<f64>,
513 changes: Vec<Vec<(String, f64)>>,
514 ) -> Self {
515 self.sparse(BRANCH_PHASE_SHIFT, base, changes)
516 }
517
518 #[must_use]
519 pub fn sparse_switch_closed(self, base: Vec<bool>, changes: Vec<Vec<(String, bool)>>) -> Self {
520 self.sparse_flags(SWITCH_CLOSED, base, changes)
521 }
522
523 fn identity_order(&self, quantity: &'static str) -> Result<Vec<String>, Error> {
526 Ok(self
527 .layout_for(quantity)?
528 .order()
529 .map(str::to_string)
530 .collect())
531 }
532
533 fn layout_for(&self, quantity: &'static str) -> Result<QuantityLayout, Error> {
534 let network = &self.network;
535 match family_of(quantity) {
536 KeyFamily::Bus => QuantityLayout::from_order(
537 quantity,
538 network.buses().iter().map(|bus| bus.id.0.to_string()),
539 ),
540 KeyFamily::Generator => QuantityLayout::from_order(
541 quantity,
542 network
543 .generators()
544 .iter()
545 .enumerate()
546 .map(|(row, g)| row_identity(g.uid.as_deref(), "generators", row)),
547 ),
548 KeyFamily::Load => QuantityLayout::from_order(
549 quantity,
550 network
551 .loads()
552 .iter()
553 .enumerate()
554 .map(|(row, l)| row_identity(l.uid.as_deref(), "loads", row)),
555 ),
556 KeyFamily::Branch => QuantityLayout::from_order(
557 quantity,
558 network
559 .branches()
560 .iter()
561 .enumerate()
562 .map(|(row, b)| row_identity(b.uid.as_deref(), "branches", row)),
563 ),
564 KeyFamily::Switch => QuantityLayout::from_order(
565 quantity,
566 network
567 .switches()
568 .iter()
569 .enumerate()
570 .map(|(row, s)| row_identity(s.uid.as_deref(), "switches", row)),
571 ),
572 }
573 }
574
575 fn build_points(
576 &self,
577 point_count: usize,
578 ) -> Result<Vec<OperatingPoint<BalancedNetwork>>, Error> {
579 let mut quantities = std::collections::HashMap::new();
580 for (quantity, input) in &self.quantities {
581 let layout = self.layout_for(quantity)?;
582 let built = match input {
583 ColumnsInput::Dense(values) => {
584 dense_quantity(quantity, layout, point_count, values.clone())?
585 }
586 ColumnsInput::Sparse { base, changes } => {
587 sparse_quantity(quantity, layout, point_count, base.clone(), changes.clone())?
588 }
589 };
590 if quantities.insert(*quantity, built).is_some() {
591 return Err(Error::new(
592 &codes::BUILD_OPERATING_POINT_SHAPE_MISMATCH,
593 format!("{quantity} was supplied twice"),
594 ));
595 }
596 }
597 let columns: SharedColumns = Arc::new(OperatingPointColumns {
598 point_count,
599 quantities,
600 });
601 Ok((0..point_count)
602 .map(|index| OperatingPoint {
603 network: self.network.clone(),
604 columns: Arc::clone(&columns),
605 index,
606 })
607 .collect())
608 }
609
610 pub fn build(self) -> Result<TimeSeries<OperatingPoint<BalancedNetwork>>, Error> {
617 let point_count = self.time_points.len();
618 if point_count == 0 {
619 return Err(Error::new(
620 &codes::BUILD_OPERATING_POINT_SHAPE_MISMATCH,
621 "an operating point series needs at least one time point",
622 ));
623 }
624 let points = self.build_points(point_count)?;
625 TimeSeries::new(self.time_points, points)
626 }
627
628 pub fn build_point(self) -> Result<OperatingPoint<BalancedNetwork>, Error> {
633 if self.time_points.len() > 1 {
634 return Err(Error::new(
635 &codes::BUILD_OPERATING_POINT_SHAPE_MISMATCH,
636 "a scalar operating point builder cannot contain several time points",
637 ));
638 }
639 self.build_points(1)?.pop().ok_or_else(|| {
640 Error::new(
641 &codes::BUILD_OPERATING_POINT_SHAPE_MISMATCH,
642 "the scalar operating point builder produced no point",
643 )
644 })
645 }
646}
647
648fn encode_flags(values: Vec<bool>) -> Vec<f64> {
649 values.into_iter().map(encode_flag).collect()
650}
651
652fn encode_flag(value: bool) -> f64 {
653 if value { 1.0 } else { 0.0 }
654}