Skip to content

Commit b3da276

Browse files
committed
Merge remote-tracking branch 'origin/main' into beer-powdiff
2 parents 951a26d + 7f4ca04 commit b3da276

7 files changed

Lines changed: 87 additions & 25 deletions

File tree

docs/user-guide/dream/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,5 @@ dream-instrument-view
5454
workflow-widget-dream
5555
dream-detector-diagnostics
5656
dream-visualize-absorption
57-
dream-make-tof-lookup-table
57+
dream-make-wavelength-lookup-table
5858
```

src/ess/beer/clustering.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def cluster_events_by_streak(
2020

2121
# We need to keep these coordinates after binning,
2222
# adding them to the binned data coords achieves this.
23-
for coord in ('two_theta', 'Ltotal'):
23+
for coord in ('two_theta', 'Ltotal', 'frame_cutoff_time'):
2424
da.bins.coords[coord] = sc.bins_like(da, da.coords[coord])
2525

2626
h = da.bins.concat().hist(coarse_d=1000)

src/ess/beer/conversions.py

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
def compute_wavelength_in_each_cluster(
2020
da: StreakClusteredData[RunType],
21+
chopper_delay: WavelengthDefinitionChopperDelay,
2122
mod_period: ModulationPeriod,
2223
graph: GeometryCoordTransformGraph,
2324
) -> WavelengthDetector[RunType]:
@@ -47,7 +48,7 @@ def compute_wavelength_in_each_cluster(
4748
sin_theta_L = sc.sin(da.bins.coords['two_theta'] / 2) * da.bins.coords['Ltotal']
4849
t = time_of_arrival(
4950
da.bins.coords['event_time_offset'],
50-
da.coords['tc'].to(unit=da.bins.coords['event_time_offset'].unit),
51+
da.bins.coords['frame_cutoff_time'],
5152
)
5253
for _ in range(15):
5354
s, t0 = _linear_regression_by_bin(sin_theta_L, t, da.data)
@@ -59,12 +60,33 @@ def compute_wavelength_in_each_cluster(
5960
too_far_from_center=(distance_to_self > max_distance_from_streak_line),
6061
)
6162

62-
da = da.assign_coords(t0=sc.values(t0))
63-
da = da.bins.assign_coords(tof=(t - sc.values(t0)))
64-
da = da.transform_coords(('wavelength',), graph=graph)
63+
# The t0 estimate from fitting is influenced by peak overlap, background,
64+
# and other factors that can make the estimate offset from the true
65+
# chopper opening time that it should match.
66+
# We know the true chopper opening times, so instead of using the t0 estimte
67+
# directly we can round the estimate to the closest chopper opening time.
68+
# That way the t0 estimate becomes more robust and is guaranteed to correspond to
69+
# a true chopper opening time.
70+
t0 = _round_t0_to_nearest_chopper_opening(sc.values(t0), mod_period, chopper_delay)
71+
da = da.assign_coords(t0=t0)
72+
da = da.bins.assign_coords(tof=(t - t0))
6573
return da
6674

6775

76+
def _round_t0_to_nearest_chopper_opening(
77+
t0: sc.Variable,
78+
mod_period: sc.Variable,
79+
chopper_delay: sc.Variable,
80+
) -> sc.Variable:
81+
out = t0 - chopper_delay
82+
out /= mod_period
83+
out += 0.5
84+
sc.floor(out, out=out)
85+
out *= mod_period
86+
out += chopper_delay
87+
return out
88+
89+
6890
def _linear_regression_by_bin(
6991
x: sc.Variable, y: sc.Variable, w: sc.Variable
7092
) -> tuple[sc.Variable, sc.Variable]:
@@ -124,14 +146,14 @@ def _compute_d_given_list_of_peaks(
124146

125147
def time_of_arrival(
126148
event_time_offset: sc.Variable,
127-
tc: sc.Variable,
149+
frame_cutoff_time: sc.Variable,
128150
):
129151
"""Does frame unwrapping for pulse shaping chopper modes.
130152
131-
Events before the "cutoff time" `tc` are assumed to come from the previous pulse."""
153+
Events before the "cutoff time" are assumed to come from the previous pulse."""
132154
_eto = event_time_offset
133155
T = sc.scalar(1 / 14, unit='s').to(unit=_eto.unit)
134-
tc = tc.to(unit=_eto.unit)
156+
tc = frame_cutoff_time.to(unit=_eto.unit)
135157
return sc.where(_eto >= tc, _eto, _eto + T)
136158

137159

@@ -141,13 +163,13 @@ def _tof_from_dhkl(
141163
coarse_dhkl: sc.Variable,
142164
Ltotal: sc.Variable,
143165
mod_period: sc.Variable,
144-
time0: sc.Variable,
166+
chopper_delay: sc.Variable,
145167
) -> sc.Variable:
146168
"""Computes tof for BEER given the dhkl peak that the event belongs to"""
147169
# Source: https://www.mcstas.org/download/components/current/contrib/NPI_tof_dhkl_detector.comp
148170
# tref = 2 * d_hkl * sin(theta) / hm * Ltotal
149-
# tc = time_of_arrival - time0 - tref
150-
# dt = floor(tc / mod_period + 0.5) * mod_period + time0
171+
# tc = time_of_arrival - chopper_delay - tref
172+
# dt = floor(tc / mod_period + 0.5) * mod_period + chopper_delay
151173
# tof = time_of_arrival - dt
152174
c = (-2 * 1.0 / (scipp.constants.h / scipp.constants.m_n)).to(
153175
unit=f'{time_of_arrival.unit}/m/angstrom'
@@ -156,12 +178,12 @@ def _tof_from_dhkl(
156178
out *= sc.sin(theta)
157179
out *= Ltotal
158180
out += time_of_arrival
159-
out -= time0
181+
out -= chopper_delay
160182
out /= mod_period
161183
out += 0.5
162184
sc.floor(out, out=out)
163185
out *= mod_period
164-
out += time0
186+
out += chopper_delay
165187
out *= -1
166188
out += time_of_arrival
167189
return out
@@ -205,7 +227,7 @@ def tof_from_known_dhkl_graph(
205227
da: RawDetector[RunType],
206228
mod_period: ModulationPeriod,
207229
pulse_length: PulseLength,
208-
time0: WavelengthDefinitionChopperDelay,
230+
chopper_delay: WavelengthDefinitionChopperDelay,
209231
dhkl_list: DHKLList,
210232
gg: GeometryCoordTransformGraph,
211233
) -> ElasticCoordTransformGraph[RunType]:
@@ -236,7 +258,7 @@ def _compute_coarse_dspacing(
236258
**gg,
237259
'pulse_length': lambda: pulse_length,
238260
'mod_period': lambda: mod_period,
239-
'time0': lambda: time0,
261+
'chopper_delay': lambda: chopper_delay,
240262
'tof': _tof_from_dhkl,
241263
'time_of_arrival': time_of_arrival,
242264
'coarse_dhkl': _compute_coarse_dspacing,

src/ess/beer/data.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
# Simulation with 3D detector model - almost no events
4545
# - only used to verify we can load the 3D geometry.
4646
"few_neutrons_3d_detector_example.h5": "md5:88cbe29cb539c8acebf9fd7cee9d3c57",
47+
"silicon-mode9-3d-more-neutrons.h5": "md5:a6afdc4ee9827a57f88c2a3c6ef27383",
4748
},
4849
)
4950

@@ -81,6 +82,10 @@ def mcstas_few_neutrons_3d_detector_example():
8182
return _registry.get_path('few_neutrons_3d_detector_example.h5')
8283

8384

85+
def mcstas_more_neutrons_3d_detector_example():
86+
return _registry.get_path('silicon-mode9-3d-more-neutrons.h5')
87+
88+
8489
def duplex_peaks() -> Path:
8590
return _registry.get_path('duplex-dhkl.tab')
8691

src/ess/beer/io.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -257,8 +257,8 @@ def _load_beer_mcstas(f, north_or_south=None, number=None):
257257
# Bin detector panel into rectangular "pixels"
258258
# similar in size to the physical detector pixels.
259259
da = da.bin(
260-
y=sc.linspace('y', -0.5, 0.5, 501, unit='m'),
261-
x=sc.linspace('x', -0.5, 0.5, 201, unit='m'),
260+
y=sc.linspace('y', -0.5, 0.5, 201, unit='m'),
261+
x=sc.linspace('x', -0.5, 0.5, 501, unit='m'),
262262
)
263263

264264
# Compute the position of each pixel in the global coordinate system.
@@ -302,7 +302,7 @@ def _load_beer_mcstas(f, north_or_south=None, number=None):
302302
# Used in modulation mode automatic-peak-finding reduction to estimate d.
303303
# In practice this will probably be replaced by the regular tof workflow.
304304
# But I'm not 100% sure.
305-
da.coords["tc"] = (
305+
da.coords["frame_cutoff_time"] = (
306306
sc.constants.m_n
307307
/ sc.constants.h
308308
* da.coords['wavelength_estimate']

tests/beer/mcstas_reduction_test.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import numpy as np
2+
import pytest
23
import scipp as sc
34
import scippneutron as scn
45
from scipp.testing import assert_allclose
@@ -12,10 +13,15 @@
1213
duplex_peaks_array,
1314
mcstas_duplex,
1415
mcstas_few_neutrons_3d_detector_example,
16+
mcstas_more_neutrons_3d_detector_example,
1517
mcstas_silicon_new_model,
1618
)
17-
from ess.beer.io import load_beer_mcstas, load_beer_mcstas_monitor
18-
from ess.beer.types import DetectorBank, DHKLList
19+
from ess.beer.io import (
20+
load_beer_mcstas,
21+
load_beer_mcstas_monitor,
22+
mcstas_chopper_delay_from_mode_new_simulations,
23+
)
24+
from ess.beer.types import DetectorBank, DHKLList, TofDetector
1925
from ess.reduce.nexus.types import Filename, SampleRun
2026
from ess.reduce.unwrap.types import WavelengthDetector
2127

@@ -41,11 +47,15 @@ def test_can_reduce_using_known_peaks_workflow():
4147
)
4248

4349

44-
def test_can_reduce_using_unknown_peaks_workflow():
50+
@pytest.mark.parametrize(
51+
'fname', [mcstas_silicon_new_model(7), mcstas_more_neutrons_3d_detector_example()]
52+
)
53+
def test_can_reduce_using_unknown_peaks_workflow(fname):
4554
wf = BeerModMcStasWorkflow()
46-
wf[Filename[SampleRun]] = mcstas_duplex(7)
55+
wf[Filename[SampleRun]] = fname
4756
wf[DetectorBank] = DetectorBank.north
48-
da = wf.compute(WavelengthDetector[SampleRun])
57+
wf.insert(mcstas_chopper_delay_from_mode_new_simulations)
58+
da = wf.compute(TofDetector[SampleRun])
4959
da = da.transform_coords(
5060
('dspacing',),
5161
graph=scn.conversion.graph.tof.elastic('tof'),
@@ -54,7 +64,11 @@ def test_can_reduce_using_unknown_peaks_workflow():
5464
max_peak_d = sc.midpoints(h['dspacing', np.argmax(h.values)].coords['dspacing'])[0]
5565
assert_allclose(
5666
max_peak_d,
57-
sc.scalar(2.0407, unit='angstrom'),
67+
# The two peaks around 1.6 are very similar in magnitude,
68+
# so either of them can be bigger and that is fine.
69+
sc.scalar(1.5677, unit='angstrom')
70+
if max_peak_d < sc.scalar(1.6, unit='angstrom')
71+
else sc.scalar(1.6374, unit='angstrom'),
5872
atol=sc.scalar(1e-2, unit='angstrom'),
5973
)
6074

tests/diffraction/test_peaks.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
1+
import urllib.request
2+
3+
import pytest
14
import scipp as sc
25

36
from ess.diffraction.peaks import dspacing_peaks_from_cif
47

58

9+
def _cifdb_is_reachable(timeout=5):
10+
try:
11+
req = urllib.request.Request('http://cod.ibt.lt', method="HEAD")
12+
with urllib.request.urlopen(req, timeout=timeout): # noqa: S310
13+
return True
14+
except Exception:
15+
return False
16+
17+
18+
@pytest.mark.skipif(
19+
not _cifdb_is_reachable(), reason='The CIF database can not be reached.'
20+
)
621
def test_retreived_peak_list_has_expected_units():
722
d = dspacing_peaks_from_cif(
823
'codid::9011998',
@@ -12,6 +27,9 @@ def test_retreived_peak_list_has_expected_units():
1227
assert d.unit == 'barn'
1328

1429

30+
@pytest.mark.skipif(
31+
not _cifdb_is_reachable(), reason='The CIF database can not be reached.'
32+
)
1533
def test_intensity_threshold_is_taken_into_account():
1634
d = dspacing_peaks_from_cif(
1735
'codid::9011998',
@@ -26,6 +44,9 @@ def test_intensity_threshold_is_taken_into_account():
2644
assert len(d) == 0
2745

2846

47+
@pytest.mark.skipif(
48+
not _cifdb_is_reachable(), reason='The CIF database can not be reached.'
49+
)
2950
def test_retreived_peak_list_with_temperature_kwarg():
3051
d = dspacing_peaks_from_cif(
3152
'codid::9011998', sc.scalar(50, unit='barn'), uiso_temperature=300

0 commit comments

Comments
 (0)