reboost 0.6.2__py3-none-any.whl → 0.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- reboost/_version.py +16 -3
- reboost/build_hit.py +102 -58
- reboost/cli.py +1 -0
- reboost/core.py +18 -9
- reboost/daq/__init__.py +5 -0
- reboost/daq/core.py +262 -0
- reboost/daq/utils.py +28 -0
- reboost/hpge/psd.py +444 -94
- reboost/hpge/surface.py +34 -1
- reboost/hpge/utils.py +2 -1
- reboost/iterator.py +4 -1
- reboost/math/stats.py +2 -2
- reboost/optmap/cli.py +40 -101
- reboost/optmap/convolve.py +206 -233
- reboost/optmap/create.py +41 -124
- reboost/optmap/evt.py +5 -2
- reboost/optmap/mapview.py +9 -7
- reboost/optmap/optmap.py +13 -14
- reboost/shape/cluster.py +4 -4
- reboost/spms/__init__.py +5 -0
- reboost/spms/pe.py +178 -0
- reboost/units.py +40 -8
- reboost/utils.py +65 -3
- {reboost-0.6.2.dist-info → reboost-0.8.0.dist-info}/METADATA +7 -5
- reboost-0.8.0.dist-info/RECORD +42 -0
- reboost-0.6.2.dist-info/RECORD +0 -37
- {reboost-0.6.2.dist-info → reboost-0.8.0.dist-info}/WHEEL +0 -0
- {reboost-0.6.2.dist-info → reboost-0.8.0.dist-info}/entry_points.txt +0 -0
- {reboost-0.6.2.dist-info → reboost-0.8.0.dist-info}/licenses/LICENSE +0 -0
- {reboost-0.6.2.dist-info → reboost-0.8.0.dist-info}/top_level.txt +0 -0
reboost/daq/utils.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def print_random_crash_msg(rng):
|
|
5
|
+
msgs = [
|
|
6
|
+
"Segmentation fault (core dumped)",
|
|
7
|
+
"zsh: segmentation fault ./orca.out",
|
|
8
|
+
"Segmentation fault: 11",
|
|
9
|
+
"Bus error (core dumped)",
|
|
10
|
+
"Bus error: 10",
|
|
11
|
+
"*** stack smashing detected ***: terminated",
|
|
12
|
+
"free(): double free detected in tcache 2",
|
|
13
|
+
"free(): invalid pointer",
|
|
14
|
+
"malloc(): corrupted top size",
|
|
15
|
+
"malloc(): memory corruption",
|
|
16
|
+
"malloc(): unaligned tcache chunk detected",
|
|
17
|
+
"Killed",
|
|
18
|
+
"Out of memory: Killed process 4321 (orca.out)",
|
|
19
|
+
"Illegal instruction (core dumped)",
|
|
20
|
+
"Illegal instruction: 4",
|
|
21
|
+
"general protection fault: 0000 [#1] SMP",
|
|
22
|
+
"fish: Job 1, './orca.out' terminated by signal SIGSEGV (Address boundary error)",
|
|
23
|
+
"==24567==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000010",
|
|
24
|
+
"Abort trap: 6",
|
|
25
|
+
"Trace/BPT trap: 5",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
print(msgs[rng.choice(len(msgs))]) # noqa: T201
|
reboost/hpge/psd.py
CHANGED
|
@@ -133,7 +133,6 @@ def drift_time(
|
|
|
133
133
|
np.sqrt(xloc**2 + yloc**2),
|
|
134
134
|
zloc,
|
|
135
135
|
)
|
|
136
|
-
|
|
137
136
|
return VectorOfVectors(
|
|
138
137
|
dt_values,
|
|
139
138
|
attrs={"units": units.unit_to_lh5_attr(dt_map.φ_units)},
|
|
@@ -240,7 +239,7 @@ def _drift_time_heuristic_impl(
|
|
|
240
239
|
|
|
241
240
|
|
|
242
241
|
@numba.njit(cache=True)
|
|
243
|
-
def
|
|
242
|
+
def _njit_erf(x: ArrayLike) -> NDArray:
|
|
244
243
|
"""Error function that can take in a numpy array."""
|
|
245
244
|
out = np.empty_like(x)
|
|
246
245
|
for i in range(x.size):
|
|
@@ -250,23 +249,31 @@ def _vectorized_erf(x: ArrayLike) -> NDArray:
|
|
|
250
249
|
|
|
251
250
|
@numba.njit(cache=True)
|
|
252
251
|
def _current_pulse_model(
|
|
253
|
-
times: ArrayLike,
|
|
252
|
+
times: ArrayLike,
|
|
253
|
+
amax: float,
|
|
254
|
+
mu: float,
|
|
255
|
+
sigma: float,
|
|
256
|
+
tail_fraction: float,
|
|
257
|
+
tau: float,
|
|
258
|
+
high_tail_fraction: float = 0,
|
|
259
|
+
high_tau: float = 0,
|
|
254
260
|
) -> NDArray:
|
|
255
261
|
r"""Analytic model for the current pulse in a Germanium detector.
|
|
256
262
|
|
|
257
|
-
Consists of a Gaussian
|
|
263
|
+
Consists of a Gaussian, a high side exponential tail and a low side tail:
|
|
258
264
|
|
|
259
265
|
.. math::
|
|
260
266
|
|
|
261
|
-
A(t) = A_{max}\times (1-p)\times \text{Gauss}(t,\mu,\sigma)+ A \times p (1-\text{Erf}((t-\mu)/
|
|
262
|
-
\frac{e^{(t/\tau)}}{2e^{\mu/\tau}}
|
|
267
|
+
A(t) = A_{max}\times (1-p-p_h)\times \text{Gauss}(t,\mu,\sigma)+ A \times p (1-\text{Erf}((t-\mu)/sigma_i))\times
|
|
268
|
+
\frac{e^{( t/\tau)}}{2e^{\mu/\tau}} + A \times p_h (1-\text{Erf}(-(t-\mu)/sigma_i))\times
|
|
269
|
+
\frac{e^{-( t/\tau)}}{2}
|
|
263
270
|
|
|
264
271
|
Parameters
|
|
265
272
|
----------
|
|
266
273
|
times
|
|
267
|
-
Array of times to compute current for
|
|
268
|
-
|
|
269
|
-
Maximum current
|
|
274
|
+
Array of times to compute current for.
|
|
275
|
+
amax
|
|
276
|
+
Maximum current for the template
|
|
270
277
|
mu
|
|
271
278
|
Time of the maximum current.
|
|
272
279
|
sigma
|
|
@@ -275,18 +282,84 @@ def _current_pulse_model(
|
|
|
275
282
|
Fraction of the tail in the pulse.
|
|
276
283
|
tau
|
|
277
284
|
Time constant of the low time tail.
|
|
285
|
+
high__tail_fraction
|
|
286
|
+
Fraction of the high tail in the pulse.
|
|
287
|
+
high_tau
|
|
288
|
+
Time constant of the high time tail.
|
|
278
289
|
|
|
279
290
|
Returns
|
|
280
291
|
-------
|
|
281
292
|
The predicted current waveform for this energy deposit.
|
|
282
293
|
"""
|
|
283
294
|
norm = 2 * exp(mu / tau)
|
|
295
|
+
norm_high = 2
|
|
284
296
|
|
|
285
297
|
dx = times - mu
|
|
286
|
-
term1 =
|
|
287
|
-
|
|
298
|
+
term1 = (
|
|
299
|
+
amax * (1 - tail_fraction - high_tail_fraction) * np.exp(-(dx * dx) / (2 * sigma * sigma))
|
|
300
|
+
)
|
|
301
|
+
term2 = amax * tail_fraction * (1 - _njit_erf(dx / sigma)) * np.exp(times / tau) / norm
|
|
302
|
+
term3 = (
|
|
303
|
+
amax
|
|
304
|
+
* high_tail_fraction
|
|
305
|
+
* (1 - _njit_erf(-dx / sigma))
|
|
306
|
+
* np.exp(-(times - mu) / high_tau)
|
|
307
|
+
/ norm_high
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
return term1 + term2 + term3
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
@numba.njit(cache=True)
|
|
314
|
+
def _interpolate_pulse_model(
|
|
315
|
+
template: Array, time: float, start: float, end: float, dt: float, mu: float
|
|
316
|
+
) -> NDArray:
|
|
317
|
+
"""Interpolate to extract the pulse model given a particular mu."""
|
|
318
|
+
local_time = time - mu - start
|
|
319
|
+
|
|
320
|
+
if (local_time < start) or (int(local_time) > end):
|
|
321
|
+
return 0
|
|
322
|
+
|
|
323
|
+
sample = int(local_time / dt)
|
|
324
|
+
A_before = template[sample]
|
|
325
|
+
A_after = template[sample + 1]
|
|
326
|
+
|
|
327
|
+
frac = (local_time - int(local_time)) / dt
|
|
328
|
+
return A_before + frac * (A_after - A_before)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def make_convolved_surface_library(bulk_template: np.array, surface_library: np.array) -> NDArray:
|
|
332
|
+
"""Make the convolved surface library out of the template.
|
|
333
|
+
|
|
334
|
+
This convolves every row of the surface_library with the template and reshapes the output
|
|
335
|
+
to match the initial template. It returns a 2D array with one more row than the surface_library
|
|
336
|
+
and each row the same length as the template. The final row is the bulk_template for easier interpolation.
|
|
337
|
+
|
|
338
|
+
Parameters
|
|
339
|
+
----------
|
|
340
|
+
bulk_template
|
|
341
|
+
The template for the bulk response
|
|
342
|
+
surface_library
|
|
343
|
+
The 2D array of the surface library.
|
|
288
344
|
|
|
289
|
-
|
|
345
|
+
Returns
|
|
346
|
+
-------
|
|
347
|
+
2D array of the surface library convolved with the bulk response.
|
|
348
|
+
"""
|
|
349
|
+
# force surface library to be 2D
|
|
350
|
+
if surface_library.ndim == 1:
|
|
351
|
+
surface_library = surface_library.reshape((-1, 1))
|
|
352
|
+
|
|
353
|
+
templates = np.zeros((len(bulk_template), np.shape(surface_library)[1] + 1))
|
|
354
|
+
|
|
355
|
+
for i in range(np.shape(surface_library)[1]):
|
|
356
|
+
templates[:, i] = convolve_surface_response(
|
|
357
|
+
surface_library[1:, i] - surface_library[:-1, i], bulk_template
|
|
358
|
+
)[: len(bulk_template)]
|
|
359
|
+
|
|
360
|
+
templates[:, -1] = bulk_template
|
|
361
|
+
|
|
362
|
+
return templates
|
|
290
363
|
|
|
291
364
|
|
|
292
365
|
def convolve_surface_response(surf_current: np.ndarray, bulk_pulse: np.ndarray) -> NDArray:
|
|
@@ -339,7 +412,7 @@ def get_current_waveform(
|
|
|
339
412
|
drift_time
|
|
340
413
|
Array of drift times for each step
|
|
341
414
|
template
|
|
342
|
-
array of the template for the current waveforms
|
|
415
|
+
array of the template for the current waveforms
|
|
343
416
|
start
|
|
344
417
|
first time value of the template
|
|
345
418
|
dt
|
|
@@ -354,35 +427,248 @@ def get_current_waveform(
|
|
|
354
427
|
n = len(template)
|
|
355
428
|
|
|
356
429
|
times = np.arange(n) * dt + start
|
|
357
|
-
y = np.zeros_like(times)
|
|
430
|
+
y = np.zeros_like(times, dtype=np.float64)
|
|
431
|
+
|
|
432
|
+
for j in range(n):
|
|
433
|
+
time = start + dt * j
|
|
434
|
+
if (time < range_t[0]) or (time > (range_t[1] - dt)):
|
|
435
|
+
continue
|
|
436
|
+
y[j] = _get_waveform_value(j, edep, drift_time, template, start, dt, range_t)
|
|
437
|
+
|
|
438
|
+
return times, y
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
@numba.njit(cache=True)
|
|
442
|
+
def _get_waveform_value_surface(
|
|
443
|
+
idx: int,
|
|
444
|
+
edep: NDArray,
|
|
445
|
+
drift_time: np.array,
|
|
446
|
+
dist_to_nplus: np.array,
|
|
447
|
+
bulk_template: ArrayLike,
|
|
448
|
+
templates_surface: ArrayLike,
|
|
449
|
+
activeness_surface: ArrayLike,
|
|
450
|
+
distance_step_in_um: float,
|
|
451
|
+
fccd: float,
|
|
452
|
+
start: float,
|
|
453
|
+
dt: float,
|
|
454
|
+
) -> tuple[float, float]:
|
|
455
|
+
"""Get the value of the waveform at a certain index.
|
|
456
|
+
|
|
457
|
+
Parameters
|
|
458
|
+
----------
|
|
459
|
+
idx
|
|
460
|
+
the index of the time array to find the waveform at.
|
|
461
|
+
edep
|
|
462
|
+
Array of energies for each step
|
|
463
|
+
drift_time
|
|
464
|
+
Array of drift times for each step
|
|
465
|
+
template
|
|
466
|
+
array of the template for the current waveforms
|
|
467
|
+
templates_surface
|
|
468
|
+
The current templates from the surface.
|
|
469
|
+
activeness_surface
|
|
470
|
+
The total collected charge for each surface point.
|
|
471
|
+
dist_step_in_um
|
|
472
|
+
The binning in distance for the surface pulse library.
|
|
473
|
+
start
|
|
474
|
+
first time value of the template
|
|
475
|
+
dt
|
|
476
|
+
timestep (in ns) for the template.
|
|
477
|
+
|
|
478
|
+
Returns
|
|
479
|
+
-------
|
|
480
|
+
Value of the current waveform and the energy.
|
|
481
|
+
"""
|
|
482
|
+
n = len(bulk_template)
|
|
483
|
+
out = 0
|
|
484
|
+
etmp = 0
|
|
485
|
+
time = start + dt * idx
|
|
358
486
|
|
|
359
487
|
for i in range(len(edep)):
|
|
360
488
|
E = edep[i]
|
|
361
489
|
mu = drift_time[i]
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
# Add scaled template starting at index `shift`
|
|
365
|
-
for j in range(n):
|
|
366
|
-
if (
|
|
367
|
-
(shift + j) >= n
|
|
368
|
-
or (times[shift + j] < range_t[0])
|
|
369
|
-
or (times[shift + j] > range_t[1])
|
|
370
|
-
):
|
|
371
|
-
continue
|
|
372
|
-
y[shift + j] += E * template[j]
|
|
490
|
+
dist = dist_to_nplus[i]
|
|
373
491
|
|
|
374
|
-
|
|
492
|
+
if dist < fccd:
|
|
493
|
+
dist_bin = int(dist / distance_step_in_um)
|
|
494
|
+
|
|
495
|
+
# get two values (to allow linear interpolation)
|
|
496
|
+
value_low = _interpolate_pulse_model(
|
|
497
|
+
templates_surface[dist_bin], time, start, start + dt * n, dt, mu
|
|
498
|
+
)
|
|
499
|
+
value_high = _interpolate_pulse_model(
|
|
500
|
+
templates_surface[dist_bin + 1], time, start, start + dt * n, dt, mu
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
# interpolate between distance bins
|
|
504
|
+
diff = dist / distance_step_in_um - dist_bin
|
|
505
|
+
out += E * (value_low + diff * (value_high - value_low))
|
|
506
|
+
|
|
507
|
+
act_low = activeness_surface[dist_bin]
|
|
508
|
+
act_high = activeness_surface[dist_bin + 1]
|
|
509
|
+
etmp += (act_low + diff * (act_high - act_low)) * E
|
|
510
|
+
|
|
511
|
+
else:
|
|
512
|
+
out += E * _interpolate_pulse_model(bulk_template, time, start, start + dt * n, dt, mu)
|
|
513
|
+
etmp += E
|
|
514
|
+
return out, etmp
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
@numba.njit(cache=True)
|
|
518
|
+
def _get_waveform_value(
|
|
519
|
+
idx: int,
|
|
520
|
+
edep: ak.Array,
|
|
521
|
+
drift_time: ak.Array,
|
|
522
|
+
template: ArrayLike,
|
|
523
|
+
start: float,
|
|
524
|
+
dt: float,
|
|
525
|
+
) -> float:
|
|
526
|
+
"""Get the value of the waveform at a certain index.
|
|
527
|
+
|
|
528
|
+
Parameters
|
|
529
|
+
----------
|
|
530
|
+
idx
|
|
531
|
+
the index of the time array to find the waveform at.
|
|
532
|
+
edep
|
|
533
|
+
Array of energies for each step
|
|
534
|
+
drift_time
|
|
535
|
+
Array of drift times for each step
|
|
536
|
+
template
|
|
537
|
+
array of the template for the current waveforms
|
|
538
|
+
start
|
|
539
|
+
first time value of the template
|
|
540
|
+
dt
|
|
541
|
+
timestep (in ns) for the template.
|
|
542
|
+
|
|
543
|
+
Returns
|
|
544
|
+
-------
|
|
545
|
+
Value of the current waveform
|
|
546
|
+
"""
|
|
547
|
+
n = len(template)
|
|
548
|
+
out = 0
|
|
549
|
+
time = start + dt * idx
|
|
550
|
+
|
|
551
|
+
for i in range(len(edep)):
|
|
552
|
+
E = edep[i]
|
|
553
|
+
mu = drift_time[i]
|
|
554
|
+
|
|
555
|
+
out += E * _interpolate_pulse_model(template, time, start, start + dt * n, dt, mu)
|
|
556
|
+
|
|
557
|
+
return out
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def get_current_template(
|
|
561
|
+
low: float = -1000, high: float = 4000, step: float = 1, mean_aoe: float = 1, **kwargs
|
|
562
|
+
) -> tuple[NDArray, NDArray]:
|
|
563
|
+
"""Build the current template from the analytic model, defined by :func:`_current_pulse_model`.
|
|
564
|
+
|
|
565
|
+
Parameters
|
|
566
|
+
----------
|
|
567
|
+
low
|
|
568
|
+
start of the template
|
|
569
|
+
high
|
|
570
|
+
end of the template
|
|
571
|
+
step
|
|
572
|
+
time-step, this should divide high-low
|
|
573
|
+
mean_aoe
|
|
574
|
+
The mean AoE value for this detector (to normalise current pulses).
|
|
575
|
+
**kwargs
|
|
576
|
+
Other keyword arguments passed to :func:`_current_pulse_model`.
|
|
577
|
+
|
|
578
|
+
Returns
|
|
579
|
+
-------
|
|
580
|
+
tuple of the (template,times)
|
|
581
|
+
"""
|
|
582
|
+
if int((high - low) / step) != (high - low) / step:
|
|
583
|
+
msg = "Time template is not a multiple of the time-step."
|
|
584
|
+
raise ValueError(msg)
|
|
585
|
+
|
|
586
|
+
x = np.linspace(low, high, int((high - low) / step) + 1)
|
|
587
|
+
template = _current_pulse_model(x, **kwargs)
|
|
588
|
+
template /= np.max(template)
|
|
589
|
+
template *= mean_aoe
|
|
590
|
+
|
|
591
|
+
return template, x
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
@numba.njit(cache=True)
|
|
595
|
+
def _get_waveform_maximum_impl(
|
|
596
|
+
t: ArrayLike,
|
|
597
|
+
e: ArrayLike,
|
|
598
|
+
dist: ArrayLike,
|
|
599
|
+
template: ArrayLike,
|
|
600
|
+
templates_surface: ArrayLike,
|
|
601
|
+
activeness_surface: ArrayLike,
|
|
602
|
+
tmin: float,
|
|
603
|
+
tmax: float,
|
|
604
|
+
start: float,
|
|
605
|
+
fccd: float,
|
|
606
|
+
n: int,
|
|
607
|
+
time_step: int,
|
|
608
|
+
surface_step_in_um: float,
|
|
609
|
+
include_surface_effects: bool,
|
|
610
|
+
):
|
|
611
|
+
"""Basic implementation to get the maximum of the waveform.
|
|
612
|
+
|
|
613
|
+
Parameters
|
|
614
|
+
----------
|
|
615
|
+
t
|
|
616
|
+
drift time for each step.
|
|
617
|
+
e
|
|
618
|
+
energy for each step.
|
|
619
|
+
dist
|
|
620
|
+
distance to surface for each step.
|
|
621
|
+
"""
|
|
622
|
+
max_a = 0
|
|
623
|
+
max_t = 0
|
|
624
|
+
energy = np.sum(e)
|
|
625
|
+
|
|
626
|
+
for j in range(0, n, time_step):
|
|
627
|
+
time = start + j
|
|
628
|
+
|
|
629
|
+
# skip anything not in the range tmin to tmax (for surface affects this can be later)
|
|
630
|
+
has_surface_hit = include_surface_effects
|
|
631
|
+
|
|
632
|
+
if time < tmin or (time > (tmax + time_step)):
|
|
633
|
+
continue
|
|
634
|
+
|
|
635
|
+
if not has_surface_hit:
|
|
636
|
+
val_tmp = _get_waveform_value(j, e, t, template, start=start, dt=1.0)
|
|
637
|
+
else:
|
|
638
|
+
val_tmp, energy = _get_waveform_value_surface(
|
|
639
|
+
j,
|
|
640
|
+
e,
|
|
641
|
+
t,
|
|
642
|
+
dist,
|
|
643
|
+
template,
|
|
644
|
+
templates_surface.T,
|
|
645
|
+
activeness_surface,
|
|
646
|
+
distance_step_in_um=surface_step_in_um,
|
|
647
|
+
fccd=fccd,
|
|
648
|
+
start=start,
|
|
649
|
+
dt=1.0,
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
if val_tmp > max_a:
|
|
653
|
+
max_t = time
|
|
654
|
+
max_a = val_tmp
|
|
655
|
+
|
|
656
|
+
return max_t, max_a, energy
|
|
375
657
|
|
|
376
658
|
|
|
377
659
|
@numba.njit(cache=True)
|
|
378
660
|
def _estimate_current_impl(
|
|
379
661
|
edep: ak.Array,
|
|
380
662
|
dt: ak.Array,
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
663
|
+
dist_to_nplus: ak.Array,
|
|
664
|
+
template: np.array,
|
|
665
|
+
times: np.array,
|
|
666
|
+
include_surface_effects: bool,
|
|
667
|
+
fccd: float,
|
|
668
|
+
templates_surface: np.array,
|
|
669
|
+
activeness_surface: np.array,
|
|
670
|
+
surface_step_in_um: float,
|
|
671
|
+
) -> tuple[NDArray, NDArray, NDArray]:
|
|
386
672
|
"""Estimate the maximum current that would be measured in the HPGe detector.
|
|
387
673
|
|
|
388
674
|
This is based on extracting a waveform with :func:`get_current_waveform` and finding the maxima of it.
|
|
@@ -391,66 +677,92 @@ def _estimate_current_impl(
|
|
|
391
677
|
----------
|
|
392
678
|
edep
|
|
393
679
|
Array of energies for each step.
|
|
394
|
-
|
|
680
|
+
dt
|
|
395
681
|
Array of drift times for each step.
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
mean_AoE
|
|
403
|
-
The mean AoE value for this detector (to normalise current pulses).
|
|
682
|
+
dist_to_nplus
|
|
683
|
+
Array of distance to nplus contact for each step (can be `None`, in which case no surface effects are included.)
|
|
684
|
+
template
|
|
685
|
+
array of the bulk pulse template
|
|
686
|
+
times
|
|
687
|
+
time-stamps for the bulk pulse template
|
|
404
688
|
"""
|
|
405
689
|
A = np.zeros(len(dt))
|
|
406
690
|
maximum_t = np.zeros(len(dt))
|
|
691
|
+
energy = np.zeros(len(dt))
|
|
407
692
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
# make a template with 1 ns binning so
|
|
413
|
-
# template[(i-start)/dt] = _current_pulse_model(x,1,i,...)
|
|
693
|
+
time_step = 1
|
|
694
|
+
n = len(template)
|
|
695
|
+
start = times[0]
|
|
414
696
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
template_coarse *= mean_AoE
|
|
697
|
+
if include_surface_effects:
|
|
698
|
+
offsets = times[np.argmax(templates_surface, axis=0)]
|
|
418
699
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
700
|
+
# make the convolved surface library
|
|
701
|
+
if include_surface_effects and np.diff(times)[0] != 1.0:
|
|
702
|
+
msg = "The surface convolution requires a template with 1 ns binning"
|
|
703
|
+
raise ValueError(msg)
|
|
422
704
|
|
|
423
705
|
for i in range(len(dt)):
|
|
424
706
|
t = np.asarray(dt[i])
|
|
425
707
|
e = np.asarray(edep[i])
|
|
708
|
+
dist = np.asarray(dist_to_nplus[i])
|
|
709
|
+
|
|
710
|
+
# get the expected maximum
|
|
711
|
+
tmax = float(np.max(t))
|
|
712
|
+
tmin = float(np.min(t))
|
|
713
|
+
|
|
714
|
+
# correct the maximum expected time for surface sims
|
|
715
|
+
if include_surface_effects:
|
|
716
|
+
ncols = templates_surface.shape[1]
|
|
717
|
+
|
|
718
|
+
for j, d in enumerate(dist):
|
|
719
|
+
dtmp = int(d / surface_step_in_um)
|
|
720
|
+
|
|
721
|
+
# Use branchless selection
|
|
722
|
+
use_offset = dtmp <= ncols
|
|
723
|
+
offset_val = offsets[dtmp] if use_offset else 0.0
|
|
724
|
+
time_tmp = t[j] + offset_val * use_offset
|
|
725
|
+
|
|
726
|
+
tmax = max(tmax, time_tmp)
|
|
727
|
+
|
|
728
|
+
for time_step in [20, 1]:
|
|
729
|
+
if time_step == 1:
|
|
730
|
+
tmin = int(maximum_t[i] - 50)
|
|
731
|
+
tmax = int(maximum_t[i] + 50)
|
|
732
|
+
|
|
733
|
+
# get the value
|
|
734
|
+
maximum_t[i], A[i], energy[i] = _get_waveform_maximum_impl(
|
|
735
|
+
t,
|
|
736
|
+
e,
|
|
737
|
+
dist,
|
|
738
|
+
template,
|
|
739
|
+
templates_surface,
|
|
740
|
+
activeness_surface,
|
|
741
|
+
tmin=tmin,
|
|
742
|
+
tmax=tmax,
|
|
743
|
+
start=start,
|
|
744
|
+
fccd=fccd,
|
|
745
|
+
n=n,
|
|
746
|
+
time_step=time_step,
|
|
747
|
+
surface_step_in_um=surface_step_in_um,
|
|
748
|
+
include_surface_effects=include_surface_effects,
|
|
749
|
+
)
|
|
426
750
|
|
|
427
|
-
|
|
428
|
-
times_coarse, W = get_current_waveform(
|
|
429
|
-
e, t, template=template_coarse, start=-1000, dt=20, range_t=(-1000, 3000)
|
|
430
|
-
)
|
|
431
|
-
|
|
432
|
-
max_t = times_coarse[np.argmax(W)]
|
|
433
|
-
|
|
434
|
-
# fine scan
|
|
435
|
-
times, W = get_current_waveform(
|
|
436
|
-
e, t, template=template_fine, start=-1000, dt=1, range_t=(max_t - 50, max_t + 50)
|
|
437
|
-
)
|
|
438
|
-
|
|
439
|
-
A[i] = np.max(W)
|
|
440
|
-
maximum_t[i] = times[np.argmax(W)]
|
|
441
|
-
|
|
442
|
-
return A, maximum_t
|
|
751
|
+
return A, maximum_t, energy
|
|
443
752
|
|
|
444
753
|
|
|
445
754
|
def maximum_current(
|
|
446
755
|
edep: ArrayLike,
|
|
447
756
|
drift_time: ArrayLike,
|
|
757
|
+
dist_to_nplus: ArrayLike | None = None,
|
|
448
758
|
*,
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
759
|
+
template: np.array,
|
|
760
|
+
times: np.array,
|
|
761
|
+
fccd_in_um: float = 0,
|
|
762
|
+
templates_surface: ArrayLike | None = None,
|
|
763
|
+
activeness_surface: ArrayLike | None = None,
|
|
764
|
+
surface_step_in_um: float = 10,
|
|
765
|
+
return_mode: str = "current",
|
|
454
766
|
) -> Array:
|
|
455
767
|
"""Estimate the maximum current in the HPGe detector based on :func:`_estimate_current_impl`.
|
|
456
768
|
|
|
@@ -460,36 +772,74 @@ def maximum_current(
|
|
|
460
772
|
Array of energies for each step.
|
|
461
773
|
drift_time
|
|
462
774
|
Array of drift times for each step.
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
775
|
+
dist_to_nplus
|
|
776
|
+
Distance to n-plus electrode, only needed if surface heuristics are enabled.
|
|
777
|
+
template
|
|
778
|
+
array of the bulk pulse template
|
|
779
|
+
times
|
|
780
|
+
time-stamps for the bulk pulse template
|
|
781
|
+
fccd
|
|
782
|
+
Value of the full-charge-collection depth, if `None` no surface corrections are performed.
|
|
783
|
+
surface_library
|
|
784
|
+
2D array (distance, time) of the rate of charge arriving at the p-n junction. Each row
|
|
785
|
+
should be an array of length 10000 giving the charge arriving at the p-n junction for each timestep
|
|
786
|
+
(in ns). This is produced by :func:`.hpge.surface.get_surface_response` or other libraries.
|
|
787
|
+
surface_step_in_um
|
|
788
|
+
Distance step for the surface library.
|
|
789
|
+
return_mode
|
|
790
|
+
either current, energy or max_time
|
|
473
791
|
|
|
474
792
|
Returns
|
|
475
793
|
-------
|
|
476
|
-
An Array of the maximum current for each hit.
|
|
794
|
+
An Array of the maximum current/ time / energy for each hit.
|
|
477
795
|
"""
|
|
478
796
|
# extract LGDO data and units
|
|
479
|
-
drift_time, _ = units.unwrap_lgdo(drift_time)
|
|
480
797
|
|
|
798
|
+
drift_time, _ = units.unwrap_lgdo(drift_time)
|
|
481
799
|
edep, _ = units.unwrap_lgdo(edep)
|
|
800
|
+
dist_to_nplus, _ = units.unwrap_lgdo(dist_to_nplus)
|
|
482
801
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
802
|
+
include_surface_effects = False
|
|
803
|
+
|
|
804
|
+
if templates_surface is not None:
|
|
805
|
+
if dist_to_nplus is None:
|
|
806
|
+
msg = "Surface effects requested but distance not provided"
|
|
807
|
+
raise ValueError(msg)
|
|
808
|
+
|
|
809
|
+
include_surface_effects = True
|
|
810
|
+
else:
|
|
811
|
+
# convert types to keep numba happy
|
|
812
|
+
templates_surface = np.zeros((1, len(template)))
|
|
813
|
+
dist_to_nplus = ak.full_like(edep, np.nan)
|
|
814
|
+
|
|
815
|
+
# convert types for numba
|
|
816
|
+
if activeness_surface is None:
|
|
817
|
+
activeness_surface = np.zeros(len(template))
|
|
818
|
+
|
|
819
|
+
if not ak.all(ak.num(edep, axis=-1) == ak.num(drift_time, axis=-1)):
|
|
820
|
+
msg = "edep and drift time must have the same shape"
|
|
821
|
+
raise ValueError(msg)
|
|
822
|
+
|
|
823
|
+
curr, time, energy = _estimate_current_impl(
|
|
824
|
+
ak.values_astype(ak.Array(edep), np.float64),
|
|
825
|
+
ak.values_astype(ak.Array(drift_time), np.float64),
|
|
826
|
+
ak.values_astype(ak.Array(dist_to_nplus), np.float64),
|
|
827
|
+
template=template,
|
|
828
|
+
times=times,
|
|
829
|
+
fccd=fccd_in_um,
|
|
830
|
+
include_surface_effects=include_surface_effects,
|
|
831
|
+
templates_surface=templates_surface,
|
|
832
|
+
activeness_surface=activeness_surface,
|
|
833
|
+
surface_step_in_um=surface_step_in_um,
|
|
490
834
|
)
|
|
491
835
|
|
|
492
836
|
# return
|
|
493
|
-
if
|
|
837
|
+
if return_mode == "max_time":
|
|
494
838
|
return Array(time)
|
|
495
|
-
|
|
839
|
+
if return_mode == "current":
|
|
840
|
+
return Array(curr)
|
|
841
|
+
if return_mode == "energy":
|
|
842
|
+
return Array(energy)
|
|
843
|
+
|
|
844
|
+
msg = f"Return mode {return_mode} is not implemented."
|
|
845
|
+
raise ValueError(msg)
|