feat: symbolic superposition — learn the shift law that collapses a curve family - #25
Merged
Conversation
…urve family Adds `jaxsr.superposition`, which discovers a *symbolic* transform collapsing a family of curves indexed by a condition (time-temperature superposition and its relatives), instead of returning a table of per-curve shift factors that cannot extrapolate to a condition never measured. Uses the differential strategy: fit one smoothed surface per channel, use y_q = σ·s'(q)·y_x + v'(q) to eliminate the unknown master curve, sparse-regress s' and v' against structured Θ(q) ⊙ y_x | Θ(q) blocks, then integrate back analytically with the anchor s(c_ref) = 0. Candidate families are arrhenius, wlf (denominator constant fitted by profile likelihood) and polynomial, each carrying an exact antiderivative so the recovered law stays symbolic. Two findings shape the API rather than being footnotes: The transform is identifiable; the equation is not. Over a realistic condition window 1/(1+q)^2, 1/(c2+q)^2 and low-order polynomials span nearly the same function space, so `shift_expression_` is documented as *not* the headline. The transform and `effective_activation_energy()` are, being stable across structural variants. Expression stability is anti-correlated with validity. A material with no valid shift factor yields a confident, stable, reproducible law and a fine in-sample collapse. So `validity_report_` grades supported / weakly_supported / not_supported from leave-one-condition-out collapse against a measured noise floor, and a test reproduces the separation (1.0x for a genuine superposition, 7.9x for a thermorheologically complex control). Rows are weighted by the precision of the estimated target (built on the `sample_weight` support from #19), since derivative standard errors blow up at the edges of the condition range. On a synthetic Arrhenius benchmark at 3% noise this halves the shift error (0.023 -> 0.013 decades) and tightens E_eff from 52.9 ± 1.8 to 54.1 ± 1.2 kJ/mol against a true 55.9. Conventions that fail silently are checked rather than assumed: the domain sign convention (which names the transform rather than changing it, so getting it wrong flips every shift factor while the plots still look perfect), kelvin (non-positive rejected, Celsius-looking warned), and a dimensionless condition coordinate so design columns stay order-one. The master-curve smoother is fitted separately from the derivative surface, so the discovery does not certify itself. Closes #14. 78 new tests; full suite 886 passed, and green on the NumPy backend. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NYK13hwKpUryx5hQw3A3Fy
An audit of the six features merged today found three gaps. This closes them. #17 (grouped/pipeline resampling) and #18 (structured blocks) had no CHANGELOG entry at all, so `add_block`, `without_blocks`, `blocks`, `filter_by_block`, the `groups=`/`resample_fn=` parameters, `summarize_selection_replicates` and `cross_validate(groups=, strategy=)` were invisible in the release notes. Both now have entries. Both features also had no published docs home -- they were covered only under .claude/skills/, because docs/guides/ has no basis-library or uncertainty guide to extend. Rather than copy ~700 lines that would immediately drift from the skill originals, this adds two focused guides matching how surface-derivatives.md and sample-weights.md were done: - docs/guides/structured-blocks.md -- the Theta(a) * column pattern, what add_block handles for you, and the without_blocks diagnostic for whether a block earned its place. - docs/guides/resampling.md -- choosing the resampling unit, grouped rows, pipeline-level resampling, and reporting your own replicates. Every code block in both guides was executed. One was wrong on first run: cross_validate with 4 groups and the default cv=5 raises, so the example now passes cv=4 and the guide documents the constraint and the leave-one-group-out alternative. Adds docs/examples/superposition_master_curves.ipynb, the first example notebook for any of today's features. It walks synthetic rheology end to end and demonstrates all three findings on real output rather than asserting them: - the transform is identifiable but the equation is not -- generated from pure Arrhenius with the Arrhenius basis available, the search picks WLF at c2=1.07 (the same function), recovering the transform to 0.016 decades and E_eff to 54.2 vs 55.9 kJ/mol; - a thermorheologically complex control still reads as a master curve on the plot while sitting at 6x the noise floor in-sample and 7.9x held out; - that control comes back *perfectly* stable -- 1 distinct structure, score 1.00 -- against 3 structures and 0.75 for the genuine case. The notebook's narrative was corrected after reading its own executed figure: an earlier draft claimed both collapses look identical, which the plot does not support. It now makes the defensible point instead -- the eye cannot supply the noise floor, so 0.18 reads as ordinary scatter on an axis spanning seven decades. Also fixes two pre-existing broken links in docs/quickstart.md (stray ../ prefixes); all docs cross-references now resolve. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NYK13hwKpUryx5hQw3A3Fy
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.
Closes #14.
Adds
jaxsr.superposition, which discovers a symbolic transform collapsing a family of curves indexed by a condition (time–temperature superposition and its relatives), instead of returning a table of per-curve shift factors that cannot extrapolate to a condition never measured.Built on all five supporting issues, now in
main: #15 (SurfaceDerivatives), #16 (basis-identity keying), #17 (pipeline-level resampling), #18 (add_block), #19 (sample_weight).Mechanism
The differential strategy from the issue. Fit one smoothed surface per channel, use
to eliminate the unknown master curve entirely, sparse-regress
s'andv'against structuredΘ(q) ⊙ y_x | Θ(q)blocks, then integrate back analytically with the anchors(c_ref) = 0.Candidate families are
arrhenius,wlf(denominator constant fitted by profile likelihood) andpolynomial. Each term carries an exact antiderivative, so the recovered law is symbolic rather than a quadrature table — and a test numerically integrates every derivative term and checks it against its antiderivative.The two findings are load-bearing, not documentation
The transform is identifiable; the equation is not.
shift_expression_'s own docstring says it is not the headline result. The reported quantities areshift_factors()andeffective_activation_energy(), which are stable across structural variants.The quick-start output in the guide is a real run, deliberately kept because it is unflattering: on data generated from a pure Arrhenius law, with the Arrhenius basis available, the search picked the WLF term with
c2 = 1.070instead. Arrhenius is WLF atc2 = 1, so they are the same function and no information criterion can separate them. The transform is right to 0.02 decades; the named family is wrong.Expression stability is anti-correlated with validity. So the verdict comes from leave-one-condition-out collapse against a noise floor measured from the raw data before any surface is fitted, graded
supported/weakly_supported/not_supported. A test pair reproduces the separation:A companion test asserts the control's in-sample collapse still looks respectable — that is the whole reason the module exists.
Each withheld condition also records the shift that would have aligned it best, so a bad collapse can be attributed either to the law extrapolating wrongly or to the curve not collapsing under any shift at all.
Using #19
The issue filed
sample_weightas a dependency, so the module uses it. Rows are weighted by1/σ²of the estimated target, since derivative standard errors blow up at the edges of the condition range — exactly where a shift law is most tempted to bend. Measured over six noise realizations at 3% noise:weightingE_eff(true 55.9)"none""derivative_se"(default)That lands on the 54.2 ± 1.3 reported in the issue's Phase 0 study.
Conventions checked rather than assumed
E_effwhile the plots still look perfect. No guessing from data; there is a test asserting exactly this.q, so design columns stay order-one instead of mixingT⁻²against polynomials.Scope note
Strategy 2 (direct collapse) is not implemented — the issue ranked it as worth doing only after strategy 1 establishes baselines. Its known bias is documented rather than hidden: over-smoothing in the condition direction flattens
s'and biasesE_effa few percent low, which is visible in the table above.Verification
scripts/test_under_numpy.py, 28 s) — no new JAX API surface needed shim additionsblack --checkandruff checkcleansrc/jaxsr/skill/re-synced from.claude/skills/jaxsr/Also adds
docs/guides/superposition.md, the matching skill guide with a SKILL.md routing entry, an API reference page, and CHANGELOG/README entries.Generated by Claude Code