Skip to content

fix(profile): fade each peak to zero at its support edge - #18

Merged
drthyang merged 1 commit into
mainfrom
claude/relaxed-lamport-0e6845
Sep 28, 2026
Merged

drthyang merged 1 commit into
mainfrom
claude/relaxed-lamport-0e6845

Conversation

@drthyang

Copy link
Copy Markdown
Owner

Problem

synthesizePattern summed each peak inside a hard ±20·FWHM window, and leBailExtract inside ±12·FWHM. The edge moves with every width and position parameter. When a data point crossed it, y jumped by the pseudo-Voigt's Lorentzian tail. A central difference divided that jump by 2h, so the column spiked ∝ 1/h at one point.

On the Mn₃Ga 30 K example (Example 3, claude/example-magnetic), the profW column at the engine's step gave JᵀJ = 218, 91 % of it from one data point, instead of 8.7.

Fix

supportTaper fades each peak to exactly 0 over the outer 20 % of its support with a C² quintic. It depends only on dist/half, so it moves smoothly with Γ and the centre.

  • The binary-search window is unchanged. Points in the flat 80 % are bit-identical.
  • The fade removes 0.18 % of a Lorentzian's area; the 1.6 % beyond 20 Γ was never modelled.
  • Gaussian and TOF tails are already below 1e-7 of the peak there. TOF's old jump was at most ~2e-9 of the peak, so TOF moves only at roundoff.
  • Le Bail gets the same fade. The cell check refines its FWHM by finite differences, and a crossing there had moved yCalc by 7e-4 of the peak.
  • Cost: ~7 % per synthesis (7.2 vs 6.75 ms at 20k points × 5k pseudo-Voigt peaks).

Example 3 replay (old → fixed)

old fixed
profW JᵀJ at the engine step 218.4 (90.6 % from point #1273) 8.730, flat for h = 1e-2…1e-6
profW esd 0.071 1.16
profU / profV esd 1.60 / 1.59 3.40 / 4.66
final χ² → after continuing 6366.1 → 6357.7 6366.1 → 6357.5

The last row is a separate issue this PR does not fix. The final fit stops early on a flat U/V/W–moment valley, with or without the spike.

Tests

All four new tests fail on the old code.

  • peakWindow.test.ts puts a data point exactly on the strongest peak's edge. The profW JᵀJ must agree across h = 1e-3…1e-7·|W|. With the hard cutoff it was up to 7·10⁶× too large.
  • profile.test.ts requires the finite-difference width derivative to match the analytic ∂pV/∂Γ at every step. It also checks that a peak is exactly 0 at its edge, and that ascending, descending and unordered grids agree.
  • leBail.test.ts requires Le Bail to stay continuous in the width across an edge crossing.
  • nextParameters.test.ts changed its "converged model promises no real wR gain" case. It now converges scale + background on noisy data first. Its starting values alone left wR at 18 %, and the profile group's predicted gain there (0.0053 vs a 0.005 limit) depended on the tails. Converged: wR 0.8 %, largest predicted gain 2e-5, limit tightened to 1e-3.

Golden shifts

All are GaNb₄Se₈ synchrotron data with a TCH pseudo-Voigt. Every other golden moved only at roundoff.

  • Rietveld wR: 10.3484 → 10.3475 %
  • Staged: wR 5.718 → 5.721 %, W 5.040 → 4.997
  • GSAS-II benchmark, fixed atoms: 18.31 → 18.33 % (single pV) and 23.44 → 23.33 % (TCH+FCJ)

Verification

  • vitest run with data/ present: 1495 passed, 46 skipped (the same env-gated skips as main).
  • tsc -b --noEmit and eslint . are clean.

🤖 Generated with Claude Code

synthesizePattern summed each peak inside a hard ±20·FWHM window, and
leBailExtract inside ±12·FWHM. The edge moves with every width and
position parameter, so y jumped by the pseudo-Voigt Lorentzian tail
whenever a data point crossed it, and a central difference divided that
jump by 2h. On the Mn3Ga 30 K example (Example 3), the profW column at
the engine's step gave JtJ = 218 (91 % from one point) instead of 8.7,
and profW's esd came out 0.071 instead of 1.16.

A C² quintic now fades each peak to exactly 0 over the outer 20 % of its
support (supportTaper). The binary-search window is unchanged, and
points in the flat 80 % are bit-identical. The fade removes 0.18 % of a
Lorentzian's area. Gaussian and TOF tails are already below 1e-7 of the
peak there, so those patterns move only at roundoff.

Tests: FD width columns across an edge crossing (profile level vs the
analytic dpV/dΓ; powder level for profW), Le Bail continuity in the
width, and grid-order invariance. nextParameters' "converged" case now
converges scale + background on noisy data first. Its seeds had left
wR at 18 %, and the profile group's predicted gain there (0.0053)
depended on the tails.

Golden shifts (all GaNb4Se8 synchrotron, TCH pseudo-Voigt):
Rietveld wR 10.3484 → 10.3475 %; staged wR 5.718 → 5.721 %
(W 5.040 → 4.997); GSAS-II benchmark fixed-atom wR 18.31 → 18.33 %
(single pV) and 23.44 → 23.33 % (TCH+FCJ). TOF goldens: roundoff only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@drthyang
drthyang merged commit a53be9f into main Sep 28, 2026
2 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