[ESSSANS] fix(sans): snap wavelength band edges onto WavelengthBins - #726
Open
SimonHeybrock wants to merge 1 commit into
Open
[ESSSANS] fix(sans): snap wavelength band edges onto WavelengthBins#726SimonHeybrock wants to merge 1 commit into
SimonHeybrock wants to merge 1 commit into
Conversation
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.
SimonHeybrock
marked this pull request as ready for review
August 25, 2026 08:05
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.
Problem
_reducesplits(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_beamhistograms 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:
direct_beampathEverything 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 setsWavelengthBandsand so is unaffected.Fix
process_wavelength_bandssnaps each band edge onto the nearestWavelengthBinsvalue, 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
wavelengthcoordinate of the result. Warning from inside a Sciline provider would fire2 × nitertimes perdirect_beamcall, which spams or deduplicates into invisibility.Alternatives considered. Rebinning the dense parts onto exact band edges keeps the requested boundaries, but needs
WavelengthBinsplumbed into_reducesince 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 requiresProcessedWavelengthBandsto 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
ProcessedWavelengthBandsrather than left implicit in_reduce.Behaviour changes beyond the bias
Bands falling outside
WavelengthBinswere 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. EveryWavelengthBandssetting 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 iswavelength, a per-event coordinate binned once directly toWavelengthBins; 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
direct_beamfeeds processed bands back in), and each new error condition are covered.directbeam_test.pyandsans2d_reduction_test.pypass unchanged.