Give the spec its own parameters and one read path - #949
Merged
Conversation
4 tasks
A build wrote the retained parameters and lookups straight into `model.parameters`, by assignment. That clobbered whatever the caller had put there, and left nothing able to say which of the model's parameters the spec owned -- `netcdf._coded` compensated by scanning every parameter on the model for an object dtype, so a caller's own labelled array was factorized into the spec's sub-dataset and came back as spec data. `ModelSpec` now holds its own dataset. `model.parameters` is untouched by a build and by a read, the two can carry the same name over different dimensions, and `_coded` sees only what the spec put there. The spec's parameters are written under the `spec-` prefix that already carried its coordinates: plainly as `param__<name>` over bare dimensions, or as codes where a lookup or an object array needs them, and put back on the master coordinates on read. `assert_model_equal` compares them, and `Model.copy` hands the copy its own buffers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ZfHfTFENUy6WxnFFri5Td
Reading `model.spec.expressions[name]` went through the retained parameters and raised if `retain` had dropped one; reading the same expression through `evaluate(name, sources)` went through the data instead. Two ways to read one thing, forked by a flag chosen at build time -- and `retain="none"` was a trap in a live session, where the sources were still right there. The two paths differed by one argument: `Resolve`, the callable a `Context` resolves a parameter with. So `ModelSpec` keeps the `Attached` a build made and resolves a parameter from the retained dataset first, from the sources behind it otherwise. `retain` now decides what a netcdf file holds, not what a session can read, and the refusal is left for the one case that cannot be served: a model read back from a file, whose sources are gone. `evaluate` keeps the job that is really its own, reading the spec against other data. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ZfHfTFENUy6WxnFFri5Td
FBumann
force-pushed
the
spec-parameter-ownership
branch
from
September 8, 2026 17:24
041ab2d to
4d4a508
Compare
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. |
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 #946. Two review findings from #922, one commit each.
1. The spec owns its parameters, not
model.parametersA build wrote the retained parameters and lookups straight into
model.parameters,by assignment. Two things followed from that:
add_speconly checks variables and constraints, so nothing caught it;netcdf._codedcompensated by scanning every parameter on the model for anobject dtype, so a caller's own labelled array was factorized into the spec's
sub-dataset and came back as spec data.
ModelSpecnow holds its own dataset.model.parametersis untouched by a buildand by a read, the two can carry the same name over different dimensions, and
_codedsees only what the spec put there. The spec's parameters are writtenunder the
spec-prefix that already carried its coordinates: plainly asparam__<name>over bare dimensions, or as codes where a lookup or an objectarray needs them, and put back on the master coordinates on read.
assert_model_equalcompares them, andModel.copyhands the copy its ownbuffers.
model.spec.parameterswas already the documented read path, so nothing thatread the spec through the accessor changes.
2. One read path for a named expression
Reading
model.spec.expressions[name]went through the retained parameters andraised if
retainhad dropped one; reading the same expression throughevaluate(name, sources)went through the data instead. Two ways to read onething, forked by a flag chosen at build time — and
retain="none"was a trap ina live session, where the sources were still right there.
The two paths differed by exactly one argument:
Resolve, the callable aContextresolves a parameter with. SoModelSpeckeeps theAttacheda buildmade and resolves a parameter from the retained dataset first, from the sources
behind it otherwise.
retainnow decides what a netcdf file holds, not what a session can read.from a file, whose sources are gone.
evaluate(name, sources)keeps the job that is really its own — reading thespec against other data.
Keeping the
Attachedstores nothing new: a build already built one and threw itaway after
build(). It does mean the model holds the caller'ssourcesfor itslifetime;
sourcesis read by key and is often a lazy view, so that is usuallycheap, but it is a change in ownership worth knowing about.
Verification (041ab2d)
One unrelated hunk in
examples/building-models-from-specs.ipynb: the repo'sruff-formatpre-commit hook normalised a cell that was stored in compact stringform into list form when the file was touched.
Checklist
AGENTS.md).doc/release_notes.rstis included.🤖 Generated with Claude Code
https://claude.ai/code/session_015ZfHfTFENUy6WxnFFri5Td