Repository navigation
fix(core): evolve recomputes defaults computed from the cosmology - #444
Conversation
`build` takes sigma_8 and n_s from the cosmology's meta when they are not given, but `evolve(cosmology=...)` kept the old values, so a cosmology scan done with `evolve` silently gave hybrid models (e.g. Planck15 with Planck18's sigma_8), and evolving to a cosmology without meta silently kept sigma_8 where `build` raises. Each stage now records which parameters below it took a default from its `computed_defaults`, and which paths of the given parameters the hook looked up (`ComputedRecord`, in an init=False, eq=False, physics=False field, so it is not part of the value, the hash or the content hash). `evolve` makes every parameter it sets explicit, and when it changes a path the hook read, calls the hook again on the new tree for the parameters still computed. So `build(**a).evolve(**b) == build(**a, **b)`. A pickle or copy keeps the record; a configuration does not, so every value a configuration gives is explicit when read. `Stage.computed_parameters()` lists the computed ones. The missing-meta error now names the cosmology's class when it has no name, and asks to set the parameter explicitly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019U8u44xFH87Am2mNHVrimP
|
Sorry @steven-murray, you've used your own review budget of 250,000 diff characters for the last 7 days. You can request another review in 4 days and 1 hour by commenting |
Reviewer's GuideData structuresThe router records stages’ declared computed-default inputs and validates that each path exists in the stage tree, while
APIThe Stage contract declares computed-default inputs and documents when
ImplementationEvolving now reruns computed defaults when declared inputs change, adds only defaults not overridden by the call, and checks hook lookups for undeclared inputs.
The missing-cosmology-parameter error now identifies unnamed cosmologies by class and asks callers to set the parameter explicitly.
DocumentationThe core documentation page is updated to explain the computed-default behavior and its use during evolution.
TestsThe new tests cover recomputation, overrides, missing cosmology metadata, stage composition, and declared-input validation.
Routing tests adapt cosmology comparisons to omit defaults that are recomputed, verify lookup tracking, and declare inputs for the toy computed-default hook.
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #444 +/- ##
=======================================
Coverage 99.70% 99.70%
=======================================
Files 69 69
Lines 9468 9537 +69
=======================================
+ Hits 9440 9509 +69
Misses 28 28 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
from_flat on a tree with an optional stage that stays None (only shared names reach it) tried to set that stage's record on None. Skip it. Cover the remaining branches of the re-derivation: a shared computed default, a stage given whole inside an extension, and the evolving tree's parameter view. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019U8u44xFH87Am2mNHVrimP
|
asdf couldn't fetch uv. On the previous head, b744d22, the RTD build of the same docs passed. The new commit only touches Generated by Claude Code |
…ted-defaults # Conflicts: # docs/core.rst # src/hmf/core/config.py
Replace the provenance design with a rule that depends only on the stage's value: when evolve changes a parameter a stage's defaults are computed from, every parameter computed_defaults gives a default for takes the new default, unless it is given in the same call. So mf.evolve(cosmology=c) takes sigma_8 and n_s from c, and mf.evolve(cosmology=c, sigma_8=0.8) keeps 0.8; any other change computes no default again. A stage declares what its defaults are computed from in computed_defaults_inputs (MassFunction: the transfer stage's cosmology); the router checks each is a field. Tracking what the hook looks up would also count its "is it given?" checks, so evolve(sigma_8=...) would reset an n_s set earlier. undeclared_inputs now also checks the hook against its declared inputs, through the lookups GivenParameters records. Removed: the init=False provenance field on Stage, ComputedRecord, computed_parameters(), and the computed-defaults element of the pickle state. A stage read from a pickle or a configuration evolves as the original does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019U8u44xFH87Am2mNHVrimP
…_defaults
A stage that defines computed_defaults without declaring what its defaults are
computed from would never have them recomputed by evolve, which is the bug
this PR fixes. Building its routing table now raises a TypeError that asks for
the inputs. No stage outside the tests defines the hook except MassFunction,
which declares its cosmology.
The toy Holder stage in the routing tests declares base, with a hook computed
from base's value, so evolve(base=4) now equals from_flat({"base": 4}).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019U8u44xFH87Am2mNHVrimP
Summary
_with_computed_defaultsran only infrom_flat/build, never inevolve, so:A cosmology scan done with
evolvegave hybrid models. Also,evolveto a cosmology with nometakept σ8 without a word, whilebuildraised.The rule
When
evolvechanges a parameter that a stage's defaults are computed from, every parameter thatcomputed_defaultsgives a default for takes the new default, unless it is given in the same call. Any other change recomputes nothing.So
mf.evolve(cosmology=c)equalsbuildwithcand all ofmf's other parameters except σ8 and n_s. To keep σ8 fixed through a scan, pass it with each cosmology.The result depends only on the stage's value and the changes: there is no hidden state. A stage read back from a pickle, a copy or a configuration evolves exactly as the original does.
If the new cosmology can't supply a parameter that isn't passed,
evolveraises the sameValueErrorasbuild. The message names the parameter and says "set it explicitly". It also names the cosmology ascosmology.name or type(cosmology).__name__, where it used to print'None'.Design: declared inputs
Stage.computed_defaults_inputsis a class attribute listing the dotted paths below the stage that its defaults are computed from.MassFunctiondeclares("linear_power.transfer.cosmology",). When the routing table is first built, the router checks that each entry is a field of the tree.computed_defaultswithout declaring any inputs raises aTypeErrorwhen its routing table is first built. Without the declaration,evolvewould never recompute that stage's defaults, which is exactly this bug. No extension stages exist yet, so enforcing it now breaks nothing;MassFunctionis the only stage insrcthat defines the hook.evolvere-runs the hook only when a change overlaps a declared input. Replacing a stage that holds the input counts too, e.g.evolve(**{"linear_power.transfer": T}). The hook is called with the changes from the call and the new input values.sigma_8andn_s, to check whether they were given. A tracker would count those lookups as inputs, soevolve(sigma_8=0.8)would re-run the hook and reset ann_sthe user had set earlier.undeclared_inputsalso runs the hook with no parameters and reports any lookup that is neither a declared input nor one of the hook's own outputs. It reports"*"if the hook iterates its parameters.GivenParametersrecords its lookups for this purpose.Tests
tests/core/test_core_computed_defaults.py:build: for Planck13, Planck15, WMAP7 and WMAP9,build().evolve(cosmology=X)==build(cosmology=X)==from_flat(...), with equal stage trees and bit-identicaldndm. σ8 and n_s equalX.meta. This holds from any starting cosmology, and evolving back to Planck18 recovers the default.build, in an earlierevolve, or after another cosmology. Values passed in the same call are kept.n_sset earlier survivesevolve(sigma_8=...), and the transfer stage is shared.evolve(fit=...)shares the linear power.meta-less cosmology raises, naming n_s or σ8 and the cosmology, unless both are passed.deepcopyorto_config/from_configround trip,evolve(cosmology=...)andevolve(sigma_8=...)give the same results as on the original.MassFunctionrecomputes its defaults. A mass function given whole is kept as is.undeclared_inputson a hook with an undeclared input, and on one that iterates its parameters;TypeError;computed_defaultswith no declared inputs raisesTypeErrorfrom bothfrom_flatandevolve.test_core_routing.py:test_evolve_every_parameterandtest_cosmology_change_is_consistentnow compare againstfrom_flatof the stage's parameters minus σ8 and n_s.Holderstage now declaresbaseas its input and computes its default frombase's value. As a result,evolve(base=4)equalsfrom_flat({"base": 4}).Gate numbers (local)
benchmarks/test_gates.py: 25 passed. Median time per call on a warm EH tree:evolve(...)sigma_8=0.85fit="ST"cosmology=Planck15The routed-evolve gate (≤ 250 µs) and the σ8/z/fit no-recompute gates pass. Only a cosmology change calls the hook.
Checks run
tests/core+tests/regression: 2342 passed, 11 skipped.routing.py,stage.pyandmass_function.pyhave 100% coverage.benchmarks/test_gates.py: 25 passed.uv run mypy(strict): clean.prekon the changed files: clean.-W: clean.The PR touches
routing.py,stage.py,mass_function.py,docs/core.rstand tests. It no longer touchesconfig.pyor pickling.Type of change
Checklist
I have
🤖 Generated with Claude Code
https://claude.ai/code/session_019U8u44xFH87Am2mNHVrimP