Skip to content

Linearised unit commitment in PyPSA#

This page states the spec of the model that n.optimize(linearized_unit_commitment=True) builds in PyPSA. The file examples/variants/pypsa_linearized_uc.yaml states only the change to a base spec (a patch), and its base is PyPSA in one file. override lays the patch over the base:

import mathspec as ms

spec = ms.override('examples/pypsa.yaml', ['examples/variants/pypsa_linearized_uc.yaml'])

spec is the base with the patch laid over it.

The patch relaxes the status (variables.py:91-92), the start (:142-143) and the stop (:193-194) of each committable unit. PyPSA refuses a modular committable unit under the keyword (optimize.py:841-842). The other rows of the integer run have their scenarios, periods and active conditions.

Five steps of the PyPSA ladder (rungs), numbered 12, 44, 47, 67 and 74, run under the keyword. Four of them start from a shared two-bus network (the spine), in spine.py, with the additions of the rung script. Rung 74 builds a network of its own.

Rung 12 — linearised unit commitment#

PyPSA status note
Generator-status, -start_up, -shut_down done shares in [0, 1]
Generator-com-p-before done where start and stop cost the same in every snapshot and scenario, by a count over snapshot, then over scenario
Generator-com-p-current done
Generator-com-partly-start-up done
Generator-com-partly-shut-down done

✔ pypsa 1.4.0.post1.dev62+g3625c8168 solves this rung's network at objective 7775.0, 131 rows.

The network, as PyPSA code

rung_12_linearized_uc.py

# SPDX-FileCopyrightText: mathspec Contributors
#
# SPDX-License-Identifier: MIT

"""Rung 12: linearized unit commitment — the status a share in [0, 1], stated by the patch `variants/pypsa_linearized_uc.yaml`."""

from __future__ import annotations

import spine

PATCH = 'variants/pypsa_linearized_uc.yaml'
OPTIMIZE = {'linearized_unit_commitment': True}


def build():
    """The spine plus two committable units, one whose start and stop cost the same, so PyPSA tightens its relaxation."""
    n = spine.build()
    n.add(
        'Generator',
        'uc12',
        bus='north',
        committable=True,
        p_nom=50,
        marginal_cost=5,
        p_min_pu=0.4,
        min_up_time=3,
        min_down_time=2,
        up_time_before=1,
        ramp_limit_up=0.5,
        ramp_limit_down=0.5,
        ramp_limit_start_up=0.6,
        ramp_limit_shut_down=0.6,
        start_up_cost=100,
        shut_down_cost=100,
        stand_by_cost=5,
    )
    n.add(
        'Generator',
        'cold12',
        bus='south',
        committable=True,
        p_nom=30,
        marginal_cost=60,
        p_min_pu=0.3,
        min_up_time=2,
        min_down_time=1,
        up_time_before=0,
        ramp_limit_up=0.5,
        ramp_limit_down=0.5,
        ramp_limit_start_up=0.7,
        ramp_limit_shut_down=0.7,
        start_up_cost=80,
        shut_down_cost=40,
    )
    n.add('Load', 'swing12', bus='north', p_set=[25, 45, 45, 10])
    return n

Rung 44 — relaxed integer commitment rows#

Under the keyword, PyPSA builds the commitment rows of the integer run and adds the four tightening rows (constraints.py:684-792). The keyword relaxes only the status, start and stop to shares in [0, 1] (variables.py:91-92). The patch leaves each of these rows to PyPSA in one file, and the rung's network contains them under the keyword:

  • A unit still serving the down time it brought in stays off, through its down-time row (constraints.py:663-683).
  • PyPSA builds a ramp row where a unit has a ramp limit, or only a start-up or shut-down ramp (constraints.py:1116-1125). A missing limit is the full build.
  • A maintainable unit is off for its events. The maintenance start stays a binary under the keyword (variables.py:239-264). The product of status and maintenance enters the commitment rows through the maint-status rows (constraints.py:510-543). A maintainable unit that is not committable loses the same share of its fixed rows (constraints.py:153-163).

The rung adds five cheap units under a load that swings. A start costs more than a stop, so PyPSA builds no tightening rows for them, and each row is tight at the optimum. PyPSA solves the rung and its variants to these objectives (#977):

network objective
the rung as built 7400.0
without the down time brought in 6540.0
without the start-up ramp 7164.0
the missing start-up and shut-down ramps as 0 8060.0
the committable unit not maintainable 7310.0
the fixed unit not maintainable 7100.0
PyPSA status note
Generator-com-down-time with the down time brought in done
Generator-p-ramp_limit_up, -down for a start-up or shut-down ramp alone, and a missing limit done Generator_ramp_up_rate and the other three rates are 1 where the limit is missing
Generator-maint-*, Generator-maint-status-* done fixed units
Generator-com-p-lower, -upper, Generator-fix-p-lower, -upper in maintenance done

✔ pypsa 1.4.0.post1.dev62+g3625c8168 solves this rung's network at objective 7400.0, 190 rows.

The network, as PyPSA code

rung_44_linearized_commitment.py

# SPDX-FileCopyrightText: mathspec Contributors
#
# SPDX-License-Identifier: MIT

"""Rung 44: linearized commitment with the rows the integer file has — a unit that must stay down, ramps read at the full build where a limit is missing, and maintenance."""

from __future__ import annotations

import spine

PATCH = 'variants/pypsa_linearized_uc.yaml'
OPTIMIZE = {'linearized_unit_commitment': True}

#: a start costs more than a stop, so PyPSA does not tighten these units and the rung isolates the rows it adds
UNTIGHTENED = {'committable': True, 'up_time_before': 0, 'start_up_cost': 1}


def build():
    """The spine plus five cheap units on a north bus with a swinging load: each carries one of the rows under review."""
    n = spine.build()
    n.add(
        'Generator',
        'down44',
        bus='north',
        p_nom=40,
        marginal_cost=2,
        min_down_time=3,
        down_time_before=1,
        **UNTIGHTENED,
    )
    n.add('Generator', 'pulse44', bus='north', p_nom=40, marginal_cost=3, ramp_limit_start_up=0.4, **UNTIGHTENED)
    n.add(
        'Generator',
        'ramp44',
        bus='north',
        p_nom=40,
        marginal_cost=4,
        ramp_limit_up=0.5,
        ramp_limit_down=0.5,
        **UNTIGHTENED,
    )
    n.add(
        'Generator',
        'maint44',
        bus='north',
        p_nom=40,
        p_min_pu=0.3,
        marginal_cost=5,
        maintainable=True,
        maintenance_duration=2,
        **UNTIGHTENED,
    )
    n.add('Generator', 'fixed44', bus='north', p_nom=20, marginal_cost=1, maintainable=True, maintenance_duration=1)
    n.add('Load', 'swing44', bus='north', p_set=[40, 120, 160, 60])
    return n

Rung 47 — ramps and signs under the keyword#

Under the keyword, the rung's network contains five kinds of row, each on fixed builds. The patch states the first kind, the four tightening rows. PyPSA in one file states the other four:

  • In the four tightening rows, a missing ramp limit is the full build (constraints.py:410-415). The rows contain Generator_ramp_up_rate and the other three rates.
  • A ramp limit has a value per snapshot, and the ramp row uses its value at the later of the two snapshots (constraints.py:1110-1111). A row is absent at a snapshot where neither the limit nor the start-up or shut-down ramp has a value (constraints.py:1116-1117, 1122, 1140).
  • A unit that came in running with a p_init ramps from it into the first snapshot (constraints.py:1149-1151, 1105-1109). Where p_init has no value, the unit has no ramp row at the first snapshot.
  • A unit that is not committable has ramp rows with its status fixed at 1 (constraints.py:1141-1147), and comes in running whatever its up_time_before (constraints.py:323).
  • Each generator's and load's part enters the bus balance with its component's sign (constraints.py:1480-1481, :1592).

The rung adds a relax bus with a tightened unit that has only start-up and shut-down ramps. One committable unit has a ramp limit that lifts at the third snapshot, and a dear committable unit came in running at p_init=40. A fixed unit with ramp limits, a unit of sign -1, a load of sign 1 and a dear backup complete the bus. The row of each unit is tight at the optimum, and PyPSA solves the rung and its variants to these objectives (#977):

network objective
the rung as built 12862.5
the missing limits as 0 in the two partly rows 36126.875
the ramp limit at 0.25 in every snapshot 14554.75
without the p_init 8437.5
without the fixed unit's ramp limits 12800.0
the unit's sign at 1 12592.5
the load's sign at -1 13887.5
PyPSA status note
Generator-com-p-before, -current, -partly-start-up, -partly-shut-down with a missing ramp limit done the four rates are 1 where the limit is missing
Generator-p-ramp_limit_up, -down per snapshot done Generator_ramp_limit_up and -down over [scenario, snapshot, generator]
Generator-p-ramp_limit_up, -down at the first snapshot from p_init done Generator_previous_p opens at Generator_status_initial * Generator_p_init
Generator-p-ramp_limit_up, -down for a unit that is not committable done Generator_ramp_up_allowance and -down have the status fixed at 1
Bus-nodal_balance with sign done Generator_sign and Load_sign

✔ pypsa 1.4.0.post1.dev62+g3625c8168 solves this rung's network at objective 12862.5, 183 rows.

The network, as PyPSA code

rung_47_linearized_ramps.py

# SPDX-FileCopyrightText: mathspec Contributors
#
# SPDX-License-Identifier: MIT

"""Rung 47: the relaxed file's ramps and signs — tightening at the full build, a ramp limit per snapshot, a `p_init`, a unit that is not committable, and a sign."""

from __future__ import annotations

import math

import spine

PATCH = 'variants/pypsa_linearized_uc.yaml'
OPTIMIZE = {'linearized_unit_commitment': True}


def build():
    """The spine plus a relax bus: five units that each carry one of the rows under review, a feeding load and a dear backup."""
    n = spine.build()
    n.add('Bus', 'relax')
    n.add(
        'Generator',
        'tight47',
        bus='relax',
        committable=True,
        p_nom=50,
        p_min_pu=0.2,
        marginal_cost=2,
        up_time_before=0,
        ramp_limit_start_up=0.5,
        ramp_limit_shut_down=0.5,
        start_up_cost=10,
        shut_down_cost=10,
    )
    n.add(
        'Generator',
        'steep47',
        bus='relax',
        committable=True,
        p_nom=40,
        marginal_cost=3,
        start_up_cost=1,
        ramp_limit_up=[0.25, 0.25, math.nan, 0.25],
        ramp_limit_down=[0.25, 0.25, 0.25, math.nan],
        ramp_limit_start_up=0.25,
    )
    n.add(
        'Generator',
        'warm47',
        bus='relax',
        committable=True,
        p_nom=40,
        marginal_cost=50,
        start_up_cost=1,
        p_init=40,
        ramp_limit_down=0.25,
        ramp_limit_shut_down=0.5,
    )
    n.add('Generator', 'fixed47', bus='relax', p_nom=60, marginal_cost=1, ramp_limit_up=0.25, ramp_limit_down=0.25)
    n.add('Generator', 'sink47', bus='relax', p_nom=20, p_min_pu=0.5, sign=-1)
    n.add('Generator', 'backup47', bus='relax', p_nom=300, marginal_cost=500)
    n.add('Load', 'feed47', bus='relax', p_set=10, sign=1)
    n.add('Load', 'swing47', bus='relax', p_set=[60, 60, 140, 40])
    return n

The keyword relaxes Link and Process as it relaxes Generator (optimize.py:876-880), and PyPSA builds the four tightening rows for each (constraints.py:684-792). The rung adds an east bus that three committable units serve from the north. They are a link and a process, each with a fixed build and the same start and stop cost, and an extendable link. PyPSA does not tighten the extendable link, because the tightening rows contain the build as data (constraints.py:702-704). A dear backup on the east bus balances the bus.

Each tightening row is tight at the optimum, and PyPSA solves to 47777.33. When the stop of the link costs 1e-7 more than its start, PyPSA does not tighten the link, and it solves to 46554.0. When the process is left untightened in the same way, PyPSA solves to 47465.70. Without the keyword, it solves to 54013.13.

PyPSA status note
Link-status, -start_up, -shut_down done shares in [0, 1]
Process-status, -start_up, -shut_down done shares in [0, 1]
Link-com-p-before, -current, -partly-start-up, -partly-shut-down done a fixed build only
Process-com-p-before, -current, -partly-start-up, -partly-shut-down done a fixed build only
Link-com-ext-p-* under the keyword done no tightening for an extendable build

✔ pypsa 1.4.0.post1.dev62+g3625c8168 solves this rung's network at objective 47777.33333333333, 194 rows.

The network, as PyPSA code

rung_67_linearized_link_process.py

# SPDX-FileCopyrightText: mathspec Contributors
#
# SPDX-License-Identifier: MIT

"""Rung 67: linearized commitment of a link and a process — both tightened where start and stop cost the same, and an extendable link the tightening skips."""

from __future__ import annotations

import spine

PATCH = 'variants/pypsa_linearized_uc.yaml'
OPTIMIZE = {'linearized_unit_commitment': True}


def build():
    """The spine plus an east bus that committable links and a process serve from the north, and a dear backup there."""
    n = spine.build()
    n.add('Bus', 'east')
    n.add(
        'Link',
        'tie67',
        bus0='north',
        bus1='east',
        committable=True,
        p_nom=60,
        p_min_pu=0.3,
        marginal_cost=4,
        min_up_time=2,
        up_time_before=0,
        ramp_limit_up=0.4,
        ramp_limit_down=0.4,
        ramp_limit_start_up=0.5,
        ramp_limit_shut_down=0.5,
        start_up_cost=30,
        shut_down_cost=30,
    )
    n.add(
        'Process',
        'conv67',
        bus0='north',
        bus1='east',
        rate0=-1.25,
        committable=True,
        p_nom=50,
        p_min_pu=0.3,
        marginal_cost=3,
        min_up_time=2,
        up_time_before=0,
        ramp_limit_up=0.4,
        ramp_limit_down=0.4,
        ramp_limit_start_up=0.5,
        ramp_limit_shut_down=0.5,
        start_up_cost=20,
        shut_down_cost=20,
    )
    n.add(
        'Link',
        'ext67',
        bus0='north',
        bus1='east',
        committable=True,
        p_nom_extendable=True,
        p_nom_max=30,
        capital_cost=5,
        p_min_pu=0.2,
        marginal_cost=2,
        up_time_before=0,
        start_up_cost=10,
        shut_down_cost=10,
    )
    n.add('Generator', 'backup67', bus='east', p_nom=200, marginal_cost=400)
    n.add('Load', 'east_load', bus='east', p_set=[10, 110, 120, 20])
    return n

Rung 74 — a relaxed unit that opens in a later period#

Under the keyword and multi_investment_periods, a committable unit built in the later period has no output or status before its first snapshot. PyPSA fills both with zero there (constraints.py:716), so the four tightening rows open on a unit that was off. The patch reads Generator_previous_p, which is zero at the first snapshot a unit is active in past the first of the horizon.

The rung's unit has equal start and stop costs, so PyPSA tightens it. It opens in 2030 and its start-up ramp of 30 is tight at the period's first snapshot. PyPSA solves to 24825.2778. With the unit built in 2020, PyPSA solves to 3462.5. With a start that costs more than a stop, PyPSA does not tighten the unit, and it solves to 23226.3333 (#977).

PyPSA status note
Generator-com-p-before, -partly-start-up, -partly-shut-down at a unit's first snapshot past the horizon's first done Generator_previous_p and Generator_previous_status are zero there

✔ pypsa 1.4.0.post1.dev62+g3625c8168 solves this rung's network at objective 24825.277777777777, 74 rows.

The network, as PyPSA code

rung_74_linearized_late_opening.py

# SPDX-FileCopyrightText: mathspec Contributors
#
# SPDX-License-Identifier: MIT

"""Rung 74: a relaxed committable unit built in the later period — its tightening rows open on a zero output and a zero status at its first snapshot."""

from __future__ import annotations

from datetime import datetime

import pandas as pd

PATCH = 'variants/pypsa_linearized_uc.yaml'
OPTIMIZE = {'linearized_unit_commitment': True, 'multi_investment_periods': True}


def build():
    """A whole network, not the spine: eight snapshots over two periods, a cheap tightened unit built in 2030, and a dear peaker."""
    import pypsa

    n = pypsa.Network()
    n.snapshots = pd.MultiIndex.from_tuples(
        [(2020, datetime(2020, 1, 1, t)) for t in range(4)] + [(2030, datetime(2030, 1, 1, t)) for t in range(4)]
    )
    n.investment_periods = [2020, 2030]
    n.investment_period_weightings['objective'] = [1.0, 0.5]
    n.investment_period_weightings['years'] = [10.0, 10.0]
    n.snapshot_weightings['objective'] = [2.0, 1.5, 2.5, 2.0, 2.0, 1.5, 2.5, 2.0]
    n.add('Bus', 'hub')
    n.add('Generator', 'peak74', bus='hub', p_nom=300, marginal_cost=50)
    n.add(
        'Generator',
        'late74',
        bus='hub',
        committable=True,
        p_nom=60,
        p_min_pu=0.2,
        marginal_cost=2,
        start_up_cost=10,
        shut_down_cost=10,
        ramp_limit_up=0.25,
        ramp_limit_down=0.25,
        ramp_limit_start_up=0.5,
        ramp_limit_shut_down=0.5,
        build_year=2030,
        lifetime=30,
    )
    n.add('Load', 'hub_load74', bus='hub', p_set=[40, 60, 70, 50, 50, 60, 55, 20])
    return n

The file#

A committable generator, link or process has a status, starts and stops that are shares in [0, 1]. The patch states n.optimize(linearized_unit_commitment=True) over examples/pypsa.yaml, and the shares relax that file's integers (variables.py:91-92). Four rows PyPSA adds only under the keyword tighten the relaxation (constraints.py:684-792). PyPSA builds them only for a unit with a fixed build whose start and stop cost the same in every snapshot and every scenario. The patch states an assumption of no modular committable unit, which PyPSA refuses under the keyword and a tool refuses when data is attached. Every other row is the integer run's.

Generator-status#

Generator_status

Generator_status:
  description: >-
    `Generator-status` — how much of a committable unit is on, a share in [0, 1]
    rather than an integer: the relaxation the keyword solves
    (`variables.py:91-92`)
  domain: null
  bounds:
    upper: 1
\[ 0 \le u_{g,\xi,t} \le 1 \qquad \forall\, g \in \mathcal{G},\ \xi \in \Xi,\ t \in \mathcal{T} \,:\, \mathrm{on}^{\mathrm{com}}_{g,t} \]

Generator-start_up#

Generator_start_up

Generator_start_up:
  description: "`Generator-start_up` — how much of a committable unit turns on this snapshot, a share in [0, 1] (`variables.py:142-143`)"
  domain: null
  bounds:
    upper: 1
\[ 0 \le \mathit{up}_{g,\xi,t} \le 1 \qquad \forall\, g \in \mathcal{G},\ \xi \in \Xi,\ t \in \mathcal{T} \,:\, \mathrm{on}^{\mathrm{com}}_{g,t} \]

Generator-shut_down#

Generator_shut_down

Generator_shut_down:
  description: "`Generator-shut_down` — how much of a committable unit turns off this snapshot, a share in [0, 1] (`variables.py:193-194`)"
  domain: null
  bounds:
    upper: 1
\[ 0 \le \mathit{dn}_{g,\xi,t} \le 1 \qquad \forall\, g \in \mathcal{G},\ \xi \in \Xi,\ t \in \mathcal{T} \,:\, \mathrm{on}^{\mathrm{com}}_{g,t} \]

Link_status

Link_status:
  description: >-
    `Link-status` — how much of a committable link is on, a share in [0, 1]
    rather than an integer: the relaxation the keyword solves
    (`variables.py:91-92`)
  domain: null
  bounds:
    upper: 1
\[ 0 \le u^{f}_{l,\xi,t} \le 1 \qquad \forall\, l \in \mathcal{L},\ \xi \in \Xi,\ t \in \mathcal{T} \,:\, \mathrm{on}^{f,\mathrm{com}}_{l,t} \]

Link_start_up

Link_start_up:
  description: "`Link-start_up` — how much of a committable link turns on this snapshot, a share in [0, 1] (`variables.py:142-143`)"
  domain: null
  bounds:
    upper: 1
\[ 0 \le \mathit{up}^{f}_{l,\xi,t} \le 1 \qquad \forall\, l \in \mathcal{L},\ \xi \in \Xi,\ t \in \mathcal{T} \,:\, \mathrm{on}^{f,\mathrm{com}}_{l,t} \]

Link_shut_down

Link_shut_down:
  description: "`Link-shut_down` — how much of a committable link turns off this snapshot, a share in [0, 1] (`variables.py:193-194`)"
  domain: null
  bounds:
    upper: 1
\[ 0 \le \mathit{dn}^{f}_{l,\xi,t} \le 1 \qquad \forall\, l \in \mathcal{L},\ \xi \in \Xi,\ t \in \mathcal{T} \,:\, \mathrm{on}^{f,\mathrm{com}}_{l,t} \]

Process-status#

Process_status

Process_status:
  description: >-
    `Process-status` — how much of a committable process is on, a share in [0, 1]
    rather than an integer: the relaxation the keyword solves
    (`variables.py:91-92`)
  domain: null
  bounds:
    upper: 1
\[ 0 \le u^{z}_{j,\xi,t} \le 1 \qquad \forall\, j \in \mathcal{J},\ \xi \in \Xi,\ t \in \mathcal{T} \,:\, \mathrm{on}^{z,\mathrm{com}}_{j,t} \]

Process-start_up#

Process_start_up

Process_start_up:
  description: "`Process-start_up` — how much of a committable process turns on this snapshot, a share in [0, 1] (`variables.py:142-143`)"
  domain: null
  bounds:
    upper: 1
\[ 0 \le \mathit{up}^{z}_{j,\xi,t} \le 1 \qquad \forall\, j \in \mathcal{J},\ \xi \in \Xi,\ t \in \mathcal{T} \,:\, \mathrm{on}^{z,\mathrm{com}}_{j,t} \]

Process-shut_down#

Process_shut_down

Process_shut_down:
  description: "`Process-shut_down` — how much of a committable process turns off this snapshot, a share in [0, 1] (`variables.py:193-194`)"
  domain: null
  bounds:
    upper: 1
\[ 0 \le \mathit{dn}^{z}_{j,\xi,t} \le 1 \qquad \forall\, j \in \mathcal{J},\ \xi \in \Xi,\ t \in \mathcal{T} \,:\, \mathrm{on}^{z,\mathrm{com}}_{j,t} \]

Generator-com-p-before#

Generator_com_p_before

Generator_com_p_before:
  description: >-
    `Generator-com-p-before` — the output a unit had entering this snapshot
    fits the share of it still on (`constraints.py:729-742`). The share it
    is shutting down counts only at the shut-down ramp. At the first snapshot,
    the unit carries in the state and output it brought into the horizon,
    and has no row where it came in running without an initial output
    (`constraints.py:712-719`)
  dims: [scenario, snapshot, generator]
  where: Generator_committable AND NOT Generator_p_nom_extendable AND count(count(Generator_start_up_cost != Generator_shut_down_cost, over=snapshot) > 0, over=scenario) == 0 AND Generator_active AND (position(snapshot) > 0 OR Generator_status_initial == 0 OR Generator_p_init)
  expression: >-
    Generator_previous_p
    - Generator_shut_down_rate * Generator_p_nom * Generator_previous_status
    - (Generator_p_max_pu * Generator_p_nom - Generator_shut_down_rate * Generator_p_nom)
    * (Generator_status - Generator_start_up) <= 0
\[ \overleftarrow{p}_{g,\xi,t} - \widetilde{\mathrm{rd}}^{\mathrm{dn}}_{g,\xi} \cdot \mathrm{p}^{\mathrm{nom}}_{g,\xi} \cdot \overleftarrow{u}_{g,\xi,t} - \left( \overline{\mathrm{p}}_{g,\xi,t} \cdot \mathrm{p}^{\mathrm{nom}}_{g,\xi} - \widetilde{\mathrm{rd}}^{\mathrm{dn}}_{g,\xi} \cdot \mathrm{p}^{\mathrm{nom}}_{g,\xi} \right) \cdot \left( u_{g,\xi,t} - \mathit{up}_{g,\xi,t} \right) \le 0 \qquad \forall\, g \in \mathcal{G},\ \xi \in \Xi,\ t \in \mathcal{T} \,:\, \mathrm{com}_{g} \wedge \neg \mathrm{ext}_{g} \wedge \lvert \{ \xi' \in \Xi \,:\, \lvert \{ t' \in \mathcal{T} \,:\, \mathrm{c}^{\mathrm{up}}_{g,\xi',t'} \neq \mathrm{c}^{\mathrm{dn}}_{g,\xi',t'} \} \rvert > 0 \} \rvert = 0 \wedge \mathrm{on}_{g,t} \wedge \left( \mathrm{pos}(t) > 0 \vee \mathrm{u}^{0}_{g,\xi} = 0 \vee \mathrm{p}^{0}_{g,\xi} \text{ is defined} \right) \]

Generator-com-p-current#

Generator_com_p_current

Generator_com_p_current:
  description: >-
    `Generator-com-p-current` — output fits the share on, and the share starting
    up only up to the start-up ramp (`constraints.py:744-757`)
  dims: [scenario, snapshot, generator]
  where: Generator_committable AND NOT Generator_p_nom_extendable AND count(count(Generator_start_up_cost != Generator_shut_down_cost, over=snapshot) > 0, over=scenario) == 0 AND Generator_active
  expression: >-
    Generator_p - Generator_p_max_pu * Generator_p_nom * Generator_status
    + (Generator_p_max_pu * Generator_p_nom - Generator_start_up_rate * Generator_p_nom) * Generator_start_up <= 0
\[ p_{g,\xi,t} - \overline{\mathrm{p}}_{g,\xi,t} \cdot \mathrm{p}^{\mathrm{nom}}_{g,\xi} \cdot u_{g,\xi,t} + \left( \overline{\mathrm{p}}_{g,\xi,t} \cdot \mathrm{p}^{\mathrm{nom}}_{g,\xi} - \widetilde{\mathrm{ru}}^{\mathrm{up}}_{g,\xi} \cdot \mathrm{p}^{\mathrm{nom}}_{g,\xi} \right) \cdot \mathit{up}_{g,\xi,t} \le 0 \qquad \forall\, g \in \mathcal{G},\ \xi \in \Xi,\ t \in \mathcal{T} \,:\, \mathrm{com}_{g} \wedge \neg \mathrm{ext}_{g} \wedge \lvert \{ \xi' \in \Xi \,:\, \lvert \{ t' \in \mathcal{T} \,:\, \mathrm{c}^{\mathrm{up}}_{g,\xi',t'} \neq \mathrm{c}^{\mathrm{dn}}_{g,\xi',t'} \} \rvert > 0 \} \rvert = 0 \wedge \mathrm{on}_{g,t} \]

Generator-com-partly-start-up#

Generator_com_partly_start_up

Generator_com_partly_start_up:
  description: >-
    `Generator-com-partly-start-up` — raising output while a share is
    starting up is bounded by two ramps (`constraints.py:759-774`). They are
    the ramp of the share on and the start-up ramp of the share coming on
  dims: [scenario, snapshot, generator]
  where: Generator_committable AND NOT Generator_p_nom_extendable AND count(count(Generator_start_up_cost != Generator_shut_down_cost, over=snapshot) > 0, over=scenario) == 0 AND Generator_active AND (position(snapshot) > 0 OR Generator_status_initial == 0 OR Generator_p_init)
  expression: >-
    Generator_p - Generator_previous_p
    - (Generator_p_min_pu * Generator_p_nom + Generator_ramp_up_rate * Generator_p_nom) * Generator_status
    + Generator_p_min_pu * Generator_p_nom * Generator_previous_status
    + (Generator_p_min_pu * Generator_p_nom + Generator_ramp_up_rate * Generator_p_nom - Generator_start_up_rate * Generator_p_nom)
    * Generator_start_up <= 0
\[ p_{g,\xi,t} - \overleftarrow{p}_{g,\xi,t} - \left( \underline{\mathrm{p}}_{g,\xi,t} \cdot \mathrm{p}^{\mathrm{nom}}_{g,\xi} + \widetilde{\mathrm{ru}}_{g,\xi,t} \cdot \mathrm{p}^{\mathrm{nom}}_{g,\xi} \right) \cdot u_{g,\xi,t} + \underline{\mathrm{p}}_{g,\xi,t} \cdot \mathrm{p}^{\mathrm{nom}}_{g,\xi} \cdot \overleftarrow{u}_{g,\xi,t} + \left( \underline{\mathrm{p}}_{g,\xi,t} \cdot \mathrm{p}^{\mathrm{nom}}_{g,\xi} + \widetilde{\mathrm{ru}}_{g,\xi,t} \cdot \mathrm{p}^{\mathrm{nom}}_{g,\xi} - \widetilde{\mathrm{ru}}^{\mathrm{up}}_{g,\xi} \cdot \mathrm{p}^{\mathrm{nom}}_{g,\xi} \right) \cdot \mathit{up}_{g,\xi,t} \le 0 \qquad \forall\, g \in \mathcal{G},\ \xi \in \Xi,\ t \in \mathcal{T} \,:\, \mathrm{com}_{g} \wedge \neg \mathrm{ext}_{g} \wedge \lvert \{ \xi' \in \Xi \,:\, \lvert \{ t' \in \mathcal{T} \,:\, \mathrm{c}^{\mathrm{up}}_{g,\xi',t'} \neq \mathrm{c}^{\mathrm{dn}}_{g,\xi',t'} \} \rvert > 0 \} \rvert = 0 \wedge \mathrm{on}_{g,t} \wedge \left( \mathrm{pos}(t) > 0 \vee \mathrm{u}^{0}_{g,\xi} = 0 \vee \mathrm{p}^{0}_{g,\xi} \text{ is defined} \right) \]

Generator-com-partly-shut-down#

Generator_com_partly_shut_down

Generator_com_partly_shut_down:
  description: >-
    `Generator-com-partly-shut-down` — lowering output while a share is shutting
    down is bounded likewise, by the shut-down ramp (`constraints.py:776-791`)
  dims: [scenario, snapshot, generator]
  where: Generator_committable AND NOT Generator_p_nom_extendable AND count(count(Generator_start_up_cost != Generator_shut_down_cost, over=snapshot) > 0, over=scenario) == 0 AND Generator_active AND (position(snapshot) > 0 OR Generator_status_initial == 0 OR Generator_p_init)
  expression: >-
    Generator_previous_p - Generator_p
    - Generator_shut_down_rate * Generator_p_nom * Generator_previous_status
    + (Generator_shut_down_rate * Generator_p_nom - Generator_ramp_down_rate * Generator_p_nom) * Generator_status
    - (Generator_p_min_pu * Generator_p_nom + Generator_ramp_down_rate * Generator_p_nom - Generator_shut_down_rate * Generator_p_nom)
    * Generator_start_up <= 0
\[ \overleftarrow{p}_{g,\xi,t} - p_{g,\xi,t} - \widetilde{\mathrm{rd}}^{\mathrm{dn}}_{g,\xi} \cdot \mathrm{p}^{\mathrm{nom}}_{g,\xi} \cdot \overleftarrow{u}_{g,\xi,t} + \left( \widetilde{\mathrm{rd}}^{\mathrm{dn}}_{g,\xi} \cdot \mathrm{p}^{\mathrm{nom}}_{g,\xi} - \widetilde{\mathrm{rd}}_{g,\xi,t} \cdot \mathrm{p}^{\mathrm{nom}}_{g,\xi} \right) \cdot u_{g,\xi,t} - \left( \underline{\mathrm{p}}_{g,\xi,t} \cdot \mathrm{p}^{\mathrm{nom}}_{g,\xi} + \widetilde{\mathrm{rd}}_{g,\xi,t} \cdot \mathrm{p}^{\mathrm{nom}}_{g,\xi} - \widetilde{\mathrm{rd}}^{\mathrm{dn}}_{g,\xi} \cdot \mathrm{p}^{\mathrm{nom}}_{g,\xi} \right) \cdot \mathit{up}_{g,\xi,t} \le 0 \qquad \forall\, g \in \mathcal{G},\ \xi \in \Xi,\ t \in \mathcal{T} \,:\, \mathrm{com}_{g} \wedge \neg \mathrm{ext}_{g} \wedge \lvert \{ \xi' \in \Xi \,:\, \lvert \{ t' \in \mathcal{T} \,:\, \mathrm{c}^{\mathrm{up}}_{g,\xi',t'} \neq \mathrm{c}^{\mathrm{dn}}_{g,\xi',t'} \} \rvert > 0 \} \rvert = 0 \wedge \mathrm{on}_{g,t} \wedge \left( \mathrm{pos}(t) > 0 \vee \mathrm{u}^{0}_{g,\xi} = 0 \vee \mathrm{p}^{0}_{g,\xi} \text{ is defined} \right) \]

Link_com_p_before

Link_com_p_before:
  description: >-
    `Link-com-p-before` — the output a link had entering this snapshot fits
    the share of it still on (`constraints.py:729-742`). The share it is
    shutting down counts only at the shut-down ramp. At the first snapshot,
    the link carries in the state and output it brought into the horizon,
    and has no row where it came in running without an initial output
    (`constraints.py:712-719`)
  dims: [scenario, snapshot, link]
  where: Link_committable AND NOT Link_p_nom_extendable AND count(count(Link_start_up_cost != Link_shut_down_cost, over=snapshot) > 0, over=scenario) == 0 AND Link_active AND (position(snapshot) > 0 OR Link_status_initial == 0 OR Link_p_init)
  expression: >-
    Link_previous_p
    - Link_shut_down_rate * Link_p_nom * Link_previous_status
    - (Link_p_max_pu * Link_p_nom - Link_shut_down_rate * Link_p_nom)
    * (Link_status - Link_start_up) <= 0
\[ \overleftarrow{f}_{l,\xi,t} - \widetilde{\mathrm{rd}}^{f,\mathrm{dn}}_{l,\xi} \cdot \mathrm{f}^{\mathrm{nom}}_{l,\xi} \cdot \overleftarrow{u}^{f}_{l,\xi,t} - \left( \overline{\mathrm{f}}_{l,\xi,t} \cdot \mathrm{f}^{\mathrm{nom}}_{l,\xi} - \widetilde{\mathrm{rd}}^{f,\mathrm{dn}}_{l,\xi} \cdot \mathrm{f}^{\mathrm{nom}}_{l,\xi} \right) \cdot \left( u^{f}_{l,\xi,t} - \mathit{up}^{f}_{l,\xi,t} \right) \le 0 \qquad \forall\, l \in \mathcal{L},\ \xi \in \Xi,\ t \in \mathcal{T} \,:\, \mathrm{com}^{f}_{l} \wedge \neg \mathrm{ext}^{f}_{l} \wedge \lvert \{ \xi' \in \Xi \,:\, \lvert \{ t' \in \mathcal{T} \,:\, \mathrm{c}^{f,\mathrm{up}}_{l,\xi',t'} \neq \mathrm{c}^{f,\mathrm{dn}}_{l,\xi',t'} \} \rvert > 0 \} \rvert = 0 \wedge \mathrm{on}^{f}_{l,t} \wedge \left( \mathrm{pos}(t) > 0 \vee \mathrm{u}^{f,0}_{l,\xi} = 0 \vee \mathrm{f}^{0}_{l,\xi} \text{ is defined} \right) \]

Link_com_p_current

Link_com_p_current:
  description: >-
    `Link-com-p-current` — output fits the share on, and the share starting
    up only up to the start-up ramp (`constraints.py:744-757`)
  dims: [scenario, snapshot, link]
  where: Link_committable AND NOT Link_p_nom_extendable AND count(count(Link_start_up_cost != Link_shut_down_cost, over=snapshot) > 0, over=scenario) == 0 AND Link_active
  expression: >-
    Link_p - Link_p_max_pu * Link_p_nom * Link_status
    + (Link_p_max_pu * Link_p_nom - Link_start_up_rate * Link_p_nom) * Link_start_up <= 0
\[ f_{l,\xi,t} - \overline{\mathrm{f}}_{l,\xi,t} \cdot \mathrm{f}^{\mathrm{nom}}_{l,\xi} \cdot u^{f}_{l,\xi,t} + \left( \overline{\mathrm{f}}_{l,\xi,t} \cdot \mathrm{f}^{\mathrm{nom}}_{l,\xi} - \widetilde{\mathrm{ru}}^{f,\mathrm{up}}_{l,\xi} \cdot \mathrm{f}^{\mathrm{nom}}_{l,\xi} \right) \cdot \mathit{up}^{f}_{l,\xi,t} \le 0 \qquad \forall\, l \in \mathcal{L},\ \xi \in \Xi,\ t \in \mathcal{T} \,:\, \mathrm{com}^{f}_{l} \wedge \neg \mathrm{ext}^{f}_{l} \wedge \lvert \{ \xi' \in \Xi \,:\, \lvert \{ t' \in \mathcal{T} \,:\, \mathrm{c}^{f,\mathrm{up}}_{l,\xi',t'} \neq \mathrm{c}^{f,\mathrm{dn}}_{l,\xi',t'} \} \rvert > 0 \} \rvert = 0 \wedge \mathrm{on}^{f}_{l,t} \]

Link_com_partly_start_up

Link_com_partly_start_up:
  description: >-
    `Link-com-partly-start-up` — raising output while a share is starting up
    is bounded by two ramps (`constraints.py:759-774`). They are the ramp of
    the share on and the start-up ramp of the share coming on
  dims: [scenario, snapshot, link]
  where: Link_committable AND NOT Link_p_nom_extendable AND count(count(Link_start_up_cost != Link_shut_down_cost, over=snapshot) > 0, over=scenario) == 0 AND Link_active AND (position(snapshot) > 0 OR Link_status_initial == 0 OR Link_p_init)
  expression: >-
    Link_p - Link_previous_p
    - (Link_p_min_pu * Link_p_nom + Link_ramp_up_rate * Link_p_nom) * Link_status
    + Link_p_min_pu * Link_p_nom * Link_previous_status
    + (Link_p_min_pu * Link_p_nom + Link_ramp_up_rate * Link_p_nom - Link_start_up_rate * Link_p_nom)
    * Link_start_up <= 0
\[ f_{l,\xi,t} - \overleftarrow{f}_{l,\xi,t} - \left( \underline{\mathrm{f}}_{l,\xi,t} \cdot \mathrm{f}^{\mathrm{nom}}_{l,\xi} + \widetilde{\mathrm{ru}}^{f}_{l,\xi,t} \cdot \mathrm{f}^{\mathrm{nom}}_{l,\xi} \right) \cdot u^{f}_{l,\xi,t} + \underline{\mathrm{f}}_{l,\xi,t} \cdot \mathrm{f}^{\mathrm{nom}}_{l,\xi} \cdot \overleftarrow{u}^{f}_{l,\xi,t} + \left( \underline{\mathrm{f}}_{l,\xi,t} \cdot \mathrm{f}^{\mathrm{nom}}_{l,\xi} + \widetilde{\mathrm{ru}}^{f}_{l,\xi,t} \cdot \mathrm{f}^{\mathrm{nom}}_{l,\xi} - \widetilde{\mathrm{ru}}^{f,\mathrm{up}}_{l,\xi} \cdot \mathrm{f}^{\mathrm{nom}}_{l,\xi} \right) \cdot \mathit{up}^{f}_{l,\xi,t} \le 0 \qquad \forall\, l \in \mathcal{L},\ \xi \in \Xi,\ t \in \mathcal{T} \,:\, \mathrm{com}^{f}_{l} \wedge \neg \mathrm{ext}^{f}_{l} \wedge \lvert \{ \xi' \in \Xi \,:\, \lvert \{ t' \in \mathcal{T} \,:\, \mathrm{c}^{f,\mathrm{up}}_{l,\xi',t'} \neq \mathrm{c}^{f,\mathrm{dn}}_{l,\xi',t'} \} \rvert > 0 \} \rvert = 0 \wedge \mathrm{on}^{f}_{l,t} \wedge \left( \mathrm{pos}(t) > 0 \vee \mathrm{u}^{f,0}_{l,\xi} = 0 \vee \mathrm{f}^{0}_{l,\xi} \text{ is defined} \right) \]

Link_com_partly_shut_down

Link_com_partly_shut_down:
  description: >-
    `Link-com-partly-shut-down` — lowering output while a share is shutting
    down is bounded likewise, by the shut-down ramp (`constraints.py:776-791`)
  dims: [scenario, snapshot, link]
  where: Link_committable AND NOT Link_p_nom_extendable AND count(count(Link_start_up_cost != Link_shut_down_cost, over=snapshot) > 0, over=scenario) == 0 AND Link_active AND (position(snapshot) > 0 OR Link_status_initial == 0 OR Link_p_init)
  expression: >-
    Link_previous_p - Link_p
    - Link_shut_down_rate * Link_p_nom * Link_previous_status
    + (Link_shut_down_rate * Link_p_nom - Link_ramp_down_rate * Link_p_nom) * Link_status
    - (Link_p_min_pu * Link_p_nom + Link_ramp_down_rate * Link_p_nom - Link_shut_down_rate * Link_p_nom)
    * Link_start_up <= 0
\[ \overleftarrow{f}_{l,\xi,t} - f_{l,\xi,t} - \widetilde{\mathrm{rd}}^{f,\mathrm{dn}}_{l,\xi} \cdot \mathrm{f}^{\mathrm{nom}}_{l,\xi} \cdot \overleftarrow{u}^{f}_{l,\xi,t} + \left( \widetilde{\mathrm{rd}}^{f,\mathrm{dn}}_{l,\xi} \cdot \mathrm{f}^{\mathrm{nom}}_{l,\xi} - \widetilde{\mathrm{rd}}^{f}_{l,\xi,t} \cdot \mathrm{f}^{\mathrm{nom}}_{l,\xi} \right) \cdot u^{f}_{l,\xi,t} - \left( \underline{\mathrm{f}}_{l,\xi,t} \cdot \mathrm{f}^{\mathrm{nom}}_{l,\xi} + \widetilde{\mathrm{rd}}^{f}_{l,\xi,t} \cdot \mathrm{f}^{\mathrm{nom}}_{l,\xi} - \widetilde{\mathrm{rd}}^{f,\mathrm{dn}}_{l,\xi} \cdot \mathrm{f}^{\mathrm{nom}}_{l,\xi} \right) \cdot \mathit{up}^{f}_{l,\xi,t} \le 0 \qquad \forall\, l \in \mathcal{L},\ \xi \in \Xi,\ t \in \mathcal{T} \,:\, \mathrm{com}^{f}_{l} \wedge \neg \mathrm{ext}^{f}_{l} \wedge \lvert \{ \xi' \in \Xi \,:\, \lvert \{ t' \in \mathcal{T} \,:\, \mathrm{c}^{f,\mathrm{up}}_{l,\xi',t'} \neq \mathrm{c}^{f,\mathrm{dn}}_{l,\xi',t'} \} \rvert > 0 \} \rvert = 0 \wedge \mathrm{on}^{f}_{l,t} \wedge \left( \mathrm{pos}(t) > 0 \vee \mathrm{u}^{f,0}_{l,\xi} = 0 \vee \mathrm{f}^{0}_{l,\xi} \text{ is defined} \right) \]

Process-com-p-before#

Process_com_p_before

Process_com_p_before:
  description: >-
    `Process-com-p-before` — the output a process had entering this snapshot
    fits the share of it still on (`constraints.py:729-742`). The share it
    is shutting down counts only at the shut-down ramp. At the first snapshot,
    the process carries in the state and output it brought into the horizon,
    and has no row where it came in running without an initial output
    (`constraints.py:712-719`)
  dims: [scenario, snapshot, process]
  where: Process_committable AND NOT Process_p_nom_extendable AND count(count(Process_start_up_cost != Process_shut_down_cost, over=snapshot) > 0, over=scenario) == 0 AND Process_active AND (position(snapshot) > 0 OR Process_status_initial == 0 OR Process_p_init)
  expression: >-
    Process_previous_p
    - Process_shut_down_rate * Process_p_nom * Process_previous_status
    - (Process_p_max_pu * Process_p_nom - Process_shut_down_rate * Process_p_nom)
    * (Process_status - Process_start_up) <= 0
\[ \overleftarrow{z}_{j,\xi,t} - \widetilde{\mathrm{rd}}^{z,\mathrm{dn}}_{j,\xi} \cdot \mathrm{z}^{\mathrm{nom}}_{j,\xi} \cdot \overleftarrow{u}^{z}_{j,\xi,t} - \left( \overline{\mathrm{z}}_{j,\xi,t} \cdot \mathrm{z}^{\mathrm{nom}}_{j,\xi} - \widetilde{\mathrm{rd}}^{z,\mathrm{dn}}_{j,\xi} \cdot \mathrm{z}^{\mathrm{nom}}_{j,\xi} \right) \cdot \left( u^{z}_{j,\xi,t} - \mathit{up}^{z}_{j,\xi,t} \right) \le 0 \qquad \forall\, j \in \mathcal{J},\ \xi \in \Xi,\ t \in \mathcal{T} \,:\, \mathrm{com}^{z}_{j} \wedge \neg \mathrm{ext}^{z}_{j} \wedge \lvert \{ \xi' \in \Xi \,:\, \lvert \{ t' \in \mathcal{T} \,:\, \mathrm{c}^{z,\mathrm{up}}_{j,\xi',t'} \neq \mathrm{c}^{z,\mathrm{dn}}_{j,\xi',t'} \} \rvert > 0 \} \rvert = 0 \wedge \mathrm{on}^{z}_{j,t} \wedge \left( \mathrm{pos}(t) > 0 \vee \mathrm{u}^{z,0}_{j,\xi} = 0 \vee \mathrm{z}^{0}_{j,\xi} \text{ is defined} \right) \]

Process-com-p-current#

Process_com_p_current

Process_com_p_current:
  description: >-
    `Process-com-p-current` — output fits the share on, and the share starting
    up only up to the start-up ramp (`constraints.py:744-757`)
  dims: [scenario, snapshot, process]
  where: Process_committable AND NOT Process_p_nom_extendable AND count(count(Process_start_up_cost != Process_shut_down_cost, over=snapshot) > 0, over=scenario) == 0 AND Process_active
  expression: >-
    Process_p - Process_p_max_pu * Process_p_nom * Process_status
    + (Process_p_max_pu * Process_p_nom - Process_start_up_rate * Process_p_nom) * Process_start_up <= 0
\[ z_{j,\xi,t} - \overline{\mathrm{z}}_{j,\xi,t} \cdot \mathrm{z}^{\mathrm{nom}}_{j,\xi} \cdot u^{z}_{j,\xi,t} + \left( \overline{\mathrm{z}}_{j,\xi,t} \cdot \mathrm{z}^{\mathrm{nom}}_{j,\xi} - \widetilde{\mathrm{ru}}^{z,\mathrm{up}}_{j,\xi} \cdot \mathrm{z}^{\mathrm{nom}}_{j,\xi} \right) \cdot \mathit{up}^{z}_{j,\xi,t} \le 0 \qquad \forall\, j \in \mathcal{J},\ \xi \in \Xi,\ t \in \mathcal{T} \,:\, \mathrm{com}^{z}_{j} \wedge \neg \mathrm{ext}^{z}_{j} \wedge \lvert \{ \xi' \in \Xi \,:\, \lvert \{ t' \in \mathcal{T} \,:\, \mathrm{c}^{z,\mathrm{up}}_{j,\xi',t'} \neq \mathrm{c}^{z,\mathrm{dn}}_{j,\xi',t'} \} \rvert > 0 \} \rvert = 0 \wedge \mathrm{on}^{z}_{j,t} \]

Process-com-partly-start-up#

Process_com_partly_start_up

Process_com_partly_start_up:
  description: >-
    `Process-com-partly-start-up` — raising output while a share is starting
    up is bounded by two ramps (`constraints.py:759-774`). They are the ramp
    of the share on and the start-up ramp of the share coming on
  dims: [scenario, snapshot, process]
  where: Process_committable AND NOT Process_p_nom_extendable AND count(count(Process_start_up_cost != Process_shut_down_cost, over=snapshot) > 0, over=scenario) == 0 AND Process_active AND (position(snapshot) > 0 OR Process_status_initial == 0 OR Process_p_init)
  expression: >-
    Process_p - Process_previous_p
    - (Process_p_min_pu * Process_p_nom + Process_ramp_up_rate * Process_p_nom) * Process_status
    + Process_p_min_pu * Process_p_nom * Process_previous_status
    + (Process_p_min_pu * Process_p_nom + Process_ramp_up_rate * Process_p_nom - Process_start_up_rate * Process_p_nom)
    * Process_start_up <= 0
\[ z_{j,\xi,t} - \overleftarrow{z}_{j,\xi,t} - \left( \underline{\mathrm{z}}_{j,\xi,t} \cdot \mathrm{z}^{\mathrm{nom}}_{j,\xi} + \widetilde{\mathrm{ru}}^{z}_{j,\xi,t} \cdot \mathrm{z}^{\mathrm{nom}}_{j,\xi} \right) \cdot u^{z}_{j,\xi,t} + \underline{\mathrm{z}}_{j,\xi,t} \cdot \mathrm{z}^{\mathrm{nom}}_{j,\xi} \cdot \overleftarrow{u}^{z}_{j,\xi,t} + \left( \underline{\mathrm{z}}_{j,\xi,t} \cdot \mathrm{z}^{\mathrm{nom}}_{j,\xi} + \widetilde{\mathrm{ru}}^{z}_{j,\xi,t} \cdot \mathrm{z}^{\mathrm{nom}}_{j,\xi} - \widetilde{\mathrm{ru}}^{z,\mathrm{up}}_{j,\xi} \cdot \mathrm{z}^{\mathrm{nom}}_{j,\xi} \right) \cdot \mathit{up}^{z}_{j,\xi,t} \le 0 \qquad \forall\, j \in \mathcal{J},\ \xi \in \Xi,\ t \in \mathcal{T} \,:\, \mathrm{com}^{z}_{j} \wedge \neg \mathrm{ext}^{z}_{j} \wedge \lvert \{ \xi' \in \Xi \,:\, \lvert \{ t' \in \mathcal{T} \,:\, \mathrm{c}^{z,\mathrm{up}}_{j,\xi',t'} \neq \mathrm{c}^{z,\mathrm{dn}}_{j,\xi',t'} \} \rvert > 0 \} \rvert = 0 \wedge \mathrm{on}^{z}_{j,t} \wedge \left( \mathrm{pos}(t) > 0 \vee \mathrm{u}^{z,0}_{j,\xi} = 0 \vee \mathrm{z}^{0}_{j,\xi} \text{ is defined} \right) \]

Process-com-partly-shut-down#

Process_com_partly_shut_down

Process_com_partly_shut_down:
  description: >-
    `Process-com-partly-shut-down` — lowering output while a share is shutting
    down is bounded likewise, by the shut-down ramp (`constraints.py:776-791`)
  dims: [scenario, snapshot, process]
  where: Process_committable AND NOT Process_p_nom_extendable AND count(count(Process_start_up_cost != Process_shut_down_cost, over=snapshot) > 0, over=scenario) == 0 AND Process_active AND (position(snapshot) > 0 OR Process_status_initial == 0 OR Process_p_init)
  expression: >-
    Process_previous_p - Process_p
    - Process_shut_down_rate * Process_p_nom * Process_previous_status
    + (Process_shut_down_rate * Process_p_nom - Process_ramp_down_rate * Process_p_nom) * Process_status
    - (Process_p_min_pu * Process_p_nom + Process_ramp_down_rate * Process_p_nom - Process_shut_down_rate * Process_p_nom)
    * Process_start_up <= 0
\[ \overleftarrow{z}_{j,\xi,t} - z_{j,\xi,t} - \widetilde{\mathrm{rd}}^{z,\mathrm{dn}}_{j,\xi} \cdot \mathrm{z}^{\mathrm{nom}}_{j,\xi} \cdot \overleftarrow{u}^{z}_{j,\xi,t} + \left( \widetilde{\mathrm{rd}}^{z,\mathrm{dn}}_{j,\xi} \cdot \mathrm{z}^{\mathrm{nom}}_{j,\xi} - \widetilde{\mathrm{rd}}^{z}_{j,\xi,t} \cdot \mathrm{z}^{\mathrm{nom}}_{j,\xi} \right) \cdot u^{z}_{j,\xi,t} - \left( \underline{\mathrm{z}}_{j,\xi,t} \cdot \mathrm{z}^{\mathrm{nom}}_{j,\xi} + \widetilde{\mathrm{rd}}^{z}_{j,\xi,t} \cdot \mathrm{z}^{\mathrm{nom}}_{j,\xi} - \widetilde{\mathrm{rd}}^{z,\mathrm{dn}}_{j,\xi} \cdot \mathrm{z}^{\mathrm{nom}}_{j,\xi} \right) \cdot \mathit{up}^{z}_{j,\xi,t} \le 0 \qquad \forall\, j \in \mathcal{J},\ \xi \in \Xi,\ t \in \mathcal{T} \,:\, \mathrm{com}^{z}_{j} \wedge \neg \mathrm{ext}^{z}_{j} \wedge \lvert \{ \xi' \in \Xi \,:\, \lvert \{ t' \in \mathcal{T} \,:\, \mathrm{c}^{z,\mathrm{up}}_{j,\xi',t'} \neq \mathrm{c}^{z,\mathrm{dn}}_{j,\xi',t'} \} \rvert > 0 \} \rvert = 0 \wedge \mathrm{on}^{z}_{j,t} \wedge \left( \mathrm{pos}(t) > 0 \vee \mathrm{u}^{z,0}_{j,\xi} = 0 \vee \mathrm{z}^{0}_{j,\xi} \text{ is defined} \right) \]

Generator_marginal_cost_incremental#

Generator_marginal_cost_incremental

Generator_marginal_cost_incremental:
  description: >-
    `Generator-marginal_cost_piecewise` — the running cost of a generator
    with a curve is at least the curve at its output, stated by segments
    filled in order, where linopy chooses the incremental rows. No status
    gates it, because no generator with a curve is committable under the
    keyword
  activity: null
\[ C^{\mathrm{curve}}_{g,\xi,t} \ge \mathrm{pwl}_{b \in \mathcal{B} \,:\, \neg \mathrm{lp} \wedge \mathrm{curve}_{g} \wedge \mathrm{on}_{g,t} \wedge \mathrm{p}^{\mathrm{curve}}_{b,g} \text{ is defined}}(\mathrm{p}^{\mathrm{curve}}_{b,g},\ \mathrm{C}^{\mathrm{curve}}_{b,g})(p_{g,\xi,t}) \qquad \forall\, g \in \mathcal{G},\ \xi \in \Xi,\ t \in \mathcal{T} \]

Generator_marginal_cost_curve_is_not_committable#

Generator_marginal_cost_curve_is_not_committable

Generator_marginal_cost_curve_is_not_committable:
  holds: "NOT Generator_committable"
  where: "Generator_marginal_cost_curve"
  description: >-
    a generator with a piecewise `marginal_cost` curve is not committable.
    PyPSA gates such a curve by the status share (`optimize.py:314-318`,
    `piecewise.py:200-210`), and a curve gate is binary or integer
\[ \neg \mathrm{com}_{g} \qquad \forall\, g \in \mathcal{G} \,:\, \mathrm{curve}_{g} \]

Generator_committable_is_not_modular#

Generator_committable_is_not_modular

Generator_committable_is_not_modular:
  holds: "NOT (Generator_p_nom_mod > 0)"
  where: "Generator_committable AND count(Generator_active, over=snapshot) > 0"
  description: >-
    a modular committable unit counts its status in modules, which the
    relaxation cannot share out. PyPSA refuses it under the keyword
    (`optimize.py:841-842`, `consistency.py:1588-1629`)
\[ \neg \left( \mathrm{p}^{\mathrm{mod}}_{g} > 0 \right) \qquad \forall\, g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \lvert \{ t \in \mathcal{T} \,:\, \mathrm{on}_{g,t} \} \rvert > 0 \]

Link_committable_is_not_modular

Link_committable_is_not_modular:
  holds: "NOT (Link_p_nom_mod > 0)"
  where: "Link_committable AND count(Link_active, over=snapshot) > 0"
  description: >-
    a modular committable unit counts its status in modules, which the
    relaxation cannot share out. PyPSA refuses it under the keyword
    (`optimize.py:841-842`, `consistency.py:1588-1629`)
\[ \neg \left( \mathrm{f}^{\mathrm{mod}}_{l} > 0 \right) \qquad \forall\, l \in \mathcal{L} \,:\, \mathrm{com}^{f}_{l} \wedge \lvert \{ t \in \mathcal{T} \,:\, \mathrm{on}^{f}_{l,t} \} \rvert > 0 \]

Process_committable_is_not_modular#

Process_committable_is_not_modular

Process_committable_is_not_modular:
  holds: "NOT (Process_p_nom_mod > 0)"
  where: "Process_committable AND count(Process_active, over=snapshot) > 0"
  description: >-
    a modular committable unit counts its status in modules, which the
    relaxation cannot share out. PyPSA refuses it under the keyword
    (`optimize.py:841-842`, `consistency.py:1588-1629`)
\[ \neg \left( \mathrm{z}^{\mathrm{mod}}_{j} > 0 \right) \qquad \forall\, j \in \mathcal{J} \,:\, \mathrm{com}^{z}_{j} \wedge \lvert \{ t \in \mathcal{T} \,:\, \mathrm{on}^{z}_{j,t} \} \rvert > 0 \]

See also