feat(language)!: missing: says what a missing row means, a table short of a row is refused unless the file says otherwise, and a variable's absence: is now missing: - #810
Open
FBumann wants to merge 21 commits into
Conversation
`default:` on a parameter gives the value a missing row reads wherever a value is read: a coefficient, a term, a divisor, a bound, and a comparison in a where. A bare numeric name in a where still asks whether the data has a row. The exclusivity prover reads the default in a comparison, and the legend prints it. Refs #773 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QiDTE4WvKobVxTDxUeWhc9
… prints one Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QiDTE4WvKobVxTDxUeWhc9
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QiDTE4WvKobVxTDxUeWhc9
Documentation build overview
39 files changed ·
|
Contributor
|
nice! |
…tum8xc # Conflicts: # CHANGELOG.md
… is complete unless the file says otherwise One key replaces `default:`, `absence:` and the `coverage:` of #671. - A parameter takes `missing: error | absent | neutral | <value>`, default `error`. A value has the parameter's dtype; `inf` and `.inf` are one number. - A relation with `values:` takes `missing: error | absent`, default `error`. A bare relation takes none. - A variable takes `missing: absent | neutral`, the old `absence:`. - A parameter a piecewise block reads takes no `missing:`; the expansion declares it `neutral` where the curve has `points:`. - The exclusivity prover reads a value in a comparison at a missing row. - The PyPSA examples mark their sparse tables `neutral`, and the two partial outage maps `absent`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QiDTE4WvKobVxTDxUeWhc9
missing: says what a missing row means, and a table is complete unless the file says otherwise
FBumann
pushed a commit
that referenced
this pull request
Oct 2, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QiDTE4WvKobVxTDxUeWhc9
FBumann
added this pull request to stack #832
October 2, 2026 09:11
Contributor
Author
|
@FabianHofmann We need to agree on a default! "error" will the most strict/safe, but might be cumbersome. EDIT: I think |
…tum8xc A test from #787 builds the exclusivity grid directly, and now passes it no defaults. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QiDTE4WvKobVxTDxUeWhc9
…t `error` `refused` completes "a missing row is ___" as `absent` and `neutral` do, and is the word the docs use for data a consumer rejects; "error" is the word for what `to_spec` raises. How a consumer reports a refusal is its own, and it does not build the model from the data. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QiDTE4WvKobVxTDxUeWhc9
…ing:`, and a relation refuses `missing: null` The parameter and the relation hold `None` until read, and the schema published that `null`. The parameter's readings and its `inf` spellings were one enum; they are two branches now, and the closed-vocabulary test covers the parameter too. A relation took `missing: null` where a parameter and a variable refuse it. The validator that still said "error" is `_refused_or_absent`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QiDTE4WvKobVxTDxUeWhc9
…tum8xc #808 and #815 add six sparse tables, marked missing: neutral: Line_v_ang_max and Transformer_v_ang_max (no row by default), and the module sizes of a storage unit, a store, a line and a transformer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QiDTE4WvKobVxTDxUeWhc9
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011a8N2JQdmmd1WhVCKN1a2A
…limit The six start-up and shut-down ramp tables kept the default `refused`, but the example reads them as "has a row" tests: `when:` with `otherwise: 1`, and `(ramp_limit_up OR ramp_limit_start_up)` in the big-M masks. Under `refused` both are finiteness tests, so the `otherwise:` was dead and the OR always true. `missing: neutral` gives them the meaning they had on main and the one PyPSA gives a NaN ramp limit. The same two tables in pypsa_linearized_uc.yaml are marked too. The other bare names on a `refused` parameter (`*_nom_max`, `e_sum_min`, `e_sum_max`) are finiteness tests, because PyPSA writes infinity where no cap is meant. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CsaSWMofYqJiKmnSiPycV9
Lowering reported `None` for every parameter a `piecewise:` block consumes, while the expansion declared the same parameter `neutral`. A row outside the curve that reads the parameter (`r <= by`) so had no reading in the spec and `neutral` in its expansion. `curve_readings` is now the one home of the rule: `neutral` where only curves with `points:` read the parameter, `refused` otherwise. Lowering reports it and the expansion declares it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CsaSWMofYqJiKmnSiPycV9
…sing breakpoint reads as zero A curve over every breakpoint now reads its parameters `refused`, so the `<block>_complete` text, the `assumptions_of` docstring and the ragged example said what the code no longer does. The assumption now says that a missing row does not shorten the curve. The expansion fixtures and the generated how-to page follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CsaSWMofYqJiKmnSiPycV9
…ence:` The compose how-to quotes the message `merge` prints now, taken from a run. The pypsa page's rung table names the spill variable's `missing: neutral`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CsaSWMofYqJiKmnSiPycV9
…at it is absent The witness for a missing row read `efficiency is absent`, for a parameter declared `missing: 1`, whose reading is not `absent`. A null cell now reads `has no row`, or `has no value` for a comparison of expressions. The exclusivity docstrings name `missing:`, not a `default:` key that does not exist. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CsaSWMofYqJiKmnSiPycV9
…s as The fuzz passed no values, so the prover's reading of a missing row in a comparison never ran there. Each pair now draws one per parameter: none, a literal, a value between literals, or infinity. The infinity test carries its claim in an assertion message. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CsaSWMofYqJiKmnSiPycV9
… reading but the default The legend printed a value beside a parameter and left out `absent` and `neutral`, and a relation's `missing:`. It now prints the reading in monospace in the same place: `` `neutral` where the data has no row ``. A map that may leave a key out says so beside each set it joins. `refused` is the default and prints nothing, as an unwritten key does. The golden model marks one parameter of each reading and one partial map; the goldens and the generated pages are regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CsaSWMofYqJiKmnSiPycV9
The line takes the PR's new title, `feat(language)!:`, and says that a table short of a row is refused unless the file says otherwise, and that a variable's `absence:` is now `missing:`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CsaSWMofYqJiKmnSiPycV9
missing: says what a missing row means, and a table is complete unless the file says otherwisemissing: says what a missing row means, a table short of a row is refused unless the file says otherwise, and a variable's absence: is now missing:
…t where a row outside the curve reads it The loader refused `missing:` on a parameter a `piecewise:` block reads, and the reading came from the curve. A row outside the curve that read the same table had no reading the file could set. Now: - A curve's parameter takes `missing:` like any other parameter. - A parameter a curve reads and a declaration outside every curve reads too, with no `missing:`, is refused at load. The message names the parameter, the curve, the outside reader and the four readings. - A values parameter that only curves with `points:` read, with no `missing:`, reads `neutral`: the curve stops where its mask stops, so its table has no rows past it and the curve reads none. `piecewise.ragged` names these, and lowering and the expansion both read it. - A boolean `points:` mask is no longer inferred `neutral`. A full table of `true` and `false` is a mask as well, so it reads the default, `refused`, unless the file says otherwise. `program.names_under` is public, as lowering now reads it too. The reading page declares `bp_y` `missing: refused`, because an assumption reads it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CsaSWMofYqJiKmnSiPycV9
…here it is not the default A variable's `missing: neutral` now prints beside the variable, in the slot a parameter's reading uses: `` `neutral` where the mask leaves it out ``. The default, `absent`, prints nothing. The golden model marks `headroom`, and the goldens and generated pages are regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CsaSWMofYqJiKmnSiPycV9
This branch has not been deployed
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.
One key,
missing:, says what a missing row means. It replaces a variable'sabsence:and #671'scoverage:. Parameters and relations default torefused, so a row lost in data prep is refused when the data is attached.Closes #773. Closes #790. Closes #296. Supersedes #671 (closed). #824 and #826 are stacked on this PR.
What this changes, breaks, gate output, departures
What this changes
missing:values:refusedabsentneutral0as a coefficient andfalsein awherewhere:refusedcompletes "a missing row is ___" asabsentandneutraldo, and is the docs' word for data a consumer rejects. "Error" stays the word for whatto_specraises. A consumer does not build the model from data with a refused row; how it says so (raise at the first gap, list every coordinate) is its own.Loader (
spec.py).ParameterBlock.missing, with.readinggivingrefusedwhere the file wrote nothing.RelationBlock.missing, with.readinggivingNonefor a bare relation, whose rows are its membership;missing:on a bare relation is refused.VariableBlock.missingreplacesabsence:(undefined→absent,zero→neutral). A value has the dtype: any number forfloat, an integer forint(noinf),true/falseforbool, none forstr.infand-infload as the same floats as.infand-.inf.missing: null(on all three kinds), NaN, a quoted number, an unknown word, and a reading a kind does not take are refused at load, each naming the fix. Agiven:parameter takes nomissing:.Program.
ParameterDeclaration.missing,RelationDeclaration.missing,VariableDeclaration.missing; the typesMissing,MissingReading,RelationMissing,VariableMissingreplaceVariableAbsenceinmathspec.program.__all__, besideVariableDomainandParameterDtype.names_underis public, because lowering reads it too.Schema. Each kind publishes its readings as an enum and its unwritten reading as the default:
refusedfor a parameter and a relation,absentfor a variable. A parameter'sinf/-infspellings are their own branch. The closed-vocabulary test covers all three.Piecewise (ported from feat(language): a parameter and a relation each say whether their data must be complete, so a row lost in preparation is not read as a mask #671, then changed by the second follow-up). A curve's parameter takes
missing:like any other. Where a declaration outside every curve also reads it and the file writes nomissing:, the load is refused:The assumptions a method implies are the curve's and do not count; an
assumptions:entry the file writes does. One reading is still inferred, because the curve fixes it: a values parameter that only curves withpoints:read, with nomissing:written, isneutral. The curve stops where its mask stops, so the table has no rows past it by design and the curve reads none of them;refusedwould refuse the curve the block declares.piecewise.raggednames these; lowering reports them and the expansion declares them. A booleanpoints:mask is no longer inferred: a fulltrue/falsetable is a mask as well, so it reads the default,refused. The<block>_completeassumption says "a missing row does not shorten the curve".Exclusivity prover. A comparison at a missing row reads the value. Before this change,
efficiency == 1andnot efficiencywere proved apart undermissing: 1, although a missing row is in both. A bare name keeps the null cell, and so does arefusedparameter, which can only add witnesses. A witness saysefficiency has no row, notis absent, so it does not use the name of a reading. The soundness fuzz draws a value per parameter.Typesetter. The legend prints every reading but the default beside its declaration, in all three formats: a parameter's value as math (
…, ∞ where the data has no row), a parameter's reading in monospace (…, `neutral` where the data has no row), a map withmissing: absentbeside each set it joins, and a variable'smissing: neutral(…, `neutral` where the mask leaves it out).refusedand a variable'sabsentprint nothing, as an unwritten key does.format.numberis shared by the walk and the legend, and prints-∞.Examples. Under the new default, every sparse table is marked. In
examples/pypsa.yaml: 71 parametersmissing: neutral, the ones whose description says a row may be missing (*_set, ramp limits including the start-up and shut-down ones,p_init,*_nom_mod,v_ang_max, maintenance durations, cycle and constraint weights, BODF,big_m,Carrier_max_growth), and the two partial outage mapsOutage_lineandOutage_transformermissing: absent. Six inpypsa_linearized_uc.yaml. The 24 fragments are regenerated withtools.pypsa_split, andcheckpasses. No example has a curve parameter read outside its curve; the curve onreading.mddeclaresbp_ymissing: refused, because an assumption reads it.Docs.
absence.md,declarations.md("A missing row"),relations.md(data contract),expressions.md,piecewise.md("Missing breakpoints"),reading.md, the glossary, the compose how-to and rule 8 on the language index. Schema, goldens and generated pages regenerated.What breaks
A release with this raises the minor version.
absence:on a variable is nowmissing::undefined→absent,zero→neutral. No alias.missing:is nowrefused: a consumer refuses a table short of a row. A file with a deliberately sparse table needsmissing: neutral(the old reading) ormissing: absent.missing:, or the load is refused.ParameterDeclarationandRelationDeclarationtakemissingbeforedescription, so a positionaldescriptionmoves one place.Bare names on a
refusedparameter in the PyPSA exampleA bare numeric name asks "has a row and is finite". Under
refusedevery row is there, so it asks only "finite?". The six start-up and shut-down ramp tables (Generator_,Link_,Process_ramp_limit_start_upand_shut_down) wererefused, sootherwise: 1in*_start_up_ratewas dead and(ramp_limit_up OR ramp_limit_start_up)was always true. They are nowmissing: neutral, the meaning they had onmainand the one PyPSA gives a NaN ramp limit (no limit). The bare names left on arefusedparameter are*_nom_max,Generator_e_sum_minandGenerator_e_sum_max; PyPSA writes infinity where no cap is meant, so "finite?" is what they mean.Gates
Run on
475ac3e5:pixi run lint: passed.pixi run test: 2710 passed.tests.typesetting.golden,tools.gallery,tools.notation,tools.home_math,tools.spec_math,tools.expansion_math,tools.pypsa_split).pixi run docs-build: blocked here. The proxy refusesdocs.python.org/3/objects.inv, and the only warning is thepathlib.Pathautoref that inventory resolves. CI runs it.pixi run compile-tex: blocked here. Tectonic cannot download its bundle. CI runs it.Guards, each deleted in turn (on
5f63706unless noted):missing:(on03ba7392)points:values parameter asneutral(on03ba7392)neutralrefused(flipped toneutral)Nonemissing:on a bare relationneutraland values on a relationrefusedand values on a variablemissing: nullon a parameter / on a variablemissing: nullon a relation (on7a2449af)refusedas the default (on7a2449af)infas a numberhas no row(onbf743d07)714aa67c)714aa67c)475ac3e5)The fuzz does not catch a deleted value read in the prover: it checks that the cells cover the values, and both of its sides read through the same function.
TestAMissingRowcatches it.Departures and things deliberately not done
missing: nullis refused, althoughupper: nullloads because every open field takesnull. Herenullcould mean the default (refused) or an absent row, so the refusal names both words.refused. feat(language): a parameter and a relation each say whether their data must be complete, so a row lost in preparation is not read as a mask #671's list was the starting point; this branch's examples have grown since.points:, where the defaultrefusedwould serve the curve and the outside row alike. The rule is stated once, for every curve.infvalue on a coefficient. Whether a row is missing depends on the data.missingtypes are not exported from the top-levelmathspecpackage, as no vocabulary type is; they live inmathspec.program.🤖 Generated with Claude Code
https://claude.ai/code/session_01CsaSWMofYqJiKmnSiPycV9