Repository navigation
feat!: the pooled fit models sum peaks when the strongest line's self pair is significant - #59
Merged
Merged
Conversation
…aks as explained With the sum/escape model on, the unlisted-line check proposed on the bare spectrum and named the modelled pile-up and escape peaks as elements. SumEscapeResult.componentCounts (the fitted c S(A) + f E(A)) is now carried by the quantification and handed to ElementProposer.propose(explained:), which adds it to every fit as a fixed offset. nil (the default, and the model off) runs the proposer as before. Planted pools (tools/edx-sim proposer_sim, model on): 100 counts/px no longer names Ar, Ag, Pd; 300 counts/px no longer names Rh, Ar, In, Sn, Pd, Ag, Cd, Ru, Bi, K; a planted Fe escape with the free escape columns off no longer names La, Ce, Cr, Cs. A planted K K-alpha between two sum peaks is still named first. Model off: unchanged. Done: sum/escape peaks are explained counts for the proposer and the check. Next: docs rows for parity.md and open-items.md item 8. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… pair is significant The pooled quantification fitted one Gaussian per line and no sum (pile-up) peaks, exSpy's model. On the public synthetic AlMgSi demo (planted truth: the simulation recipe of sim_edx.py v2.0, not a measurement) that fit read noise-free Si K-alpha 15-18 % low on the Al-rich map and fixture pools and 57 % low on the highest-rate pool, held a planted Cu K-alpha at 0 at high rate, and had a reduced chi2 of 2.1-31 on the Poisson counts. The Al+Al sum peak (2.97 keV) and its neighbours pull the continuum segment above the Al K edge, and Si K-alpha with it. FitPolicy.sumPeaks (SumPeakRule, default .whenSignificant) now tests the strongest line's self pair alone with EDSSumEscapeFit; when its pile-up coefficient is 3 sigma above 0 (sigma scaled by sqrt(max(chi2_red, 1)) of that fit) the sum peaks of the four strongest lines are fitted and kept, otherwise the result is the old fit bit for bit. The self pair is the test because the cross pairs absorbed an unlisted Cu K-alpha (Ni K-alpha + O K-alpha) on a synthetic spectrum with no pile-up. The footer names the decision with its number, and names the alpha lines within one FWHM of the self pair (Ar K-alpha, Ag L-alpha under Al+Al): a real line there is read as pile-up. .off (FitPolicy.exspyEquivalent) is the old fit; a caller passing sumEscape keeps full control. The unlisted-line and fit-range refits repeat the reported decision; the proposer axis refinement runs without sum peaks. Noise-free Si K-alpha against planted: -0.0 to -4.0 % on five pools, -7.9 % at the highest rate; Cu K-alpha 92-97 % of planted at high rate (0-42 % before); reduced chi2 on the Poisson counts 0.70-1.08. The absorption-truth Si pin moves from -0.203 to -0.067 (+- 0.051), and the old pin stays on the .off fit. The Al K-alpha tail stays a detector calibration: measured once with TailEstimate on a high-count spectrum and typed, it brings Mg K-alpha from -22 to -32 % to within 5 % (matrix map -15 %). New fixture edx-demo-pools.json from tools/edx-sim/demo_pools.py. The golden scenarios pin the explicit off model; their numbers are unchanged, only the two new nil fields are added. Results stay validation none. Reviewed (numerics, two independent reviews): sigma scaling, the coincident-line caveat, the axis refinement cost and the wording fixed. BREAKING CHANGE: PooledQuantifier.run fits sum peaks by default when the data support them, so nets, at% and wt% change on spectra with pile-up. Migration: pass a policy with fit.sumPeaks = .off (or FitPolicy.exspyEquivalent), or sumEscape: SumEscapeModel(), to keep the old numbers. A stored QuantificationPolicy without the new field decodes as .off and its footer names the non-default policy. Done: sum peaks on by default in the pooled fit, with planted-truth tests on the demo pools. Next: the parity and open-items rows once the open PRs on those files land, a check of the default on real data with published values, and the sum-peak question for the auto ID possible candidates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 31, 2026
…-peak branch Conflicts resolved keeping both sides: - FitPolicy.init takes sumPeaks, sumPeakSignificance and continuumDeadLayer. - FitPolicy CodingKeys and init(from:) now carry continuumDeadLayer. The branch had a hand-written decoder, so without this the dead-layer term was dropped from the encoded policy, its footer line and canonicalJSON (caught by testPooledQuantifierRunsTheTermTypedAndFittedAndNamesIt). - PooledQuantification keeps appliedSumEscape and continuumDeadLayerMicrons; the result sets both. - Spectro goldens: both new nil lines kept. Re-recorded from the merged code: byte-identical to the resolved files, no number moves. Done: the sum-peak branch is on current main, fast gate and SpectroGoldenTests green. Next: stack the continuum-edge branch on it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paullo9
added a commit
that referenced
this pull request
Oct 31, 2026
Main gained #59, which carries this branch's feature in a wider form: the pooled result's sumEscapeCounts is filled from every kept sum-peak fit (explicit model, always, and whenSignificant), and SumEscapeExplainedTests is a superset of this branch's copy. Conflicts in PooledQuantification.swift and SumEscapeExplainedTests.swift take main's side; the golden re-record and its header note are already in main. After this merge the tree equals origin/main. Done: branch up to date; it adds nothing over main. Next: the PR can close as superseded by #59. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paullo9
added a commit
that referenced
this pull request
Oct 31, 2026
Main gained #59 (squashed, the base this branch was stacked on) and the continuum dead-layer term. Conflicts: - PooledQuantification.swift, QuantificationPolicy.swift: this branch does not change them; main's side kept (it is the branch side plus continuumDeadLayer). - the four golden baselines: main's files taken, then re-recorded with SPECTRO_GOLDEN_DIR. Against main the recording adds only the explainedBySumPeak = nil lines of this branch (35 in eds-realistic-l-sim, 60 in eds-unlisted-sim); demo-edx-tiny-dm4, eds-weakline-sim and eds-proposer-sim match exactly. No number moves. After the merge the diff against main is this branch's two commits only. Done: branch up to date with main, builds, gate green. Next: review and merge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paullo9
added a commit
that referenced
this pull request
Nov 1, 2026
Keeps one copy of the #59 sum-peak migration note (it appeared five times) and adds #65's breaking change: the broadened continuum step is the new default, and FitPolicy.continuumEdgeStep = .sharp keeps the old numbers. Collapses the duplicate #46, #47 and CI lines. Notes under #68 that refits on the opt-in fitted dead layer repeat the reported thickness, so their numbers can change. The version is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paullo9
added a commit
that referenced
this pull request
Nov 1, 2026
* chore(main): release 0.13.0 * docs: tidy the 0.13.0 CHANGELOG entry Keeps one copy of the #59 sum-peak migration note (it appeared five times) and adds #65's breaking change: the broadened continuum step is the new default, and FitPolicy.continuumEdgeStep = .sharp keeps the old numbers. Collapses the duplicate #46, #47 and CI lines. Notes under #68 that refits on the opt-in fitted dead layer repeat the reported thickness, so their numbers can change. The version is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: macem-release[bot] <349909138+macem-release[bot]@users.noreply.github.com> Co-authored-by: Paullo9 <paullobpreis@icloud.com> Co-authored-by: Claude Opus 5.5 <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.
Requested by Paul · project thread
Part of #8
Part of #15
Stacked on
issue-15-pileup-explained(the sum and escape peaks as explained counts for the proposer). This PR's base is that branch; retarget tomainonce it merges. It is stacked becausePooledQuantification.swiftis already in two open PRs.What changes
The pooled quantification fitted one Gaussian per line and no sum (pile-up) peaks, exSpy's model.
FitPolicy.sumPeaks(SumPeakRule, default.whenSignificant) now tests the strongest line's sum peak with itself alone (Al K-alpha + Al K-alpha in an Al alloy). If its pile-up coefficient is 3 sigma above 0, the fit keeps the sum peaks of the four strongest lines (EDSSumEscapeFit). Sigma is scaled by sqrt(max(chi2_red, 1)) of that fit. If the test fails, the result is the old fit, bit for bit..off(FitPolicy.exspyEquivalent). A caller that passessumEscapekeeps full control. The golden scenarios pin the explicit off model; their numbers are byte-identical, and only the two new nil fields are added.TailEstimateon a high-count spectrum and typed intoFitPolicy.lineTail, it takes Mg K-alpha to within 5 %.Numbers against planted truth on synthetic data
The truth is the simulation recipe of
sim_edx.pyv2.0 (mac4DSTEMdemo-edx, public), not a measurement, so every result staysvalidation: "none". The pools come fromtools/edx-sim/demo_pools.pyand are stored in the new fixtureedx-demo-pools.json; the kit'seds-absorption-truth.jsonis the sixth pool. Setup: true k typed, truth geometry, absorption on, axis locked.Si planted counts include the Si internal-fluorescence peak, the kit's convention. The "before" columns are the
.offfit, which equals the old default bit for bit.Si K-alpha, net against planted, noise-free
Cu K-alpha, read against planted, noise-free
On the L6 Poisson counts Cu reads 0.90 of planted, against 0 before.
Mg K-alpha, net against planted, noise-free
Reduced chi2 on the Poisson counts: L5 6.36 to 0.70, L6 31.3 to 1.08, L7 3.30 to 0.99, M_A 2.11 to 0.83, M_matrix 4.95 to 0.98. The self pair passes on every pool's Poisson counts at z = 5.1 (L6) to 35.5 (M_matrix).
At% on the Poisson counts (Si target includes the internal fluorescence):
Absorption-truth pin: the Si bias moves from -0.203 +- 0.040 to -0.067 +- 0.051. The old pin stays, on the
.offfit.Unlisted-line check (#9): with the default, Ar and Ag are not named on L5, M_A or M_matrix. The Al+Al sum is a modelled peak and is explained to the proposer.
Bounds are measured regression bounds on fixed inputs, not derived limits: Si 5 % (10 % on L5), Cu 0.85 at high rate, chi2_red 1.2.
Migration
PooledQuantifier.runnow fits sum peaks by default when the data support them, so nets, at% and wt% change on spectra with pile-up. To keep the old numbers, pass either:fit.sumPeaks = .off(orFitPolicy.exspyEquivalent), orsumEscape: SumEscapeModel().A stored
QuantificationPolicywithout the new field decodes as.off, and its footer names the non-default policy.Test evidence
swift test -c release --parallel --num-workers 1 -j 1 --skip SpectroKitParityTests: green, 284 tests (2 known issues, unchanged).SpectroKitParityTestsrun once, because a default changed: green, 83 tests..offfit, which is the old default bit for bit (asserted). Before, Si read -8 to -57 % and Cu 0 to 0.42 at high rate; now they pass the bounds.What remains
docs/parity.mdanddocs/open-items.mdrows, and thetools/edx-sim/README.mdline fordemo_pools.py, wait until the open PRs on those files land (each file is already in two open PRs).proposeOnRefinedAxispath still lists Ar and Ag as possible candidates and does not give them the sum-peak question. That needs proposer files in other open PRs.🤖 Generated with Claude Code