Skip to content

Commit 5aed46f

Browse files
committed
fix: load chopper settings in all modes - truncate time to limits of event_time_offset
1 parent ed8ba37 commit 5aed46f

File tree

5 files changed

+133
-58
lines changed

5 files changed

+133
-58
lines changed

src/ess/beer/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from .io import load_beer_mcstas
1111
from .workflow import (
12+
BeerMcStasWorkflowPulseShaping,
1213
BeerModMcStasWorkflow,
1314
BeerModMcStasWorkflowKnownPeaks,
1415
default_parameters,
@@ -22,6 +23,7 @@
2223
del importlib
2324

2425
__all__ = [
26+
'BeerMcStasWorkflowPulseShaping',
2527
'BeerModMcStasWorkflow',
2628
'BeerModMcStasWorkflowKnownPeaks',
2729
'__version__',

src/ess/beer/clustering.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from scippneutron.conversion.tof import dspacing_from_tof
33
from scipy.signal import find_peaks, medfilt
44

5+
from .conversions import _t0_estimate, _time_of_arrival
56
from .types import RawDetector, RunType, StreakClusteredData
67

78

@@ -10,12 +11,17 @@ def cluster_events_by_streak(da: RawDetector[RunType]) -> StreakClusteredData[Ru
1011
return sc.DataGroup({k: cluster_events_by_streak(v) for k, v in da.items()})
1112
da = da.copy(deep=False)
1213

13-
# TODO: approximate t0 should depend on chopper information
14-
approximate_t0 = sc.scalar(6e-3, unit='s')
14+
t = _time_of_arrival(
15+
da.coords['event_time_offset'],
16+
da.coords['tc'].to(unit=da.coords['event_time_offset'].unit),
17+
)
18+
approximate_t0 = _t0_estimate(
19+
da.coords['wavelength_estimate'], da.coords['L0'], da.coords['Ltotal']
20+
).to(unit=t.unit)
1521

1622
da.coords['coarse_d'] = dspacing_from_tof(
17-
tof=da.coords['event_time_offset'] - approximate_t0,
18-
Ltotal=da.coords['L0'],
23+
tof=t - approximate_t0,
24+
Ltotal=da.coords['Ltotal'],
1925
two_theta=da.coords['two_theta'],
2026
).to(unit='angstrom')
2127

src/ess/beer/conversions.py

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ def compute_tof_in_each_cluster(
3838

3939
max_distance_from_streak_line = mod_period / 3
4040
sin_theta_L = sc.sin(da.bins.coords['two_theta'] / 2) * da.bins.coords['Ltotal']
41-
t = da.bins.coords['event_time_offset']
41+
t = _time_of_arrival(
42+
da.bins.coords['event_time_offset'],
43+
da.coords['tc'].to(unit=da.bins.coords['event_time_offset'].unit),
44+
)
4245
for _ in range(15):
4346
s, t0 = _linear_regression_by_bin(sin_theta_L, t, da.data)
4447

@@ -77,7 +80,7 @@ def _linear_regression_by_bin(
7780

7881

7982
def _compute_d(
80-
event_time_offset: sc.Variable,
83+
time_of_arrival: sc.Variable,
8184
theta: sc.Variable,
8285
dhkl_list: sc.Variable,
8386
pulse_length: sc.Variable,
@@ -87,15 +90,15 @@ def _compute_d(
8790
given a list of known peaks."""
8891
# Source: https://www2.mcstas.org/download/components/3.4/contrib/NPI_tof_dhkl_detector.comp
8992
sinth = sc.sin(theta)
90-
t = event_time_offset
93+
t = time_of_arrival
9194

9295
d = sc.empty(dims=sinth.dims, shape=sinth.shape, unit=dhkl_list[0].unit)
9396
d[:] = sc.scalar(float('nan'), unit=dhkl_list[0].unit)
9497
dtfound = sc.empty(dims=sinth.dims, shape=sinth.shape, dtype='float64', unit=t.unit)
9598
dtfound[:] = sc.scalar(float('nan'), unit=t.unit)
9699

97100
const = (2 * sinth * L0 / (scipp.constants.h / scipp.constants.m_n)).to(
98-
unit=f'{event_time_offset.unit}/angstrom'
101+
unit=f'{time_of_arrival.unit}/angstrom'
99102
)
100103

101104
for dhkl in dhkl_list:
@@ -112,8 +115,18 @@ def _compute_d(
112115
return d
113116

114117

115-
def _tof_from_dhkl(
118+
def _time_of_arrival(
116119
event_time_offset: sc.Variable,
120+
tc: sc.Variable,
121+
):
122+
_eto = event_time_offset
123+
T = sc.scalar(1 / 14, unit='s').to(unit=_eto.unit)
124+
tc = tc.to(unit=_eto.unit)
125+
return sc.where(_eto >= tc % T, _eto, _eto + T)
126+
127+
128+
def _tof_from_dhkl(
129+
time_of_arrival: sc.Variable,
117130
theta: sc.Variable,
118131
coarse_dhkl: sc.Variable,
119132
Ltotal: sc.Variable,
@@ -123,25 +136,25 @@ def _tof_from_dhkl(
123136
'''Computes tof for BEER given the dhkl peak that the event belongs to'''
124137
# Source: https://www2.mcstas.org/download/components/3.4/contrib/NPI_tof_dhkl_detector.comp
125138
# tref = 2 * d_hkl * sin(theta) / hm * Ltotal
126-
# tc = event_time_zero - time0 - tref
139+
# tc = time_of_arrival - time0 - tref
127140
# dt = floor(tc / mod_period + 0.5) * mod_period + time0
128-
# tof = event_time_offset - dt
141+
# tof = time_of_arrival - dt
129142
c = (-2 * 1.0 / (scipp.constants.h / scipp.constants.m_n)).to(
130-
unit=f'{event_time_offset.unit}/m/angstrom'
143+
unit=f'{time_of_arrival.unit}/m/angstrom'
131144
)
132145
out = sc.sin(theta)
133146
out *= c
134147
out *= coarse_dhkl
135148
out *= Ltotal
136-
out += event_time_offset
149+
out += time_of_arrival
137150
out -= time0
138151
out /= mod_period
139152
out += 0.5
140153
sc.floor(out, out=out)
141154
out *= mod_period
142155
out += time0
143156
out *= -1
144-
out += event_time_offset
157+
out += time_of_arrival
145158
return out
146159

147160

@@ -152,10 +165,10 @@ def tof_from_known_dhkl_graph(
152165
dhkl_list: DHKLList,
153166
) -> TofCoordTransformGraph:
154167
def _compute_coarse_dspacing(
155-
event_time_offset,
168+
time_of_arrival: sc.Variable,
156169
theta: sc.Variable,
157170
pulse_length: sc.Variable,
158-
L0,
171+
L0: sc.Variable,
159172
):
160173
'''To capture dhkl_list, otherwise it causes an error when
161174
``.transform_coords`` is called unless it is called with
@@ -164,7 +177,7 @@ def _compute_coarse_dspacing(
164177
with dimensions not present on the data.
165178
'''
166179
return _compute_d(
167-
event_time_offset=event_time_offset,
180+
time_of_arrival=time_of_arrival,
168181
theta=theta,
169182
pulse_length=pulse_length,
170183
L0=L0,
@@ -176,19 +189,52 @@ def _compute_coarse_dspacing(
176189
'mod_period': lambda: mod_period,
177190
'time0': lambda: time0,
178191
'tof': _tof_from_dhkl,
192+
'time_of_arrival': _time_of_arrival,
179193
'coarse_dhkl': _compute_coarse_dspacing,
180194
'theta': lambda two_theta: two_theta / 2,
181195
}
182196

183197

184-
def compute_tof_from_known_peaks(
198+
def _t0_estimate(
199+
wavelength_estimate: sc.Variable,
200+
L0: sc.Variable,
201+
Ltotal: sc.Variable,
202+
) -> sc.Variable:
203+
return (
204+
sc.constants.m_n
205+
/ sc.constants.h
206+
* wavelength_estimate
207+
* (L0 - Ltotal).to(unit=wavelength_estimate.unit)
208+
).to(unit='s')
209+
210+
211+
def _tof_from_t0_estimate(
212+
time_of_arrival: sc.Variable,
213+
t0_estimate: sc.Variable,
214+
) -> sc.Variable:
215+
return time_of_arrival - t0_estimate
216+
217+
218+
def tof_from_t0_estimate() -> TofCoordTransformGraph:
219+
return {
220+
't0_estimate': _t0_estimate,
221+
'tof': _tof_from_t0_estimate,
222+
'time_of_arrival': _time_of_arrival,
223+
}
224+
225+
226+
def compute_tof(
185227
da: RawDetector[RunType], graph: TofCoordTransformGraph
186228
) -> TofDetector[RunType]:
187229
return da.transform_coords(('tof',), graph=graph)
188230

189231

190232
convert_from_known_peaks_providers = (
191233
tof_from_known_dhkl_graph,
192-
compute_tof_from_known_peaks,
234+
compute_tof,
235+
)
236+
convert_pulse_shaping = (
237+
tof_from_t0_estimate,
238+
compute_tof,
193239
)
194240
providers = (compute_tof_in_each_cluster,)

src/ess/beer/io.py

Lines changed: 50 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -48,22 +48,32 @@ def _load_beer_mcstas(f, bank=1):
4848
positions = {
4949
name: f'/entry1/instrument/components/{key}/Position'
5050
for key in f['/entry1/instrument/components']
51-
for name in ['sampleMantid', 'PSC1', 'PSC2', 'PSC3', 'MCA', 'MCC']
51+
for name in ['sampleMantid', 'PSC1', 'PSC2', 'PSC3', 'MCA', 'MCB', 'MCC']
5252
if name in key
5353
}
54-
data, events, params, sample_pos, psc1_pos, psc2_pos, psc3_pos, mca_pos, mcc_pos = (
55-
_load_h5(
56-
f,
57-
f'NXentry/NXdetector/bank{bank:02}_events_dat_list_p_x_y_n_id_t',
58-
f'NXentry/NXdetector/bank{bank:02}_events_dat_list_p_x_y_n_id_t/events',
59-
'NXentry/simulation/Param',
60-
positions['sampleMantid'],
61-
positions['PSC1'],
62-
positions['PSC2'],
63-
positions['PSC3'],
64-
positions['MCA'],
65-
positions['MCC'],
66-
)
54+
(
55+
data,
56+
events,
57+
params,
58+
sample_pos,
59+
psc1_pos,
60+
psc2_pos,
61+
psc3_pos,
62+
mca_pos,
63+
mcb_pos,
64+
mcc_pos,
65+
) = _load_h5(
66+
f,
67+
f'NXentry/NXdetector/bank{bank:02}_events_dat_list_p_x_y_n_id_t',
68+
f'NXentry/NXdetector/bank{bank:02}_events_dat_list_p_x_y_n_id_t/events',
69+
'NXentry/simulation/Param',
70+
positions['sampleMantid'],
71+
positions['PSC1'],
72+
positions['PSC2'],
73+
positions['PSC3'],
74+
positions['MCA'],
75+
positions['MCB'],
76+
positions['MCC'],
6777
)
6878

6979
events = events[()]
@@ -86,7 +96,10 @@ def _load_beer_mcstas(f, bank=1):
8696
if k in ('mode', 'sample_filename', 'lambda'):
8797
da.coords[k] = sc.scalar(v)
8898

89-
if da.coords['lambda'].value == '0':
99+
if 'lambda' in da.coords:
100+
da.coords['wavelength_estimate'] = da.coords.pop('lambda')
101+
102+
if da.coords['wavelength_estimate'].value == '0':
90103
if da.coords['mode'].value in [
91104
'0',
92105
'3',
@@ -100,28 +113,28 @@ def _load_beer_mcstas(f, bank=1):
100113
'15',
101114
'16',
102115
]:
103-
da.coords['lambda'] = sc.scalar(2.1, unit='angstrom')
116+
da.coords['wavelength_estimate'] = sc.scalar(2.1, unit='angstrom')
104117
elif da.coords['mode'].value in ['1', '2']:
105-
da.coords['lambda'] = sc.scalar(3.1, unit='angstrom')
118+
da.coords['wavelength_estimate'] = sc.scalar(3.1, unit='angstrom')
106119
elif da.coords['mode'].value == '11':
107-
da.coords['lambda'] = sc.scalar(3.0, unit='angstrom')
120+
da.coords['wavelength_estimate'] = sc.scalar(3.0, unit='angstrom')
108121
elif da.coords['mode'].value == '12':
109-
da.coords['lambda'] = sc.scalar(3.5, unit='angstrom')
122+
da.coords['wavelength_estimate'] = sc.scalar(3.5, unit='angstrom')
110123
elif da.coords['mode'].value == '13':
111-
da.coords['lambda'] = sc.scalar(6.0, unit='angstrom')
124+
da.coords['wavelength_estimate'] = sc.scalar(6.0, unit='angstrom')
112125
elif da.coords['mode'].value == '14':
113-
da.coords['lambda'] = sc.scalar(4.0, unit='angstrom')
126+
da.coords['wavelength_estimate'] = sc.scalar(4.0, unit='angstrom')
114127
else:
115-
da.coords['lambda'] = sc.scalar(
116-
float(da.coords['lambda'].value), unit='angstrom'
128+
da.coords['wavelength_estimate'] = sc.scalar(
129+
float(da.coords['wavelength_estimate'].value), unit='angstrom'
117130
)
118131

119132
da.coords['sample_position'] = sc.vector(sample_pos[:], unit='m')
120133
da.coords['detector_position'] = sc.vector(
121134
list(map(float, da.coords.pop('position').value.split(' '))), unit='m'
122135
)
123136

124-
if da.coords['mode'].value in ['0', '1', '2', '11', '16']:
137+
if da.coords['mode'].value in ['0', '1', '2', '11']:
125138
da.coords['chopper_position'] = sc.vector([0.0, 0.0, 0.0], unit='m')
126139
elif da.coords['mode'].value in ['3', '4', '12', '13', '15']:
127140
da.coords['chopper_position'] = sc.vector(
@@ -132,9 +145,13 @@ def _load_beer_mcstas(f, bank=1):
132145
0.5 * (psc1_pos[:] + psc2_pos[:]), unit='m'
133146
)
134147
elif da.coords['mode'].value in ['7', '8', '9', '10']:
135-
da.coords['chopper_position'] = sc.vector(0.5 * mca_pos[:], unit='m')
148+
da.coords['chopper_position'] = sc.vector(mca_pos[:], unit='m')
136149
elif da.coords['mode'].value == '14':
137150
da.coords['chopper_position'] = sc.vector(mcc_pos[:], unit='m')
151+
elif da.coords['mode'].value == '16':
152+
da.coords['chopper_position'] = sc.vector(
153+
0.5 * (mca_pos[:] + mcb_pos[:]), unit='m'
154+
)
138155
else:
139156
raise ValueError(f'Unkonwn chopper mode {da.coords["mode"].value}.')
140157

@@ -143,7 +160,6 @@ def _load_beer_mcstas(f, bank=1):
143160
da.coords['t'].unit = 's'
144161

145162
z = sc.norm(da.coords['detector_position'] - da.coords['sample_position'])
146-
# z = da.coords['position'].fields.z - da.coords['sample_position'].fields.z
147163
L1 = sc.norm(da.coords['sample_position'] - da.coords['chopper_position'])
148164
L2 = sc.sqrt(da.coords['x'] ** 2 + da.coords['y'] ** 2 + z**2)
149165

@@ -159,17 +175,15 @@ def _load_beer_mcstas(f, bank=1):
159175
da.coords.pop('y')
160176
da.coords.pop('n')
161177

162-
# expression of temporal offset delta_t checked for pulse shaping mode: 4,5,6
163-
delta_t = (
178+
t = da.coords.pop('t')
179+
da.coords['event_time_offset'] = t % sc.scalar(1 / 14, unit='s').to(unit=t.unit)
180+
da.coords["tc"] = (
164181
sc.constants.m_n
165182
/ sc.constants.h
166-
* da.coords['lambda']
167-
* sc.norm(da.coords['chopper_position']).to(unit='angstrom')
168-
).to(unit='s')
183+
* da.coords['wavelength_estimate']
184+
* da.coords['L0'].min().to(unit='angstrom')
185+
).to(unit='s') - sc.scalar(1 / 14, unit='s') / 2
169186

170-
t = da.coords.pop('t')
171-
da.coords['event_time_offset'] = t % sc.scalar(1 / 14, unit=t.unit)
172-
da.coords["approximate_tof"] = da.coords['event_time_offset'] - delta_t
173187
return da
174188

175189

@@ -218,10 +232,8 @@ def mcstas_chopper_delay_from_mode(
218232
da: RawDetector[SampleRun],
219233
) -> WavelengthDefinitionChopperDelay:
220234
mode = next(iter(d.coords['mode'] for d in da.values())).value
221-
if mode in ('7', '8'):
222-
return sc.scalar(0.00245635, unit='s')
223-
if mode in ('9', '10'):
224-
return sc.scalar(0.0033730158730158727, unit='s')
235+
if mode in ('7', '8', '9', '10'):
236+
return sc.scalar(0.0024730158730158727, unit='s')
225237
if mode == '16':
226238
return sc.scalar(0.000876984126984127, unit='s')
227239
raise ValueError(f'Mode {mode} is not known.')

src/ess/beer/workflow.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import scipp as sc
55

66
from .clustering import providers as clustering_providers
7-
from .conversions import convert_from_known_peaks_providers
7+
from .conversions import convert_from_known_peaks_providers, convert_pulse_shaping
88
from .conversions import providers as conversion_providers
99
from .io import mcstas_providers
1010
from .types import (
@@ -41,3 +41,12 @@ def BeerModMcStasWorkflowKnownPeaks():
4141
params=default_parameters,
4242
constraints={RunType: (SampleRun,)},
4343
)
44+
45+
46+
def BeerMcStasWorkflowPulseShaping():
47+
'''Workflow to process BEER (pulse shaping modes) McStas files'''
48+
return sl.Pipeline(
49+
(*mcstas_providers, *convert_pulse_shaping),
50+
params=default_parameters,
51+
constraints={RunType: (SampleRun,)},
52+
)

0 commit comments

Comments
 (0)