import json
from pathlib import Path
import pytest
import gridoxide
FIXTURE_DIR = Path(__file__).resolve().parent.parent.parent / "tests/data/pgm/powerflow/symmetric/line"
INPUT_JSON = FIXTURE_DIR / "input.json"
EXPECTED_U_PU = [1.0000134893760249, 1.000207096167134, 1.0002750640565956]
@pytest.mark.parametrize("backend", ["scalar", "block"])
def test_solve_matches_reference_output(backend):
model = gridoxide.PowerFlowModel.from_pgm_json(str(INPUT_JSON), backend=backend)
assert model.n_nodes == 4
model.solve()
vm = model.voltage_mag()
assert len(vm) == 4
for actual, expected in zip(vm[:3], EXPECTED_U_PU):
assert actual == pytest.approx(expected, abs=1e-5)
def test_repeated_solve_reuses_persistent_solver():
model = gridoxide.PowerFlowModel.from_pgm_json(str(INPUT_JSON), backend="scalar")
model.solve()
first = model.voltage_mag()
model.solve()
second = model.voltage_mag()
assert first == second
def test_reset_then_solve_still_converges():
model = gridoxide.PowerFlowModel.from_pgm_json(str(INPUT_JSON), backend="scalar")
model.solve()
model.reset()
model.solve()
vm = model.voltage_mag()
for actual, expected in zip(vm, EXPECTED_U_PU):
assert actual == pytest.approx(expected, abs=1e-5)
def test_missing_file_raises():
with pytest.raises(RuntimeError):
gridoxide.PowerFlowModel.from_pgm_json("/nonexistent/path/does/not/exist.json")
def test_invalid_json_raises():
with pytest.raises(ValueError):
gridoxide.PowerFlowModel.from_pgm_json(str(Path(__file__)))
def test_block_backend_matches_scalar_on_pv_bus(tmp_path):
data = json.loads(INPUT_JSON.read_text())
data["data"]["sym_gen"] = [
{"id": 20, "node": 2, "status": 1, "type": 0, "p_specified": 0.0, "q_specified": 0.0}
]
data["data"]["voltage_regulator"] = [
{"id": 21, "regulated_object": 20, "status": 1, "u_ref": 1.0}
]
pv_input = tmp_path / "pv_input.json"
pv_input.write_text(json.dumps(data))
solved = {}
for backend in ("scalar", "block"):
model = gridoxide.PowerFlowModel.from_pgm_json(str(pv_input), backend=backend)
model.solve()
solved[backend] = (model.voltage_mag(), model.voltage_ang())
assert solved["scalar"][0][1] == pytest.approx(1.0, abs=1e-12)
mag_s, ang_s = solved["scalar"]
mag_b, ang_b = solved["block"]
assert mag_b == pytest.approx(mag_s, abs=1e-9)
assert ang_b == pytest.approx(ang_s, abs=1e-9)
DISTRIBUTION_CASE = INPUT_JSON.parent.parent / "distribution-case" / "input.json"
def test_init_picks_the_starting_point():
model = gridoxide.PowerFlowModel.from_pgm_json(str(DISTRIBUTION_CASE))
with pytest.raises(RuntimeError, match="did not converge"):
model.solve(init="flat")
model.solve()
default = model.voltage_mag()
model.solve(init="dc")
dc = model.voltage_mag()
model.solve(init="linear_impedance")
assert model.voltage_mag() == default
assert dc == pytest.approx(default, abs=1e-6)
def test_flat_start_solves_what_it_can():
model = gridoxide.PowerFlowModel.from_pgm_json(str(INPUT_JSON))
model.solve(init="flat")
for actual, expected in zip(model.voltage_mag()[:3], EXPECTED_U_PU):
assert actual == pytest.approx(expected, abs=1e-5)
def test_unknown_init_raises():
model = gridoxide.PowerFlowModel.from_pgm_json(str(INPUT_JSON))
with pytest.raises(ValueError, match="unknown init"):
model.solve(init="warm")