From 21c356379c99eaef22ac2b4b5a22162e566a7407 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 28 Sep 2026 11:08:50 +0000 Subject: [PATCH 1/2] docs(notation): the golden model and the notation page show a named expression with a declared frame `rating` declares `dims: [generator, snapshot]` over a body that carries only `generator`, and `under_rating` reads it over `[snapshot, generator]`. The golden output pins the frame as written and the broadcast along `snapshot`. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01W1VBZvT7UNJJexTrMX7mRi --- docs/reference/notation.md | 31 +++++++++++++++++++++++++++ tests/typesetting/golden/latex.out | 3 +++ tests/typesetting/golden/markdown.out | 13 +++++++++++ tests/typesetting/golden/model.yaml | 6 ++++++ tests/typesetting/golden/typst.out | 3 +++ tools/notation.py | 2 ++ 6 files changed, 58 insertions(+) diff --git a/docs/reference/notation.md b/docs/reference/notation.md index 030bc436..7f279842 100644 --- a/docs/reference/notation.md +++ b/docs/reference/notation.md @@ -123,6 +123,7 @@ parameters: |---|---| | $`\mathrm{spend}^{\mathrm{cap}}`$ | `spend_cap` over $`\mathcal{G}`$ | | $`\mathit{spend}`$ | `spend` over $`\mathcal{T}`$ — what a snapshot's dispatch costs | +| $`\mathrm{rating}`$ | `rating` over $`\mathcal{G} \times \mathcal{T}`$ | | $`\mathit{lcoe}`$ | `lcoe` (scalar) | | $`\mathit{marginal\_price}`$ | `marginal_price` over $`\mathcal{T} \times \mathcal{B}`$ | | $`\mathrm{startup\_cost}`$ | `startup_cost` over $`\mathcal{T} \times \mathcal{G}`$ — what starting a unit in this snapshot costs, which the horizon's edge changes | @@ -645,6 +646,21 @@ constraints: p_{t,g} \le \mathrm{startup\_cost}_{t,g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} ``` +#### Expression with a declared frame in a constraint + +names the expression with a declared frame: the row reads it at every snapshot, though its body has no snapshot + +```yaml +constraints: + under_rating: + dims: [snapshot, generator] + expression: p <= rating +``` + +```math +p_{t,g} \le \mathrm{rating}_{g,t} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} +``` + #### Plain named expression a plain named expression: its symbol prints where it is used, its body once as a definition @@ -659,6 +675,21 @@ expressions: \mathit{spend}_{t} = \sum_{g \in \mathcal{G}} p_{t,g} \cdot \mathrm{cost}_{g} \qquad \forall\, t \in \mathcal{T} ``` +#### Named expression with a declared frame + +a frame wider than the body, in an order of its own: the value is the same at every snapshot + +```yaml +expressions: + rating: + dims: [generator, snapshot] + expression: eta * p_max +``` + +```math +\mathrm{rating}_{g,t} = \mathrm{eta}_{g} \cdot \mathrm{p}^{\mathrm{max}}_{g} \qquad \forall\, g \in \mathcal{G},\ t \in \mathcal{T} +``` + #### Expression defined by cases a quantity defined by region: no two cases overlap, and `otherwise` is the rest diff --git a/tests/typesetting/golden/latex.out b/tests/typesetting/golden/latex.out index c69496c8..3d9b2e81 100644 --- a/tests/typesetting/golden/latex.out +++ b/tests/typesetting/golden/latex.out @@ -60,6 +60,7 @@ \begin{description} \item[{$\mathrm{spend}^{\mathrm{cap}}$}] \texttt{spend\_cap} over $\mathcal{G}$ \item[{$\mathit{spend}$}] \texttt{spend} over $\mathcal{T}$ --- what a snapshot's dispatch costs +\item[{$\mathrm{rating}$}] \texttt{rating} over $\mathcal{G} \times \mathcal{T}$ \item[{$\mathit{lcoe}$}] \texttt{lcoe} (scalar) \item[{$\mathit{marginal\_price}$}] \texttt{marginal\_price} over $\mathcal{T} \times \mathcal{B}$ \item[{$\mathrm{startup\_cost}$}] \texttt{startup\_cost} over $\mathcal{T} \times \mathcal{G}$ --- what starting a unit in this snapshot costs, which the horizon's edge changes @@ -88,6 +89,7 @@ \begin{align} \text{budgeted} && \mathit{spend}_{t} & \le \mathrm{budget} && \forall\, t \in \mathcal{T} \\ \text{starts} && p_{t,g} & \le \mathrm{startup\_cost}_{t,g} && \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \\ +\text{under\_rating} && p_{t,g} & \le \mathrm{rating}_{g,t} && \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \\ \text{balance} && \sum_{g \in \mathcal{G} \,:\, \mathrm{gen\_bus}(g) = b} p_{t,g} + \mathit{spill}_{t} - \mathit{slack}_{t} & = \mathrm{load}_{t,b} && \forall\, t \in \mathcal{T},\ b \in \mathcal{B} \\ \text{ramp} && p_{t,g} - p_{t \ominus 1,g} & \le p_{t - 1,g} + \mathrm{p}^{\mathrm{max}}_{g} && \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \\ \text{edges} && p_{t \boxminus_{0} 1,g} & \le p_{t \boxplus_{0} 1,g} + \mathrm{p}^{\mathrm{max}}_{g} && \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \\ @@ -143,6 +145,7 @@ \begin{align} \text{spend\_cap} && \mathrm{spend}^{\mathrm{cap}}_{g} & = \mathrm{cost}_{g} \cdot 2 && \forall\, g \in \mathcal{G} \\ \text{spend} && \mathit{spend}_{t} & = \sum_{g \in \mathcal{G}} p_{t,g} \cdot \mathrm{cost}_{g} && \forall\, t \in \mathcal{T} \\ +\text{rating} && \mathrm{rating}_{g,t} & = \mathrm{eta}_{g} \cdot \mathrm{p}^{\mathrm{max}}_{g} && \forall\, g \in \mathcal{G},\ t \in \mathcal{T} \\ \text{lcoe} && \mathit{lcoe} & = \frac{\sum_{t \in \mathcal{T},\ g \in \mathcal{G}} p_{t,g} \cdot \mathrm{cost}_{g}}{\sum_{t \in \mathcal{T},\ g \in \mathcal{G}} p_{t,g}} \\ \text{marginal\_price} && \mathit{marginal\_price}_{t,b} & = \lambda_{\mathrm{balance},t,b} && \forall\, t \in \mathcal{T},\ b \in \mathcal{B} \\ \text{startup\_cost} && \mathrm{startup\_cost}_{t,g} & = \begin{cases} \mathrm{cost}_{g} & \text{if } \mathrm{pos}(t) = 0 \\ \mathrm{cost}_{g} \cdot 2 & \text{if } \mathrm{pos}(t) > 0 \wedge \mathrm{season\_of}(t) = \text{'}\mathrm{winter}\text{'} \\ 0 & \text{otherwise} \end{cases} && \forall\, t \in \mathcal{T},\ g \in \mathcal{G} diff --git a/tests/typesetting/golden/markdown.out b/tests/typesetting/golden/markdown.out index 9d724f0f..3d966aaf 100644 --- a/tests/typesetting/golden/markdown.out +++ b/tests/typesetting/golden/markdown.out @@ -60,6 +60,7 @@ every character a notation escapes, set as text: link\_to, 100% & \#1 costs \$5 |---|---| | $`\mathrm{spend}^{\mathrm{cap}}`$ | `spend_cap` over $`\mathcal{G}`$ | | $`\mathit{spend}`$ | `spend` over $`\mathcal{T}`$ — what a snapshot's dispatch costs | +| $`\mathrm{rating}`$ | `rating` over $`\mathcal{G} \times \mathcal{T}`$ | | $`\mathit{lcoe}`$ | `lcoe` (scalar) | | $`\mathit{marginal\_price}`$ | `marginal_price` over $`\mathcal{T} \times \mathcal{B}`$ | | $`\mathrm{startup\_cost}`$ | `startup_cost` over $`\mathcal{T} \times \mathcal{G}`$ — what starting a unit in this snapshot costs, which the horizon's edge changes | @@ -98,6 +99,12 @@ $`\lvert \mathcal{T} \rvert`$ denotes the size of the set being counted along, a p_{t,g} \le \mathrm{startup\_cost}_{t,g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} ``` +**`under_rating`** + +```math +p_{t,g} \le \mathrm{rating}_{g,t} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} +``` + **`balance`** ```math @@ -406,6 +413,12 @@ p_{t,g} \le \mathrm{p}^{\mathrm{max}}_{g} \qquad \forall\, t \in \mathcal{T},\ g \mathit{spend}_{t} = \sum_{g \in \mathcal{G}} p_{t,g} \cdot \mathrm{cost}_{g} \qquad \forall\, t \in \mathcal{T} ``` +**`rating`** + +```math +\mathrm{rating}_{g,t} = \mathrm{eta}_{g} \cdot \mathrm{p}^{\mathrm{max}}_{g} \qquad \forall\, g \in \mathcal{G},\ t \in \mathcal{T} +``` + **`lcoe`** ```math diff --git a/tests/typesetting/golden/model.yaml b/tests/typesetting/golden/model.yaml index ebe77903..44cd81f4 100644 --- a/tests/typesetting/golden/model.yaml +++ b/tests/typesetting/golden/model.yaml @@ -143,6 +143,9 @@ expressions: spend: # a plain named expression: its symbol prints where it is used, its body once as a definition description: what a snapshot's dispatch costs expression: sum(p * cost, over=generator) + rating: # a frame wider than the body, in an order of its own: the value is the same at every snapshot + dims: [generator, snapshot] + expression: eta * p_max lcoe: sum(p * cost) / sum(p) # nothing in the math reads it, so its divisor may carry a variable marginal_price: dual(balance) # the row dual of a constraint, the one builtin only an entry the math never reads may call startup_cost: # a quantity defined by region: no two cases overlap, and `otherwise` is the rest @@ -160,6 +163,9 @@ constraints: starts: # names the cased expression: its symbol prints here, its block once below dims: [snapshot, generator] expression: p <= startup_cost + under_rating: # names the expression with a declared frame: the row reads it at every snapshot, though its body has no snapshot + dims: [snapshot, generator] + expression: p <= rating balance: # sum over a relation dims: [snapshot, bus] expression: sum(p, by=gen_bus, over=generator, into=bus) + spill - slack == load diff --git a/tests/typesetting/golden/typst.out b/tests/typesetting/golden/typst.out index cd926e14..22616c23 100644 --- a/tests/typesetting/golden/typst.out +++ b/tests/typesetting/golden/typst.out @@ -49,6 +49,7 @@ every character a notation escapes, set as text: link\_to, 100% & \#1 costs \$5 == Definitions / $upright("spend")^(upright("cap"))$: `spend_cap` over $cal(G)$ / $italic("spend")$: `spend` over $cal(T)$ --- what a snapshot's dispatch costs +/ $upright("rating")$: `rating` over $cal(G) times cal(T)$ / $italic("lcoe")$: `lcoe` (scalar) / $italic("marginal_price")$: `marginal_price` over $cal(T) times cal(B)$ / $upright("startup_cost")$: `startup_cost` over $cal(T) times cal(G)$ --- what starting a unit in this snapshot costs, which the horizon's edge changes @@ -75,6 +76,7 @@ $ & max & sum_(t in cal(T), g in cal(G)) p_(t,g) dot upright("cost")_(g) + sum_ #set math.equation(numbering: "(1)") $ upright("budgeted") & italic("spend")_(t) & <= upright("budget") & forall t in cal(T) \ upright("starts") & p_(t,g) & <= upright("startup_cost")_(t,g) & forall t in cal(T), g in cal(G) \ + upright("under_rating") & p_(t,g) & <= upright("rating")_(g,t) & forall t in cal(T), g in cal(G) \ upright("balance") & sum_(g in cal(G) colon upright("gen_bus")(g) = b) p_(t,g) + italic("spill")_(t) - italic("slack")_(t) & = upright("load")_(t,b) & forall t in cal(T), b in cal(B) \ upright("ramp") & p_(t,g) - p_(t minus.o 1,g) & <= p_(t - 1,g) + upright("p")^(upright("max"))_(g) & forall t in cal(T), g in cal(G) \ upright("edges") & p_(t minus.square_(0) 1,g) & <= p_(t plus.square_(0) 1,g) + upright("p")^(upright("max"))_(g) & forall t in cal(T), g in cal(G) \ @@ -129,6 +131,7 @@ $ upright("budgeted") & italic("spend")_(t) & <= upright("budget") & forall t in #set math.equation(numbering: "(1)") $ upright("spend_cap") & upright("spend")^(upright("cap"))_(g) & = upright("cost")_(g) dot 2 & forall g in cal(G) \ upright("spend") & italic("spend")_(t) & = sum_(g in cal(G)) p_(t,g) dot upright("cost")_(g) & forall t in cal(T) \ + upright("rating") & upright("rating")_(g,t) & = upright("eta")_(g) dot upright("p")^(upright("max"))_(g) & forall g in cal(G), t in cal(T) \ upright("lcoe") & italic("lcoe") & = frac(sum_(t in cal(T), g in cal(G)) p_(t,g) dot upright("cost")_(g), sum_(t in cal(T), g in cal(G)) p_(t,g)) \ upright("marginal_price") & italic("marginal_price")_(t,b) & = lambda_(upright("balance"),t,b) & forall t in cal(T), b in cal(B) \ upright("startup_cost") & upright("startup_cost")_(t,g) & = cases(upright("cost")_(g) & upright("if ") upright("pos")(t) = 0, upright("cost")_(g) dot 2 & upright("if ") upright("pos")(t) > 0 and upright("season_of")(t) = upright("'winter'"), 0 & upright("otherwise")) & forall t in cal(T), g in cal(G) $ diff --git a/tools/notation.py b/tools/notation.py index 50510746..137bbf93 100644 --- a/tools/notation.py +++ b/tools/notation.py @@ -103,7 +103,9 @@ 'Named expressions': { 'budgeted': 'Plain expression in a constraint', 'starts': 'Cased expression in a constraint', + 'under_rating': 'Expression with a declared frame in a constraint', 'spend': 'Plain named expression', + 'rating': 'Named expression with a declared frame', 'startup_cost': 'Expression defined by cases', 'spend_cap': 'Data-only expression', 'capped': 'Named expression in a condition', From 9c924c0095dae7e62109c4671f7cdbf4b8d2346d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 28 Sep 2026 14:44:45 +0000 Subject: [PATCH 2/2] Add the changelog line for #756 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01W1VBZvT7UNJJexTrMX7mRi --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3e2626d..f685b057 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ it releases that version ([RELEASING.md](https://github.com/energy-models/mathsp ## Upcoming version +- docs(notation): the notation page shows a named expression whose declared frame is wider than its body ([#756](https://github.com/energy-models/mathspec/pull/756)) - feat(language): a named expression may declare the frame it is read over ([#741](https://github.com/energy-models/mathspec/pull/741)) - docs: code examples on the site are readable in light and dark mode, and a diagram shows what mathspec leaves to engines and other tools ([#730](https://github.com/energy-models/mathspec/pull/730)) - docs: the site follows the reader's light or dark setting, and a page shows where it sits in the navigation ([#727](https://github.com/energy-models/mathspec/pull/727))