1use std::collections::HashMap;
11
12use thiserror::Error;
13
14use surge_network::Network;
15
16use crate::OpfSolution;
17
18#[derive(Debug, Error, PartialEq, Eq)]
20pub enum ApplySolutionError {
21 #[error(
22 "generator dispatch length mismatch: expected {expected} in-service generators, got {actual}"
23 )]
24 GeneratorDispatchLengthMismatch { expected: usize, actual: usize },
25 #[error(
26 "generator reactive dispatch length mismatch: expected {expected} in-service generators, got {actual}"
27 )]
28 GeneratorReactiveDispatchLengthMismatch { expected: usize, actual: usize },
29 #[error(
30 "generator bus mapping length mismatch: expected {expected} in-service generators, got {actual}"
31 )]
32 GeneratorBusMappingLengthMismatch { expected: usize, actual: usize },
33 #[error(
34 "generator machine-id mapping length mismatch: expected {expected} in-service generators, got {actual}"
35 )]
36 GeneratorMachineIdMappingLengthMismatch { expected: usize, actual: usize },
37 #[error(
38 "generator ID mapping length mismatch: expected {expected} in-service generators, got {actual}"
39 )]
40 GeneratorIdMappingLengthMismatch { expected: usize, actual: usize },
41 #[error(
42 "generator identity mismatch at OPF position {position}: expected ({expected_bus}, {expected_machine_id}), got ({actual_bus}, {actual_machine_id})"
43 )]
44 GeneratorIdentityMismatch {
45 position: usize,
46 expected_bus: u32,
47 expected_machine_id: String,
48 actual_bus: u32,
49 actual_machine_id: String,
50 },
51 #[error(
52 "ambiguous generator identity ({bus}, {machine_id}) in OPF replay; generator identities must be unique among in-service units"
53 )]
54 AmbiguousGeneratorIdentity { bus: u32, machine_id: String },
55 #[error(
56 "duplicate generator ID {generator_id} in OPF replay mapping; solution generator IDs must be unique"
57 )]
58 DuplicateGeneratorIdInSolution { generator_id: String },
59 #[error(
60 "generator ID mismatch at OPF position {position}: expected {expected_id}, got {actual_id}"
61 )]
62 GeneratorIdMismatch {
63 position: usize,
64 expected_id: String,
65 actual_id: String,
66 },
67 #[error(
68 "ambiguous generator ID {generator_id} in target network; generator IDs must be unique"
69 )]
70 AmbiguousGeneratorIdInNetwork { generator_id: String },
71 #[error("bus voltage magnitude length mismatch: expected {expected} buses, got {actual}")]
72 VoltageMagnitudeLengthMismatch { expected: usize, actual: usize },
73 #[error("bus voltage angle length mismatch: expected {expected} buses, got {actual}")]
74 VoltageAngleLengthMismatch { expected: usize, actual: usize },
75 #[error(
76 "bus voltage bus-number mapping length mismatch: expected {expected} buses, got {actual}"
77 )]
78 VoltageBusNumberLengthMismatch { expected: usize, actual: usize },
79 #[error("bus number {bus_number} from the solution was not found in the target network")]
80 VoltageBusNumberNotFound { bus_number: u32 },
81 #[error("duplicate bus number {bus_number} in target network")]
82 DuplicateBusNumberInNetwork { bus_number: u32 },
83 #[error("duplicate bus number {bus_number} in solution bus-voltage mapping")]
84 DuplicateBusNumberInSolution { bus_number: u32 },
85}
86
87fn in_service_generator_count(network: &Network) -> usize {
88 network.generators.iter().filter(|g| g.in_service).count()
89}
90
91fn normalized_machine_id(machine_id: Option<&str>) -> &str {
92 machine_id.unwrap_or("1")
93}
94
95fn canonical_in_service_generator_id_map(
96 network: &Network,
97) -> Result<HashMap<String, usize>, ApplySolutionError> {
98 let mut canonical_network = Network::new("opf_replay_ids");
99 let mut source_indices = Vec::new();
100
101 for (idx, generator) in network.generators.iter().enumerate() {
102 if !generator.in_service {
103 continue;
104 }
105 canonical_network.generators.push(generator.clone());
106 source_indices.push(idx);
107 }
108 canonical_network.canonicalize_generator_ids();
109
110 let mut id_map = HashMap::new();
111 for (canonical_generator, &source_idx) in canonical_network
112 .generators
113 .iter()
114 .zip(source_indices.iter())
115 {
116 if id_map
117 .insert(canonical_generator.id.clone(), source_idx)
118 .is_some()
119 {
120 return Err(ApplySolutionError::AmbiguousGeneratorIdInNetwork {
121 generator_id: canonical_generator.id.clone(),
122 });
123 }
124 }
125 Ok(id_map)
126}
127
128pub fn apply_opf_dispatch(
136 network: &mut Network,
137 sol: &OpfSolution,
138) -> Result<(), ApplySolutionError> {
139 let expected_generators = in_service_generator_count(network);
140 if sol.generators.gen_p_mw.len() != expected_generators {
141 return Err(ApplySolutionError::GeneratorDispatchLengthMismatch {
142 expected: expected_generators,
143 actual: sol.generators.gen_p_mw.len(),
144 });
145 }
146 if !sol.generators.gen_q_mvar.is_empty()
147 && sol.generators.gen_q_mvar.len() != expected_generators
148 {
149 return Err(
150 ApplySolutionError::GeneratorReactiveDispatchLengthMismatch {
151 expected: expected_generators,
152 actual: sol.generators.gen_q_mvar.len(),
153 },
154 );
155 }
156 if !sol.generators.gen_bus_numbers.is_empty()
157 && sol.generators.gen_bus_numbers.len() != expected_generators
158 {
159 return Err(ApplySolutionError::GeneratorBusMappingLengthMismatch {
160 expected: expected_generators,
161 actual: sol.generators.gen_bus_numbers.len(),
162 });
163 }
164 if !sol.generators.gen_machine_ids.is_empty()
165 && sol.generators.gen_machine_ids.len() != expected_generators
166 {
167 return Err(
168 ApplySolutionError::GeneratorMachineIdMappingLengthMismatch {
169 expected: expected_generators,
170 actual: sol.generators.gen_machine_ids.len(),
171 },
172 );
173 }
174 if !sol.generators.gen_ids.is_empty() && sol.generators.gen_ids.len() != expected_generators {
175 return Err(ApplySolutionError::GeneratorIdMappingLengthMismatch {
176 expected: expected_generators,
177 actual: sol.generators.gen_ids.len(),
178 });
179 }
180
181 let mut generator_dispatch_applied = false;
182 if !sol.generators.gen_ids.is_empty() {
183 let network_indices = canonical_in_service_generator_id_map(network)?;
184 let mut seen_solution_ids = HashMap::new();
185 let mut pending_updates = Vec::with_capacity(sol.generators.gen_ids.len());
186 for (j, generator_id) in sol.generators.gen_ids.iter().enumerate() {
187 if seen_solution_ids.insert(generator_id.clone(), j).is_some() {
188 return Err(ApplySolutionError::DuplicateGeneratorIdInSolution {
189 generator_id: generator_id.clone(),
190 });
191 }
192 let Some(&generator_index) = network_indices.get(generator_id) else {
193 if !sol.generators.gen_bus_numbers.is_empty()
194 && !sol.generators.gen_machine_ids.is_empty()
195 {
196 pending_updates.clear();
197 break;
198 }
199 let actual_id = network
200 .generators
201 .iter()
202 .filter(|g| g.in_service)
203 .nth(j)
204 .map(|g| g.id.clone())
205 .unwrap_or_else(|| "<missing>".to_string());
206 return Err(ApplySolutionError::GeneratorIdMismatch {
207 position: j,
208 expected_id: generator_id.clone(),
209 actual_id,
210 });
211 };
212 pending_updates.push((generator_index, j));
213 }
214
215 if !pending_updates.is_empty() {
216 for (generator_index, j) in pending_updates {
217 let generator = &mut network.generators[generator_index];
218 generator.p = sol.generators.gen_p_mw[j];
219 if !sol.generators.gen_q_mvar.is_empty() {
220 generator.q = sol.generators.gen_q_mvar[j];
221 }
222 }
223 generator_dispatch_applied = true;
224 }
225 }
226
227 if !generator_dispatch_applied && !sol.generators.gen_bus_numbers.is_empty() {
228 if sol.generators.gen_machine_ids.len() != expected_generators {
229 return Err(
230 ApplySolutionError::GeneratorMachineIdMappingLengthMismatch {
231 expected: expected_generators,
232 actual: sol.generators.gen_machine_ids.len(),
233 },
234 );
235 }
236
237 let mut generator_indices = HashMap::new();
238 for (idx, generator) in network.generators.iter().enumerate() {
239 if !generator.in_service {
240 continue;
241 }
242 let key = (
243 generator.bus,
244 normalized_machine_id(generator.machine_id.as_deref()).to_string(),
245 );
246 if generator_indices.insert(key.clone(), idx).is_some() {
247 return Err(ApplySolutionError::AmbiguousGeneratorIdentity {
248 bus: key.0,
249 machine_id: key.1,
250 });
251 }
252 }
253
254 for (j, (&expected_bus, expected_machine_id)) in sol
255 .generators
256 .gen_bus_numbers
257 .iter()
258 .zip(sol.generators.gen_machine_ids.iter())
259 .enumerate()
260 {
261 let Some(&generator_index) =
262 generator_indices.get(&(expected_bus, expected_machine_id.clone()))
263 else {
264 let actual = network
265 .generators
266 .iter()
267 .filter(|g| g.in_service)
268 .nth(j)
269 .map(|g| {
270 (
271 g.bus,
272 normalized_machine_id(g.machine_id.as_deref()).to_string(),
273 )
274 })
275 .unwrap_or((0, "<missing>".to_string()));
276 return Err(ApplySolutionError::GeneratorIdentityMismatch {
277 position: j,
278 expected_bus,
279 expected_machine_id: expected_machine_id.clone(),
280 actual_bus: actual.0,
281 actual_machine_id: actual.1,
282 });
283 };
284
285 let generator = &mut network.generators[generator_index];
286 generator.p = sol.generators.gen_p_mw[j];
287 if !sol.generators.gen_q_mvar.is_empty() {
288 generator.q = sol.generators.gen_q_mvar[j];
289 }
290 }
291 generator_dispatch_applied = true;
292 }
293
294 if !generator_dispatch_applied {
295 let mut j = 0usize;
296 for g in network.generators.iter_mut() {
297 if !g.in_service {
298 continue;
299 }
300 g.p = sol.generators.gen_p_mw[j];
301 if !sol.generators.gen_q_mvar.is_empty() {
302 g.q = sol.generators.gen_q_mvar[j];
303 }
304 j += 1;
305 }
306 }
307
308 if !sol.power_flow.voltage_magnitude_pu.is_empty()
309 || !sol.power_flow.voltage_angle_rad.is_empty()
310 {
311 apply_bus_voltages_by_bus_number(
312 network,
313 &sol.power_flow.bus_numbers,
314 &sol.power_flow.voltage_magnitude_pu,
315 &sol.power_flow.voltage_angle_rad,
316 )?;
317 }
318
319 Ok(())
320}
321
322pub fn apply_dispatch_mw(
333 network: &mut Network,
334 gen_p_mw: &[f64],
335) -> Result<(), ApplySolutionError> {
336 let expected_generators = in_service_generator_count(network);
337 if gen_p_mw.len() != expected_generators {
338 return Err(ApplySolutionError::GeneratorDispatchLengthMismatch {
339 expected: expected_generators,
340 actual: gen_p_mw.len(),
341 });
342 }
343
344 let mut j = 0usize;
345 for g in network.generators.iter_mut() {
346 if !g.in_service {
347 continue;
348 }
349 g.p = gen_p_mw[j];
350 j += 1;
351 }
352
353 Ok(())
354}
355
356pub fn apply_bus_voltages_by_bus_number(
362 network: &mut Network,
363 bus_numbers: &[u32],
364 vm: &[f64],
365 va: &[f64],
366) -> Result<(), ApplySolutionError> {
367 if bus_numbers.len() != network.buses.len() {
368 return Err(ApplySolutionError::VoltageBusNumberLengthMismatch {
369 expected: network.buses.len(),
370 actual: bus_numbers.len(),
371 });
372 }
373 if vm.len() != network.buses.len() {
374 return Err(ApplySolutionError::VoltageMagnitudeLengthMismatch {
375 expected: network.buses.len(),
376 actual: vm.len(),
377 });
378 }
379 if va.len() != network.buses.len() {
380 return Err(ApplySolutionError::VoltageAngleLengthMismatch {
381 expected: network.buses.len(),
382 actual: va.len(),
383 });
384 }
385
386 let mut bus_indices = HashMap::new();
387 for (idx, bus) in network.buses.iter().enumerate() {
388 if bus_indices.insert(bus.number, idx).is_some() {
389 return Err(ApplySolutionError::DuplicateBusNumberInNetwork {
390 bus_number: bus.number,
391 });
392 }
393 }
394
395 let mut seen_solution_bus_numbers = HashMap::new();
396 for (i, &bus_number) in bus_numbers.iter().enumerate() {
397 if seen_solution_bus_numbers.insert(bus_number, i).is_some() {
398 return Err(ApplySolutionError::DuplicateBusNumberInSolution { bus_number });
399 }
400 let Some(&bus_idx) = bus_indices.get(&bus_number) else {
401 return Err(ApplySolutionError::VoltageBusNumberNotFound { bus_number });
402 };
403 let bus = &mut network.buses[bus_idx];
404 bus.voltage_magnitude_pu = vm[i];
405 bus.voltage_angle_rad = va[i];
406 }
407
408 Ok(())
409}
410
411pub fn apply_bus_voltages(
417 network: &mut Network,
418 vm: &[f64],
419 va: &[f64],
420) -> Result<(), ApplySolutionError> {
421 if vm.len() != network.buses.len() {
422 return Err(ApplySolutionError::VoltageMagnitudeLengthMismatch {
423 expected: network.buses.len(),
424 actual: vm.len(),
425 });
426 }
427 if va.len() != network.buses.len() {
428 return Err(ApplySolutionError::VoltageAngleLengthMismatch {
429 expected: network.buses.len(),
430 actual: va.len(),
431 });
432 }
433
434 for (i, bus) in network.buses.iter_mut().enumerate() {
435 bus.voltage_magnitude_pu = vm[i];
436 bus.voltage_angle_rad = va[i];
437 }
438
439 Ok(())
440}
441
442#[cfg(test)]
443mod tests {
444 use super::*;
445 use crate::{OpfGeneratorResults, PfSolution};
446 use surge_network::network::{Bus, BusType, Generator};
447 fn make_generator(bus: u32, in_service: bool) -> Generator {
448 let mut g = Generator::new(bus, 0.0, 1.0);
449 g.in_service = in_service;
450 g
451 }
452
453 fn make_opf_solution(
454 gen_p_mw: Vec<f64>,
455 gen_q_mvar: Vec<f64>,
456 voltage_magnitude_pu: Vec<f64>,
457 voltage_angle_rad: Vec<f64>,
458 ) -> OpfSolution {
459 OpfSolution {
460 power_flow: PfSolution {
461 voltage_magnitude_pu,
462 voltage_angle_rad,
463 ..Default::default()
464 },
465 generators: OpfGeneratorResults {
466 gen_p_mw,
467 gen_q_mvar,
468 ..Default::default()
469 },
470 ..Default::default()
471 }
472 }
473
474 #[test]
475 fn test_apply_opf_dispatch_pg_qg() {
476 let mut net = Network::new("test");
477 let mut g0 = make_generator(1, true);
478 g0.machine_id = Some("A".to_string());
479 let mut g1 = make_generator(2, false);
480 g1.machine_id = Some("B".to_string());
481 let mut g2 = make_generator(3, true);
482 g2.machine_id = Some("C".to_string());
483 net.generators.push(g0);
484 net.generators.push(g1);
485 net.generators.push(g2);
486 net.canonicalize_generator_ids();
487
488 let mut sol = make_opf_solution(vec![100.0, 200.0], vec![30.0, 60.0], vec![], vec![]);
489 sol.generators.gen_ids = vec![net.generators[0].id.clone(), net.generators[2].id.clone()];
490
491 apply_opf_dispatch(&mut net, &sol).unwrap();
492
493 assert_eq!(net.generators[0].p, 100.0);
494 assert_eq!(net.generators[0].q, 30.0);
495 assert_eq!(net.generators[1].p, 0.0);
496 assert_eq!(net.generators[1].q, 0.0);
497 assert_eq!(net.generators[2].p, 200.0);
498 assert_eq!(net.generators[2].q, 60.0);
499 }
500
501 #[test]
502 fn test_apply_dispatch_mw_skips_out_of_service() {
503 let mut net = Network::new("test");
504 net.generators.push(make_generator(1, true));
505 net.generators.push(make_generator(2, false));
506 net.generators.push(make_generator(3, true));
507
508 apply_dispatch_mw(&mut net, &[150.0, 250.0]).unwrap();
509
510 assert_eq!(net.generators[0].p, 150.0);
511 assert_eq!(net.generators[1].p, 0.0);
512 assert_eq!(net.generators[2].p, 250.0);
513 }
514
515 #[test]
516 fn test_apply_dispatch_rejects_short_generator_vector() {
517 let mut net = Network::new("test");
518 net.generators.push(make_generator(1, true));
519 net.generators.push(make_generator(2, true));
520
521 let err = apply_dispatch_mw(&mut net, &[150.0]).unwrap_err();
522 assert_eq!(
523 err,
524 ApplySolutionError::GeneratorDispatchLengthMismatch {
525 expected: 2,
526 actual: 1,
527 }
528 );
529 }
530
531 #[test]
532 fn test_apply_bus_voltages_requires_full_vectors() {
533 let mut net = Network::new("test");
534 net.buses.push(Bus::new(1, BusType::Slack, 138.0));
535 net.buses.push(Bus::new(2, BusType::PQ, 138.0));
536
537 let err = apply_bus_voltages(&mut net, &[1.0], &[0.0, 0.1]).unwrap_err();
538 assert_eq!(
539 err,
540 ApplySolutionError::VoltageMagnitudeLengthMismatch {
541 expected: 2,
542 actual: 1,
543 }
544 );
545 }
546
547 #[test]
548 fn test_apply_bus_voltages_by_bus_number_matches_identity() {
549 let mut net = Network::new("test");
550 net.buses.push(Bus::new(20, BusType::Slack, 138.0));
551 net.buses.push(Bus::new(10, BusType::PQ, 138.0));
552
553 apply_bus_voltages_by_bus_number(&mut net, &[10, 20], &[1.02, 1.05], &[0.1, -0.2]).unwrap();
554
555 assert_eq!(net.buses[0].number, 20);
556 assert!((net.buses[0].voltage_magnitude_pu - 1.05).abs() < 1e-12);
557 assert!((net.buses[0].voltage_angle_rad + 0.2).abs() < 1e-12);
558 assert_eq!(net.buses[1].number, 10);
559 assert!((net.buses[1].voltage_magnitude_pu - 1.02).abs() < 1e-12);
560 assert!((net.buses[1].voltage_angle_rad - 0.1).abs() < 1e-12);
561 }
562
563 #[test]
564 fn test_apply_opf_dispatch_requires_voltage_bus_numbers() {
565 let mut net = Network::new("test");
566 net.buses.push(Bus::new(10, BusType::Slack, 138.0));
567 net.buses.push(Bus::new(20, BusType::PQ, 138.0));
568 net.generators.push(make_generator(10, true));
569
570 let sol = make_opf_solution(vec![100.0], vec![], vec![1.0, 0.98], vec![0.0, -0.1]);
571 let err = apply_opf_dispatch(&mut net, &sol).unwrap_err();
572 assert_eq!(
573 err,
574 ApplySolutionError::VoltageBusNumberLengthMismatch {
575 expected: 2,
576 actual: 0,
577 }
578 );
579 }
580
581 #[test]
582 fn test_apply_opf_dispatch_matches_generators_by_identity() {
583 let mut net = Network::new("test");
584 let mut gen_a = make_generator(10, true);
585 gen_a.machine_id = Some("A".to_string());
586 let mut gen_b = make_generator(20, true);
587 gen_b.machine_id = Some("B".to_string());
588 net.generators.push(gen_b);
589 net.generators.push(gen_a);
590 net.canonicalize_generator_ids();
591
592 let mut sol = make_opf_solution(vec![75.0, 125.0], vec![15.0, 25.0], vec![], vec![]);
593 sol.generators.gen_ids = vec![net.generators[1].id.clone(), net.generators[0].id.clone()];
594
595 apply_opf_dispatch(&mut net, &sol).unwrap();
596
597 assert_eq!(net.generators[0].p, 125.0);
598 assert_eq!(net.generators[0].q, 25.0);
599 assert_eq!(net.generators[1].p, 75.0);
600 assert_eq!(net.generators[1].q, 15.0);
601 }
602
603 #[test]
604 fn test_apply_opf_dispatch_matches_bus_voltages_by_identity() {
605 let mut net = Network::new("test");
606 net.buses.push(Bus::new(20, BusType::Slack, 138.0));
607 net.buses.push(Bus::new(10, BusType::PQ, 138.0));
608
609 let mut sol = make_opf_solution(vec![], vec![], vec![1.02, 1.05], vec![0.1, -0.2]);
610 sol.power_flow.bus_numbers = vec![10, 20];
611
612 apply_opf_dispatch(&mut net, &sol).unwrap();
613
614 assert!((net.buses[0].voltage_magnitude_pu - 1.05).abs() < 1e-12);
615 assert!((net.buses[0].voltage_angle_rad + 0.2).abs() < 1e-12);
616 assert!((net.buses[1].voltage_magnitude_pu - 1.02).abs() < 1e-12);
617 assert!((net.buses[1].voltage_angle_rad - 0.1).abs() < 1e-12);
618 }
619
620 #[test]
621 fn test_apply_opf_dispatch_rejects_identity_mismatch() {
622 let mut net = Network::new("test");
623 let mut generator = make_generator(10, true);
624 generator.machine_id = Some("A".to_string());
625 net.generators.push(generator);
626
627 let mut sol = make_opf_solution(vec![75.0], vec![], vec![], vec![]);
628 sol.generators.gen_ids = vec!["missing-generator".to_string()];
629
630 let err = apply_opf_dispatch(&mut net, &sol).unwrap_err();
631 assert_eq!(
632 err,
633 ApplySolutionError::GeneratorIdMismatch {
634 position: 0,
635 expected_id: "missing-generator".to_string(),
636 actual_id: net.generators[0].id.clone(),
637 }
638 );
639 }
640
641 #[test]
642 fn test_apply_opf_dispatch_rejects_duplicate_solution_generator_ids() {
643 let mut net = Network::new("test");
644 net.generators.push(make_generator(10, true));
645 net.generators.push(make_generator(10, true));
646 net.canonicalize_generator_ids();
647
648 let mut sol = make_opf_solution(vec![75.0, 125.0], vec![], vec![], vec![]);
649 sol.generators.gen_ids = vec![net.generators[0].id.clone(), net.generators[0].id.clone()];
650
651 let err = apply_opf_dispatch(&mut net, &sol).unwrap_err();
652 assert_eq!(
653 err,
654 ApplySolutionError::DuplicateGeneratorIdInSolution {
655 generator_id: net.generators[0].id.clone(),
656 }
657 );
658 }
659
660 #[test]
661 fn test_apply_opf_dispatch_rejects_partial_voltage_vectors() {
662 let mut net = Network::new("test");
663 net.buses.push(Bus::new(1, BusType::Slack, 138.0));
664 net.buses.push(Bus::new(2, BusType::PQ, 138.0));
665
666 let sol = make_opf_solution(vec![], vec![], vec![], vec![0.0, 0.1]);
667
668 let err = apply_opf_dispatch(&mut net, &sol).unwrap_err();
669 assert_eq!(
670 err,
671 ApplySolutionError::VoltageBusNumberLengthMismatch {
672 expected: 2,
673 actual: 0,
674 }
675 );
676 }
677
678 #[test]
679 fn test_apply_opf_dispatch_matches_canonicalized_generator_ids() {
680 let mut net = Network::new("test");
681 let mut gen_a = make_generator(10, true);
682 gen_a.machine_id = Some("A".to_string());
683 gen_a.id = " ".to_string();
684 let mut gen_b = make_generator(20, true);
685 gen_b.machine_id = Some("B".to_string());
686 gen_b.id = "\t".to_string();
687 net.generators.push(gen_b);
688 net.generators.push(gen_a);
689
690 let mut canonical = net.clone();
691 canonical.canonicalize_generator_ids();
692
693 let mut sol = make_opf_solution(vec![75.0, 125.0], vec![15.0, 25.0], vec![], vec![]);
694 sol.generators.gen_ids = vec![
695 canonical.generators[1].id.clone(),
696 canonical.generators[0].id.clone(),
697 ];
698
699 apply_opf_dispatch(&mut net, &sol).unwrap();
700
701 assert_eq!(net.generators[0].p, 125.0);
702 assert_eq!(net.generators[0].q, 25.0);
703 assert_eq!(net.generators[1].p, 75.0);
704 assert_eq!(net.generators[1].q, 15.0);
705 }
706}