Skip to content

mambo_power.io.pandapower_json

pandapower JSON (pp.to_json / pp.from_json) importer and exporter: load / loads / load_with_report read a pandapowerNet document into a Network; dumps / dump / dumps_with_report write one that loads in pp.from_json and on which pandapower's own rundcpp / runpp agree with pf.solve_dc / pf.solve_ac. pandapower is imported lazily. See File formats › pandapower JSON for the table map, unit conversions, the ext_grid rule, report codes and limitations.

mambo_power.io.pandapower_json

pandapower JSON (pp.to_json / pp.from_json) importer and exporter (wave M8, W1/W2).

pandapower is imported lazily inside the functions that need it, so mambo_power itself keeps its zero-optional-dependency import (R9). Every conversion is best effort + report (design item D1): anything the other side cannot hold is dropped or repaired and named in the returned ImportReport / ExportReport with the element id and the field. An empty report means the conversion was lossless. Nothing is logged or printed.

Tables read on import: bus, ext_grid, gen, sgen, load, shunt, line, trafo, poly_cost, pwl_cost. Results tables (res_*) are neither read nor written (the wave's "Not doing"; M8 critic finding 10): a bus's stored vm_pu/va_deg does not travel through this format except for the slack, whose state is the ext_grid setpoint; the export names every other bus that had one (FIELD_DROPPED). Every other non-empty table (trafo3w, switch, impedance, ward, xward, dcline, storage, ...) is dropped row by row with ELEMENT_DROPPED.

Unit conventions (measured on pandapower 3.3.0 against fixtures/matpower/case14.m, record/m8-research.md §1; Zb = vn_kv(from)² / sn_mva):

  • line: r = r_ohm_per_km · length / parallel / Zb (same for x); b = 2π·f_hz · c_nf_per_km·1e-9 · length · parallel · Zb; rating_mva = max_i_ka · df · parallel · √3 · vn_kv(from);
  • trafo: z = vk_percent/100 · sn_mva/sn_trafo · (vn_lv_kv/vn(lv bus))² / parallel, r from vkr_percent the same way, x = √(z² − r²); tap_ratio = (vn_hv_kv/vn(hv bus)) / (vn_lv_kv/vn(lv bus)) after the tap changer has scaled the tapped winding (1 + (tap_pos − tap_neutral)·tap_step_percent/100 for a Ratio tap; pandapower 3.3's full rule, tap_changer_type None = no tap, is in _Importer.tap_changer); from_bus = hv_bus (mambo's tap side), shift_deg = shift_degree plus what the changer adds;
  • shunt: pandapower's p_mw/q_mvar are consumption, mambo's b_mvar is injection: b_mvar = −q_mvar · step · (vn(bus)/vn_kv)², g_mw = p_mw · step · (vn(bus)/vn_kv)²;
  • costs: poly_cost cp2/cp1/cp0PolynomialCost.coefficients == [c2, c1, c0]; pwl_cost.points == [[p0, p1, slope], ...]PiecewiseCost breakpoints with the cost at p0 taken as 0 (pandapower has no offset column).

Bus roles: the first in-service ext_grid is the slack (mambo needs exactly one); any other ext_grid becomes a PV generator (EXTRA_EXT_GRID_DEMOTED); with no in-service ext_grid the first in-service gen with slack = True is the slack (GEN_SLACK_PROMOTED); a bus with an in-service gen is pv; everything else pq. A file with neither leaves the network without a slack, which Network refuses (NO_SLACK). On export the rule runs backwards: the first in-service generator of the slack bus becomes ext_grid, PV-bus generators gen, PQ-bus generators sgen. Ids: import takes name when it is present, else <table>-<index>; export writes the id into name. Bus.area travels as an extra bus.area column (pandapower keeps unknown columns through to_json, measured).

CODES module-attribute

CODES: tuple[str, ...] = (
    "EXTRA_EXT_GRID_DEMOTED",
    "COLUMN_DROPPED",
    "ELEMENT_DROPPED",
    "FIELD_DEFAULTED",
    "ISLAND_DEACTIVATED",
    "TAP_CHANGER_TYPE_UNSUPPORTED",
    "GEN_SLACK_PROMOTED",
    "FIELD_DROPPED",
    "COST_DROPPED",
    "BID_DROPPED",
)

Every report code this module emits (import: the first seven; export: the last three plus ELEMENT_DROPPED for storage and FIELD_DEFAULTED for an unrated transformer's sn_mva). Registered in mambo_power.io.limitations.LIMITATIONS.

DEFAULT_F_HZ module-attribute

DEFAULT_F_HZ = 50.0

net.f_hz written by the exporter unless given; it only enters the bc_nf_per_km conversion and the importer inverts it with the file's own f_hz.

load

load(source: str | PathLike[str]) -> Network

Read the pandapower JSON file at source; the report is discarded.

Source code in src/mambo_power/io/pandapower_json.py
def load(source: str | PathLike[str]) -> Network:
    """Read the pandapower JSON file at ``source``; the report is discarded."""
    return load_with_report(source)[0]

loads

loads(text: str) -> Network

Read pandapower JSON text; the report is discarded.

Source code in src/mambo_power/io/pandapower_json.py
def loads(text: str) -> Network:
    """Read pandapower JSON text; the report is discarded."""
    return loads_with_report(text)[0]

load_with_report

load_with_report(
    source: str | PathLike[str],
) -> tuple[Network, ImportReport]

Read the file at source and return (network, report).

Source code in src/mambo_power/io/pandapower_json.py
def load_with_report(source: str | PathLike[str]) -> tuple[Network, ImportReport]:
    """Read the file at ``source`` and return ``(network, report)``."""
    return loads_with_report(Path(source).read_text(encoding="utf-8"))

loads_with_report

loads_with_report(
    text: str,
) -> tuple[Network, ImportReport]

Read pandapower JSON text and return (network, report); see the module docstring.

Source code in src/mambo_power/io/pandapower_json.py
def loads_with_report(text: str) -> tuple[Network, ImportReport]:
    """Read pandapower JSON text and return ``(network, report)``; see the module docstring."""
    import pandapower as pp

    return _from_pandapower(pp.from_json_string(text))

dumps

dumps(net: Network, *, f_hz: float = DEFAULT_F_HZ) -> str

pandapower JSON text for net; the report is discarded.

Source code in src/mambo_power/io/pandapower_json.py
def dumps(net: Network, *, f_hz: float = DEFAULT_F_HZ) -> str:
    """pandapower JSON text for ``net``; the report is discarded."""
    return dumps_with_report(net, f_hz=f_hz)[0]

dump

dump(
    net: Network,
    target: str | PathLike[str],
    *,
    f_hz: float = DEFAULT_F_HZ,
) -> None

Write pandapower JSON for net to target; the report is discarded.

Source code in src/mambo_power/io/pandapower_json.py
def dump(net: Network, target: str | PathLike[str], *, f_hz: float = DEFAULT_F_HZ) -> None:
    """Write pandapower JSON for ``net`` to ``target``; the report is discarded."""
    Path(target).write_text(dumps(net, f_hz=f_hz), encoding="utf-8")

dumps_with_report

dumps_with_report(
    net: Network, *, f_hz: float = DEFAULT_F_HZ
) -> tuple[str, ExportReport]

pandapower JSON text for net and the ExportReport of what was dropped.

Source code in src/mambo_power/io/pandapower_json.py
def dumps_with_report(net: Network, *, f_hz: float = DEFAULT_F_HZ) -> tuple[str, ExportReport]:
    """pandapower JSON text for ``net`` and the :class:`ExportReport` of what was dropped."""
    import pandapower as pp

    pn, warnings = _to_pandapower(net, f_hz=f_hz)
    return pp.to_json(pn), ExportReport(warnings=warnings)