1use serde::{Deserialize, Serialize};
9
10use crate::network::{
11 BalancedNetwork, BranchCurrentRatings, BranchRatingSet, BusId, BusType, GenCaps, GenCost, Hvdc,
12 LoadVoltageModel,
13};
14use crate::normalize::{NormalizeOptions, NormalizeSourceRows};
15use crate::{Error, IndexedNetwork, Result};
16
17pub const NORMALIZED_SOLVER_TABLES_PASS: &str = "balanced-to-normalized-solver-tables";
19
20#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
27#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
28#[non_exhaustive]
29#[doc(hidden)]
33pub struct NormalizedSolverTables {
34 pub pass: String,
35 pub network_name: String,
36 pub base_mva: f64,
37 pub base_frequency: f64,
38 pub units: SolverTableUnits,
39 pub index: SolverTableIndex,
40 pub buses: Vec<SolverBusRow>,
41 pub loads: Vec<SolverLoadRow>,
42 pub shunts: Vec<SolverShuntRow>,
43 pub branches: Vec<SolverBranchRow>,
44 pub switches: Vec<SolverSwitchRow>,
45 pub arcs: Vec<SolverArcRow>,
46 pub generators: Vec<SolverGeneratorRow>,
47 pub storage: Vec<SolverStorageRow>,
48 pub hvdc: Vec<SolverHvdcRow>,
49}
50
51#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
53#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
54#[non_exhaustive]
55pub struct SolverTableUnits {
56 pub power: String,
57 pub voltage: String,
58 pub angle: String,
59 pub impedance: String,
60 pub admittance: String,
61 pub dense_index_base: String,
62}
63
64impl Default for SolverTableUnits {
65 fn default() -> Self {
66 Self {
67 power: "per_unit".to_string(),
68 voltage: "per_unit".to_string(),
69 angle: "radian".to_string(),
70 impedance: "per_unit".to_string(),
71 admittance: "per_unit".to_string(),
72 dense_index_base: "zero".to_string(),
73 }
74 }
75}
76
77#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
79#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
80#[non_exhaustive]
81pub struct SolverTableIndex {
82 pub bus_ids: Vec<BusId>,
85 pub reference_bus_indices: Vec<usize>,
86 pub component_labels: Vec<usize>,
87 pub branch_from_arc_indices: Vec<usize>,
88 pub branch_to_arc_indices: Vec<usize>,
89 pub bus_source_rows: Vec<Option<usize>>,
90 pub load_source_rows: Vec<Option<usize>>,
91 pub shunt_source_rows: Vec<Option<usize>>,
92 pub branch_source_rows: Vec<Option<usize>>,
93 pub switch_source_rows: Vec<Option<usize>>,
94 pub generator_source_rows: Vec<Option<usize>>,
95 pub storage_source_rows: Vec<Option<usize>>,
96 pub hvdc_source_rows: Vec<Option<usize>>,
97}
98
99#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
100#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
101#[non_exhaustive]
102#[doc(hidden)]
106pub struct SolverBusRow {
107 pub index: usize,
108 pub bus_id: BusId,
109 pub source_row: Option<usize>,
110 pub kind: BusType,
111 pub vm: f64,
112 pub va: f64,
113 pub base_kv: f64,
114 pub vmax: f64,
115 pub vmin: f64,
116 pub evhi: Option<f64>,
117 pub evlo: Option<f64>,
118 pub area: usize,
119 pub zone: usize,
120 pub pd: f64,
121 pub qd: f64,
122 pub gs: f64,
123 pub bs: f64,
124}
125
126#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
127#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
128#[non_exhaustive]
129#[doc(hidden)]
133pub struct SolverLoadRow {
134 pub index: usize,
135 pub source_row: Option<usize>,
136 pub bus_index: usize,
137 pub p: f64,
138 pub q: f64,
139 pub voltage_model: Option<LoadVoltageModel>,
140}
141
142#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
143#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
144#[non_exhaustive]
145#[doc(hidden)]
149pub struct SolverShuntRow {
150 pub index: usize,
151 pub source_row: Option<usize>,
152 pub bus_index: usize,
153 pub g: f64,
154 pub b: f64,
155}
156
157#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
158#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
159#[non_exhaustive]
160#[doc(hidden)]
164pub struct SolverBranchRow {
165 pub index: usize,
166 pub source_row: Option<usize>,
167 pub from_bus_index: usize,
168 pub to_bus_index: usize,
169 pub r: f64,
170 pub x: f64,
171 pub b: f64,
172 pub g_fr: f64,
173 pub b_fr: f64,
174 pub g_to: f64,
175 pub b_to: f64,
176 pub rate_a: f64,
177 pub rate_b: f64,
178 pub rate_c: f64,
179 pub rating_sets: Vec<BranchRatingSet>,
180 pub current_ratings: Option<BranchCurrentRatings>,
181 pub tap: f64,
182 pub shift: f64,
183 pub angmin: f64,
184 pub angmax: f64,
185}
186
187#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
188#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
189#[non_exhaustive]
190#[doc(hidden)]
194pub struct SolverSwitchRow {
195 pub index: usize,
196 pub source_row: Option<usize>,
197 pub from_bus_index: usize,
198 pub to_bus_index: usize,
199 pub closed: bool,
200 pub thermal_rating: Option<f64>,
201 pub current_rating: Option<f64>,
202 pub pf: Option<f64>,
203 pub qf: Option<f64>,
204 pub pt: Option<f64>,
205 pub qt: Option<f64>,
206}
207
208#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
209#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
210#[serde(rename_all = "snake_case")]
211pub enum SolverArcTerminal {
212 From,
213 To,
214}
215
216#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
217#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
218#[non_exhaustive]
219#[doc(hidden)]
223pub struct SolverArcRow {
224 pub index: usize,
225 pub branch_index: usize,
226 pub terminal: SolverArcTerminal,
227 pub from_bus_index: usize,
228 pub to_bus_index: usize,
229 pub tap: f64,
230 pub shift: f64,
231 pub g_shunt: f64,
232 pub b_shunt: f64,
233 pub rate_a: f64,
234}
235
236#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
237#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
238#[non_exhaustive]
239#[doc(hidden)]
243pub struct SolverGeneratorRow {
244 pub index: usize,
245 pub source_row: Option<usize>,
246 pub bus_index: usize,
247 pub pg: f64,
248 pub qg: f64,
249 pub pmax: f64,
250 pub pmin: f64,
251 pub qmax: f64,
252 pub qmin: f64,
253 pub vg: f64,
254 pub mbase: f64,
255 pub cost: Option<SolverCostRow>,
256 pub caps: GenCaps,
257 pub regulated_bus_index: Option<usize>,
258}
259
260#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
261#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
262#[non_exhaustive]
263#[doc(hidden)]
267pub struct SolverStorageRow {
268 pub index: usize,
269 pub source_row: Option<usize>,
270 pub bus_index: usize,
271 pub ps: f64,
272 pub qs: f64,
273 pub energy: f64,
274 pub energy_rating: f64,
275 pub charge_rating: f64,
276 pub discharge_rating: f64,
277 pub charge_efficiency: f64,
278 pub discharge_efficiency: f64,
279 pub thermal_rating: f64,
280 pub current_rating: Option<f64>,
281 pub qmin: f64,
282 pub qmax: f64,
283 pub r: f64,
284 pub x: f64,
285 pub p_loss: f64,
286 pub q_loss: f64,
287}
288
289#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
290#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
291#[non_exhaustive]
292#[doc(hidden)]
296pub struct SolverHvdcRow {
297 pub index: usize,
298 pub source_row: Option<usize>,
299 pub from_bus_index: usize,
300 pub to_bus_index: usize,
301 pub pf: f64,
302 pub pt: f64,
303 pub qf: f64,
304 pub qt: f64,
305 pub vf: f64,
306 pub vt: f64,
307 pub pmin: f64,
308 pub pmax: f64,
309 pub qminf: f64,
310 pub qmaxf: f64,
311 pub qmint: f64,
312 pub qmaxt: f64,
313 pub loss0: f64,
314 pub loss1: f64,
315 pub cost: Option<SolverCostRow>,
316}
317
318#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
319#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
320#[non_exhaustive]
321#[doc(hidden)]
325pub struct SolverCostRow {
326 pub model: u8,
327 pub startup: f64,
328 pub shutdown: f64,
329 pub ncost: usize,
330 pub coeffs: Vec<f64>,
331}
332
333impl From<&GenCost> for SolverCostRow {
334 fn from(cost: &GenCost) -> Self {
335 Self {
336 model: cost.model,
337 startup: cost.startup,
338 shutdown: cost.shutdown,
339 ncost: cost.ncost,
340 coeffs: cost.coeffs.clone(),
341 }
342 }
343}
344
345impl BalancedNetwork {
346 pub fn to_normalized_solver_tables(&self) -> Result<NormalizedSolverTables> {
353 NormalizedSolverTables::from_network(self)
354 }
355}
356
357impl NormalizedSolverTables {
358 pub fn from_network(source: &BalancedNetwork) -> Result<Self> {
359 let (normalized, provenance) = normalized_for_solver(source)?;
360 let view = IndexedNetwork::new(&normalized);
361 let net = view.network();
362
363 let branch_arcs = branch_and_arc_rows(&view, &provenance)?;
364 let buses = bus_rows(&view, &provenance);
365 let loads = load_rows(&view, &provenance)?;
366 let shunts = shunt_rows(&view, &provenance)?;
367 let switches = switch_rows(&view, &provenance)?;
368 let generators = generator_rows(&view, &provenance)?;
369 let storage = storage_rows(&view, &provenance)?;
370 let hvdc = hvdc_rows(&view, &provenance)?;
371
372 Ok(Self {
373 pass: NORMALIZED_SOLVER_TABLES_PASS.to_string(),
374 network_name: net.name().clone(),
375 base_mva: net.base_mva(),
376 base_frequency: net.base_frequency(),
377 units: SolverTableUnits::default(),
378 index: SolverTableIndex {
379 bus_ids: net.buses().iter().map(|b| b.id).collect(),
380 reference_bus_indices: view.reference_bus_indices(),
381 component_labels: view.connected_component_labels(),
382 branch_from_arc_indices: branch_arcs.branch_from_arc_indices,
383 branch_to_arc_indices: branch_arcs.branch_to_arc_indices,
384 bus_source_rows: provenance.buses,
385 load_source_rows: provenance.loads,
386 shunt_source_rows: provenance.shunts,
387 branch_source_rows: provenance.branches,
388 switch_source_rows: provenance.switches,
389 generator_source_rows: provenance.generators,
390 storage_source_rows: provenance.storage,
391 hvdc_source_rows: provenance.hvdc,
392 },
393 buses,
394 loads,
395 shunts,
396 branches: branch_arcs.branches,
397 switches,
398 arcs: branch_arcs.arcs,
399 generators,
400 storage,
401 hvdc,
402 })
403 }
404}
405
406fn normalized_for_solver(
410 source: &BalancedNetwork,
411) -> Result<(BalancedNetwork, NormalizeSourceRows)> {
412 if source.is_normalized() {
413 let net = source.clone();
414 let mut rows = NormalizeSourceRows::identity(&net);
415 rows.pad_to_lowered(&net);
416 Ok((net, rows))
417 } else {
418 let (normalized, rows) =
419 source.to_normalized_with_source_rows(&NormalizeOptions::default())?;
420 Ok((normalized.network, rows))
421 }
422}
423
424fn bus_rows(view: &IndexedNetwork<'_>, provenance: &NormalizeSourceRows) -> Vec<SolverBusRow> {
425 view.network()
426 .buses()
427 .iter()
428 .enumerate()
429 .map(|(i, bus)| SolverBusRow {
430 index: i,
431 bus_id: bus.id,
432 source_row: provenance.buses[i],
433 kind: bus.kind,
434 vm: bus.vm,
435 va: bus.va,
436 base_kv: bus.base_kv,
437 vmax: bus.vmax,
438 vmin: bus.vmin,
439 evhi: bus.evhi,
440 evlo: bus.evlo,
441 area: bus.area,
442 zone: bus.zone,
443 pd: view.pd()[i],
444 qd: view.qd()[i],
445 gs: view.gs()[i],
446 bs: view.bs()[i],
447 })
448 .collect()
449}
450
451fn load_rows(
452 view: &IndexedNetwork<'_>,
453 provenance: &NormalizeSourceRows,
454) -> Result<Vec<SolverLoadRow>> {
455 view.network()
456 .loads()
457 .iter()
458 .enumerate()
459 .map(|(i, load)| {
460 Ok(SolverLoadRow {
461 index: i,
462 source_row: provenance.loads[i],
463 bus_index: dense_bus(view, load.bus, i)?,
464 p: load.p,
465 q: load.q,
466 voltage_model: load.voltage_model.clone(),
467 })
468 })
469 .collect()
470}
471
472fn shunt_rows(
473 view: &IndexedNetwork<'_>,
474 provenance: &NormalizeSourceRows,
475) -> Result<Vec<SolverShuntRow>> {
476 view.network()
477 .shunts()
478 .iter()
479 .enumerate()
480 .map(|(i, shunt)| {
481 Ok(SolverShuntRow {
482 index: i,
483 source_row: provenance.shunts[i],
484 bus_index: dense_bus(view, shunt.bus, i)?,
485 g: shunt.g,
486 b: shunt.b,
487 })
488 })
489 .collect()
490}
491
492struct BranchArcRows {
493 branches: Vec<SolverBranchRow>,
494 arcs: Vec<SolverArcRow>,
495 branch_from_arc_indices: Vec<usize>,
496 branch_to_arc_indices: Vec<usize>,
497}
498
499fn branch_and_arc_rows(
500 view: &IndexedNetwork<'_>,
501 provenance: &NormalizeSourceRows,
502) -> Result<BranchArcRows> {
503 let net = view.network();
504 let mut branch_from_arc_indices = Vec::with_capacity(net.branches().len());
505 let mut branch_to_arc_indices = Vec::with_capacity(net.branches().len());
506 let mut arcs = Vec::with_capacity(net.branches().len() * 2);
507 let branches = net
508 .branches()
509 .iter()
510 .enumerate()
511 .map(|(i, branch)| {
512 let from_bus_index = dense_bus(view, branch.from, i)?;
513 let to_bus_index = dense_bus(view, branch.to, i)?;
514 let charging = branch.terminal_charging();
515 let from_arc = arcs.len();
516 arcs.push(SolverArcRow {
517 index: from_arc,
518 branch_index: i,
519 terminal: SolverArcTerminal::From,
520 from_bus_index,
521 to_bus_index,
522 tap: branch.tap,
523 shift: branch.shift,
524 g_shunt: charging.g_fr,
525 b_shunt: charging.b_fr,
526 rate_a: branch.rate_a,
527 });
528 let to_arc = arcs.len();
529 arcs.push(SolverArcRow {
530 index: to_arc,
531 branch_index: i,
532 terminal: SolverArcTerminal::To,
533 from_bus_index: to_bus_index,
534 to_bus_index: from_bus_index,
535 tap: 1.0,
536 shift: 0.0,
537 g_shunt: charging.g_to,
538 b_shunt: charging.b_to,
539 rate_a: branch.rate_a,
540 });
541 branch_from_arc_indices.push(from_arc);
542 branch_to_arc_indices.push(to_arc);
543
544 Ok(SolverBranchRow {
545 index: i,
546 source_row: provenance.branches[i],
547 from_bus_index,
548 to_bus_index,
549 r: branch.r,
550 x: branch.x,
551 b: branch.b,
552 g_fr: charging.g_fr,
553 b_fr: charging.b_fr,
554 g_to: charging.g_to,
555 b_to: charging.b_to,
556 rate_a: branch.rate_a,
557 rate_b: branch.rate_b,
558 rate_c: branch.rate_c,
559 rating_sets: branch.rating_sets.clone(),
560 current_ratings: branch.current_ratings,
561 tap: branch.tap,
562 shift: branch.shift,
563 angmin: branch.angmin,
564 angmax: branch.angmax,
565 })
566 })
567 .collect::<Result<Vec<_>>>()?;
568
569 Ok(BranchArcRows {
570 branches,
571 arcs,
572 branch_from_arc_indices,
573 branch_to_arc_indices,
574 })
575}
576
577fn switch_rows(
578 view: &IndexedNetwork<'_>,
579 provenance: &NormalizeSourceRows,
580) -> Result<Vec<SolverSwitchRow>> {
581 view.network()
582 .switches()
583 .iter()
584 .enumerate()
585 .map(|(i, switch)| {
586 Ok(SolverSwitchRow {
587 index: i,
588 source_row: provenance.switches[i],
589 from_bus_index: dense_bus(view, switch.from, i)?,
590 to_bus_index: dense_bus(view, switch.to, i)?,
591 closed: switch.closed,
592 thermal_rating: switch.thermal_rating,
593 current_rating: switch.current_rating,
594 pf: switch.pf,
595 qf: switch.qf,
596 pt: switch.pt,
597 qt: switch.qt,
598 })
599 })
600 .collect()
601}
602
603fn generator_rows(
604 view: &IndexedNetwork<'_>,
605 provenance: &NormalizeSourceRows,
606) -> Result<Vec<SolverGeneratorRow>> {
607 view.network()
608 .generators()
609 .iter()
610 .enumerate()
611 .map(|(i, generator)| {
612 Ok(SolverGeneratorRow {
613 index: i,
614 source_row: provenance.generators[i],
615 bus_index: dense_bus(view, generator.bus, i)?,
616 pg: generator.pg,
617 qg: generator.qg,
618 pmax: generator.pmax,
619 pmin: generator.pmin,
620 qmax: generator.qmax,
621 qmin: generator.qmin,
622 vg: generator.vg,
623 mbase: generator.mbase,
624 cost: generator.cost.as_ref().map(SolverCostRow::from),
625 caps: generator.caps,
626 regulated_bus_index: generator
627 .regulated_bus
628 .map(|bus| dense_bus(view, bus, i))
629 .transpose()?,
630 })
631 })
632 .collect()
633}
634
635fn storage_rows(
636 view: &IndexedNetwork<'_>,
637 provenance: &NormalizeSourceRows,
638) -> Result<Vec<SolverStorageRow>> {
639 let base_mva = view.network().base_mva();
640 view.network()
641 .storage()
642 .iter()
643 .enumerate()
644 .map(|(i, storage)| {
645 Ok(SolverStorageRow {
646 index: i,
647 source_row: provenance.storage[i],
648 bus_index: dense_bus(view, storage.bus, i)?,
649 ps: storage.ps / base_mva,
650 qs: storage.qs / base_mva,
651 energy: storage.energy,
652 energy_rating: storage.energy_rating,
653 charge_rating: storage.charge_rating,
654 discharge_rating: storage.discharge_rating,
655 charge_efficiency: storage.charge_efficiency,
656 discharge_efficiency: storage.discharge_efficiency,
657 thermal_rating: storage.thermal_rating,
658 current_rating: storage.current_rating,
659 qmin: storage.qmin,
660 qmax: storage.qmax,
661 r: storage.r,
662 x: storage.x,
663 p_loss: storage.p_loss,
664 q_loss: storage.q_loss,
665 })
666 })
667 .collect()
668}
669
670fn hvdc_rows(
671 view: &IndexedNetwork<'_>,
672 provenance: &NormalizeSourceRows,
673) -> Result<Vec<SolverHvdcRow>> {
674 view.network()
675 .hvdc()
676 .iter()
677 .enumerate()
678 .map(|(i, hvdc)| hvdc_row(view, provenance, i, hvdc))
679 .collect()
680}
681
682fn hvdc_row(
683 view: &IndexedNetwork<'_>,
684 provenance: &NormalizeSourceRows,
685 i: usize,
686 hvdc: &Hvdc,
687) -> Result<SolverHvdcRow> {
688 let base_mva = view.network().base_mva();
689 Ok(SolverHvdcRow {
690 index: i,
691 source_row: provenance.hvdc[i],
692 from_bus_index: dense_bus(view, hvdc.from, i)?,
693 to_bus_index: dense_bus(view, hvdc.to, i)?,
694 pf: hvdc.pf,
695 pt: hvdc.pt,
696 qf: hvdc.qf,
697 qt: hvdc.qt,
698 vf: hvdc.vf,
699 vt: hvdc.vt,
700 pmin: hvdc.pmin / base_mva,
701 pmax: hvdc.pmax / base_mva,
702 qminf: hvdc.qminf,
703 qmaxf: hvdc.qmaxf,
704 qmint: hvdc.qmint,
705 qmaxt: hvdc.qmaxt,
706 loss0: hvdc.loss0,
707 loss1: hvdc.loss1,
708 cost: hvdc.cost.as_ref().map(SolverCostRow::from),
709 })
710}
711
712fn dense_bus(view: &IndexedNetwork<'_>, bus_id: BusId, element_index: usize) -> Result<usize> {
713 view.bus_index(bus_id).ok_or(Error::UnknownBus {
714 bus_id,
715 element_index,
716 })
717}
718
719#[cfg(test)]
720mod tests {
721 use super::*;
722 use crate::network::{Branch, Bus, Extras, Generator, Hvdc, Load, SourceFormat, Storage};
723 use crate::parse_file;
724
725 fn approx(a: f64, b: f64) -> bool {
726 (a - b).abs() < 1e-12
727 }
728
729 fn bus(id: usize, kind: BusType) -> Bus {
730 Bus {
731 id: BusId(id),
732 kind,
733 vm: 1.0,
734 va: 0.0,
735 base_kv: 230.0,
736 vmax: 1.1,
737 vmin: 0.9,
738 evhi: None,
739 evlo: None,
740 area: 1,
741 zone: 1,
742 name: None,
743 uid: None,
744 location: None,
745 extras: Extras::new(),
746 }
747 }
748
749 fn branch(from: usize, to: usize, in_service: bool) -> Branch {
750 Branch {
751 from: BusId(from),
752 to: BusId(to),
753 r: 0.01,
754 x: 0.1,
755 b: 0.02,
756 charging: None,
757 rate_a: 100.0,
758 rate_b: 110.0,
759 rate_c: 120.0,
760 rating_sets: Vec::new(),
761 current_ratings: None,
762 tap: 0.0,
763 shift: 30.0,
764 in_service,
765 angmin: -360.0,
766 angmax: 360.0,
767 control: None,
768 solution: None,
769 uid: None,
770 route: None,
771 extras: Extras::new(),
772 }
773 }
774
775 fn generator(bus: usize, in_service: bool) -> Generator {
776 Generator {
777 bus: BusId(bus),
778 pg: 50.0,
779 qg: 5.0,
780 pmax: 80.0,
781 pmin: 0.0,
782 qmax: 40.0,
783 qmin: -40.0,
784 vg: 1.0,
785 mbase: 100.0,
786 in_service,
787 cost: None,
788 caps: [None; crate::network::GEN_EXTRA_KEYS.len()],
789 regulated_bus: None,
790 uid: None,
791 }
792 }
793
794 #[test]
795 fn solver_tables_are_dense_normalized_and_traceable() {
796 let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../tests/data/case14.m");
797 let net = parse_file(path, None).unwrap().network;
798
799 let tables = net.to_normalized_solver_tables().unwrap();
800
801 assert_eq!(tables.pass, NORMALIZED_SOLVER_TABLES_PASS);
802 assert_eq!(tables.units.power, "per_unit");
803 assert_eq!(tables.units.angle, "radian");
804 assert_eq!(tables.buses.len(), 14);
805 assert_eq!(tables.branches.len(), 20);
806 assert_eq!(tables.arcs.len(), 40);
807 assert_eq!(tables.index.reference_bus_indices, vec![0]);
808 assert_eq!(tables.index.branch_from_arc_indices[0], 0);
809 assert_eq!(tables.index.branch_to_arc_indices[0], 1);
810 assert_eq!(tables.arcs[0].terminal, SolverArcTerminal::From);
811 assert_eq!(tables.arcs[1].terminal, SolverArcTerminal::To);
812 assert!(tables.index.bus_source_rows.iter().all(Option::is_some));
813 assert!(tables.index.branch_source_rows.iter().all(Option::is_some));
814
815 let bus_2 = &tables.buses[1];
816 assert_eq!(bus_2.bus_id, BusId(2));
817 assert!(approx(bus_2.pd, 21.7 / 100.0));
818 assert!(approx(bus_2.qd, 12.7 / 100.0));
819 }
820
821 #[test]
822 fn solver_tables_filter_out_of_service_rows_and_keep_source_rows() {
823 let mut net = BalancedNetwork::in_memory(
824 "filtered",
825 100.0,
826 vec![
827 bus(1, BusType::Ref),
828 bus(2, BusType::Pq),
829 bus(3, BusType::Isolated),
830 ],
831 vec![branch(1, 2, true), branch(1, 3, true), branch(1, 2, false)],
832 );
833 net.loads_mut().push(Load {
834 bus: BusId(2),
835 p: 10.0,
836 q: 5.0,
837 voltage_model: None,
838 in_service: true,
839 uid: None,
840 extras: Extras::new(),
841 });
842 net.loads_mut().push(Load {
843 bus: BusId(3),
844 p: 99.0,
845 q: 99.0,
846 voltage_model: None,
847 in_service: true,
848 uid: None,
849 extras: Extras::new(),
850 });
851 net.generators_mut().push(generator(1, true));
852 net.generators_mut().push(generator(2, false));
853 *net.source_format_mut() = SourceFormat::Matpower;
854 let tables = net.to_normalized_solver_tables().unwrap();
855
856 assert_eq!(tables.index.bus_ids, vec![BusId(1), BusId(2)]);
857 assert_eq!(tables.branches.len(), 1);
858 assert_eq!(tables.loads.len(), 1);
859 assert_eq!(tables.generators.len(), 1);
860 assert_eq!(tables.index.branch_source_rows, vec![Some(0)]);
861 assert_eq!(tables.index.load_source_rows, vec![Some(0)]);
862 assert_eq!(tables.index.generator_source_rows, vec![Some(0)]);
863 assert!(approx(tables.loads[0].p, 0.1));
864 assert!(approx(tables.branches[0].rate_a, 1.0));
865 assert!(approx(tables.branches[0].tap, 1.0));
866 assert!(approx(tables.branches[0].shift, 30.0_f64.to_radians()));
867 }
868
869 #[test]
870 fn solver_tables_map_an_already_normalized_network_to_its_own_rows() {
871 let mut net = BalancedNetwork::in_memory(
877 "identity",
878 100.0,
879 vec![
880 bus(1, BusType::Ref),
881 bus(2, BusType::Pq),
882 bus(3, BusType::Pq),
883 bus(4, BusType::Isolated),
884 ],
885 vec![branch(1, 2, true), branch(1, 3, false)],
886 );
887 net.loads_mut().push(Load {
888 bus: BusId(2),
889 p: 0.1,
890 q: 0.05,
891 voltage_model: None,
892 in_service: false,
893 uid: None,
894 extras: Extras::new(),
895 });
896 net.loads_mut().push(Load {
897 bus: BusId(3),
898 p: 0.2,
899 q: 0.1,
900 voltage_model: None,
901 in_service: true,
902 uid: None,
903 extras: Extras::new(),
904 });
905 net.generators_mut().push(generator(1, false));
906 net.generators_mut().push(generator(2, true));
907 *net.source_format_mut() = SourceFormat::Normalized;
908 let tables = net.to_normalized_solver_tables().unwrap();
909
910 assert_eq!(
911 tables.index.bus_source_rows,
912 vec![Some(0), Some(1), Some(2), Some(3)]
913 );
914 assert_eq!(tables.index.branch_source_rows, vec![Some(0), Some(1)]);
915 assert_eq!(tables.index.load_source_rows, vec![Some(0), Some(1)]);
916 assert_eq!(tables.index.generator_source_rows, vec![Some(0), Some(1)]);
917 }
918
919 #[test]
920 fn solver_tables_do_not_scale_an_already_normalized_network_twice() {
921 let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../tests/data/case14.m");
922 let net = parse_file(path, None).unwrap().network;
923 let normalized = net.to_normalized().unwrap();
924
925 let tables = normalized.to_normalized_solver_tables().unwrap();
926
927 let bus_2 = &tables.buses[1];
928 assert!(approx(bus_2.pd, 21.7 / 100.0));
929 assert!(approx(bus_2.qd, 12.7 / 100.0));
930 }
931
932 #[test]
933 fn solver_tables_scale_storage_and_hvdc_power_fields_to_per_unit() {
934 let mut net = BalancedNetwork::in_memory(
935 "storage-hvdc",
936 100.0,
937 vec![bus(1, BusType::Ref), bus(2, BusType::Pq)],
938 Vec::new(),
939 );
940 net.generators_mut().push(generator(1, true));
941 net.storage_mut().push(Storage {
942 bus: BusId(2),
943 ps: 30.0,
944 qs: -10.0,
945 energy: 50.0,
946 energy_rating: 100.0,
947 charge_rating: 20.0,
948 discharge_rating: 25.0,
949 charge_efficiency: 0.9,
950 discharge_efficiency: 0.85,
951 thermal_rating: 40.0,
952 current_rating: None,
953 qmin: -15.0,
954 qmax: 15.0,
955 r: 0.01,
956 x: 0.02,
957 p_loss: 2.0,
958 q_loss: 1.0,
959 in_service: true,
960 uid: None,
961 extras: Extras::new(),
962 });
963 net.hvdc_mut().push(Hvdc {
964 from: BusId(1),
965 to: BusId(2),
966 in_service: true,
967 pf: 20.0,
968 pt: -19.0,
969 qf: 5.0,
970 qt: -4.0,
971 vf: 1.0,
972 vt: 1.0,
973 pmin: -40.0,
974 pmax: 75.0,
975 qminf: -25.0,
976 qmaxf: 30.0,
977 qmint: -20.0,
978 qmaxt: 22.0,
979 loss0: 1.5,
980 loss1: 0.02,
981 cost: None,
982 uid: None,
983 extras: Extras::new(),
984 });
985
986 let tables = net.to_normalized_solver_tables().unwrap();
987
988 let storage = &tables.storage[0];
989 assert!(approx(storage.ps, 0.3));
990 assert!(approx(storage.qs, -0.1));
991 assert!(approx(storage.energy, 0.5));
992 assert!(approx(storage.thermal_rating, 0.4));
993 assert!(approx(storage.p_loss, 0.02));
994
995 let hvdc = &tables.hvdc[0];
996 assert!(approx(hvdc.pf, 0.2));
997 assert!(approx(hvdc.pt, -0.19));
998 assert!(approx(hvdc.pmin, -0.4));
999 assert!(approx(hvdc.pmax, 0.75));
1000 assert!(approx(hvdc.qminf, -0.25));
1001 assert!(approx(hvdc.loss0, 0.015));
1002 }
1003}