Typeset by format, and say what the render leaves out - #951
Open
FBumann wants to merge 3 commits into
Open
Conversation
Build cost — v1 vs legacyv1 build peak & time relative to legacy, on this commit — not a comparison against master (that is CodSpeed).
Full table (time + peak, mean)📊 Interactive plots + CSV: download the semantics-report-v1-vs-legacy artifact from this run. Report-only · not a gate · refreshed on every push · obsolete once legacy is dropped. |
FBumann
force-pushed
the
spec-review-fixes
branch
from
September 8, 2026 17:24
3dadc43 to
435bb27
Compare
math-spec spells its typesetter as `typeset(model, fmt)` over a `FORMATS` registry, and `typeset_declaration(model, name, fmt)` the same way. linopy re-expanded that one axis into six methods, three per class, each a one-line delegation -- so linopy's public surface tracked math-spec's list of formats, and a fourth format upstream would have meant either two more methods here or a silent asymmetry. `typeset(fmt)` is the method now, on `ModelSpec` and on `Declaration` alike, with `to_latex`, `to_markdown` and `to_typst` kept as the three-line aliases that spell today's formats -- they read the way pandas taught everyone to expect, and a notebook repr needs one. A format math-spec adds is reachable the day it lands, without a release here. The docstrings said "the whole model", which the code never delivered: it renders the spec, and a spec-built model can hold more than its spec. They say the spec now; making the difference visible is the next commit's job. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ZfHfTFENUy6WxnFFri5Td
Typesetting renders the spec, and a spec-built model need not be only its spec: `add_variables` and `add_constraints` go on working on one, and what they add carries no math-spec declaration to typeset. Nothing said so. A hybrid model rendered to LaTeX silently dropped every hand-added variable and constraint -- the kind of omission that survives review and reaches a paper. `model.spec.unspecified` names them, in one place: `Model.__repr__` computed the same difference inline to decide its `[spec]` tags and now reads it too. Where it is non-empty, typesetting says so three ways, because no one channel reaches every reader: - a `UserWarning`, for the script that renders a file; - a comment of the format's own opening the rendered text -- `%` in LaTeX, `<!--` in Markdown, `//` in Typst -- gone once compiled, there for whoever reads the source; - a visible line under the Markdown a notebook displays, which is the one place a warning is routinely swallowed. A format math-spec adds that we have no comment syntax for renders without one rather than with a wrong one; the warning still fires. `Declaration` is left alone: it is reached by name through the spec, so it can never be out of step with the model. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ZfHfTFENUy6WxnFFri5Td
`unspecified` compared names, and two of the things a model can hold have no name to compare. `add_sos_constraints` writes `sos_type` and `sos_dim` onto a variable that is already there, and `add_objective` mutates the objective in place -- so a hand-added special-ordered set, and an objective replaced after the build, both changed the mathematics while `unspecified` stayed empty. The objective is the worse of the two: the render then shows the spec's objective, which is no longer the model's, so it is wrong rather than incomplete. Named expressions were missed for a duller reason: a spec's own are read lazily off `model.spec` and never live in `model.expressions`, so everything that does live there was added by hand and none of it was reported. Piecewise was reported, at the wrong altitude -- `add_piecewise_formulation` puts four generated constraints in the model, and the tally named all four instead of the formulation. `Model.__repr__` already groups them through `_get_piecewise_groups`; `unspecified` now reads the same grouping and names formulations, leaving `variables` and `constraints` to what a caller actually declared. The objective is a flag rather than a comparison, since the object is mutated in place: `add_objective` sets it when the model already carries a spec, which a build cannot trigger because it runs before `_spec` is assigned. A copy carries it, and a netcdf file records it as an attribute -- written only when true, so a file from an untouched spec is byte-for-byte what it was. Nothing else in the file could say the typeset objective is not the model's. None of this fires on a spec's own constructs: math-spec lowers `piecewise:` and `sos:` into ordinary declarations, so they sit in the program like anything else and typeset like anything else. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ZfHfTFENUy6WxnFFri5Td
FBumann
force-pushed
the
spec-typeset-api
branch
from
September 8, 2026 17:25
f08408c to
dc327f6
Compare
4 tasks
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.
Note
The following content was generated by AI.
Stacked on #950. The typesetting API, in three commits.
1. The format is an argument, not six methods
math-spec spells its typesetter as
typeset(model, fmt)over aFORMATSregistry, and
typeset_declaration(model, name, fmt)the same way. linopyre-expanded that one axis into six methods — three on
ModelSpec, three onDeclaration— each a one-line delegation. linopy's public surface thereforetracked math-spec's list of formats: a fourth format upstream meant either two
more methods here or a silent asymmetry.
**optionswas passed throughuntyped too, so
standalone=,legend=,numbered=,symbols=wereinvisible at the linopy level.
typeset(fmt)is the method now, on both classes, withto_latex,to_markdownandto_typstkept as three-line aliases — they read the waypandas taught everyone to expect, and a notebook repr needs one. A format
math-spec adds is reachable the day it lands, with no release here.
The docstrings also said "the whole model", which the code never delivered —
it renders the spec. They say the spec now.
2. Say what the render leaves out
A spec-built model need not be only its spec:
add_variablesandadd_constraintsgo on working on one, and what they add carries no math-specdeclaration to typeset. Nothing said so — a hybrid model rendered to LaTeX
silently dropped every hand-added variable and constraint. That is the kind of
omission that survives review and reaches a paper.
model.spec.unspecifiedreports it in one place —Model.__repr__computed thesame difference inline to decide its
[spec]tags and now reads it too. Where itis non-empty, typesetting says so three ways, because no one channel reaches
every reader:
UserWarning, for the script that renders a file;%in LaTeX,<!--in Markdown,//in Typst — gone once compiled, there for whoever readsthe source;
place a warning is routinely swallowed.
A format math-spec adds that we have no comment syntax for renders without one
rather than with a wrong one; the warning still fires.
Declarationis leftalone — it is reached by name through the spec, so it can never be out of step
with the model.
What is not reported
A spec declaration the data emptied — a variable under a
where:no rowsatisfies — still typesets, with its
where:clause intact. That is correct: thespec is the mathematics and the data is the data, and the condition is right
there in the output. Only the model-has-more direction is drift.
3. Everything a model can hold, not just variables and constraints
The first pass compared names, and two of the things a model can hold have no
name to compare:
add_sos_constraintswritessos_type/sos_dimonto a variablethat is already there — no new name, so name-comparison could never see it.
add_objectivemutates the objective in place. A modelwhose objective was replaced after the build reported no drift at all, and the
render then shows the spec's objective, which is no longer the model's — wrong
rather than incomplete.
Two more were missed for duller reasons:
Named expressions. A spec's own are read lazily off
model.specand neverlive in
model.expressions, so everything that does live there was added byhand and none of it was reported.
Piecewise, at the wrong altitude.
add_piecewise_formulationputs fourgenerated constraints in the model and the tally named all four rather than the
formulation.
Model.__repr__already groups them via_get_piecewise_groups;unspecifiednow reads the same grouping, sovariablesandconstraintsareleft to what a caller actually declared:
The objective is a flag rather than a comparison, since the object is mutated in
place:
add_objectivesets it when the model already carries a spec, which abuild cannot trigger because it runs before
_specis assigned. A copy carriesit, and a netcdf file records it as an attribute — written only when true, so a
file from an untouched spec is byte-for-byte what it was. Nothing else in the
file could say the typeset objective is not the model's.
No false drift on a spec's own constructs
Verified, because this is the way the design could have failed: math-spec lowers
piecewise:andsos:into ordinary declarations, so they sit in the programlike anything else and typeset like anything else.
Verification (f08408c)
test/remoteis excluded: its OETC cloud-provider tests fail identically on thebase commit in this environment and touch nothing here.
Checklist
AGENTS.md).doc/release_notes.rstis included.🤖 Generated with Claude Code
https://claude.ai/code/session_015ZfHfTFENUy6WxnFFri5Td