Conversation
Documentation build overview
58 files changed ·
|
04b81f1 to
2004634
Compare
dd31dee to
8d053c6
Compare
2004634 to
9c8762c
Compare
8d053c6 to
839e7ef
Compare
9c8762c to
2b75498
Compare
2b75498 to
010f9ea
Compare
003ea7c to
200b831
Compare
010f9ea to
751e787
Compare
200b831 to
4ada6e5
Compare
751e787 to
1da93bc
Compare
1da93bc to
badd60f
Compare
|
@brynpickering @FabianHofmann opinions? |
…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)
… so a port nobody wired is not a term that vanishes A map short of a label lands its terms in no group at all, which is a shape a model wants and a wiring mistake in equal measure. `coverage:` says which, and a composed model declaring its coupling map total turns the second from a plausible answer into an error. The vocabulary is now one Coverage, shared with the parameter key it repeats. Co-Authored-By: Claude <noreply@anthropic.com> (cherry picked from commit 2b75498)
…and a bound's coverage is data's to check Drops the load-time refusal of `coverage: masked` as a bound or a divisor. It refused examples/ports/pypsa_losses.yaml, whose `loss` is masked on the same parameter that bounds it — a covered bound the file cannot be read to prove, and the message told the author to apply a rewrite the model already had. Whatever binds the table refuses it row by row instead. Adds the refusal `points:` was already the answer to: a parameter a piecewise block consumes declares no coverage at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013a3tFCULeY7Skmkhbxpj7V
…e carries no coverage, and the absence page says what a missing row is Six schedule parameters in examples/pypsa.yaml are consumed only as `where:` masks and said so in prose; they now say so with `coverage: masked`, or the example refuses to bind on any network where most components carry no schedule. A label-space lookup lowers with `coverage=None`: the file cannot declare one there, and a program reporting `total` invited a consumer to enforce it. absence.md kept saying a missing parameter row reads as the value that contributes nothing, unconditionally; it now says that is the `masked` reading and a bind-time error is the `total` one, and dimensions.md agrees at both places it spoke. The schema enum test gains the coverage rows, and the unread `COVERAGE` set is gone. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BFctf4r4YcXZsc65N2Mfwt
…r than claiming a complete table Load refuses `coverage:` on a parameter a `piecewise:` block consumes, because `points:` already says how far each curve runs. Lowering then wrote the unwritten default, so the program told a consumer that a ragged breakpoint table must carry every coordinate its dims reach — and a binder that trusts it refuses the curve the block admits. The lookup half already answers this the right way: a label space lowers with `None`, and the parameter side now does the same for the names a block consumes or emits. `_NO_READING_FOR_ABSENCE` goes with it: the last trace of the bound/divisor guard this branch removed, defined and read nowhere. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVCNZr7CyqT2UY9kKdCWGR
… mask, so a network that leaves one out still binds The corpus migration read descriptions, and the ones whose prose already said "no value means …" got `coverage: masked`. Binding the corpus against the PyPSA ladder finds the rest: `pypsa.yaml` is a superset model, one file covering every feature, and any one network exercises a slice of it — so a parameter for a feature the network does not use arrives with no rows at all. Fourteen of the thirty-eight such declarations were already marked; these are the other twenty-four. Measured on lpspec's ladder — sixteen rungs, every declared parameter's rows against the product of its dims' labels, in one pass rather than one refusal at a time. No lookup in the corpus is short of a label. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVCNZr7CyqT2UY9kKdCWGR
b5935a4 to
df2da95
Compare
|
@FBumann I'm not sure it's needed for numeric parameters. I expect them to always be at least partially masked. I agree that some cases you want all data to be there (a boolean membership array, a string lookup that maps assets to buses). I wonder whether the more generalised checks
all_gen_bus:
where: not gen_bus
errors: raise
message: "all generators must map to a bus"
p_max_min:
where: p_max < 0
errors: raise
message: "Cannot have negative `p_max`"Perhaps for If we have multi-dims then |
|
@brynpickering Such a check syntax is kind of another mini lang inside the model, especially the error stuff etc. Not sure about wether this should be in scope. How would we treat that in typeseting? total/masked is pretty straight forward. |
The |
|
On where dimensions:
asset: ...
lookups:
gen_bus: {over: asset, into: bus} # will be masked to just generators
store_bus: {over: asset, into: bus} # will be masked to just storage devices
checks
all_bus:
where: not (gen_bus or store_bus)
errors: raise
message: "all assets must map to a bus" |
Good point! Could we open an issue discussing the scope of this checks feature more explicitly? Which operations need to be possible? I would close this PR for now if you agree @brynpickering |
Sure, keep the branch around in case we decide it is actually the best approach after diving deep on I'll work on a fleshed out |
|
@brynpickering I dived deeper into checks. #471 With |
Three reference pages conflicted, each where main had rewritten the same passage this branch edited. Main's prose is kept in every case and this branch's semantics folded into it: - absence.md: main's two-paragraph shape, carrying the coverage split and this branch's "the bind is refused" for the four positions where absence has no reading. - declarations.md: main's field table and dtype section, plus the coverage row, the worked example and the four coverage paragraphs. - dimensions.md: main's three numbered rules, whose third now says a coordinate with no row is how `coverage: masked` masks rather than that it is absence — which absence.md no longer says either. The partial-lookup paragraph carries the lookup example, and the lookup field table gains the `coverage` key the schema already took. Two test fixtures declared `variables.s` with `foreach:`, which #429 renamed to `dims:` on main. Git flagged no conflict there; the four coverage tests failed until they were updated. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012k5ThJdGaqKM8NYoE2XhG5
Note The following content was generated by AI. Merged What each conflict was, and how it was settled
Main's sentence there read "a coordinate with no row is absence, and absence is how a model masks" — which One thing added that was not a conflict: the lookup field table did not list One semantic conflict git did not flag. Both fixtures now say Verified on the merged tree at
|
| Gate | Result |
|---|---|
pytest -q -n auto |
1214 passed, 6 skipped |
ruff check / ruff format --check |
pass, 131 files |
pyrefly check |
0 errors (9 suppressed) |
prettier --check on the changed pages |
pass |
mkdocs build --strict |
pass — no dead link, despite main's docs restructure |
pyrefly reports 3 errors in a venv without types-PyYAML, which pixi's environment provides; with it installed the count is 0.
The strict docs build needed the docs.python.org inventory dropped into a throwaway config, because this session's proxy answers that host 403. mkdocs.yml is untouched, and the throwaway was deleted. The schema and the typeset goldens did not need regenerating — test_schema.py and test_docs.py pass as merged.
Not run: compile-tex, reuse, typos, taplo, zizmor. prettier --check over the whole tree flags mkdocs.yml, which is equally unformatted on main and is not this branch's to fix.
Worth deciding before this lands, and not a merge question. #471 stacks assumptions: onto the same ground: a missing row reads as false there too, so any assumption naming a parameter refuses a short table, and where: on an assumption says a gap was meant. The two mechanisms differ in default — coverage: total catches every short table with no author action, while an assumption catches only what somebody wrote a predicate about, and a predicate that folds to a literal is refused, so "this table is complete" has no spelling of its own there. Neither PR mentions the other. Both close work on #296.
Generated by Claude Code
Brings the branch up to 0b4f046. Deliberately 0b4f046 rather than main: main also carries #474, which #481 reverts. The lookup half of this branch had to be rewritten rather than merged. #437 replaced `lookups:` with `relations:`, so the `over:`/`into:` map this branch declared `coverage:` on no longer exists. `coverage:` now sits on `RelationBlock`, through the same `_CoveredBlock` the parameter uses, and `RelationDeclaration` carries it into the program. **This takes a reading the author should confirm.** A relation declares no direction, so `total` can no longer mean "every label of the dimension it is over". It now means the table carries a row for every coordinate of its `key:`, and where there is no key, for every combination of its columns' dimensions. The keyless case is the one worth arguing about: a bare relation is many-to-many, so a `total` claim over the full product is rarely what a file means, and refusing `coverage:` there may be the better answer. The parameter half is untouched by the rename and merged as it stood. Coverage moved: `TestLookupCoverage` is `TestRelationCoverage` and declares `columns:`/`key:`; the schema enum row is `RelationBlock`. The schema, goldens and pages are regenerated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017VApqcmBKLXtKTmk3ajmtK
|
Note The following content was generated by AI. Brought up to the alpha.90 release as #437 replaced The reading I had to choose. A relation declares no direction, so The keyless case is the one worth arguing about. A bare relation is many-to-many, so a The parameter half is untouched by the rename and merged as it stood. Coverage moved, and what was verified
Not run: The merge is against Generated by Claude Code |
|
Superseeded by #671 |
Note
The following content was generated by AI.
Closes #296.
parameters:andlookups:each takecoverage: total | masked, defaulttotal— one word, one vocabulary. Atotaldeclaration short of a row is a bind-time error naming what is missing;maskedsays the gap is the point.Nothing here checks a table against data, because nothing here holds data. The language states the rule and names the vocabulary; the consumer that binds enforces it. One refusal stays at load, and it is a question the file alone settles:
coverage:on a parameter apiecewise:block consumes.What the rebase onto
eac1930changed#422 removed the label-space kind of lookup — every lookup now maps
into:a declared dimension — so the second of this branch's two refusals has nothing left to refuse and is gone with it, along with the test that pinned it and the sentence indimensions.mdthat stated it.LookupBlock.intois required again andLookupDeclaration.coverageisCoveragerather thanCoverage | None: with one kind of lookup, every map answers for its coverage.Three semantic conflicts git did not flag, each carried into the commit that introduced the assertion:
SMALL_MODEL'stagis a parameter onmain, where it used to be a label-space lookup, so the lowering test's parameter table gains a'tag': 'total'row.TestRulesDecidedWithoutDatapins the closed schema's valid-key list for a lookup, which this branch addscoverageto.dimensions.mdwas rewritten around this branch's edit; the surviving sentence took it.What changed after review
An earlier revision also refused
coverage: maskedat load wherever it stood as a bound or a divisor. That is gone. It refusedexamples/ports/pypsa_losses.yamlin the lpspec corpus, whoselossvariable is masked onloss_maxand bounded by it:Reproduced here before removing it — and the message told the author to "move the mask onto the declaration that wants it, as a
where:", which that model already does. Narrowing it to the case where thewhere:names the same parameter is decidable (the loweredwhereisMask(root=ParameterDefinedNode('loss_max'))), but it only moves the false refusal: a variable masked indirectly is still refused, and the file cannot say whether it is covered. Whether a bound has a row is a question about rows, so it belongs to whatever binds — where the check is row-precise and already exists.That guard was the one place this PR broke its own division. Removing it is most of the src diff.
In its place, the refusal
points:was already the answer to. A ragged curve — three breakpoints for one generator, five for another — is not something a parameter declares:Both spellings are refused, not just
masked:totalthere would claim every coordinate thedimsreach, which is exactly what a short curve does not carry. Without this, a file could say "the missing row is meant" whilepoints:and the curve-extent check still refuse it — a contradiction the review found, and the language already had the third state.The two spellings of
coverage:are now one.ParameterBlockandLookupBlockshare_CoveredBlock, so the field isCoverage | None = Noneon both andcoverage_or_defaultis what consumers ask. Needed anyway: the piecewise refusal has to know whether the file wrote the key.What changed after the second review
coverage: masked.Generator_p_set,Link_p_set,StorageUnit_p_set,StorageUnit_state_of_charge_set,Store_e_setandLine_s_setare consumed only aswhere:masks and their descriptions already said a component without one has no row; under the default the example would refuse to bind on any network where most components carry no schedule. Onlyexamples/pypsa.yamldeclares them.maskedreading and a bind-time error naming the coordinate is thetotalone. declarations.md said amaskedgap "is absence", which under absence.md's own rules would spread through arithmetic and take the row — the earlier revision of this PR testeda-term-reads-as-zero, and lpspec reads it that way, so the page now says "reads as the value that contributes nothing" and leaves the table of what creates absence as it was. dimensions.md agrees at both places it spoke.coveragefield, and the unreadCOVERAGEset is deleted.What changed after the third review
A parameter a curve owns answers for no coverage. Load refuses
coverage:on a parameter apiecewise:block consumes —points:says how far each curve runs — but that refusal is a fact about the file, and whatever binds the table reads the program. Lowering wrote the unwritten default there, so the program said a ragged breakpoint table must carry every coordinate its dims reach. Against lpspec'sexamples/piecewise_ragged.yaml, which declaresbp_xandbp_yover[generator, bp]with no two curves the same length, before and after:A binder that trusted the first refuses a model the block admits.
ParameterDeclaration.coverageisCoverage | None, and lowering passesNonefor the names a block consumes or emits — the emitted mask is derived from a values parameter and is exactly as ragged as it is, so it needs the same answer. The consumed set is one property onPiecewiseBlock, read by the load-time refusal and by lowering rather than spelled twice. Reproduced as a failing test first: the three names came back'total'._NO_READING_FOR_ABSENCEis deleted — the last trace of the bound/divisor guard this branch removed, defined and read nowhere, and its comment still citing a rule the guard no longer applies.Verified after the rebase
pixiis not installed in the session that rebased this, so the gates were run tool by tool in a plain venv at the pinnedruff==0.16.1andpyrefly==1.2.0, ondf2da95, clean worktree, baseeac1930:ruff check/ruff format --checkpyrefly checkreuse lintpytest -qorigin/mainin the same venv: 1155 passed, 6 skippedmkdocs build --strictprettier --checkon every changed pageschema/math-spec.schema.jsonandtests/typesetting/golden/*.outwere regenerated and are byte-identical to what the branch already carried. The schema diff is the parameter'scoveragetaking the shape the lookup's already had —anyOf [enum, null],default: null. The published schema therefore no longer advertisesdefault: "total"for it; the reading lives incoverage_or_defaultand in the language reference, as it already did for lookups.Not run:
compile-tex— tectonic and typst are absent here, and the golden output did not move. The 6 skips are those two. The strict docs build had the one intersphinx inventory (https://docs.python.org/3/objects.inv) removed frommkdocs.ymlfor the run and restored after: the proxy answers it 403, and it is the only thing that failed the gate.pixi run ciitself was not run.Every guard deleted in turn — measured before the rebase, at
badd60fApplied to a clean tree, reverted between. The counts are the pre-rebase suite, and the label-space row is the refusal #422 has since made unreachable:
maskedThe last one survived on the first attempt at 1028 passed —
ruff-formathad reflowed the call across lines, so the edit silently matched nothing. Re-applied against the formatted source it fails as it should. Worth recording: a mutation that "survives" is a claim about the edit before it is a claim about the suite.What lpspec has to do
Per the review on that side:
sources.py, not the engine._short_parameterslives in the polars engine only, so a check there would be lane-asymmetric;tidy_sourcesis the door both lanes enter.lookup_relationsgains one check after_check_keys_are_labels, which already holds both the labels and the rows.diagnostics().sparse_parametersdies. It reports rather than judges precisely because nothing said which parameters could be either; withtotalan error andmaskeddeclared there is no middle rung left. That field, its engine block, its docs row and its tests go — the one place the stack makes lpspec smaller.bench/anddifferential/pypsa/, unmeasured. Three of the 28 are block-owned —piecewise_ragged'sbp_x,bp_yandcost_curve_points— and declare nothing at all. The other 25 writecoverage: masked,piecewise_conversion'sbp_rateandgenx_piecewise_fuel'sfuel_slope/fuel_interceptamong them: they read like breakpoints and nopiecewise:block consumes them, so the exemption does not reach them.relation()helper turns each null into a missing row, and of the four linesline_frommaps all four whileline_tomaps three. Soline_todeclarescoverage: maskedandline_fromis left as it is — 1 of 63into:lookups in that corpus.Deliberately not done: the bind-time check itself, which is the consumer's.
coverageis not typeset: two files differing only in it render identically in all three formats, which is the precedentdtypealready sets — the legend states the model, not the contract its tables must meet. A row in the legend is a follow-up if a reader of the rendered document turns out to need it. Base:main; #242 has merged, and its overclaim on unsupplied dimensions — also from this review — is #378, not this PR. #244 and #249 still need retargeting offclaude/parameter-coverage.