Skip to content

Give the response genealogy more than one way to be evaluated - #162

Merged
sotashimozono merged 1 commit into
mainfrom
feat/derivative-routes
Sep 15, 2026
Merged

sotashimozono merged 1 commit into
mainfrom
feat/derivative-routes

Conversation

@sotashimozono

Copy link
Copy Markdown
Member

Stacked on #161 (base feat/conventions), which it needs only for the version line.

structure/response.jl declares WHICH derivative a response function is. There was exactly one way to evaluate it, nested forward-mode AD in a package extension, so a project holding a measured potential rather than a differentiable one could not traverse the declared genealogy at all.

Finite differences were already here three times, written inline and unnamed:

relations/region_entropy.jl:598 (S2 - S1) / (log(ℓ2) - log(ℓ1))
relations/finite_size.jl:44 (log(O2) - log(O1)) / (log(L2) - log(L1))
relations/finite_size.jl:51 (log(-log(O2)) - log(-log(O1))) / (log(L2) - log(L1))

Those difference two MEASURED points and stay where they are, but they are why a route is a declared thing now.

No duplicated genealogy

The AD path had already factored the genealogy out, so the route is the only thing that differs:

return -_nth(F, x, derivative_order(q, e.field()))

_genealogy_derivative now holds that once and both paths call it. The extension supplies its nth and nothing else; its 44 tests are unchanged and green.

Routes: AutoDiff (the extension), CentralDifference(h), Richardson(h; levels). A new one is a subtype plus an nth_derivative method.

F(h) = -log(2cosh(h))
thermal_derivative(Magnetization(:z), F, 0.3, Richardson(1e-2))   # no backend needed

The trust number is on the normal return path

Measured on F(h) = -log(2cosh h) at h = 0.3, where M = -F'(h) = tanh(0.3):

route error observed_order
AutoDiff() 0 n/a, no step
Richardson(1e-2) 6.3e-14 n/a, at machine precision
CentralDifference(1e-2) 8.9e-6 2.00
CentralDifference(1e-9) 2.7e-8 0.32

The last row is the point. Its error is smaller than the row above it and its step is on the roundoff side, and only the order says so. A threshold would have passed it.

The control the diagnostic needs is a function it must fail on: on a kinked potential a central difference is first-order exactly, and observed_order reports 1.

derivative_report runs several routes and reports each value with its order, so a route that cannot run (missing backend) is a NaN row rather than an aborted sweep.

Mutation

Against test/core/test_derivative_routes.jl (26 assertions, runs with no backend):

mutation assertions failed
drop the genealogy's minus sign 5
_route_order always 1 2
central difference to forward difference 8

test/ext/test_derivative_routes_ad.jl (21 assertions) pins AD against Richardson across seven genealogy edges, comparing the sign before the magnitude.

Additive (git diff -- src/ | grep -E "^-[^-]" is empty), so 0.7.15 -> 0.7.16.

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

📚 Docs preview: https://qatlashub.github.io/AbstractQAtlas.jl/previews/PR162/

(updates on each push to this PR)

@codecov

codecov Bot commented Sep 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.80519% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/derivative_routes.jl 94.73% 4 Missing ⚠️

📢 Thoughts on this report? Let us know!

structure/response.jl declares WHICH derivative a response function is. Until
now there was exactly one way to evaluate it, nested forward-mode AD in a
package extension, so a project holding a measured potential rather than a
differentiable one could not traverse the declared genealogy at all.

Finite differences were already here three times, written inline and unnamed:
the entropy secant in relations/region_entropy.jl:598 and the two log-log
secants in relations/finite_size.jl:44,51. Those difference two MEASURED points
and stay where they are, but they are why a route is a declared thing now.

The genealogy work was already factored out of the AD path, so the route is the
only thing that differs. `_genealogy_derivative` now holds it once and both
paths call it; the extension supplies its `nth` and nothing else, and its 44
tests are unchanged and green.

Routes: AutoDiff (the extension), CentralDifference(h), Richardson(h; levels).
A new one is a subtype plus an `nth_derivative` method.

Measured on `F(h) = -log(2cosh h)` at `h = 0.3`, `M = -F'(h) = tanh(0.3)`:

| route | error | observed_order |
|---|---|---|
| AutoDiff | 0 | n/a, no step |
| Richardson(1e-2) | 6.3e-14 | n/a, at machine precision |
| CentralDifference(1e-2) | 8.9e-6 | 2.00 |
| CentralDifference(1e-9) | 2.7e-8 | 0.32 |

The last row is why `observed_order` is on the normal return path. Its error is
smaller than the row above it and its step is on the roundoff side, and only the
order says so. The control is a kinked function, where a central difference is
first-order exactly and the diagnostic reports 1.

`derivative_report` runs several routes and reports each value with its order,
so a route that cannot run (no backend) is a NaN row rather than an aborted
sweep.

Mutation, against test/core/test_derivative_routes.jl:

| mutation | assertions failed |
|---|---|
| drop the genealogy's minus sign | 5 |
| `_route_order` always 1 | 2 |
| central difference to forward difference | 8 |

Additive (`git diff -- src/ | grep -E "^-[^-]"` is empty), so 0.7.15 -> 0.7.16.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sotashimozono
sotashimozono merged commit 4de106b into main Sep 15, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant