""" Grid Loader — IEEE Test Systems via pandapower Loads IEEE 33-bus (case33bw) and IEEE 118-bus (case118) systems directly from pandapower's built-in network library. No external file I/O required. """ from __future__ import annotations import copy from typing import Literal import pandas as pd import pandapower as pp import pandapower.networks as pn _LOADERS = { "case33bw": pn.case33bw, "case118": pn.case118, } # IEEE 33-bus tie line indices (out of service in default config) IEEE33_TIE_LINES = [32, 33, 34, 35, 36] # Default open lines for IEEE 33-bus in OptiQ (override to 3 OOS) IEEE33_DEFAULT_OPEN_LINES = [32, 33, 34] def load_network(system: Literal["case33bw", "case118"] = "case33bw") -> pp.pandapowerNet: """Return a fresh pandapower network for the given IEEE test system. Parameters ---------- system : str One of ``"case33bw"`` (IEEE 33-bus distribution) or ``"case118"`` (IEEE 118-bus transmission). Returns ------- pp.pandapowerNet A ready-to-simulate network object. """ loader = _LOADERS.get(system) if loader is None: raise ValueError(f"Unknown system '{system}'. Choose from {list(_LOADERS)}") net = loader() net["optiq_system"] = system net["optiq_is_distribution"] = system == "case33bw" if system == "case33bw": # Ensure only 3 lines are open by default for 33-bus net["optiq_tie_lines"] = list(IEEE33_TIE_LINES) net["optiq_default_open_lines"] = list(IEEE33_DEFAULT_OPEN_LINES) net.line["in_service"] = True for idx in IEEE33_DEFAULT_OPEN_LINES: if idx in net.line.index: net.line.at[idx, "in_service"] = False return net def clone_network(net: pp.pandapowerNet) -> pp.pandapowerNet: """Deep-copy a pandapower network (useful for parallel scenarios).""" return copy.deepcopy(net) def get_line_info(net: pp.pandapowerNet) -> dict: """Identify switchable lines (all lines), in-service and out-of-service. In IEEE 33-bus, reconfiguration is modelled by toggling ``line.in_service``. Lines that are out of service in the default config are the tie lines. Returns ------- dict with keys: ``"all"`` – all line indices ``"in_service"`` – indices of lines currently in service (closed) ``"out_of_service"`` – indices of lines currently out of service (open/tie) ``"tie_lines"`` – the default tie line indices (for IEEE 33-bus) """ all_idx = net.line.index.tolist() in_svc = net.line.index[net.line.in_service].tolist() out_svc = net.line.index[~net.line.in_service].tolist() tie_lines = net.get("optiq_tie_lines", out_svc) n_required_open = len(net.line) - (len(net.bus) - 1) return { "all": all_idx, "in_service": in_svc, "out_of_service": out_svc, "tie_lines": list(tie_lines), "n_required_open": n_required_open, } def get_network_summary(net: pp.pandapowerNet) -> dict: """Return a JSON-serialisable summary of the network structure.""" line_info = get_line_info(net) return { "n_buses": len(net.bus), "n_lines": len(net.line), "n_lines_in_service": len(line_info["in_service"]), "n_tie_lines": len(line_info["out_of_service"]), "n_generators": len(net.gen) + len(net.ext_grid), "n_loads": len(net.load), "total_load_mw": float(net.load.p_mw.sum()), "total_load_mvar": float(net.load.q_mvar.sum()), "tie_line_indices": line_info["out_of_service"], } def get_topology_data(net: pp.pandapowerNet) -> list[dict]: """Return line data for topology visualization. Each entry has: index, from_bus, to_bus, r_ohm, x_ohm, in_service, is_tie. """ line_info = get_line_info(net) tie_set = set(line_info["tie_lines"]) rows = [] for idx, row in net.line.iterrows(): rows.append({ "index": int(idx), "from_bus": int(row["from_bus"]), "to_bus": int(row["to_bus"]), "r_ohm_per_km": float(row["r_ohm_per_km"]), "x_ohm_per_km": float(row["x_ohm_per_km"]), "length_km": float(row["length_km"]), "in_service": bool(row["in_service"]), "is_tie": idx in tie_set, }) return rows def get_bus_data(net: pp.pandapowerNet) -> list[dict]: """Return bus data for visualization.""" rows = [] for idx, row in net.bus.iterrows(): load_p = 0.0 load_q = 0.0 if len(net.load) > 0: bus_loads = net.load[net.load.bus == idx] load_p = float(bus_loads.p_mw.sum()) load_q = float(bus_loads.q_mvar.sum()) rows.append({ "index": int(idx), "name": str(row.get("name", f"Bus {idx}")), "vn_kv": float(row["vn_kv"]), "load_mw": load_p, "load_mvar": load_q, "is_slack": int(idx) in net.ext_grid.bus.values, }) return rows