Skip to content

[ESSSANS] fix(sans): snap wavelength band edges onto WavelengthBins - #726

Open
SimonHeybrock wants to merge 1 commit into
mainfrom
sans-wavelength-band-alignment
Open

[ESSSANS] fix(sans): snap wavelength band edges onto WavelengthBins#726
SimonHeybrock wants to merge 1 commit into
mainfrom
sans-wavelength-band-alignment

Conversation

@SimonHeybrock

Copy link
Copy Markdown
Member

Problem

_reduce splits (Q, wavelength) data into wavelength bands along paths that disagree about where a band starts and ends. Event data is re-binned onto the band edges, so bands are exact. Dense data is label-sliced, so band boundaries snap to whole wavelength bins. The I(Q) denominator (monitor × transmission × direct beam × solid angle) is always dense, so in a multi-band reduction the numerator and the denominator cover different wavelength ranges and each band is biased.

The dense parts also disagree among themselves, because scipp label slicing means different things depending on the wavelength coord. The denominator carries midpoints, so it assigns each bin to the band containing that bin's midpoint. The numerator that direct_beam histograms carries bin edges, so it picks up every bin overlapping the band, and adjacent bands overlap.

Measured per-band I(Q) error on Larmor data, against an exact integral over the same range:

bins / bands normal path direct_beam path
50 / 10 (loki tests) 0.00% 0.00%
140 / 10 (sans2d docs) 0.00%
overlapping-bands direct-beam test −2.42%…+4.19% +4.00%…+21.98%
50 / 7 −11.23%…+6.93% −0.18%…+42.91%
141 / 10 −6.32%…+2.81%

Everything in the docs and tests happens to use bands aligned with the bins and is unaffected, except test_can_compute_direct_beam_with_overlapping_wavelength_bands, whose bands are 12/11 Å wide against 0.24 Å bins. Live data reduction never sets WavelengthBands and so is unaffected.

Fix

process_wavelength_bands snaps each band edge onto the nearest WavelengthBins value, taking it from the bins array so the two are bit-identical. All three representations then select identical wavelength ranges and every measurement above goes to 0.00%. Bands already aligned with the bins are unchanged bit-for-bit, so no currently-correct reduction moves.

An edge can move by at most half a bin width. This is not announced: the bound is small by construction, and the bands actually used are returned and become the wavelength coordinate of the result. Warning from inside a Sciline provider would fire 2 × niter times per direct_beam call, which spams or deduplicates into invisibility.

Alternatives considered. Rebinning the dense parts onto exact band edges keeps the requested boundaries, but needs WavelengthBins plumbed into _reduce since the denominator only carries midpoints, and the proportional split correlates adjacent bands and understates their variances — the trade-off weighed in #725. Selecting on bin indices rather than values removes the coord-kind duality outright, but requires ProcessedWavelengthBands to carry both indices and values, changing a public workflow type for no user-visible gain.

Because the fix now rests on an invariant that spans two providers, it is written on ProcessedWavelengthBands rather than left implicit in _reduce.

Behaviour changes beyond the bias

Bands falling outside WavelengthBins were silently clamped to whatever range existed, which turns a unit mistake or a stale config into a plausible-looking result. Those now raise, as do reversed bands and NaN edges. Every WavelengthBands setting in the repo is already in range, so nothing here breaks. Making these loud is what makes it defensible for the half-bin snap to stay quiet.

Relation to #670 / #725

Same family, different mechanism. In #670 the second axis is two_theta, a pixel-geometry coordinate that has to be regridded onto a coarser output grid, and the varying number of internal bins per output bin produced periodic spikes. Here the second axis is wavelength, a per-event coordinate binned once directly to WavelengthBins; nothing is regridded and there is no aliasing. The error comes from the band-splitting slice snapping to bin boundaries, and only on the dense paths.

Test plan

  • New tests reduce the same flat-density data in all three wavelength representations (events, bin edges, midpoints) and assert the per-band results agree; they fail on unaligned bands without the fix.
  • Snapping, overlap preservation, idempotence (direct_beam feeds processed bands back in), and each new error condition are covered.
  • Existing directbeam_test.py and sans2d_reduction_test.py pass unchanged.

Band boundaries are realized by slicing the wavelength axis, which can only cut
at bin boundaries. Event data was re-binned onto the requested band edges and
was therefore exact, while the dense denominator was sliced and snapped to whole
bins. Numerator and denominator then covered different wavelength ranges,
biasing I(Q) per band by several percent for bands only a few bins wide.

The dense parts are inconsistent among themselves too: the denominator carries
wavelength midpoints and so assigns each bin to the band containing its
midpoint, whereas the numerator that `direct_beam` histograms carries bin edges
and so picks up every overlapping bin. On real Larmor data the existing
overlapping-bands direct-beam configuration was off by up to 22%.

Snapping the band edges onto WavelengthBins makes all three representations
select identical wavelength ranges. Aligned bands, which is everything in the
docs and tests bar the overlapping-bands case, are unchanged bit-for-bit.

Bands falling outside WavelengthBins used to be clamped to whatever range
existed; they now raise, as do reversed bands and NaN edges, so that the
remaining silent adjustment is bounded by half a bin width.
@github-actions github-actions Bot added the esssans Issues for esssans. label Aug 25, 2026
@github-actions github-actions Bot changed the title fix(sans): snap wavelength band edges onto WavelengthBins [ESSSANS] fix(sans): snap wavelength band edges onto WavelengthBins Aug 25, 2026
@SimonHeybrock
SimonHeybrock marked this pull request as ready for review August 25, 2026 08:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

esssans Issues for esssans.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant