tap_deadband_probe/
tap_deadband_probe.rs1use std::path::{Path, PathBuf};
8
9fn main() {
10 let root =
11 Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/data/CGMES-Test-Configurations/v3.0");
12 for name in std::env::args().skip(1) {
13 let dir = root.join(&name);
14 let mut paths: Vec<PathBuf> = std::fs::read_dir(&dir)
15 .unwrap()
16 .filter_map(|e| e.ok().map(|e| e.path()))
17 .filter(|p| p.extension().is_some_and(|e| e == "xml"))
18 .collect();
19 paths.sort();
20 let refs: Vec<&Path> = paths.iter().map(|p| p.as_path()).collect();
21 let ds = gridoxide::cgmes::load_profiles(&refs).expect("load");
22 let mut net = gridoxide::cgmes::cgmes_to_network(&ds, 100e6).expect("convert");
23 match gridoxide::cgmes::cgmes_resolve_dc_converters(&ds, &mut net.buses, 100e6) {
26 Ok(_) => println!(" dc: resolved"),
27 Err(e) => println!(" dc: {e}"),
28 }
29 let index = gridoxide::cgmes::cgmes_topological_node_bus_index(&ds).expect("index");
30
31 let mut published = vec![f64::NAN; net.buses.len()];
33 for mrid in &ds.by_type["SvVoltage"] {
34 let sv = &ds.entries[mrid];
35 let (Some(tn), Some(v)) = (
36 sv.get_ref("SvVoltage.TopologicalNode"),
37 sv.get_f64("SvVoltage.v"),
38 ) else {
39 continue;
40 };
41 if let Some(&b) = index.get(tn) {
42 published[b] = v * 1e3 / net.buses[b].u_rated;
43 }
44 }
45
46 let report = gridoxide::run_power_flow(
48 net.buses.clone(),
49 &net.lines,
50 &net.transformers,
51 &net.shunts,
52 gridoxide::TapData::none(),
53 gridoxide::PowerFlowOptions {
54 tol: 1e-8,
55 max_iter: 30,
56 ..Default::default()
57 },
58 );
59 let after = gridoxide::run_power_flow(
61 net.buses.clone(),
62 &net.lines,
63 &net.transformers,
64 &net.shunts,
65 gridoxide::TapData {
66 changers: &net.tap_changers,
67 regulation: &net.regulation,
68 },
69 gridoxide::PowerFlowOptions {
70 control_taps: true,
71 max_outer_iter: 60,
72 tol: 1e-8,
73 max_iter: 30,
74 ..Default::default()
75 },
76 );
77 println!(
78 "=== {name}: base solve {:?}, controlled solve {:?}",
79 report.stats.status, after.stats.status
80 );
81 println!(
82 "{:>4} {:>9} {:>9} {:>9} {:>9} {:>9} {:>9}",
83 "bus", "target", "half-db", "gridoxide", "|err|", "published", "|err|"
84 );
85 for r in &net.regulation {
86 if r.mode != gridoxide::outerloop::RegulationMode::Voltage {
87 continue;
88 }
89 let mine = report.buses[r.controlled_bus].voltage_mag;
90 let theirs = published[r.controlled_bus];
91 println!(
92 "{:>4} {:>9.5} {:>9.5} {:>9.5} {:>9.5}{} {:>9.5} {:>9.5}{}",
93 r.controlled_bus,
94 r.target,
95 r.deadband / 2.0,
96 mine,
97 (mine - r.target).abs(),
98 if (mine - r.target).abs() > r.deadband / 2.0 {
99 " OUT"
100 } else {
101 " in"
102 },
103 theirs,
104 (theirs - r.target).abs(),
105 if (theirs - r.target).abs() > r.deadband / 2.0 {
106 " OUT"
107 } else {
108 " in"
109 },
110 );
111 let controlled = after.buses[r.controlled_bus].voltage_mag;
112 println!(
113 " -> after tap control: {controlled:.5} |err| {:.5}{}",
114 (controlled - r.target).abs(),
115 if (controlled - r.target).abs() > r.deadband / 2.0 {
116 " OUT"
117 } else {
118 " in"
119 },
120 );
121 }
122 }
123}