Skip to content

Generators#

This file states PyPSA's Generator. It is one of the 25 whole specs of one topic each (fragments) that merge composes into the spec of examples/pypsa.yaml. It adds a term to each of these sums: primary_energy, operational_limit, tech_capacity_expansion, scenario_opex, total_cost, Carrier_additions and Bus_injection. Generator_p_nom_effective is the build the limits use: the chosen one where the generator is extendable, the given one otherwise.

given:
  parameters:
    snapshot_weightings_objective: { dims: [snapshot] }
    Generator_marginal_cost_curve_p: { dims: [generator, breakpoint] }
    Generator_marginal_cost_curve_cost: { dims: [generator, breakpoint] }
    Generator_committable: { dims: [generator], dtype: bool }
    Generator_maintenance_pu: { dims: [scenario, generator] }
    scenario_weight: { dims: [scenario] }
    CVaR_omega: { dims: [] }
    period_weight_objective: { dims: [period] }
  variables:
    Generator_maintenance: { dims: [scenario, snapshot, generator] }
    Generator_maintenance_capacity: { dims: [scenario, snapshot, generator] }
  expressions:
    energy_limit_weight: { dims: [snapshot] }
    GlobalConstraint_energy_weight: { dims: [global_constraint, scenario, snapshot] }
    primary_energy: { dims: [global_constraint, scenario] }
    operational_limit: { dims: [global_constraint, scenario] }
    tech_capacity_expansion: { dims: [global_constraint] }
    scenario_opex: { dims: [scenario] }
    total_cost: { dims: [] }
    Carrier_additions: { dims: [carrier, period] }
    Bus_injection: { dims: [bus, scenario, snapshot] }

dimensions:
  scenario:
    description: the futures dispatch is chosen in, each with a weight
  snapshot:
    description: dispatch periods
    dtype: datetime
    ordered: true
  bus:
    description: network nodes
  generator:
    description: generating units, each on one bus
  global_constraint:
    description: PyPSA's `GlobalConstraint` rows, one label per declared limit
  period:
    description: investment periods. It is PyPSA's `investment_periods`
    dtype: int
    ordered: true
  carrier:
    description: energy carriers, what a growth limit is set per
  breakpoint:
    description: >-
      the corners of a piecewise-linear curve, in order. A curve runs over the
      breakpoints its data has rows for, so curves may differ in length
    dtype: int
    ordered: true

maps:
  snapshot:
    period:
      description: the investment period a snapshot falls in
      refers_to: period
  generator:
    carrier:
      description: the carrier a generator converts from
      refers_to: carrier
    bus:
      description: the bus a generator sits on
      refers_to: bus

parameters:
  Generator_p_nom:
    description: nominal power
    dims: [scenario, generator]
  Generator_p_nom_extendable:
    description: whether the nominal power is a decision
    dims: [generator]
    dtype: bool
  Generator_p_min_pu:
    description: least output, per unit of nominal power
    dims: [scenario, snapshot, generator]
  Generator_p_max_pu:
    description: "most output, per unit of nominal power: an availability profile"
    dims: [scenario, snapshot, generator]
  Generator_marginal_cost:
    description: cost of one unit of output
    dims: [scenario, snapshot, generator]
  Generator_marginal_cost_quadratic:
    description: cost of the square of one unit of output
    dims: [scenario, snapshot, generator]
  Generator_sign:
    description: >-
      the sign output enters its bus's balance with. It is PyPSA's `sign`, `1`
      unless given, `-1` for a unit that draws power. PyPSA refuses one that
      differs by scenario (`constants.py:46`)
    dims: [generator]
  Generator_p_nom_mod:
    description: the module size a build comes in whole numbers of. No value means the build is continuous
    dims: [generator]
    missing: absent
  Generator_modules_installed:
    description: >-
      how many whole modules a committable build has in place, data prep. It
      is `Generator_p_nom / Generator_p_nom_mod` where a fixed build is
      modular, and one where it is not. PyPSA refuses a fixed modular build
      whose nominal power is not a whole number of modules
    dims: [scenario, generator]
  Generator_p_min_pu_nonneg:
    description: >-
      true where none of the generator's own minimums-per-unit is negative. It
      is PyPSA's per-unit `(p_min_pu >= 0).all()` over every snapshot and
      scenario, data prep
    dims: [generator]
    dtype: bool
  Generator_active:
    description: whether a generator is active in a snapshot's period. It is PyPSA's `active`, from build year and lifetime, data prep
    dims: [snapshot, generator]
    dtype: bool
  Generator_active_asset:
    description: >-
      whether a generator is one of PyPSA's `active_assets`, by its `active`
      flag alone, without build year and lifetime. A unit that is active in no
      snapshot of the horizon still counts. PyPSA refuses one that differs by
      scenario (`constants.py:54`)
    dims: [generator]
    dtype: bool
  Generator_capital_weight:
    description: the sum of period weights a generator is active in. It is PyPSA's `active * period_weighting`, summed, data prep
    dims: [generator]
  Generator_first_active:
    description: >-
      one in the first period a generator is active in, zero elsewhere, data prep.
      PyPSA takes `active & (active.cumsum() == 1)` (`global_constraints.py:278`)
    dims: [period, generator]
  Generator_p_set:
    description: a given output schedule. A generator without one has no row here
    dims: [scenario, snapshot, generator]
    missing: absent
  Generator_p_nom_min:
    description: least nominal power an extendable generator may be built at
    dims: [scenario, generator]
  Generator_p_nom_max:
    description: most nominal power an extendable generator may be built at
    dims: [scenario, generator]
  Generator_capital_cost:
    description: >-
      cost of one unit of nominal power for the modelled horizon (PyPSA's
      `periodized_cost`), data prep. It is `overnight_cost` as an annuity over
      `lifetime` at `discount_rate`, times `nyears`, where `overnight_cost` is
      given, and `capital_cost` where it is not. Both add `fom_cost`
      (`components.py:1157-1178`, `costs.py:102-203`)
    dims: [scenario, generator]
  Generator_p_nom_set:
    description: a given nominal power for an extendable generator. One without a value has no row here
    dims: [generator]
    missing: absent
  Generator_e_sum_min:
    description: least energy over the horizon. It is minus infinity where no floor is meant
    dims: [scenario, generator]
  Generator_e_sum_max:
    description: "most energy over the horizon: a fuel or emission budget in energy terms. It is infinity where no cap is meant"
    dims: [scenario, generator]
  Generator_primary_energy_weight:
    description: >-
      the constrained attribute per unit of energy at the bus: the carrier's
      `co2_emissions` over the generator's efficiency at the snapshot, data
      prep. A generator of an unweighted carrier has no row
    dims: [scenario, global_constraint, snapshot, generator]
    missing: absent
  Generator_operational_limit_weight:
    description: one where the generator is in the row's set, data prep. One outside it has no row
    dims: [scenario, global_constraint, generator]
    missing: absent
  Generator_tech_capacity_weight:
    description: >-
      one where the generator is in the row's carrier-and-bus set, data prep.
      One outside it, or one that is not active in the row's
      `investment_period`, has no row
    dims: [global_constraint, generator]
    missing: absent

variables:
  Generator_p:
    description: "`Generator-p` — output of a generator in a snapshot"
    dims: [scenario, snapshot, generator]
    where: Generator_active
  Generator_marginal_cost_piecewise:
    description: >-
      `Generator-marginal_cost_piecewise` — the running cost of a generator
      that pays along a curve, held above the curve
    dims: [scenario, snapshot, generator]
    where: Generator_marginal_cost_curve AND Generator_active
  Generator_n_mod:
    description: "`Generator-n_mod` — how many modules of an extendable modular build. A unit that is not active has none"
    dims: [generator]
    where: Generator_p_nom_extendable AND Generator_p_nom_mod > 0 AND Generator_active_asset
    domain: integer
    bounds:
      lower: 0
  Generator_p_nom_ext:
    description: >-
      `Generator-p_nom` — nominal power where it is a decision, for a
      generator that is active. A fixed build is the parameter of the same
      PyPSA name
    dims: [generator]
    where: Generator_p_nom_extendable AND Generator_active_asset

expressions:
  Generator_p_nom_effective:
    description: "the build a generator's limits are taken against: the chosen one where it is extendable, the given one otherwise"
    dims: [scenario, generator]
    cases:
      extendable: { when: Generator_p_nom_extendable, expression: Generator_p_nom_ext }
    otherwise: Generator_p_nom
  Generator_modules:
    description: >-
      how many modules a committed unit's status counts: the chosen number
      where the build is extendable and modular, and the modules installed
      otherwise (`constraints.py:656-661`)
    dims: [scenario, generator]
    cases:
      chosen: { when: Generator_p_nom_extendable AND Generator_p_nom_mod > 0, expression: Generator_n_mod }
    otherwise: Generator_modules_installed
  Generator_p_nom_committed:
    description: >-
      the build a committed unit's ramp rows are taken against: one module
      where the build is modular, since its status counts modules, and the
      given build otherwise (`constraints.py:1131-1134`)
    dims: [scenario, generator]
    cases:
      modular_build: { when: "Generator_p_nom_mod > 0", expression: Generator_p_nom_mod }
    otherwise: Generator_p_nom
  Generator_primary_energy:
    expression: >-
      sum(sum((Generator_p * GlobalConstraint_energy_weight) * Generator_primary_energy_weight, over=snapshot), over=generator)
    adds_to: primary_energy
  Generator_operational_limit:
    expression: >-
      sum(sum((Generator_p * GlobalConstraint_energy_weight) * Generator_operational_limit_weight, over=snapshot), over=generator)
    adds_to: operational_limit
  Generator_tech_capacity_expansion:
    expression: sum(Generator_p_nom_ext * Generator_tech_capacity_weight, over=generator)
    adds_to: tech_capacity_expansion
  Generator_running_cost:
    description: >-
      what a generator's output costs in a snapshot: the value read off its
      piecewise curve where it has one, and its linear `marginal_cost`
      otherwise. A curve replaces the linear cost, as PyPSA drops the unit
      from that term (`optimize.py:333-348`)
    dims: [scenario, snapshot, generator]
    cases:
      curve: { when: Generator_marginal_cost_curve, expression: Generator_marginal_cost_piecewise }
    otherwise: Generator_p * Generator_marginal_cost
  Generator_opex:
    expression: >-
      sum(sum((Generator_running_cost * snapshot_weightings_objective) * at(period_weight_objective, by=snapshot[period]), over=generator), over=snapshot)
      + sum(sum((((Generator_p * Generator_p) * Generator_marginal_cost_quadratic) * snapshot_weightings_objective) * at(period_weight_objective, by=snapshot[period]), over=generator), over=snapshot)
    adds_to: scenario_opex
  Generator_capex:
    expression: sum(scenario_weight * Generator_p_nom_ext * Generator_capital_cost * Generator_capital_weight)
    adds_to: total_cost
  Generator_additions:
    expression: >-
      sum(Generator_p_nom_ext * Generator_first_active, over=generator, by=generator[carrier])
    adds_to: Carrier_additions
  Generator_injection:
    expression: sum(Generator_sign * Generator_p, over=generator, by=generator[bus])
    adds_to: Bus_injection

conditions:
  Generator_marginal_cost_curve:
    description: >-
      a generator that pays along a piecewise `marginal_cost` curve: one of
      PyPSA's `active_assets` with curve data (`optimize.py:307`,
      `piecewise.py:230-257`)
    condition: count(Generator_marginal_cost_curve_p, over=breakpoint) > 0 AND Generator_active_asset
  Generator_marginal_cost_curve_bends_down:
    description: >-
      a breakpoint where a generator's cost curve gets less steep: the
      segment after it rises less per unit of output than the one before
    condition: >-
      Generator_marginal_cost_curve_p
      AND shift(Generator_marginal_cost_curve_p, along=breakpoint, offset=1)
      AND shift(Generator_marginal_cost_curve_p, along=breakpoint, offset=-1)
      AND (Generator_marginal_cost_curve_cost - shift(Generator_marginal_cost_curve_cost, along=breakpoint, offset=1, edge=0))
      * (shift(Generator_marginal_cost_curve_p, along=breakpoint, offset=-1, edge=0) - Generator_marginal_cost_curve_p)
      > (shift(Generator_marginal_cost_curve_cost, along=breakpoint, offset=-1, edge=0) - Generator_marginal_cost_curve_cost)
      * (Generator_marginal_cost_curve_p - shift(Generator_marginal_cost_curve_p, along=breakpoint, offset=1, edge=0))
  Generator_marginal_cost_as_lp:
    description: >-
      linopy's choice of method for all generator cost curves at once
      (`method="auto"`): the segment lines where every curve is convex and no
      generator with a curve is committable, the incremental rows otherwise.
      PyPSA gates the curves by `Generator-status` where one is committable,
      and linopy states no segment lines under a gate (`optimize.py:314-318`,
      `piecewise.py:200-224`)
    condition: >-
      count(Generator_marginal_cost_curve AND count(Generator_marginal_cost_curve_bends_down, over=breakpoint) > 0, over=generator) == 0
      AND count(Generator_marginal_cost_curve AND Generator_committable, over=generator) == 0
  Generator_committed:
    description: >-
      a committable generator that is active in the snapshot's period: every
      unit-commitment row's set
    condition: Generator_committable AND Generator_active

constraints:
  Generator_fix_p_lower:
    description: "`Generator-fix-p-lower` — a fixed generator outputs at least its minimum"
    dims: [scenario, snapshot, generator]
    where: not Generator_p_nom_extendable AND not Generator_committable AND Generator_active
    expression: Generator_p >= Generator_p_min_pu * Generator_p_nom * (1 - Generator_maintenance_pu * Generator_maintenance)
  Generator_fix_p_upper:
    description: "`Generator-fix-p-upper` — a fixed generator outputs at most what is available"
    dims: [scenario, snapshot, generator]
    where: not Generator_p_nom_extendable AND not Generator_committable AND Generator_active
    expression: Generator_p <= Generator_p_max_pu * Generator_p_nom * (1 - Generator_maintenance_pu * Generator_maintenance)
  Generator_ext_p_lower:
    description: "`Generator-ext-p-lower` — an extendable generator outputs at least its minimum of the chosen build"
    dims: [scenario, snapshot, generator]
    where: Generator_p_nom_extendable AND not Generator_committable AND Generator_active
    expression: Generator_p >= Generator_p_min_pu * (Generator_p_nom_ext - Generator_maintenance_pu * Generator_maintenance_capacity)
  Generator_ext_p_upper:
    description: "`Generator-ext-p-upper` — an extendable generator outputs at most what is available of the chosen build"
    dims: [scenario, snapshot, generator]
    where: Generator_p_nom_extendable AND not Generator_committable AND Generator_active
    expression: Generator_p <= Generator_p_max_pu * (Generator_p_nom_ext - Generator_maintenance_pu * Generator_maintenance_capacity)
  Generator_ext_p_nom_lower:
    description: "`Generator-ext-p_nom-lower` — the chosen build is at least its floor in every scenario"
    dims: [scenario, generator]
    where: Generator_p_nom_extendable AND Generator_active_asset
    expression: Generator_p_nom_ext >= Generator_p_nom_min
  Generator_ext_p_nom_upper:
    description: "`Generator-ext-p_nom-upper` — the chosen build is at most its cap in every scenario. A cap of infinity is no row"
    dims: [scenario, generator]
    where: Generator_p_nom_extendable AND Generator_active_asset AND Generator_p_nom_max
    expression: Generator_p_nom_ext <= Generator_p_nom_max
  Generator_p_nom_set:
    description: "`Generator-p_nom_set` — the chosen build pinned, wherever a value is given"
    dims: [generator]
    where: Generator_p_nom_extendable AND Generator_active_asset AND Generator_p_nom_set
    expression: Generator_p_nom_ext == Generator_p_nom_set
  Generator_e_sum_min:
    description: "`Generator-e_sum_min` — energy over the horizon, each period weighted by its years, is at least its floor. A floor of minus infinity is no row, and so is a generator active in no snapshot"
    dims: [scenario, generator]
    where: Generator_e_sum_min AND count(Generator_active, over=snapshot) > 0
    expression: sum(Generator_p * energy_limit_weight, over=snapshot) >= Generator_e_sum_min
  Generator_e_sum_max:
    description: "`Generator-e_sum_max` — energy over the horizon, each period weighted by its years, is at most its budget. A budget of infinity is no row, and so is a generator active in no snapshot"
    dims: [scenario, generator]
    where: Generator_e_sum_max AND count(Generator_active, over=snapshot) > 0
    expression: sum(Generator_p * energy_limit_weight, over=snapshot) <= Generator_e_sum_max
  Generator_p_nom_modularity:
    description: "`Generator-p_nom_modularity` — the chosen build is a whole number of modules"
    dims: [generator]
    where: Generator_p_nom_extendable AND Generator_p_nom_mod > 0 AND Generator_active_asset
    expression: Generator_p_nom_ext == Generator_p_nom_mod * Generator_n_mod
  Generator_p_set:
    description: "`Generator-p_set` — output pinned to the given schedule, wherever one is given"
    dims: [scenario, snapshot, generator]
    where: Generator_p_set AND Generator_active
    expression: Generator_p == Generator_p_set

assumptions:
  Generator_marginal_cost_curve_on_a_fixed_build:
    holds: "NOT Generator_p_nom_extendable AND Generator_p_nom > 0 AND Generator_p_nom < inf"
    where: "Generator_marginal_cost_curve"
    description: >-
      a generator's cost curve is given per unit of a fixed, positive, finite
      `p_nom`, which scales it to output. PyPSA refuses an extendable unit and
      a zero, missing or infinite `p_nom` (`piecewise.py:284-304`), and takes
      a negative one, which the file does not state
  Generator_marginal_cost_quadratic_without_risk_preference:
    holds: "Generator_marginal_cost_quadratic == 0"
    where: "CVaR_omega"
    description: >-
      a quadratic cost puts a square into every `CVaR-excess` row. PyPSA
      refuses quadratic costs under any risk preference, `omega = 0` included
      (`optimize.py:506-513`). In data prep you write a `CVaR_omega` row only
      where a risk preference is set

Given#

Symbol Meaning
\(\omega\) CVaR_omega (scalar), data another file declares
\(\mathrm{com}\) Generator_committable over \(\mathcal{G}\), data another file declares
\(\gamma\) Generator_maintenance_pu over \(\mathcal{G} \times \Xi\), data another file declares
\(\mathrm{C}^{\mathrm{curve}}\) Generator_marginal_cost_curve_cost over \(\mathcal{B} \times \mathcal{G}\), data another file declares
\(\mathrm{p}^{\mathrm{curve}}\) Generator_marginal_cost_curve_p over \(\mathcal{B} \times \mathcal{G}\), data another file declares
\(\mathrm{w}^{y}\) period_weight_objective over \(\mathcal{Y}\), data another file declares
\(\pi\) scenario_weight over \(\Xi\), data another file declares
\(\mathrm{w}\) snapshot_weightings_objective over \(\mathcal{T}\), data another file declares
\(\mu\) Generator_maintenance over \(\mathcal{G} \times \Xi \times \mathcal{T}\)
\(\mu^{\mathrm{nom}}\) Generator_maintenance_capacity over \(\mathcal{G} \times \Xi \times \mathcal{T}\)
\(\mathit{Bus\_injection}\) Bus_injection over \(\mathcal{N} \times \Xi \times \mathcal{T}\), an expression this file adds Generator_injection to
\(\mathit{Carrier\_additions}\) Carrier_additions over \(\mathcal{I} \times \mathcal{Y}\), an expression this file adds Generator_additions to
\(\mathit{w}^{\mathrm{gc}}\) GlobalConstraint_energy_weight over \(\mathcal{L} \times \Xi \times \mathcal{T}\), an expression another file defines
\(\mathit{w}^{E}\) energy_limit_weight over \(\mathcal{T}\), an expression another file defines
\(\mathit{operational\_limit}\) operational_limit over \(\mathcal{L} \times \Xi\), an expression this file adds Generator_operational_limit to
\(\mathit{primary\_energy}\) primary_energy over \(\mathcal{L} \times \Xi\), an expression this file adds Generator_primary_energy to
\(\mathit{scenario\_opex}\) scenario_opex over \(\Xi\), an expression this file adds Generator_opex to
\(\mathit{tech\_capacity\_expansion}\) tech_capacity_expansion over \(\mathcal{L}\), an expression this file adds Generator_tech_capacity_expansion to
\(\mathit{total\_cost}\) total_cost (scalar), an expression this file adds Generator_capex to

Sets#

Symbol Meaning
\(\mathcal{B}\) index \(b\) — breakpoint — the corners of a piecewise-linear curve, in order. A curve runs over the breakpoints its data has rows for, so curves may differ in length
\(\mathcal{N}\) index \(n\) — bus with \(\mathrm{bus}: \mathcal{G} \to \mathcal{N}\) — network nodes
\(\mathcal{I}\) index \(i\) — carrier with \(\mathrm{carrier}: \mathcal{G} \to \mathcal{I}\) — energy carriers, what a growth limit is set per
\(\mathcal{G}\) index \(g\) — generator with \(\mathrm{bus}: \mathcal{G} \to \mathcal{N},\ \mathrm{carrier}: \mathcal{G} \to \mathcal{I}\) — generating units, each on one bus
\(\mathcal{L}\) index \(l\) — global_constraint — PyPSA's GlobalConstraint rows, one label per declared limit
\(\mathcal{Y}\) index \(y\) — period with \(\mathrm{period}: \mathcal{T} \to \mathcal{Y}\) — investment periods. It is PyPSA's investment_periods
\(\Xi\) index \(\xi\) — scenario — the futures dispatch is chosen in, each with a weight
\(\mathcal{T}\) index \(t\) — snapshot with \(\mathrm{period}: \mathcal{T} \to \mathcal{Y}\) — dispatch periods

Parameters#

Symbol Meaning
\(\mathrm{on}\) Generator_active over \(\mathcal{G} \times \mathcal{T}\) — whether a generator is active in a snapshot's period. It is PyPSA's active, from build year and lifetime, data prep
\(\mathrm{act}\) Generator_active_asset over \(\mathcal{G}\) — whether a generator is one of PyPSA's active_assets, by its active flag alone, without build year and lifetime. A unit that is active in no snapshot of the horizon still counts. PyPSA refuses one that differs by scenario (constants.py:54)
\(\mathrm{c}^{\mathrm{cap}}\) Generator_capital_cost over \(\mathcal{G} \times \Xi\) — cost of one unit of nominal power for the modelled horizon (PyPSA's periodized_cost), data prep. It is overnight_cost as an annuity over lifetime at discount_rate, times nyears, where overnight_cost is given, and capital_cost where it is not. Both add fom_cost (components.py:1157-1178, costs.py:102-203)
\(\mathrm{W}\) Generator_capital_weight over \(\mathcal{G}\) — the sum of period weights a generator is active in. It is PyPSA's active * period_weighting, summed, data prep
\(\overline{\mathrm{E}}\) Generator_e_sum_max over \(\mathcal{G} \times \Xi\) — most energy over the horizon: a fuel or emission budget in energy terms. It is infinity where no cap is meant
\(\underline{\mathrm{E}}\) Generator_e_sum_min over \(\mathcal{G} \times \Xi\) — least energy over the horizon. It is minus infinity where no floor is meant
\(\mathrm{new}\) Generator_first_active over \(\mathcal{G} \times \mathcal{Y}\) — one in the first period a generator is active in, zero elsewhere, data prep. PyPSA takes active & (active.cumsum() == 1) (global_constraints.py:278)
\(\mathrm{c}\) Generator_marginal_cost over \(\mathcal{G} \times \Xi \times \mathcal{T}\) — cost of one unit of output
\(\mathrm{c}^{(2)}\) Generator_marginal_cost_quadratic over \(\mathcal{G} \times \Xi \times \mathcal{T}\) — cost of the square of one unit of output
\(\mathrm{N}^{\mathrm{fix}}\) Generator_modules_installed over \(\mathcal{G} \times \Xi\) — how many whole modules a committable build has in place, data prep. It is Generator_p_nom / Generator_p_nom_mod where a fixed build is modular, and one where it is not. PyPSA refuses a fixed modular build whose nominal power is not a whole number of modules
\(\mathrm{b}\) Generator_operational_limit_weight over \(\mathcal{G} \times \mathcal{L} \times \Xi\), absent where the data has no row — one where the generator is in the row's set, data prep. One outside it has no row
\(\overline{\mathrm{p}}\) Generator_p_max_pu over \(\mathcal{G} \times \Xi \times \mathcal{T}\) — most output, per unit of nominal power: an availability profile
\(\underline{\mathrm{p}}\) Generator_p_min_pu over \(\mathcal{G} \times \Xi \times \mathcal{T}\) — least output, per unit of nominal power
\(\mathrm{nonneg}\) Generator_p_min_pu_nonneg over \(\mathcal{G}\) — true where none of the generator's own minimums-per-unit is negative. It is PyPSA's per-unit (p_min_pu >= 0).all() over every snapshot and scenario, data prep
\(\mathrm{p}^{\mathrm{nom}}\) Generator_p_nom over \(\mathcal{G} \times \Xi\) — nominal power
\(\mathrm{ext}\) Generator_p_nom_extendable over \(\mathcal{G}\) — whether the nominal power is a decision
\(\overline{\mathrm{p}}^{\mathrm{nom}}\) Generator_p_nom_max over \(\mathcal{G} \times \Xi\) — most nominal power an extendable generator may be built at
\(\underline{\mathrm{p}}^{\mathrm{nom}}\) Generator_p_nom_min over \(\mathcal{G} \times \Xi\) — least nominal power an extendable generator may be built at
\(\mathrm{p}^{\mathrm{mod}}\) Generator_p_nom_mod over \(\mathcal{G}\), absent where the data has no row — the module size a build comes in whole numbers of. No value means the build is continuous
\(\mathrm{p}^{\mathrm{nom,set}}\) Generator_p_nom_set over \(\mathcal{G}\), absent where the data has no row — a given nominal power for an extendable generator. One without a value has no row here
\(\mathrm{p}^{\mathrm{set}}\) Generator_p_set over \(\mathcal{G} \times \Xi \times \mathcal{T}\), absent where the data has no row — a given output schedule. A generator without one has no row here
\(\mathrm{a}\) Generator_primary_energy_weight over \(\mathcal{G} \times \mathcal{L} \times \Xi \times \mathcal{T}\), absent where the data has no row — the constrained attribute per unit of energy at the bus: the carrier's co2_emissions over the generator's efficiency at the snapshot, data prep. A generator of an unweighted carrier has no row
\(\mathrm{sgn}\) Generator_sign over \(\mathcal{G}\) — the sign output enters its bus's balance with. It is PyPSA's sign, 1 unless given, -1 for a unit that draws power. PyPSA refuses one that differs by scenario (constants.py:46)
\(\mathrm{m}\) Generator_tech_capacity_weight over \(\mathcal{G} \times \mathcal{L}\), absent where the data has no row — one where the generator is in the row's carrier-and-bus set, data prep. One outside it, or one that is not active in the row's investment_period, has no row

Conditions#

Symbol Meaning
\(\mathrm{on}^{\mathrm{com}}\) Generator_committed over \(\mathcal{G} \times \mathcal{T}\) — a committable generator that is active in the snapshot's period: every unit-commitment row's set
\(\mathrm{lp}\) Generator_marginal_cost_as_lp (scalar) — linopy's choice of method for all generator cost curves at once (method="auto"): the segment lines where every curve is convex and no generator with a curve is committable, the incremental rows otherwise. PyPSA gates the curves by Generator-status where one is committable, and linopy states no segment lines under a gate (optimize.py:314-318, piecewise.py:200-224)
\(\mathrm{curve}\) Generator_marginal_cost_curve over \(\mathcal{G}\) — a generator that pays along a piecewise marginal_cost curve: one of PyPSA's active_assets with curve data (optimize.py:307, piecewise.py:230-257)
\(\mathrm{bend}\) Generator_marginal_cost_curve_bends_down over \(\mathcal{B} \times \mathcal{G}\) — a breakpoint where a generator's cost curve gets less steep: the segment after it rises less per unit of output than the one before

Variables#

Symbol Meaning
\(C^{\mathrm{curve}}\) Generator_marginal_cost_piecewise over \(\mathcal{G} \times \Xi \times \mathcal{T}\) — Generator-marginal_cost_piecewise — the running cost of a generator that pays along a curve, held above the curve
\(N\) Generator_n_mod over \(\mathcal{G}\) — Generator-n_mod — how many modules of an extendable modular build. A unit that is not active has none
\(p\) Generator_p over \(\mathcal{G} \times \Xi \times \mathcal{T}\) — Generator-p — output of a generator in a snapshot
\(P\) Generator_p_nom_ext over \(\mathcal{G}\) — Generator-p_nom — nominal power where it is a decision, for a generator that is active. A fixed build is the parameter of the same PyPSA name

Definitions#

Symbol Meaning
\(\mathit{Generator\_additions}\) Generator_additions over \(\mathcal{I} \times \mathcal{Y}\)
\(\mathit{Generator\_capex}\) Generator_capex (scalar)
\(\mathit{Generator\_injection}\) Generator_injection over \(\mathcal{N} \times \Xi \times \mathcal{T}\)
\(\mathrm{M}\) Generator_modules over \(\mathcal{G} \times \Xi\) — how many modules a committed unit's status counts: the chosen number where the build is extendable and modular, and the modules installed otherwise (constraints.py:656-661)
\(\mathit{Generator\_operational\_limit}\) Generator_operational_limit over \(\mathcal{L} \times \Xi\)
\(\mathit{Generator\_opex}\) Generator_opex over \(\Xi\)
\(\widehat{\mathrm{p}}^{\mathrm{nom}}\) Generator_p_nom_committed over \(\mathcal{G} \times \Xi\) — the build a committed unit's ramp rows are taken against: one module where the build is modular, since its status counts modules, and the given build otherwise (constraints.py:1131-1134)
\(\widetilde{\mathrm{p}}^{\mathrm{nom}}\) Generator_p_nom_effective over \(\mathcal{G} \times \Xi\) — the build a generator's limits are taken against: the chosen one where it is extendable, the given one otherwise
\(\mathit{Generator\_primary\_energy}\) Generator_primary_energy over \(\mathcal{L} \times \Xi\)
\(C^{\mathrm{run}}\) Generator_running_cost over \(\mathcal{G} \times \Xi \times \mathcal{T}\) — what a generator's output costs in a snapshot: the value read off its piecewise curve where it has one, and its linear marginal_cost otherwise. A curve replaces the linear cost, as PyPSA drops the unit from that term (optimize.py:333-348)
\(\mathit{Generator\_tech\_capacity\_expansion}\) Generator_tech_capacity_expansion over \(\mathcal{L}\)

Notation#

Symbol Meaning
\(t \boxminus_{v} k\) edge=v — \(t-k\), and \(v\) past the edge

Conditions#

Generator_committed

\[ \mathrm{on}^{\mathrm{com}}_{g,t} \iff \mathrm{com}_{g} \wedge \mathrm{on}_{g,t} \qquad \forall\, g \in \mathcal{G},\ t \in \mathcal{T} \]

Generator_marginal_cost_as_lp

\[ \mathrm{lp} \iff \lvert \{ g \in \mathcal{G} \,:\, \mathrm{curve}_{g} \wedge \lvert \{ b \in \mathcal{B} \,:\, \mathrm{bend}_{b,g} \} \rvert > 0 \} \rvert = 0 \wedge \lvert \{ g \in \mathcal{G} \,:\, \mathrm{curve}_{g} \wedge \mathrm{com}_{g} \} \rvert = 0 \]

Generator_marginal_cost_curve

\[ \mathrm{curve}_{g} \iff \lvert \{ b \in \mathcal{B} \,:\, \mathrm{p}^{\mathrm{curve}}_{b,g} \text{ is defined} \} \rvert > 0 \wedge \mathrm{act}_{g} \qquad \forall\, g \in \mathcal{G} \]

Generator_marginal_cost_curve_bends_down

\[ \mathrm{bend}_{b,g} \iff \mathrm{p}^{\mathrm{curve}}_{b,g} \text{ is defined} \wedge \mathrm{p}^{\mathrm{curve}}_{b - 1,g} \text{ is defined} \wedge \mathrm{p}^{\mathrm{curve}}_{b + 1,g} \text{ is defined} \wedge \left( \mathrm{C}^{\mathrm{curve}}_{b,g} - \mathrm{C}^{\mathrm{curve}}_{b \boxminus_{0} 1,g} \right) \cdot \left( \mathrm{p}^{\mathrm{curve}}_{b \boxplus_{0} 1,g} - \mathrm{p}^{\mathrm{curve}}_{b,g} \right) > \left( \mathrm{C}^{\mathrm{curve}}_{b \boxplus_{0} 1,g} - \mathrm{C}^{\mathrm{curve}}_{b,g} \right) \cdot \left( \mathrm{p}^{\mathrm{curve}}_{b,g} - \mathrm{p}^{\mathrm{curve}}_{b \boxminus_{0} 1,g} \right) \qquad \forall\, b \in \mathcal{B},\ g \in \mathcal{G} \]

Variable domains#

Generator_marginal_cost_piecewise

\[ C^{\mathrm{curve}}_{g,\xi,t} \in \mathbb{R} \qquad \forall\, g \in \mathcal{G},\ \xi \in \Xi,\ t \in \mathcal{T} \,:\, \mathrm{curve}_{g} \wedge \mathrm{on}_{g,t} \]

Generator_n_mod

\[ N_{g} \ge 0, N_{g} \in \mathbb{Z} \qquad \forall\, g \in \mathcal{G} \,:\, \mathrm{ext}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \wedge \mathrm{act}_{g} \]

Generator_p

\[ p_{g,\xi,t} \in \mathbb{R} \qquad \forall\, g \in \mathcal{G},\ \xi \in \Xi,\ t \in \mathcal{T} \,:\, \mathrm{on}_{g,t} \]

Generator_p_nom_ext

\[ P_{g} \in \mathbb{R} \qquad \forall\, g \in \mathcal{G} \,:\, \mathrm{ext}_{g} \wedge \mathrm{act}_{g} \]

Definitions#

Generator_additions

\[ \mathit{Generator\_additions}_{i,y} = \sum_{g \in \mathcal{G} \,:\, \mathrm{carrier}(g) = i} P_{g} \cdot \mathrm{new}_{g,y} \qquad \forall\, i \in \mathcal{I},\ y \in \mathcal{Y} \]

Generator_capex

\[ \mathit{Generator\_capex} = \sum_{g \in \mathcal{G},\ \xi \in \Xi} \pi_{\xi} \cdot P_{g} \cdot \mathrm{c}^{\mathrm{cap}}_{g,\xi} \cdot \mathrm{W}_{g} \]

Generator_injection

\[ \mathit{Generator\_injection}_{n,\xi,t} = \sum_{g \in \mathcal{G} \,:\, \mathrm{bus}(g) = n} \mathrm{sgn}_{g} \cdot p_{g,\xi,t} \qquad \forall\, n \in \mathcal{N},\ \xi \in \Xi,\ t \in \mathcal{T} \]

Generator_modules

\[ \mathrm{M}_{g,\xi} = \begin{cases} N_{g} & \text{if } \mathrm{ext}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \\ \mathrm{N}^{\mathrm{fix}}_{g,\xi} & \text{otherwise} \end{cases} \qquad \forall\, g \in \mathcal{G},\ \xi \in \Xi \]

Generator_operational_limit

\[ \mathit{Generator\_operational\_limit}_{l,\xi} = \sum_{g \in \mathcal{G}} \sum_{t \in \mathcal{T}} p_{g,\xi,t} \cdot \mathit{w}^{\mathrm{gc}}_{l,\xi,t} \cdot \mathrm{b}_{g,l,\xi} \qquad \forall\, l \in \mathcal{L},\ \xi \in \Xi \]

Generator_opex

\[ \mathit{Generator\_opex}_{\xi} = \sum_{t \in \mathcal{T}} \sum_{g \in \mathcal{G}} C^{\mathrm{run}}_{g,\xi,t} \cdot \mathrm{w}_{t} \cdot \mathrm{w}^{y}_{\mathrm{period}(t)} + \sum_{t \in \mathcal{T}} \sum_{g \in \mathcal{G}} p_{g,\xi,t} \cdot p_{g,\xi,t} \cdot \mathrm{c}^{(2)}_{g,\xi,t} \cdot \mathrm{w}_{t} \cdot \mathrm{w}^{y}_{\mathrm{period}(t)} \qquad \forall\, \xi \in \Xi \]

Generator_p_nom_committed

\[ \widehat{\mathrm{p}}^{\mathrm{nom}}_{g,\xi} = \begin{cases} \mathrm{p}^{\mathrm{mod}}_{g} & \text{if } \mathrm{p}^{\mathrm{mod}}_{g} > 0 \\ \mathrm{p}^{\mathrm{nom}}_{g,\xi} & \text{otherwise} \end{cases} \qquad \forall\, g \in \mathcal{G},\ \xi \in \Xi \]

Generator_p_nom_effective

\[ \widetilde{\mathrm{p}}^{\mathrm{nom}}_{g,\xi} = \begin{cases} P_{g} & \text{if } \mathrm{ext}_{g} \\ \mathrm{p}^{\mathrm{nom}}_{g,\xi} & \text{otherwise} \end{cases} \qquad \forall\, g \in \mathcal{G},\ \xi \in \Xi \]

Generator_primary_energy

\[ \mathit{Generator\_primary\_energy}_{l,\xi} = \sum_{g \in \mathcal{G}} \sum_{t \in \mathcal{T}} p_{g,\xi,t} \cdot \mathit{w}^{\mathrm{gc}}_{l,\xi,t} \cdot \mathrm{a}_{g,l,\xi,t} \qquad \forall\, l \in \mathcal{L},\ \xi \in \Xi \]

Generator_running_cost

\[ C^{\mathrm{run}}_{g,\xi,t} = \begin{cases} C^{\mathrm{curve}}_{g,\xi,t} & \text{if } \mathrm{curve}_{g} \\ p_{g,\xi,t} \cdot \mathrm{c}_{g,\xi,t} & \text{otherwise} \end{cases} \qquad \forall\, g \in \mathcal{G},\ \xi \in \Xi,\ t \in \mathcal{T} \]

Generator_tech_capacity_expansion

\[ \mathit{Generator\_tech\_capacity\_expansion}_{l} = \sum_{g \in \mathcal{G}} P_{g} \cdot \mathrm{m}_{g,l} \qquad \forall\, l \in \mathcal{L} \]

Subject to#

Generator_e_sum_max

\[ \sum_{t \in \mathcal{T}} p_{g,\xi,t} \cdot \mathit{w}^{E}_{t} \le \overline{\mathrm{E}}_{g,\xi} \qquad \forall\, g \in \mathcal{G},\ \xi \in \Xi \,:\, \overline{\mathrm{E}}_{g,\xi} \text{ is defined} \wedge \lvert \{ t \in \mathcal{T} \,:\, \mathrm{on}_{g,t} \} \rvert > 0 \]

Generator_e_sum_min

\[ \sum_{t \in \mathcal{T}} p_{g,\xi,t} \cdot \mathit{w}^{E}_{t} \ge \underline{\mathrm{E}}_{g,\xi} \qquad \forall\, g \in \mathcal{G},\ \xi \in \Xi \,:\, \underline{\mathrm{E}}_{g,\xi} \text{ is defined} \wedge \lvert \{ t \in \mathcal{T} \,:\, \mathrm{on}_{g,t} \} \rvert > 0 \]

Generator_ext_p_lower

\[ p_{g,\xi,t} \ge \underline{\mathrm{p}}_{g,\xi,t} \cdot \left( P_{g} - \gamma_{g,\xi} \cdot \mu^{\mathrm{nom}}_{g,\xi,t} \right) \qquad \forall\, g \in \mathcal{G},\ \xi \in \Xi,\ t \in \mathcal{T} \,:\, \mathrm{ext}_{g} \wedge \neg \mathrm{com}_{g} \wedge \mathrm{on}_{g,t} \]

Generator_ext_p_nom_lower

\[ P_{g} \ge \underline{\mathrm{p}}^{\mathrm{nom}}_{g,\xi} \qquad \forall\, g \in \mathcal{G},\ \xi \in \Xi \,:\, \mathrm{ext}_{g} \wedge \mathrm{act}_{g} \]

Generator_ext_p_nom_upper

\[ P_{g} \le \overline{\mathrm{p}}^{\mathrm{nom}}_{g,\xi} \qquad \forall\, g \in \mathcal{G},\ \xi \in \Xi \,:\, \mathrm{ext}_{g} \wedge \mathrm{act}_{g} \wedge \overline{\mathrm{p}}^{\mathrm{nom}}_{g,\xi} \text{ is defined} \]

Generator_ext_p_upper

\[ p_{g,\xi,t} \le \overline{\mathrm{p}}_{g,\xi,t} \cdot \left( P_{g} - \gamma_{g,\xi} \cdot \mu^{\mathrm{nom}}_{g,\xi,t} \right) \qquad \forall\, g \in \mathcal{G},\ \xi \in \Xi,\ t \in \mathcal{T} \,:\, \mathrm{ext}_{g} \wedge \neg \mathrm{com}_{g} \wedge \mathrm{on}_{g,t} \]

Generator_fix_p_lower

\[ p_{g,\xi,t} \ge \underline{\mathrm{p}}_{g,\xi,t} \cdot \mathrm{p}^{\mathrm{nom}}_{g,\xi} \cdot \left( 1 - \gamma_{g,\xi} \cdot \mu_{g,\xi,t} \right) \qquad \forall\, g \in \mathcal{G},\ \xi \in \Xi,\ t \in \mathcal{T} \,:\, \neg \mathrm{ext}_{g} \wedge \neg \mathrm{com}_{g} \wedge \mathrm{on}_{g,t} \]

Generator_fix_p_upper

\[ p_{g,\xi,t} \le \overline{\mathrm{p}}_{g,\xi,t} \cdot \mathrm{p}^{\mathrm{nom}}_{g,\xi} \cdot \left( 1 - \gamma_{g,\xi} \cdot \mu_{g,\xi,t} \right) \qquad \forall\, g \in \mathcal{G},\ \xi \in \Xi,\ t \in \mathcal{T} \,:\, \neg \mathrm{ext}_{g} \wedge \neg \mathrm{com}_{g} \wedge \mathrm{on}_{g,t} \]

Generator_p_nom_modularity

\[ P_{g} = \mathrm{p}^{\mathrm{mod}}_{g} \cdot N_{g} \qquad \forall\, g \in \mathcal{G} \,:\, \mathrm{ext}_{g} \wedge \mathrm{p}^{\mathrm{mod}}_{g} > 0 \wedge \mathrm{act}_{g} \]

Generator_p_nom_set

\[ P_{g} = \mathrm{p}^{\mathrm{nom,set}}_{g} \qquad \forall\, g \in \mathcal{G} \,:\, \mathrm{ext}_{g} \wedge \mathrm{act}_{g} \wedge \mathrm{p}^{\mathrm{nom,set}}_{g} \text{ is defined} \]

Generator_p_set

\[ p_{g,\xi,t} = \mathrm{p}^{\mathrm{set}}_{g,\xi,t} \qquad \forall\, g \in \mathcal{G},\ \xi \in \Xi,\ t \in \mathcal{T} \,:\, \mathrm{p}^{\mathrm{set}}_{g,\xi,t} \text{ is defined} \wedge \mathrm{on}_{g,t} \]

Assumptions#

Generator_marginal_cost_curve_on_a_fixed_build

\[ \neg \mathrm{ext}_{g} \wedge \mathrm{p}^{\mathrm{nom}}_{g,\xi} > 0 \wedge \mathrm{p}^{\mathrm{nom}}_{g,\xi} < \infty \qquad \forall\, g \in \mathcal{G},\ \xi \in \Xi \,:\, \mathrm{curve}_{g} \]

Generator_marginal_cost_quadratic_without_risk_preference

\[ \mathrm{c}^{(2)}_{g,\xi,t} = 0 \qquad \forall\, g \in \mathcal{G},\ \xi \in \Xi,\ t \in \mathcal{T} \,:\, \omega \text{ is defined} \]

See also