Repository navigation
feat: upright is what the model is given, italic is what the solver chooses - #44
Merged
Merged
Conversation
…an a rename A pass over the page as a whole — which is what `notation.md` exists to make possible — turned up six places where the notation said something other than what it meant. - **A name that is a Greek letter prints as the letter.** A variable called `theta` set as the italic word *theta* is the one derived symbol no paper would accept. Same shape as `_INDEX_ALIASES`: a small curated map, lower case only, with `--symbols` for anything it does not know. It composes with the qualifier rule, so `theta_max` is now a head a qualifier hangs off. - **A dimension is not a head a qualifier hangs off.** `zone_cap` is a capacity *indexed by* zone, not a zone qualified by cap — and reading the axis as the head made a parameter's symbol depend on whether some unrelated dimension happened to share its prefix. The head must name a quantity now. The cost is real and visible in the goldens: `zone_cap` and `bp_x` print as plain names where they used to split, which is what a symbol table is for. - **A negation under a `+` folds into the operator.** The objective was printing `- reserve + -headroom`; nobody writes `a + -b`. Folding also hands the right operand subtraction's bracket rule, which is the one it needs. - **A bracketed operand delimits its own negation**, so `-(Σ x) · 3` no longer wears a second pair of brackets around the whole of it. - **The fill and the group take the operator's two slots** — fill below, group above. They shared one subscript because two subscripts is a TeX error (#1165), but comma-joined there, `0,season_of(t)` said nothing about which was the value standing at the boundary and which was the group. A legend note now states the rule, gated like the others on the symbol appearing. - **A mask that is only `True` prints no condition.** `expressions.md` says `True` is the same as no `where`, so `∀ t ∈ T : ⊤` was a condition that reads as one and is not. Nested it still prints: `⊤ ∧ x` is what the file says, and simplifying a mask belongs to resolution. The seventh was the curve weights: `economies_of_scale_lam` printed six times in one row is unreadable, and papers call it λ. Renaming it in the typesetter would have been a symbol nobody could trace back to the file, so the rename is a **declaration** — the `--symbols` sidecar any reader may write, under the filename convention `tools/render_tex.py` already uses — and the notation page prints the table beside the math it renamed. `bp_x`/`bp_y` ride along as x and y, which is also what those models' equations want. The fixture gains the two cases these need: a nested `True` beside the mask that is only one, and a unary `+`, whose walk arm the line-coverage guard had never reached — it was hidden inside a ternary before this change split it.
Documentation build overview
10 files changed ·
|
…hooses
Which of a linear model's symbols the solver picks is the distinction a reader
cannot afford to guess at, and the page was leaving it to the legend. In
Σ_{g : gen_bus(g) = b} p_{t,g} + spill_t - slack_t = load_{t,b}
three of those four are chosen and one is data, and all four printed the same.
`ParameterNode` and `VariableNode` reached the page through one call on one
symbol dict; nothing between them.
The nomenclature table is a real convention and stays — but it is a *lookup*
rather than a reading, and an equation quoted on a slide does not take the
legend with it. So the symbols carry it. This also completes a system the page
was already three-quarters through: script for index sets, upright for the
maps, qualifiers and labels a model is handed, italic for quantities. The cut
this adds is *inside* italic.
load_{t,b} → \mathrm{load}_{t,b} given
spill_t → \mathit{spill}_{t} chosen, unchanged
p_max → \mathrm{p}^{\mathrm{max}}
No exception for single letters: `\mathrm{p}^{\mathrm{max}}` beside a variable
`p` is exactly the pair a reader has to be able to tell apart, and carving it
out would have left the rule with one. The legend states the convention once,
in the model's own symbols, gated on the model having both — a note explaining
a contrast the page does not draw is the dead end the translation notes avoid.
Two things the distinction does not reach, both deliberate:
- **Greek.** LaTeX sets lower-case Greek italic and has no upright form in the
two-package preamble the render gate holds itself to, so a Greek-named
parameter prints as the letter and the legend is what places it.
- **The index-collision guard**, which shrank to exactly the collisions that
are still collisions: a dimension may take `p` beside a parameter `p`,
because `\mathrm{p}` and `p` are not the same symbol on the page.
The sidecar tables follow the rule they now live under: `bp_x`/`bp_y` are data,
so they print upright, while the curve weights they sit beside are variables
and stay Greek — `λ_{p,m,b} ≤ δ_{p,m,b} + δ_{p,m,b ⊟₀ 1}` is one row that used
to name a 25-character word three times.
`upgreek` is what LaTeX needs for an upright lower-case Greek letter, and
taking it costs two things this repository holds on purpose:
- **A two-package preamble.** `latex.py` loads amsmath and amssymb, and the CI
render gate exists partly to assert the preamble stays installable from a
small TeX.
- **Markdown that renders the same in both places.** `docs/static/mathjax.js`
says it outright — "a block pasted out of `to_markdown` should render the
same here as it does on GitHub" — and GitHub's MathJax takes no
configuration, so an extension the docs site can load is one GitHub cannot.
`\uptheta` would render there as an error.
So the letter yields to the rule rather than the rule to the letter: a Greek
name prints as the letter where it is **chosen**, and upright as the word where
it is **given**. An italic `\eta` that might be either is worse than an upright
`\mathrm{eta}` that is one — the whole point of the distinction being that a
reader can tell without the legend.
The escape hatch costs nothing and is already there: a symbol table entry is
printed verbatim, so an author whose own preamble loads `upgreek` writes
`eta: "\\upeta"` and gets it.
The fixture gains the case, which the page now shows in one legend beside the
other: `eta` given prints $\mathrm{eta}$, `theta` chosen prints $\theta$.
Commenting `examples/symbols/dispatch.yaml` — a table whose three parameter entries are italic, which the convention says they should not be — turned up two things worth more than the comment. **The homepage was stale, and nothing was holding it.** `docs/index.md` and `README.md` carry `examples/dispatch.yaml` rendered two ways, written by `tools/home_math.py`, and no test or workflow ran its `--check`. The upright convention changed how a parameter prints and the page kept the old math with every test green — the same failure `tools/notation.py` had until #41, on the page a reader arrives at first. It has the same guard now, and the guard was checked against a perturbed page rather than assumed. **The note contradicted itself there.** It quotes the model's own first parameter, and on that page the first parameter is `\bar p` — italic, because the table says so — under a sentence claiming a parameter is upright. So the note now quotes only symbols the *derivation* produced, and is suppressed where a table has taken over the ones it would have quoted. A table is printed verbatim and is the author's to write; a symbol it supplies is not one the note governs. Which is what the comment says, in the file that earns it: entries are the reader's notation to choose, and the same property is what lets an author whose preamble loads `upgreek` write `\upeta` for a parameter the derivation has to spell out.
Asking whether anything would catch the next divergence turned up a third stale page. `docs/reference/language/operators.md` carries the operator table `tools/spec_math.py` writes, its `--check` was called by no test and no workflow, and the upright convention had left sixteen of its rows behind. It is in the same trap the other two were: the generator writes unpadded tables, prettier pads them, and the committed block has been prettier's shape and never the generator's since the day it was written — which is why no guard could exist. So it joins `.prettierignore` under the rule `docs/examples/` set, and the entry was checked to be load-bearing rather than decorative. `tools/home_math.py` is the other way out of the same conflict and needs no entry: it emits the blank lines around its markers that prettier wants, so `--check` and the formatter already agree. Padding a table is a harder shape for a generator to match than a blank line, which is why three pages take the generator-wins route and one does not — now said out loud in `.prettierignore` rather than rediscovered per page. The guards are a table now, one row per committed page, and `test_every_ generator_is_asked` holds that table to the tools: a `tools/*.py` that can detect drift and has no row fails the suite. That is the part that catches the *next* one — three pages have now gone stale with everything green, each time because the tool knew and nothing asked it. Verified by making a tool look like a generator and watching the test fail.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Targets
main, so #31 and the stack rebase onto it. A pass over the page as a whole — which is whatnotation.mdexists to make possible — turned up one distinction the notation was not drawing at all, six places where it said something other than what it meant, and one about to start saying something a reader could not trace.The distinction that was missing
Three of those four are chosen by the solver and one is data. Before this, all four printed identically:
ParameterNodeandVariableNodereached the page through one call on one symbol dict, with nothing between them.Upright is what the model is given — a parameter, a coordinate map, a label. Italic is what the solver chooses. The nomenclature table stays and is a real convention in this field, but it is a lookup rather than a reading: quote one equation on a slide and the legend does not travel with it.
It also completes a system the page was already three-quarters through — script for index sets, upright for the maps and qualifiers a model is handed, italic for quantities. The cut this adds is inside italic.
The rule admits no exception. Not for single letters:$\mathrm{p}^{\mathrm{max}}$ beside a variable $p$ is exactly the pair a reader has to be able to tell apart. And not for Greek, which was the interesting one:
So the letter yields to the rule rather than the rule to the letter. A Greek name prints as the letter where it is chosen and upright as the word where it is given; an italic$\eta$ that might be either is worse than an upright $\mathrm{eta}$ that is one. The escape hatch costs nothing and already exists: a table entry is printed verbatim, so an author whose own preamble loads $\mathrm{eta}$ beside $\theta$ .
upgreekwriteseta: "\upeta". The fixture now shows both cases in one legend,The legend states the convention once, in the model's own symbols, gated on the model having both — a note explaining a contrast the page does not draw is the dead end the translation notes already avoid. The index-collision guard shrank to exactly the collisions that are still collisions: a dimension may now take$\mathrm{p}$ and $p$ are not the same symbol.
pbeside a parameterp, becauseThe six fixes
+True_INDEX_ALIASES, lower case only, and it composes with the qualifier rule sotheta_maxis a head a qualifier hangs off.zone_capis a capacity indexed by zone, not a zone qualified by cap — and reading the axis as the head made a parameter's symbol depend on whether some unrelated dimension shared its prefix, which is why the fixture showedzone_capsplitting andtech_capnot.a + -bis a spelling nobody uses; folding also hands the right operand subtraction's bracket rule, which is the one it needs.0,season_of(t)said nothing about which was the value at the boundary and which was the group. Fill below, group above, with a legend note.Trueis the same as nowhereperexpressions.md, so the condition read as one and was not. Nested it still prints;⊥stays either way, because "this constraint has no rows" is worth seeing.One inconsistency left deliberately: a group rides the superscript on a translation and the subscript on
pos(#31). Each is unambiguous alone, but they are not the same slot. Making them agree means movingpos, which is a decision in flight on that PR rather than one to take from here.The seventh: a table, not a rename
economies_of_scale_lamprinted six times in one row is unreadable, and the piecewise-linear literature calls it λ. Renaming it inside the typesetter would have produced a symbol nobody could trace back to the file, so the rename is a declaration — the--symbolssidecar any reader may write, under the filename conventiontools/render_tex.pyalready uses — and the notation page prints the table beside the math it renamed:$\mathit{economies_of_scale_lam}{p,m,b} \le \mathit{economies_of_scale_seg}{p,m,b} + \ldots$ becomes$\lambda_{p,m,b} \le \delta_{p,m,b} + \delta_{p,m,b \boxminus_{0} 1}$ , and none of it is the typesetter's opinion. The tables follow the convention they now live under: the weights are variables, so they stay Greek;
bp_x/bp_yare data, so they print upright.The fixture
Four cases it owed: a nested
Truebeside the mask that is only one, a unary+— whose walk arm the line-coverage guard had never reached, because it sat inside a ternary until this change split it — and a Greek name on each side of the convention.Checks
pixi.shis unreachable from this environment, so the gates ran on a 3.13 venv at the versionspixi.tomlpins, plusprettier@3.9.3andtyposfetched directly: 375 passed (including the Typst compiles and the walk's line-coverage guard),ruffcheck and format clean,pyrefly0 errors, prettier clean over the files this touches,typosclean,reuse lintcompliant. Goldens,notation.mdanddocs/examples/all regenerated by their own tools;render_texrenders all nineteen models, the four new tables included.pixi.lockandschema/math-spec.schema.jsonfail a repo-wideprettier --checkhere exactly as they do onmain— both generator-owned and outside the hook's*.{md,yml,yaml}glob.