reboost 0.8.5__tar.gz → 0.9.0__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.8.5/src/reboost.egg-info → reboost-0.9.0}/PKG-INFO +1 -1
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/_version.py +3 -3
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/hpge/psd.py +168 -104
- reboost-0.9.0/src/reboost/hpge/utils.py +165 -0
- {reboost-0.8.5 → reboost-0.9.0/src/reboost.egg-info}/PKG-INFO +1 -1
- {reboost-0.8.5 → reboost-0.9.0}/tests/conftest.py +44 -0
- {reboost-0.8.5 → reboost-0.9.0}/tests/hpge/test_current.py +35 -0
- {reboost-0.8.5 → reboost-0.9.0}/tests/hpge/test_dt_heuristic.py +2 -7
- {reboost-0.8.5 → reboost-0.9.0}/tests/hpge/test_hpge_map.py +17 -3
- reboost-0.8.5/src/reboost/hpge/utils.py +0 -79
- {reboost-0.8.5 → reboost-0.9.0}/LICENSE +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/README.md +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/pyproject.toml +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/setup.cfg +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/__init__.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/__main__.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/build_evt.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/build_glm.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/build_hit.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/cli.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/core.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/daq/__init__.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/daq/core.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/daq/utils.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/hpge/__init__.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/hpge/surface.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/iterator.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/log_utils.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/math/__init__.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/math/functions.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/math/stats.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/optmap/__init__.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/optmap/__main__.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/optmap/cli.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/optmap/convolve.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/optmap/create.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/optmap/evt.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/optmap/mapview.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/optmap/numba_pdg.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/optmap/optmap.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/profile.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/shape/__init__.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/shape/cluster.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/shape/group.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/shape/reduction.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/spms/__init__.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/spms/pe.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/units.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost/utils.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost.egg-info/SOURCES.txt +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost.egg-info/dependency_links.txt +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost.egg-info/entry_points.txt +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost.egg-info/not-zip-safe +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost.egg-info/requires.txt +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/src/reboost.egg-info/top_level.txt +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/tests/evt/test_evt.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/tests/glm/test_build_glm.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/tests/hit/configs/args.yaml +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/tests/hit/configs/basic.yaml +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/tests/hit/configs/foward_only.yaml +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/tests/hit/configs/geom.gdml +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/tests/hit/configs/hit_config.yaml +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/tests/hit/configs/pars.yaml +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/tests/hit/configs/reshape.yaml +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/tests/hit/configs/spms.yaml +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/tests/hit/test_build_hit.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/tests/hpge/simulation/gammas.mac +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/tests/hpge/simulation/geometry.gdml +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/tests/hpge/simulation/make_dt_map.jl +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/tests/hpge/simulation/make_geom.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/tests/hpge/test_files/drift_time_maps.lh5 +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/tests/hpge/test_files/internal_electron.lh5 +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/tests/hpge/test_r90.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/tests/hpge/test_surface.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/tests/spms/test_pe.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/tests/test_cli.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/tests/test_core.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/tests/test_math.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/tests/test_optmap.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/tests/test_optmap_dets.gdml +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/tests/test_profile.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/tests/test_shape.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/tests/test_units.py +0 -0
- {reboost-0.8.5 → reboost-0.9.0}/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.9.0
|
|
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
|
|
@@ -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.9.0'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 9, 0)
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'g0f7fc07b4'
|
|
@@ -13,7 +13,7 @@ from numpy.typing import ArrayLike, NDArray
|
|
|
13
13
|
|
|
14
14
|
from .. import units
|
|
15
15
|
from ..units import ureg as u
|
|
16
|
-
from .utils import
|
|
16
|
+
from .utils import HPGePulseShapeLibrary, HPGeRZField
|
|
17
17
|
|
|
18
18
|
log = logging.getLogger(__name__)
|
|
19
19
|
|
|
@@ -83,7 +83,7 @@ def drift_time(
|
|
|
83
83
|
xloc: ArrayLike,
|
|
84
84
|
yloc: ArrayLike,
|
|
85
85
|
zloc: ArrayLike,
|
|
86
|
-
dt_map:
|
|
86
|
+
dt_map: HPGeRZField,
|
|
87
87
|
coord_offset: pint.Quantity | pyg4ometry.gdml.Position = (0, 0, 0) * u.m,
|
|
88
88
|
) -> VectorOfVectors:
|
|
89
89
|
"""Calculates drift times for each step (cluster) in an HPGe detector.
|
|
@@ -454,33 +454,7 @@ def _get_waveform_value_surface(
|
|
|
454
454
|
start: float,
|
|
455
455
|
dt: float,
|
|
456
456
|
) -> tuple[float, float]:
|
|
457
|
-
"""Get the value of the waveform at a certain index.
|
|
458
|
-
|
|
459
|
-
Parameters
|
|
460
|
-
----------
|
|
461
|
-
idx
|
|
462
|
-
the index of the time array to find the waveform at.
|
|
463
|
-
edep
|
|
464
|
-
Array of energies for each step
|
|
465
|
-
drift_time
|
|
466
|
-
Array of drift times for each step
|
|
467
|
-
template
|
|
468
|
-
array of the template for the current waveforms
|
|
469
|
-
templates_surface
|
|
470
|
-
The current templates from the surface.
|
|
471
|
-
activeness_surface
|
|
472
|
-
The total collected charge for each surface point.
|
|
473
|
-
dist_step_in_um
|
|
474
|
-
The binning in distance for the surface pulse library.
|
|
475
|
-
start
|
|
476
|
-
first time value of the template
|
|
477
|
-
dt
|
|
478
|
-
timestep (in ns) for the template.
|
|
479
|
-
|
|
480
|
-
Returns
|
|
481
|
-
-------
|
|
482
|
-
Value of the current waveform and the energy.
|
|
483
|
-
"""
|
|
457
|
+
"""Get the value of the waveform at a certain index."""
|
|
484
458
|
n = len(bulk_template)
|
|
485
459
|
out = 0
|
|
486
460
|
etmp = 0
|
|
@@ -513,6 +487,7 @@ def _get_waveform_value_surface(
|
|
|
513
487
|
else:
|
|
514
488
|
out += E * _interpolate_pulse_model(bulk_template, time, start, start + dt * n, dt, mu)
|
|
515
489
|
etmp += E
|
|
490
|
+
|
|
516
491
|
return out, etmp
|
|
517
492
|
|
|
518
493
|
|
|
@@ -525,40 +500,78 @@ def _get_waveform_value(
|
|
|
525
500
|
start: float,
|
|
526
501
|
dt: float,
|
|
527
502
|
) -> float:
|
|
528
|
-
"""Get the value of the waveform at a certain index.
|
|
503
|
+
"""Get the value of the waveform at a certain index."""
|
|
504
|
+
out = 0
|
|
505
|
+
time = start + dt * idx
|
|
529
506
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
idx
|
|
533
|
-
the index of the time array to find the waveform at.
|
|
534
|
-
edep
|
|
535
|
-
Array of energies for each step
|
|
536
|
-
drift_time
|
|
537
|
-
Array of drift times for each step
|
|
538
|
-
template
|
|
539
|
-
array of the template for the current waveforms
|
|
540
|
-
start
|
|
541
|
-
first time value of the template
|
|
542
|
-
dt
|
|
543
|
-
timestep (in ns) for the template.
|
|
507
|
+
for i in range(len(edep)):
|
|
508
|
+
n = len(template)
|
|
544
509
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
510
|
+
E = edep[i]
|
|
511
|
+
mu = drift_time[i]
|
|
512
|
+
|
|
513
|
+
out += E * _interpolate_pulse_model(template, time, start, start + dt * n, dt, mu)
|
|
514
|
+
|
|
515
|
+
return out
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
@numba.njit(cache=True)
|
|
519
|
+
def _get_waveform_value_pulse_shape_library(
|
|
520
|
+
idx: int,
|
|
521
|
+
edep: ak.Array,
|
|
522
|
+
drift_time: ak.Array,
|
|
523
|
+
r: ak.Array,
|
|
524
|
+
z: ak.Array,
|
|
525
|
+
pulse_shape_library: tuple[np.array, np.array, np.array],
|
|
526
|
+
start: float,
|
|
527
|
+
dt: float,
|
|
528
|
+
) -> float:
|
|
529
|
+
"""Get the value of the waveform at a certain index, using the pulse shape library."""
|
|
550
530
|
out = 0
|
|
551
531
|
time = start + dt * idx
|
|
552
532
|
|
|
553
533
|
for i in range(len(edep)):
|
|
534
|
+
ri, zi = _get_template_idx(r[i], z[i], pulse_shape_library[1], pulse_shape_library[2])
|
|
535
|
+
|
|
536
|
+
n = len(pulse_shape_library[0][ri][zi])
|
|
537
|
+
|
|
554
538
|
E = edep[i]
|
|
555
539
|
mu = drift_time[i]
|
|
556
540
|
|
|
557
|
-
out += E * _interpolate_pulse_model(
|
|
541
|
+
out += E * _interpolate_pulse_model(
|
|
542
|
+
pulse_shape_library[0][ri][zi], time, start, start + dt * n, dt, mu
|
|
543
|
+
)
|
|
558
544
|
|
|
559
545
|
return out
|
|
560
546
|
|
|
561
547
|
|
|
548
|
+
@numba.njit(cache=True)
|
|
549
|
+
def _get_template_idx(
|
|
550
|
+
r: float,
|
|
551
|
+
z: float,
|
|
552
|
+
r_grid: np.array,
|
|
553
|
+
z_grid: np.array,
|
|
554
|
+
) -> tuple[int, int]:
|
|
555
|
+
"""Extract the closest template to a given (r,z) point with uniform grid, apart from the first and last point."""
|
|
556
|
+
if r < r_grid[1]:
|
|
557
|
+
ri = 0
|
|
558
|
+
elif r > r_grid[-2]:
|
|
559
|
+
ri = len(r_grid) - 1
|
|
560
|
+
else:
|
|
561
|
+
dr = r_grid[2] - r_grid[1]
|
|
562
|
+
ri = int((r - r_grid[1]) / dr) + 1
|
|
563
|
+
|
|
564
|
+
if z < z_grid[1]:
|
|
565
|
+
zi = 0
|
|
566
|
+
elif z > z_grid[-2]:
|
|
567
|
+
zi = len(z_grid) - 1
|
|
568
|
+
else:
|
|
569
|
+
dz = z_grid[2] - z_grid[1]
|
|
570
|
+
zi = int((z - z_grid[1]) / dz) + 1
|
|
571
|
+
|
|
572
|
+
return ri, zi
|
|
573
|
+
|
|
574
|
+
|
|
562
575
|
def get_current_template(
|
|
563
576
|
low: float = -1000, high: float = 4000, step: float = 1, mean_aoe: float = 1, **kwargs
|
|
564
577
|
) -> tuple[NDArray, NDArray]:
|
|
@@ -598,7 +611,10 @@ def _get_waveform_maximum_impl(
|
|
|
598
611
|
t: ArrayLike,
|
|
599
612
|
e: ArrayLike,
|
|
600
613
|
dist: ArrayLike,
|
|
614
|
+
r: ArrayLike,
|
|
615
|
+
z: ArrayLike,
|
|
601
616
|
template: ArrayLike,
|
|
617
|
+
pulse_shape_library: tuple[np.array, np.array, np.array],
|
|
602
618
|
templates_surface: ArrayLike,
|
|
603
619
|
activeness_surface: ArrayLike,
|
|
604
620
|
tmin: float,
|
|
@@ -609,18 +625,9 @@ def _get_waveform_maximum_impl(
|
|
|
609
625
|
time_step: int,
|
|
610
626
|
surface_step_in_um: float,
|
|
611
627
|
include_surface_effects: bool,
|
|
628
|
+
use_library: bool,
|
|
612
629
|
):
|
|
613
|
-
"""Basic implementation to get the maximum of the waveform.
|
|
614
|
-
|
|
615
|
-
Parameters
|
|
616
|
-
----------
|
|
617
|
-
t
|
|
618
|
-
drift time for each step.
|
|
619
|
-
e
|
|
620
|
-
energy for each step.
|
|
621
|
-
dist
|
|
622
|
-
distance to surface for each step.
|
|
623
|
-
"""
|
|
630
|
+
"""Basic implementation to get the maximum of the waveform."""
|
|
624
631
|
max_a = 0
|
|
625
632
|
max_t = 0
|
|
626
633
|
energy = np.sum(e)
|
|
@@ -634,8 +641,12 @@ def _get_waveform_maximum_impl(
|
|
|
634
641
|
if time < tmin or (time > (tmax + time_step)):
|
|
635
642
|
continue
|
|
636
643
|
|
|
637
|
-
if not has_surface_hit:
|
|
644
|
+
if not has_surface_hit and (not use_library):
|
|
638
645
|
val_tmp = _get_waveform_value(j, e, t, template, start=start, dt=1.0)
|
|
646
|
+
elif use_library:
|
|
647
|
+
val_tmp = _get_waveform_value_pulse_shape_library(
|
|
648
|
+
j, e, t, r, z, pulse_shape_library, start=start, dt=1.0
|
|
649
|
+
)
|
|
639
650
|
else:
|
|
640
651
|
val_tmp, energy = _get_waveform_value_surface(
|
|
641
652
|
j,
|
|
@@ -663,9 +674,13 @@ def _estimate_current_impl(
|
|
|
663
674
|
edep: ak.Array,
|
|
664
675
|
dt: ak.Array,
|
|
665
676
|
dist_to_nplus: ak.Array,
|
|
677
|
+
r: ak.Array,
|
|
678
|
+
z: ak.Array,
|
|
666
679
|
template: np.array,
|
|
680
|
+
pulse_shape_library: tuple[np.array, np.array, np.array],
|
|
667
681
|
times: np.array,
|
|
668
682
|
include_surface_effects: bool,
|
|
683
|
+
use_library: bool,
|
|
669
684
|
fccd: float,
|
|
670
685
|
templates_surface: np.array,
|
|
671
686
|
activeness_surface: np.array,
|
|
@@ -674,26 +689,13 @@ def _estimate_current_impl(
|
|
|
674
689
|
"""Estimate the maximum current that would be measured in the HPGe detector.
|
|
675
690
|
|
|
676
691
|
This is based on extracting a waveform with :func:`get_current_waveform` and finding the maxima of it.
|
|
677
|
-
|
|
678
|
-
Parameters
|
|
679
|
-
----------
|
|
680
|
-
edep
|
|
681
|
-
Array of energies for each step.
|
|
682
|
-
dt
|
|
683
|
-
Array of drift times for each step.
|
|
684
|
-
dist_to_nplus
|
|
685
|
-
Array of distance to nplus contact for each step (can be `None`, in which case no surface effects are included.)
|
|
686
|
-
template
|
|
687
|
-
array of the bulk pulse template
|
|
688
|
-
times
|
|
689
|
-
time-stamps for the bulk pulse template
|
|
690
692
|
"""
|
|
691
693
|
A = np.zeros(len(dt))
|
|
692
694
|
maximum_t = np.zeros(len(dt))
|
|
693
695
|
energy = np.zeros(len(dt))
|
|
694
696
|
|
|
695
697
|
time_step = 1
|
|
696
|
-
n = len(
|
|
698
|
+
n = len(times)
|
|
697
699
|
start = times[0]
|
|
698
700
|
|
|
699
701
|
if include_surface_effects:
|
|
@@ -707,6 +709,9 @@ def _estimate_current_impl(
|
|
|
707
709
|
for i in range(len(dt)):
|
|
708
710
|
t = np.asarray(dt[i])
|
|
709
711
|
e = np.asarray(edep[i])
|
|
712
|
+
r_tmp = np.asarray(r[i])
|
|
713
|
+
z_tmp = np.asarray(z[i])
|
|
714
|
+
|
|
710
715
|
dist = np.asarray(dist_to_nplus[i])
|
|
711
716
|
|
|
712
717
|
# get the expected maximum
|
|
@@ -720,7 +725,6 @@ def _estimate_current_impl(
|
|
|
720
725
|
for j, d in enumerate(dist):
|
|
721
726
|
dtmp = int(d / surface_step_in_um)
|
|
722
727
|
|
|
723
|
-
# Use branchless selection
|
|
724
728
|
use_offset = dtmp <= ncols
|
|
725
729
|
offset_val = offsets[dtmp] if use_offset else 0.0
|
|
726
730
|
time_tmp = t[j] + offset_val * use_offset
|
|
@@ -737,9 +741,12 @@ def _estimate_current_impl(
|
|
|
737
741
|
t,
|
|
738
742
|
e,
|
|
739
743
|
dist,
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
744
|
+
r=r_tmp,
|
|
745
|
+
z=z_tmp,
|
|
746
|
+
template=template,
|
|
747
|
+
pulse_shape_library=pulse_shape_library,
|
|
748
|
+
templates_surface=templates_surface,
|
|
749
|
+
activeness_surface=activeness_surface,
|
|
743
750
|
tmin=tmin,
|
|
744
751
|
tmax=tmax,
|
|
745
752
|
start=start,
|
|
@@ -748,17 +755,73 @@ def _estimate_current_impl(
|
|
|
748
755
|
time_step=time_step,
|
|
749
756
|
surface_step_in_um=surface_step_in_um,
|
|
750
757
|
include_surface_effects=include_surface_effects,
|
|
758
|
+
use_library=use_library,
|
|
751
759
|
)
|
|
752
760
|
|
|
753
761
|
return A, maximum_t, energy
|
|
754
762
|
|
|
755
763
|
|
|
764
|
+
def prepare_surface_inputs(
|
|
765
|
+
dist_to_nplus: ak.Array,
|
|
766
|
+
edep: ak.Array,
|
|
767
|
+
templates_surface: ArrayLike,
|
|
768
|
+
activeness_surface,
|
|
769
|
+
template: ArrayLike,
|
|
770
|
+
) -> tuple:
|
|
771
|
+
"""Prepare the inputs needed for surface sims."""
|
|
772
|
+
include_surface_effects = False
|
|
773
|
+
|
|
774
|
+
# prepare surface templates
|
|
775
|
+
if templates_surface is not None:
|
|
776
|
+
if dist_to_nplus is None:
|
|
777
|
+
msg = "Surface effects requested but distance not provided"
|
|
778
|
+
raise ValueError(msg)
|
|
779
|
+
|
|
780
|
+
include_surface_effects = True
|
|
781
|
+
else:
|
|
782
|
+
# convert types to keep numba happy
|
|
783
|
+
templates_surface = np.zeros((1, len(template)))
|
|
784
|
+
dist_to_nplus = ak.full_like(edep, np.nan)
|
|
785
|
+
|
|
786
|
+
# convert types for numba
|
|
787
|
+
if activeness_surface is None:
|
|
788
|
+
activeness_surface = np.zeros(len(template))
|
|
789
|
+
|
|
790
|
+
return include_surface_effects, dist_to_nplus, templates_surface, activeness_surface
|
|
791
|
+
|
|
792
|
+
|
|
793
|
+
def prepare_pulse_shape_library(
|
|
794
|
+
template: ArrayLike | HPGePulseShapeLibrary,
|
|
795
|
+
times: ArrayLike,
|
|
796
|
+
edep: ak.Array,
|
|
797
|
+
r: ak.Array,
|
|
798
|
+
z: ak.Array,
|
|
799
|
+
):
|
|
800
|
+
"""Prepare the inputs for the full pulse shape library."""
|
|
801
|
+
use_library = False
|
|
802
|
+
if isinstance(template, HPGePulseShapeLibrary):
|
|
803
|
+
# convert to a form we can use
|
|
804
|
+
times = template.t
|
|
805
|
+
pulse_shape_library = (template.waveforms, template.r, template.z)
|
|
806
|
+
template = np.zeros_like(template.waveforms[0][0])
|
|
807
|
+
use_library = True
|
|
808
|
+
|
|
809
|
+
else:
|
|
810
|
+
pulse_shape_library = (np.zeros((1, 1, len(template))), np.zeros(1), np.zeros(1))
|
|
811
|
+
r = ak.full_like(edep, np.nan)
|
|
812
|
+
z = ak.full_like(edep, np.nan)
|
|
813
|
+
|
|
814
|
+
return use_library, pulse_shape_library, template, times, r, z
|
|
815
|
+
|
|
816
|
+
|
|
756
817
|
def maximum_current(
|
|
757
818
|
edep: ArrayLike,
|
|
758
819
|
drift_time: ArrayLike,
|
|
759
820
|
dist_to_nplus: ArrayLike | None = None,
|
|
821
|
+
r: ArrayLike | None = None,
|
|
822
|
+
z: ArrayLike | None = None,
|
|
760
823
|
*,
|
|
761
|
-
template: np.array,
|
|
824
|
+
template: np.array | HPGePulseShapeLibrary,
|
|
762
825
|
times: np.array,
|
|
763
826
|
fccd_in_um: float = 0,
|
|
764
827
|
templates_surface: ArrayLike | None = None,
|
|
@@ -776,16 +839,22 @@ def maximum_current(
|
|
|
776
839
|
Array of drift times for each step.
|
|
777
840
|
dist_to_nplus
|
|
778
841
|
Distance to n-plus electrode, only needed if surface heuristics are enabled.
|
|
842
|
+
r
|
|
843
|
+
Radial coordinate (only needed if a full PSS library is used)
|
|
844
|
+
z
|
|
845
|
+
z coordinate (only needed if a full PSS library is used).
|
|
779
846
|
template
|
|
780
|
-
array of the bulk pulse template
|
|
847
|
+
array of the bulk pulse template, can also be a :class:`HPGePulseShapeLibrary`.
|
|
781
848
|
times
|
|
782
849
|
time-stamps for the bulk pulse template
|
|
783
|
-
|
|
850
|
+
fccd_in_um
|
|
784
851
|
Value of the full-charge-collection depth, if `None` no surface corrections are performed.
|
|
785
|
-
|
|
852
|
+
templates_surface
|
|
786
853
|
2D array (distance, time) of the rate of charge arriving at the p-n junction. Each row
|
|
787
854
|
should be an array of length 10000 giving the charge arriving at the p-n junction for each timestep
|
|
788
855
|
(in ns). This is produced by :func:`.hpge.surface.get_surface_response` or other libraries.
|
|
856
|
+
activeness_surface
|
|
857
|
+
An array of the activeness at each surface point.
|
|
789
858
|
surface_step_in_um
|
|
790
859
|
Distance step for the surface library.
|
|
791
860
|
return_mode
|
|
@@ -796,40 +865,35 @@ def maximum_current(
|
|
|
796
865
|
An Array of the maximum current/ time / energy for each hit.
|
|
797
866
|
"""
|
|
798
867
|
# extract LGDO data and units
|
|
799
|
-
|
|
800
868
|
drift_time, _ = units.unwrap_lgdo(drift_time)
|
|
801
869
|
edep, _ = units.unwrap_lgdo(edep)
|
|
802
870
|
dist_to_nplus, _ = units.unwrap_lgdo(dist_to_nplus)
|
|
871
|
+
r, _ = units.unwrap_lgdo(r)
|
|
872
|
+
z, _ = units.unwrap_lgdo(z)
|
|
803
873
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
msg = "Surface effects requested but distance not provided"
|
|
809
|
-
raise ValueError(msg)
|
|
810
|
-
|
|
811
|
-
include_surface_effects = True
|
|
812
|
-
else:
|
|
813
|
-
# convert types to keep numba happy
|
|
814
|
-
templates_surface = np.zeros((1, len(template)))
|
|
815
|
-
dist_to_nplus = ak.full_like(edep, np.nan)
|
|
816
|
-
|
|
817
|
-
# convert types for numba
|
|
818
|
-
if activeness_surface is None:
|
|
819
|
-
activeness_surface = np.zeros(len(template))
|
|
874
|
+
# prepare inputs for surface sims
|
|
875
|
+
include_surface_effects, dist_to_nplus, templates_surface, activeness_surface = (
|
|
876
|
+
prepare_surface_inputs(dist_to_nplus, edep, templates_surface, activeness_surface, template)
|
|
877
|
+
)
|
|
820
878
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
879
|
+
# and for the full PS library
|
|
880
|
+
use_library, pulse_shape_library, template, times, r, z = prepare_pulse_shape_library(
|
|
881
|
+
template, times, edep, r, z
|
|
882
|
+
)
|
|
824
883
|
|
|
884
|
+
# and now compute the current
|
|
825
885
|
curr, time, energy = _estimate_current_impl(
|
|
826
886
|
ak.values_astype(ak.Array(edep), np.float64),
|
|
827
887
|
ak.values_astype(ak.Array(drift_time), np.float64),
|
|
828
888
|
ak.values_astype(ak.Array(dist_to_nplus), np.float64),
|
|
889
|
+
r=ak.values_astype(ak.Array(r), np.float64),
|
|
890
|
+
z=ak.values_astype(ak.Array(z), np.float64),
|
|
829
891
|
template=template,
|
|
892
|
+
pulse_shape_library=pulse_shape_library,
|
|
830
893
|
times=times,
|
|
831
894
|
fccd=fccd_in_um,
|
|
832
895
|
include_surface_effects=include_surface_effects,
|
|
896
|
+
use_library=use_library,
|
|
833
897
|
templates_surface=templates_surface,
|
|
834
898
|
activeness_surface=activeness_surface,
|
|
835
899
|
surface_step_in_um=surface_step_in_um,
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from typing import NamedTuple
|
|
5
|
+
|
|
6
|
+
import lgdo
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pint
|
|
9
|
+
from dbetto import AttrsDict
|
|
10
|
+
from lgdo import lh5
|
|
11
|
+
from scipy.interpolate import RegularGridInterpolator
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class HPGePulseShapeLibrary(NamedTuple):
|
|
15
|
+
"""A set of templates defined in the cylindrical-like (r, z) HPGe plane."""
|
|
16
|
+
|
|
17
|
+
waveforms: np.array
|
|
18
|
+
"Field, function of the coordinates (r, z)."
|
|
19
|
+
r_units: pint.Unit
|
|
20
|
+
"Physical units of the coordinate `r`."
|
|
21
|
+
z_units: pint.Unit
|
|
22
|
+
"Physical units of the coordinate `z`."
|
|
23
|
+
t_units: pint.Unit
|
|
24
|
+
"Physical units of the times."
|
|
25
|
+
r: np.array
|
|
26
|
+
"One dimensional arrays specifying the radial coordinates"
|
|
27
|
+
z: np.array
|
|
28
|
+
"One dimensional arrays specifying the z coordinates"
|
|
29
|
+
t: np.array
|
|
30
|
+
"Times used to define the waveforms"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_hpge_pulse_shape_library(
|
|
34
|
+
filename: str, obj: str, field: str, out_of_bounds_val: int | float = np.nan
|
|
35
|
+
) -> HPGePulseShapeLibrary:
|
|
36
|
+
"""Create the pulse shape library, holding simulated waveforms.
|
|
37
|
+
|
|
38
|
+
Reads from disk the following data structure: ::
|
|
39
|
+
|
|
40
|
+
FILENAME/
|
|
41
|
+
└── OBJ · struct{r,z,dt,t0,FIELD}
|
|
42
|
+
├── r · array<1>{real} ── {'units': 'UNITS'}
|
|
43
|
+
├── z · array<1>{real} ── {'units': 'UNITS'}
|
|
44
|
+
├── dt · real ── {'units': 'UNITS'}
|
|
45
|
+
├── t0 · real ── {'units': 'UNITS'}
|
|
46
|
+
└── FIELD · array<3>{real} ── {'units': 'UNITS'}
|
|
47
|
+
|
|
48
|
+
The conventions follow those used for :func:`get_hpge_rz_field`.
|
|
49
|
+
For the FIELD the first and second dimensions are `r` and `z`, respectively, with the last
|
|
50
|
+
dimension representing the waveform. dt and t0 define the timestamps for the waveforms.
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
Parameters
|
|
54
|
+
----------
|
|
55
|
+
filename
|
|
56
|
+
name of the LH5 file containing the gridded scalar field.
|
|
57
|
+
obj
|
|
58
|
+
name of the HDF5 dataset where the data is saved.
|
|
59
|
+
field
|
|
60
|
+
name of the HDF5 dataset holding the waveforms.
|
|
61
|
+
out_of_bounds_val
|
|
62
|
+
value to use to replace NaNs in the field values.
|
|
63
|
+
"""
|
|
64
|
+
data = lh5.read(obj, filename)
|
|
65
|
+
|
|
66
|
+
if not isinstance(data, lgdo.Struct):
|
|
67
|
+
msg = f"{obj} in {filename} is not an LGDO Struct"
|
|
68
|
+
raise ValueError(msg)
|
|
69
|
+
|
|
70
|
+
t0 = data["t0"].value
|
|
71
|
+
dt = data["dt"].value
|
|
72
|
+
|
|
73
|
+
t0_u = data["t0"].attrs["units"]
|
|
74
|
+
dt_u = data["dt"].attrs["units"]
|
|
75
|
+
|
|
76
|
+
if t0_u != dt_u:
|
|
77
|
+
msg = "t0 and dt must have the same units"
|
|
78
|
+
raise ValueError(msg)
|
|
79
|
+
|
|
80
|
+
tu = t0_u
|
|
81
|
+
|
|
82
|
+
data = AttrsDict(
|
|
83
|
+
{
|
|
84
|
+
k: np.nan_to_num(data[k].view_as("np", with_units=True), nan=out_of_bounds_val)
|
|
85
|
+
for k in ("r", "z", field)
|
|
86
|
+
}
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
times = t0 + np.arange(np.shape(data[field].m)[2]) * dt
|
|
90
|
+
|
|
91
|
+
return HPGePulseShapeLibrary(data[field].m, data.r.u, data.z.u, tu, data.r.m, data.z.m, times)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class HPGeRZField(NamedTuple):
|
|
95
|
+
"""A field defined in the cylindrical-like (r, z) HPGe plane."""
|
|
96
|
+
|
|
97
|
+
φ: Callable
|
|
98
|
+
"Field, function of the coordinates (r, z)."
|
|
99
|
+
r_units: pint.Unit
|
|
100
|
+
"Physical units of the coordinate `r`."
|
|
101
|
+
z_units: pint.Unit
|
|
102
|
+
"Physical units of the coordinate `z`."
|
|
103
|
+
φ_units: pint.Unit
|
|
104
|
+
"Physical units of the field."
|
|
105
|
+
ndim: int
|
|
106
|
+
"Number of dimensions for the field"
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def get_hpge_rz_field(
|
|
110
|
+
filename: str, obj: str, field: str, out_of_bounds_val: int | float = np.nan, **kwargs
|
|
111
|
+
) -> HPGeRZField:
|
|
112
|
+
"""Create an interpolator for a gridded HPGe field defined on `(r, z)`.
|
|
113
|
+
|
|
114
|
+
Reads from disk the following data structure: ::
|
|
115
|
+
|
|
116
|
+
FILENAME/
|
|
117
|
+
└── OBJ · struct{r,z,FIELD}
|
|
118
|
+
├── r · array<1>{real} ── {'units': 'UNITS'}
|
|
119
|
+
├── z · array<1>{real} ── {'units': 'UNITS'}
|
|
120
|
+
└── FIELD · array<N+2>{real} ── {'units': 'UNITS'}
|
|
121
|
+
|
|
122
|
+
where ``FILENAME``, ``OBJ`` and ``FIELD`` are provided as
|
|
123
|
+
arguments to this function. `obj` is a :class:`~lgdo.types.struct.Struct`,
|
|
124
|
+
`r` and `z` are one dimensional arrays specifying the radial and z
|
|
125
|
+
coordinates of the rectangular grid — not the coordinates of each single
|
|
126
|
+
grid point. In this coordinate system, the center of the p+ contact surface
|
|
127
|
+
is at `(0, 0)`, with the p+ contact facing downwards. `field` is instead a
|
|
128
|
+
ndim plus two-dimensional array specifying the field value at each grid point. The
|
|
129
|
+
first and second dimensions are `r` and `z`, respectively, with the latter dimensions
|
|
130
|
+
representing the dimensions of the output field.
|
|
131
|
+
|
|
132
|
+
NaN values are interpreted as points outside the detector profile in the `(r, z)` plane.
|
|
133
|
+
|
|
134
|
+
Before returning a :class:`HPGeScalarRZField`, the gridded field is fed to
|
|
135
|
+
:class:`scipy.interpolate.RegularGridInterpolator`.
|
|
136
|
+
|
|
137
|
+
Parameters
|
|
138
|
+
----------
|
|
139
|
+
filename
|
|
140
|
+
name of the LH5 file containing the gridded scalar field.
|
|
141
|
+
obj
|
|
142
|
+
name of the HDF5 dataset where the data is saved.
|
|
143
|
+
field
|
|
144
|
+
name of the HDF5 dataset holding the field values.
|
|
145
|
+
out_of_bounds_val
|
|
146
|
+
value to use to replace NaNs in the field values.
|
|
147
|
+
"""
|
|
148
|
+
data = lh5.read(obj, filename)
|
|
149
|
+
|
|
150
|
+
if not isinstance(data, lgdo.Struct):
|
|
151
|
+
msg = f"{obj} in {filename} is not an LGDO Struct"
|
|
152
|
+
raise ValueError(msg)
|
|
153
|
+
|
|
154
|
+
data = AttrsDict(
|
|
155
|
+
{
|
|
156
|
+
k: np.nan_to_num(data[k].view_as("np", with_units=True), nan=out_of_bounds_val)
|
|
157
|
+
for k in ("r", "z", field)
|
|
158
|
+
}
|
|
159
|
+
)
|
|
160
|
+
ndim = data[field].m.ndim - 2
|
|
161
|
+
interpolator = RegularGridInterpolator(
|
|
162
|
+
(data.r.m, data.z.m), data[field].m, **(kwargs | {"fill_value": out_of_bounds_val})
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
return HPGeRZField(interpolator, data.r.u, data.z.u, data[field].u, ndim)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: reboost
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
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
|
|
@@ -7,8 +7,12 @@ from pathlib import Path
|
|
|
7
7
|
from tempfile import gettempdir
|
|
8
8
|
|
|
9
9
|
import numba
|
|
10
|
+
import numpy as np
|
|
10
11
|
import pytest
|
|
11
12
|
from legendtestdata import LegendTestData
|
|
13
|
+
from lgdo import Array, Scalar, Struct, lh5
|
|
14
|
+
|
|
15
|
+
from reboost.hpge import psd
|
|
12
16
|
|
|
13
17
|
_tmptestdir = Path(gettempdir()) / f"reboost-tests-{getuser()}-{uuid.uuid4()!s}"
|
|
14
18
|
|
|
@@ -49,4 +53,44 @@ def patch_numba_for_tests():
|
|
|
49
53
|
numba.njit = njit_patched
|
|
50
54
|
|
|
51
55
|
|
|
56
|
+
@pytest.fixture(scope="module")
|
|
57
|
+
def test_pulse_shape_library(tmptestdir):
|
|
58
|
+
model, _ = psd.get_current_template(
|
|
59
|
+
-1000,
|
|
60
|
+
3000,
|
|
61
|
+
1.0,
|
|
62
|
+
amax=1,
|
|
63
|
+
mean_aoe=1,
|
|
64
|
+
mu=0,
|
|
65
|
+
sigma=100,
|
|
66
|
+
tau=100,
|
|
67
|
+
tail_fraction=0.65,
|
|
68
|
+
high_tail_fraction=0.1,
|
|
69
|
+
high_tau=10,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# loop
|
|
73
|
+
r = z = np.linspace(0, 100, 200)
|
|
74
|
+
waveforms = np.zeros((200, 200, 4001))
|
|
75
|
+
for i in range(200):
|
|
76
|
+
for j in range(200):
|
|
77
|
+
waveforms[i, j] = model
|
|
78
|
+
|
|
79
|
+
t0 = -1000
|
|
80
|
+
dt = 1
|
|
81
|
+
|
|
82
|
+
res = Struct(
|
|
83
|
+
{
|
|
84
|
+
"r": Array(r, attrs={"units": "mm"}),
|
|
85
|
+
"z": Array(z, attrs={"units": "mm"}),
|
|
86
|
+
"waveforms": Array(waveforms, attrs={"units": ""}),
|
|
87
|
+
"dt": Scalar(dt, attrs={"units": "ns"}),
|
|
88
|
+
"t0": Scalar(t0, attrs={"units": "ns"}),
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
lh5.write(res, "V01", f"{tmptestdir}/pulse_shape_lib.lh5")
|
|
92
|
+
|
|
93
|
+
return f"{tmptestdir}/pulse_shape_lib.lh5"
|
|
94
|
+
|
|
95
|
+
|
|
52
96
|
patch_numba_for_tests()
|
|
@@ -6,6 +6,8 @@ import pytest
|
|
|
6
6
|
from lgdo import Array, VectorOfVectors
|
|
7
7
|
|
|
8
8
|
from reboost.hpge import psd, surface
|
|
9
|
+
from reboost.hpge.psd import _get_template_idx
|
|
10
|
+
from reboost.hpge.utils import get_hpge_pulse_shape_library
|
|
9
11
|
from reboost.shape import cluster
|
|
10
12
|
|
|
11
13
|
|
|
@@ -46,6 +48,14 @@ def test_model():
|
|
|
46
48
|
return model, x
|
|
47
49
|
|
|
48
50
|
|
|
51
|
+
def test_get_template(test_pulse_shape_library):
|
|
52
|
+
lib = get_hpge_pulse_shape_library(test_pulse_shape_library, "V01", "waveforms")
|
|
53
|
+
|
|
54
|
+
ri, zi = _get_template_idx(10, 10, lib.r, lib.z)
|
|
55
|
+
|
|
56
|
+
assert len(lib.waveforms[ri][zi]) == 4001
|
|
57
|
+
|
|
58
|
+
|
|
49
59
|
def test_maximum_current(test_model):
|
|
50
60
|
model, x = test_model
|
|
51
61
|
|
|
@@ -183,3 +193,28 @@ def test_maximum_current_surface(test_model):
|
|
|
183
193
|
|
|
184
194
|
# surface effects reduce the current
|
|
185
195
|
assert np.all(curr_surf < curr_bulk)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def test_maximum_current_library(test_pulse_shape_library):
|
|
199
|
+
lib = get_hpge_pulse_shape_library(test_pulse_shape_library, "V01", "waveforms")
|
|
200
|
+
|
|
201
|
+
model = lib.waveforms[0][0]
|
|
202
|
+
x = lib.t
|
|
203
|
+
|
|
204
|
+
edep = VectorOfVectors(
|
|
205
|
+
ak.Array([[100.0, 300.0, 50.0], [10.0, 0.0, 100.0], [500.0]]), attrs={"unit": "keV"}
|
|
206
|
+
)
|
|
207
|
+
times = VectorOfVectors(
|
|
208
|
+
ak.Array([[400, 500, 700], [800, 0, 1500], [700]], attrs={"unit": "ns"})
|
|
209
|
+
)
|
|
210
|
+
r = VectorOfVectors(
|
|
211
|
+
ak.Array([[20.0, 10.0, 5.0], [10.0, 1.0, 0.0], [70.0]]), attrs={"unit": "mm"}
|
|
212
|
+
)
|
|
213
|
+
z = VectorOfVectors(
|
|
214
|
+
ak.Array([[40.0, 2.0, 25.0], [22.0, 4.0, 1.2], [20.0]]), attrs={"unit": "mm"}
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
curr = psd.maximum_current(edep, times, template=model, times=x).view_as("ak")
|
|
218
|
+
curr2 = psd.maximum_current(edep, times, r=r, z=z, template=lib, times=x).view_as("ak")
|
|
219
|
+
|
|
220
|
+
assert ak.all(curr == curr2)
|
|
@@ -80,7 +80,7 @@ gamma_stp = Table(
|
|
|
80
80
|
|
|
81
81
|
@pytest.fixture(scope="module")
|
|
82
82
|
def dt_map(legendtestdata):
|
|
83
|
-
return utils.
|
|
83
|
+
return utils.get_hpge_rz_field(
|
|
84
84
|
legendtestdata["lh5/hpge-drift-time-maps.lh5"],
|
|
85
85
|
"V99000A",
|
|
86
86
|
"drift_time",
|
|
@@ -114,12 +114,7 @@ def dt_map_dummy(legendtestdata):
|
|
|
114
114
|
drift_time,
|
|
115
115
|
)
|
|
116
116
|
|
|
117
|
-
return utils.
|
|
118
|
-
interpolator,
|
|
119
|
-
data.r.u,
|
|
120
|
-
data.z.u,
|
|
121
|
-
u.us,
|
|
122
|
-
)
|
|
117
|
+
return utils.HPGeRZField(interpolator, data.r.u, data.z.u, u.us, 1)
|
|
123
118
|
|
|
124
119
|
|
|
125
120
|
def test_drift_time_dummy(dt_map_dummy):
|
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import numpy as np
|
|
3
4
|
import pytest
|
|
4
5
|
from scipy.interpolate import RegularGridInterpolator
|
|
5
6
|
|
|
6
|
-
from reboost.hpge.utils import
|
|
7
|
+
from reboost.hpge.utils import (
|
|
8
|
+
HPGePulseShapeLibrary,
|
|
9
|
+
HPGeRZField,
|
|
10
|
+
get_hpge_pulse_shape_library,
|
|
11
|
+
get_hpge_rz_field,
|
|
12
|
+
)
|
|
7
13
|
from reboost.units import ureg as u
|
|
8
14
|
|
|
9
15
|
|
|
10
16
|
def test_read_hpge_map(legendtestdata):
|
|
11
|
-
dt_map =
|
|
17
|
+
dt_map = get_hpge_rz_field(
|
|
12
18
|
legendtestdata["lh5/hpge-drift-time-maps.lh5"],
|
|
13
19
|
"V99000A",
|
|
14
20
|
"drift_time",
|
|
15
21
|
out_of_bounds_val=0,
|
|
16
22
|
)
|
|
17
23
|
|
|
18
|
-
assert isinstance(dt_map,
|
|
24
|
+
assert isinstance(dt_map, HPGeRZField)
|
|
19
25
|
|
|
20
26
|
assert dt_map.r_units == u.m
|
|
21
27
|
assert dt_map.z_units == u.m
|
|
@@ -28,3 +34,11 @@ def test_read_hpge_map(legendtestdata):
|
|
|
28
34
|
|
|
29
35
|
assert dt_map.φ((0, 0)) == 0
|
|
30
36
|
assert dt_map.φ([(0, 0.01), (0.03, 0.03)]) == pytest.approx([135, 695])
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_read_pulse_shape_library(test_pulse_shape_library):
|
|
40
|
+
# check th reading works
|
|
41
|
+
lib = get_hpge_pulse_shape_library(test_pulse_shape_library, "V01", "waveforms")
|
|
42
|
+
assert isinstance(lib, HPGePulseShapeLibrary)
|
|
43
|
+
|
|
44
|
+
assert np.shape(lib.waveforms) == (200, 200, 4001)
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from collections.abc import Callable
|
|
4
|
-
from typing import NamedTuple
|
|
5
|
-
|
|
6
|
-
import lgdo
|
|
7
|
-
import numpy as np
|
|
8
|
-
import pint
|
|
9
|
-
from dbetto import AttrsDict
|
|
10
|
-
from lgdo import lh5
|
|
11
|
-
from scipy.interpolate import RegularGridInterpolator
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class HPGeScalarRZField(NamedTuple):
|
|
15
|
-
"""A scalar field defined in the cylindrical-like (r, z) HPGe plane."""
|
|
16
|
-
|
|
17
|
-
φ: Callable
|
|
18
|
-
"Scalar field, function of the coordinates (r, z)."
|
|
19
|
-
r_units: pint.Unit
|
|
20
|
-
"Physical units of the coordinate `r`."
|
|
21
|
-
z_units: pint.Unit
|
|
22
|
-
"Physical units of the coordinate `z`."
|
|
23
|
-
φ_units: pint.Unit
|
|
24
|
-
"Physical units of the field."
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def get_hpge_scalar_rz_field(
|
|
28
|
-
filename: str, obj: str, field: str, out_of_bounds_val: int | float = np.nan, **kwargs
|
|
29
|
-
) -> HPGeScalarRZField:
|
|
30
|
-
"""Create an interpolator for a gridded scalar HPGe field defined on `(r, z)`.
|
|
31
|
-
|
|
32
|
-
Reads from disk the following data structure: ::
|
|
33
|
-
|
|
34
|
-
FILENAME/
|
|
35
|
-
└── OBJ · struct{r,z,FIELD}
|
|
36
|
-
├── r · array<1>{real} ── {'units': 'UNITS'}
|
|
37
|
-
├── z · array<1>{real} ── {'units': 'UNITS'}
|
|
38
|
-
└── FIELD · array<2>{real} ── {'units': 'UNITS'}
|
|
39
|
-
|
|
40
|
-
where ``FILENAME``, ``OBJ`` and ``FIELD`` are provided as
|
|
41
|
-
arguments to this function. `obj` is a :class:`~lgdo.types.struct.Struct`,
|
|
42
|
-
`r` and `z` are one dimensional arrays specifying the radial and z
|
|
43
|
-
coordinates of the rectangular grid — not the coordinates of each single
|
|
44
|
-
grid point. In this coordinate system, the center of the p+ contact surface
|
|
45
|
-
is at `(0, 0)`, with the p+ contact facing downwards. `field` is instead a
|
|
46
|
-
two-dimensional array specifying the field value at each grid point. The
|
|
47
|
-
first and second dimensions are `r` and `z`, respectively. NaN values are
|
|
48
|
-
interpreted as points outside the detector profile in the `(r, z)` plane.
|
|
49
|
-
|
|
50
|
-
Before returning a :class:`HPGeScalarRZField`, the gridded field is fed to
|
|
51
|
-
:class:`scipy.interpolate.RegularGridInterpolator`.
|
|
52
|
-
|
|
53
|
-
Parameters
|
|
54
|
-
----------
|
|
55
|
-
filename
|
|
56
|
-
name of the LH5 file containing the gridded scalar field.
|
|
57
|
-
obj
|
|
58
|
-
name of the HDF5 dataset where the data is saved.
|
|
59
|
-
field
|
|
60
|
-
name of the HDF5 dataset holding the field values.
|
|
61
|
-
out_of_bounds_val
|
|
62
|
-
value to use to replace NaNs in the field values.
|
|
63
|
-
"""
|
|
64
|
-
data = lh5.read(obj, filename)
|
|
65
|
-
|
|
66
|
-
if not isinstance(data, lgdo.Struct):
|
|
67
|
-
msg = f"{obj} in {filename} is not an LGDO Struct"
|
|
68
|
-
raise ValueError(msg)
|
|
69
|
-
|
|
70
|
-
data = AttrsDict(
|
|
71
|
-
{
|
|
72
|
-
k: np.nan_to_num(data[k].view_as("np", with_units=True), nan=out_of_bounds_val)
|
|
73
|
-
for k in ("r", "z", field)
|
|
74
|
-
}
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
interpolator = RegularGridInterpolator((data.r.m, data.z.m), data[field].m, **kwargs)
|
|
78
|
-
|
|
79
|
-
return HPGeScalarRZField(interpolator, data.r.u, data.z.u, data[field].u)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|