1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
mod cgmes_common;
use gridoxide::cgmes::{
cgmes_to_buses_and_branches, cgmes_topological_node_bus_index, load_profiles,
};
use gridoxide::network::{build_ybus, stamp_shunts};
use gridoxide::run_power_flow_analysis_from_ybus;
/// ENTSO-E's MicroGrid conformance test configuration, Belgian area
/// ("MicroGrid-BE-MAS" = Model As Supplied), referenced via the
/// `CGMES-Test-Configurations` git submodule rather than copied into this
/// repo — see tests/data/cgmes/README.md for why (its CC BY-NC-SA 4.0
/// license doesn't fit a copy inside this Apache-2.0 repo's own tree).
///
/// Tolerance is looser than the PGM fixtures' (1e-2, i.e. up to a few
/// percent, not 1e-5): this is a genuinely boundary-truncated sub-model
/// (only BE's own area is loaded, not the full BE+NL merged CGMES model),
/// solved with fixed-P/Q `EquivalentInjection`s standing in for the rest of
/// the interconnected system. Cross-checked against pypowsybl's own
/// independent CGMES import + AC load flow on the same bundled case: it
/// *also* deviates from this fixture's published SV values by a comparable
/// few percent (e.g. ~1.3% at the 10.5 kV bus), confirming that gap is
/// inherent to boundary-truncated solving with simple fixed-injection
/// equivalents, not a correctness bug in either tool. A further few percent
/// here traces to boundary `ACLineSegment`s connecting a 400 kV boundary bus
/// to this area's own 380 kV bus — CGMES's own docs explicitly allow such
/// nominal-voltage mismatches at boundary points, but gridoxide's `Line`
/// (unlike `Transformer`) has no tap ratio to absorb one.
#[test]
fn test_cgmes_microgrid_be_mas() {
let dir = cgmes_common::fixture_dir();
if !dir.exists() {
eprintln!(
"skipping: {} not found — run `git submodule update --init tests/data/CGMES-Test-Configurations`",
dir.display()
);
return;
}
// The boundary EQ (EQ_BD) is needed too: some TopologicalNode.BaseVoltage
// references (e.g. the 380 kV boundary bus) resolve only to objects
// defined there, not in BE-MAS's own EQ file — confirmed by actually
// hitting an UnresolvedReference without it, not assumed up front.
let eq_bd = dir.join("../MicroGrid-BD-MAS/20171002T0930Z_ENTSO-E_EQ_BD_2.xml");
let eq = dir.join("20210325T1530Z_1D_BE_EQ_001.xml");
let ssh = dir.join("20210325T1530Z_1D_BE_SSH_001.xml");
let tp = dir.join("20210325T1530Z_1D_BE_TP_001.xml");
let sv = dir.join("20210325T1530Z_1D_BE_SV_001.xml");
let ds =
load_profiles(&[&eq_bd, &eq, &ssh, &tp, &sv]).expect("failed to decode CGMES profiles");
let expected = cgmes_common::expected_voltages(&ds);
assert_eq!(expected.len(), 7, "fixture should have 7 SvVoltage entries");
let s_base_va = 100e6;
let (buses, lines, transformers, shunts) =
cgmes_to_buses_and_branches(&ds, s_base_va).expect("conversion failed");
// 7 physical TopologicalNode buses, plus one synthesized bus per
// 3-winding star point and per boundary ConnectivityNode.
assert!(
buses.len() > 7,
"expected at least the 7 physical TopologicalNode buses, got {}",
buses.len()
);
let n = buses.len();
let mut ybus = build_ybus(n, &lines, &transformers);
stamp_shunts(&mut ybus, &shunts);
let result = run_power_flow_analysis_from_ybus(buses, ybus).buses;
let bus_index = cgmes_topological_node_bus_index(&ds).expect("bus index lookup failed");
cgmes_common::assert_matches_sv(&result, &bus_index, &expected, 4e-2);
}