import json
import sys
import time
from pathlib import Path
from power_grid_model import (
CalculationMethod,
ComponentType,
DatasetType,
PowerGridModel,
initialize_array,
)
import gridoxide
DEFAULT_CASES = ["case14", "case118", "case300", "case1354pegase", "case2869pegase"]
CEILING = 300
REPEATS = 5
def load(path):
raw = json.loads(path.read_text())["data"]
for entry in raw.get("voltage_regulator", []):
entry.pop("q_min", None)
entry.pop("q_max", None)
data = {}
for component, entries in raw.items():
if not entries:
continue
arr = initialize_array(DatasetType.input, ComponentType[component], len(entries))
for i, e in enumerate(entries):
for field, value in e.items():
if field in arr.dtype.names:
arr[field][i] = value
data[ComponentType[component]] = arr
return raw, data
def smallest_budget(attempt):
if not attempt(CEILING):
return None
lo, hi = 1, CEILING
while lo < hi:
mid = (lo + hi) // 2
if attempt(mid):
hi = mid
else:
lo = mid + 1
return lo
def best_ms(fn):
times = []
for _ in range(REPEATS):
start = time.perf_counter()
fn()
times.append((time.perf_counter() - start) * 1e3)
return min(times)
def main():
root = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("/tmp/se_bench")
cases = sys.argv[2:] or DEFAULT_CASES
header = (
f"{'case':<15}{'buses':>6} {'PGM ms':>7}{'it':>4}{'ms/it':>8} "
f"{'gx ms':>7}{'it':>4}{'ms/it':>8} {'time x':>7}{'iter x':>7}{'per-it x':>9}"
)
print(header)
print("-" * len(header))
for name in cases:
path = root / f"{name}_se.json"
if not path.exists():
print(f"{name:<15} no SE document at {path} — run bench_se.rs --emit first")
continue
raw, data = load(path)
try:
model = PowerGridModel(data)
except Exception as exc: print(f"{name:<15} power-grid-model rejected the document: {type(exc).__name__}")
continue
def pgm_attempt(k):
try:
model.calculate_state_estimation(
calculation_method=CalculationMethod.iterative_linear, max_iterations=k
)
return True
except Exception: return False
def gx_attempt(k):
try:
m = gridoxide.StateEstimationModel.from_pgm_json(
str(path), method="iterative_linear", max_iter=k
)
m.solve()
return True
except Exception: return False
pgm_it = smallest_budget(pgm_attempt)
gx_it = smallest_budget(gx_attempt)
pgm_ms = best_ms(
lambda: model.calculate_state_estimation(
calculation_method=CalculationMethod.iterative_linear
)
)
gx = gridoxide.StateEstimationModel.from_pgm_json(
str(path), method="iterative_linear", max_iter=CEILING
)
gx_ms = best_ms(gx.solve)
if pgm_it is None or gx_it is None:
print(f"{name:<15}{len(raw['node']):>6} "
f"{pgm_ms:>7.2f}{'div' if pgm_it is None else pgm_it:>4}{'':>8} "
f"{gx_ms:>7.2f}{'div' if gx_it is None else gx_it:>4}")
continue
pgm_pi, gx_pi = pgm_ms / pgm_it, gx_ms / gx_it
print(
f"{name:<15}{len(raw['node']):>6} "
f"{pgm_ms:>7.2f}{pgm_it:>4}{pgm_pi:>8.3f} "
f"{gx_ms:>7.2f}{gx_it:>4}{gx_pi:>8.3f} "
f"{gx_ms / pgm_ms:>7.2f}{gx_it / pgm_it:>7.2f}{gx_pi / pgm_pi:>9.2f}"
)
print()
print("Times are milliseconds, best of", REPEATS, "runs; `it` is the smallest")
print("max_iterations that converges. `per-it x` below 1 means gridoxide's own")
print("iterations are individually cheaper and the gap is iteration count.")
if __name__ == "__main__":
main()