Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,10 @@ CHANGELOG.md
# hand. `index.md` is not listed — it carries no generated block.
docs/examples/dispatch.md
docs/examples/operators.md

# `tools/notation.py` writes this page's block, and it is the same trap: the
# legend tables it emits are the same unpadded ones. The page was not listed
# when the rule was written, so every regeneration since has been padded back
# by the hook and the generator's output has never been what the file holds —
# which is why nothing could compare them.
docs/reference/notation.md
68 changes: 33 additions & 35 deletions docs/reference/notation.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ $\ell_t$. A [symbol table](typeset.md#symbol-tables) replaces them wholesale
and changes nothing else on this page.

<!-- notation:begin -->

### The legend

A dimension, a lookup and a parameter declare no equation; what they print is the legend every model opens with.
Expand Down Expand Up @@ -71,46 +70,46 @@ parameters:

#### Sets

| Symbol | Meaning |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| $\mathcal{T}$ | index $t$ — `snapshot` with $\mathrm{season\_of}: \mathcal{T} \to \mathcal{S}$ |
| Symbol | Meaning |
|---|---|
| $\mathcal{T}$ | index $t$ — `snapshot` with $\mathrm{season\_of}: \mathcal{T} \to \mathcal{S}$ |
| $\mathcal{G}$ | index $g$ — `generator` with $\mathrm{gen\_bus}: \mathcal{G} \to \mathcal{B},\enspace \mathrm{gen\_tech}: \mathcal{G} \to \mathcal{E}$ carrying label $\mathrm{tech}$ |
| $\mathcal{B}$ | index $b$ — `bus` with $\mathrm{zone\_of}: \mathcal{B} \to \mathcal{Z},\enspace \mathrm{area\_of}: \mathcal{B} \to \mathcal{Z}$ |
| $\mathcal{Z}$ | index $z$ — `zone` |
| $\mathcal{S}$ | index $s$ — `season` |
| $\mathcal{E}$ | index $e$ — `technology` |
| $\mathcal{B}$ | index $b$ — `bus` with $\mathrm{zone\_of}: \mathcal{B} \to \mathcal{Z},\enspace \mathrm{area\_of}: \mathcal{B} \to \mathcal{Z}$ |
| $\mathcal{Z}$ | index $z$ — `zone` |
| $\mathcal{S}$ | index $s$ — `season` |
| $\mathcal{E}$ | index $e$ — `technology` |

#### Parameters

| Symbol | Meaning |
| ------------------------------ | ------------------------------------------------ |
| $p^{\mathrm{max}}$ | `p_max` over $\mathcal{G}$ |
| $p^{\mathrm{min}}$ | `p_min` over $\mathcal{G}$ |
| $\mathit{cost}$ | `cost` over $\mathcal{G}$ |
| $\mathit{load}$ | `load` over $\mathcal{T} \times \mathcal{B}$ |
| $\mathit{is\_flexible}$ | `is_flexible` over $\mathcal{G}$ |
| $\mathit{zone}^{\mathrm{cap}}$ | `zone_cap` over $\mathcal{Z}$ |
| $\mathit{tech\_cap}$ | `tech_cap` over $\mathcal{B} \times \mathcal{E}$ |
| $\mathit{min\_up}$ | `min_up` over $\mathcal{G}$ |
| $\mathit{lead}$ | `lead` over $\mathcal{G}$ |
| $\mathit{budget}$ | `budget` (scalar) |
| $\mathit{growth}$ | `growth` (scalar) |
| Symbol | Meaning |
|---|---|
| $p^{\mathrm{max}}$ | `p_max` over $\mathcal{G}$ |
| $p^{\mathrm{min}}$ | `p_min` over $\mathcal{G}$ |
| $\mathit{cost}$ | `cost` over $\mathcal{G}$ |
| $\mathit{load}$ | `load` over $\mathcal{T} \times \mathcal{B}$ |
| $\mathit{is\_flexible}$ | `is_flexible` over $\mathcal{G}$ |
| $\mathit{zone}^{\mathrm{cap}}$ | `zone_cap` over $\mathcal{Z}$ |
| $\mathit{tech\_cap}$ | `tech_cap` over $\mathcal{B} \times \mathcal{E}$ |
| $\mathit{min\_up}$ | `min_up` over $\mathcal{G}$ |
| $\mathit{lead}$ | `lead` over $\mathcal{G}$ |
| $\mathit{budget}$ | `budget` (scalar) |
| $\mathit{growth}$ | `growth` (scalar) |

#### Variables

| Symbol | Meaning |
| ------------------- | ---------------------------------------------- |
| $p$ | `p` over $\mathcal{T} \times \mathcal{G}$ |
| $\mathit{spill}$ | `spill` over $\mathcal{T}$ |
| $\mathit{slack}$ | `slack` over $\mathcal{T}$ |
| $\mathit{theta}$ | `theta` over $\mathcal{B}$ |
| $\mathit{on}$ | `on` over $\mathcal{T} \times \mathcal{G}$ |
| $\mathit{units}$ | `units` over $\mathcal{G}$ |
| $\mathit{spare}$ | `spare` over $\mathcal{G}$ |
| $\mathit{reserve}$ | `reserve` (scalar) |
| $\mathit{headroom}$ | `headroom` (scalar) |
| $\mathit{void}$ | `void` over $\mathcal{B}$ |
| $\mathit{weight}$ | `weight` over $\mathcal{T} \times \mathcal{G}$ |
| Symbol | Meaning |
|---|---|
| $p$ | `p` over $\mathcal{T} \times \mathcal{G}$ |
| $\mathit{spill}$ | `spill` over $\mathcal{T}$ |
| $\mathit{slack}$ | `slack` over $\mathcal{T}$ |
| $\mathit{theta}$ | `theta` over $\mathcal{B}$ |
| $\mathit{on}$ | `on` over $\mathcal{T} \times \mathcal{G}$ |
| $\mathit{units}$ | `units` over $\mathcal{G}$ |
| $\mathit{spare}$ | `spare` over $\mathcal{G}$ |
| $\mathit{reserve}$ | `reserve` (scalar) |
| $\mathit{headroom}$ | `headroom` (scalar) |
| $\mathit{void}$ | `void` over $\mathcal{B}$ |
| $\mathit{weight}$ | `weight` over $\mathcal{T} \times \mathcal{G}$ |

$t \ominus k$ denotes cyclic translation: index $t-k$ taken modulo the size of the dimension (`roll`). Plain $t-k$ (`shift`) has no wraparound — terms translated past the edge are simply absent.

Expand Down Expand Up @@ -661,5 +660,4 @@ adjacent:
```

$$\left( \mathit{weight}_{t,g} \right)_{g \in \mathcal{G}} \in \mathrm{SOS}2 \qquad \forall\thinspace t \in \mathcal{T}$$

<!-- notation:end -->
66 changes: 66 additions & 0 deletions examples/piecewise.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# SPDX-FileCopyrightText: math-spec Contributors
#
# SPDX-License-Identifier: MIT

description: >-
Least-cost dispatch where each generator's cost curve is piecewise-linear in
its output, expanded into a lambda formulation.

dimensions:
snapshot:
description: dispatch periods
dtype: int
generator:
description: dispatchable units
dtype: str
bp:
description: breakpoints of the cost curve
dtype: int

parameters:
p_max:
description: maximum dispatch
dims: [generator]
load:
description: demand to be met
dims: [snapshot]
bp_x:
description: breakpoint dispatch levels, one curve per generator
dims: [generator, bp]
bp_y:
description: cost at each breakpoint, one curve per generator
dims: [generator, bp]

variables:
p:
description: dispatched power
foreach: [snapshot, generator]
bounds:
lower: 0
upper: p_max
op_cost:
description: operating cost, piecewise-linear in dispatch
foreach: [snapshot, generator]
bounds:
lower: 0

piecewise:
cost_curve:
description: >-
cost read off the generator's curve — convex, so the weights need no
binaries to keep them on one segment
over: bp
links:
- [p, bp_x]
- [op_cost, bp_y]
method: convex

constraints:
balance:
foreach: [snapshot]
expression: sum(p, over=generator) == load

objective:
sense: minimize
description: total operating cost, taken off the curves rather than from a marginal rate
expression: sum(op_cost)
71 changes: 71 additions & 0 deletions examples/piecewise_lp.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# SPDX-FileCopyrightText: math-spec Contributors
#
# SPDX-License-Identifier: MIT

description: >-
The same least-cost dispatch as `piecewise.yaml`, with each generator's cost
curve stated as the lines its segments lie on rather than interpolated
between its breakpoints. The curve is convex and the objective pushes the
cost down, so a cost above every segment line settles on the curve — which
needs no interpolation weights, and so declares no auxiliary variable at all.

dimensions:
snapshot:
description: dispatch periods
dtype: int
generator:
description: dispatchable units
dtype: str
bp:
description: breakpoints of the cost curve
dtype: int

parameters:
p_max:
description: maximum dispatch
dims: [generator]
load:
description: demand to be met
dims: [snapshot]
bp_x:
description: breakpoint dispatch levels, one curve per generator
dims: [generator, bp]
bp_y:
description: cost at each breakpoint, one curve per generator
dims: [generator, bp]

variables:
p:
description: dispatched power
foreach: [snapshot, generator]
bounds:
lower: 0
upper: p_max
op_cost:
description: operating cost, held above every segment of the generator's curve
foreach: [snapshot, generator]
bounds:
lower: 0

piecewise:
cost_curve:
description: >-
cost bounded below by the curve — the `>=` is what says which side of the
lines the cost sits on, and the curvature has to match it: lines that
envelope a convex curve would cut a concave one, and the solve comes back
optimal either way
over: bp
links:
- [p, bp_x]
- [op_cost, bp_y, ">="]
method: lp

constraints:
balance:
foreach: [snapshot]
expression: sum(p, over=generator) == load

objective:
sense: minimize
description: total operating cost, taken off the curves rather than from a marginal rate
expression: sum(op_cost)
84 changes: 84 additions & 0 deletions examples/ports/transport_pwl.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# SPDX-FileCopyrightText: math-spec Contributors
#
# SPDX-License-Identifier: MIT

description: >-
Dantzig's transportation problem with economies of scale — GAMS model library
trnspwl. Shipping cost grows as the square root of the consignment rather
than linearly, so a big consignment is cheaper per unit. Optimum
8.786852757777865, from linopy's own piecewise formulation.

dimensions:
plant:
description: canning plants, with limited capacity
values: [seattle, san-diego]
market:
description: markets, with demand to be met
values: [new-york, chicago, topeka]
bp:
description: breakpoints of the discretised square-root curve
dtype: int

parameters:
capacity:
description: capacity of each plant
dims: [plant]
demand:
description: demand at each market
dims: [market]
distance:
description: distance from plant to market
dims: [plant, market]
freight:
description: freight rate per case per unit distance
dims: []
bp_x:
description: >-
breakpoint shipment levels — one curve, the same on every route, so it
carries the breakpoint dimension alone and broadcasts across the pairs
dims: [bp]
bp_y:
description: the curve's value at each breakpoint
dims: [bp]

variables:
shipment:
description: cases shipped from a plant to a market
foreach: [plant, market]
bounds:
lower: 0
scaled:
description: >-
what the objective is charged on — the square root of the shipment, read
off the curve rather than computed
foreach: [plant, market]
bounds:
lower: 0

piecewise:
economies_of_scale:
description: >-
the shipment priced through the discretisation GAMS publishes, on segment
binaries and deliberately not the convex method: the curve is concave and
this is a
minimisation, so the convex-hull relaxation would let the solver ride the
chord underneath the true curve and buy transport cheaper than the model
allows. The binaries are what make the answer right — and what make this
port a MILP.
over: bp
links:
- [shipment, bp_x]
- [scaled, bp_y]

constraints:
within_capacity:
foreach: [plant]
expression: sum(shipment, over=market) <= capacity
meet_demand:
foreach: [market]
expression: sum(shipment, over=plant) >= demand

objective:
sense: minimize
description: total freight, charged on the scaled consignment rather than on the shipment
expression: sum(scaled * distance * freight / 1000)
67 changes: 67 additions & 0 deletions examples/sos.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# SPDX-FileCopyrightText: math-spec Contributors
#
# SPDX-License-Identifier: MIT

description: >-
A piecewise-linear cost curve stated as a special-ordered set, so the solver
is handed the adjacency restriction rather than binaries that encode it.

dimensions:
snapshot:
description: dispatch periods
dtype: int
generator:
description: dispatchable units
dtype: str
bp:
description: breakpoints of the cost curve
dtype: int

parameters:
p_max:
description: maximum dispatch
dims: [generator]
load:
description: demand to be met
dims: [snapshot]
bp_x:
description: breakpoint dispatch levels, one curve per generator
dims: [generator, bp]
bp_y:
description: cost at each breakpoint, one curve per generator
dims: [generator, bp]

variables:
p:
description: dispatched power
foreach: [snapshot, generator]
bounds:
lower: 0
upper: p_max
op_cost:
description: operating cost, piecewise-linear in dispatch
foreach: [snapshot, generator]
bounds:
lower: 0

piecewise:
cost_curve:
description: >-
cost read off the generator's curve, with at most two adjacent weights
non-zero — the restriction the default method builds out of binaries,
declared as a set instead
over: bp
links:
- [p, bp_x]
- [op_cost, bp_y]
method: sos2

constraints:
balance:
foreach: [snapshot]
expression: sum(p, over=generator) == load

objective:
sense: minimize
description: total operating cost, taken off the curves rather than from a marginal rate
expression: sum(op_cost)
Loading
Loading