reboost 0.9.0__tar.gz → 0.10.0a0__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.9.0/src/reboost.egg-info → reboost-0.10.0a0}/PKG-INFO +2 -2
- {reboost-0.9.0 → reboost-0.10.0a0}/pyproject.toml +1 -1
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/_version.py +3 -3
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/build_hit.py +2 -16
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/hpge/psd.py +37 -36
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/hpge/surface.py +29 -16
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/math/functions.py +20 -31
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/math/stats.py +3 -6
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/shape/cluster.py +36 -48
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/shape/group.py +34 -8
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/units.py +57 -2
- {reboost-0.9.0 → reboost-0.10.0a0/src/reboost.egg-info}/PKG-INFO +2 -2
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost.egg-info/requires.txt +1 -1
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/hit/configs/hit_config.yaml +1 -1
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/hit/test_build_hit.py +28 -24
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/hpge/test_current.py +65 -36
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/hpge/test_dt_heuristic.py +27 -36
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/hpge/test_r90.py +30 -3
- reboost-0.10.0a0/tests/hpge/test_surface.py +118 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/test_core.py +1 -1
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/test_math.py +8 -27
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/test_shape.py +66 -40
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/test_units.py +13 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/test_utils.py +6 -6
- reboost-0.9.0/tests/hpge/test_surface.py +0 -76
- {reboost-0.9.0 → reboost-0.10.0a0}/LICENSE +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/README.md +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/setup.cfg +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/__init__.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/__main__.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/build_evt.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/build_glm.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/cli.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/core.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/daq/__init__.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/daq/core.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/daq/utils.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/hpge/__init__.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/hpge/utils.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/iterator.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/log_utils.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/math/__init__.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/optmap/__init__.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/optmap/__main__.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/optmap/cli.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/optmap/convolve.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/optmap/create.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/optmap/evt.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/optmap/mapview.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/optmap/numba_pdg.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/optmap/optmap.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/profile.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/shape/__init__.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/shape/reduction.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/spms/__init__.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/spms/pe.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost/utils.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost.egg-info/SOURCES.txt +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost.egg-info/dependency_links.txt +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost.egg-info/entry_points.txt +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost.egg-info/not-zip-safe +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/src/reboost.egg-info/top_level.txt +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/conftest.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/evt/test_evt.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/glm/test_build_glm.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/hit/configs/args.yaml +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/hit/configs/basic.yaml +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/hit/configs/foward_only.yaml +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/hit/configs/geom.gdml +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/hit/configs/pars.yaml +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/hit/configs/reshape.yaml +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/hit/configs/spms.yaml +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/hpge/simulation/gammas.mac +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/hpge/simulation/geometry.gdml +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/hpge/simulation/make_dt_map.jl +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/hpge/simulation/make_geom.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/hpge/test_files/drift_time_maps.lh5 +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/hpge/test_files/internal_electron.lh5 +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/hpge/test_hpge_map.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/spms/test_pe.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/test_cli.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/test_optmap.py +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/test_optmap_dets.gdml +0 -0
- {reboost-0.9.0 → reboost-0.10.0a0}/tests/test_profile.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: reboost
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.0a0
|
|
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
|
|
@@ -26,7 +26,7 @@ Requires-Dist: colorlog
|
|
|
26
26
|
Requires-Dist: numpy
|
|
27
27
|
Requires-Dist: scipy
|
|
28
28
|
Requires-Dist: numba>=0.60
|
|
29
|
-
Requires-Dist: legend-pydataobj>=1.
|
|
29
|
+
Requires-Dist: legend-pydataobj>=1.17.2
|
|
30
30
|
Requires-Dist: legend-pygeom-optics>=0.15.0
|
|
31
31
|
Requires-Dist: legend-pygeom-tools>=0.0.26
|
|
32
32
|
Requires-Dist: legend-pygeom-hpges
|
|
@@ -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.10.0a0'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 10, 0, 'a0')
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'gd31e91903'
|
|
@@ -323,7 +323,7 @@ def build_hit(
|
|
|
323
323
|
if time_dict is not None:
|
|
324
324
|
start_time = time.time()
|
|
325
325
|
|
|
326
|
-
ak_obj = stps.view_as("ak")
|
|
326
|
+
ak_obj = stps.view_as("ak", with_units=True)
|
|
327
327
|
|
|
328
328
|
if time_dict is not None:
|
|
329
329
|
time_dict[proc_name].update_field("conv", start_time)
|
|
@@ -349,9 +349,6 @@ def build_hit(
|
|
|
349
349
|
if out_detector not in output_tables and files.hit[file_idx] is None:
|
|
350
350
|
output_tables[out_detector] = None
|
|
351
351
|
|
|
352
|
-
# get the attributes
|
|
353
|
-
attrs = utils.copy_units(stps)
|
|
354
|
-
|
|
355
352
|
# if we have more than one output detector, make an independent copy.
|
|
356
353
|
hit_table = (
|
|
357
354
|
copy.deepcopy(hit_table_layouted)
|
|
@@ -376,9 +373,6 @@ def build_hit(
|
|
|
376
373
|
hit_table, outputs=proc_group["outputs"]
|
|
377
374
|
)
|
|
378
375
|
|
|
379
|
-
# assign units in the output table
|
|
380
|
-
hit_table = utils.assign_units(hit_table, attrs)
|
|
381
|
-
|
|
382
376
|
# now write
|
|
383
377
|
if files.hit[file_idx] is not None:
|
|
384
378
|
# get modes to write with
|
|
@@ -443,12 +437,7 @@ def build_hit(
|
|
|
443
437
|
def _evaluate_operation(
|
|
444
438
|
hit_table, field: str, info: str | dict, local_dict: dict, time_dict: ProfileDict
|
|
445
439
|
) -> None:
|
|
446
|
-
if isinstance(info, str)
|
|
447
|
-
expression = info
|
|
448
|
-
units = None
|
|
449
|
-
else:
|
|
450
|
-
expression = info["expression"]
|
|
451
|
-
units = info.get("units", None)
|
|
440
|
+
expression = info if isinstance(info, str) else info["expression"]
|
|
452
441
|
|
|
453
442
|
# evaluate the expression
|
|
454
443
|
col = core.evaluate_output_column(
|
|
@@ -460,7 +449,4 @@ def _evaluate_operation(
|
|
|
460
449
|
name=field,
|
|
461
450
|
)
|
|
462
451
|
|
|
463
|
-
if units is not None:
|
|
464
|
-
col.attrs["units"] = units
|
|
465
|
-
|
|
466
452
|
core.add_field_with_nesting(hit_table, field, col)
|
|
@@ -18,7 +18,7 @@ from .utils import HPGePulseShapeLibrary, HPGeRZField
|
|
|
18
18
|
log = logging.getLogger(__name__)
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
def r90(edep: ak.Array, xloc: ak.Array, yloc: ak.Array, zloc: ak.Array) -> Array:
|
|
21
|
+
def r90(edep: ak.Array, xloc: ak.Array, yloc: ak.Array, zloc: ak.Array) -> ak.Array:
|
|
22
22
|
"""R90 HPGe pulse shape heuristic.
|
|
23
23
|
|
|
24
24
|
Parameters
|
|
@@ -31,18 +31,22 @@ def r90(edep: ak.Array, xloc: ak.Array, yloc: ak.Array, zloc: ak.Array) -> Array
|
|
|
31
31
|
array of y coordinate position.
|
|
32
32
|
zloc
|
|
33
33
|
array of z coordinate position.
|
|
34
|
+
|
|
35
|
+
Returns
|
|
36
|
+
-------
|
|
37
|
+
calculated r90 for each hit
|
|
34
38
|
"""
|
|
39
|
+
pos = [units.units_conv_ak(pos, "mm") for pos in [xloc, yloc, zloc]]
|
|
40
|
+
|
|
41
|
+
edep = units.units_conv_ak(edep, "keV")
|
|
42
|
+
|
|
35
43
|
tot_energy = ak.sum(edep, axis=-1, keepdims=True)
|
|
36
44
|
|
|
37
45
|
def eweight_mean(field, energy):
|
|
38
46
|
return ak.sum(energy * field, axis=-1, keepdims=True) / tot_energy
|
|
39
47
|
|
|
40
48
|
# Compute distance of each edep to the weighted mean
|
|
41
|
-
dist = np.sqrt(
|
|
42
|
-
(xloc - eweight_mean(edep, xloc)) ** 2
|
|
43
|
-
+ (yloc - eweight_mean(edep, yloc)) ** 2
|
|
44
|
-
+ (zloc - eweight_mean(edep, zloc)) ** 2
|
|
45
|
-
)
|
|
49
|
+
dist = np.sqrt(sum((p - eweight_mean(edep, p)) ** 2 for p in pos))
|
|
46
50
|
|
|
47
51
|
# Sort distances and corresponding edep within each event
|
|
48
52
|
sorted_indices = ak.argsort(dist, axis=-1)
|
|
@@ -76,16 +80,16 @@ def r90(edep: ak.Array, xloc: ak.Array, yloc: ak.Array, zloc: ak.Array) -> Array
|
|
|
76
80
|
r90_indices = ak.argmax(cumsum_edep_corrected >= threshold, axis=-1, keepdims=True)
|
|
77
81
|
r90 = sorted_dist[r90_indices]
|
|
78
82
|
|
|
79
|
-
return Array(ak.flatten(r90)
|
|
83
|
+
return units.attach_units(ak.Array(ak.flatten(r90)), "mm")
|
|
80
84
|
|
|
81
85
|
|
|
82
86
|
def drift_time(
|
|
83
|
-
xloc:
|
|
84
|
-
yloc:
|
|
85
|
-
zloc:
|
|
87
|
+
xloc: ak.Array | VectorOfVectors,
|
|
88
|
+
yloc: ak.Array | VectorOfVectors,
|
|
89
|
+
zloc: ak.Array | VectorOfVectors,
|
|
86
90
|
dt_map: HPGeRZField,
|
|
87
91
|
coord_offset: pint.Quantity | pyg4ometry.gdml.Position = (0, 0, 0) * u.m,
|
|
88
|
-
) ->
|
|
92
|
+
) -> ak.Array:
|
|
89
93
|
"""Calculates drift times for each step (cluster) in an HPGe detector.
|
|
90
94
|
|
|
91
95
|
Parameters
|
|
@@ -106,7 +110,7 @@ def drift_time(
|
|
|
106
110
|
# sanitize coord_offset
|
|
107
111
|
coord_offset = units.pg4_to_pint(coord_offset)
|
|
108
112
|
|
|
109
|
-
# unit handling (for
|
|
113
|
+
# unit handling (.r_units) for data in (xloc, yloc)]
|
|
110
114
|
xu, yu = [units.units_convfact(data, dt_map.r_units) for data in (xloc, yloc)]
|
|
111
115
|
zu = units.units_convfact(zloc, dt_map.z_units)
|
|
112
116
|
|
|
@@ -122,7 +126,6 @@ def drift_time(
|
|
|
122
126
|
|
|
123
127
|
return None
|
|
124
128
|
|
|
125
|
-
# transform coordinates
|
|
126
129
|
xloc = xu * xloc - coord_offset[0].to(dt_map.r_units).m
|
|
127
130
|
yloc = yu * yloc - coord_offset[1].to(dt_map.r_units).m
|
|
128
131
|
zloc = zu * zloc - coord_offset[2].to(dt_map.z_units).m
|
|
@@ -133,16 +136,13 @@ def drift_time(
|
|
|
133
136
|
np.sqrt(xloc**2 + yloc**2),
|
|
134
137
|
zloc,
|
|
135
138
|
)
|
|
136
|
-
return
|
|
137
|
-
dt_values,
|
|
138
|
-
attrs={"units": units.unit_to_lh5_attr(dt_map.φ_units)},
|
|
139
|
-
)
|
|
139
|
+
return units.attach_units(ak.Array(dt_values), units.unit_to_lh5_attr(dt_map.φ_units))
|
|
140
140
|
|
|
141
141
|
|
|
142
142
|
def drift_time_heuristic(
|
|
143
|
-
drift_time:
|
|
144
|
-
edep:
|
|
145
|
-
) -> Array:
|
|
143
|
+
drift_time: ak.Array | VectorOfVectors,
|
|
144
|
+
edep: ak.Array | VectorOfVectors,
|
|
145
|
+
) -> ak.Array:
|
|
146
146
|
"""HPGe drift-time-based pulse-shape heuristic.
|
|
147
147
|
|
|
148
148
|
See :func:`_drift_time_heuristic_impl` for a description of the algorithm.
|
|
@@ -160,11 +160,11 @@ def drift_time_heuristic(
|
|
|
160
160
|
edep, e_units = units.unwrap_lgdo(edep)
|
|
161
161
|
|
|
162
162
|
# we want to attach the right units to the dt heuristic, if possible
|
|
163
|
-
attrs = {}
|
|
164
163
|
if t_units is not None and e_units is not None:
|
|
165
|
-
|
|
164
|
+
unit = units.unit_to_lh5_attr(t_units / e_units)
|
|
165
|
+
return units.attach_units(ak.Array(_drift_time_heuristic_impl(drift_time, edep)), unit)
|
|
166
166
|
|
|
167
|
-
return Array(_drift_time_heuristic_impl(drift_time, edep)
|
|
167
|
+
return ak.Array(_drift_time_heuristic_impl(drift_time, edep))
|
|
168
168
|
|
|
169
169
|
|
|
170
170
|
@numba.njit(cache=True)
|
|
@@ -828,15 +828,15 @@ def maximum_current(
|
|
|
828
828
|
activeness_surface: ArrayLike | None = None,
|
|
829
829
|
surface_step_in_um: float = 10,
|
|
830
830
|
return_mode: str = "current",
|
|
831
|
-
) -> Array:
|
|
831
|
+
) -> ak.Array:
|
|
832
832
|
"""Estimate the maximum current in the HPGe detector based on :func:`_estimate_current_impl`.
|
|
833
833
|
|
|
834
834
|
Parameters
|
|
835
835
|
----------
|
|
836
836
|
edep
|
|
837
|
-
|
|
837
|
+
Energies for each step.
|
|
838
838
|
drift_time
|
|
839
|
-
|
|
839
|
+
Drift times for each step.
|
|
840
840
|
dist_to_nplus
|
|
841
841
|
Distance to n-plus electrode, only needed if surface heuristics are enabled.
|
|
842
842
|
r
|
|
@@ -844,7 +844,7 @@ def maximum_current(
|
|
|
844
844
|
z
|
|
845
845
|
z coordinate (only needed if a full PSS library is used).
|
|
846
846
|
template
|
|
847
|
-
|
|
847
|
+
Array of the bulk pulse template
|
|
848
848
|
times
|
|
849
849
|
time-stamps for the bulk pulse template
|
|
850
850
|
fccd_in_um
|
|
@@ -864,12 +864,12 @@ def maximum_current(
|
|
|
864
864
|
-------
|
|
865
865
|
An Array of the maximum current/ time / energy for each hit.
|
|
866
866
|
"""
|
|
867
|
-
#
|
|
868
|
-
drift_time
|
|
869
|
-
edep
|
|
870
|
-
dist_to_nplus
|
|
871
|
-
r
|
|
872
|
-
z
|
|
867
|
+
# convert to target units
|
|
868
|
+
drift_time = units.units_conv_ak(drift_time, "ns")
|
|
869
|
+
edep = units.units_conv_ak(edep, "keV")
|
|
870
|
+
dist_to_nplus = units.units_conv_ak(dist_to_nplus, "mm")
|
|
871
|
+
r = units.units_conv_ak(r, "mm")
|
|
872
|
+
z = units.units_conv_ak(z, "mm")
|
|
873
873
|
|
|
874
874
|
# prepare inputs for surface sims
|
|
875
875
|
include_surface_effects, dist_to_nplus, templates_surface, activeness_surface = (
|
|
@@ -901,11 +901,12 @@ def maximum_current(
|
|
|
901
901
|
|
|
902
902
|
# return
|
|
903
903
|
if return_mode == "max_time":
|
|
904
|
-
return Array(time)
|
|
904
|
+
return units.attach_units(ak.Array(time), "ns")
|
|
905
905
|
if return_mode == "current":
|
|
906
|
-
|
|
906
|
+
# current has no unit (depends on the template)
|
|
907
|
+
return ak.Array(curr)
|
|
907
908
|
if return_mode == "energy":
|
|
908
|
-
return Array(energy)
|
|
909
|
+
return units.attach_units(ak.Array(energy), "keV")
|
|
909
910
|
|
|
910
911
|
msg = f"Return mode {return_mode} is not implemented."
|
|
911
912
|
raise ValueError(msg)
|
|
@@ -5,27 +5,32 @@ import logging
|
|
|
5
5
|
import awkward as ak
|
|
6
6
|
import numba
|
|
7
7
|
import numpy as np
|
|
8
|
+
import pint
|
|
9
|
+
import pyg4ometry
|
|
8
10
|
import pygeomhpges
|
|
9
11
|
from lgdo import VectorOfVectors
|
|
10
12
|
from lgdo.types import LGDO
|
|
11
|
-
from numpy.typing import
|
|
13
|
+
from numpy.typing import NDArray
|
|
12
14
|
from scipy import stats
|
|
13
15
|
|
|
16
|
+
from reboost.units import ureg as u
|
|
17
|
+
|
|
18
|
+
from .. import units
|
|
19
|
+
|
|
14
20
|
log = logging.getLogger(__name__)
|
|
15
21
|
|
|
16
22
|
|
|
17
23
|
def distance_to_surface(
|
|
18
|
-
positions_x: VectorOfVectors,
|
|
19
|
-
positions_y: VectorOfVectors,
|
|
20
|
-
positions_z: VectorOfVectors,
|
|
24
|
+
positions_x: ak.Array | VectorOfVectors,
|
|
25
|
+
positions_y: ak.Array | VectorOfVectors,
|
|
26
|
+
positions_z: ak.Array | VectorOfVectors,
|
|
21
27
|
hpge: pygeomhpges.base.HPGe,
|
|
22
|
-
det_pos:
|
|
28
|
+
det_pos: pint.Quantity | pyg4ometry.gdml.Position | tuple = (0, 0, 0) * u.m,
|
|
23
29
|
*,
|
|
24
30
|
surface_type: str | None = None,
|
|
25
|
-
|
|
26
|
-
distances_precompute: VectorOfVectors | None = None,
|
|
31
|
+
distances_precompute: ak.Array | None = None,
|
|
27
32
|
precompute_cutoff: float | None = None,
|
|
28
|
-
) ->
|
|
33
|
+
) -> ak.Array:
|
|
29
34
|
"""Computes the distance from each step to the detector surface.
|
|
30
35
|
|
|
31
36
|
The calculation can be performed for any surface type `nplus`, `pplus`,
|
|
@@ -44,32 +49,36 @@ def distance_to_surface(
|
|
|
44
49
|
hpge
|
|
45
50
|
HPGe object.
|
|
46
51
|
det_pos
|
|
47
|
-
position of the detector origin, must be a 3 component array corresponding to `(x,y,z)`.
|
|
52
|
+
position of the detector origin, must be a 3 component array corresponding to `(x,y,z)`. If no units
|
|
53
|
+
are specified mm is assumed.
|
|
48
54
|
surface_type
|
|
49
55
|
string of which surface to use, can be `nplus`, `pplus` `passive` or None (in which case the distance to any surface is calculated).
|
|
50
56
|
unit
|
|
51
57
|
unit for the hit tier positions table.
|
|
52
58
|
distances_precompute
|
|
53
|
-
|
|
59
|
+
Distance to any surface computed by remage.
|
|
54
60
|
precompute_cutoff
|
|
55
61
|
cutoff on distances_precompute to not compute the distance for (in mm)
|
|
56
62
|
|
|
57
63
|
Returns
|
|
58
64
|
-------
|
|
59
|
-
|
|
65
|
+
Distance to the surface for each hit with the same shape as `positions_x/y/z`.
|
|
60
66
|
|
|
61
67
|
Note
|
|
62
68
|
----
|
|
63
69
|
`positions_x/positions_y/positions_z` must all have the same shape.
|
|
64
70
|
"""
|
|
65
|
-
factor = np.array([1, 100, 1000])[unit == np.array(["mm", "cm", "m"])][0]
|
|
66
|
-
|
|
67
71
|
# compute local positions
|
|
68
72
|
pos = []
|
|
69
73
|
sizes = []
|
|
70
74
|
|
|
75
|
+
if not isinstance(det_pos, pint.Quantity | pyg4ometry.gdml.Position):
|
|
76
|
+
det_pos = det_pos * u.mm
|
|
77
|
+
|
|
78
|
+
det_pos = units.pg4_to_pint(det_pos)
|
|
79
|
+
|
|
71
80
|
for idx, pos_tmp in enumerate([positions_x, positions_y, positions_z]):
|
|
72
|
-
local_pos_tmp =
|
|
81
|
+
local_pos_tmp = units.units_conv_ak(pos_tmp, "mm") - det_pos[idx].to("mm").m
|
|
73
82
|
local_pos_flat_tmp = ak.flatten(local_pos_tmp).to_numpy()
|
|
74
83
|
pos.append(local_pos_flat_tmp)
|
|
75
84
|
sizes.append(ak.num(local_pos_tmp, axis=1))
|
|
@@ -106,7 +115,7 @@ def distance_to_surface(
|
|
|
106
115
|
local_positions[indices], surface_indices=surface_indices
|
|
107
116
|
)
|
|
108
117
|
|
|
109
|
-
return
|
|
118
|
+
return units.attach_units(ak.Array(ak.unflatten(distances, size)), "mm")
|
|
110
119
|
|
|
111
120
|
|
|
112
121
|
@numba.njit(cache=True)
|
|
@@ -222,7 +231,7 @@ def get_surface_response(
|
|
|
222
231
|
factor: float = 0.29,
|
|
223
232
|
nsteps: int = 10000,
|
|
224
233
|
delta_x: float = 10,
|
|
225
|
-
):
|
|
234
|
+
) -> NDArray:
|
|
226
235
|
"""Extract the surface response current pulse based on diffusion.
|
|
227
236
|
|
|
228
237
|
This extracts the amount of charge arrived (cumulative) at the p-n
|
|
@@ -248,6 +257,10 @@ def get_surface_response(
|
|
|
248
257
|
the number of time steps.
|
|
249
258
|
delta_x
|
|
250
259
|
the width of each position bin.
|
|
260
|
+
|
|
261
|
+
Returns
|
|
262
|
+
-------
|
|
263
|
+
2D array of the amount of charge arriving at the p-n junction as a function of time for each depth.
|
|
251
264
|
"""
|
|
252
265
|
# number of position steps
|
|
253
266
|
nx = int(fccd / delta_x)
|
|
@@ -5,14 +5,13 @@ import logging
|
|
|
5
5
|
import awkward as ak
|
|
6
6
|
import numpy as np
|
|
7
7
|
from lgdo import Array, VectorOfVectors
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
from .. import units
|
|
9
10
|
|
|
10
11
|
log = logging.getLogger(__name__)
|
|
11
12
|
|
|
12
13
|
|
|
13
|
-
def piecewise_linear_activeness(
|
|
14
|
-
distances: VectorOfVectors | ak.Array, fccd: float, dlf: float
|
|
15
|
-
) -> VectorOfVectors | Array:
|
|
14
|
+
def piecewise_linear_activeness(distances: ak.Array, fccd_in_mm: float, dlf: float) -> ak.Array:
|
|
16
15
|
r"""Piecewise linear HPGe activeness model.
|
|
17
16
|
|
|
18
17
|
Based on:
|
|
@@ -38,11 +37,10 @@ def piecewise_linear_activeness(
|
|
|
38
37
|
Parameters
|
|
39
38
|
----------
|
|
40
39
|
distances
|
|
41
|
-
the distance from each step to the detector surface.
|
|
42
|
-
`numpy` or `awkward` array, or a LGDO `VectorOfVectors` or `Array`. The computation
|
|
40
|
+
the distance from each step to the detector surface. The computation
|
|
43
41
|
is performed for each element and the shape preserved in the output.
|
|
44
42
|
|
|
45
|
-
|
|
43
|
+
fccd_in_mm
|
|
46
44
|
the value of the FCCD
|
|
47
45
|
dlf
|
|
48
46
|
the fraction of the FCCD which is fully inactive.
|
|
@@ -52,14 +50,9 @@ def piecewise_linear_activeness(
|
|
|
52
50
|
a :class:`VectorOfVectors` or :class:`Array` of the activeness
|
|
53
51
|
"""
|
|
54
52
|
# convert to ak
|
|
55
|
-
|
|
56
|
-
distances_ak = distances.view_as("ak")
|
|
57
|
-
elif not isinstance(distances, ak.Array):
|
|
58
|
-
distances_ak = ak.Array(distances)
|
|
59
|
-
else:
|
|
60
|
-
distances_ak = distances
|
|
53
|
+
distances_ak = units.units_conv_ak(distances, "mm")
|
|
61
54
|
|
|
62
|
-
dl =
|
|
55
|
+
dl = fccd_in_mm * dlf
|
|
63
56
|
distances_flat = (
|
|
64
57
|
ak.flatten(distances_ak).to_numpy() if distances_ak.ndim > 1 else distances_ak.to_numpy()
|
|
65
58
|
)
|
|
@@ -68,24 +61,26 @@ def piecewise_linear_activeness(
|
|
|
68
61
|
results = np.full_like(distances_flat, np.nan, dtype=np.float64)
|
|
69
62
|
lengths = ak.num(distances_ak) if distances_ak.ndim > 1 else len(distances_ak)
|
|
70
63
|
|
|
71
|
-
mask1 = (distances_flat >
|
|
64
|
+
mask1 = (distances_flat > fccd_in_mm) | np.isnan(distances_flat)
|
|
72
65
|
mask2 = (distances_flat <= dl) & (~mask1)
|
|
73
66
|
mask3 = ~(mask1 | mask2)
|
|
74
67
|
|
|
75
68
|
# assign the values
|
|
76
69
|
results[mask1] = 1
|
|
77
70
|
results[mask2] = 0
|
|
78
|
-
results[mask3] = (distances_flat[mask3] - dl) / (
|
|
71
|
+
results[mask3] = (distances_flat[mask3] - dl) / (fccd_in_mm - dl)
|
|
79
72
|
|
|
80
73
|
# reshape
|
|
81
74
|
results = ak.unflatten(ak.Array(results), lengths) if distances_ak.ndim > 1 else results
|
|
82
75
|
|
|
83
|
-
|
|
76
|
+
results = ak.Array(results)
|
|
77
|
+
|
|
78
|
+
return units.attach_units(results, "mm")
|
|
84
79
|
|
|
85
80
|
|
|
86
81
|
def vectorised_active_energy(
|
|
87
|
-
distances:
|
|
88
|
-
edep:
|
|
82
|
+
distances: ak.Array,
|
|
83
|
+
edep: ak.Array,
|
|
89
84
|
fccd: float | list,
|
|
90
85
|
dlf: float | list,
|
|
91
86
|
) -> VectorOfVectors | Array:
|
|
@@ -115,7 +110,7 @@ def vectorised_active_energy(
|
|
|
115
110
|
|
|
116
111
|
Returns
|
|
117
112
|
-------
|
|
118
|
-
|
|
113
|
+
Activeness for each set of parameters
|
|
119
114
|
"""
|
|
120
115
|
# add checks on fccd, dlf
|
|
121
116
|
fccd = np.array(fccd)
|
|
@@ -133,20 +128,14 @@ def vectorised_active_energy(
|
|
|
133
128
|
|
|
134
129
|
dl = fccd * dlf
|
|
135
130
|
|
|
136
|
-
def _convert(field):
|
|
131
|
+
def _convert(field, unit):
|
|
137
132
|
# convert to ak
|
|
138
|
-
|
|
139
|
-
field_ak = field.view_as("ak")
|
|
140
|
-
elif not isinstance(field, ak.Array):
|
|
141
|
-
field_ak = ak.Array(field)
|
|
142
|
-
else:
|
|
143
|
-
msg = f"{field} must be an awkward array or VectorOfVectors"
|
|
144
|
-
raise TypeError(msg)
|
|
133
|
+
field_ak = units.units_conv_ak(field, unit)
|
|
145
134
|
|
|
146
135
|
return field_ak, ak.flatten(field_ak).to_numpy()[:, np.newaxis]
|
|
147
136
|
|
|
148
|
-
distances_ak, distances_flat = _convert(distances)
|
|
149
|
-
_, edep_flat = _convert(edep)
|
|
137
|
+
distances_ak, distances_flat = _convert(distances, "mm")
|
|
138
|
+
_, edep_flat = _convert(edep, "keV")
|
|
150
139
|
runs = ak.num(distances_ak, axis=-1)
|
|
151
140
|
|
|
152
141
|
# vectorise fccd or tl
|
|
@@ -172,4 +161,4 @@ def vectorised_active_energy(
|
|
|
172
161
|
|
|
173
162
|
energy = ak.sum(ak.unflatten(results * edep_flat, runs), axis=-2)
|
|
174
163
|
|
|
175
|
-
return
|
|
164
|
+
return units.attach_units(energy, "keV")
|
|
@@ -6,7 +6,6 @@ from collections.abc import Callable
|
|
|
6
6
|
import awkward as ak
|
|
7
7
|
import numpy as np
|
|
8
8
|
from lgdo import Array
|
|
9
|
-
from numpy.typing import ArrayLike
|
|
10
9
|
|
|
11
10
|
log = logging.getLogger(__name__)
|
|
12
11
|
|
|
@@ -72,7 +71,7 @@ def apply_energy_resolution(
|
|
|
72
71
|
return ak.unflatten(energies_flat_smear, num)
|
|
73
72
|
|
|
74
73
|
|
|
75
|
-
def gaussian_sample(mu:
|
|
74
|
+
def gaussian_sample(mu: ak.Array, sigma: ak.Array | float, *, seed: int | None = None) -> ak.Array:
|
|
76
75
|
r"""Generate samples from a gaussian.
|
|
77
76
|
|
|
78
77
|
Based on:
|
|
@@ -99,9 +98,7 @@ def gaussian_sample(mu: ArrayLike, sigma: ArrayLike | float, *, seed: int | None
|
|
|
99
98
|
"""
|
|
100
99
|
# convert inputs
|
|
101
100
|
|
|
102
|
-
if isinstance(mu, Array):
|
|
103
|
-
mu = mu.view_as("np")
|
|
104
|
-
elif isinstance(mu, ak.Array):
|
|
101
|
+
if isinstance(mu, ak.Array):
|
|
105
102
|
mu = mu.to_numpy()
|
|
106
103
|
elif not isinstance(mu, np.ndarray):
|
|
107
104
|
mu = np.array(mu)
|
|
@@ -116,4 +113,4 @@ def gaussian_sample(mu: ArrayLike, sigma: ArrayLike | float, *, seed: int | None
|
|
|
116
113
|
|
|
117
114
|
rng = np.random.default_rng(seed=seed) # Create a random number generator
|
|
118
115
|
|
|
119
|
-
return Array(rng.normal(loc=mu, scale=sigma))
|
|
116
|
+
return ak.Array(rng.normal(loc=mu, scale=sigma))
|