Skip to content

Commit 647e080

Browse files
committed
Rebin vanadium if needed
1 parent a190522 commit 647e080

File tree

2 files changed

+158
-43
lines changed

2 files changed

+158
-43
lines changed

src/ess/powder/correction.py

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
import enum
66
from typing import TypeVar
77

8-
import ess.reduce
98
import sciline
109
import scipp as sc
10+
11+
import ess.reduce
1112
from ess.reduce.uncertainty import broadcast_uncertainties
1213

1314
from ._util import event_or_outer_coord
@@ -151,9 +152,9 @@ def _normalize_by_vanadium(
151152
uncertainty_broadcast_mode: UncertaintyBroadcastMode,
152153
) -> sc.DataArray:
153154
norm = (
154-
vanadium.hist(dspacing=data.coords['dspacing'])
155+
vanadium.hist(data.coords)
155156
if vanadium.is_binned
156-
else vanadium.rebin(dspacing=data.coords['dspacing'])
157+
else vanadium.rebin(data.coords)
157158
)
158159
norm = broadcast_uncertainties(
159160
norm, prototype=data, mode=uncertainty_broadcast_mode
@@ -177,8 +178,18 @@ def normalize_by_vanadium_dspacing(
177178
vanadium: FocussedDataDspacing[VanadiumRun],
178179
uncertainty_broadcast_mode: UncertaintyBroadcastMode,
179180
) -> IntensityDspacing[_RunTypeNoVanadium]:
180-
"""
181-
Normalize sample data by a vanadium measurement and return intensity vs. d-spacing.
181+
"""Normalize sample data binned in d-spacing by a vanadium measurement.
182+
183+
If the vanadium data is binned, it gets :func:`histogrammed <scipp.hist>` to the
184+
same bins as ``data``. If it is not binned, it gets :func:`rebinned <scipp.rebin>`
185+
to the same coordinates as ``data``. Then, the result is computed as
186+
187+
.. code-block:: python
188+
189+
data / vanadium
190+
191+
And any bins where vanadium is zero are masked out
192+
with a mask called "zero_vanadium".
182193
183194
Parameters
184195
----------
@@ -196,6 +207,11 @@ def normalize_by_vanadium_dspacing(
196207
``data / vanadium``.
197208
May contain a mask "zero_vanadium" which is ``True``
198209
for bins where vanadium is zero.
210+
211+
See Also
212+
--------
213+
normalize_by_vanadium_dspacing_and_two_theta:
214+
Normalization for 2d data binned in d-spacing and :math`2\\theta`.
199215
"""
200216
return IntensityDspacing(
201217
_normalize_by_vanadium(data, vanadium, uncertainty_broadcast_mode)
@@ -207,9 +223,18 @@ def normalize_by_vanadium_dspacing_and_two_theta(
207223
vanadium: FocussedDataDspacingTwoTheta[VanadiumRun],
208224
uncertainty_broadcast_mode: UncertaintyBroadcastMode,
209225
) -> IntensityDspacingTwoTheta[_RunTypeNoVanadium]:
210-
"""
211-
Normalize sample data by a vanadium measurement and return intensity vs.
212-
(d-spacing, 2theta).
226+
"""Normalize sample data binned in (d-spacing, 2theta) by a vanadium measurement.
227+
228+
If the vanadium data is binned, it gets :func:`histogrammed <scipp.hist>` to the
229+
same bins as ``data``. If it is not binned, it gets :func:`rebinned <scipp.rebin>`
230+
to the same coordinates as ``data``. Then, the result is computed as
231+
232+
.. code-block:: python
233+
234+
data / vanadium
235+
236+
And any bins where vanadium is zero are masked out
237+
with a mask called "zero_vanadium".
213238
214239
Parameters
215240
----------
@@ -227,6 +252,11 @@ def normalize_by_vanadium_dspacing_and_two_theta(
227252
``data / vanadium``.
228253
May contain a mask "zero_vanadium" which is ``True``
229254
for bins where vanadium is zero.
255+
256+
See Also
257+
--------
258+
normalize_by_vanadium_dspacing:
259+
Normalization for 1d data binned in d-spacing.
230260
"""
231261
return IntensityDspacingTwoTheta(
232262
_normalize_by_vanadium(data, vanadium, uncertainty_broadcast_mode)

tests/powder/correction_test.py

Lines changed: 120 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -461,39 +461,57 @@ def test_normalize_by_monitor_integrated_assigns_mask_if_monitor_range_too_narro
461461
)
462462

463463

464-
def random_variable(
465-
rng: np.random.Generator, dim: str, n: int, unit: str, with_variances: bool = False
466-
) -> sc.Variable:
467-
values = rng.uniform(0.1, 2.0, n)
468-
variances = values * rng.uniform(0.1, 0.5, n) if with_variances else None
469-
return sc.array(dims=[dim], values=values, variances=variances, unit=unit)
470-
471-
472-
def random_binned_data(
473-
rng: np.random.Generator,
474-
dim: str,
475-
n_events: int,
476-
n_bins: int,
477-
unit: str,
478-
coord_unit: str,
479-
with_variances: bool = False,
480-
) -> sc.DataArray:
481-
return sc.DataArray(
482-
random_variable(rng, 'event', n_events, unit, with_variances=with_variances),
483-
coords={dim: random_variable(rng, 'event', n_events, coord_unit)},
484-
).bin({dim: n_bins})
485-
486-
487-
def make_sample_and_vanadium() -> tuple[sc.DataArray, sc.DataArray]:
488-
rng = np.random.default_rng(seed=495)
489-
sample = random_binned_data(rng, 'dspacing', 84, 35, 'counts', 'Å', True)
490-
vanadium = random_binned_data(rng, 'dspacing', 146, 79, 'counts', 'Å', True)
491-
return sample, vanadium
464+
class TestNormalizeByVanadium:
465+
def random_variable(
466+
self,
467+
rng: np.random.Generator,
468+
dim: str,
469+
n: int,
470+
unit: str,
471+
with_variances: bool = False,
472+
) -> sc.Variable:
473+
values = rng.uniform(0.1, 2.0, n)
474+
variances = values * rng.uniform(0.1, 0.5, n) if with_variances else None
475+
return sc.array(dims=[dim], values=values, variances=variances, unit=unit)
476+
477+
def random_binned_data(
478+
self,
479+
rng: np.random.Generator,
480+
n_events: int,
481+
unit: str,
482+
with_variances: bool = False,
483+
*coords: tuple[str, int, str],
484+
) -> sc.DataArray:
485+
return sc.DataArray(
486+
self.random_variable(
487+
rng, 'event', n_events, unit, with_variances=with_variances
488+
),
489+
coords={
490+
dim: self.random_variable(rng, 'event', n_events, coord_unit)
491+
for (dim, _, coord_unit) in coords
492+
},
493+
).bin({dim: n_bins for (dim, n_bins, _) in coords})
494+
495+
def make_sample_and_vanadium_1d(self) -> tuple[sc.DataArray, sc.DataArray]:
496+
rng = np.random.default_rng(seed=495)
497+
sample = self.random_binned_data(rng, 84, 'count', True, ('dspacing', 35, 'Å'))
498+
vanadium = self.random_binned_data(
499+
rng, 146, 'count', True, ('dspacing', 79, 'Å')
500+
)
501+
return sample, vanadium
492502

503+
def make_sample_and_vanadium_2d(self) -> tuple[sc.DataArray, sc.DataArray]:
504+
rng = np.random.default_rng(seed=3193)
505+
sample = self.random_binned_data(
506+
rng, 138, 'count', True, ('dspacing', 35, 'Å'), ('two_theta', 13, 'rad')
507+
)
508+
vanadium = self.random_binned_data(
509+
rng, 170, 'count', True, ('dspacing', 79, 'Å'), ('two_theta', 14, 'rad')
510+
)
511+
return sample, vanadium
493512

494-
class TestNormalizeByVanadium:
495513
def test_1d_binned_vanadium(self) -> None:
496-
sample, vanadium = make_sample_and_vanadium()
514+
sample, vanadium = self.make_sample_and_vanadium_1d()
497515
normed = normalize_by_vanadium_dspacing(
498516
FocussedDataDspacing[SampleRun](sample),
499517
FocussedDataDspacing[VanadiumRun](vanadium),
@@ -507,7 +525,7 @@ def test_1d_binned_vanadium(self) -> None:
507525
sc.testing.assert_allclose(normed, expected)
508526

509527
def test_1d_histogrammed_vanadium(self) -> None:
510-
sample, vanadium = make_sample_and_vanadium()
528+
sample, vanadium = self.make_sample_and_vanadium_1d()
511529
vanadium = vanadium.hist()
512530
normed = normalize_by_vanadium_dspacing(
513531
FocussedDataDspacing[SampleRun](sample),
@@ -521,8 +539,8 @@ def test_1d_histogrammed_vanadium(self) -> None:
521539
expected = sample / sc.values(norm)
522540
sc.testing.assert_allclose(normed, expected)
523541

524-
def test_binned_vanadium_binning_has_no_effect(self) -> None:
525-
sample, vanadium = make_sample_and_vanadium()
542+
def test_1d_binned_vanadium_binning_has_no_effect(self) -> None:
543+
sample, vanadium = self.make_sample_and_vanadium_1d()
526544
vana_binned_like_sample = vanadium.bin(dspacing=sample.coords['dspacing'])
527545
normed_a = normalize_by_vanadium_dspacing(
528546
FocussedDataDspacing[SampleRun](sample),
@@ -537,7 +555,7 @@ def test_binned_vanadium_binning_has_no_effect(self) -> None:
537555
sc.testing.assert_allclose(normed_a, normed_b)
538556

539557
def test_1d_masks_zero_vanadium_bins(self) -> None:
540-
sample, vanadium = make_sample_and_vanadium()
558+
sample, vanadium = self.make_sample_and_vanadium_1d()
541559
vanadium['dspacing', 5] = sc.scalar(0.0, variance=0.0, unit='counts')
542560
normed = normalize_by_vanadium_dspacing(
543561
FocussedDataDspacing[SampleRun](sample),
@@ -551,4 +569,71 @@ def test_1d_masks_zero_vanadium_bins(self) -> None:
551569
normed.masks['zero_vanadium'], norm.data == sc.scalar(0.0, unit='counts')
552570
)
553571

554-
# TODO vanadium and 2theta
572+
def test_2d_binned_vanadium(self) -> None:
573+
sample, vanadium = self.make_sample_and_vanadium_2d()
574+
normed = normalize_by_vanadium_dspacing_and_two_theta(
575+
FocussedDataDspacingTwoTheta[SampleRun](sample),
576+
FocussedDataDspacingTwoTheta[VanadiumRun](vanadium),
577+
UncertaintyBroadcastMode.drop,
578+
)
579+
# we test masks separately
580+
normed = normed.drop_masks(list(normed.masks.keys()))
581+
582+
norm = vanadium.hist(
583+
dspacing=sample.coords['dspacing'], two_theta=sample.coords['two_theta']
584+
)
585+
expected = sample / sc.values(norm)
586+
sc.testing.assert_allclose(normed, expected)
587+
588+
def test_2d_histogrammed_vanadium(self) -> None:
589+
sample, vanadium = self.make_sample_and_vanadium_2d()
590+
vanadium = vanadium.hist()
591+
normed = normalize_by_vanadium_dspacing_and_two_theta(
592+
FocussedDataDspacingTwoTheta[SampleRun](sample),
593+
FocussedDataDspacingTwoTheta[VanadiumRun](vanadium),
594+
UncertaintyBroadcastMode.drop,
595+
)
596+
# we test masks separately
597+
normed = normed.drop_masks(list(normed.masks.keys()))
598+
599+
norm = vanadium.rebin(
600+
dspacing=sample.coords['dspacing'], two_theta=sample.coords['two_theta']
601+
)
602+
expected = sample / sc.values(norm)
603+
sc.testing.assert_allclose(normed, expected)
604+
605+
def test_2d_binned_vanadium_binning_has_no_effect(self) -> None:
606+
sample, vanadium = self.make_sample_and_vanadium_2d()
607+
vana_binned_like_sample = vanadium.bin(
608+
dspacing=sample.coords['dspacing'], two_theta=sample.coords['two_theta']
609+
)
610+
normed_a = normalize_by_vanadium_dspacing_and_two_theta(
611+
FocussedDataDspacingTwoTheta[SampleRun](sample),
612+
FocussedDataDspacingTwoTheta[VanadiumRun](vanadium),
613+
UncertaintyBroadcastMode.drop,
614+
)
615+
normed_b = normalize_by_vanadium_dspacing_and_two_theta(
616+
FocussedDataDspacingTwoTheta[SampleRun](sample),
617+
FocussedDataDspacingTwoTheta[VanadiumRun](vana_binned_like_sample),
618+
UncertaintyBroadcastMode.drop,
619+
)
620+
sc.testing.assert_allclose(normed_a, normed_b)
621+
622+
def test_2d_masks_zero_vanadium_bins(self) -> None:
623+
sample, vanadium = self.make_sample_and_vanadium_2d()
624+
vanadium['dspacing', 5]['two_theta', 7] = sc.scalar(
625+
0.0, variance=0.0, unit='counts'
626+
)
627+
normed = normalize_by_vanadium_dspacing_and_two_theta(
628+
FocussedDataDspacingTwoTheta[SampleRun](sample),
629+
FocussedDataDspacingTwoTheta[VanadiumRun](vanadium),
630+
UncertaintyBroadcastMode.drop,
631+
)
632+
633+
norm = vanadium.hist(
634+
dspacing=sample.coords['dspacing'], two_theta=sample.coords['two_theta']
635+
)
636+
637+
sc.testing.assert_allclose(
638+
normed.masks['zero_vanadium'], norm.data == sc.scalar(0.0, unit='counts')
639+
)

0 commit comments

Comments
 (0)