mod common;
use std::path::PathBuf;
use gridoxide::branch_flow::{Terminal, branch_params, bus_voltages, terminal_flow};
use gridoxide::network::{build_ybus, linear_initial_guess, stamp_shunts};
use gridoxide::pgm::{PgmLineOutput, node_id_to_idx, pgm_shunts_1ph, pgm_to_network};
use gridoxide::solver::{JacobianBackend, PersistentSolver};
fn data_dir(rel: &str) -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/data/pgm/powerflow/symmetric")
.join(rel)
}
fn assert_branch_flows(fixture: &str, tol: f64) {
let base = data_dir(fixture);
let input = common::load_pgm_input(&base.join("input.json"));
let expected = common::load_json(&base.join("sym_output.json"));
let s_base_va = 1e6;
let id_to_idx = node_id_to_idx(&input);
let shunts = pgm_shunts_1ph(&input, &id_to_idx, s_base_va);
let net = pgm_to_network(input, s_base_va, 50.0);
let mut ybus = build_ybus(net.buses.len(), &net.lines, &net.transformers);
stamp_shunts(&mut ybus, &shunts);
let ybus = ybus.finish();
let mut solved = net.buses.clone();
linear_initial_guess(&mut solved, &ybus);
PersistentSolver::new(JacobianBackend::Scalar).solve(&mut solved, &ybus, 1e-12, 50);
let params = branch_params(&net.lines, &net.transformers);
let v = bus_voltages(&solved);
let line_outputs: Vec<PgmLineOutput> =
serde_json::from_value(expected["data"]["line"].clone()).expect("line output array");
assert!(
!line_outputs.is_empty(),
"{fixture}: fixture has no line output to check"
);
let mut checked = 0;
for out in &line_outputs {
let check = |actual_pu: f64, expected_w: f64, what: &str| {
let actual_w = actual_pu * s_base_va;
assert!(
(actual_w - expected_w).abs() < tol,
"{fixture} line {}: {what} = {actual_w}, PGM says {expected_w}",
out.id
);
};
let flow = |terminal: Terminal| match net.resolve_terminal(out.id, terminal) {
Some((b, t)) => terminal_flow(¶ms[b], t, &v),
None => (0.0, 0.0),
};
let (p_from, q_from) = flow(Terminal::From);
let (p_to, q_to) = flow(Terminal::To);
check(p_from, out.p_from, "p_from");
check(q_from, out.q_from, "q_from");
check(p_to, out.p_to, "p_to");
check(q_to, out.q_to, "q_to");
checked += 1;
}
assert!(checked > 0, "{fixture}: no branch was actually compared");
}
#[test]
fn branch_flows_match_pgm_line_fixture() {
assert_branch_flows("line", 1.0);
}
#[test]
fn branch_flows_match_pgm_node_fixture() {
assert_branch_flows("node", 1.0);
}
#[test]
fn branch_flows_match_pgm_shunt_fixture() {
assert_branch_flows("shunt", 1.0);
}
#[test]
fn branch_flows_match_pgm_source_fixture() {
assert_branch_flows("source", 1.0);
}
#[test]
fn branch_flows_match_pgm_sym_load_fixture() {
assert_branch_flows("sym_load", 1.0);
}
#[test]
fn branch_flows_match_pgm_sym_gen_fixture() {
assert_branch_flows("sym_gen", 1.0);
}
#[test]
fn branch_flows_match_pgm_transmission_case() {
assert_branch_flows("transmission-case", 100.0);
}