Source code for monee.problem.utils
"""
Cross-cutting helpers used by load-shedding / dispatch problems.
"""
from __future__ import annotations
from monee.model.core import Var
from monee.model.grid import DEFAULT_GAS_HHV_KWH_PER_KG, KGPS_KWHPERKG_TO_MW, GasGrid
from monee.model.multi import (
CHPControlNode,
CHPHGControlNode,
GasToHeatControlNode,
GasToHeatHG,
GasToPower,
PowerToGas,
PowerToHeatControlNode,
PowerToHeatHG,
)
# Fallback HHV (kWh/kg) when a gas grid is missing ``higher_heating_value_kwh_per_kg``.
_HHV_DEFAULT = DEFAULT_GAS_HHV_KWH_PER_KG
[docs]
def line_loading_limit(branch_model, side: str, max_loading: float):
"""LP-writable line-loading constraint.
AC: ``loading_*_percent ≤ max``. MISOCP: emit
``current_pu_squared · scale² ≤ max²`` using the formulation-stashed
``_misocp_loading_*_scale_squared`` (the sqrt form is LP-incompatible).
"""
if side not in ("from", "to"):
raise ValueError(f"side must be 'from' or 'to', got {side!r}")
scale_attr = f"_misocp_loading_{side}_scale_squared"
if hasattr(branch_model, scale_attr):
scale_sq = getattr(branch_model, scale_attr)
return branch_model.current_pu_squared * scale_sq <= max_loading * max_loading
return getattr(branch_model, f"loading_{side}_pu") <= max_loading
[docs]
def cp_input_rated_mw(component):
"""Return ``(carrier, rated_input_mw)`` for a coupling-point component or
``None`` if *component* is not a coupling point.
``carrier`` is ``'gas'`` or ``'power'`` (the carrier the CP draws *from*).
``rated_input_mw`` is the nameplate input the CP consumes at regulation 1.0.
Used both by the resilience metric (to account CP curtailment) and by
``min_load_shedding`` (to treat CPs as additional loads in the objective).
"""
model = component.model
grid = getattr(component, "grid", None)
def _hhv():
gg = None
if isinstance(grid, dict):
gg = grid.get(GasGrid)
elif isinstance(grid, list):
gg = next((g for g in grid if isinstance(g, GasGrid)), None)
elif isinstance(grid, GasGrid):
gg = grid
return (
getattr(gg, "higher_heating_value_kwh_per_kg", _HHV_DEFAULT)
if gg
else _HHV_DEFAULT
)
def _scalar(x):
# Read the bound (or the value) off a Var; pass scalars through.
if isinstance(x, Var):
fallback = x.value if x.value is not None else 0.0
return x.max if x.max is not None else fallback
return x if x is not None else 0.0
# ---- Gas-input CPs ----------------------------------------------------
if isinstance(model, (CHPControlNode, CHPHGControlNode, GasToHeatControlNode)):
return (
"gas",
abs(_scalar(model.gas_mass_flow_kgs)) * KGPS_KWHPERKG_TO_MW * _hhv(),
)
if isinstance(model, GasToPower):
eff = max(getattr(model, "efficiency", 1.0), 1e-6)
# el_mw is stored as -p_mw_setpoint (scalar).
return ("gas", abs(_scalar(model.el_mw)) / eff)
if isinstance(model, GasToHeatHG):
eff = max(getattr(model, "efficiency", 1.0), 1e-6)
return ("gas", abs(_scalar(model.heat_mw)) / eff)
# ---- Power-input CPs --------------------------------------------------
if isinstance(model, PowerToHeatControlNode):
return ("power", abs(_scalar(model.el_mw)))
if isinstance(model, PowerToHeatHG):
return ("power", abs(_scalar(model.load_p_mw)))
if isinstance(model, PowerToGas):
eff = max(getattr(model, "efficiency", 1.0), 1e-6)
# gas_mass_flow_kgs stored as -mass_flow_setpoint_kgs; rated input power = output / η.
return (
"power",
abs(_scalar(model.gas_mass_flow_kgs)) * KGPS_KWHPERKG_TO_MW * _hhv() / eff,
)
return None