reboost 0.7.0__tar.gz → 0.8.1__tar.gz
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.
- {reboost-0.7.0 → reboost-0.8.1}/PKG-INFO +5 -2
- {reboost-0.7.0 → reboost-0.8.1}/README.md +2 -0
- {reboost-0.7.0 → reboost-0.8.1}/pyproject.toml +2 -1
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/_version.py +3 -3
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/core.py +0 -2
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/hpge/psd.py +11 -9
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/optmap/cli.py +40 -101
- reboost-0.8.1/src/reboost/optmap/convolve.py +325 -0
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/optmap/create.py +37 -124
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/optmap/evt.py +5 -2
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/optmap/mapview.py +9 -7
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/optmap/optmap.py +11 -12
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/spms/pe.py +83 -4
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/utils.py +1 -1
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost.egg-info/PKG-INFO +5 -2
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost.egg-info/SOURCES.txt +2 -1
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost.egg-info/requires.txt +2 -1
- {reboost-0.7.0 → reboost-0.8.1}/tests/hit/configs/spms.yaml +4 -4
- {reboost-0.7.0 → reboost-0.8.1}/tests/hit/test_build_hit.py +4 -6
- reboost-0.8.1/tests/spms/test_pe.py +34 -0
- {reboost-0.7.0 → reboost-0.8.1}/tests/test_optmap.py +57 -75
- reboost-0.7.0/src/reboost/optmap/convolve.py +0 -641
- {reboost-0.7.0 → reboost-0.8.1}/LICENSE +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/setup.cfg +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/__init__.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/build_evt.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/build_glm.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/build_hit.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/cli.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/daq/__init__.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/daq/core.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/daq/utils.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/hpge/__init__.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/hpge/surface.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/hpge/utils.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/iterator.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/log_utils.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/math/__init__.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/math/functions.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/math/stats.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/optmap/__init__.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/optmap/numba_pdg.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/profile.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/shape/__init__.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/shape/cluster.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/shape/group.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/shape/reduction.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/spms/__init__.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost/units.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost.egg-info/dependency_links.txt +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost.egg-info/entry_points.txt +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost.egg-info/not-zip-safe +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/src/reboost.egg-info/top_level.txt +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/tests/conftest.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/tests/evt/test_evt.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/tests/glm/test_build_glm.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/tests/hit/configs/args.yaml +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/tests/hit/configs/basic.yaml +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/tests/hit/configs/foward_only.yaml +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/tests/hit/configs/geom.gdml +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/tests/hit/configs/hit_config.yaml +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/tests/hit/configs/pars.yaml +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/tests/hit/configs/reshape.yaml +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/tests/hpge/simulation/gammas.mac +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/tests/hpge/simulation/geometry.gdml +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/tests/hpge/simulation/make_dt_map.jl +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/tests/hpge/simulation/make_geom.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/tests/hpge/test_current.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/tests/hpge/test_dt_heuristic.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/tests/hpge/test_files/drift_time_maps.lh5 +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/tests/hpge/test_files/internal_electron.lh5 +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/tests/hpge/test_hpge_map.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/tests/hpge/test_r90.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/tests/hpge/test_surface.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/tests/test_cli.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/tests/test_core.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/tests/test_math.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/tests/test_optmap_dets.gdml +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/tests/test_profile.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/tests/test_shape.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/tests/test_units.py +0 -0
- {reboost-0.7.0 → reboost-0.8.1}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: reboost
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.1
|
|
4
4
|
Summary: New LEGEND Monte-Carlo simulation post-processing
|
|
5
5
|
Author-email: Manuel Huber <info@manuelhu.de>, Toby Dixon <toby.dixon.23@ucl.ac.uk>, Luigi Pertoldi <gipert@pm.me>
|
|
6
6
|
Maintainer: The LEGEND Collaboration
|
|
@@ -700,10 +700,11 @@ Requires-Dist: hdf5plugin
|
|
|
700
700
|
Requires-Dist: colorlog
|
|
701
701
|
Requires-Dist: numpy
|
|
702
702
|
Requires-Dist: scipy
|
|
703
|
-
Requires-Dist: numba
|
|
703
|
+
Requires-Dist: numba>=0.60
|
|
704
704
|
Requires-Dist: legend-pydataobj>=1.15.1
|
|
705
705
|
Requires-Dist: legend-pygeom-optics>=0.12.0
|
|
706
706
|
Requires-Dist: legend-pygeom-tools>=0.0.11
|
|
707
|
+
Requires-Dist: legend-pygeom-hpges
|
|
707
708
|
Requires-Dist: hist
|
|
708
709
|
Requires-Dist: dbetto
|
|
709
710
|
Requires-Dist: particle
|
|
@@ -728,6 +729,8 @@ Dynamic: license-file
|
|
|
728
729
|
|
|
729
730
|
# reboost
|
|
730
731
|
|
|
732
|
+
[](https://pypi.org/project/reboost/)
|
|
733
|
+
[](https://anaconda.org/conda-forge/reboost)
|
|
731
734
|

|
|
732
735
|
[](https://github.com/legend-exp/reboost/actions)
|
|
733
736
|
[](https://github.com/pre-commit/pre-commit)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# reboost
|
|
2
2
|
|
|
3
|
+
[](https://pypi.org/project/reboost/)
|
|
4
|
+
[](https://anaconda.org/conda-forge/reboost)
|
|
3
5
|

|
|
4
6
|
[](https://github.com/legend-exp/reboost/actions)
|
|
5
7
|
[](https://github.com/pre-commit/pre-commit)
|
|
@@ -36,10 +36,11 @@ dependencies = [
|
|
|
36
36
|
"colorlog",
|
|
37
37
|
"numpy",
|
|
38
38
|
"scipy",
|
|
39
|
-
"numba",
|
|
39
|
+
"numba >=0.60",
|
|
40
40
|
"legend-pydataobj >=1.15.1",
|
|
41
41
|
"legend-pygeom-optics >=0.12.0",
|
|
42
42
|
"legend-pygeom-tools >=0.0.11",
|
|
43
|
+
"legend-pygeom-hpges",
|
|
43
44
|
"hist",
|
|
44
45
|
"dbetto",
|
|
45
46
|
"particle",
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.
|
|
32
|
-
__version_tuple__ = version_tuple = (0,
|
|
31
|
+
__version__ = version = '0.8.1'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 8, 1)
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'g3148f45bd'
|
|
@@ -325,8 +325,6 @@ def get_one_detector_mapping(
|
|
|
325
325
|
out_list = list(output_detector_expression)
|
|
326
326
|
|
|
327
327
|
for expression_tmp in out_list:
|
|
328
|
-
func, globs = utils.get_function_string(expression_tmp)
|
|
329
|
-
|
|
330
328
|
# if no package was imported its just a name
|
|
331
329
|
try:
|
|
332
330
|
objs = evaluate_object(expression_tmp, local_dict={"ARGS": args, "OBJECTS": objects})
|
|
@@ -262,11 +262,13 @@ def _current_pulse_model(
|
|
|
262
262
|
|
|
263
263
|
Consists of a Gaussian, a high side exponential tail and a low side tail:
|
|
264
264
|
|
|
265
|
-
|
|
265
|
+
.. math::
|
|
266
266
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
\frac{e^{
|
|
267
|
+
\begin{align}
|
|
268
|
+
A(t) = \; &A_\text{max} \times (1-p-p_h) \times \text{Gauss}(t;\mu,\sigma) \\
|
|
269
|
+
&+ A \times p \; \left(1 - \text{erf}\left(\frac{t-\mu}{\sigma_i}\right)\right) \times \frac{e^{t/\tau}}{2e^{\mu/\tau}} \\
|
|
270
|
+
&+ A \times p_h \; \left(1 - \text{erf}\left(-\frac{t-\mu}{\sigma_i}\right)\right) \times \frac{1}{2}e^{-t/\tau}
|
|
271
|
+
\end{align}
|
|
270
272
|
|
|
271
273
|
Parameters
|
|
272
274
|
----------
|
|
@@ -397,13 +399,13 @@ def get_current_waveform(
|
|
|
397
399
|
the template.
|
|
398
400
|
|
|
399
401
|
.. math::
|
|
400
|
-
A(t) = \sum_i E_i \times N f(t,dt_i
|
|
402
|
+
A(t) = \sum_i E_i \times N f(t, dt_i, \vec{\theta})
|
|
401
403
|
|
|
402
404
|
Where:
|
|
403
|
-
-
|
|
404
|
-
-
|
|
405
|
-
-
|
|
406
|
-
- N is a normalisation term
|
|
405
|
+
- :math:`f(t)` is the template
|
|
406
|
+
- :math`\vec{\theta}` are the parameters :math:`(\sigma, p, \tau)`
|
|
407
|
+
- :math:`E_i` and :math:`dt_i` are the deposited energy and drift time.
|
|
408
|
+
- :math:`N` is a normalisation term
|
|
407
409
|
|
|
408
410
|
Parameters
|
|
409
411
|
----------
|
|
@@ -35,21 +35,7 @@ def optical_cli() -> None:
|
|
|
35
35
|
|
|
36
36
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
37
37
|
|
|
38
|
-
# STEP
|
|
39
|
-
evt_parser = subparsers.add_parser("evt", help="build optmap-evt file from remage stp file")
|
|
40
|
-
evt_parser_det_group = evt_parser.add_mutually_exclusive_group(required=True)
|
|
41
|
-
evt_parser_det_group.add_argument(
|
|
42
|
-
"--geom",
|
|
43
|
-
help="GDML geometry file",
|
|
44
|
-
)
|
|
45
|
-
evt_parser_det_group.add_argument(
|
|
46
|
-
"--detectors",
|
|
47
|
-
help="file with detector ids of all optical channels.",
|
|
48
|
-
)
|
|
49
|
-
evt_parser.add_argument("input", help="input stp LH5 file", metavar="INPUT_STP")
|
|
50
|
-
evt_parser.add_argument("output", help="output evt LH5 file", metavar="OUTPUT_EVT")
|
|
51
|
-
|
|
52
|
-
# STEP 2a: build map file from evt tier
|
|
38
|
+
# STEP 1a: build map file from evt tier
|
|
53
39
|
map_parser = subparsers.add_parser("createmap", help="build optical map from evt file(s)")
|
|
54
40
|
map_parser.add_argument(
|
|
55
41
|
"--settings",
|
|
@@ -91,42 +77,67 @@ def optical_cli() -> None:
|
|
|
91
77
|
)
|
|
92
78
|
map_parser.add_argument("output", help="output map LH5 file", metavar="OUTPUT_MAP")
|
|
93
79
|
|
|
94
|
-
# STEP
|
|
95
|
-
mapview_parser = subparsers.add_parser(
|
|
80
|
+
# STEP 1b: view maps
|
|
81
|
+
mapview_parser = subparsers.add_parser(
|
|
82
|
+
"viewmap",
|
|
83
|
+
help="view optical map (arrows: navigate slices/axes, 'c': channel selector)",
|
|
84
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
85
|
+
description=(
|
|
86
|
+
"Interactively view optical maps stored in LH5 files.\n\n"
|
|
87
|
+
"Keyboard controls:\n"
|
|
88
|
+
" left/right - previous/next slice along the current axis\n"
|
|
89
|
+
" up/down - switch slicing axis (x, y, z)\n"
|
|
90
|
+
" c - open channel selector overlay to switch detector map\n\n"
|
|
91
|
+
"Display notes:\n"
|
|
92
|
+
" - Cells where no primary photons were simulated are shown in white.\n"
|
|
93
|
+
" - Cells where no photons were detected are shown in grey.\n"
|
|
94
|
+
" - Cells with values above the colormap maximum are shown in red.\n"
|
|
95
|
+
" - Use --hist to choose which histogram to display. 'prob_unc_rel' shows the\n"
|
|
96
|
+
" relative uncertainty prob_unc / prob where defined.\n"
|
|
97
|
+
" - Use --divide to show the ratio of two map files (this/other)."
|
|
98
|
+
),
|
|
99
|
+
epilog=(
|
|
100
|
+
"Examples:\n"
|
|
101
|
+
" reboost-optical viewmap mymap.lh5\n"
|
|
102
|
+
" reboost-optical viewmap mymap.lh5 --channel _1067205\n"
|
|
103
|
+
" reboost-optical viewmap mymap.lh5 --hist prob_unc_rel --min 0 --max 1\n"
|
|
104
|
+
" reboost-optical viewmap mymap.lh5 --divide other.lh5 --title 'Comparison'"
|
|
105
|
+
),
|
|
106
|
+
)
|
|
96
107
|
mapview_parser.add_argument("input", help="input map LH5 file", metavar="INPUT_MAP")
|
|
97
108
|
mapview_parser.add_argument(
|
|
98
109
|
"--channel",
|
|
99
110
|
action="store",
|
|
100
111
|
default="all",
|
|
101
|
-
help="default: %(default)s",
|
|
112
|
+
help="channel to display ('all' or '_<detid>'). Press 'c' in the viewer to switch. default: %(default)s",
|
|
102
113
|
)
|
|
103
114
|
mapview_parser.add_argument(
|
|
104
115
|
"--hist",
|
|
105
|
-
choices=("
|
|
116
|
+
choices=("_nr_gen", "_nr_det", "prob", "prob_unc", "prob_unc_rel"),
|
|
106
117
|
action="store",
|
|
107
|
-
default="
|
|
118
|
+
default="prob",
|
|
108
119
|
help="select optical map histogram to show. default: %(default)s",
|
|
109
120
|
)
|
|
110
121
|
mapview_parser.add_argument(
|
|
111
122
|
"--divide",
|
|
112
123
|
action="store",
|
|
113
|
-
help="default: none",
|
|
124
|
+
help="divide by another map file before display (ratio). default: none",
|
|
114
125
|
)
|
|
115
126
|
mapview_parser.add_argument(
|
|
116
127
|
"--min",
|
|
117
128
|
default=1e-4,
|
|
118
129
|
type=(lambda s: s if s == "auto" else float(s)),
|
|
119
|
-
help="colormap min value. default: %(default)e",
|
|
130
|
+
help="colormap min value; use 'auto' for automatic scaling. default: %(default)e",
|
|
120
131
|
)
|
|
121
132
|
mapview_parser.add_argument(
|
|
122
133
|
"--max",
|
|
123
134
|
default=1e-2,
|
|
124
135
|
type=(lambda s: s if s == "auto" else float(s)),
|
|
125
|
-
help="colormap max value. default: %(default)e",
|
|
136
|
+
help="colormap max value; use 'auto' for automatic scaling. default: %(default)e",
|
|
126
137
|
)
|
|
127
138
|
mapview_parser.add_argument("--title", help="title of figure. default: stem of filename")
|
|
128
139
|
|
|
129
|
-
# STEP
|
|
140
|
+
# STEP 1c: merge maps
|
|
130
141
|
mapmerge_parser = subparsers.add_parser("mergemap", help="merge optical maps")
|
|
131
142
|
mapmerge_parser.add_argument(
|
|
132
143
|
"input", help="input map LH5 files", metavar="INPUT_MAP", nargs="+"
|
|
@@ -151,49 +162,10 @@ def optical_cli() -> None:
|
|
|
151
162
|
help="""Check map statistics after creation. default: %(default)s""",
|
|
152
163
|
)
|
|
153
164
|
|
|
154
|
-
# STEP
|
|
165
|
+
# STEP 1d: check map
|
|
155
166
|
checkmap_parser = subparsers.add_parser("checkmap", help="check optical maps")
|
|
156
167
|
checkmap_parser.add_argument("input", help="input map LH5 file", metavar="INPUT_MAP")
|
|
157
168
|
|
|
158
|
-
# STEP 3: convolve with hits from non-optical simulations
|
|
159
|
-
convolve_parser = subparsers.add_parser(
|
|
160
|
-
"convolve", help="convolve non-optical hits with optical map"
|
|
161
|
-
)
|
|
162
|
-
convolve_parser.add_argument(
|
|
163
|
-
"--material",
|
|
164
|
-
action="store",
|
|
165
|
-
choices=("lar", "pen", "fiber"),
|
|
166
|
-
default="lar",
|
|
167
|
-
help="default: %(default)s",
|
|
168
|
-
)
|
|
169
|
-
convolve_parser.add_argument(
|
|
170
|
-
"--map",
|
|
171
|
-
action="store",
|
|
172
|
-
required=True,
|
|
173
|
-
metavar="INPUT_MAP",
|
|
174
|
-
help="input map LH5 file",
|
|
175
|
-
)
|
|
176
|
-
convolve_parser.add_argument(
|
|
177
|
-
"--edep",
|
|
178
|
-
action="store",
|
|
179
|
-
required=True,
|
|
180
|
-
metavar="INPUT_EDEP",
|
|
181
|
-
help="input non-optical LH5 hit file",
|
|
182
|
-
)
|
|
183
|
-
convolve_parser.add_argument(
|
|
184
|
-
"--edep-lgdo",
|
|
185
|
-
action="store",
|
|
186
|
-
required=True,
|
|
187
|
-
metavar="LGDO_PATH",
|
|
188
|
-
help="path to LGDO inside non-optical LH5 hit file (e.g. /stp/detXX)",
|
|
189
|
-
)
|
|
190
|
-
convolve_parser.add_argument(
|
|
191
|
-
"--dist-mode",
|
|
192
|
-
action="store",
|
|
193
|
-
default="poisson+no-fano",
|
|
194
|
-
)
|
|
195
|
-
convolve_parser.add_argument("--output", help="output hit LH5 file", metavar="OUTPUT_HIT")
|
|
196
|
-
|
|
197
169
|
# STEP X: rebin maps
|
|
198
170
|
rebin_parser = subparsers.add_parser("rebin", help="rebin optical maps")
|
|
199
171
|
rebin_parser.add_argument("input", help="input map LH5 files", metavar="INPUT_MAP")
|
|
@@ -205,24 +177,7 @@ def optical_cli() -> None:
|
|
|
205
177
|
log_level = (None, logging.INFO, logging.DEBUG)[min(args.verbose, 2)]
|
|
206
178
|
setup_log(log_level)
|
|
207
179
|
|
|
208
|
-
# STEP
|
|
209
|
-
if args.command == "evt":
|
|
210
|
-
from .evt import build_optmap_evt, get_optical_detectors_from_geom
|
|
211
|
-
|
|
212
|
-
_check_input_file(parser, args.input)
|
|
213
|
-
_check_output_file(parser, args.output)
|
|
214
|
-
|
|
215
|
-
# load detector ids from the geometry.
|
|
216
|
-
if args.geom is not None:
|
|
217
|
-
_check_input_file(parser, args.geom, "geometry")
|
|
218
|
-
detectors = get_optical_detectors_from_geom(args.geom)
|
|
219
|
-
else:
|
|
220
|
-
_check_input_file(parser, args.detectors, "detectors")
|
|
221
|
-
detectors = dbetto.utils.load_dict(args.detectors)
|
|
222
|
-
|
|
223
|
-
build_optmap_evt(args.input, args.output, detectors, args.bufsize)
|
|
224
|
-
|
|
225
|
-
# STEP 2a: build map file from evt tier
|
|
180
|
+
# STEP 1a: build map file from evt tier
|
|
226
181
|
if args.command == "createmap":
|
|
227
182
|
from .create import create_optical_maps
|
|
228
183
|
|
|
@@ -250,7 +205,7 @@ def optical_cli() -> None:
|
|
|
250
205
|
geom_fn=args.geom,
|
|
251
206
|
)
|
|
252
207
|
|
|
253
|
-
# STEP
|
|
208
|
+
# STEP 1b: view maps
|
|
254
209
|
if args.command == "viewmap":
|
|
255
210
|
from .mapview import view_optmap
|
|
256
211
|
|
|
@@ -267,7 +222,7 @@ def optical_cli() -> None:
|
|
|
267
222
|
histogram_choice=args.hist,
|
|
268
223
|
)
|
|
269
224
|
|
|
270
|
-
# STEP
|
|
225
|
+
# STEP 1c: merge maps
|
|
271
226
|
if args.command == "mergemap":
|
|
272
227
|
from .create import merge_optical_maps
|
|
273
228
|
|
|
@@ -281,29 +236,13 @@ def optical_cli() -> None:
|
|
|
281
236
|
args.input, args.output, settings, check_after_create=args.check, n_procs=args.n_procs
|
|
282
237
|
)
|
|
283
238
|
|
|
284
|
-
# STEP
|
|
239
|
+
# STEP 1d: check maps
|
|
285
240
|
if args.command == "checkmap":
|
|
286
241
|
from .create import check_optical_map
|
|
287
242
|
|
|
288
243
|
_check_input_file(parser, args.input)
|
|
289
244
|
check_optical_map(args.input)
|
|
290
245
|
|
|
291
|
-
# STEP 3: convolve with hits from non-optical simulations
|
|
292
|
-
if args.command == "convolve":
|
|
293
|
-
from .convolve import convolve
|
|
294
|
-
|
|
295
|
-
_check_input_file(parser, [args.map, args.edep])
|
|
296
|
-
_check_output_file(parser, args.output, optional=True)
|
|
297
|
-
convolve(
|
|
298
|
-
args.map,
|
|
299
|
-
args.edep,
|
|
300
|
-
args.edep_lgdo,
|
|
301
|
-
args.material,
|
|
302
|
-
args.output,
|
|
303
|
-
args.bufsize,
|
|
304
|
-
dist_mode=args.dist_mode,
|
|
305
|
-
)
|
|
306
|
-
|
|
307
246
|
# STEP X: rebin maps
|
|
308
247
|
if args.command == "rebin":
|
|
309
248
|
from .create import rebin_optical_maps
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import NamedTuple
|
|
5
|
+
|
|
6
|
+
import awkward as ak
|
|
7
|
+
import legendoptics.scintillate as sc
|
|
8
|
+
import numba
|
|
9
|
+
import numpy as np
|
|
10
|
+
from legendoptics import fibers, lar, pen
|
|
11
|
+
from lgdo import lh5
|
|
12
|
+
from lgdo.types import Histogram
|
|
13
|
+
from numba import njit
|
|
14
|
+
from numpy.typing import NDArray
|
|
15
|
+
|
|
16
|
+
from .numba_pdg import numba_pdgid_funcs
|
|
17
|
+
|
|
18
|
+
log = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
OPTMAP_ANY_CH = -1
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class OptmapForConvolve(NamedTuple):
|
|
25
|
+
"""A loaded optmap for convolving."""
|
|
26
|
+
|
|
27
|
+
dets: NDArray
|
|
28
|
+
detidx: NDArray
|
|
29
|
+
edges: NDArray
|
|
30
|
+
weights: NDArray
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def open_optmap(optmap_fn: str) -> OptmapForConvolve:
|
|
34
|
+
dets = lh5.ls(optmap_fn, "/channels/")
|
|
35
|
+
detidx = np.arange(0, dets.shape[0])
|
|
36
|
+
|
|
37
|
+
optmap_all = lh5.read("/all/prob", optmap_fn)
|
|
38
|
+
assert isinstance(optmap_all, Histogram)
|
|
39
|
+
optmap_edges = tuple([b.edges for b in optmap_all.binning])
|
|
40
|
+
|
|
41
|
+
ow = np.empty((detidx.shape[0] + 2, *optmap_all.weights.nda.shape), dtype=np.float64)
|
|
42
|
+
# 0, ..., len(detidx)-1 AND OPTMAP_ANY_CH might be negative.
|
|
43
|
+
ow[OPTMAP_ANY_CH] = optmap_all.weights.nda
|
|
44
|
+
for i, nt in zip(detidx, dets, strict=True):
|
|
45
|
+
optmap = lh5.read(f"/{nt}/prob", optmap_fn)
|
|
46
|
+
assert isinstance(optmap, Histogram)
|
|
47
|
+
ow[i] = optmap.weights.nda
|
|
48
|
+
|
|
49
|
+
# if we have any individual channels registered, the sum is potentially larger than the
|
|
50
|
+
# probability to find _any_ hit.
|
|
51
|
+
if len(detidx) != 0:
|
|
52
|
+
map_sum = np.sum(ow[0:-2], axis=0, where=(ow[0:-2] >= 0))
|
|
53
|
+
assert not np.any(map_sum < 0)
|
|
54
|
+
|
|
55
|
+
# give this check some numerical slack.
|
|
56
|
+
if np.any(
|
|
57
|
+
np.abs(map_sum[ow[OPTMAP_ANY_CH] >= 0] - ow[OPTMAP_ANY_CH][ow[OPTMAP_ANY_CH] >= 0])
|
|
58
|
+
< -1e-15
|
|
59
|
+
):
|
|
60
|
+
msg = "optical map does not fulfill relation sum(p_i) >= p_any"
|
|
61
|
+
raise ValueError(msg)
|
|
62
|
+
else:
|
|
63
|
+
detidx = np.array([OPTMAP_ANY_CH])
|
|
64
|
+
dets = np.array(["all"])
|
|
65
|
+
|
|
66
|
+
# check the exponent from the optical map file
|
|
67
|
+
if "_hitcounts_exp" in lh5.ls(optmap_fn):
|
|
68
|
+
msg = "found _hitcounts_exp which is not supported any more"
|
|
69
|
+
raise RuntimeError(msg)
|
|
70
|
+
|
|
71
|
+
dets = [d.replace("/channels/", "") for d in dets]
|
|
72
|
+
|
|
73
|
+
return OptmapForConvolve(dets, detidx, optmap_edges, ow)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def open_optmap_single(optmap_fn: str, spm_det: str) -> OptmapForConvolve:
|
|
77
|
+
# check the exponent from the optical map file
|
|
78
|
+
if "_hitcounts_exp" in lh5.ls(optmap_fn):
|
|
79
|
+
msg = "found _hitcounts_exp which is not supported any more"
|
|
80
|
+
raise RuntimeError(msg)
|
|
81
|
+
|
|
82
|
+
h5_path = f"channels/{spm_det}" if spm_det != "all" else spm_det
|
|
83
|
+
optmap = lh5.read(f"/{h5_path}/prob", optmap_fn)
|
|
84
|
+
assert isinstance(optmap, Histogram)
|
|
85
|
+
ow = np.empty((1, *optmap.weights.nda.shape), dtype=np.float64)
|
|
86
|
+
ow[0] = optmap.weights.nda
|
|
87
|
+
optmap_edges = tuple([b.edges for b in optmap.binning])
|
|
88
|
+
|
|
89
|
+
return OptmapForConvolve(np.array([spm_det]), np.array([0]), optmap_edges, ow)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def iterate_stepwise_depositions_pois(
|
|
93
|
+
edep_hits: ak.Array,
|
|
94
|
+
optmap: OptmapForConvolve,
|
|
95
|
+
scint_mat_params: sc.ComputedScintParams,
|
|
96
|
+
det: str,
|
|
97
|
+
map_scaling: float = 1,
|
|
98
|
+
map_scaling_sigma: float = 0,
|
|
99
|
+
rng: np.random.Generator | None = None,
|
|
100
|
+
):
|
|
101
|
+
if edep_hits.particle.ndim == 1:
|
|
102
|
+
msg = "the pe processors only support already reshaped output"
|
|
103
|
+
raise ValueError(msg)
|
|
104
|
+
|
|
105
|
+
if det not in optmap.dets:
|
|
106
|
+
msg = f"channel {det} not available in optical map (contains {optmap.dets})"
|
|
107
|
+
raise ValueError(msg)
|
|
108
|
+
|
|
109
|
+
rng = np.random.default_rng() if rng is None else rng
|
|
110
|
+
res, output_list = _iterate_stepwise_depositions_pois(
|
|
111
|
+
edep_hits,
|
|
112
|
+
rng,
|
|
113
|
+
np.where(optmap.dets == det)[0][0],
|
|
114
|
+
map_scaling,
|
|
115
|
+
map_scaling_sigma,
|
|
116
|
+
optmap.edges,
|
|
117
|
+
optmap.weights,
|
|
118
|
+
scint_mat_params,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# convert the numba result back into an awkward array.
|
|
122
|
+
builder = ak.ArrayBuilder()
|
|
123
|
+
for r in output_list:
|
|
124
|
+
with builder.list():
|
|
125
|
+
for a in r:
|
|
126
|
+
builder.extend(a)
|
|
127
|
+
|
|
128
|
+
if res["det_no_stats"] > 0:
|
|
129
|
+
log.warning(
|
|
130
|
+
"had edep out in voxels without stats: %d",
|
|
131
|
+
res["det_no_stats"],
|
|
132
|
+
)
|
|
133
|
+
if res["oob"] > 0:
|
|
134
|
+
log.warning(
|
|
135
|
+
"had edep out of map bounds: %d (%.2f%%)",
|
|
136
|
+
res["oob"],
|
|
137
|
+
(res["oob"] / (res["ib"] + res["oob"])) * 100,
|
|
138
|
+
)
|
|
139
|
+
log.debug(
|
|
140
|
+
"VUV_primary %d ->hits %d (%.2f %% primaries detected in this channel)",
|
|
141
|
+
res["vuv_primary"],
|
|
142
|
+
res["hits"],
|
|
143
|
+
(res["hits"] / res["vuv_primary"]) * 100,
|
|
144
|
+
)
|
|
145
|
+
return builder.snapshot()
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def iterate_stepwise_depositions_scintillate(
|
|
149
|
+
edep_hits: ak.Array,
|
|
150
|
+
scint_mat_params: sc.ComputedScintParams,
|
|
151
|
+
rng: np.random.Generator | None = None,
|
|
152
|
+
mode: str = "no-fano",
|
|
153
|
+
):
|
|
154
|
+
if edep_hits.particle.ndim == 1:
|
|
155
|
+
msg = "the pe processors only support already reshaped output"
|
|
156
|
+
raise ValueError(msg)
|
|
157
|
+
|
|
158
|
+
rng = np.random.default_rng() if rng is None else rng
|
|
159
|
+
output_list = _iterate_stepwise_depositions_scintillate(edep_hits, rng, scint_mat_params, mode)
|
|
160
|
+
|
|
161
|
+
# convert the numba result back into an awkward array.
|
|
162
|
+
builder = ak.ArrayBuilder()
|
|
163
|
+
for r in output_list:
|
|
164
|
+
with builder.list():
|
|
165
|
+
builder.extend(r)
|
|
166
|
+
|
|
167
|
+
return builder.snapshot()
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
_pdg_func = numba_pdgid_funcs()
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@njit
|
|
174
|
+
def _pdgid_to_particle(pdgid: int) -> sc.ParticleIndex:
|
|
175
|
+
abs_pdgid = abs(pdgid)
|
|
176
|
+
if abs_pdgid == 1000020040:
|
|
177
|
+
return sc.PARTICLE_INDEX_ALPHA
|
|
178
|
+
if abs_pdgid == 1000010020:
|
|
179
|
+
return sc.PARTICLE_INDEX_DEUTERON
|
|
180
|
+
if abs_pdgid == 1000010030:
|
|
181
|
+
return sc.PARTICLE_INDEX_TRITON
|
|
182
|
+
if _pdg_func.is_nucleus(pdgid):
|
|
183
|
+
return sc.PARTICLE_INDEX_ION
|
|
184
|
+
return sc.PARTICLE_INDEX_ELECTRON
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
__counts_per_bin_key_type = numba.types.UniTuple(numba.types.int64, 3)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# - run with NUMBA_FULL_TRACEBACKS=1 NUMBA_BOUNDSCHECK=1 for testing/checking
|
|
191
|
+
# - cache=True does not work with outer prange, i.e. loading the cached file fails (numba bug?)
|
|
192
|
+
# - the output dictionary is not threadsafe, so parallel=True is not working with it.
|
|
193
|
+
@njit(parallel=False, nogil=True, cache=True)
|
|
194
|
+
def _iterate_stepwise_depositions_pois(
|
|
195
|
+
edep_hits,
|
|
196
|
+
rng,
|
|
197
|
+
detidx: int,
|
|
198
|
+
map_scaling: float,
|
|
199
|
+
map_scaling_sigma: float,
|
|
200
|
+
optmap_edges,
|
|
201
|
+
optmap_weights,
|
|
202
|
+
scint_mat_params: sc.ComputedScintParams,
|
|
203
|
+
):
|
|
204
|
+
pdgid_map = {}
|
|
205
|
+
oob = ib = ph_cnt = ph_det2 = det_no_stats = 0 # for statistics
|
|
206
|
+
output_list = []
|
|
207
|
+
|
|
208
|
+
for rowid in range(len(edep_hits)): # iterate hits
|
|
209
|
+
hit = edep_hits[rowid]
|
|
210
|
+
hit_output = []
|
|
211
|
+
|
|
212
|
+
map_scaling_evt = map_scaling
|
|
213
|
+
if map_scaling_sigma > 0:
|
|
214
|
+
map_scaling_evt = rng.normal(loc=map_scaling, scale=map_scaling_sigma)
|
|
215
|
+
|
|
216
|
+
assert len(hit.particle) == len(hit.num_scint_ph)
|
|
217
|
+
# iterate steps inside the hit
|
|
218
|
+
for si in range(len(hit.particle)):
|
|
219
|
+
loc = np.array([hit.xloc[si], hit.yloc[si], hit.zloc[si]])
|
|
220
|
+
# coordinates -> bins of the optical map.
|
|
221
|
+
bins = np.empty(3, dtype=np.int64)
|
|
222
|
+
for j in range(3):
|
|
223
|
+
bins[j] = np.digitize(loc[j], optmap_edges[j])
|
|
224
|
+
# normalize all out-of-bounds bins just to one end.
|
|
225
|
+
if bins[j] == optmap_edges[j].shape[0]:
|
|
226
|
+
bins[j] = 0
|
|
227
|
+
|
|
228
|
+
# note: subtract 1 from bins, to account for np.digitize output.
|
|
229
|
+
cur_bins = (bins[0] - 1, bins[1] - 1, bins[2] - 1)
|
|
230
|
+
if cur_bins[0] == -1 or cur_bins[1] == -1 or cur_bins[2] == -1:
|
|
231
|
+
oob += 1
|
|
232
|
+
continue # out-of-bounds of optmap
|
|
233
|
+
ib += 1
|
|
234
|
+
|
|
235
|
+
# get probabilities from map.
|
|
236
|
+
detp = optmap_weights[detidx, cur_bins[0], cur_bins[1], cur_bins[2]] * map_scaling_evt
|
|
237
|
+
if detp < 0.0:
|
|
238
|
+
det_no_stats += 1
|
|
239
|
+
continue
|
|
240
|
+
|
|
241
|
+
pois_cnt = rng.poisson(lam=hit.num_scint_ph[si] * detp)
|
|
242
|
+
ph_cnt += hit.num_scint_ph[si]
|
|
243
|
+
ph_det2 += pois_cnt
|
|
244
|
+
|
|
245
|
+
# get the particle information.
|
|
246
|
+
particle = hit.particle[si]
|
|
247
|
+
if particle not in pdgid_map:
|
|
248
|
+
pdgid_map[particle] = (_pdgid_to_particle(particle), _pdg_func.charge(particle))
|
|
249
|
+
part, _charge = pdgid_map[particle]
|
|
250
|
+
|
|
251
|
+
# get time spectrum.
|
|
252
|
+
# note: we assume "immediate" propagation after scintillation.
|
|
253
|
+
scint_times = sc.scintillate_times(scint_mat_params, part, pois_cnt, rng) + hit.time[si]
|
|
254
|
+
|
|
255
|
+
hit_output.append(scint_times)
|
|
256
|
+
|
|
257
|
+
output_list.append(hit_output)
|
|
258
|
+
|
|
259
|
+
stats = {
|
|
260
|
+
"oob": oob,
|
|
261
|
+
"ib": ib,
|
|
262
|
+
"vuv_primary": ph_cnt,
|
|
263
|
+
"hits": ph_det2,
|
|
264
|
+
"det_no_stats": det_no_stats,
|
|
265
|
+
}
|
|
266
|
+
return stats, output_list
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
# - run with NUMBA_FULL_TRACEBACKS=1 NUMBA_BOUNDSCHECK=1 for testing/checking
|
|
270
|
+
# - cache=True does not work with outer prange, i.e. loading the cached file fails (numba bug?)
|
|
271
|
+
@njit(parallel=False, nogil=True, cache=True)
|
|
272
|
+
def _iterate_stepwise_depositions_scintillate(
|
|
273
|
+
edep_hits, rng, scint_mat_params: sc.ComputedScintParams, mode: str
|
|
274
|
+
):
|
|
275
|
+
pdgid_map = {}
|
|
276
|
+
output_list = []
|
|
277
|
+
|
|
278
|
+
for rowid in range(len(edep_hits)): # iterate hits
|
|
279
|
+
hit = edep_hits[rowid]
|
|
280
|
+
hit_output = []
|
|
281
|
+
|
|
282
|
+
# iterate steps inside the hit
|
|
283
|
+
for si in range(len(hit.particle)):
|
|
284
|
+
# get the particle information.
|
|
285
|
+
particle = hit.particle[si]
|
|
286
|
+
if particle not in pdgid_map:
|
|
287
|
+
pdgid_map[particle] = (_pdgid_to_particle(particle), _pdg_func.charge(particle))
|
|
288
|
+
part, _charge = pdgid_map[particle]
|
|
289
|
+
|
|
290
|
+
# do the scintillation.
|
|
291
|
+
num_phot = sc.scintillate_numphot(
|
|
292
|
+
scint_mat_params,
|
|
293
|
+
part,
|
|
294
|
+
hit.edep[si],
|
|
295
|
+
rng,
|
|
296
|
+
emission_term_model=("poisson" if mode == "no-fano" else "normal_fano"),
|
|
297
|
+
)
|
|
298
|
+
hit_output.append(num_phot)
|
|
299
|
+
|
|
300
|
+
assert len(hit_output) == len(hit.particle)
|
|
301
|
+
output_list.append(hit_output)
|
|
302
|
+
|
|
303
|
+
return output_list
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _get_scint_params(material: str):
|
|
307
|
+
if material == "lar":
|
|
308
|
+
return sc.precompute_scintillation_params(
|
|
309
|
+
lar.lar_scintillation_params(),
|
|
310
|
+
lar.lar_lifetimes().as_tuple(),
|
|
311
|
+
)
|
|
312
|
+
if material == "pen":
|
|
313
|
+
return sc.precompute_scintillation_params(
|
|
314
|
+
pen.pen_scintillation_params(),
|
|
315
|
+
(pen.pen_scint_timeconstant(),),
|
|
316
|
+
)
|
|
317
|
+
if material == "fiber":
|
|
318
|
+
return sc.precompute_scintillation_params(
|
|
319
|
+
fibers.fiber_core_scintillation_params(),
|
|
320
|
+
(fibers.fiber_wls_timeconstant(),),
|
|
321
|
+
)
|
|
322
|
+
if isinstance(material, str):
|
|
323
|
+
msg = f"unknown material {material} for scintillation"
|
|
324
|
+
raise ValueError(msg)
|
|
325
|
+
return sc.precompute_scintillation_params(*material)
|