docs: four comments that misdescribe the code beside them (3.2.3, 3.2.4, 3.2.5, 3.5.3) - #255
Merged
Merged
Conversation
The class docstring said build_pymc "refuses a reference to any non-sampled element", the inline comment said the pre-patch tensor "is only final on SAMPLED elements", the error text said the offending elements "are not SAMPLED", and component.py's _resolve_dep_node comment said the same. The guard is `_non_sampled = inactive | (every expression mask)`, i.e. it refuses DERIVED, REPORTED and INACTIVE elements only. A `sigma: 0` pin passes, and correctly so: a fixed element takes the constant-0 raw coordinate and the linear branch, whose value is exactly `gaussian_mus[i] == inits[i]` -- its pin -- and the pin rule already guarantees that number exists. All four sites now say "sampled or fixed (final); derived, reported and inactive slots are placeholders or bookkeeping pins", and the docstring says to read `_non_sampled` as "not yet final" rather than as "not sampled" so the variable name cannot re-seed the same misreading. The docstring also records the one element the guard does NOT cover, found while verifying the claim: a HARD-LINKED element is `sigma: 0` too, but section 5b writes its value after 5a's patch, so its pre-patch slot holds its resolved initval rather than the link's value. Nothing combines a hard link with a same-parameter element dep today. Text only -- no expression touched, no behavior change. No test asserted the old error string. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The comment still narrated run.py's pre-#215 inline formula ("run.py resolves `cores` as max(1, min(int(n_phys * CORE_FRACTION), n_phys - 1))"). That formula now has exactly one spelling, `samplers/_common.default_cores()` (_common.py:296-315), and run.py:379 just calls it: cores = int(_cores_raw) if _cores_raw is not None else default_cores() The block's own rationale for naming the constant here -- "cannot find the other place that has to move with it" -- only stays true if it names the right other place, so the comment now points at default_cores() and records that #215 collapsed three drifting copies into it (run.py's inline formula, create_pool's and nested.py's hardcoded 0.75, the last having dropped the `n_phys - 1` arm entirely). Deliberately says nothing about what a user's `cores:` VALUE means downstream: review 2.4.8 has cores=0 meaning three different things across the three resolvers, so the comment claims only that a `sampler: cores:` key is passed through by run.py and bypasses this constant, and that `cores=None` means AUTO -- which is default_cores' own documented contract. Comment only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ckwards (review 3.2.5)
The message read "Conversion failure from '{u_str}' to '{i_str}'" -- user to
internal -- while the operation it reports is internal -> user.
HOW THE DIRECTION WAS CONFIRMED, three independent ways, because CLAUDE.md
devotes an invariant to these two reciprocal factors and getting the message
backwards a second time would be worse than leaving it:
1. The operative call is `self.internal_unit.to(target_u)`, where
target_u is the USER unit. astropy's `Unit.to(other)` returns the
multiplier taking a value expressed in `self` to one expressed in
`other`, so the factor is internal -> user. Measured:
Unit('solMass').to(Unit('jupiterMass')) == 1047.5655146604772, which is
1 Msun expressed in Mjup.
2. `from_internal` (INTERNAL -> USER, per its own docstring) MULTIPLIES by
this factor; `to_internal` DIVIDES. Only an internal -> user factor
makes both correct.
3. The function's own docstring ("from internal -> user units"), the
DIRECTION block above element_factor, and the CLAUDE.md invariant all
already said internal -> user. The message was the lone dissenter.
"Ensure units are valid astropy strings" was also the wrong advice for the
case that actually reaches this branch: both units can be perfectly valid and
simply not convertible. Measured: Unit('dex') and Unit('solMass') are both
valid and `.to()` between them raises UnitConversionError -- and the dex
short-circuit above only fires when BOTH sides are dex, so a dex internal
unit against a linear user one lands here. The advice now names both
possibilities.
A comment at the raise records which direction the message states and why, so
the next reader does not have to re-derive it.
String and comment only; the arithmetic is untouched. No test asserted the
old message (nothing in src, tests or docs matches "Conversion failure").
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
_build_dilution's docstring said "``build_likelihood`` clears it (see
``_reset_build_caches``)". There is no `_reset_build_caches` anywhere in the
tree -- that string occurred exactly once, in this comment -- so a reader
looking for it either hunts for nothing or "restores" a method that never
existed.
The two mechanisms that do exist:
* the inline `self._dilution_node = None` at the top of `build_likelihood`
(transit.py:549), which is what actually clears this cache. Transit does
NOT declare `per_build_caches`; only SED does (sed.py:733).
* the general seam, `Component.per_build_caches` / `reset_build_caches`
(component.py:319-324), which `System.build_model` runs before stage 5
(system.py:782).
The comment now names the inline reset and points at the general contract,
including why this cache does not use it: nothing reads the dilution node
before stage 7, which is the distinction components.md already draws against
`SED._m_pred_matrix` (built at stage 6, so a stage-7 reset would come too
late). `tests/test_rebuild_caches.py::test_build_likelihood_drops_a_stale_dilution_node`
pins the behavior either way.
Docstring only.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Batch 3C of the review sweep: four places where a comment, docstring or error
message misdescribes the code beside it. In all four the code is right and only
the text changed. No expression was touched.
All four premises held on the current tree. Every anchor had drifted and every one
was re-verified against HEAD before editing.
3.2.3 -- OwnPrePatchRef docs overstated the guard
What the text claimed. The class docstring (
parameter.py:570-586): build_pymc"refuses a reference to any non-sampled element". The inline comment: the pre-patch
tensor "is only final on SAMPLED elements". The error text: the referenced elements
"are not SAMPLED".
What the code actually does. The guard is
i.e.
inactive | derived | reported. It is not~is_sampled, so asigma: 0pin passes -- and correctly so.
How I confirmed it. Read the guard's construction, then traced what a fixed
element's pre-patch slot actually holds:
is_fixed = ((sigmas == 0) | is_inactive) & ~is_derived;:1760opens withif not is_sampled[i]: continue, so afixed element keeps
use_logit[i] == Falseandgaussian_mus[i] == inits[i](from the
np.copy(inits)initializer);pt.constant(0.0)(:1876), so the linear branch yieldsgaussian_mus[i] + gaussian_scales[i]*0 == inits[i]-- exactly its pin;:1523already guarantees that number exists for an active pin.A fixed element's pre-patch slot is therefore final, and the review's reading is right.
Corrected wording. All four sites now agree -- the fourth being
component.py:784, which made the same claim and is corrected for consistency. Theynow say: the pre-patch tensor is final on sampled and fixed elements; what is
refused is a derived, reported or inactive element, whose slot holds the
transform's placeholder or a bookkeeping pin. The docstring adds that
_non_sampledshould be read as "not yet final", not as "not sampled", so the variable name
cannot re-seed the same misreading.
One residual edge found while verifying, recorded rather than "fixed". A
HARD-LINKED element is
sigma: 0too, but section 5b writes its value (:2155)after 5a's patch (
:2054), so its pre-patch slot holds its resolved initval ratherthan the link's value -- the guard does not cover it. Nothing combines a hard link
with a same-parameter element dep today. Widening the mask would be a behavior
change, so the docstring names it and says to widen the mask rather than trust the
paragraph if something ever does.
No test asserted the old error string (grep over
tests/for "not SAMPLED" /"pre-patch" finds only prose in
test_fitmurel.pyandtest_vcve.py, neither anassertion).
3.2.4 -- constants.py CORE_FRACTION comment went stale in PR #215
What the text claimed. "run.py resolves
coresasmax(1, min(int(n_phys * CORE_FRACTION), n_phys - 1))".
What the code actually does. That formula now has exactly one spelling,
samplers/_common.py:296-315'sdefault_cores(), andrun.py:379just calls it:How I confirmed it.
grep -n default_cores src/exozippy/run.py-- imported at:21, called at:379and:1973-- and readdefault_cores()'s body anddocstring, which records the same three-copy collapse from #215.
Corrected wording. The comment now points at
default_cores()as the onespelling and names the three copies #215 collapsed into it (run.py's inline formula,
create_pool's andnested.py's hardcoded 0.75, the last having dropped then_phys - 1arm entirely). That keeps the constants block's own rationale -- "cannotfind the other place that has to move with it" -- true, which it only is if it names
the right other place.
It deliberately says nothing about what a user's
cores:value means downstream:review 2.4.8 has
cores=0meaning three different things across the threeresolvers, so the comment claims only that a
sampler: cores:key is passed throughby run.py and bypasses this constant, and that
cores=Nonemeans AUTO -- which isdefault_cores' own documented contract.3.2.5 -- _get_conversion_factors' failure message stated the conversion BACKWARDS
Filed PLAUS, and the one item here that touches the reciprocal-factor trap CLAUDE.md
devotes an invariant to. The premise held.
What the text claimed vs. what the code does. The message read
"Conversion failure from '{u_str}' to '{i_str}'"-- user -> internal -- while theoperation it reports is internal -> user.
How I confirmed the direction, three independent ways (getting this backwards a
second time would be worse than leaving it):
return float(self.internal_unit.to(target_u)), wheretarget_uis the USER unit. astropy'sUnit.to(other)returns the multipliertaking a value expressed in
selfto one expressed inother, so the factor isinternal -> user. Measured in this venv:
Unit('solMass').to(Unit('jupiterMass')) == 1047.5655146604772, and(1*solMass).to(jupiterMass) == 1047.5655146604772 jupiterMass-- the factor takesan internal-unit number to a user-unit number.
from_internal(INTERNAL -> USER)multiplies by this factor;
to_internaldivides. Only an internal -> userfactor makes both correct.
dissenter: the function's own docstring ("the numerical conversion factor from
internal -> user units"), the DIRECTION block above
element_factor(:3155-3163),and the CLAUDE.md invariant. That the message disagreed with its own docstring two
lines up is itself the evidence that the message, not the code, was wrong.
The advice was wrong too. "Ensure units are valid astropy strings" misdiagnoses
the case that reaches this branch: both units can be valid and simply not
convertible. Measured:
Unit('dex')andUnit('solMass')are both valid and.to()between them raisesUnitConversionError: 'dex' and 'solMass' (mass) are not convertible. The dex short-circuit above only fires when BOTH sides are dex, so a dexinternal unit against a linear user one lands here.
Corrected wording.
plus a comment at the raise recording which direction the message states and why, so
the next reader does not have to re-derive it. Nothing in
src,testsordocsmatched "Conversion failure", so no test pinned the old string.
3.5.3 -- transit.py cited a nonexistent
_reset_build_cachesWhat the text claimed.
_build_dilution's docstring: "build_likelihoodclears it (see
_reset_build_caches)".What the code actually does.
_reset_build_cachesexists nowhere --grep -rn '_reset_build_caches' src tests docsreturns that one comment and nothingelse. The real mechanisms are two:
self._dilution_node = Noneat the top ofbuild_likelihood(
components/transit/transit.py:549), which is what actually clears this cache;Component.per_build_caches/reset_build_caches(
component.py:319-324), run bySystem.build_modelbefore stage 5(
system.py:782).How I confirmed it. Read both, and confirmed
Transitdoes not declareper_build_caches-- onlySEDdoes (sed.py:733) -- so the inline reset really isthis cache's mechanism.
tests/test_rebuild_caches.py::test_build_likelihood_drops_a_stale_dilution_nodepins the behavior.
Corrected wording. The docstring now names the inline reset and points at the
general contract, including why this cache does not use it: nothing reads the node
before stage 7, which is exactly the distinction
components.mdalready drawsagainst
SED._m_pred_matrix(built at stage 6, so a stage-7 reset would come toolate).
Acceptance: start logp is bit-identical
Ran the full acceptance pair before and after the edits on the same machine:
scripts/make_mulens_fixtures.py --check-- every per-term logp and every fixtureverdict reproduced identically.
EXOZIPPY_ACCEPTANCE_STRICT=1 pytest tests/test_mulens_acceptance.py-- 24passed on the strict 1e-14 tier. Sign tally over 925 terms: 923 bit-identical,
1 above and 1 below, max |rel| 5.4e-16 -- the same two 1-ulp terms with the same
values before and after.
Diffing the numeric and verdict lines of the two runs: 38 lines, byte-identical.
The only textual difference between the two logs is pytensor compile-cache
housekeeping warnings and the pytest timing line.
AST-level confirmation of "no behavior change". Parsed master's and this
branch's version of all four touched files and compared the ASTs with string
constants normalized and docstrings stripped.
component.py,transit.pyandconstants.pyare structurally identical.parameter.pydiffers in exactly twofunctions --
_get_conversion_factors(and_process_single, its nested closure) andbuild_pymc-- and in each, exactly one f-string differs with the f-string countunchanged (1 -> 1 and 28 -> 28). Those two f-strings are the two error messages this
PR intends to change. Nothing else executable moved.
parameter.mdwas checked for a contradiction and needs no amendment: it does notdocument the OwnPrePatchRef guard condition at all, so nothing in it demanded the
stricter refusal, exactly as item 3.2.3 anticipated.
Two pre-existing issues found, NOT from this PR (worth filing)
make_mulens_fixtures.py --checkalready exits 1 on master at 78a1141,identically before and after this change:
ob09020'sPOT:galacticmodel.imf_priordiffers by 1 ulp (2.2e-16 absolute, rel 1.3e-16),OGLE_0383LD'sRV:mulensinstrument.modelby 1 ulp (rel 5.4e-16), andob09020_accept,ob09020_diag,ob09020_polish,ob09020_tunereport MISSINGfixture. Both deltas are far inside the strict 1e-14 tier, which is why
test_mulens_acceptance.pyis green. As it stands--checkcannot be used as aclean pass/fail gate.
Two concurrent suites corrupt each other's pytensor compiledir. A full-suite
run of this branch produced 13 failures and 4 errors, every one
pytensor CompileErrorwith/bin/ld: cannot open output file .../pytensor-pytest/gwN/compiledir_.../tmpXXXX/....so: No such file or directory-- a temp compile directory removed mid-link. Cause:EXOZIPPY_TEST_COMPILEDIRis shared, conftest appends only the worker id (gwN),and the controller prunes LRU and sweeps sibling platform trees on the stated
rationale that "this base_compiledir belongs to the test suite alone" -- an
assumption the design otherwise breaks deliberately, since the cache is shared
across worktrees so parallel agents get a warm one. Re-running those 10 files
against a private compiledir: 213 passed, 0 failed. So the suite is green for
this branch, but the shared-compiledir race makes a concurrent run look like 13
real breakages.
🤖 Generated with Claude Code