Skip to content

feat(language): a parameter says whether its table must cover every coordinate, so a row lost in preparation is not read as a mask - #243

Closed
FBumann wants to merge 2 commits into
mainfrom
claude/parameter-coverage
Closed

FBumann wants to merge 2 commits into
mainfrom
claude/parameter-coverage

Conversation

@FBumann

@FBumann FBumann commented Aug 29, 2026 •

Copy link
Copy Markdown
Contributor

Prompt: "Please do a few stacked PRs implementing this / All stacked onto the cases pr / And to the description, add what lpspec removes or changes"

Note

The following content was generated by AI.

What this changes

parameters: takes coverage: total | masked, default total. A parameter declaring itself a mask is refused as a bound or a divisor — the two positions rule 8 gives absence no reading — before any data arrives.

parameters:
  cost: { dims: [generator] } # total: every generator has one
  ramp_limit: { dims: [generator], coverage: masked } # no row means no limit

Why

A table short of a coordinate and a table that never had one are identical in the data and mean opposite things. lpspec says so, and says it cannot decide:

sparse_parameters … reports rather than judges: a table that lost a row and a where: that removed one build the same model, and nothing else would say which parameters could be either.

That is a precise statement that the information is not in the file. Rule 8 gives absence a reading; nothing gave it a permission. This is the permission, and once declared the reporting can start judging.

What lpspec removes or changes

The one of the three with real downstream work.

  • Gains a bind-time check. sources.py refuses a total parameter short of a coordinate its dims reach — a DataError naming parameter and coordinate. Today that is countable, not reportable.
  • diagnostics().sparse_parameters narrows and loses its caveat. The "could be either" sentence is exactly what this key removes. It stays useful for masked parameters; for total ones the row becomes an error.
  • The ports corpus migrates. Deliberately-sparse parameters in examples/ports/*.yaml and differential/pypsa/ need coverage: masked, as this PR does to math-spec's own examples. Mechanical — they are the ones whose descriptions already say "no value means …".
  • Unchanged: curves.py, shape recognition, both lanes' compilers.

Verified

Toolchain reconstructed at the versions pixi.toml pins, run on this branch: ruff format --check / ruff check (0.16.1, pinned) clean; pyrefly (1.2.0, pinned) 0 errors; reuse compliant; typos clean; prettier clean; pytest -q -n auto 837 passed, 1 skipped (829 on the base — 8 new); mkdocs build --strict builds, and Read the Docs built the PR for real.

schema/math-spec.schema.json regenerated with python -m tools.schema (+9 lines); test_schema.py green.

Not run: zizmor, taplo (neither's files changed), compile-tex — coverage is not a rendered attribute and the golden typesetter tests are green, so this branch's LaTeX is byte-identical to its base's.

Mutation table

Both guards deleted in turn on a clean tree, restored with git checkout --, __pycache__ dropped both sides. tools/mutate.py expresses neither (a deleted call; a deleted dict update), so both were taken by hand.

Mutation Result
the guard is never called 4 failed
the divisor arm dropped, bounds still checked 2 failed
restored 833 passed (837 once the typst extra was installed)

The second is the one that matters: bounds are the obvious position, divisors the one a reader forgets, so a guard catching only the first would look right.

The permissive half is tested too — masked as a coefficient, a term and a where all still load. A guard refusing those would refuse the construct the declaration exists to describe.

The decision worth arguing about

The default. total breaks nothing here (this repo binds no data) and is what the other rules assume — but it is the whole downstream cost, and reversing it later is not free: ship masked as the default and every existing model silently means "sparsity allowed", after which total can only arrive as a per-file opt-in forever. One line in model.py plus eight example declarations if you want it the other way. Worth settling before there is a corpus, which is why it is here rather than buried in the diff.

Stack: second of three, on #242.

Relates to #296 (parameter coverage; #245 covers lookups)

@read-the-docs-community

read-the-docs-community Bot commented Aug 29, 2026 •

Copy link
Copy Markdown

@FBumann
FBumann marked this pull request as draft August 29, 2026 14:17
@FBumann
FBumann force-pushed the claude/data-rules-in-the-language branch from a0cc2d4 to 1e2b8a9 Compare August 30, 2026 18:19
@FBumann
FBumann force-pushed the claude/parameter-coverage branch from 92c8b6e to 9a8045c Compare August 30, 2026 18:19
@FBumann
FBumann force-pushed the claude/data-rules-in-the-language branch from 1e2b8a9 to 33b7db7 Compare August 31, 2026 18:29
@FBumann
FBumann force-pushed the claude/parameter-coverage branch from 9a8045c to 981aa95 Compare August 31, 2026 18:29
FBumann added a commit that referenced this pull request Aug 31, 2026
…bproblem is a call rather than a second file

A myopic pathway, a rolling horizon and a Benders subproblem share one move: a
variable stops being a decision and becomes a number somebody else chose. fix
takes every name in one call and validates once at the end. Two translations are
decisions, not copies: a where-masked variable becomes coverage: masked, and a
binary or integer variable becomes an int parameter — never a float, never a
bool that would read as a mask.

Rebased onto the parameter-coverage branch (#243) it needs for Coverage, off the
closed #247/#248 stack it was authored on; only the fix additions are kept.

Co-Authored-By: Claude <noreply@anthropic.com>
@FBumann
FBumann marked this pull request as ready for review August 31, 2026 20:46
@FBumann
FBumann force-pushed the claude/data-rules-in-the-language branch from 33b7db7 to f488380 Compare September 2, 2026 08:28
@FBumann
FBumann force-pushed the claude/parameter-coverage branch from 003ea7c to 200b831 Compare September 2, 2026 08:28
Base automatically changed from claude/data-rules-in-the-language to main September 2, 2026 08:44
claude and others added 2 commits September 2, 2026 10:44
…oordinate, so a row lost in preparation is not read as a mask

A table short of a coordinate and a table that never had one are identical in
the data and mean opposite things. `coverage:` says which was meant, and a
parameter declaring itself a mask is refused as a bound or a divisor — the two
positions where absence has no reading — before any data arrives.

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 9a8045c)
The absence-reading guard walked the variable lower/upper nodes twice — once
into the pool it scans for divisors, once to find the bound parameters — and
carried a module constant nothing read. Name the bound nodes once and drop the
constant. No observable change; both symbols are private.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011UmU9E9JpW14mNh2CrRMor
@FBumann
FBumann force-pushed the claude/parameter-coverage branch from 200b831 to 4ada6e5 Compare September 2, 2026 08:44
@FBumann

FBumann commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Note

The following content was generated by AI.

Closed in favour of #245, which now carries both halves of #296 as one PR.

#296 proposes coverage: on parameters: and lookups: sharing one vocabulary; this PR and #245 were that one issue written in two sittings. Keeping them apart also meant publishing ParameterCoverage on the program surface for exactly one release, since #245 renames it to Coverage.

No coverage is lost: the commit from this branch is unchanged in #245 (1bb17a0 + 200b831), tests included.

Branch claude/parameter-coverage is left in place for now, since #244 and #249 are still based on it.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants