import os
import pytest
import gridoxide
FIXTURES = os.path.join(
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
"tests",
"data",
"pgm",
"powerflow",
"symmetric",
)
def fixture(name):
return os.path.join(FIXTURES, name, "input.json")
@pytest.fixture(scope="module")
def model():
return gridoxide.AcSensitivityModel(fixture("distribution-case"))
def test_shapes_follow_the_network(model):
assert model.n_buses == 9
assert model.n_branches == 10
col = model.column(active_injection=2)
assert len(col.d_branch_active) == model.n_branches
assert len(col.d_branch_reactive) == model.n_branches
assert len(col.d_voltage_magnitude) == model.n_buses
assert len(col.d_voltage_angle) == model.n_buses
row = model.row(branch=8)
assert len(row.d_active_injection) == model.n_buses
assert len(row.d_transformer_ratio) == model.n_branches
def test_forward_and_adjoint_agree(model):
for branch in range(model.n_branches):
row = model.row(branch=branch)
for bus in (0, 2, 5, 8):
forward = model.column(active_injection=bus).d_branch_active[branch]
assert row.d_active_injection[bus] == pytest.approx(forward, abs=1e-9)
def test_tap_sensitivities_are_zero_for_lines_and_nonzero_for_transformers(model):
row = model.row(branch=8)
for line in range(8):
assert row.d_transformer_ratio[line] == 0.0
assert row.d_phase_shift[line] == 0.0
assert any(abs(row.d_transformer_ratio[t]) > 1e-6 for t in (8, 9))
assert any(abs(row.d_phase_shift[t]) > 1e-6 for t in (8, 9))
def test_a_slack_bus_injection_moves_nothing(model):
reference = model.n_buses - 1
col = model.column(active_injection=reference)
assert all(v == 0.0 for v in col.d_voltage_angle)
assert all(v == 0.0 for v in col.d_voltage_magnitude)
assert all(v == 0.0 for v in col.d_branch_active)
for bus in range(reference):
other = model.column(active_injection=bus)
assert any(abs(v) > 1e-9 for v in other.d_voltage_angle), bus
def test_the_two_terminals_differ(model):
a = model.column(active_injection=2, terminal="from").d_branch_active
b = model.column(active_injection=2, terminal="to").d_branch_active
assert a != b
def test_voltage_functions_are_available(model):
row = model.row(bus=5, quantity="magnitude")
forward = model.column(active_injection=2).d_voltage_magnitude[5]
assert row.d_active_injection[2] == pytest.approx(forward, abs=1e-9)
row = model.row(bus=5, quantity="angle")
forward = model.column(active_injection=2).d_voltage_angle[5]
assert row.d_active_injection[2] == pytest.approx(forward, abs=1e-9)
def test_exactly_one_variable_is_required(model):
with pytest.raises(ValueError, match="exactly one"):
model.column()
with pytest.raises(ValueError, match="exactly one"):
model.column(active_injection=1, reactive_injection=2)
with pytest.raises(ValueError, match="exactly one"):
model.row()
with pytest.raises(ValueError, match="exactly one"):
model.row(branch=1, bus=2)
def test_bad_arguments_are_rejected(model):
with pytest.raises(ValueError, match="terminal"):
model.column(active_injection=1, terminal="sideways")
with pytest.raises(ValueError, match="quantity"):
model.row(branch=1, quantity="sideways")
with pytest.raises(ValueError):
model.column(active_injection=999)
def test_a_missing_file_is_reported_clearly():
with pytest.raises(RuntimeError, match="reading"):
gridoxide.AcSensitivityModel(fixture("no-such-case"))