1use std::borrow::Cow;
19use std::collections::BTreeMap;
20use std::sync::Arc;
21
22use powerio_core::ComponentId;
23
24use crate::diagnostics::{Diagnostic, codes};
25use crate::format::psse::{
26 detailed_source_property, quoted_circuit_id, quoted_device_id, transformer_3w_id,
27};
28use crate::network::{BalancedNetwork, BusId, BusType};
29
30use super::{ContingencyAction, ContingencySet, MAX_READER_NOTES};
31
32const BUS: &str = "bus";
36const LOAD: &str = "load";
37const SHUNT: &str = "shunt";
38const GENERATOR: &str = "generator";
39const BRANCH: &str = "branch";
40const SWITCH: &str = "switch";
41const TRANSFORMER_3W: &str = "transformer_3w";
42
43#[derive(Debug, Clone)]
60pub struct PsseEquipmentIndex<'n> {
61 net: &'n BalancedNetwork,
62 tables: Cow<'n, EquipmentTables>,
65}
66
67#[derive(Debug, Clone)]
70struct EquipmentTables {
71 machine_ids: Vec<String>,
72 circuit_ids: Vec<String>,
73 switch_ids: Vec<String>,
74 transformer_3w_ids: Vec<String>,
75 bus_rows: BTreeMap<BusId, usize>,
76 branch_rows: BranchRows,
79 switch_rows: SwitchRows,
82 machine_rows: MachineRows,
83 fixed_shunt_rows: DeviceRows,
84 switched_shunt_rows: BTreeMap<BusId, Vec<usize>>,
85 load_rows: DeviceRows,
86 transformer_3w_rows: Transformer3wRows,
89 names: super::tara::NameIndex,
91}
92
93fn sorted_triple(buses: [BusId; 3]) -> [BusId; 3] {
94 let mut sorted = buses;
95 sorted.sort_unstable();
96 sorted
97}
98
99type DeviceRows = BTreeMap<BusId, Vec<(String, usize)>>;
104
105type BranchRows = BTreeMap<(BusId, BusId, bool, String), Vec<usize>>;
114
115type MachineRows = BTreeMap<(BusId, String), usize>;
117
118type SwitchRows = BTreeMap<(BusId, BusId, String), Vec<usize>>;
121
122type Transformer3wRows = BTreeMap<[BusId; 3], Vec<(String, usize)>>;
125
126fn machine_index(net: &BalancedNetwork, sanitized: &mut usize) -> (Vec<String>, MachineRows) {
134 let mut ids = Vec::with_capacity(net.generators().len());
135 let mut rows = BTreeMap::new();
136 let mut used = BTreeMap::new();
137 for (row, generator) in net.generators().iter().enumerate() {
138 let preferred =
139 detailed_source_property(net, GENERATOR, generator.uid.as_deref(), "psse_eqid")
140 .filter(|id| !id.is_empty());
141 let id = quoted_circuit_id(preferred, generator.bus, &mut used, sanitized);
142 rows.insert((generator.bus, id.trim().to_owned()), row);
143 ids.push(id);
144 }
145 (ids, rows)
146}
147
148fn branch_index(net: &BalancedNetwork, sanitized: &mut usize) -> (Vec<String>, BranchRows) {
156 let mut ids = vec![String::new(); net.branches().len()];
157 let mut rows: BranchRows = BTreeMap::new();
158 let mut line_ids = BTreeMap::new();
159 let mut transformer_ids = BTreeMap::new();
160 for transformers in [false, true] {
161 for (row, branch) in net.branches().iter().enumerate() {
162 if branch.is_transformer() != transformers {
163 continue;
164 }
165 let retained = transformers
166 .then(|| {
167 detailed_source_property(net, "transformer", branch.uid.as_deref(), "psse_eqid")
168 })
169 .flatten();
170 let preferred = branch
171 .extras
172 .get("id")
173 .and_then(serde_json::Value::as_str)
174 .or(retained);
175 let used = if transformers {
176 &mut transformer_ids
177 } else {
178 &mut line_ids
179 };
180 let id = quoted_circuit_id(preferred, (branch.from, branch.to), used, sanitized);
181 rows.entry((branch.from, branch.to, transformers, id.trim().to_owned()))
182 .or_default()
183 .push(row);
184 ids[row] = id;
185 }
186 }
187 (ids, rows)
188}
189
190fn switch_index(net: &BalancedNetwork, sanitized: &mut usize) -> (Vec<String>, SwitchRows) {
195 let mut ids = Vec::with_capacity(net.switches().len());
196 let mut rows: SwitchRows = BTreeMap::new();
197 let mut used = BTreeMap::new();
198 for (row, switch) in net.switches().iter().enumerate() {
199 let preferred = switch
200 .extras
201 .get("psse_ckt")
202 .and_then(serde_json::Value::as_str);
203 let id = quoted_circuit_id(preferred, (switch.from, switch.to), &mut used, sanitized);
204 rows.entry((switch.from, switch.to, id.trim().to_owned()))
205 .or_default()
206 .push(row);
207 ids.push(id);
208 }
209 (ids, rows)
210}
211
212fn load_index(net: &BalancedNetwork, sanitized: &mut usize) -> DeviceRows {
214 let mut rows: DeviceRows = BTreeMap::new();
215 let mut used = BTreeMap::new();
216 for (row, load) in net.loads().iter().enumerate() {
217 let id = quoted_device_id(&load.extras, load.bus, &mut used, sanitized);
218 rows.entry(load.bus)
219 .or_default()
220 .push((id.trim().to_owned(), row));
221 }
222 rows
223}
224
225fn shunt_index(
231 net: &BalancedNetwork,
232 sanitized: &mut usize,
233) -> (DeviceRows, BTreeMap<BusId, Vec<usize>>) {
234 let mut fixed: DeviceRows = BTreeMap::new();
235 let mut switched: BTreeMap<BusId, Vec<usize>> = BTreeMap::new();
236 let mut used = BTreeMap::new();
237 for (row, shunt) in net.shunts().iter().enumerate() {
238 if shunt.control.is_some() {
239 switched.entry(shunt.bus).or_default().push(row);
240 continue;
241 }
242 let id = quoted_device_id(&shunt.extras, shunt.bus, &mut used, sanitized);
243 fixed
244 .entry(shunt.bus)
245 .or_default()
246 .push((id.trim().to_owned(), row));
247 }
248 (fixed, switched)
249}
250
251fn transformer_3w_index(
254 net: &BalancedNetwork,
255 sanitized: &mut usize,
256) -> (Vec<String>, Transformer3wRows) {
257 let mut ids = Vec::with_capacity(net.transformers_3w().len());
258 let mut rows: Transformer3wRows = BTreeMap::new();
259 let mut used = BTreeMap::new();
260 for (row, transformer) in net.transformers_3w().iter().enumerate() {
261 let id = transformer_3w_id(net, transformer, &mut used, sanitized);
262 let buses = sorted_triple([
263 transformer.windings[0].bus,
264 transformer.windings[1].bus,
265 transformer.windings[2].bus,
266 ]);
267 rows.entry(buses)
268 .or_default()
269 .push((id.trim().to_owned(), row));
270 ids.push(id);
271 }
272 (ids, rows)
273}
274
275impl EquipmentTables {
276 fn new(net: &BalancedNetwork) -> Self {
277 let mut sanitized = 0usize;
280 let (machine_ids, machine_rows) = machine_index(net, &mut sanitized);
281 let (circuit_ids, branch_rows) = branch_index(net, &mut sanitized);
282 let (switch_ids, switch_rows) = switch_index(net, &mut sanitized);
283 let (fixed_shunt_rows, switched_shunt_rows) = shunt_index(net, &mut sanitized);
284 let (transformer_3w_ids, transformer_3w_rows) = transformer_3w_index(net, &mut sanitized);
285 Self {
286 machine_ids,
287 circuit_ids,
288 switch_ids,
289 transformer_3w_ids,
290 bus_rows: net
291 .buses()
292 .iter()
293 .enumerate()
294 .map(|(row, bus)| (bus.id, row))
295 .collect(),
296 branch_rows,
297 switch_rows,
298 machine_rows,
299 fixed_shunt_rows,
300 switched_shunt_rows,
301 load_rows: load_index(net, &mut sanitized),
302 transformer_3w_rows,
303 names: super::tara::NameIndex::new(net),
304 }
305 }
306}
307
308impl<'n> PsseEquipmentIndex<'n> {
309 #[must_use]
311 pub fn new(net: &'n BalancedNetwork) -> Self {
312 Self {
313 net,
314 tables: Cow::Owned(EquipmentTables::new(net)),
315 }
316 }
317
318 #[must_use]
321 pub fn network(&self) -> &'n BalancedNetwork {
322 self.net
323 }
324
325 #[must_use]
327 pub fn machine_ids(&self) -> &[String] {
328 &self.tables.machine_ids
329 }
330
331 #[must_use]
333 pub fn circuit_ids(&self) -> &[String] {
334 &self.tables.circuit_ids
335 }
336
337 #[must_use]
339 pub fn switch_ids(&self) -> &[String] {
340 &self.tables.switch_ids
341 }
342
343 #[must_use]
346 pub fn transformer_3w_ids(&self) -> &[String] {
347 &self.tables.transformer_3w_ids
348 }
349
350 #[must_use]
352 pub fn bus_row(&self, bus: BusId) -> Option<usize> {
353 self.tables.bus_rows.get(&bus).copied()
354 }
355
356 #[must_use]
365 pub fn branch_rows(&self, from: BusId, to: BusId, circuit: &str) -> Vec<usize> {
366 let lines = self.family_rows(from, to, circuit, false);
367 if lines.is_empty() {
368 self.family_rows(from, to, circuit, true)
369 } else {
370 lines
371 }
372 }
373
374 #[must_use]
380 pub fn switch_rows(&self, from: BusId, to: BusId, circuit: &str) -> Vec<usize> {
381 let circuit = circuit.trim();
382 let mut rows = Vec::new();
383 if let Some(forward) = self.tables.switch_rows.get(&(from, to, circuit.to_owned())) {
384 rows.extend_from_slice(forward);
385 }
386 if from != to
387 && let Some(reverse) = self.tables.switch_rows.get(&(to, from, circuit.to_owned()))
388 {
389 rows.extend_from_slice(reverse);
390 }
391 rows
392 }
393
394 fn family_rows(&self, from: BusId, to: BusId, circuit: &str, transformer: bool) -> Vec<usize> {
397 let circuit = circuit.trim();
398 let mut rows = Vec::new();
399 if let Some(forward) =
400 self.tables
401 .branch_rows
402 .get(&(from, to, transformer, circuit.to_owned()))
403 {
404 rows.extend_from_slice(forward);
405 }
406 if from != to
407 && let Some(reverse) =
408 self.tables
409 .branch_rows
410 .get(&(to, from, transformer, circuit.to_owned()))
411 {
412 rows.extend_from_slice(reverse);
413 }
414 rows
415 }
416
417 #[must_use]
422 pub fn machine_row(&self, bus: BusId, id: &str) -> Option<usize> {
423 self.tables
424 .machine_rows
425 .get(&(bus, id.trim().to_owned()))
426 .copied()
427 }
428
429 #[must_use]
432 pub fn fixed_shunt_rows(&self, bus: BusId, id: Option<&str>) -> Vec<usize> {
433 select_rows(self.tables.fixed_shunt_rows.get(&bus), id)
434 }
435
436 #[must_use]
441 pub fn switched_shunt_rows(&self, bus: BusId) -> Vec<usize> {
442 self.tables
443 .switched_shunt_rows
444 .get(&bus)
445 .cloned()
446 .unwrap_or_default()
447 }
448
449 #[must_use]
452 pub fn load_rows(&self, bus: BusId, id: Option<&str>) -> Vec<usize> {
453 select_rows(self.tables.load_rows.get(&bus), id)
454 }
455
456 #[must_use]
462 pub fn transformer_3w_row(&self, buses: [BusId; 3], circuit: &str) -> Option<usize> {
463 self.transformer_3w_rows(buses, circuit).first().copied()
464 }
465
466 #[must_use]
472 pub fn transformer_3w_rows(&self, buses: [BusId; 3], circuit: &str) -> Vec<usize> {
473 let circuit = circuit.trim();
474 self.tables
475 .transformer_3w_rows
476 .get(&sorted_triple(buses))
477 .into_iter()
478 .flatten()
479 .filter(|(id, _)| id == circuit)
480 .map(|(_, row)| *row)
481 .collect()
482 }
483}
484
485#[derive(Debug, Clone)]
497pub struct SharedPsseEquipmentIndex {
498 net: Arc<BalancedNetwork>,
499 tables: EquipmentTables,
500}
501
502impl SharedPsseEquipmentIndex {
503 #[must_use]
507 pub fn new(net: impl Into<Arc<BalancedNetwork>>) -> Self {
508 let net = net.into();
509 let tables = EquipmentTables::new(&net);
510 Self { net, tables }
511 }
512
513 #[must_use]
515 pub fn network(&self) -> &Arc<BalancedNetwork> {
516 &self.net
517 }
518
519 #[must_use]
522 pub fn index(&self) -> PsseEquipmentIndex<'_> {
523 PsseEquipmentIndex {
524 net: &self.net,
525 tables: Cow::Borrowed(&self.tables),
526 }
527 }
528}
529
530fn select_rows(at_bus: Option<&Vec<(String, usize)>>, id: Option<&str>) -> Vec<usize> {
534 let Some(devices) = at_bus else {
535 return Vec::new();
536 };
537 match id {
538 None => devices.iter().map(|(_, row)| *row).collect(),
539 Some(wanted) => {
540 let wanted = wanted.trim();
541 devices
542 .iter()
543 .filter(|(id, _)| id == wanted)
544 .map(|(_, row)| *row)
545 .collect()
546 }
547 }
548}
549
550#[derive(Debug, Clone, Default, PartialEq)]
552#[non_exhaustive]
553pub struct ContingencyResolution {
554 pub cases: Vec<ResolvedCase>,
556 pub resolved: usize,
558 pub unresolved: usize,
560 pub unrecognized_statements: usize,
562}
563
564impl ContingencyResolution {
565 #[must_use]
573 pub fn diagnostics(&self) -> Vec<Diagnostic> {
574 let mut notes = Vec::new();
575 for case in self.cases.iter().filter(|case| !case.is_resolved()) {
576 if notes.len() == MAX_READER_NOTES {
577 notes.push(Diagnostic::of(
578 &codes::BUILD_CON_NOTES_TRUNCATED,
579 "further resolution notes suppressed",
580 ));
581 break;
582 }
583 let first = &case.unresolved[0];
584 notes.push(Diagnostic::of(
585 &codes::BUILD_CON_CASE_UNRESOLVED,
586 format!(
587 "contingency '{}': {}",
588 case.name,
589 describe(&first.action, first.reason)
590 ),
591 ));
592 }
593 notes
594 }
595}
596
597#[derive(Debug, Clone, Default, PartialEq)]
599#[non_exhaustive]
600pub struct ResolvedCase {
601 pub name: String,
602 pub components: Vec<ResolvedComponent>,
605 pub unresolved: Vec<UnresolvedAction>,
607}
608
609impl ResolvedCase {
610 #[must_use]
612 pub fn is_resolved(&self) -> bool {
613 self.unresolved.is_empty()
614 }
615}
616
617#[derive(Debug, Clone, PartialEq, Eq)]
619#[non_exhaustive]
620pub struct ResolvedComponent {
621 pub component_type: &'static str,
625 pub id: Option<ComponentId>,
631 pub row: usize,
633 pub in_service: bool,
637}
638
639#[derive(Debug, Clone, PartialEq)]
641#[non_exhaustive]
642pub struct UnresolvedAction {
643 pub action: ContingencyAction,
644 pub reason: UnresolvedReason,
645}
646
647#[derive(Debug, Clone, Copy, PartialEq, Eq)]
649#[non_exhaustive]
650pub enum UnresolvedReason {
651 NoSuchBus,
652 NoSuchBranch,
653 AmbiguousBranch {
656 matches: usize,
657 },
658 AmbiguousTransformer3w {
661 matches: usize,
662 },
663 NoSuchMachine,
664 NoSuchShunt,
665 NoSuchLoad,
666 NoSuchTransformer3w,
667 Unrecognized,
669 NoSuchBusName,
671 AmbiguousBusName {
674 matches: usize,
675 },
676 NoSuchBranchName,
678 AmbiguousBranchName {
680 matches: usize,
681 },
682}
683
684impl UnresolvedReason {
685 #[must_use]
687 pub fn name(&self) -> &'static str {
688 match self {
689 Self::NoSuchBus => "no_such_bus",
690 Self::NoSuchBranch => "no_such_branch",
691 Self::AmbiguousBranch { .. } => "ambiguous_branch",
692 Self::AmbiguousTransformer3w { .. } => "ambiguous_transformer_3w",
693 Self::NoSuchMachine => "no_such_machine",
694 Self::NoSuchShunt => "no_such_shunt",
695 Self::NoSuchLoad => "no_such_load",
696 Self::NoSuchTransformer3w => "no_such_transformer_3w",
697 Self::Unrecognized => "unrecognized",
698 Self::NoSuchBusName => "no_such_bus_name",
699 Self::AmbiguousBusName { .. } => "ambiguous_bus_name",
700 Self::NoSuchBranchName => "no_such_branch_name",
701 Self::AmbiguousBranchName { .. } => "ambiguous_branch_name",
702 }
703 }
704}
705
706fn describe(action: &ContingencyAction, reason: UnresolvedReason) -> String {
708 match (action, reason) {
709 (ContingencyAction::OpenBranch { from, to, circuit }, UnresolvedReason::NoSuchBranch) => {
710 format!("no branch {from} to {to} circuit {circuit}")
711 }
712 (
713 ContingencyAction::OpenBranch { from, to, circuit },
714 UnresolvedReason::AmbiguousBranch { matches },
715 ) => format!("branch {from} to {to} circuit {circuit} names {matches} branches"),
716 (
717 ContingencyAction::OpenThreeWinding { buses, circuit },
718 UnresolvedReason::AmbiguousTransformer3w { matches },
719 ) => format!(
720 "three winding transformer on buses {} {} {} circuit {circuit} names {matches} transformers",
721 buses[0], buses[1], buses[2]
722 ),
723 (ContingencyAction::OpenThreeWinding { buses, circuit }, _) => format!(
724 "no three winding transformer on buses {} {} {} circuit {circuit}",
725 buses[0], buses[1], buses[2]
726 ),
727 (
728 ContingencyAction::RemoveMachine { bus, id }
729 | ContingencyAction::AddMachine { bus, id },
730 _,
731 ) => format!("no machine {id} at bus {bus}"),
732 (ContingencyAction::RemoveShunt { bus, id }, _) => match id {
733 Some(id) => format!("no fixed shunt {id} at bus {bus}"),
734 None => format!("no fixed shunt at bus {bus}"),
735 },
736 (ContingencyAction::RemoveSwitchedShunt { bus }, _) => {
737 format!("no switched shunt at bus {bus}")
738 }
739 (ContingencyAction::RemoveLoad { bus, id }, _) => match id {
740 Some(id) => format!("no load {id} at bus {bus}"),
741 None => format!("no load at bus {bus}"),
742 },
743 (ContingencyAction::Unrecognized { text }, UnresolvedReason::NoSuchBusName) => {
744 format!("a bus name names no bus: {text}")
745 }
746 (
747 ContingencyAction::Unrecognized { text },
748 UnresolvedReason::AmbiguousBusName { matches },
749 ) => {
750 format!("a bus name names {matches} buses: {text}")
751 }
752 (ContingencyAction::Unrecognized { text }, UnresolvedReason::NoSuchBranchName) => {
753 format!("the branch name names no branch: {text}")
754 }
755 (
756 ContingencyAction::Unrecognized { text },
757 UnresolvedReason::AmbiguousBranchName { matches },
758 ) => format!("the branch name names {matches} branches: {text}"),
759 (ContingencyAction::Unrecognized { text }, _) => {
760 format!("statement kept as text: {text}")
761 }
762 (
763 ContingencyAction::DisconnectBus { bus }
764 | ContingencyAction::ChangeLoad { bus, .. }
765 | ContingencyAction::ChangeGeneration { bus, .. },
766 _,
767 ) => format!("no bus {bus}"),
768 (ContingencyAction::OpenBranch { from, to, circuit }, _) => {
769 format!("branch {from} to {to} circuit {circuit} did not bind")
770 }
771 }
772}
773
774impl ContingencySet {
775 #[must_use]
786 pub fn resolve(&self, net: &BalancedNetwork) -> ContingencyResolution {
787 self.resolve_with(&PsseEquipmentIndex::new(net))
788 }
789
790 #[must_use]
796 pub fn resolve_with(&self, index: &PsseEquipmentIndex<'_>) -> ContingencyResolution {
797 let mut out = ContingencyResolution::default();
798 for case in &self.cases {
799 let mut resolved = ResolvedCase {
800 name: case.name.clone(),
801 ..ResolvedCase::default()
802 };
803 for action in &case.actions {
804 if matches!(action, ContingencyAction::Unrecognized { .. }) {
805 out.unrecognized_statements += 1;
806 }
807 match bind(index, action) {
808 Ok(components) => resolved.components.extend(components),
809 Err(reason) => resolved.unresolved.push(UnresolvedAction {
810 action: action.clone(),
811 reason,
812 }),
813 }
814 }
815 if resolved.is_resolved() {
816 out.resolved += 1;
817 } else {
818 out.unresolved += 1;
819 }
820 out.cases.push(resolved);
821 }
822 out
823 }
824}
825
826fn bind(
835 index: &PsseEquipmentIndex<'_>,
836 action: &ContingencyAction,
837) -> Result<Vec<ResolvedComponent>, UnresolvedReason> {
838 let net = index.network();
839 match action {
840 ContingencyAction::OpenBranch { from, to, circuit } => {
841 let rows = index.branch_rows(*from, *to, circuit);
842 let (rows, component): (_, fn(&BalancedNetwork, usize) -> ResolvedComponent) =
843 if rows.is_empty() {
844 (index.switch_rows(*from, *to, circuit), switch_component)
845 } else {
846 (rows, branch_component)
847 };
848 match rows.as_slice() {
849 [] => Err(UnresolvedReason::NoSuchBranch),
850 [row] => Ok(vec![component(net, *row)]),
851 many => Err(UnresolvedReason::AmbiguousBranch {
852 matches: many.len(),
853 }),
854 }
855 }
856 ContingencyAction::OpenThreeWinding { buses, circuit } => {
857 let rows = index.transformer_3w_rows(*buses, circuit);
858 match rows.as_slice() {
859 [] => Err(UnresolvedReason::NoSuchTransformer3w),
860 [row] => Ok(vec![transformer_3w_component(net, *row)]),
861 many => Err(UnresolvedReason::AmbiguousTransformer3w {
862 matches: many.len(),
863 }),
864 }
865 }
866 ContingencyAction::RemoveMachine { bus, id }
867 | ContingencyAction::AddMachine { bus, id } => index
868 .machine_row(*bus, id)
869 .map(|row| vec![generator_component(net, row)])
870 .ok_or(UnresolvedReason::NoSuchMachine),
871 ContingencyAction::RemoveShunt { bus, id } => {
872 let rows = index.fixed_shunt_rows(*bus, id.as_deref());
873 non_empty(rows, UnresolvedReason::NoSuchShunt).map(|rows| {
874 rows.into_iter()
875 .map(|row| shunt_component(net, row))
876 .collect()
877 })
878 }
879 ContingencyAction::RemoveSwitchedShunt { bus } => {
880 let rows = index.switched_shunt_rows(*bus);
881 non_empty(rows, UnresolvedReason::NoSuchShunt).map(|rows| {
882 rows.into_iter()
883 .map(|row| shunt_component(net, row))
884 .collect()
885 })
886 }
887 ContingencyAction::RemoveLoad { bus, id } => {
888 let rows = index.load_rows(*bus, id.as_deref());
889 non_empty(rows, UnresolvedReason::NoSuchLoad).map(|rows| {
890 rows.into_iter()
891 .map(|row| load_component(net, row))
892 .collect()
893 })
894 }
895 ContingencyAction::DisconnectBus { bus }
896 | ContingencyAction::ChangeLoad { bus, .. }
897 | ContingencyAction::ChangeGeneration { bus, .. } => index
898 .bus_row(*bus)
899 .map(|row| vec![bus_component(net, row)])
900 .ok_or(UnresolvedReason::NoSuchBus),
901 ContingencyAction::Unrecognized { text } => {
904 match super::tara::read_text_action(&index.tables.names, text) {
905 None => Err(UnresolvedReason::Unrecognized),
906 Some(Err(reason)) => Err(reason),
907 Some(Ok(super::tara::TextAction::Action(action))) => bind(index, &action),
908 Some(Ok(super::tara::TextAction::Branch(row))) => {
909 Ok(vec![branch_component(net, row)])
910 }
911 }
912 }
913 }
914}
915
916fn non_empty(rows: Vec<usize>, reason: UnresolvedReason) -> Result<Vec<usize>, UnresolvedReason> {
917 if rows.is_empty() {
918 Err(reason)
919 } else {
920 Ok(rows)
921 }
922}
923
924fn component_id(component_type: &str, uid: Option<&str>) -> Option<ComponentId> {
931 let local = uid.filter(|uid| !uid.is_empty())?;
932 ComponentId::new(component_type, local).ok()
933}
934
935fn bus_component(net: &BalancedNetwork, row: usize) -> ResolvedComponent {
936 let bus = &net.buses()[row];
937 ResolvedComponent {
938 component_type: BUS,
939 id: component_id(BUS, bus.uid.as_deref()),
940 row,
941 in_service: bus.kind != BusType::Isolated,
942 }
943}
944
945fn load_component(net: &BalancedNetwork, row: usize) -> ResolvedComponent {
946 let load = &net.loads()[row];
947 ResolvedComponent {
948 component_type: LOAD,
949 id: component_id(LOAD, load.uid.as_deref()),
950 row,
951 in_service: load.in_service,
952 }
953}
954
955fn shunt_component(net: &BalancedNetwork, row: usize) -> ResolvedComponent {
956 let shunt = &net.shunts()[row];
957 ResolvedComponent {
958 component_type: SHUNT,
959 id: component_id(SHUNT, shunt.uid.as_deref()),
960 row,
961 in_service: shunt.in_service,
962 }
963}
964
965fn generator_component(net: &BalancedNetwork, row: usize) -> ResolvedComponent {
966 let generator = &net.generators()[row];
967 ResolvedComponent {
968 component_type: GENERATOR,
969 id: component_id(GENERATOR, generator.uid.as_deref()),
970 row,
971 in_service: generator.in_service,
972 }
973}
974
975fn branch_component(net: &BalancedNetwork, row: usize) -> ResolvedComponent {
976 let branch = &net.branches()[row];
977 ResolvedComponent {
978 component_type: BRANCH,
979 id: component_id(BRANCH, branch.uid.as_deref()),
980 row,
981 in_service: branch.in_service,
982 }
983}
984
985fn switch_component(net: &BalancedNetwork, row: usize) -> ResolvedComponent {
986 let switch = &net.switches()[row];
987 ResolvedComponent {
988 component_type: SWITCH,
989 id: component_id(SWITCH, switch.uid.as_deref()),
990 row,
991 in_service: switch.closed,
992 }
993}
994
995fn transformer_3w_component(net: &BalancedNetwork, row: usize) -> ResolvedComponent {
996 let transformer = &net.transformers_3w()[row];
997 ResolvedComponent {
998 component_type: TRANSFORMER_3W,
999 id: component_id(TRANSFORMER_3W, transformer.uid.as_deref()),
1000 row,
1001 in_service: transformer.in_service,
1002 }
1003}