essreduce 25.5.2__py3-none-any.whl → 25.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ess/reduce/time_of_flight/__init__.py +22 -25
- ess/reduce/time_of_flight/eto_to_tof.py +10 -344
- ess/reduce/time_of_flight/lut.py +478 -0
- ess/reduce/time_of_flight/resample.py +97 -0
- ess/reduce/time_of_flight/types.py +0 -131
- ess/reduce/time_of_flight/workflow.py +10 -27
- {essreduce-25.5.2.dist-info → essreduce-25.7.0.dist-info}/METADATA +1 -1
- {essreduce-25.5.2.dist-info → essreduce-25.7.0.dist-info}/RECORD +12 -12
- {essreduce-25.5.2.dist-info → essreduce-25.7.0.dist-info}/WHEEL +1 -1
- ess/reduce/time_of_flight/simulation.py +0 -108
- ess/reduce/time_of_flight/to_events.py +0 -111
- {essreduce-25.5.2.dist-info → essreduce-25.7.0.dist-info}/entry_points.txt +0 -0
- {essreduce-25.5.2.dist-info → essreduce-25.7.0.dist-info}/licenses/LICENSE +0 -0
- {essreduce-25.5.2.dist-info → essreduce-25.7.0.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# SPDX-License-Identifier: BSD-3-Clause
|
|
2
2
|
# Copyright (c) 2025 Scipp contributors (https://github.com/scipp)
|
|
3
3
|
|
|
4
|
-
from dataclasses import dataclass
|
|
5
4
|
from typing import NewType
|
|
6
5
|
|
|
7
6
|
import sciline as sl
|
|
@@ -9,81 +8,6 @@ import scipp as sc
|
|
|
9
8
|
|
|
10
9
|
from ..nexus.types import MonitorType, RunType
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
@dataclass
|
|
14
|
-
class SimulationResults:
|
|
15
|
-
"""
|
|
16
|
-
Results of a time-of-flight simulation used to create a lookup table.
|
|
17
|
-
|
|
18
|
-
The results (apart from ``distance``) should be flat lists (1d arrays) of length N
|
|
19
|
-
where N is the number of neutrons, containing the properties of the neutrons in the
|
|
20
|
-
simulation.
|
|
21
|
-
|
|
22
|
-
Parameters
|
|
23
|
-
----------
|
|
24
|
-
time_of_arrival:
|
|
25
|
-
Time of arrival of the neutrons at the position where the events were recorded
|
|
26
|
-
(1d array of size N).
|
|
27
|
-
speed:
|
|
28
|
-
Speed of the neutrons, typically derived from the wavelength of the neutrons
|
|
29
|
-
(1d array of size N).
|
|
30
|
-
wavelength:
|
|
31
|
-
Wavelength of the neutrons (1d array of size N).
|
|
32
|
-
weight:
|
|
33
|
-
Weight/probability of the neutrons (1d array of size N).
|
|
34
|
-
distance:
|
|
35
|
-
Distance from the source to the position where the events were recorded
|
|
36
|
-
(single value; we assume all neutrons were recorded at the same position).
|
|
37
|
-
For a ``tof`` simulation, this is just the position of the detector where the
|
|
38
|
-
events are recorded. For a ``McStas`` simulation, this is the distance between
|
|
39
|
-
the source and the event monitor.
|
|
40
|
-
"""
|
|
41
|
-
|
|
42
|
-
time_of_arrival: sc.Variable
|
|
43
|
-
speed: sc.Variable
|
|
44
|
-
wavelength: sc.Variable
|
|
45
|
-
weight: sc.Variable
|
|
46
|
-
distance: sc.Variable
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
NumberOfSimulatedNeutrons = NewType("NumberOfSimulatedNeutrons", int)
|
|
50
|
-
"""
|
|
51
|
-
Number of neutrons simulated in the simulation that is used to create the lookup table.
|
|
52
|
-
This is typically a large number, e.g., 1e6 or 1e7.
|
|
53
|
-
"""
|
|
54
|
-
|
|
55
|
-
LtotalRange = NewType("LtotalRange", tuple[sc.Variable, sc.Variable])
|
|
56
|
-
"""
|
|
57
|
-
Range (min, max) of the total length of the flight path from the source to the detector.
|
|
58
|
-
This is used to create the lookup table to compute the neutron time-of-flight.
|
|
59
|
-
Note that the resulting table will extend slightly beyond this range, as the supplied
|
|
60
|
-
range is not necessarily a multiple of the distance resolution.
|
|
61
|
-
|
|
62
|
-
Note also that the range of total flight paths is supplied manually to the workflow
|
|
63
|
-
instead of being read from the input data, as it allows us to compute the expensive part
|
|
64
|
-
of the workflow in advance (the lookup table) and does not need to be repeated for each
|
|
65
|
-
run, or for new data coming in in the case of live data collection.
|
|
66
|
-
"""
|
|
67
|
-
|
|
68
|
-
DistanceResolution = NewType("DistanceResolution", sc.Variable)
|
|
69
|
-
"""
|
|
70
|
-
Step size of the distance axis in the lookup table.
|
|
71
|
-
Should be a single scalar value with a unit of length.
|
|
72
|
-
This is typically of the order of 1-10 cm.
|
|
73
|
-
"""
|
|
74
|
-
|
|
75
|
-
TimeResolution = NewType("TimeResolution", sc.Variable)
|
|
76
|
-
"""
|
|
77
|
-
Step size of the event_time_offset axis in the lookup table.
|
|
78
|
-
This is basically the 'time-of-flight' resolution of the detector.
|
|
79
|
-
Should be a single scalar value with a unit of time.
|
|
80
|
-
This is typically of the order of 0.1-0.5 ms.
|
|
81
|
-
|
|
82
|
-
Since the event_time_offset range needs to span exactly one pulse period, the final
|
|
83
|
-
resolution in the lookup table will be at least the supplied value here, but may be
|
|
84
|
-
smaller if the pulse period is not an integer multiple of the time resolution.
|
|
85
|
-
"""
|
|
86
|
-
|
|
87
11
|
TimeOfFlightLookupTableFilename = NewType("TimeOfFlightLookupTableFilename", str)
|
|
88
12
|
"""Filename of the time-of-flight lookup table."""
|
|
89
13
|
|
|
@@ -93,21 +17,6 @@ TimeOfFlightLookupTable = NewType("TimeOfFlightLookupTable", sc.DataArray)
|
|
|
93
17
|
Lookup table giving time-of-flight as a function of distance and time of arrival.
|
|
94
18
|
"""
|
|
95
19
|
|
|
96
|
-
LookupTableRelativeErrorThreshold = NewType("LookupTableRelativeErrorThreshold", float)
|
|
97
|
-
"""
|
|
98
|
-
Threshold for the relative standard deviation (coefficient of variation) of the
|
|
99
|
-
projected time-of-flight above which values are masked.
|
|
100
|
-
"""
|
|
101
|
-
|
|
102
|
-
PulsePeriod = NewType("PulsePeriod", sc.Variable)
|
|
103
|
-
"""
|
|
104
|
-
Period of the source pulses, i.e., time between consecutive pulse starts.
|
|
105
|
-
"""
|
|
106
|
-
|
|
107
|
-
PulseStride = NewType("PulseStride", int)
|
|
108
|
-
"""
|
|
109
|
-
Stride of used pulses. Usually 1, but may be a small integer when pulse-skipping.
|
|
110
|
-
"""
|
|
111
20
|
|
|
112
21
|
PulseStrideOffset = NewType("PulseStrideOffset", int | None)
|
|
113
22
|
"""
|
|
@@ -130,43 +39,3 @@ class DetectorTofData(sl.Scope[RunType, sc.DataArray], sc.DataArray):
|
|
|
130
39
|
|
|
131
40
|
class MonitorTofData(sl.Scope[RunType, MonitorType, sc.DataArray], sc.DataArray):
|
|
132
41
|
"""Monitor data with time-of-flight coordinate."""
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
class ResampledDetectorTofData(sl.Scope[RunType, sc.DataArray], sc.DataArray):
|
|
136
|
-
"""
|
|
137
|
-
Histogrammed detector data with time-of-flight coordinate, that has been resampled.
|
|
138
|
-
|
|
139
|
-
Histogrammed data that has been converted to `tof` will typically have
|
|
140
|
-
unsorted bin edges (due to either wrapping of `time_of_flight` or wavelength
|
|
141
|
-
overlap between subframes).
|
|
142
|
-
We thus resample the data to ensure that the bin edges are sorted.
|
|
143
|
-
It makes use of the ``to_events`` helper which generates a number of events in each
|
|
144
|
-
bin with a uniform distribution. The new events are then histogrammed using a set of
|
|
145
|
-
sorted bin edges to yield a new histogram with sorted bin edges.
|
|
146
|
-
|
|
147
|
-
WARNING:
|
|
148
|
-
This function is highly experimental, has limitations and should be used with
|
|
149
|
-
caution. It is a workaround to the issue that rebinning data with unsorted bin
|
|
150
|
-
edges is not supported in scipp.
|
|
151
|
-
"""
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
class ResampledMonitorTofData(
|
|
155
|
-
sl.Scope[RunType, MonitorType, sc.DataArray], sc.DataArray
|
|
156
|
-
):
|
|
157
|
-
"""
|
|
158
|
-
Histogrammed monitor data with time-of-flight coordinate, that has been resampled.
|
|
159
|
-
|
|
160
|
-
Histogrammed data that has been converted to `tof` will typically have
|
|
161
|
-
unsorted bin edges (due to either wrapping of `time_of_flight` or wavelength
|
|
162
|
-
overlap between subframes).
|
|
163
|
-
We thus resample the data to ensure that the bin edges are sorted.
|
|
164
|
-
It makes use of the ``to_events`` helper which generates a number of events in each
|
|
165
|
-
bin with a uniform distribution. The new events are then histogrammed using a set of
|
|
166
|
-
sorted bin edges to yield a new histogram with sorted bin edges.
|
|
167
|
-
|
|
168
|
-
WARNING:
|
|
169
|
-
This function is highly experimental, has limitations and should be used with
|
|
170
|
-
caution. It is a workaround to the issue that rebinning data with unsorted bin
|
|
171
|
-
edges is not supported in scipp.
|
|
172
|
-
"""
|
|
@@ -1,22 +1,17 @@
|
|
|
1
1
|
# SPDX-License-Identifier: BSD-3-Clause
|
|
2
2
|
# Copyright (c) 2025 Scipp contributors (https://github.com/scipp)
|
|
3
3
|
from collections.abc import Iterable
|
|
4
|
-
from enum import Enum, auto
|
|
5
4
|
|
|
6
5
|
import sciline
|
|
7
6
|
import scipp as sc
|
|
8
7
|
|
|
9
8
|
from ..nexus import GenericNeXusWorkflow
|
|
10
|
-
from . import eto_to_tof
|
|
11
|
-
from .types import
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
FILE = auto() # From file
|
|
18
|
-
TOF = auto() # Computed with 'tof' package from chopper settings
|
|
19
|
-
MCSTAS = auto() # McStas simulation (not implemented yet)
|
|
9
|
+
from . import eto_to_tof
|
|
10
|
+
from .types import (
|
|
11
|
+
PulseStrideOffset,
|
|
12
|
+
TimeOfFlightLookupTable,
|
|
13
|
+
TimeOfFlightLookupTableFilename,
|
|
14
|
+
)
|
|
20
15
|
|
|
21
16
|
|
|
22
17
|
def load_tof_lookup_table(
|
|
@@ -29,11 +24,11 @@ def GenericTofWorkflow(
|
|
|
29
24
|
*,
|
|
30
25
|
run_types: Iterable[sciline.typing.Key],
|
|
31
26
|
monitor_types: Iterable[sciline.typing.Key],
|
|
32
|
-
tof_lut_provider: TofLutProvider = TofLutProvider.FILE,
|
|
33
27
|
) -> sciline.Pipeline:
|
|
34
28
|
"""
|
|
35
29
|
Generic workflow for computing the neutron time-of-flight for detector and monitor
|
|
36
30
|
data.
|
|
31
|
+
|
|
37
32
|
This workflow builds on the ``GenericNeXusWorkflow`` and computes time-of-flight
|
|
38
33
|
from a lookup table that is created from the chopper settings, detector Ltotal and
|
|
39
34
|
the neutron time-of-arrival.
|
|
@@ -58,11 +53,6 @@ def GenericTofWorkflow(
|
|
|
58
53
|
List of monitor types to include in the workflow.
|
|
59
54
|
Constrains the possible values of :class:`ess.reduce.nexus.types.MonitorType`
|
|
60
55
|
and :class:`ess.reduce.nexus.types.Component`.
|
|
61
|
-
tof_lut_provider:
|
|
62
|
-
Specifies how the time-of-flight lookup table is provided:
|
|
63
|
-
- FILE: Read from a file
|
|
64
|
-
- TOF: Computed from chopper settings using the 'tof' package
|
|
65
|
-
- MCSTAS: From McStas simulation (not implemented yet)
|
|
66
56
|
|
|
67
57
|
Returns
|
|
68
58
|
-------
|
|
@@ -74,16 +64,9 @@ def GenericTofWorkflow(
|
|
|
74
64
|
for provider in eto_to_tof.providers():
|
|
75
65
|
wf.insert(provider)
|
|
76
66
|
|
|
77
|
-
|
|
78
|
-
wf.insert(load_tof_lookup_table)
|
|
79
|
-
else:
|
|
80
|
-
wf.insert(eto_to_tof.compute_tof_lookup_table)
|
|
81
|
-
if tof_lut_provider == TofLutProvider.TOF:
|
|
82
|
-
wf.insert(simulation.simulate_chopper_cascade_using_tof)
|
|
83
|
-
if tof_lut_provider == TofLutProvider.MCSTAS:
|
|
84
|
-
raise NotImplementedError("McStas simulation not implemented yet")
|
|
67
|
+
wf.insert(load_tof_lookup_table)
|
|
85
68
|
|
|
86
|
-
|
|
87
|
-
|
|
69
|
+
# Default parameters
|
|
70
|
+
wf[PulseStrideOffset] = None
|
|
88
71
|
|
|
89
72
|
return wf
|
|
@@ -18,15 +18,15 @@ ess/reduce/nexus/json_nexus.py,sha256=QrVc0p424nZ5dHX9gebAJppTw6lGZq9404P_OFl1gi
|
|
|
18
18
|
ess/reduce/nexus/types.py,sha256=DE82JnbgpTlQnt7UN2a2Gur2N9QupV3CDL9j4Iy4lsE,9178
|
|
19
19
|
ess/reduce/nexus/workflow.py,sha256=Ytt80-muk5EiXmip890ahb_m5DQqlTGRQUyaTVXRNzo,24568
|
|
20
20
|
ess/reduce/scripts/grow_nexus.py,sha256=hET3h06M0xlJd62E3palNLFvJMyNax2kK4XyJcOhl-I,3387
|
|
21
|
-
ess/reduce/time_of_flight/__init__.py,sha256=
|
|
22
|
-
ess/reduce/time_of_flight/eto_to_tof.py,sha256=
|
|
21
|
+
ess/reduce/time_of_flight/__init__.py,sha256=bhteT_xvkRYBh_oeJZTzCtiPg0WSW_TRrtm_xzGbzw4,1441
|
|
22
|
+
ess/reduce/time_of_flight/eto_to_tof.py,sha256=pkxtj1Gg0b2aF9Ryr2vL8PeL8RdujuzWbcp1L4TMghU,14712
|
|
23
23
|
ess/reduce/time_of_flight/fakes.py,sha256=0gtbSX3ZQilaM4ZP5dMr3fqbnhpyoVsZX2YEb8GgREE,4489
|
|
24
24
|
ess/reduce/time_of_flight/interpolator_numba.py,sha256=wh2YS3j2rOu30v1Ok3xNHcwS7t8eEtZyZvbfXOCtgrQ,3835
|
|
25
25
|
ess/reduce/time_of_flight/interpolator_scipy.py,sha256=_InoAPuMm2qhJKZQBAHOGRFqtvvuQ8TStoN7j_YgS4M,1853
|
|
26
|
-
ess/reduce/time_of_flight/
|
|
27
|
-
ess/reduce/time_of_flight/
|
|
28
|
-
ess/reduce/time_of_flight/types.py,sha256=
|
|
29
|
-
ess/reduce/time_of_flight/workflow.py,sha256=
|
|
26
|
+
ess/reduce/time_of_flight/lut.py,sha256=ylZbnf6LeOUzkBkUbLCudeqVfh-Gtf9M-z8PS-l9Db4,18719
|
|
27
|
+
ess/reduce/time_of_flight/resample.py,sha256=Opmi-JA4zNH725l9VB99U4O9UlM37f5ACTCGtwBcows,3718
|
|
28
|
+
ess/reduce/time_of_flight/types.py,sha256=EroKBxi4WUErNx8d200jh8kqkhwtjAGKIV7PvBUwAJs,1310
|
|
29
|
+
ess/reduce/time_of_flight/workflow.py,sha256=mkgESvQ5Yt3CyAsa1iewkjBOHUqrHm5rRc1EhOQRewQ,2213
|
|
30
30
|
ess/reduce/widgets/__init__.py,sha256=SoSHBv8Dc3QXV9HUvPhjSYWMwKTGYZLpsWwsShIO97Q,5325
|
|
31
31
|
ess/reduce/widgets/_base.py,sha256=_wN3FOlXgx_u0c-A_3yyoIH-SdUvDENGgquh9S-h5GI,4852
|
|
32
32
|
ess/reduce/widgets/_binedges_widget.py,sha256=ZCQsGjYHnJr9GFUn7NjoZc1CdsnAzm_fMzyF-fTKKVY,2785
|
|
@@ -39,9 +39,9 @@ ess/reduce/widgets/_spinner.py,sha256=2VY4Fhfa7HMXox2O7UbofcdKsYG-AJGrsgGJB85nDX
|
|
|
39
39
|
ess/reduce/widgets/_string_widget.py,sha256=iPAdfANyXHf-nkfhgkyH6gQDklia0LebLTmwi3m-iYQ,1482
|
|
40
40
|
ess/reduce/widgets/_switchable_widget.py,sha256=fjKz99SKLhIF1BLgGVBSKKn3Lu_jYBwDYGeAjbJY3Q8,2390
|
|
41
41
|
ess/reduce/widgets/_vector_widget.py,sha256=aTaBqCFHZQhrIoX6-sSqFWCPePEW8HQt5kUio8jP1t8,1203
|
|
42
|
-
essreduce-25.
|
|
43
|
-
essreduce-25.
|
|
44
|
-
essreduce-25.
|
|
45
|
-
essreduce-25.
|
|
46
|
-
essreduce-25.
|
|
47
|
-
essreduce-25.
|
|
42
|
+
essreduce-25.7.0.dist-info/licenses/LICENSE,sha256=nVEiume4Qj6jMYfSRjHTM2jtJ4FGu0g-5Sdh7osfEYw,1553
|
|
43
|
+
essreduce-25.7.0.dist-info/METADATA,sha256=3qNSqqDpvL6iuZoOlcFiI1P6Vyhse0kfAn7K3oeEhxw,3768
|
|
44
|
+
essreduce-25.7.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
45
|
+
essreduce-25.7.0.dist-info/entry_points.txt,sha256=PMZOIYzCifHMTe4pK3HbhxUwxjFaZizYlLD0td4Isb0,66
|
|
46
|
+
essreduce-25.7.0.dist-info/top_level.txt,sha256=0JxTCgMKPLKtp14wb1-RKisQPQWX7i96innZNvHBr-s,4
|
|
47
|
+
essreduce-25.7.0.dist-info/RECORD,,
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: BSD-3-Clause
|
|
2
|
-
# Copyright (c) 2025 Scipp contributors (https://github.com/scipp)
|
|
3
|
-
from collections.abc import Mapping
|
|
4
|
-
|
|
5
|
-
import scipp as sc
|
|
6
|
-
import scippnexus as snx
|
|
7
|
-
from scippneutron.chopper import DiskChopper
|
|
8
|
-
|
|
9
|
-
from ..nexus.types import DiskChoppers, Position, SampleRun
|
|
10
|
-
from .types import NumberOfSimulatedNeutrons, SimulationResults
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def simulate_beamline(
|
|
14
|
-
*,
|
|
15
|
-
choppers: Mapping[str, DiskChopper],
|
|
16
|
-
source_position: sc.Variable,
|
|
17
|
-
neutrons: int = 1_000_000,
|
|
18
|
-
pulses: int = 1,
|
|
19
|
-
seed: int | None = None,
|
|
20
|
-
facility: str = 'ess',
|
|
21
|
-
) -> SimulationResults:
|
|
22
|
-
"""
|
|
23
|
-
Simulate a pulse of neutrons propagating through a chopper cascade using the
|
|
24
|
-
``tof`` package (https://tof.readthedocs.io).
|
|
25
|
-
|
|
26
|
-
Parameters
|
|
27
|
-
----------
|
|
28
|
-
choppers:
|
|
29
|
-
A dict of DiskChopper objects representing the choppers in the beamline. See
|
|
30
|
-
https://scipp.github.io/scippneutron/user-guide/chopper/processing-nexus-choppers.html#Build-DiskChopper
|
|
31
|
-
for more information.
|
|
32
|
-
source_position:
|
|
33
|
-
A scalar variable with ``dtype=vector3`` that defines the source position.
|
|
34
|
-
Must be in the same coordinate system as the choppers' axle positions.
|
|
35
|
-
neutrons:
|
|
36
|
-
Number of neutrons to simulate.
|
|
37
|
-
pulses:
|
|
38
|
-
Number of pulses to simulate.
|
|
39
|
-
seed:
|
|
40
|
-
Seed for the random number generator used in the simulation.
|
|
41
|
-
facility:
|
|
42
|
-
Facility where the experiment is performed.
|
|
43
|
-
"""
|
|
44
|
-
import tof
|
|
45
|
-
|
|
46
|
-
tof_choppers = [
|
|
47
|
-
tof.Chopper(
|
|
48
|
-
frequency=abs(ch.frequency),
|
|
49
|
-
direction=tof.AntiClockwise
|
|
50
|
-
if (ch.frequency.value > 0.0)
|
|
51
|
-
else tof.Clockwise,
|
|
52
|
-
open=ch.slit_begin,
|
|
53
|
-
close=ch.slit_end,
|
|
54
|
-
phase=abs(ch.phase),
|
|
55
|
-
distance=sc.norm(
|
|
56
|
-
ch.axle_position - source_position.to(unit=ch.axle_position.unit)
|
|
57
|
-
),
|
|
58
|
-
name=name,
|
|
59
|
-
)
|
|
60
|
-
for name, ch in choppers.items()
|
|
61
|
-
]
|
|
62
|
-
source = tof.Source(facility=facility, neutrons=neutrons, pulses=pulses, seed=seed)
|
|
63
|
-
if not tof_choppers:
|
|
64
|
-
events = source.data.squeeze().flatten(to='event')
|
|
65
|
-
return SimulationResults(
|
|
66
|
-
time_of_arrival=events.coords["birth_time"],
|
|
67
|
-
speed=events.coords["speed"],
|
|
68
|
-
wavelength=events.coords["wavelength"],
|
|
69
|
-
weight=events.data,
|
|
70
|
-
distance=0.0 * sc.units.m,
|
|
71
|
-
)
|
|
72
|
-
model = tof.Model(source=source, choppers=tof_choppers)
|
|
73
|
-
results = model.run()
|
|
74
|
-
# Find name of the furthest chopper in tof_choppers
|
|
75
|
-
furthest_chopper = max(tof_choppers, key=lambda c: c.distance)
|
|
76
|
-
events = results[furthest_chopper.name].data.squeeze().flatten(to='event')
|
|
77
|
-
events = events[
|
|
78
|
-
~(events.masks["blocked_by_others"] | events.masks["blocked_by_me"])
|
|
79
|
-
]
|
|
80
|
-
return SimulationResults(
|
|
81
|
-
time_of_arrival=events.coords["toa"],
|
|
82
|
-
speed=events.coords["speed"],
|
|
83
|
-
wavelength=events.coords["wavelength"],
|
|
84
|
-
weight=events.data,
|
|
85
|
-
distance=furthest_chopper.distance,
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def simulate_chopper_cascade_using_tof(
|
|
90
|
-
choppers: DiskChoppers[SampleRun],
|
|
91
|
-
neutrons: NumberOfSimulatedNeutrons,
|
|
92
|
-
source_position: Position[snx.NXsource, SampleRun],
|
|
93
|
-
) -> SimulationResults:
|
|
94
|
-
"""
|
|
95
|
-
Simulate neutrons traveling through the chopper cascade using the ``tof`` package.
|
|
96
|
-
|
|
97
|
-
Parameters
|
|
98
|
-
----------
|
|
99
|
-
choppers:
|
|
100
|
-
Chopper settings.
|
|
101
|
-
neutrons:
|
|
102
|
-
Number of neutrons to simulate.
|
|
103
|
-
source_position:
|
|
104
|
-
Position of the source.
|
|
105
|
-
"""
|
|
106
|
-
return simulate_beamline(
|
|
107
|
-
choppers=choppers, neutrons=neutrons, source_position=source_position
|
|
108
|
-
)
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: BSD-3-Clause
|
|
2
|
-
# Copyright (c) 2025 Scipp contributors (https://github.com/scipp)
|
|
3
|
-
|
|
4
|
-
from functools import reduce
|
|
5
|
-
|
|
6
|
-
import numpy as np
|
|
7
|
-
import scipp as sc
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def to_events(
|
|
11
|
-
da: sc.DataArray, event_dim: str, events_per_bin: int = 500
|
|
12
|
-
) -> sc.DataArray:
|
|
13
|
-
"""
|
|
14
|
-
Convert a histogrammed data array to an event list.
|
|
15
|
-
The generated events have a uniform distribution within each bin.
|
|
16
|
-
Each dimension with a bin-edge coordinate is converted to an event coordinate.
|
|
17
|
-
The contract is that if we re-histogram the event list with the same bin edges,
|
|
18
|
-
we should get the original counts back.
|
|
19
|
-
Masks on non-bin-edge dimensions are preserved.
|
|
20
|
-
If there are masks on bin-edge dimensions, the masked values are zeroed out in the
|
|
21
|
-
original data before the conversion to events.
|
|
22
|
-
|
|
23
|
-
Parameters
|
|
24
|
-
----------
|
|
25
|
-
da:
|
|
26
|
-
DataArray to convert to events.
|
|
27
|
-
event_dim:
|
|
28
|
-
Name of the new event dimension.
|
|
29
|
-
events_per_bin:
|
|
30
|
-
Number of events to generate per bin.
|
|
31
|
-
"""
|
|
32
|
-
if da.bins is not None:
|
|
33
|
-
raise ValueError("Cannot convert a binned DataArray to events.")
|
|
34
|
-
rng = np.random.default_rng()
|
|
35
|
-
event_coords = {}
|
|
36
|
-
edge_dims = []
|
|
37
|
-
midp_dims = set(da.dims)
|
|
38
|
-
midp_coord_names = []
|
|
39
|
-
# Separate bin-edge and midpoints coords
|
|
40
|
-
for name in da.coords:
|
|
41
|
-
dims = da.coords[name].dims
|
|
42
|
-
is_edges = False if not dims else da.coords.is_edges(name)
|
|
43
|
-
if is_edges:
|
|
44
|
-
if name in dims:
|
|
45
|
-
edge_dims.append(name)
|
|
46
|
-
midp_dims -= {name}
|
|
47
|
-
else:
|
|
48
|
-
midp_coord_names.append(name)
|
|
49
|
-
|
|
50
|
-
edge_sizes = {dim: da.sizes[da.coords[dim].dim] for dim in edge_dims}
|
|
51
|
-
for dim in edge_dims:
|
|
52
|
-
coord = da.coords[dim]
|
|
53
|
-
left = sc.broadcast(coord[dim, :-1], sizes=edge_sizes).values
|
|
54
|
-
right = sc.broadcast(coord[dim, 1:], sizes=edge_sizes).values
|
|
55
|
-
|
|
56
|
-
# The numpy.random.uniform function below does not support NaNs, so we need to
|
|
57
|
-
# replace them with zeros, and then replace them back after the random numbers
|
|
58
|
-
# have been generated.
|
|
59
|
-
nans = np.isnan(left) | np.isnan(right)
|
|
60
|
-
left = np.where(nans, 0.0, left)
|
|
61
|
-
right = np.where(nans, 0.0, right)
|
|
62
|
-
# Ensure left <= right
|
|
63
|
-
left, right = np.minimum(left, right), np.maximum(left, right)
|
|
64
|
-
|
|
65
|
-
# In each bin, we generate a number of events with a uniform distribution.
|
|
66
|
-
events = rng.uniform(
|
|
67
|
-
left, right, size=(events_per_bin, *list(edge_sizes.values()))
|
|
68
|
-
)
|
|
69
|
-
events[..., nans] = np.nan
|
|
70
|
-
event_coords[dim] = sc.array(
|
|
71
|
-
dims=[event_dim, *edge_dims], values=events, unit=coord.unit
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
# Find and apply masks that are on a bin-edge dimension
|
|
75
|
-
event_masks = {}
|
|
76
|
-
other_masks = {}
|
|
77
|
-
edge_dims_set = set(edge_dims)
|
|
78
|
-
for key, mask in da.masks.items():
|
|
79
|
-
if set(mask.dims) & edge_dims_set:
|
|
80
|
-
event_masks[key] = mask
|
|
81
|
-
else:
|
|
82
|
-
other_masks[key] = mask
|
|
83
|
-
|
|
84
|
-
data = da.data
|
|
85
|
-
if event_masks:
|
|
86
|
-
inv_mask = (~reduce(lambda a, b: a | b, event_masks.values())).to(dtype=int)
|
|
87
|
-
inv_mask.unit = ""
|
|
88
|
-
data = data * inv_mask
|
|
89
|
-
|
|
90
|
-
# Create the data counts, which are the original counts divided by the number of
|
|
91
|
-
# events per bin
|
|
92
|
-
sizes = {event_dim: events_per_bin} | da.sizes
|
|
93
|
-
val = sc.broadcast(sc.values(data) / float(events_per_bin), sizes=sizes)
|
|
94
|
-
kwargs = {"dims": sizes.keys(), "values": val.values, "unit": data.unit}
|
|
95
|
-
if data.variances is not None:
|
|
96
|
-
# Note here that all the events are correlated.
|
|
97
|
-
# If we later histogram the events with different edges than the original
|
|
98
|
-
# histogram, then neighboring bins will be correlated, and the error obtained
|
|
99
|
-
# will be too small. It is however not clear what can be done to improve this.
|
|
100
|
-
kwargs["variances"] = sc.broadcast(
|
|
101
|
-
sc.variances(data) / float(events_per_bin), sizes=sizes
|
|
102
|
-
).values
|
|
103
|
-
new_data = sc.array(**kwargs)
|
|
104
|
-
|
|
105
|
-
new = sc.DataArray(data=new_data, coords=event_coords)
|
|
106
|
-
new = new.transpose((*midp_dims, *edge_dims, event_dim)).flatten(
|
|
107
|
-
dims=[*edge_dims, event_dim], to=event_dim
|
|
108
|
-
)
|
|
109
|
-
return new.assign_coords(
|
|
110
|
-
{dim: da.coords[dim].copy() for dim in midp_coord_names}
|
|
111
|
-
).assign_masks({key: mask.copy() for key, mask in other_masks.items()})
|
|
File without changes
|
|
File without changes
|
|
File without changes
|