Skip to main content

cgmes_area_probe/
cgmes_area_probe.rs

1//! CGMES ControlAreas: what the file says each area is scheduled to exchange,
2//! and what it is exchanging at the solved state.
3use std::path::{Path, PathBuf};
4fn main() {
5    let root =
6        Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/data/CGMES-Test-Configurations/v3.0");
7    for name in std::env::args().skip(1) {
8        let dir = root.join(&name);
9        if !dir.is_dir() {
10            println!("{name}: absent");
11            continue;
12        }
13        let mut paths: Vec<PathBuf> = std::fs::read_dir(&dir)
14            .unwrap()
15            .filter_map(|e| e.ok().map(|e| e.path()))
16            .filter(|p| p.extension().is_some_and(|x| x == "xml"))
17            .collect();
18        paths.sort();
19        let refs: Vec<&Path> = paths.iter().map(|p| p.as_path()).collect();
20        let ds = gridoxide::cgmes::load_profiles(&refs).expect("load");
21        let net = gridoxide::cgmes::cgmes_to_network(&ds, 100e6).expect("convert");
22        let areas = gridoxide::cgmes::cgmes_control_areas(&ds, &net, 100e6).expect("areas");
23        println!("=== {name}");
24        println!("    {:?}", areas.report);
25        let report = gridoxide::run_power_flow(
26            net.buses.clone(),
27            &net.lines,
28            &net.transformers,
29            &net.shunts,
30            gridoxide::TapData::none(),
31            gridoxide::PowerFlowOptions {
32                tol: 1e-8,
33                max_iter: 40,
34                ..Default::default()
35            },
36        );
37        let x = gridoxide::outerloop::AreaInterchange::measure(
38            &report.buses,
39            &net.lines,
40            &net.transformers,
41            &areas.of_bus,
42            areas.ids.len(),
43        );
44        println!("    solve {:?}", report.stats.status);
45        // What sits at the buses no area claims — the boundary X-nodes.
46        let unassigned: Vec<usize> = areas
47            .of_bus
48            .iter()
49            .enumerate()
50            .filter(|(_, a)| a.is_none())
51            .map(|(i, _)| i)
52            .collect();
53        let inj: f64 = unassigned.iter().map(|&i| report.buses[i].p_spec).sum();
54        println!(
55            "    {} bus(es) in no area, injecting {:.3} MW between them",
56            unassigned.len(),
57            inj * 100.0
58        );
59        println!(
60            "    sum of area positions: {:.3} MW",
61            x.iter().sum::<f64>() * 100.0
62        );
63        for (i, (mrid, nm)) in areas.ids.iter().enumerate() {
64            let buses = areas.of_bus.iter().filter(|a| **a == Some(i)).count();
65            println!(
66                "    {:<4} ({}) {:>4} bus(es)  scheduled export {:>10.3} MW  actual {:>10.3} MW",
67                nm,
68                &mrid[..8.min(mrid.len())],
69                buses,
70                areas.targets[i] * 100.0,
71                x[i] * 100.0
72            );
73        }
74    }
75}