All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
PMTsimulationAlg.h
Go to the documentation of this file.
1 /**
2  * @file icaruscode/PMT/Algorithms/PMTsimulationAlg.h
3  * @brief Algorithms for the simulation of ICARUS PMT channels.
4  * @date October 16, 2018
5  * @see `icaruscode/PMT/Algorithms/PMTsimulationAlg.cxx`
6  *
7  * These algoritms were originally extracted from the module
8  * `SimPMTIcarus_module.cc`, which was in turn based on
9  * `SimPMTSBND_module.cc` by L. Paulucci and F. Marinho.
10  * Heavy hands of Wesley Ketchum (ketchum@fnal.gov) and Gianluca Petrillo
11  * (petrillo@slac.standord.edu) for the ICARUS customization.
12  */
13 
14 #ifndef ICARUSCODE_PMT_ALGORITHMS_PMTSIMULATIONALG_H
15 #define ICARUSCODE_PMT_ALGORITHMS_PMTSIMULATIONALG_H
16 
17 
18 // ICARUS libraries
23 
24 // LArSoft libraries
30 #include "lardataalg/Utilities/quantities_fhicl.h" // microsecond from FHiCL
31 #include "lardataalg/Utilities/quantities/spacetime.h" // microsecond, ...
32 #include "lardataalg/Utilities/quantities/frequency.h" // hertz, gigahertz
33 #include "lardataalg/Utilities/quantities/electronics.h" // tick, counts_f
35 
36 // framework libraries
37 #include "messagefacility/MessageLogger/MessageLogger.h"
38 #include "fhiclcpp/types/Atom.h"
39 #include "fhiclcpp/types/OptionalAtom.h"
40 #include "fhiclcpp/types/Sequence.h"
41 #include "fhiclcpp/types/Table.h"
42 
43 // CLHEP libraries
44 #include "CLHEP/Random/RandEngine.h" // CLHEP::HepRandomEngine
45 
46 // C++ standard library
47 #include <vector>
48 #include <string>
49 #include <tuple>
50 #include <optional>
51 #include <ios> // std::boolalpha
52 #include <utility> // std::forward()
53 #include <memory> // std::unique_ptr()
54 #include <functional> // std::plus
55 #include <cmath> // std::abs(), std::exp()
56 #include <cstdlib> // std::size_t
57 
58 
59 // -----------------------------------------------------------------------------
60 namespace icarus::opdet {
61  // in this Issue:
62 
63  using namespace util::quantities::electromagnetism_literals;
64  using namespace util::quantities::electronics_literals;
65 
66  /// Type for single photon response shape function: nanosecond -> ADC counts.
69 
70  template <typename SampleType> class OpDetWaveformMakerClass;
71 
72  class PMTsimulationAlg;
73 
75 
76 } // namespace icarus::opdet
77 
78 
79 // -----------------------------------------------------------------------------
80 /// Helper class to cut a `raw::OpDetWaveform` from a longer waveform data.
81 template <typename SampleType>
83 
84  public:
85  using Sample_t = SampleType;
86 
87  /// Type of waveform data.
88  using WaveformData_t = std::vector<Sample_t>;
89 
90  using BufferRange_t = std::pair
92 
93  WaveformData_t const& fWaveform; ///< Full data from the PMT channel.
94 
95  /// Time of the first sample in waveform.
97 
99 
100  /// Constructor: waveform data, start time and sampling period
102  WaveformData_t const& waveform,
104  util::quantities::nanosecond samplingPeriod
105  );
106 
107  // @{
108  /// Returns an `raw::OpDetWaveform` with data at the `bufferRange`.
109  raw::OpDetWaveform create
110  (raw::Channel_t opChannel, BufferRange_t const& bufferRange) const;
111  raw::OpDetWaveform operator()
112  (raw::Channel_t opChannel, BufferRange_t const& bufferRange) const
113  { return create(opChannel, bufferRange); }
114  // @}
115 
116 }; // class icarus::opdet::OpDetWaveformMakerClass<>
117 
118 
119 // -----------------------------------------------------------------------------
120 /**
121  * @brief Algorithm class for the full simulation of PMT channels.
122  *
123  * The algorithm creates simulated PMT waveforms as read out by ICARUS,
124  * including the generation of trigger primitives.
125  * Contributions to the waveforms include:
126  * * physical photons
127  * * dark noise
128  * * electronics noise
129  *
130  * The algorithm processes an optical channel at a time, independently
131  * and uncorrelated from the other channels.
132  * For each channel, multiple waveforms may be generated according to the
133  * readout parameters.
134  *
135  *
136  * Activity sources
137  * =================
138  *
139  * Physical photons
140  * -----------------
141  *
142  * Photons are read from `sim::SimPhotons` data objects, each one pertaining
143  * a single optical detector channel.
144  * Each photon on the channel is assumed to have successfully reached the
145  * external surface of the photocathode, with the wavelength shifter.
146  * Depending on the upstream simulation, and in particular on the photon
147  * visibility library settings, the photon might have also already passed
148  * the wavelength shifting and even triggered the conversion to a detectable
149  * photoelectron.
150  *
151  * Quantum efficiency is simulated to determine if each photon converts into
152  * a photoelectron on the internal side of the photocathode. The target
153  * quantum efficiency is specified via the `QE` configuration parameter.
154  * It is assumed that some level of quantum efficiency has already been
155  * simulated upstream: more precisely, that the quantum efficiency already
156  * applied is in the amount returned by
157  * `detinfo::LArProperties::ScintPreScale()`. Therefore:
158  *
159  * 1. the quantum efficiency applied here is only the residual one to go
160  * from `detinfo::LArProperties::ScintPreScale()` to the value in `QE`
161  * 2. there is no implement here to _increase_ quantum efficiency, i.e.
162  * `QE` must not exceed `detinfo::LArProperties::ScintPreScale()`
163  * 3. if the configuration specifies a target quantum efficiency `QE` larger
164  * than the one applied upstream
165  * (`detinfo::LArProperties::ScintPreScale()`), a warning message is
166  * printed, and no change to quantum efficiency is performed
167  *
168  * Note that if the upstream code has not applied any quantum efficiency,
169  * the configuration should give a `detinfo::LArProperties::ScintPreScale()`
170  * of 1.0.
171  *
172  * @note If the photon visibility library already includes the probability
173  * of the photon converting to a photoelectron, the quantum efficiency
174  * check here should be skipped by setting the efficiency to 1.
175  *
176  * For each converting photon, a photoelectron is added to the channel by
177  * placing a template waveform shape into the channel waveform.
178  *
179  * The timestamp of each waveform is based on the same scale as the trigger
180  * time, as defined by `detinfo::DetectorClocks::TriggerTime()`.
181  * On that scale, the timestamp pins down the time of the first sample of
182  * the waveform. Note that this is typically earlier than when the actual
183  * signal starts. More precisely, the signal is defined to start at an
184  * interest point (see `FindTriggers()` for their definition), and the
185  * waveform starts (at tick #0) earlier than that by a fraction
186  * `PreTrigFraction` of the readout window size `ReadoutWindowSize`
187  * (both are configuration parameters of the algorithm), allowing for that
188  * amount of pre-trigger data.
189  *
190  * The configuration parameter `TriggerOffsetPMT` describes how much earlier
191  * than the trigger time the optical readout has started. Note that if
192  * an interest point (see above) happens early after optical readout has
193  * started, there might be not enough data to fill the pre-trigger data.
194  * In such case, the interest point will just be located earlier than usual
195  * within the final waveform. This situation may be caused for example by
196  * asynchronous physics events like scintillation light from cosmic rays
197  * or radioactive decay of the detector materials, or from a fluctuation
198  * of the noise.
199  *
200  *
201  * Photoelectrons
202  * ---------------
203  *
204  * The response of the PMT to a single photoelectron is passed to the
205  * algorithm as a full blown function of type `SinglePhotonResponseFunc_t`.
206  * The function needs to be valid for the lifetime of the algorithm, since
207  * the algorithm refers to without owning it, and it is expected not to
208  * change during that time. See `icarus::opdet::SimPMTIcarus`
209  *
210  * To account for gain fluctuations, that shape is considered to correspond
211  * to a nominal gain (`PMTspecs.gain` configuration parameter), which is
212  * then fluctuated to obtain the effective gain. This feature can be
213  * disabled by setting configuration parameter `FluctuateGain` to `false`.
214  * The approximation used here is that the fluctuation is entirely due to
215  * the first stage of multiplication. The gain on the first stage is
216  * described as a random variable with Poisson distribution around the mean
217  * gain. The gain on a single photoelectron at the first stage is, in fact,
218  * an integral number in each case.
219  * The time spread of the signal may be increased by the difference in time
220  * of the different branches of the multiplication avalanche. Therefore,
221  * increasing or decreasing the number of branches, as it is done by
222  * changing the gain of the first stage, the time evolution of the signal
223  * will also be likewise affected.
224  * At this time we do not take this aspect into account in the simulation.
225  * For the nominal settings of a Hamamatsu 5912 photomultiplier
226  * (gain 10 ^7^, high multiplication on the first stage) the gain at the
227  * first stage is around 20, causing a fluctuation of about 20%.
228  * If the multiplication were equally distributed across the stages, that
229  * fluctuation would be almost 45%.
230  *
231  * The first stage gain is computed by
232  * `icarus::opdet::PMTsimulationAlg::ConfigurationParameters_t::PMTspecs_t::multiplicationStageGain()`.
233  *
234  *
235  * Dark noise
236  * -----------
237  *
238  * Dark noise, i.e. the noise originating by "spontaneous" emission of a
239  * photoelectron in the photocathode without any external stimulation, is
240  * simulated by randomly extracting the time such emission happens.
241  * Each emission causes a photoelectron template waveform to be added at the
242  * extracted time.
243  * The rate of dark noise emission is set by configuration with
244  * `DarkNoiseRate` parameter.
245  *
246  *
247  * Electronics noise
248  * ------------------
249  *
250  * Electronics noise is described by Gaussian fluctuations of a given
251  * standard deviation, controlled by the configuration parameter `AmpNoise`.
252  * No noise correlation is simulated neither in time nor in space.
253  *
254  *
255  * Configuration
256  * ==============
257  *
258  * PMT specifications
259  * -------------------
260  *
261  * PMT specifications are used to evaluate the variance of the gain.
262  * The details of the calculation are documented in
263  * `icarus::opdet::PMTsimulationAlg::ConfigurationParameters_t::PMTspecs_t::multiplicationStageGain()`.
264  *
265  * The available parameters include:
266  *
267  * * `gain` (default: `1e7`): the nominal gain of the PMT; this is just a
268  * reference value.
269  * * `voltageDistribution` is a sequence of values, one for each stage of
270  * the multiplication chain of the PMT. Each number represents the
271  * relative size of the resistance that determines the fall of the
272  * potential on that stage. Only the stages that contribute to the gain
273  * need to be included. The absolute value of each element is
274  * inconsequential. For example, a 10-stage PMT with the first stage
275  * having twice the resistance of all the other would be represented by
276  * the setting `[ 2, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]`.
277  * * `dynodeK` (default: `0.75`) represents the dependence of the gain of
278  * a stage on the potential drop:
279  * @f$ \mu_{i} \propto (\Delta V_{i})^{k} @f$ (with @f$ \mu_{i} @f$ the
280  * gain for stage @f$ i @f$, @f$ \Delta V_{i} @f$ the drop of potential
281  * of that stage and @f$ k @f$ the parameter set by `dynodeK`.
282  *
283  *
284  * Random number generators
285  * -------------------------
286  *
287  * @anchor ICARUS_PMTSimulationAlg_RandomEngines
288  *
289  * Three independent random engines are currently used in the simulation:
290  *
291  * * "main" random engine:
292  * * residual quantum efficiency;
293  * * gain fluctuations;
294  * * "dark noise" engine: dark current noise only;
295  * * "electronics noise" engine: electronics noise only.
296  *
297  *
298  * Structure of the algorithm
299  * ===========================
300  *
301  * _This section needs completion._
302  *
303  * The algorithm is serviceable immediately after construction.
304  * Construction relies on a custom parameter data structure.
305  *
306  * An utility, `PMTsimulationAlgMaker`, splits the set up in two parts:
307  *
308  * 1. configuration, where the full set of parameters is learned;
309  * 2. set up, where service providers, random number engines and the external
310  * single photon response function are acquired.
311  *
312  * This is supposed to make the creation of the filling of parameter structure
313  * and the creation of the algorithm easier.
314  * At that point, a single `sim::SimPhotons` can be processed (`simulate()`)
315  * at a time, or multiple at the same time (see the multithreading notes
316  * below).
317  *
318  * The function used to describe the single particle response is customizable
319  * and it must in fact be specified by the caller, since there is no default
320  * form. The function must implement the `PulseFunction_t` interface.
321  *
322  *
323  * Multithreading notes
324  * ---------------------
325  *
326  * The algorithm processes one channel at a time, and it does not depend on
327  * event-level information; therefore, the same algorithm object can be used
328  * to process many events in sequence.
329  * On the other hand, multithreading is impaired by the random number
330  * generation, in the sense that multithreading will break reproducibility
331  * if the random engine is not magically thread-resistant.
332  *
333  * If the set up is event-dependent, then this object can't be used for
334  * multiple events at the same time. There is no global state, so at least
335  * different instances of the algorithm can be run at the same time.
336  * In fact, the creation of an algorithm is expected to take negligible time
337  * compared to its run time on a single event, and it is conceivable to create
338  * a new algorithm instance for each event.
339  *
340  */
342 
343  public:
351 
354 
355  /// Type holding all configuration parameters for this algorithm.
357 
358  struct PMTspecs_t {
359 
360  /// Voltage distribution of the PMT. Each number represents the
361  /// relative weight of the resistor between the two arms of a
362  /// multiplication stage.
363  std::vector<double> voltageDistribution;
364 
365  /// Gain from stage with voltage dV is proportional to dV^K.
366  double dynodeK;
367 
368  double gain; ///< Total typical gain of a PMT.
369 
370  /**
371  * @brief Returns the gain of the specified multiplication stage.
372  * @param i index of multiplication stage (default: first, `1`)
373  *
374  * The gain is assumed to be the product of gains from each
375  * multiplication stage. The stages are supposed to be connected
376  * by @f$ N @f$ resistors of known value, whose weight relative to the
377  * total (series) resistance is in `PMTvoltageDistribution`.
378  * The total gain (known from `gain`) is:
379  * @f[ \mu = \prod_{i} \mu_{i} @f]
380  * and the gain of each stage @f$ i @f$ is
381  * @f[ \mu_{i} = a (\Delta V_{i})^{k} @f]
382  * with @f$ k @f$ a known constant (`dynodeK`) and @f$ a @f$ an
383  * unknown one. Considered the total applied voltage (cathode to last
384  * dynode) to be @f$ \Delta V @f$, the total resistance
385  * @f$ R = \sum_{i} R_{i} @f$ and the weight of each stage
386  * @f$ \rho_{i} = R_{i} / R @f$ (stored in `PMTvoltageDistribution`),
387  * the potential on stage @f$ i @f$ is
388  * @f[ \Delta V_{i} = \Delta V \rho_{i} @f]
389  * (supporting a circuit current of @f$ \Delta V / R @f$) and
390  * therefore
391  * @f[ \mu = \prod_{i} a (\Delta V \rho_{i})^{k} @f]
392  * that allows to find
393  * @f[ a \Delta V^{k} = \sqrt[N]{\frac{\mu}{(\prod_{i} \rho_{i})^{k}}} @f]
394  * With this constant known, the gain of each stage is also known:
395  * @f[ \mu_{i} = a (\Delta V)^{k} (\rho_{i})^{k} @f]
396  *
397  * This function returns @f$ \mu_{i} @f$, with `i` starting from `1`
398  * to `nDynodes()` included.
399  */
400  double multiplicationStageGain(unsigned int i = 1) const;
401 
402  /// Returns the gain from the first stage of PMT multiplication.
403  double firstStageGain() const { return multiplicationStageGain(1U); }
404 
405  /// Number of dynodes in the PMTs.
406  unsigned int nDynodes() const { return voltageDistribution.size(); }
407 
408  /// @}
409 
410  /// Sets `voltageDistribution` by stealing and normalizing `Rs`.
411  void setVoltageDistribution(std::vector<double>&& Rs);
412 
413  }; // struct PMTspecs_t
414 
415 
416  /// @{
417  /// @name High level configuration parameters.
418 
419  double QEbase; ///< Uncorrected PMT quantum efficiency.
420 
421  size_t readoutWindowSize; ///< ReadoutWindowSize in samples
422  float pretrigFraction; ///< Fraction of window size to be before "trigger"
423  ADCcount thresholdADC; ///< ADC Threshold for self-triggered readout
424  int pulsePolarity; ///< Pulse polarity (=1 for positive, =-1 for negative)
425  microseconds triggerOffsetPMT; ///< Time relative to trigger when PMT readout starts TODO make it a `trigger_time` point
426 
427  microseconds readoutEnablePeriod; ///< Time (us) for which pmt readout is enabled
428 
429  bool createBeamGateTriggers; ///< Option to create unbiased readout around beam spill
430  microseconds beamGateTriggerRepPeriod; ///< Repetition Period (us) for BeamGateTriggers TODO make this a time_interval
431  size_t beamGateTriggerNReps; ///< Number of beamgate trigger reps to produce
432 
433  unsigned int pulseSubsamples = 1U; ///< Number of tick subsamples.
434 
435  unsigned int ADCbits = 14U; ///< Number of bits of the digitizer.
436  ADCcount baseline; //waveform baseline
437  ADCcount ampNoise; //amplitude of gaussian noise
438  bool useFastElectronicsNoise; ///< Whether to use fast generator for electronics noise.
440  float saturation; //equivalent to the number of p.e. that saturates the electronic signal
441  PMTspecs_t PMTspecs; ///< PMT specifications.
442  bool doGainFluctuations; ///< Whether to simulate fain fluctuations.
443  /// @}
444 
445  /// @{
446  /// @name Setup parameters
447 
448 
449  detinfo::LArProperties const* larProp = nullptr; ///< LarProperties service provider.
450 
451  detinfo::DetectorClocksData const* clockData = nullptr;
452 
453  /// Single photon response function.
455 
456  /// Main random stream engine.
457  CLHEP::HepRandomEngine* randomEngine = nullptr;
458 
459  /// Random stream engine for gain fluctuations.
460  CLHEP::HepRandomEngine* gainRandomEngine = nullptr;
461 
462  /// Dark noise random stream engine.
463  CLHEP::HepRandomEngine* darkNoiseRandomEngine = nullptr;
464 
465  /// Electronics noise random stream engine.
466  CLHEP::HepRandomEngine* elecNoiseRandomEngine = nullptr;
467 
468  /// Whether to track the scintillation photons used.
469  bool trackSelectedPhotons = false;
470 
471  /// @}
472 
473  /// @{
474  /// @name Derivative configuration parameters.
475 
476  std::size_t pretrigSize() const { return pretrigFraction * readoutWindowSize; }
477  std::size_t posttrigSize() const { return readoutWindowSize - pretrigSize(); }
478 
479  ADCcount maxADC() const { return ADCcount::castFrom((1 << ADCbits) - 1); }
480 
481  std::pair<ADCcount, ADCcount> ADCrange() const
482  { return { ADCcount{ 0 }, maxADC() }; }
483 
484  /// @}
485 
486  }; // ConfigurationParameters_t
487 
488 
489 
490  /// Constructor.
491  PMTsimulationAlg(ConfigurationParameters_t const& config);
492 
493 
494  /**
495  * @brief Returns the waveforms originating from simulated photons.
496  * @param photons all the photons simulated to land on the channel
497  * @return a list of optical waveforms, response to those photons,
498  * and which photons were used (if requested)
499  *
500  * Due to threshold readout, a single channel may result in multiple
501  * waveforms, which are all on the same channel but disjunct in time.
502  *
503  * The second element of the return value is optional and filled only
504  * if the `trackSelectedPhotons` configuration parameter is set to `true`.
505  * In that case, the returned `sim::SimPhotons` contains a copy of each of
506  * the `photons` contributing to any of the waveforms.
507  */
508  std::tuple<std::vector<raw::OpDetWaveform>, std::optional<sim::SimPhotons>>
509  simulate(sim::SimPhotons const& photons,
510  sim::SimPhotonsLite const& lite_photons);
511 
512  /// Prints the configuration into the specified output stream.
513  template <typename Stream>
514  void printConfiguration(Stream&& out, std::string indent = "") const;
515 
516 
517 
518  private:
519 
520  using OpDetWaveformMaker_t
522 
523  /// Type internally used for storing waveforms.
525  using WaveformValue_t = ADCcount::value_t; ///< Numeric type in waveforms.
526 
527  /// Type of sampled pulse shape: sequence of samples, one per tick.
529 
530  /// Type of member function to add electronics noise.
532 
533 
534  // --- BEGIN -- Helper functors ----------------------------------------------
535  /// Functor to convert tick point into a tick number and a subsample index.
537 
538  double const fNSubsamples; ///< Number of subsamples.
539 
540  public:
542 
543  TimeToTickAndSubtickConverter(unsigned int nSubsamples)
544  : fNSubsamples(static_cast<double>(nSubsamples)) {}
545 
546  /// Converts the `tick_d` in a subsample number and tick number.
547  std::tuple<tick, SubsampleIndex_t> operator() (double const tick_d) const;
548 
549  }; // TimeToTickAndSubtickConverter
550 
551 
552  /// Applies a random gain fluctuation to the specified number of
553  /// photoelectrons.
554  template <typename Rand>
556 
557  std::optional<Rand> fRandomGain; ///< Random gain extractor (optional).
558  double const fReferenceGain = 0.0; ///< Reference (average) gain.
559 
560  public:
561  GainFluctuator() = default;
562  GainFluctuator(double const refGain, Rand&& randomGain)
563  : fRandomGain(std::move(randomGain))
564  , fReferenceGain(refGain)
565  {}
566 
567  /// Returns the new number of photoelectrons after fluctuation from `n`.
568  double operator() (double const n);
569 
570  }; // GainFluctuator
571 
572  /// Returns a configured gain fluctuator object.
573  auto makeGainFluctuator() const;
574 
575  // --- END -- Helper functors ------------------------------------------------
576 
577 
578  ConfigurationParameters_t fParams; ///< Complete algorithm configuration.
579 
580  double fQE; ///< PMT quantum efficiency.
581  megahertz fSampling; ///< Wave sampling frequency [MHz].
582  std::size_t fNsamples; ///< Samples per waveform.
583 
584  DiscretePhotoelectronPulse wsp; /// Single photon pulse (sampled).
585 
586  NoiseAdderFunc_t const fNoiseAdder; ///< Selected electronics noise method.
587 
588  ///< Transformation uniform to Gaussian for electronics noise.
590 
591  /**
592  * @brief Creates `raw::OpDetWaveform` objects from simulated photoelectrons.
593  * @param photons the simulated list of photoelectrons
594  * @param photons_used (_output_) list of used photoelectrons
595  * @return a collection of digitised `raw::OpDetWaveform` objects
596  *
597  * This function performs the digitization of a optical detector channel which
598  * is collecting the photoelectrons in the `photons` list.
599  *
600  * The photoelectrons are already screened for quantum efficiency: all of them
601  * are considered for use. It is still possible, if a reduction of the quantum
602  * efficiency is requested, that some of them are discarded.
603  *
604  * The `photons_used` output argument is constructed and filled only if the
605  * configuration of the algorithm requires the creation of a list of used
606  * photons.
607  *
608  */
609  Waveform_t CreateFullWaveform(
610  sim::SimPhotons const& photons,
611  sim::SimPhotonsLite const& lite_photons,
612  std::optional<sim::SimPhotons>& photons_used
613  ) const;
614 
615  /**
616  * @brief Creates `raw::OpDetWaveform` objects from a waveform data.
617  * @param opChannel number of optical detector channel the data belongs to
618  * @param waveform the waveform data
619  * @return a collection of `raw::OpDetWaveform`
620  *
621  * The waveform data is a sequence of samples on the optical detector channel,
622  * starting at the beginning of the optical time clock, that is set by the
623  * algorithm configuration as the global hardware trigger time (configured
624  * in `detinfo::DetectorClocks`) and an offset.
625  * It may be obtained from `CreateFullWaveform()`.
626  *
627  * Multiple waveforms may be returned for a single channel, since a form of
628  * zero suppression is applied by the simulated hardware.
629  * The waveforms are guaranteed to be non-overlapping, non-contiguous and
630  * sorted with increasing timestamp.
631  *
632  * The procedure attempts to mimic the working mode of CAEN V1730B boards
633  * as described on page 32 of the manual "UM2792_V1730_V1725_rev2.pdf"
634  * in SBN DocDB 15024.
635  *
636  *
637  *
638  * Algorithm details
639  * ------------------
640  *
641  * In the board readout language, the "trigger" is the per-channel
642  * information that the signal on the channel has passed the threshold.
643  *
644  * It is critical to understand the details of the generation of the triggers.
645  * At the time of writing, the algorithm in `FindTriggers()` emits a trigger
646  * every time the signal passes from under threshold to beyond threshold.
647  * Assuming that the threshold is at less than one photoelectron,
648  * this kind of behavior implies that every scintillation photons arriving on
649  * the tail of the first (large?) signal will add a new waveform in tail.
650  * It is not clear what happens if _two_ more triggers happen while the window
651  * from the first trigger is still open.
652  *
653  * The assumptions in the code include:
654  *
655  * 1. overlapped triggers are not discarded (p. 32);
656  * 2. board is set for self-trigger (p. 38);
657  * 3. each waveform has a fixed duration (except the ones overlapping the
658  * previous one);
659  * 4. when a trigger starts the recording window on a channel, only the last
660  * trigger happening during that window ("overlapping"), if any, is kept.
661  * 5. at decoding time, contiguous buffers are merged in a single waveform
662  *
663  * The fixed duration of the waveform if the sum of pre- and post-trigger
664  * window length.
665  * If during this window another trigger is emitted, a new window will follow
666  * the current one and it will be long enough to contain all the post-trigger
667  * data.
668  */
669  std::vector<raw::OpDetWaveform> CreateFixedSizeOpDetWaveforms
670  (raw::Channel_t opChannel, Waveform_t const& waveform) const;
671 
672 
673  /**
674  * @brief Adds a pulse to a waveform, starting at a given tick.
675  * @tparam Combine binary operation combining two ADC counts into one
676  * @param pulse the sampling to add to the waveform
677  * @param wave the waveform the pulse will be added to
678  * @param time_bin the tick of the waveform where the pulse starts
679  * @param combination how to combine the pulse and the waveform
680  *
681  * This is the internal implementation of `AddPhotoelectrons()`.
682  *
683  * The `combination` functor behaves as a binary function taking the
684  * existing `wave` sample and the sample from the `pulse` at the same time
685  * and returning their combination as a new sample value.
686  */
687  template <typename Combine>
688  void AddPulseShape(
689  PulseSampling_t const& pulse, Waveform_t& wave, tick const time_bin,
690  Combine combination
691  ) const;
692 
693  /**
694  * @brief Adds a number of pulses to a waveform, starting at a given tick.
695  * @param pulse the sampling to add, scaled, to the waveform
696  * @param wave the waveform the pulses will be added to
697  * @param time_bin the tick of the waveform where the pulses start being added
698  * @param n the number of pulses added (it may be fractional)
699  *
700  * All the samples of `pulse` are scaled by the factor `n` and then _added_
701  * to the sampling waveform `wave`, starting from the `time_bin` sample of
702  * this waveform.
703  *
704  * The `pulse` samples are a sequence of ADC counts describing the single
705  * photoelectron pulse shape. The waveform is also a sequence of samples
706  * representing a optical detector channel digitized waveform, starting at
707  * tick #0.
708  */
709  void AddPhotoelectrons(
710  PulseSampling_t const& pulse, Waveform_t& wave, tick const time_bin,
711  WaveformValue_t const n
712  ) const;
713 
714 
715  void AddNoise(Waveform_t& wave) const; //add noise to baseline
716  /// Same as `AddNoise()` but using an alternative generator.
717  void AddNoise_faster(Waveform_t& wave) const;
718  // Add "dark" noise to baseline.
719  void AddDarkNoise(Waveform_t& wave) const;
720 
721  /**
722  * @brief Ticks in the specified waveform where some signal activity starts.
723  * @return a collection of ticks with interesting activity, sorted
724  * @see `CreateBeamGateTriggers()`
725  *
726  * We define an "interest point" a time when some activity in the
727  * waveform is considered interesting enough to be recorded.
728  * This method returns a list of interest points, in the form of the
729  * index they are located at in the waveform `wvfm`.
730  *
731  * In general (but with the exception noted below), a time becomes an
732  * interest point if the sample recorded at that time is above the threshold
733  * set by the configuration parameter `ThresholdADC`.
734  *
735  * These interest points are local readout triggers that drive zero
736  * suppression on the optical readout channel and that are not necessarily
737  * causing any level of event trigger.
738  *
739  * This method also adds the mandatory beam gate interest points as
740  * explained in `CreateBeamGateTriggers()` documentation. These are
741  * additional interest points that are added independently of whether there
742  * is actual interesting activity in there.
743  */
744  std::vector<optical_tick> FindTriggers(Waveform_t const& wvfm) const;
745 
746 
747  /**
748  * @brief Generate periodic interest points regardless the actual activity.
749  * @return a collection of ticks where we pretend interesting activity to be
750  * @see `FindTriggers()`
751  *
752  * This methods emits a list of interest points according to the algorithm
753  * configuration. More precisely, if `CreateBeamGateTriggers` is configured
754  * `true`, `BeamGateTriggerNReps` interest points are generated at
755  * `BeamGateTriggerRepPeriod` intervals, starting from the beam gate time
756  * as defined by `detinfo::DetectorClocks::BeamGateTime()`.
757  *
758  * See `FindTriggers()` for the meaning of "interest point".
759  *
760  * @note It is assumed that tick `0` happens at a time defined by
761  * `triggerOffsetPMT` configuration parameter _after_ the trigger
762  * (but since the value of that parameter is expected to be negative,
763  * tick `0` effectively happens _before_ the trigger).
764  */
765  std::vector<optical_tick> CreateBeamGateTriggers() const;
766 
767  /// Returns a random response whether a photon generates a photoelectron.
768  bool KicksPhotoelectron() const;
769 
770  /// Returns the ADC range allowed for photoelectron saturation.
771  std::pair<ADCcount, ADCcount> saturationRange() const;
772 
773  /// Applies the configured photoelectron saturation on the `waveform`,
774  /// only if the saturation is cutting into the digitisation `range`.
775  void ApplySaturation
776  (Waveform_t& waveform, std::pair<ADCcount, ADCcount> const& range) const;
777 
778  /// Applies the configured photoelectron saturation on the `waveform`.
779  void ApplySaturation(Waveform_t& waveform) const;
780 
781  /// Forces `waveform` ADC within the `min` to `max` range (`max` included).
782  static void ClipWaveform(Waveform_t& waveform, ADCcount min, ADCcount max);
783 
784 
785 }; // class PMTsimulationAlg
786 
787 
788 // -----------------------------------------------------------------------------
789 
790 /// Returns a new `PMTsimulationAlg` with an updated configuration.
792 
793  public:
798 
799  struct PMTspecConfig {
800  using Name = fhicl::Name;
801  using Comment = fhicl::Comment;
802 
803  fhicl::Atom<double> DynodeK {
804  Name("DynodeK"),
805  Comment("exponent to the voltage in multiplication gain expression"),
806  0.75 // middle of Hamamatsu 5912 range [ 0.7 -- 0.8 ]
807  };
808  fhicl::Sequence<double> VoltageDistribution {
809  Name("VoltageDistribution"),
810  Comment("voltage distribution (relative resistor value)"),
811  { 17.4, 3.4, 5.0, 3.33, 1.67, 1.0, 1.2, 1.5, 2.2, 3.0, 2.4 }
812  // Hamamatsu 5912
813  };
814  fhicl::Atom<double> Gain {
815  Name("Gain"),
816  Comment("average total gain (from one photoelectron to full signal)"),
817  1.0e7
818  };
819 
820  }; // struct PMTspecConfig
821 
822 
823  /// Main algorithm FHiCL configuration.
824  struct Config {
825  using Name = fhicl::Name;
826  using Comment = fhicl::Comment;
827 
828  //
829  // readout settings
830  //
831  fhicl::Atom<microseconds> ReadoutEnablePeriod {
832  Name("ReadoutEnablePeriod"),
833  Comment("Time for which PMT readout is enabled [us]")
834  // mandatory
835  };
836  fhicl::Atom<double> ReadoutWindowSize {
837  Name("ReadoutWindowSize"),
838  Comment
839  ("Duration of a single PMT readout acquisition window [samples]")
840  // mandatory
841  };
842  fhicl::Atom<unsigned int> ADCBits {
843  Name("ADCBits"),
844  Comment("number of bits of the Analog-to-Digital Converter"),
845  14U
846  };
847  fhicl::Atom<float> Baseline {
848  Name("Baseline"),
849  Comment("Waveform baseline (may be fractional) [ADC]")
850  // mandatory
851  };
852  fhicl::Atom<int> PulsePolarity {
853  Name("PulsePolarity"),
854  Comment("Pulse polarity: 1 for positive, -1 for negative")
855  // mandatory
856  };
857  fhicl::Atom<double> PreTrigFraction {
858  Name("PreTrigFraction"),
859  Comment("fraction of the readout window located earlier than the readout trigger")
860  // mandatory
861  };
862 
863  //
864  // PMT settings
865  //
866  fhicl::OptionalAtom<float> Saturation {
867  Name("Saturation"),
868  Comment("photomultiplier saturation (as number of photoelectrons)")
869  };
870  fhicl::Atom<double> QE {
871  Name("QE"),
872  Comment("total photoelectron quantum efficiency")
873  // mandatory
874  };
875  fhicl::Table<PMTspecConfig> PMTspecs {
876  Name("PMTspecs"),
877  Comment("collection of PMT characteristics"),
878  };
879  fhicl::Atom<bool> FluctuateGain {
880  Name("FluctuateGain"),
881  Comment("include gain fluctuation in the photoelectron response"),
882  true
883  };
884 
885  //
886  // single photoelectron response
887  //
888  fhicl::Atom<unsigned int> PulseSubsamples {
889  Name("PulseSubsamples"),
890  Comment
891  ("split each tick by this many subsamples to increase PMT timing simulation"),
892  1U
893  };
894 
895  //
896  // dark noise
897  //
898  fhicl::Atom<hertz> DarkNoiseRate {
899  Name("DarkNoiseRate"),
900  Comment("Frequency of \"spontaneous\" emission of a dark noise photoelectron [Hz]")
901  // mandatory
902  };
903 
904  //
905  // electronics noise
906  //
907  fhicl::Atom<double> AmpNoise {
908  Name("AmpNoise"),
909  Comment("RMS of the electronics noise fluctuations [ADC counts]")
910  // mandatory
911  };
912  fhicl::Atom<bool> FastElectronicsNoise {
913  Name("FastElectronicsNoise"),
914  Comment
915  ("use an approximate and faster random generator for electronics noise"),
916  true
917  };
918 
919  //
920  // trigger
921  //
922  fhicl::Atom<float> ThresholdADC {
923  Name("ThresholdADC"),
924  Comment("Threshold for self-triggered readout [ADC counts]")
925  // mandatory
926  };
927  fhicl::Atom<bool> CreateBeamGateTriggers {
928  Name("CreateBeamGateTriggers"),
929  Comment("Whether to create unbiased readout trigger at beam spill")
930  // mandatory
931  };
932  fhicl::Atom<microseconds> BeamGateTriggerRepPeriod {
933  Name("BeamGateTriggerRepPeriod"),
934  Comment("Repetition period for beam gate generated readout triggers [us]")
935  // mandatory
936  };
937  fhicl::Atom<std::size_t> BeamGateTriggerNReps {
938  Name("BeamGateTriggerNReps"),
939  Comment("Number of beam gate readout triggers to generate")
940  // mandatory
941  };
942  fhicl::Atom<microseconds> TriggerOffsetPMT {
943  Name("TriggerOffsetPMT"),
944  Comment("Time when readout begins, relative to readout trigger [us]")
945  // mandatory
946  };
947 
948 
949  }; // struct Config
950 
951 
952  /// Constructor.
953  PMTsimulationAlgMaker(Config const& config);
954 
955  /**
956  * @brief Creates and returns a new algorithm instance.
957  * @param larProp instance of `detinfo::LArProperties` to be used
958  * @param detClocks instance of `detinfo::DetectorClocks` to be used
959  * @param SPRfunction function to use for the single photon response
960  * @param mainRandomEngine main random engine (quantum efficiency, etc.)
961  * @param darkNoiseRandomEngine random engine for dark noise simulation
962  * @param elecNoiseRandomEngine random engine for electronics noise simulation
963  * @param trackSelectedPhotons (default: `false`) keep track and return
964  * a copy of the scintillation photons used
965  *
966  * All random engines are required in this interface, even if the
967  * configuration disabled noise simulation.
968  */
969  std::unique_ptr<PMTsimulationAlg> operator()(
970  detinfo::LArProperties const& larProp,
971  detinfo::DetectorClocksData const& detClocks,
972  SinglePhotonResponseFunc_t const& SPRfunction,
973  CLHEP::HepRandomEngine& mainRandomEngine,
974  CLHEP::HepRandomEngine& darkNoiseRandomEngine,
975  CLHEP::HepRandomEngine& elecNoiseRandomEngine,
976  bool trackSelectedPhotons = false
977  ) const;
978 
979  /**
980  * @brief Returns a data structure to construct the algorithm.
981  * @param larProp instance of `detinfo::LArProperties` to be used
982  * @param detClocks instance of `detinfo::DetectorClocks` to be used
983  * @param SPRfunction function to use for the single photon response
984  * @param mainRandomEngine main random engine (quantum efficiency, etc.)
985  * @param darkNoiseRandomEngine random engine for dark noise simulation
986  * @param elecNoiseRandomEngine random engine for electronics noise simulation
987  *
988  * Returns a data structure ready to be used to construct a
989  * `PMTsimulationAlg` algorithm object, based on the configuration passed
990  * at construction time and the arguments specified in this function call.
991  *
992  * All random engines are required in this interface, even if the
993  * configuration disabled noise simulation.
994  */
996  detinfo::LArProperties const& larProp,
997  detinfo::DetectorClocksData const& clockData,
998  SinglePhotonResponseFunc_t const& SPRfunction,
999  CLHEP::HepRandomEngine& mainRandomEngine,
1000  CLHEP::HepRandomEngine& darkNoiseRandomEngine,
1001  CLHEP::HepRandomEngine& elecNoiseRandomEngine,
1002  bool trackSelectedPhotons = false
1003  ) const;
1004 
1005  private:
1006  /// Part of the configuration learned from configuration files.
1008 
1009 }; // class PMTsimulationAlgMaker
1010 
1011 
1012 
1013 //-----------------------------------------------------------------------------
1014 //--- template implementation
1015 //-----------------------------------------------------------------------------
1016 //--- icarus::opdet::OpDetWaveformMakerClass
1017 // -----------------------------------------------------------------------------
1018 template <typename SampleType>
1020  WaveformData_t const& waveform,
1022  util::quantities::nanosecond samplingPeriod
1023  )
1024  : fWaveform(waveform)
1025  , fPMTstartTime(PMTstartTime)
1026  , fSamplingPeriod(samplingPeriod)
1027 {}
1028 
1029 
1030 // -----------------------------------------------------------------------------
1031 template <typename SampleType>
1033  (raw::Channel_t opChannel, BufferRange_t const& bufferRange) const
1034 {
1035  std::size_t const start
1036  = std::min(std::size_t(bufferRange.first.value()), fWaveform.size());
1037  std::size_t const end
1038  = std::min(std::size_t(bufferRange.second.value()), fWaveform.size());
1039  assert(start <= end);
1040 
1041  // start of the waveform (tick #0) in the full optical reading
1042  raw::TimeStamp_t const timeStamp { fPMTstartTime + start * fSamplingPeriod };
1043 
1044  // create a new waveform preallocating enough room for the full buffer
1045  raw::OpDetWaveform outputWaveform(timeStamp, opChannel, end - start);
1046 
1047  // copy the buffer (need to unwrap the ADCcount value)
1049  fWaveform.begin() + start,
1050  fWaveform.begin() + end,
1051  std::back_inserter(outputWaveform),
1052  [](auto sample){ return sample.value(); }
1053  );
1054 
1055  return outputWaveform;
1056 } // icarus::opdet::OpDetWaveformMakerClass<>::create()
1057 
1058 
1059 //-----------------------------------------------------------------------------
1060 //--- icarus::opdet::PMTsimulationAlg
1061 //-----------------------------------------------------------------------------
1062 template <typename Stream>
1064  (Stream&& out, std::string indent /* = "" */) const
1065 {
1066  using namespace util::quantities::electronics_literals;
1067 
1068  out
1069  << indent << "ADC bits: " << fParams.ADCbits
1070  << " (" << fParams.ADCrange().first << " -- " << fParams.ADCrange().second
1071  << ")"
1072  << '\n' << indent << "Baseline: " << fParams.baseline
1073  << '\n' << indent << "ReadoutWindowSize: " << fParams.readoutWindowSize << " ticks"
1074  << '\n' << indent << "PreTrigFraction: " << fParams.pretrigFraction
1075  << '\n' << indent << "ThresholdADC: " << fParams.thresholdADC
1076  << '\n' << indent << "Saturation: " << fParams.saturation << " p.e."
1077  << '\n' << indent << "doGainFluctuations: "
1078  << std::boolalpha << fParams.doGainFluctuations
1079  << '\n' << indent << "PulsePolarity: " << ((fParams.pulsePolarity == 1)? "positive": "negative") << " (=" << fParams.pulsePolarity << ")"
1080  << '\n' << indent << "Sampling: " << fSampling;
1081  if (fParams.pulseSubsamples > 1U)
1082  out << " (subsampling: x" << fParams.pulseSubsamples << ")";
1083  out
1084  << '\n' << indent << "Samples/waveform: " << fNsamples << " ticks"
1085  << '\n' << indent << "Gain at first stage: " << fParams.PMTspecs.firstStageGain()
1086  ;
1087 
1088  out << '\n' << indent << "Electronics noise: ";
1089  if (fParams.ampNoise > 0_ADCf) {
1090  out << fParams.ampNoise << " RMS ("
1091  << (fParams.useFastElectronicsNoise? "faster": "slower") << " algorithm)";
1092  }
1093  else out << "none";
1094 
1095  if (fParams.createBeamGateTriggers) {
1096  out << '\n' << indent << "Create " << fParams.beamGateTriggerNReps
1097  << " beam gate triggers, one every " << fParams.beamGateTriggerRepPeriod << ".";
1098  }
1099  else out << '\n' << indent << "Do not create beam gate triggers.";
1100 
1101  out << '\n' << indent << "... and more.";
1102 
1103  out << '\n' << indent << "Template photoelectron waveform settings:"
1104  << '\n';
1105  wsp.dump(std::forward<Stream>(out), indent + " ");
1106 
1107  out << '\n' << indent << "Track used photons: "
1108  << std::boolalpha << fParams.trackSelectedPhotons
1109  << '\n';
1110 } // icarus::opdet::PMTsimulationAlg::printConfiguration()
1111 
1112 
1113 //-----------------------------------------------------------------------------
1114 
1115 
1116 #endif // ICARUSCODE_PMT_ALGORITHMS_PMTSIMULATIONALG_H
double std(const std::vector< short > &wf, const double ped_mean, size_t start, size_t nsample)
Definition: UtilFunc.cxx:42
size_t beamGateTriggerNReps
Number of beamgate trigger reps to produce.
Dimensioned variables representing frequency quantities.
unsigned int nDynodes() const
Number of dynodes in the PMTs.
temporary see SBN DocDB from CERN test stand measurement ReadoutWindowSize
float pretrigFraction
Fraction of window size to be before &quot;trigger&quot;.
double QEbase
Uncorrected PMT quantum efficiency.
value_t
the JSON type enumeration
Definition: json.hpp:2854
util::quantities::nanosecond fSamplingPeriod
static constexpr Sample_t transform(Sample_t sample)
GainFluctuator(double const refGain, Rand &&randomGain)
double dynodeK
Gain from stage with voltage dV is proportional to dV^K.
Fast approximate Gaussian random translator.
microsecond_as<> microsecond
Type of time stored in microseconds, in double precision.
Definition: spacetime.h:119
Functor to convert tick point into a tick number and a subsample index.
DiscretePhotoelectronPulse::Subsample_t PulseSampling_t
Type of sampled pulse shape: sequence of samples, one per tick.
gsl::index SubsampleIndex_t
Type of index of subsample.
bool useFastElectronicsNoise
Whether to use fast generator for electronics noise.
util::quantities::intervals::microseconds time_interval
bool createBeamGateTriggers
Option to create unbiased readout around beam spill.
double TimeStamp_t
us since 1970, based on TimeService
WaveformData_t const & fWaveform
Full data from the PMT channel.
Dimensioned variables representing electromagnetic quantities.
picocoulomb_as<> picocoulomb
Type of charge stored in picocoulomb, in double precision.
DiscretePhotoelectronPulse wsp
ADCcount thresholdADC
ADC Threshold for self-triggered readout.
Precomputed digitized shape of a given function.
Abstract interface of shape of a pulse from one photoelectron.
std::vector< Sample_t > WaveformData_t
Type of waveform data.
std::size_t fNsamples
Samples per waveform.
OpDetWaveformMaker_t::WaveformData_t Waveform_t
Type internally used for storing waveforms.
tick_as< double > tick_d
Tick number, represented by double.
Definition: electronics.h:87
SinglePhotonResponseFunc_t const * pulseFunction
Single photon response function.
PMTsimulationAlg::ConfigurationParameters_t fBaseConfig
Part of the configuration learned from configuration files.
ADCcount::value_t WaveformValue_t
Numeric type in waveforms.
size_t readoutWindowSize
ReadoutWindowSize in samples.
Simulation objects for optical detectors.
PhotoelectronPulseFunction< nanoseconds > PulseFunction_t
Type of shape (times are in nanoseconds).
void printConfiguration(Stream &&out, std::string indent="") const
Prints the configuration into the specified output stream.
Utilities to read and write quantities in FHiCL configuration.
A value measured in the specified unit.
Definition: quantities.h:566
Class for a function with precomputed values.
microseconds readoutEnablePeriod
Time (us) for which pmt readout is enabled.
megahertz_as<> megahertz
Type of frequency stored in megahertz, in double precision.
Definition: frequency.h:101
raw::OpDetWaveform create(raw::Channel_t opChannel, BufferRange_t const &bufferRange) const
Returns an raw::OpDetWaveform with data at the bufferRange.
auto end(FixedBins< T, C > const &) noexcept
Definition: FixedBins.h:585
Algorithm class for the full simulation of PMT channels.
DiscretePhotoelectronPulse::PulseFunction_t SinglePhotonResponseFunc_t
Type for single photon response shape function: nanosecond -&gt; ADC counts.
tick_as<> tick
Tick number, represented by std::ptrdiff_t.
Definition: electronics.h:75
Interface for a function describing a pulse from a photoelectron.
BEGIN_PROLOG vertical distance to the surface Name
j template void())
Definition: json.hpp:3108
microseconds triggerOffsetPMT
Time relative to trigger when PMT readout starts TODO make it a trigger_time point.
An interval (duration, length, distance) between two quantity points.
Definition: intervals.h:114
std::optional< Rand > fRandomGain
Random gain extractor (optional).
detinfo::timescales::optical_tick optical_tick
Sampling of a photoelectron pulse.
ConfigurationParameters_t fParams
Complete algorithm configuration.
physics simulate
detinfo::timescales::electronics_time const fPMTstartTime
Time of the first sample in waveform.
Dimensioned variables related to electronics.
OpDetWaveformMakerClass(WaveformData_t const &waveform, detinfo::timescales::electronics_time PMTstartTime, util::quantities::nanosecond samplingPeriod)
Sampling period.
Collection of photons which recorded on one channel.
Definition: SimPhotons.h:136
timescale_traits< OpticalTimeCategory >::tick_t optical_tick
Compact representation of photons on a channel.
Definition: SimPhotons.h:103
void(PMTsimulationAlg::*)(Waveform_t &) const NoiseAdderFunc_t
Type of member function to add electronics noise.
std::pair< ADCcount, ADCcount > ADCrange() const
Contains all timing reference information for the detector.
int pulsePolarity
Pulse polarity (=1 for positive, =-1 for negative)
megahertz fSampling
Wave sampling frequency [MHz].
temporary see SBN DocDB DarkNoiseRate
double fQE
PMT quantum efficiency.
NoiseAdderFunc_t const fNoiseAdder
Single photon pulse (sampled).
Dimensioned variables representing space or time quantities.
Data types for detinfo::DetectorTimings.
nanosecond_as<> nanosecond
Type of time stored in nanoseconds, in double precision.
Definition: spacetime.h:136
SampledFunction_t::SubsampleData_t Subsample_t
Type of subsample data (a sampling of the full range).
Type holding all configuration parameters for this algorithm.
DiscretePhotoelectronPulse::SubsampleIndex_t SubsampleIndex_t
microseconds beamGateTriggerRepPeriod
Repetition Period (us) for BeamGateTriggers TODO make this a time_interval.
std::pair< detinfo::timescales::optical_tick, detinfo::timescales::optical_tick > BufferRange_t
double firstStageGain() const
Returns the gain from the first stage of PMT multiplication.
hertz_as<> hertz
Type of frequency stored in hertz, in double precision.
Definition: frequency.h:81
timescale_traits< ElectronicsTimeCategory >::time_point_t electronics_time
A point in time on the electronics time scale.
bnb BNB Stream
Helper class to cut a raw::OpDetWaveform from a longer waveform data.
static util::FastAndPoorGauss< 32768U, float > const fFastGauss
Returns a new PMTsimulationAlg with an updated configuration.
Main algorithm FHiCL configuration.