Source code for sdom.models.formulations_network

"""Network formulation (transportation model) for SDOM zonal mode.

This module implements the line topology, signed flow variable, directional
capacity constraints, and reporting expressions for the
``AreaTransportationModelNetwork`` formulation (PRD §5.6).

Locked design decisions (PRD §5.6, 2026-05-08):

- Topology lives on the **top-level model** (``model.L``, ``model.line_from``,
  ``model.line_to``, ``model.L_in[a]``, ``model.L_out[a]``); per-area blocks
  consume but do not own it.
- Capacity limits are imposed via explicit ``Constraint`` blocks (not ``Var``
  bounds) so per-direction congestion shadow prices are exposed in the dual.
- A single signed flow variable ``f[l,h] in Reals`` — no auxiliary binary,
  no ``f+/f-`` split; simultaneous bidirectional flow is impossible by
  construction of the bounds.
- ``Z^trans = 0`` placeholder (no transmission investment in this phase).

This module is purely additive: it does not wire itself into
:func:`sdom.models.optimization_main.initialize_model` (that is commit #9).
"""

from pyomo.environ import (
    Constraint,
    Expression,
    Param,
    Reals,
    Set,
    Var,
)


[docs] def add_network_sets(model, *, lines, line_from, line_to): """Create line set and per-area in/out indexed line sets. Parameters ---------- model : pyomo.environ.ConcreteModel Top-level model that already exposes ``model.A`` (Set of areas). lines : iterable Iterable of line identifiers. line_from : Mapping[Hashable, Hashable] Mapping ``{line_id: origin_area_id}`` with origin areas in ``model.A``. line_to : Mapping[Hashable, Hashable] Mapping ``{line_id: destination_area_id}`` with destination areas in ``model.A``. Returns ------- pyomo.environ.ConcreteModel The same ``model`` instance with new attributes attached: - ``model.L`` : ``Set`` of line ids. - ``model.line_from`` : ``Param(model.L, within=model.A)``. - ``model.line_to`` : ``Param(model.L, within=model.A)``. - ``model.L_in[a]`` : ``Set(model.A, ...)`` of lines with ``line_to[l] == a``. - ``model.L_out[a]`` : ``Set(model.A, ...)`` of lines with ``line_from[l] == a``. Notes ----- ``model.L_in`` and ``model.L_out`` are constructed from local Python dicts (rather than rules that introspect ``model.line_from`` / ``model.line_to``) to avoid Pyomo deferred-construction ordering issues. """ line_ids = list(lines) from_map = dict(line_from) to_map = dict(line_to) model.L = Set(initialize=line_ids, ordered=True) model.line_from = Param(model.L, within=model.A, initialize=from_map) model.line_to = Param(model.L, within=model.A, initialize=to_map) in_map = {a: [l for l in line_ids if to_map[l] == a] for a in model.A} out_map = {a: [l for l in line_ids if from_map[l] == a] for a in model.A} model.L_in = Set(model.A, initialize=lambda m, a: in_map[a], ordered=True) model.L_out = Set(model.A, initialize=lambda m, a: out_map[a], ordered=True) return model
[docs] def add_network_parameters(model, *, line_cap_ft, line_cap_tf): """Create directional line capacity parameters indexed by ``(L, h)``. Parameters ---------- model : pyomo.environ.ConcreteModel Model with ``model.L`` and ``model.h`` already constructed. line_cap_ft : Mapping[Tuple[Hashable, Hashable], float] Mapping ``{(line_id, hour): MW}`` for the from→to direction. line_cap_tf : Mapping[Tuple[Hashable, Hashable], float] Mapping ``{(line_id, hour): MW}`` for the to→from direction. Returns ------- pyomo.environ.ConcreteModel Same ``model`` with new attributes: - ``model.LineCap_FT`` : ``Param(model.L, model.h)`` (MW). - ``model.LineCap_TF`` : ``Param(model.L, model.h)`` (MW). """ model.LineCap_FT = Param( model.L, model.h, initialize=dict(line_cap_ft), mutable=False ) model.LineCap_TF = Param( model.L, model.h, initialize=dict(line_cap_tf), mutable=False ) return model
[docs] def add_network_variables(model): """Create the signed flow variable ``model.f[l,h]`` over ``Reals``. Parameters ---------- model : pyomo.environ.ConcreteModel Model with ``model.L`` and ``model.h`` already constructed. Returns ------- pyomo.environ.ConcreteModel Same ``model`` with new attribute ``model.f`` — a ``Var`` over ``model.L × model.h`` with domain ``Reals`` and **no bounds** set. Capacity limits are enforced by :func:`add_network_constraints`. """ model.f = Var(model.L, model.h, domain=Reals) return model
def _f_upper_rule(model, l, h): return model.f[l, h] <= model.LineCap_FT[l, h] def _f_lower_rule(model, l, h): return model.f[l, h] >= -model.LineCap_TF[l, h]
[docs] def add_network_constraints(model): """Create directional flow capacity constraints. Parameters ---------- model : pyomo.environ.ConcreteModel Model with ``model.f``, ``model.LineCap_FT`` and ``model.LineCap_TF``. Returns ------- pyomo.environ.ConcreteModel Same ``model`` with two new constraint blocks: - ``model.f_upper[l,h]`` : ``f[l,h] <= LineCap_FT[l,h]``. - ``model.f_lower[l,h]`` : ``f[l,h] >= -LineCap_TF[l,h]``. Notes ----- These are explicit ``Constraint`` blocks (rather than bounds on ``model.f``) so that congestion shadow prices are accessible per direction in the dual solution. """ model.f_upper = Constraint(model.L, model.h, rule=_f_upper_rule) model.f_lower = Constraint(model.L, model.h, rule=_f_lower_rule) return model
def _f_FT_rule(model, l, h): return model.f[l, h] def _f_TF_rule(model, l, h): return -model.f[l, h]
[docs] def add_network_expressions(model): """Create reporting-only signed flow expressions. Parameters ---------- model : pyomo.environ.ConcreteModel Model with ``model.f`` already constructed. Returns ------- pyomo.environ.ConcreteModel Same ``model`` with two ``Expression`` blocks: - ``model.f_FT[l,h]`` : signed scalar ``model.f[l,h]``. - ``model.f_TF[l,h]`` : signed scalar ``-model.f[l,h]``. Notes ----- These expressions are **reporting only**. The strict mathematical definition (PRD §5.6) is .. math:: f^{FT}_{l,h} = \\max(f_{l,h}, 0), \\qquad f^{TF}_{l,h} = \\max(-f_{l,h}, 0). The ``max`` is non-linear and cannot appear in a Pyomo ``Expression`` used inside an LP. Because at the optimum at most one direction carries positive flow (the other side of ``max`` is zero), it suffices to store the **signed** component here and let downstream reporting code clip to zero, e.g.:: from pyomo.environ import value ft = max(value(model.f_FT[l, h]), 0.0) tf = max(value(model.f_TF[l, h]), 0.0) These ``Expression`` blocks add no decision variables to the LP and are not referenced by any constraint or by the objective. """ model.f_FT = Expression(model.L, model.h, rule=_f_FT_rule) model.f_TF = Expression(model.L, model.h, rule=_f_TF_rule) return model
[docs] def network_transmission_cost_rule(model): """Return the transmission cost term ``Z^trans``. Parameters ---------- model : pyomo.environ.ConcreteModel Top-level model (unused; placeholder signature). Returns ------- int Always ``0`` in this phase — transmission investment is out of scope (PRD §5.6). """ return 0