from pathlib import Path
import pytest
import gridoxide
from _pglib import fixtures
ROOT = Path(__file__).resolve().parent.parent.parent
REALGRID = ROOT / "tests/data/CGMES-Test-Configurations/v3.0/RealGrid/RealGrid-Merged"
pytestmark = pytest.mark.skipif(
not hasattr(gridoxide.PowerFlowModel, "from_cgmes"), reason="built without the cgmes feature"
)
@pytest.fixture(scope="module")
def solved():
if not REALGRID.exists():
pytest.skip(f"missing fixture: {REALGRID}")
model = gridoxide.PowerFlowModel.from_cgmes(
[str(p) for p in sorted(REALGRID.glob("*.xml"))], s_base_va=100e6, max_iter=60
)
model.solve(enforce_q_limits=True, max_outer=60)
return model
def test_it_attributes_each_bus_to_its_machines(solved):
split = solved.machine_dispatch()
assert split, "RealGrid has voltage-controlled buses"
shared = [b for b in split if len(b["machines"]) > 1]
assert len(shared) >= 62, "RealGrid's shared-control buses"
for bus in split:
assert bus["basis"] in ("explicit", "capability", "uniform")
summed = sum(m["q"] for m in bus["machines"])
assert summed == pytest.approx(bus["attributed"], abs=1e-12)
assert bus["attributed"] + bus["unattributed"] == pytest.approx(bus["required"], abs=1e-12)
for m in bus["machines"]:
assert m["q_min"] - 1e-9 <= m["q"] <= m["q_max"] + 1e-9
assert 0.0 <= m["share"] <= 1.0
assert sum(m["share"] for m in bus["machines"]) == pytest.approx(1.0)
def test_shares_are_proportional_to_capability(solved):
shared = [b for b in solved.machine_dispatch() if len(b["machines"]) > 1]
by_capability = [b for b in shared if b["basis"] == "capability"]
assert len(by_capability) >= 60
bus = by_capability[0]
ranges = [m["q_max"] - m["q_min"] for m in bus["machines"]]
total = sum(ranges)
for m, r in zip(bus["machines"], ranges):
assert m["share"] == pytest.approx(r / total)
def test_unattributed_power_is_surfaced_not_hidden(solved):
split = solved.machine_dispatch()
short = [b for b in split if abs(b["unattributed"]) > 1e-6]
assert short, "RealGrid has buses where a co-located load saturates the machine"
for bus in short:
assert all(m["at_limit"] for m in bus["machines"])
def test_a_model_without_machines_returns_nothing():
pgm = fixtures() / "pglib_opf_case14_ieee.json"
model = gridoxide.PowerFlowModel.from_pgm_json(str(pgm), s_base_va=100e6)
model.solve()
assert model.machine_dispatch() == []
REMOTE = ROOT / "tests/data/CGMES-Test-Configurations/v3.0/MicroGrid/MicroGrid-Type1/MicroGrid-Type1-Merged"
@pytest.fixture(scope="module")
def microgrid():
if not REMOTE.exists():
pytest.skip(f"missing fixture: {REMOTE}")
return [str(p) for p in sorted(REMOTE.glob("*.xml"))]
def test_remote_voltage_control_moves_the_control_onto_the_machine(microgrid):
model = gridoxide.PowerFlowModel.from_cgmes(microgrid, s_base_va=100e6, max_iter=60)
model.solve(enforce_q_limits=True, control_remote_voltage=True, max_outer=60)
reports = model.remote_voltage_control()
assert len(reports) == 1
r = reports[0]
assert r["outcome"] == "held"
assert r["controller_bus"] != r["controlled_bus"]
assert r["reached"] == pytest.approx(r["target"], abs=2e-4)
assert r["moves"] >= 1
def test_remote_voltage_control_is_off_by_default(microgrid):
model = gridoxide.PowerFlowModel.from_cgmes(microgrid, s_base_va=100e6, max_iter=60)
model.solve(enforce_q_limits=True, max_outer=60)
assert model.remote_voltage_control() == []