import pytest
import gridoxide
from _pglib import fixtures
pytestmark = pytest.mark.skipif(
not hasattr(gridoxide.PowerFlowModel, "qv_curve"), reason="built without the qv binding"
)
@pytest.fixture(scope="module")
def model():
case14 = fixtures() / "pglib_opf_case14_ieee.json"
return gridoxide.PowerFlowModel.from_pgm_json(str(case14), s_base_va=100e6)
def test_it_traces_a_curve_and_finds_a_margin(model):
curve = model.qv_curve(13, v_min=0.20)
assert curve["status"] == "nose_found"
assert curve["bus"] == 13
assert curve["nose"] is not None
assert curve["nose"]["margin_pu"] > 0
assert 0.2 < curve["nose"]["voltage"] < 1.1
assert curve["nose"]["refined"] is True
pts = curve["points"]
assert len(pts) > 50
volts = [p["voltage"] for p in pts]
assert volts == sorted(volts, reverse=True)
assert curve["nose"]["q"] <= min(p["q"] for p in pts) + 1e-9
def test_the_curve_crosses_zero_at_the_buses_own_voltage(model):
curve = model.qv_curve(13, v_min=0.20, step=0.005)
base = curve["base_voltage"]
above = [p["q"] for p in curve["points"] if p["voltage"] > base + 0.02]
below = [p["q"] for p in curve["points"] if base - 0.20 < p["voltage"] < base - 0.02]
assert all(q > 0 for q in above), "holding a bus above where it sits needs support"
assert all(q < 0 for q in below), "holding it below needs absorption"
def test_a_truncated_sweep_reports_a_bound_not_a_margin(model):
truncated = model.qv_curve(13, v_min=0.85)
full = model.qv_curve(13, v_min=0.20)
assert truncated["status"] == "nose_not_reached"
assert full["status"] == "nose_found"
assert truncated["nose"]["margin_pu"] < full["nose"]["margin_pu"]
assert truncated["nose"]["refined"] is False
def test_the_weakest_bus_can_be_found_by_ranking(model):
margins = {}
for bus in range(15):
try:
c = model.qv_curve(bus, v_min=0.20)
except ValueError:
continue if c["status"] == "nose_found":
margins[bus] = c["nose"]["margin_pu"]
assert len(margins) >= 10
weakest = min(margins, key=margins.get)
assert margins[weakest] > 0
assert weakest == 7
def test_a_slack_bus_is_refused(model):
with pytest.raises(ValueError):
model.qv_curve(14)
with pytest.raises(ValueError):
model.qv_curve(999)