Skip to content

feat(language): a model declares what it assumes of its data, and the typeset math prints it - #471

Closed
FBumann wants to merge 6 commits into
claude/zealous-archimedes-k13bq7-arithmetic-wherefrom
claude/zealous-archimedes-k13bq7-assumptions
Closed

FBumann wants to merge 6 commits into
claude/zealous-archimedes-k13bq7-arithmetic-wherefrom
claude/zealous-archimedes-k13bq7-assumptions

Conversation

@FBumann

@FBumann FBumann commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Prompt: I really like that! Especially that the assumptions in piecewise that are mathspec internal are then exposed to the user! Can we PR this? Draft

Note

The following content was generated by AI.

Stacked on #469, which lets every where compare arithmetic. Replaces #465, whose base GitHub would not change. A file states what it assumes of its data under assumptions:, in the where grammar. The program carries each entry as two masks for the engine to check. The typeset document prints every assumption, and what each piecewise: block assumes of its breakpoints, under one heading.

What this changes

The surface, the program, the rendering

Surface. An eleventh declaration key, assumptions:. An entry is a bare where string, or a mapping with holds:, an optional where: and a description:. It holds at every coordinate of the product of the dimensions its two masks name, a missing row reading as false as in any where; a parameter supplied only where it applies takes a where: naming it. There is no dims:, because a predicate widens nothing. A predicate naming a variable is refused, and so is one that folds to a literal. With #469 underneath, either side may be arithmetic: p_min <= 0.5 * p_max, sum(p_max, over=generator) >= budget, a translation with its edge= and a position() guard in where:. Reference: docs/reference/language/declarations.md#assumptions.

Two parameters may be compared by name, coordinate by coordinate, in any where string, the narrower one read at every coordinate of the wider. Both are numbers, or both share a dtype, so two labels or two flags compare too, which the arithmetic path of #469 does not admit. The exclusivity check reads the pair as one subject ordered three ways plus a missing row, so a case when may use it. ParameterPairComparisonNode joins the closed WhereNode union.

Program. Program.assumptions maps each name to AssumptionDeclaration(holds, where), and assumption_message words the refusal, beside the existing Check union and check_message for curves.

Rendering. A fourth section, Assumptions, after Variable domains: each declared entry as a line, a lone comparison aligned on its relation as a constraint is; then each piecewise: block's checks, labelled <block> increasing, curvature, breakpoints, points. The increasing x-axis is an inequality between neighbours under the plain translation; the shape a method is exact for is prose, as a paper writes it; the two conditions on a points: mask are stated of the set the mask admits (Format.set_of is the one new format method). typeset_declaration takes an assumption name. The notation page shows the derived assumptions under each method: row, and examples/commitment.yaml gains the one assumption its math needs, p_min <= p_max.

Why

An assumption about the numbers is a rule two engines must not answer differently, so it is language; the numbers are the engine's, so the engine checks. The language already does this once, for piecewise: checks, and this generalises that mechanism to what the author writes, and prints both.

Verified

Run in a uv venv (Python 3.12) because pixi is not installable in this environment:

  • pytest -n auto on this head: 1276 passed, 12 skipped. Five pre-existing test_docs.py hook tests are deselected here because they import mkdocs, which the venv lacks; they fail identically on main in this environment.
  • ruff check, ruff format --check, pyrefly check, prettier --check docs: clean.
  • The regenerated Typst golden compiles with the typst package. Generated artefacts regenerated and read: schema JSON, the three golden files, notation.md, commitment.md.
  • CI passed on this same head under feat(language): a model declares what it assumes of its data, and the typeset math prints it #465, including docs-build --strict and compile-tex.

Not run locally: compile-tex, docs-build --strict, reuse, typos, taplo, zizmor.

Mutation table

Each guard deleted in turn, the suite run, the file restored from a copy and the tree checked clean.

Guard Caught by
the literal-fold refusal TestAssumptions[a-predicate-that-is-always-true], [...-always-false]
the variable-in-an-assumption refusal TestAssumptions[a-variable-in-the-predicate], [a-variable-in-the-where]
the dtype rule on a parameter pair TestRulesDecidedWithoutData[where-against-a-parameter-of-another-dtype], TestAssumptions[two-parameters-of-different-dtypes]
a pair's dims are both sides' dims test_a_parameter_pair_carries_the_union_of_both_dims_left_first
a missing row in a pair compares false in a case when still green on first run; test_a_parameter_pair_with_a_row_missing_compares_false_under_every_comparator added as the probe, and it fails with the guard deleted
Coverage moved, defaults departed from, and what was left out
  • The refusal test where-against-a-parameter asserted that two parameters cannot be compared; it is now where-against-a-parameter-of-another-dtype, and acceptance is covered under TestAssumptions. test_a_name_declared_as_none_of_the_three_is_refused became ..._of_the_four_....
  • The validation.py message for a variable-free comparison now points at assumptions: rather than at data preparation, and the limits page's unit-checking row says why a range is not the same refusal.
  • The golden fixture gains two lp curves and eight assumptions, four of them arithmetic, so the walk's line census reaches every new arm.
  • The head merges feat(language): a where may compare arithmetic over parameters #469's branch rather than rebasing onto it, so the history of the earlier PR is kept intact.
  • Not done, on purpose: the lpspec side (evaluating Program.assumptions, the pair node and the expression comparison at the data door) is the next PR, stacked on this one's pin. Checks the language could derive from use, a divisor being nonzero and named bounds not crossing, are a separate change. datarecord is untouched.

🤖 Generated with Claude Code

https://claude.ai/code/session_013HceuCYNepeQX8SdZtiMf1


Generated by Claude Code

… typeset math prints it

An `assumptions:` block holds predicates in the where grammar; each holds at
every coordinate of the frame its two masks name, a missing row reading as
false. The program carries each as `AssumptionDeclaration(holds, where)` with
`assumption_message` for the consumer that binds the data. A where may now
compare two parameters coordinate by coordinate, which the block's own examples
need. The typesetter prints an Assumptions section: every declared entry, then
what each `piecewise:` block assumes of its breakpoints, labelled by the block.

Docs sentence lengths (n / median / over 25): declarations.md 71 / 13 / 9,
piecewise.md 75 / 16 / 13, expressions.md 133 / 15 / 23.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013HceuCYNepeQX8SdZtiMf1
…r a curve's checks

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013HceuCYNepeQX8SdZtiMf1
… the assumptions branch

An assumption's predicate now takes arithmetic on either side, as every
where does on the base branch; the golden fixture carries one of each kind.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013HceuCYNepeQX8SdZtiMf1
@read-the-docs-community

read-the-docs-community Bot commented Sep 15, 2026 •

Copy link
Copy Markdown

Comment thread examples/commitment.yaml Outdated
assumptions:
floor_below_capacity:
description: a floor above the capacity leaves `upper` and `lower` no output to agree on
holds: "p_min <= p_max"

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 don't see the need to introduce a new term here. We can just use where?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think we should deliberately use a different term here!

It has a very distinct meaning here. It should check an assumption, and not filter!

It uses the where engine underneath, but that's an implementation detail.

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 was more thinking that when saying it aloud "assumption A is valid when Y holds, otherwise raise" makes sense, as does "raise where Y doesn't hold" or "refuse on A where Y doesn't hold". I guess this leads to a question purely on naming. #268 uses refusals.

In writing this, I have come round to assumptions as "assumption A is valid when Y holds, otherwise raise" sounds OK.

Comment thread examples/commitment.yaml Outdated
Comment on lines +70 to +73
assumptions:
floor_below_capacity:
description: a floor above the capacity leaves `upper` and `lower` no output to agree on
holds: "p_min <= p_max"

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 see you've removed the difference between a strict and non-strict assumption (error vs warn). That's probably fine, although there are some non-strict assumptions in calliope math which are nice to have there. I'm not sure whether I would keep them and force the user to change their data on an error or delete them because it's annoying to get an error every time just because of some data mismatches which will be ignored in the math...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed
We need to expose a Warning and error type then
Should be easy

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
Its base now carries main through alpha.91, which is where the relations
plan and the alpha.90 revert reach this branch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014pdMgEVkGBfh1f2AyiCSKA
…gned form

The merge left main's per-node dispatch below the aligned one that
already answers for it, so four kinds of comparison had a second,
unreachable printing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014pdMgEVkGBfh1f2AyiCSKA
@FBumann

FBumann commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

Superseeded by #589

@FBumann FBumann closed this Sep 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: data contract What a file guarantees about the data it binds

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants