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/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 _vectorized_erf(x: ArrayLike) -> NDArray:
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, Amax: float, mu: float, sigma: float, tail_fraction: float, tau: float
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 and an exponential tail:
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)/sigma))\times
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
- Amax
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 = Amax * (1 - tail_fraction) * np.exp(-(dx * dx) / (2 * sigma * sigma))
287
- term2 = Amax * tail_fraction * (1 - _vectorized_erf(dx / sigma)) * np.exp(times / tau) / norm
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
- return term1 + term2
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, with 1 ns binning.
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
- shift = int(mu / dt)
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
- return times, y
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
- sigma: float,
382
- tail_fraction: float,
383
- tau: float,
384
- mean_AoE: float = 0,
385
- ) -> tuple[NDArray, NDArray]:
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
- drift_time
680
+ dt
395
681
  Array of drift times for each step.
396
- sigma
397
- Sigma parameter of the current pulse model.
398
- tail_fraction
399
- Tail-fraction parameter of the current pulse.
400
- tau
401
- Tail parameter of the current pulse
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
- # get normalisation factor
409
- x_coarse = np.linspace(-1000, 3000, 201)
410
- x_fine = np.linspace(-1000, 3000, 4001)
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
- template_coarse = _current_pulse_model(x_coarse, 1, 0, sigma, tail_fraction, tau)
416
- template_coarse /= np.max(template_coarse)
417
- template_coarse *= mean_AoE
697
+ if include_surface_effects:
698
+ offsets = times[np.argmax(templates_surface, axis=0)]
418
699
 
419
- template_fine = _current_pulse_model(x_fine, 1, 0, sigma, tail_fraction, tau)
420
- template_fine /= np.max(template_fine)
421
- template_fine *= mean_AoE
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
- # first pass
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
- sigma: float,
450
- tail_fraction: float,
451
- tau: float,
452
- mean_AoE: float = 0,
453
- get_timepoint: bool = False,
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
- sigma
464
- Sigma parameter of the current pulse model.
465
- tail_fraction
466
- Tail-fraction parameter of the current pulse.
467
- tau
468
- Tail parameter of the current pulse
469
- mean_AoE
470
- The mean AoE value for this detector (to normalise current pulses).
471
- get_timepoint
472
- Flag to return the time of the maximum current (relative to t0) instead of the current.
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
- curr, time = _estimate_current_impl(
484
- ak.Array(edep),
485
- ak.Array(drift_time),
486
- sigma=sigma,
487
- tail_fraction=tail_fraction,
488
- tau=tau,
489
- mean_AoE=mean_AoE,
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 get_timepoint:
837
+ if return_mode == "max_time":
494
838
  return Array(time)
495
- return Array(curr)
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)