From 885e3c2c06d1f5193787ad57ba7d2a8999f5f3e1 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 07:08:18 +0000 Subject: [PATCH 1/4] docs: the readme drops the internals diagram and the repeated formats, and is a third shorter The README folds only the whole document with its symbol table. The LaTeX and Typst folds, the mermaid diagram, the second intro paragraph, the Spec and Program sentence, the diff benefit and the issue-number note are gone, and the licence section is two lines. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Q8Y8AoPsJCyMsURHvgzm7h --- README.md | 118 ++------------------------------------------- tests/test_docs.py | 2 +- tools/home_math.py | 17 ++----- 3 files changed, 9 insertions(+), 128 deletions(-) diff --git a/README.md b/README.md index b9331493..856ba0ac 100644 --- a/README.md +++ b/README.md @@ -24,15 +24,9 @@ decisions the solver makes, such as `dispatch`; and the rules those decisions obey, such as `sum(dispatch, over=generator) == load`. The file [below](#example) is a complete model. -math-spec reads that file, checks everything that can be checked without data, -and hands the result on: to an engine that builds and solves the model, or to the -typesetter that prints it as LaTeX, Typst or Markdown. It builds nothing and -solves nothing itself. Every tool reads the file through the same checked syntax -tree, so an engine and a renderer cannot disagree about what the file means -([what counts as language](docs/about/what-counts-as-language.md)). - +math-spec builds nothing and solves nothing itself. [specsolve](https://github.com/fluxopt/specsolve) and [linopy](https://github.com/PyPSA/linopy) build and solve a math-spec model. Support in both is work in progress. @@ -51,8 +45,6 @@ Support in both is work in progress. - **Change the engine and keep the model.** The operators are a fixed set: `sum`, `sum_back`, `at` and `shift`. A file cannot add one, so a model does not depend on what one engine registered ([limits](https://math-spec.readthedocs.io/en/latest/about/limits/)). -- **Review a model as a diff.** The file is plain YAML, and no Python state - changes what it means. - **Write full-size models.** [PyPSA in one file](https://math-spec.readthedocs.io/en/latest/examples/pypsa/) states the model that PyPSA's `n.optimize()` builds, with an index of each PyPSA row. Five more pages add quadratic costs, relaxed unit commitment, line losses, @@ -60,25 +52,6 @@ Support in both is work in progress. -```mermaid -flowchart LR - Y["model.yaml"] --> S["schema
closed at every level"] - S --> AST["syntax tree
two grammars"] - AST --> Q{"inside the
language?"} - Q -->|"no"| ERR["load error
naming the construct + rewrite"] - Q -->|"yes"| M["Spec
what the file says"] - M -->|".program"| P["Program
names, dimensions and operators resolved"] - P --> ENG["an engine that builds → solver"] - M --> T["to_latex / to_typst / to_markdown"] - - classDef spec fill:#f0f7f0,stroke:#3a7d44,stroke-width:2px,color:#111 - classDef consumer fill:#eef1fb,stroke:#4a5fc1,stroke-width:2px,color:#111 - classDef err fill:#fdf3e7,stroke:#b7791f,color:#111 - class S,AST,M,P spec - class ENG,T consumer - class ERR err -``` - ## Example @@ -193,80 +166,6 @@ $`0 \le \mathit{dispatch}_{s,g} \le \bar p_{g} \qquad \forall\, s \in \mathcal{S -
-The same document as LaTeX - -```latex -\noindent Least-cost dispatch of a generator fleet against an hourly load. - -\paragraph{Sets} -\begin{description} -\item[{$\mathcal{S}$}] index $s$ --- \texttt{snapshot} --- dispatch periods -\item[{$\mathcal{G}$}] index $g$ --- \texttt{generator} --- generating units -\end{description} - -\paragraph{Parameters} -\begin{description} -\item[{$\bar p$}] \texttt{capacity} over $\mathcal{G}$ --- installed capacity -\item[{$\ell$}] \texttt{load} over $\mathcal{S}$ --- demand to be met -\item[{$c$}] \texttt{cost} over $\mathcal{G}$ --- marginal cost -\end{description} - -\paragraph{Variables} -\begin{description} -\item[{$\mathit{dispatch}$}] \texttt{dispatch} over $\mathcal{S} \times \mathcal{G}$ --- output of a generator in a snapshot -\end{description} - -\paragraph{Objective} -\begin{align*} - && \min & \sum_{s \in \mathcal{S},\ g \in \mathcal{G}} \mathit{dispatch}_{s,g} \cdot c_{g} -\end{align*} - -\paragraph{Subject to} -\begin{align*} -\text{power\_balance} && \sum_{g \in \mathcal{G}} \mathit{dispatch}_{s,g} & = \ell_{s} && \forall\, s \in \mathcal{S} -\end{align*} - -\paragraph{Variable domains} -\begin{align*} -\text{dispatch} && 0 \le \mathit{dispatch}_{s,g} & \le \bar p_{g} && \forall\, s \in \mathcal{S},\ g \in \mathcal{G} \,:\, \bar p_{g} > 0 -\end{align*} -``` - -
- -
-The same document as Typst, printed with no symbol table - -```typst -Least-cost dispatch of a generator fleet against an hourly load. - -== Sets -/ $cal(T)$: index $t$ --- `snapshot` --- dispatch periods -/ $cal(G)$: index $g$ --- `generator` --- generating units - -== Parameters -/ $upright("capacity")$: `capacity` over $cal(G)$ --- installed capacity -/ $upright("load")$: `load` over $cal(T)$ --- demand to be met -/ $upright("cost")$: `cost` over $cal(G)$ --- marginal cost - -== Variables -/ $italic("dispatch")$: `dispatch` over $cal(T) times cal(G)$ --- output of a generator in a snapshot - -Upright is what the model is given --- a parameter such as $upright("capacity")$, a coordinate map, a label --- and italic is what the solver chooses, such as $italic("dispatch")$. An index is italic too, being what a quantifier chooses, and a set is script. - -== Objective -$ & min & sum_(t in cal(T), g in cal(G)) italic("dispatch")_(t,g) dot upright("cost")_(g) $ - -== Subject to -$ upright("power_balance") & sum_(g in cal(G)) italic("dispatch")_(t,g) & = upright("load")_(t) & forall t in cal(T) $ - -== Variable domains -$ upright("dispatch") & 0 <= italic("dispatch")_(t,g) & <= upright("capacity")_(g) & forall t in cal(T), g in cal(G) colon upright("capacity")_(g) > 0 $ -``` - -
- @@ -283,10 +182,8 @@ ms.to_typst(spec) # compiles without a TeX toolchain ``` A [symbol table](docs/reference/typeset.md#symbol-tables) gives the names their -conventional spelling, as in the first folded block. +conventional spelling, as in the folded block. [Print a model as math](docs/howto/print.md) does the same from a shell. -`to_spec` returns a `Spec`, and `spec.program` its `Program` -([reading a spec and its program](docs/reference/reading.md#spec-and-program)). ## Documentation @@ -306,8 +203,7 @@ and extracted here. The keys themselves, which are YAML math, a block per component, `dims:` and a `where:` string, come from [Calliope](https://github.com/calliope-project/calliope). [linopy](https://github.com/PyPSA/linopy) supplies the vocabulary that -`sum(over=)` and the dimension rules are named against. Issue numbers in these -pages point at specsolve, where the arguments happened. +`sum(over=)` and the dimension rules are named against. ## Status @@ -326,9 +222,5 @@ not yet frozen. ## Licence -The code is [MIT](LICENSE): everything under `src/`, `tests/`, `tools/`, the -examples, and the generated schema. - -The prose is [CC-BY-4.0](LICENSES/CC-BY-4.0.txt): everything under `docs/`, this -README, `CHANGELOG.md`, and the logos in `resources/`. Reuse it freely, with -attribution. +The code is [MIT](LICENSE), and the prose is +[CC-BY-4.0](LICENSES/CC-BY-4.0.txt). diff --git a/tests/test_docs.py b/tests/test_docs.py index a22066ff..85cbfdc5 100644 --- a/tests/test_docs.py +++ b/tests/test_docs.py @@ -67,7 +67,7 @@ def test_no_fold_in_the_readme_prints_its_math_as_a_code_block(): `tools._page.inlined` is what rewrites a fold into it. """ folds = re.findall(r'
.*?
', home_math.README.read_text(), re.DOTALL) - assert len(folds) == 3, 'the README folds the whole document, then the same document in the other two notations' + assert len(folds) == 1, 'the README folds one block, the whole document with its symbol table' hiding = [fold[:60] for fold in folds if '```math' in fold] assert not hiding, f'a `math` fence inside a fold prints its TeX rather than its math: {hiding}' diff --git a/tools/home_math.py b/tools/home_math.py index a94d54ec..16e5288e 100644 --- a/tools/home_math.py +++ b/tools/home_math.py @@ -9,7 +9,7 @@ ``examples/dispatch.yaml`` is printed once and spliced into two pages that need different shapes for it. GitHub renders the Markdown math but not a tabbed -block, so ``README.md`` takes the equations with the other two formats folded +block, so ``README.md`` takes the equations with the whole document folded under them, and ``docs/index.md`` takes tabs, plus a third tab holding the call that produced the other two. """ @@ -19,7 +19,7 @@ import textwrap from math_spec import to_spec -from math_spec.typesetting import to_latex, to_markdown, to_typst +from math_spec.typesetting import to_latex, to_markdown from tools._page import ROOT, inlined, sidecar_for, splice, without_header from tools._page import main as page_main @@ -107,10 +107,7 @@ def readme_block() -> str: The visible block carries no legend and no symbol table, because a README is read before anything else: three legend tables are half its length, and a derived symbol is the file's own name, which needs no table to be read. - The first fold is what the legend and a table add. - - Typst is printed with no table for a second reason: the sidecar is written - in LaTeX, and a render in the other notation is refused. + The fold is what the legend and a table add. """ spec = to_spec(MODEL) symbols = sidecar_for(MODEL) @@ -121,14 +118,6 @@ def readme_block() -> str: 'The whole document: a symbol table, and the legend it prints', inlined(to_markdown(spec, symbols=symbols, numbered=False).strip()), ), - details( - 'The same document as LaTeX', - f'```latex\n{to_latex(spec, symbols=symbols, numbered=False).strip()}\n```', - ), - details( - 'The same document as Typst, printed with no symbol table', - f'```typst\n{to_typst(spec, numbered=False).strip()}\n```', - ), ) ) From c4e957a503261ed4fcb621cb79cc52ce399678f7 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 07:11:21 +0000 Subject: [PATCH 2/4] docs: the format calls in the readme and on the home page carry no inline comments Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Q8Y8AoPsJCyMsURHvgzm7h --- README.md | 6 +++--- docs/index.md | 8 ++++---- tools/home_math.py | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 856ba0ac..82763e0e 100644 --- a/README.md +++ b/README.md @@ -176,9 +176,9 @@ import math_spec as ms spec = ms.to_spec('dispatch.yaml') -ms.to_markdown(spec) # renders as-is on GitHub, as above -ms.to_latex(spec) # amsmath align -ms.to_typst(spec) # compiles without a TeX toolchain +ms.to_markdown(spec) +ms.to_latex(spec) +ms.to_typst(spec) ``` A [symbol table](docs/reference/typeset.md#symbol-tables) gives the names their diff --git a/docs/index.md b/docs/index.md index 636f1c2a..25d902bf 100644 --- a/docs/index.md +++ b/docs/index.md @@ -149,11 +149,11 @@ call. }, } - spec = ms.to_spec('dispatch.yaml') # read and checked once, then printed three ways + spec = ms.to_spec('dispatch.yaml') - ms.to_latex(spec, symbols=symbols) # amsmath align - ms.to_typst(spec) # compiles without a TeX toolchain - ms.to_markdown(spec) # renders as-is on GitHub + ms.to_latex(spec, symbols=symbols) + ms.to_typst(spec) + ms.to_markdown(spec) ``` `symbols` gives every name its conventional spelling. Pass a dict, a YAML path diff --git a/tools/home_math.py b/tools/home_math.py index 16e5288e..a9b3aaab 100644 --- a/tools/home_math.py +++ b/tools/home_math.py @@ -52,11 +52,11 @@ }, } -spec = ms.to_spec('dispatch.yaml') # read and checked once, then printed three ways +spec = ms.to_spec('dispatch.yaml') -ms.to_latex(spec, symbols=symbols) # amsmath align -ms.to_typst(spec) # compiles without a TeX toolchain -ms.to_markdown(spec) # renders as-is on GitHub +ms.to_latex(spec, symbols=symbols) +ms.to_typst(spec) +ms.to_markdown(spec) ``` `symbols` gives every name its conventional spelling. Pass a dict, a YAML path From 9aaea67df845d494d2e618604c867e16f81df179 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 07:12:05 +0000 Subject: [PATCH 3/4] docs: the readme and home page link the installation page instead of saying what is published Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Q8Y8AoPsJCyMsURHvgzm7h --- README.md | 6 ++---- docs/index.md | 3 +-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 82763e0e..66eabdee 100644 --- a/README.md +++ b/README.md @@ -191,10 +191,8 @@ The documentation is at . ## Installation -Nothing is published yet. -[Installation](docs/howto/installation.md) gives the command that installs from -git, and [contributing](docs/contributing.md#setting-up-a-development-environment) -sets up a development clone. +See [installation](docs/howto/installation.md). To work on math-spec, see +[contributing](docs/contributing.md#setting-up-a-development-environment). ## Prior art diff --git a/docs/index.md b/docs/index.md index 25d902bf..4d507d7b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -189,8 +189,7 @@ call. ## Install it -Nothing is published yet. [Installation](howto/installation.md) gives the -command that installs from git. +See [installation](howto/installation.md). !!! warning "Alpha, pre-1.0" From 53d291413566e58c117baecfd7b97b8737dd4601 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 07:24:16 +0000 Subject: [PATCH 4/4] docs: the home page's four benefit cards sit two to a row Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Q8Y8AoPsJCyMsURHvgzm7h --- docs/static/extras.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/static/extras.css b/docs/static/extras.css index 6d36af83..1dc56956 100644 --- a/docs/static/extras.css +++ b/docs/static/extras.css @@ -310,6 +310,10 @@ /* Benefit cards: the README bullet's icon and bold lead-in become the card's * heading, since the README cannot carry card markup. */ +.md-typeset .landing .grid.cards { + grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr)); +} + .md-typeset .landing .grid.cards > ul > li { border-top: 0.2rem solid var(--md-accent-fg-color); }