"""
Electricity-carrier islanding mode and grid-forming generator model.
"""
from __future__ import annotations
from monee.model.child import NoVarChildModel
from monee.model.core import Const, Var, model
from monee.model.grid import PowerGrid
from monee.model.network import Network
from monee.model.phys.islanding import (
angle_lower_bound_energized,
angle_upper_bound_energized,
source_reference_angle,
)
from .core import GridFormingMixin, IslandingMode
# ---------------------------------------------------------------------------
# GridFormingGenerator — grid-forming child model for electricity
# ---------------------------------------------------------------------------
# Angle is NOT pinned here — the islanding formulation does it.
# ---------------------------------------------------------------------------
# ElectricityIslandingMode
# ---------------------------------------------------------------------------
[docs]
class ElectricityIslandingMode(IslandingMode):
"""
Islanding configuration for the electricity carrier.
Adds:
* Single-commodity connectivity-flow constraints (via the base class).
* ``source_reference_angle`` at every grid-forming bus → θ = 0.
* ``angle_{upper,lower}_bound_energized`` at every regular bus →
forces θ = 0 when ``e_i = 0`` (de-energised).
Parameters
----------
angle_bound : float
Maximum absolute angle (radians) for energised buses. Default ≈ π.
big_m_conn : int
Big-M for connectivity-flow arc capacity. Must be ≥ number of carrier nodes.
"""
carrier_grid_type = PowerGrid
var_prefix = "el"
def __init__(self, angle_bound: float = 3.15, big_m_conn: int = 200) -> None:
self.angle_bound = angle_bound
self.big_m_conn = big_m_conn
[docs]
def prepare(self, network: Network) -> None:
"""Phase 1 — add electricity islanding Var placeholders."""
for node in network.nodes:
if isinstance(node.grid, PowerGrid) and node.active:
node.model.e_el = Var(1, min=0, max=1, integer=True, name="e_el")
is_gf = any(
isinstance(c.model, GridFormingMixin) and c.active
for c in network.childs_by_ids(node.child_ids)
)
if is_gf:
node.model.c_src_el = Var(1, min=0, name="c_src_el")
for branch in network.branches:
if isinstance(branch.grid, PowerGrid) and branch.active:
branch.model.c_el_fwd = Var(0, min=0, name="c_el_fwd")
branch.model.c_el_rev = Var(0, min=0, name="c_el_rev")
[docs]
def add_physical_constraints(
self, network, gf_nodes, regular_nodes, e_vars
) -> list:
"""
Electricity-specific physical constraint equations (returned as a list).
* Grid-forming nodes: angle reference = 0 (``source_reference_angle`` from dc.py).
* Regular nodes: angle bounds conditional on energisation.
"""
eqs = []
for node in gf_nodes:
eqs.append(source_reference_angle(node.model.va_radians))
for node in regular_nodes:
e = e_vars[node.id]
eqs.append(
angle_upper_bound_energized(node.model.va_radians, self.angle_bound, e)
)
eqs.append(
angle_lower_bound_energized(node.model.va_radians, self.angle_bound, e)
)
return eqs