Skip to content

docs: the README shows the math a model prints, in all three formats - #449

Merged
FBumann merged 5 commits into
mainfrom
claude/readme-typesetting-j1x02l
Sep 15, 2026
Merged

FBumann merged 5 commits into
mainfrom
claude/readme-typesetting-j1x02l

Conversation

@FBumann

@FBumann FBumann commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Prompt: Lets improve our readme, showing the typesetting etc!

Follow-up that set the scope: The audience is a user who discovers the package and needs to know what its for, how easy it is to use and see the typesetting.

Follow-up that set the scope: Can we make the model much smaller so the typesetting isnt that long?

Second round: Bryn's review asked for dispatch and capacity in place of p and p_max, across the docs rather than the README alone.

Third round: main renamed a declaration's foreach: to dims: (#429) while this branch renamed the same lines, so the branch is merged up in 0ac8bc9.

Fourth round: main renamed lookups: to relations: and a walk operator's over= to along= (#437), again on the lines this branch renamed, so the branch is merged up again in b3ef80d.

Note

The following content was generated by AI.

What this changes

The README described the typesetter and never showed it. examples/dispatch.yaml now stands beside the equations printed from it, in the Markdown GitHub renders as math. tools/home_math.py writes the block, so nothing in it is hand-typed.

The example models also drop PyPSA's spellings. The decision is dispatch, its bound is capacity, and p_min is min_output.

The README also names sum's own keyword again. #437 wrote sum(consume=) into the opening paragraph and the prior art section, and no version of the language has parsed that: the keyword is over=.

The rename, and the four places that keep p

p and p_max are PyPSA's names, so a reader without that background had to guess what the decision was. Renamed in examples/dispatch.yaml, commitment.yaml, sos.yaml, piecewise.yaml and piecewise_lp.yaml, in the sidecar symbol table, in the pages generated from all of them, and in the reference and how-to pages that show the same YAML by hand.

Four places keep p on purpose:

Kept Why
examples/pypsa*.yaml Generator_p_nom and Generator_p_max_pu are PyPSA's own API. Renaming them is what would make those files wrong
tests/typesetting/golden/model.yaml, and so docs/reference/notation.md a coverage fixture whose names sit beside theta, spare and headroom. Its subject is which construct prints what
examples/operators/*.yaml one-construct probes, same argument
tests.fixtures.DISPATCH_MODEL an inline dict that 11 test files vary. Renaming it moved 87 assertions and changed nothing a reader sees. Its docstring no longer claims to be examples/dispatch.yaml name for name

docs/reference/notation.md still moved by 8 lines, because its piecewise: sections come from sos.yaml and the two piecewise*.yaml files rather than from the fixture.

How the two merges with main were resolved

Both merges are the same collision. main renamed a key or a keyword on the very lines this branch renamed p, p_max and p_min, so git could not merge them, and every conflict takes main's keyword with this branch's names.

0ac8bc9, against #429, which renamed foreach: to dims: on every variable, constraint and cased expression. Thirteen files conflicted.

Conflicted Resolved by
examples/dispatch.yaml, commitment.yaml, piecewise.yaml, piecewise_lp.yaml, sos.yaml main's dims:, this branch's names
docs/howto/regimes.md, docs/reference/language/absence.md, declarations.md, index.md the same, by hand — these show YAML rather than generate it
tests/test_lowering.py the same. main's assertion messages say "the frame is the dims"
README.md, docs/examples/dispatch.md, docs/examples/commitment.md re-rendered by their generator

b3ef80d, against #437, which renamed lookups: to relations: and shift's and sum_back's over= to along=. Eight files conflicted.

Conflicted Resolved by
examples/commitment.yaml main's along=, this branch's dispatch
docs/reference/language/absence.md, dimensions.md, index.md the same, by hand
tests/test_lowering.py the same
docs/examples/commitment.md re-rendered by its generator
docs/reference/notation.md this branch's paragraph, which is the \mathrm correction below
README.md this branch's sum(dispatch, over=generator), for the reason in What this changes

prettier --write then repadded the ten-rule table on docs/reference/language/index.md after each merge, whose cells changed width.

The other five review comments
  • The symbols paragraph on docs/index.md led with what happens without symbols. It now says what symbols does, then that it is optional.
  • That same paragraph repeated six sentences of docs/reference/typeset.md. Rather than share the text through a snippet, the homepage keeps the one fact a newcomer needs and leaves the rest to the link it already carried. A homepage summary beside a reference page is not two homes for one fact.
  • Two files carry it. counted what the next two sentences said. The whole module docstring is rewritten around the constraint that made the tool exist: GitHub renders the Markdown math but not a tabbed block.
  • ### What that file says and ### How a tool reads it narrate rather than name a subject, which .claude/skills/docs-writing/SKILL.md rules out. They are now ### The math it prints and ### `Spec` and `Program`, in README.md and docs/index.md together — the second page already carried both headings from docs: the pages read in plain English, arranged by what each is for #442, so leaving it would have split them.
  • The committed suggestion on An engine reads the second. is applied verbatim.
One claim that was wrong, and one this PR withdraws

A model with no symbol table prints \mathrm{load}_t, not \mathit{load}_t. A parameter is given, so it derives upright. docs/reference/notation.md was contradicting the legend printed further down its own page, and docs/howto/print.md carried the same error — that page is fixed here too, and Bryn did not flag it. #437 rewrote that same paragraph on main and kept \mathit, which is why the page conflicts again in b3ef80d; tests/typesetting/golden/latex.out prints \mathrm{load} and settles it.

Withdrawn: an earlier version of this body said the README was wrong to call the variable dispatch, because the file declared p. Bryn's review settles it the other way, and the file now declares dispatch.

Why the visible block is short, and why a smaller model is not what does it

The ask was to shrink the model. Measured on 62ce3a4 with to_markdown(numbered=False), counting non-empty lines of the printed document:

Printed Lines
dispatch.yaml, derived symbols, with the legend 46
dispatch.yaml, symbol table and legend 44
dispatch.yaml, derived symbols, no legend 23

The legend is the lever, not the model: three tables and their headings are half the document. So the visible block drops the legend and the table. That costs nothing a reader needs, now less than ever — a derived symbol is the file's own name, and after the rename that name is \mathrm{capacity}_g. The legend, the conventional symbols and both other formats are one click away in the folds.

An earlier version of this table carried a fourth row, for dispatch.yaml cut to two parameters with no where: and no upper bound, and claimed 45 lines. That number does not reproduce: the same cut measures 43 here. It was load-bearing for nothing — 43 and 45 are both far from 23 — so the row is dropped rather than restated.

examples/dispatch.yaml keeps every construct it had, which keeps it whole for the four tests that read it and for the gallery page whose prose explains its where:.

The prose

Written to .claude/skills/docs-writing/SKILL.md: a heading names a subject rather than narrating one, a full stop separates independent clauses, and the argument for the block lives in this PR rather than on the page.

The How tab in tools/home_math.py is prose on docs/index.md that #442 could not reach, since it lives in the generator. It went through the same pass, and it links Typeset the math at the construct's first mention.

How the block regenerates, and the formatter

readme_block() in tools/home_math.py adds a second marker region to the file that already owned the README's YAML block:

pixi run python -m tools.home_math          # rewrite both pages
pixi run python -m tools.home_math --check  # 2 page(s) current

tests/test_docs.py::test_the_generated_page_is_current[home:readme] fails if it drifts.

Prettier pads the legend tables the typesetter emits unpadded, so the generated block sits inside a <!-- prettier-ignore-start --> range. That range is load-bearing: with it stripped, prettier --write README.md rewrites 9 lines of the legend tables, measured on 62ce3a4. .prettierignore's note that home_math needs no entry was written before the README carried tables, so it now names the range as the second way out.

Verified, and not

Pixi's installer host is denied by this session's egress policy, so pixi run ci did not run as one gate. Run instead on a Python 3.13 venv (pydantic, pyparsing, pyyaml, pytest, mkdocs, mkdocs-material, mkdocstrings[python], ruff) and a local prettier@3 with the repository's own .prettierrc.yaml, on the head commit 5a54ace:

Ran Result
pytest tests 1265 passed, 6 skipped
prettier --check **/*.md clean
ruff check . / ruff format --check . clean, 131 files
every generator with --check schema, notation, spec_math, gallery, home_math all current
python -m tests.typesetting.golden the three .out files re-render byte for byte
mkdocs build --strict built, no warning

The earlier round's numbers were taken on 0ac8bc9 and read 1206 passed; the merge with #437 brought main's new tests in, so they are re-run above rather than carried over.

mkdocs build --strict needed one local edit to run at all: the proxy answers https://docs.python.org/3/objects.inv with 403, and mkdocstrings aborts the strict build on an inventory it cannot fetch. The build above dropped the inventories: key, and mkdocs.yml is unchanged in the tree. Every other strict check — nav, cross-links, anchors — ran.

Not run: pixi run lint (the whole hook set: reuse, typos, pyrefly, the whitespace fixers) and pixi run compile-tex.

Not done: no src/ change, so no behaviour moved. src/math_spec/typesetting/symbols.py's _derive_name_symbol docstring shows p_max in its chosen spelling, which is correct for its given=False default and was left alone.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LjQuLw7nctEbY7ACCGsaLP

@FBumann
FBumann force-pushed the claude/readme-typesetting-j1x02l branch from dc37122 to c77504f Compare September 10, 2026 07:50
@FBumann
FBumann changed the base branch from main to claude/math-spec-docs-comparison-mmyrl8 September 10, 2026 07:50
@FBumann
FBumann added this pull request to stack #450 September 10, 2026 07:52
@FBumann
FBumann force-pushed the claude/readme-typesetting-j1x02l branch 2 times, most recently from 6aac0f5 to 9c1e8aa Compare September 10, 2026 08:17
Base automatically changed from claude/math-spec-docs-comparison-mmyrl8 to main September 10, 2026 08:26
@FBumann
FBumann force-pushed the claude/readme-typesetting-j1x02l branch from 9c1e8aa to 7f9ca89 Compare September 10, 2026 08:26
@FBumann
FBumann removed this pull request from stack #450 September 10, 2026 08:35
The README described the typesetter and never showed it. The dispatch model
now stands beside the equations printed from it, in the Markdown that GitHub
renders as math. `tools/home_math.py` writes the block from
`examples/dispatch.yaml`, so nothing in it is hand-typed.

The visible block carries no legend and no symbol table. Three legend tables
are half the length of the document, and a derived symbol is the file's own
name, so the equations read without them: 44 lines become 23. A smaller model
does not do this. The same model cut to two parameters, with no `where:` and
no upper bound, prints 45 lines, because dropping the table adds the
convention note. Four folded blocks hold the whole document: with the symbol
table and its legend, as LaTeX, and as Typst.

The Typst block is printed with no table, which makes it the evidence for what
a derived symbol looks like. A parameter is upright, so `load` prints as
\mathrm{load}_t and `p_max` as \mathrm{p}^{\mathrm{max}}_g. Three pages claimed
\mathit{load}_t and p^{\mathrm{max}}_g for the same two names, and
docs/reference/notation.md contradicted the legend printed further down its
own page.

The README also named a variable the example does not declare: the model
declares `p`, and three sentences called it `dispatch`.

README.md: n 88, avg 12.7, median 11, over25 13. Every sentence over 25 words
is in a section this commit does not touch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EQ6np3vx2fqJB5PfQjquRJ
@FBumann
FBumann force-pushed the claude/readme-typesetting-j1x02l branch from 7f9ca89 to f25159f Compare September 10, 2026 08:41
Comment thread docs/reference/typeset.md Outdated

With no table, the symbols are **derived** from the names in the file, such as
$\mathit{load}_t$ and $p^{\mathrm{max}}_g$. A derived symbol names one
$\mathrm{load}_t$ and $\mathrm{p}^{\mathrm{max}}_g$. A derived symbol names one

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we change p to dispatch and p_nom/p_max to capacity for ease of reading in the docs? I already updated that in the README in my previous PR but would be good to be consistent across all docs pages. I doubt anyone except a PyPSA person understands p!

Comment thread docs/index.md Outdated
Comment on lines +210 to +215
`symbols` is optional. Drop it and the same model prints as
$\mathrm{load}_t$ and $\mathrm{p}^{\mathrm{max}}_g$, with no setup. Pass a dict,
a YAML path or a `SymbolTable`. A key that names nothing in the model is an
error, rather than a symbol that silently never applies. Every spelling is
printed as written, and `notation` says which language it is written in. A
render in the other notation is refused.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

confusing paragraph as it first talks about what happens if symbol isn't there then goes onto say what symbol should be and what happens when it is there. Should start by saying everything symbol does and then say it's optional and without it you get X

Comment thread tools/home_math.py Outdated
snippet, and ``docs/index.md`` holds the math, which is a tabbed block and
would be raw markup on GitHub. The third tab is the call that produced the
other two.
Two files carry it. ``README.md`` holds the YAML, which the site pulls in as a

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two files carry it. is an unnecessary sentence that the skill should be able to handle removing (has this been rewritten with the latest skill available?

Comment thread tools/home_math.py Outdated
`SymbolTable`; a key naming nothing in the model is an error, not a symbol that
silently never applies. Every spelling is printed verbatim — `notation` says
which language they are, and a render in the other one refuses.
`symbols` is optional. Drop it and the same model prints as

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Repetition w.r.t. typeset.md. Can we share this text between them?

Comment thread README.md Outdated
`snapshot` and `generator`; the data it expects, such as `load` and `cost`; the
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
decisions the solver makes, such as `p`; and the rules those decisions obey, such

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made these dispatch for the reason stated in previous comment.

Comment thread README.md Outdated
That file is a complete model. Nothing outside it changes what it means.

<!--- --8<-- [start:load] -->
### What that file says

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a good header name. Maybe latest skill would modify it appropriately?

Comment thread README.md Outdated
Neither needs data or a solver, so a repository of models compiles in CI with
nothing bound to any of them. **A `Spec` holds the file as written, and a
`Program` holds the model it builds**, with every macro expanded and every curve
turned into its variables and constraints. An engine reads the second.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
turned into its variables and constraints. An engine reads the second.
turned into its variables and constraints. An engine reads the `Program`.

@FBumann

FBumann commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

@bryn thanks for your review!

`p` and `p_max` are PyPSA's spellings, so a reader without that background
had to guess what the decision was. The gallery models and the reference
pages now name the decision `dispatch` and its bound `capacity`, and
`p_min` becomes `min_output`.

The six `pypsa*.yaml` files keep `Generator_p_nom` and `Generator_p_max_pu`,
which are PyPSA's own API and are the point of those files. The golden
fixture and the operator probes keep `p` too: their subject is which
construct prints what, not what a model calls things.

Also on this page: `docs/howto/print.md` said a parameter with no symbol
table prints as $\mathit{load}_t$, and it prints $\mathrm{load}_t$.

Two headings that narrated rather than named a subject become `The math it
prints` and `Spec` and `Program`, in the README and on the site homepage
together.

The `How` tab on the homepage repeated six sentences of
`docs/reference/typeset.md`. It now says what `symbols` does, then that it
is optional, and leaves the rest to the link it already carried.

`tests.fixtures.DISPATCH_MODEL` stays on `p` and `p_max`. It is an inline
dict that 11 test files vary; renaming it moved 87 assertions and changed
nothing a reader sees. Its docstring no longer claims to be
`examples/dispatch.yaml` name for name.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FerXtCLtx64Mag9uKnA6FZ
@FBumann FBumann added the area: typesetting What the three formats print, and the annotations they print label Sep 15, 2026 — with Claude
main renamed a declaration's `foreach:` to `dims:` (#429), and this branch
renamed the example models' `p`, `p_max` and `p_min` to `dispatch`,
`capacity` and `min_output`. Every conflict is both edits on one line, so
each takes main's key with this branch's names.

The five generated pages and the golden typesetter output were re-rendered
rather than merged by hand.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZKoWvZmUg8XxUK3nKxMZr
#437 renamed `lookups:` to `relations:` and a walk operator's `over=` to
`along=` on the same lines this branch renamed `p` to `dispatch`, so eight
files conflicted. Every conflict takes main's keyword with this branch's
names.

`README.md` also takes this branch's `sum(dispatch, over=generator)`: main
writes `consume=`, which no version of the language has parsed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjQuLw7nctEbY7ACCGsaLP
#437 wrote `sum(consume=)` into the README's opening paragraph and its prior
art section. No version of the language has parsed `consume=`: the keyword
that reduces a dimension away is `over=`, as `BUILTINS['sum'].usage` and
`docs/reference/language/operators.md` both say.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjQuLw7nctEbY7ACCGsaLP
@FBumann
FBumann merged commit aad56d9 into main Sep 15, 2026
6 checks passed
FBumann pushed a commit that referenced this pull request Sep 15, 2026
Brings the branch up to 0b4f046, which carries #437's relations rename and
#449's gallery renames. Deliberately 0b4f046 rather than main: main also
carries #474, which #481 reverts, and merging it here would resurrect it.

Seven files conflicted. The renames main made are taken, and this branch's
new nodes are kept alongside them:

- The frame-check message keeps this branch's `leaf` phrase, which the two
  expression-comparison nodes need because they carry no name, with main's
  wording and `where-relation` spelling for the four named cases.
- `_comparison` takes main's dotted-column body, with this branch's
  `expressions:` check in front of it.
- `_expression_comparison` and main's `_relation_column` are both kept.
- `LookupNode` is `RelationNode`, and a translation's `partition` is now a
  `Walk`, so `names_read` reads `.name` off it.
- The new cases spell `shift`/`sum_back` with `along=` and `window=`.

The generated schema, goldens and pages are regenerated, not hand-merged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017VApqcmBKLXtKTmk3ajmtK
FBumann pushed a commit that referenced this pull request Sep 15, 2026
Brings this branch onto #469 as rebuilt on the alpha.90 release, so it picks
up #437's relations rename and #449's gallery renames.

Nine files conflicted, and three more merged cleanly while still written in
the old vocabulary, which was the larger half of the work:

- The typesetting union this branch adds was named `RelationNode`, which
  main now uses for a resolved `by=`. The alias is `AlignedComparison`, and
  `_relation` reads a relation column through `_value_read` and a position
  group through `_position_group`, as main's `_predicate` does.
- `_comparison` keeps main's dotted-column body and this branch's parameter
  pair, which is taken only where neither side names a column.
- `_parameter_pair_error` and main's `_relation_pair_error` are both kept.
- `examples/commitment.yaml` assumed `p_min <= p_max`, which #449 renamed to
  `min_output <= capacity`.
- The new cases spell `shift`/`sum_back` with `along=` and `window=`.
- The expressions page said two parameters cannot be compared, which this
  branch makes false; it now states both pair forms.

The generated schema, goldens and pages are regenerated, not hand-merged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017VApqcmBKLXtKTmk3ajmtK
FBumann pushed a commit that referenced this pull request Sep 15, 2026
Brings the branch up to 0b4f046. Deliberately 0b4f046 rather than main: main
also carries #474, which #481 reverts — and #474 adds `walk_regions` to
`program.py`, which this branch renames throughout.

One file conflicted. `tests/test_lowering.py` carries both renames at once:
this branch drops the `Node` suffix, and #449 renamed the gallery's `p` and
`p_max` to `dispatch` and `capacity`. The merged file takes #449's values
with this branch's type names, and its two API decisions hold — the mask
constant is built with `Multiply(Variable(...), Parameter(...))` rather than
the deleted operator sugar, and the domain is read off `program.variables`
rather than the deleted `program.variable()`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017VApqcmBKLXtKTmk3ajmtK
@FBumann FBumann added the docs Documentation pages, guides, reference and README label Sep 24, 2026 — with Claude
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: typesetting What the three formats print, and the annotations they print docs Documentation pages, guides, reference and README

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants