transport¶
A network: generators sit on buses, lines connect buses, and power balances at every bus.
✔ Agrees with hand-written linopy 0.10.0 — objective 4400, matched to
rtol=1e-09.
The problem¶
Each sum runs over the generators or lines a relation sends to bus \(b\). \(\mathrm{bus}\), \(\mathrm{to}\) and \(\mathrm{from}\) are relations the dimensions declare, not sets in their own right. Load is \(d\) here, because \(\ell\) is already the line index.
The model¶
The same model, as math
Least-cost dispatch over a network, where a generator sits on a bus, a line joins two of them, and what is generated has to reach the load over the lines.
Sets¶
| Symbol | Meaning |
|---|---|
| \(\mathcal{S}\) | index \(s\) — snapshot — dispatch periods |
| \(\mathcal{G}\) | index \(g\) — generator with \(\mathrm{gen\_bus}: \mathcal{G} \to \mathcal{B}\) — generating units, each sitting on one bus |
| \(\mathcal{B}\) | index \(b\) — bus with \(\mathrm{gen\_bus}: \mathcal{G} \to \mathcal{B},\ \mathrm{line\_from}: \mathcal{L} \to \mathcal{B},\ \mathrm{line\_to}: \mathcal{L} \to \mathcal{B}\) — network nodes |
| \(\mathcal{L}\) | index \(\ell\) — line with \(\mathrm{line\_from}: \mathcal{L} \to \mathcal{B},\ \mathrm{line\_to}: \mathcal{L} \to \mathcal{B}\) — transmission lines, each joining two buses |
Parameters¶
| Symbol | Meaning |
|---|---|
| \(\bar p\) | p_max over \(\mathcal{G}\) — installed capacity |
| \(c\) | cost over \(\mathcal{G}\) — marginal cost |
| \(\bar f\) | cap over \(\mathcal{L}\) — forward transmission limit |
| \(\underline{f}\) | neg_cap over \(\mathcal{L}\) — reverse transmission limit |
| \(d\) | load over \(\mathcal{S} \times \mathcal{B}\) — demand at each bus |
Variables¶
| Symbol | Meaning |
|---|---|
| \(p\) | p over \(\mathcal{S} \times \mathcal{G}\) — output of a generator in a snapshot |
| \(f\) | f over \(\mathcal{S} \times \mathcal{L}\) — flow on a line, signed towards its line_to bus |
Definitions¶
| Symbol | Meaning |
|---|---|
| \(\mathit{gen\_at\_bus}\) | gen_at_bus over \(\mathcal{S} \times \mathcal{B}\) — what the generators sitting on a bus produce there |
| \(\mathit{net\_inflow}\) | net_inflow over \(\mathcal{S} \times \mathcal{B}\) — flow arriving at a bus minus flow leaving it, so a negative value is a net export |
Objective¶
Subject to¶
balance
Definitions¶
gen_at_bus
net_inflow
Variable domains¶
p
f
The tabs start from the instance's tables — one frame per parameter.
description: >-
Least-cost dispatch over a network, where a generator sits on a bus, a line
joins two of them, and what is generated has to reach the load over the
lines.
dimensions:
snapshot:
description: dispatch periods
dtype: int
generator:
description: generating units, each sitting on one bus
dtype: str
bus:
description: network nodes
dtype: str
line:
description: transmission lines, each joining two buses
dtype: str
relations:
gen_bus:
description: the bus a generator sits on
key: generator
values: bus
line_from:
description: the bus a line leaves
key: line
values: bus
line_to:
description: the bus a line arrives at
key: line
values: bus
parameters:
p_max:
description: installed capacity
dims: [generator]
cost:
description: marginal cost
dims: [generator]
cap:
description: forward transmission limit
dims: [line]
neg_cap:
description: reverse transmission limit
dims: [line]
load:
description: demand at each bus
dims: [snapshot, bus]
variables:
p:
description: output of a generator in a snapshot
dims: [snapshot, generator]
bounds:
lower: 0
upper: p_max
f:
description: flow on a line, signed towards its `line_to` bus
dims: [snapshot, line]
bounds:
lower: neg_cap
upper: cap
expressions:
gen_at_bus:
expression: sum(p, by=gen_bus, over=generator, into=bus)
description: what the generators sitting on a bus produce there
net_inflow:
expression: sum(f, by=line_to, over=line, into=bus) - sum(f, by=line_from, over=line, into=bus)
description: flow arriving at a bus minus flow leaving it, so a negative value is a net export
constraints:
balance:
description: what is generated at a bus plus what arrives over the lines meets the load there
dims: [snapshot, bus]
expression: gen_at_bus + net_inflow == load
objective:
sense: minimize
description: total cost of generation; moving power over a line is free here
expression: sum(p * cost)
The model-building half of examples/ports/references/linopy/transport.py:
def build(tables: dict[str, pd.DataFrame]) -> linopy.Model:
"""The instance's tables as a linopy model, row for row.
``tables`` is the same mapping the specsolve call attaches as ``sources``.
"""
p_max: pd.Series = tables['p_max'].set_index('generator')['value']
cost: pd.Series = tables['cost'].set_index('generator')['value']
cap: pd.Series = tables['cap'].set_index('line')['value']
neg_cap: pd.Series = tables['neg_cap'].set_index('line')['value']
load = xr.DataArray(tables['load'].pivot(index='snapshot', columns='bus', values='value'))
snapshots, buses = load.indexes['snapshot'], load.indexes['bus']
gen_at = pd.DataFrame(0.0, index=buses, columns=p_max.index)
for gen, bus in zip(tables['gen_bus']['generator'], tables['gen_bus']['bus'], strict=True):
gen_at.loc[bus, gen] = 1.0
flow_in = pd.DataFrame(0.0, index=buses, columns=cap.index)
for line, src, dst in zip(
tables['line_from']['line'], tables['line_from']['bus'], tables['line_to']['bus'], strict=True
):
flow_in.loc[dst, line] += 1.0
flow_in.loc[src, line] -= 1.0
m = linopy.Model()
p = m.add_variables(lower=0, upper=p_max, coords=[snapshots, p_max.index], name='p')
f = m.add_variables(lower=neg_cap, upper=cap, coords=[snapshots, cap.index], name='f')
m.add_constraints(
(p * xr.DataArray(gen_at)).sum('generator') + (f * xr.DataArray(flow_in)).sum('line') == load,
name='balance',
)
m.add_objective((p * cost).sum())
return m
examples/transport.yaml · back to all models