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