Run every route to a target, not the first one the registry reaches - #163
Merged
Merged
Conversation
Contributor
|
📚 Docs preview: https://qatlashub.github.io/AbstractQAtlas.jl/previews/PR163/ (updates on each push to this PR) |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
sotashimozono
force-pushed
the
feat/derivative-routes
branch
from
September 15, 2026 12:46
3ef76b3 to
df87940
Compare
sotashimozono
force-pushed
the
feat/all-routes
branch
from
September 15, 2026 12:46
8d0346a to
6bacead
Compare
`derive` forward-chains and takes the first relation that produces the target. Measured on the current registry: 66 of the 219 symbol-keyed outputs have more than one producing relation, and 38 of the 84 typed ones, up to nineteen for a single symbol. Which one runs is iteration order, and if two disagree the caller is handed a number and told nothing. `derivation_routes` returns them all. `derive_crosschecked` refuses when they disagree. The hazard in building this is not the disagreement, it is the vacuous agreement, and it has two levels. Both are guarded and both are pinned by a mutation. 1. A SUPPLIED target lets the closure manufacture its own inputs from it. With only `c` and `ncuts` supplied, the closure builds `dS_dlogℓ`, `dS_dlogchord` and `ΔS` from `c`, and three routes return the supplied number and agree. The target is therefore held out of the data before the closure runs, as a given (`pop!` / `_holdout!`) and, since β and T are one quantity under two names, so is its alias. 2. A DERIVED target does the same thing one level down. Given a real `dS_dlogℓ`, the chain derives `c`, then rebuilds the chord slope from it, then reads `c` back off that: three rows, one measurement, perfect agreement. So the chains also take `avoid`, and never produce the target at all. | `derivation_routes(:c; dS_dlogℓ, ncuts)` | rows | values | |---|---|---| | as written | 1 | 0.5 | | without `avoid` | 3 | 0.5, 0.5, 0.5 | `min_routes` is how to ask that a cross-check happened. The default accepts data affording no independent route, which returns a number this verb's name would otherwise claim it had checked. What it catches, on the typed door, with `F` reachable both through `Z` and through the Legendre transform and the entropy broken by 0.5: | | result | |---|---| | `derive(FreeEnergy, bad)` | -1.3733, the RIGHT number off the other route | | `derive_crosschecked(FreeEnergy, bad)` | refused, spread 0.3128, both routes named | Mutation, against test/relations/test_derivation_routes.jl (27 assertions): | mutation | assertions failed | |---|---| | drop the symbol-door holdout | 2 | | drop the typed-door holdout | 2 | | drop the β/T alias holdout | 1 | | symbol chain ignores `avoid` | 3 | | never refuse a disagreement | 4 | | `min_routes` never fires | 1 | The two deleted lines in `src/` are the signatures of `_forward_chain` and `_typed_chain!`, both private, widened with a defaulted keyword. No public API is removed or changed, so 0.7.16 -> 0.7.17. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Six review agents over the stack. Four findings reproduced against the committed
diff, and everything below is fixed with a mutation or a measured pair behind it.
WRONG NUMBERS, SILENTLY
1. `declared_convention` walked `supertype`, which skips a parametric type's own
family: `supertype(Energy{:per_site})` is `AbstractThermalPotential`, so a
project keying its declaration on `Energy` got the value back unconverted.
`Energy{:per_site}` is a live bag key (`FreeEnergyLegendre` takes it), and a
`Union` key failed the same way. Matched by `<:` now, most specific first,
with two unrelated covers refused rather than resolved by `Dict` order.
2. `_value_spread` divided by `max(m, 1)`, which is absolute below one:
`[1e-12, -1e-12]`, a sign flip, read as agreement at any rtol. Now
`d <= atol + rtol*m`, `isapprox`'s rule, with `atol` exposed.
3. A route that RAISED vanished from `derivation_routes`. `PartitionFunction`
of -2.0 is impossible, `FreeEnergyFromZ` needs `log(Z)` and throws, the route
disappeared, and `derive_crosschecked` returned a clean cross-checked answer
off the one remaining route. A raise is now a row carrying its message, and
the crosscheck refuses before comparing anything. An `ErrorException` is this
package declining to be solved for a slot and still skips; a `DomainError`
does not.
4. `NaN` was the "fewer than two values" sentinel AND what a degenerate route
produces, so `isnan` short-circuited both into agreement. `_disagreement`
returns `nothing` for too-few, and a NaN route is refused by name.
5. `min_routes` defaulted to 0, so the most natural call, "I measured f, is it
consistent", degraded to `identity` whenever the data afforded no independent
route. Defaults to 1; `min_routes = 0` is the opt-out.
6. `derivative_report` caught everything. An off-diagonal susceptibility and a
potential evaluated outside its domain became the same `NaN` row as an
unloaded backend. Only `MissingRouteBackend` is absorbed now. Worse:
`_route_order` fell back to 1 for a quantity with no genealogy edge, so
`derivative_report(PartitionFunction(), ...)` reported order 1.99995 beside a
refused value, which reads as "the numerics are fine". Refused up front.
A BREAKAGE THE TESTS COULD NOT SEE
7. Making `AutoDiff` a real route put `nth_derivative(::AutoDiff, ...)` in both
the package and the extension. Identical signature, so the extension failed
to precompile while every test stayed green on the fallback load path. The
package owns no method for that signature now; `backend_package(route)` says
which extension is missing, and a test asserts the package owns none.
TYPE DESIGN
8. `_step`/`_with_step` dispatched on `Union{CentralDifference,Richardson}`, so a
future step-carrying route fell through to a generic `observed_order` that
asserted something false about it. Replaced by the `step_size` /
`with_step_size` contract, which a new route declares.
9. The `Susceptibility` off-diagonal guard was pasted in both the extension and
the route path, and the two copies had already drifted to different wording.
One `_susceptibility_derivative` now; breaking it fails both paths.
FALSE COMMENTS
10. `observed_order`'s docstring said a value near 0 means `f` is not smooth. The
PR's own kinked control returns exactly 1. Restated, and the `Inf` branch is
documented.
11. The kinked fixture's comment said the first derivative is discontinuous.
`f'(0-) = f'(0+) = 0`; it is the second that jumps 2 to 4.
TESTS
Counts: conventions 47 -> 49, derivative routes 27 -> 49, derivation routes
33 -> 49. New coverage for what the agents showed was untested: `Richardson`'s
`levels` for 2..5 (error falls monotonically, ratio > 1e3), `observed_order` on
`Richardson`, `DerivativeRouteRow.route`, array-valued conversion, the four
`conventions()` diagnoses pinned by distinguishing words rather than by type,
and a probe quantity rooted outside the potentials so the root guard can fire at
all. The `!(o > 1.9)` roundoff assertion became a band: within one decade of that
step the quotient also returns `Inf`, for which the old spelling was false.
The measured registry counts in the section comment are pinned as floors.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`_route_declined(e) = e isa ErrorException` classified every `ErrorException` as the framework declining to apply a relation. This package raises them for two unrelated reasons: | raised by | example | should be | |---|---|---| | the framework declining | `solve: ... is not affine in :β`, `MottFormula needs the supplied value :dlnσ_dε (untyped slot)` | skipped | | a relation's own physics guard | `CFTEntanglementSlope: ncuts = 0 leaves the residual independent of the central charge` | reported | The second is a statement about the DATA, and the type test dropped it. With `ncuts = 0` the caller still got a refusal, because `min_routes` is 1, but the diagnosis had become "reached by 0 independent routes" and the real reason was gone. The framework declines at exactly six sites in relations/interface.jl, in two message shapes, and `_route_declined` now matches those. Not by exception type: a typed declination is the right fix but changing it lands on 29 `@test_throws ErrorException` calls over solve/residual/check, which is not this PR. False positives measured by sweeping all 219 targets with a deliberately nonsensical value for every input: 382 rows, 17 broken, and every one of the 17 a real data problem (`ℓ = L`, `ℓ = 0`, a `DomainError`, a non-positive `L` or `f`). No declination leaked into the reported set. That sweep is now a test, so a seventh declination site added and not registered here fails. Mutation, against test/relations/test_derivation_routes.jl (49 -> 55): | mutation | assertions failed | |---|---| | classify everything as declining (the bug above) | 2 | | forget the untyped-slot shape | 2 | | forget the `solve:` shape | 2 | Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Six agents over `010e95f`, whose own 200 lines had not been reviewed. Four found real defects. Everything below has a mutation or a measured sweep behind it. UNREACHABLE FOR ORDINARY DATA `EntanglementSpectrumCorrelation` is `ε - log((1-ζ)/ζ)`. `solve` probes its target at 0, 1, 2, and at `ζ = 2` the argument of `log` is -0.5, so it raised for EVERY ε: `derive` called ζ unreachable and `derive_crosschecked` refused, with no `min_routes` escape. Its docstring had carried `ζ = 1/(e^ε + 1)` all along; the solver just never got it. Same shape for `:ncuts`, which the slope relations guard at exactly the first probe. Both now have the closed form, and the guard reads the ANSWER instead of the probe. CLASSIFICATION `_route_declined` matched `solve:` as framework vocabulary, and `scaling.jl`'s `PseudocriticalWidthScaling` guard had borrowed that prefix while being a statement about the DATA, so it vanished from the reported set. Renamed to the `<Relation>: ` form every other guard in the package uses, and `solve:` is now reserved vocabulary with a test that sweeps the registry for a leak. The refusal message also asserted "raised on this data", which is false when the cause is the relation's domain at the point `solve` probed. Reworded to name both possibilities, since the row cannot tell them apart. Measured over all 219 targets with every input at 0.7, 1.3 and 2.5: 45 refusals for disagreement (the fixture is inconsistent, so those are correct) and 5 for a broken route, all five `ℓ = L`, a real domain violation. No prober artifact left. ORDER DEPENDENCE `declared_convention` folded pairwise against a running best, so two unrelated covers reconciled by a third reported a false ambiguity in 2 of the 6 iteration orders. Now collects every cover and takes the unique minimum; all 6 orders agree, and a genuine ambiguity is still refused. ASYMMETRIC CATCHES `derivative_report`'s order column caught `ErrorException` where its value column caught only `MissingRouteBackend`, so a route reporting a `step_size` with no `with_step_size` produced the same NaN as a legitimately step-less one. The step is now checked before the call and both catches are narrow. `nth_derivative`'s fallback told a route author to install a package that is already loaded. It checks, and says "loaded, but no method matched" instead. UNREPRESENTABLE STATES `MissingRouteBackend` could be built for a route with no `backend_package`, rendering "needs the nothing extension". `DerivationRouteRow` could hold both a value and an error, or neither, the latter crashing `_disagreement` with a `MethodError`. Inner constructors on both. SIMPLIFICATION The two doors' catch bodies were byte-identical: one `_route_row!`, mirroring `_finite_size_scaling_row!`. One `_row_ok` predicate for what was four inline lambdas. `SpecificHeat` and `Energy`'s route methods merged, which the removal of their `route isa AutoDiff` short-circuits made possible. FALSE COMMENTS, MINE "A route that raised is a row rather than an absence" is not true of a declining route. `step_size`'s docstring named the "defines neither" case and described the other one. `_disagreement` returns `nothing`, not `(NaN, NaN)`. `derivative_report`'s comment justified itself against a `_route_order` fallback the same commit had removed. TESTS conventions 49 -> 59, derivative routes 49 -> 60, derivation routes 49 -> 69. Two mutations survived the last round and now fail: the ambiguity refusal (its fixture paired the query with a type it is not a subtype of, so the branch was unreachable) and `derivative_report`'s edge guard (pinned by type, and `_route_order`'s own guard fired one line later with a different message). Also newly pinned: the `THREW` display, the row invariant, `MissingRouteBackend`'s type and message, and `Richardson`'s `levels` by strict decrease rather than `issorted`, which counts the ties an ignored `levels` produces as sorted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The machinery had one-line fragments in docstrings and fixtures in tests, and
nothing that goes from a measurement to a conclusion. `examples/` held only a
`.keep`.
`examples/critical_entanglement.jl` reads a central charge off entanglement it
computes itself. A critical free-fermion chain's block correlation matrix is a
closed form, so nothing is pasted in from an external run and no non-universal
constant is imported: the slope through two block sizes carries none, which is
also why two sizes and not one (each logarithmic form has a constant that can
absorb any `c`).
| the same measurement, entered three ways | `c`, exact value 1 |
|---|---|
| nats, as the calculation produced them | 1.0001 |
| the same numbers in bits, undeclared | 1.4429 |
| the same numbers in bits, declared `Bits` | 1.0001 |
1.4429 is `1/ln 2`, so the failure the convention layer exists for is legible
without being explained. The 1e-4 gap from `c = 1` is the finite-block
correction, not a tuned fixture.
`test/core/test_examples.jl` INCLUDES the script rather than restating it, so
the two cannot drift, and asserts against the values the physics fixes (`1` and
`1/ln 2`) rather than the digits the script prints. It checks that the
undeclared answer lands on `1/ln 2` specifically, not merely that it misses 1: a
wrong answer that missed by something else would otherwise pass for the same
reason. Mutation: making the conversion a no-op, or making `declared_convention`
find no cover, each fails 2 assertions.
The script is a module. Every test file is included into one namespace, and
`block_entropy` plus two block sizes at top level would be in all of them.
That is not hypothetical: `ParametricProbeQuantity{I}`, added to
`test/core/test_conventions.jl` last round, leaks into `subtypes(AbstractQuantity)`
and fails `test/core/test_invariants.jl`'s "every concrete quantity is
instantiable" sweep, but ONLY when the two files share a process, which no
single-file run shows. Fixed the way the package fixes it for `Energy{G}`, with a
default parameter.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`_route_declined` classifies by message prefix, so a relation guard that borrows `solve:` is silently SKIPPED rather than reported. The registry sweep cannot see that direction: a skipped route leaves no row to inspect, so it never enters the set the sweep examines. One guard in `scaling.jl` had borrowed the prefix, and a reader caught it, not the test. Read off the source, since the claim is about what future authors write: no `"solve: ` literal outside `relations/interface.jl`, with a positive control that the vocabulary really is used in the file that owns it, so the assertion cannot pass by looking in the wrong place. Mutation: putting the prefix back on `PseudocriticalWidthScaling`'s guard fails it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
sotashimozono
force-pushed
the
feat/all-routes
branch
from
September 15, 2026 12:57
6bacead to
ec35394
Compare
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.
Stacked on #162, which is stacked on #161. Merge order: #161, #162, then this.
deriveforward-chains and takes the first relation that produces the target. Measured on the current registry::β)InverseTemperature)Which one runs is iteration order. If two disagree the caller is handed a number and told nothing.
derivation_routesreturns them all;derive_crosscheckedrefuses when they disagree.The hazard is the vacuous agreement, and it has two levels
Both are guarded, and each guard is pinned by a mutation that removes it.
1. A supplied target lets the closure manufacture its own inputs. With only
candncutssupplied, the closure buildsdS_dlogℓ,dS_dlogchordandΔSfromc, and three routes hand the supplied number back:So the target is held out before the closure runs, as a given, and (β and T being one quantity under two names) so is its alias.
2. A derived target does the same thing one level down. Given a real
dS_dlogℓ, the chain derivesc, rebuilds the chord slope from it, and readscback off that:derivation_routes(:c; dS_dlogℓ, ncuts)avoidThree rows, one measurement, perfect agreement. The chains therefore take
avoidand never produce the target at all.min_routesis how to ask that a cross-check actually happened. The default accepts data affording no independent route, which returns a number this verb's name would otherwise claim it had checked.What it catches
Typed door,
Freachable both throughZand through the Legendre transform, entropy broken by 0.5:derive(FreeEnergy, bad)derive_crosschecked(FreeEnergy, bad)derivereturning the correct value from good data while silently ignoring an input that contradicts it is the case this exists for.Mutation
Against
test/relations/test_derivation_routes.jl(27 assertions):avoidmin_routesnever firesThe first pass of this test file let two of these through. The typed assertions passed for reasons unrelated to what they claimed, and were rewritten against fixtures that can fail:
FreeEnergyLegendreproduces bothFandS, andKelvinRelationproducesTfrom a Peltier coefficient that β can build.The two deleted lines in
src/are the signatures of_forward_chainand_typed_chain!, both private, widened with a defaulted keyword. No public API removed or changed, so0.7.16 -> 0.7.17.🤖 Generated with Claude Code