Source code for g2elin_api.presets

"""The set of networks the API exposes.

Mirrors ``g2elin_core.network.presets`` — the API layer doesn't add any new
networks, it just exposes them over HTTP. A network editor (accepting
arbitrary user-built ``Network`` instances) is added alongside this
(``/api/network/*``, see ``network_routes.py``); this module still owns the
fixed, named preset catalog those endpoints' "start from a preset" flow
clones from.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Callable

from g2elin_core.network.presets import (
    cigre_interconnected_1sm_1gfm_1gfl,
    cigre_islanded_1sm_1gfm_1gfl,
    cigre_islanded_1sm_2gfm_1gfl,
    cigre_islanded_1sm_3gfm_1gfl,
    cigre_islanded_2sm_2gfm_2gfl,
    gfl_smib,
    gfl_smsm,
    gfm_smib,
    gfm_smsm,
    sm_smib,
    sm_smsm,
    wscc9_1gfm_2gfl,
    wscc9_1sm_1gfm_1gfl,
    wscc9_1sm_2gfl,
    wscc9_1sm_2gfm,
    wscc9_2sm_1gfl,
    wscc9_2sm_1gfm,
    wscc9_3gfm,
    ieee14,
    ieee39,
    ieee118,
    kundur_two_area,
    kundur_two_area_classic,
    wscc9_3sm,
)
from g2elin_core.network.schema import Network


[docs] @dataclass(frozen=True) class PresetInfo: id: str name: str description: str build: Callable[[], Network]
# (id, name, description, build) -- one row per registered preset. Grouped # by family (WSCC / CIGRE islanded / SMIB) to match the web UI's <optgroup> # grouping in loadPresets(). _PRESET_SPECS: list[tuple[str, str, str, Callable[[], Network]]] = [ ("wscc9_3sm", "WSCC 9-bus (3 SM)", "Classic WSCC 9-bus system, 3 synchronous machines. From WSCC_raw.m + script_WSCC.m.", wscc9_3sm), ("wscc9_2sm_1gfl", "WSCC 9-bus (2 SM + 1 GFL)", "WSCC 9-bus, 2 synchronous machines + 1 grid-following converter. script_WSCC.m case 'WSCC_2SM_1GFL'.", wscc9_2sm_1gfl), ("wscc9_1sm_2gfl", "WSCC 9-bus (1 SM + 2 GFL)", "WSCC 9-bus, 1 synchronous machine + 2 grid-following converters. script_WSCC.m case 'WSCC_1SM_2GFL'.", wscc9_1sm_2gfl), ("wscc9_1sm_1gfm_1gfl", "WSCC 9-bus (1 SM + 1 GFM + 1 GFL)", "WSCC 9-bus, 1 synchronous machine + 1 grid-forming (Droop) + 1 grid-following converter. " "script_WSCC.m case 'WSCC_1SM_1GFM_1GFL'.", wscc9_1sm_1gfm_1gfl), ("wscc9_1sm_2gfm", "WSCC 9-bus (1 SM + 2 GFM)", "WSCC 9-bus, 1 synchronous machine + 2 grid-forming (Droop) converters. script_WSCC.m case 'WSCC_1SM_2GFM'.", wscc9_1sm_2gfm), ("wscc9_2sm_1gfm", "WSCC 9-bus (2 SM + 1 GFM)", "WSCC 9-bus, 2 synchronous machines + 1 grid-forming (Droop) converter. script_WSCC.m case 'WSCC_2SM_1GFM'.", wscc9_2sm_1gfm), ("wscc9_1gfm_2gfl", "WSCC 9-bus (1 GFM + 2 GFL)", "WSCC 9-bus, 1 grid-forming (Droop, slack) + 2 grid-following converters. Power flow only -- a GFM " "slack isn't wired up for modal/EMT yet (see the preset's own docstring).", wscc9_1gfm_2gfl), ("wscc9_3gfm", "WSCC 9-bus (3 GFM)", "WSCC 9-bus, 3 grid-forming (Droop) converters, one as slack. Power flow only -- see wscc9_1gfm_2gfl.", wscc9_3gfm), ("cigre_islanded_1sm_1gfm_1gfl", "CIGRE islanded (1 SM + 1 GFM + 1 GFL)", "CIGRE MV benchmark feeder, islanded, 1 SM + 1 GFM + 1 GFL. " "CIGRE_raw.m + CIGRE_Islanded.m case 'CIGRE_Islanded_1SM_1GFM_1GFL'.", cigre_islanded_1sm_1gfm_1gfl), ("cigre_islanded", "CIGRE islanded (1 SM + 2 GFM + 1 GFL)", "CIGRE MV benchmark feeder, islanded, mixed SM/GFM/GFL. " "From CIGRE_raw.m + CIGRE/script_CIGRE_Islanded.m.", cigre_islanded_1sm_2gfm_1gfl), ("cigre_islanded_1sm_3gfm_1gfl", "CIGRE islanded (1 SM + 3 GFM + 1 GFL)", "CIGRE MV benchmark feeder, islanded, 1 SM + 3 GFM + 1 GFL. " "CIGRE_raw.m + CIGRE_Islanded.m case 'CIGRE_Islanded_1SM_3GFM_1GFL'.", cigre_islanded_1sm_3gfm_1gfl), ("cigre_islanded_2sm_2gfm_2gfl", "CIGRE islanded (2 SM + 2 GFM + 2 GFL)", "CIGRE MV benchmark feeder, islanded, 2 SM + 2 GFM + 2 GFL. " "CIGRE_raw.m + CIGRE_Islanded.m case 'CIGRE_Islanded_2SM_2GFM_2GFL'.", cigre_islanded_2sm_2gfm_2gfl), ("cigre_interconnected_1sm_1gfm_1gfl", "CIGRE interconnected (1 SM + 1 GFM + 1 GFL)", "CIGRE MV benchmark feeder connected to the upstream grid (an infinite bus behind a transformer " "at node 1, identical to the unit transformers), 1 SM + 1 GFM + 1 GFL. Topology/loads from CIGRE_raw.m (S0=1); " "grid attachment reconstructed -- preset_networks.m names the case but never defines it.", cigre_interconnected_1sm_1gfm_1gfl), ("sm_smib", "SMIB (SM)", "Single synchronous machine against an infinite bus. Topology/line/load from " "SMIB_raw.m; DER attachment reconstructed (the driving MATLAB script was never " "finished) -- see network.presets.sm_smib's docstring.", sm_smib), ("gfm_smib", "SMIB (GFM, Droop)", "Single grid-forming converter (Droop) against an infinite bus. See sm_smib for provenance.", gfm_smib), ("gfl_smib", "SMIB (GFL)", "Single grid-following converter against an infinite bus. See sm_smib for provenance.", gfl_smib), ("sm_smsm", "SMSM (SM vs SM)", "Single synchronous machine against a synchronous machine acting as the grid (slack), on the " "SMIB line and load. Reconstruction, like the SMIB presets.", sm_smsm), ("gfm_smsm", "SMSM (GFM vs SM)", "Single grid-forming converter (Droop) against a synchronous machine acting as the grid. See sm_smsm.", gfm_smsm), ("gfl_smsm", "SMSM (GFL vs SM)", "Single grid-following converter against a synchronous machine acting as the grid. The GFL absorbs " "0.9 MVAr of the line charging -- at Q = 0 the under-excited grid machine is unstable (see gfl_smsm).", gfl_smsm), ("kundur_two_area", "Kundur two-area (11-bus, 4 SM)", "Kundur's two-area system (Power System Stability and Control, Example 12.6): two areas of two " "900 MVA machines, a weak 220 km double-circuit tie carrying 400 MW, and the textbook case for " "inter-area oscillations. With this tool's own controls (AVR, PSS and governor).", kundur_two_area), ("kundur_two_area_classic", "Kundur two-area (book's controls)", "The same system under the book's own assumptions -- fast static exciter, no PSS, constant " "mechanical torque -- where the inter-area mode near 0.6 Hz comes out negatively damped. The case " "the example exists for.", kundur_two_area_classic), ("ieee14", "IEEE 14-bus", "IEEE 14-bus test case, converted from pandapower. Power flow matches the published case; the " "machines carry generic dynamic data, since a power-flow case has none.", ieee14), ("ieee39", "IEEE 39-bus (New England)", "IEEE 39-bus 'New England' case, 10 machines -- the standard mid-size transient-stability network. " "Power flow matches the published case; the machine data is generic, so its modes are indicative only " "(one local mode is negatively damped with it).", ieee39), ("ieee118", "IEEE 118-bus", "IEEE 118-bus case, 54 machines. Power flow is quick; modal analysis is slow and EMT impractical at " "this size. Machine data is generic.", ieee118), ] PRESETS: dict[str, PresetInfo] = { id_: PresetInfo(id=id_, name=name, description=description, build=build) for id_, name, description, build in _PRESET_SPECS }
[docs] def get_preset(preset_id: str) -> PresetInfo: try: return PRESETS[preset_id] except KeyError: raise KeyError(f"unknown preset {preset_id!r}; known presets: {sorted(PRESETS)}") from None