All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
Classes | Public Types | Public Member Functions | Private Types | Private Member Functions | Static Private Member Functions | Private Attributes | Static Private Attributes | List of all members
icarus::opdet::PMTsimulationAlg Class Reference

Algorithm class for the full simulation of PMT channels. More...

#include <PMTsimulationAlg.h>

Classes

struct  ConfigurationParameters_t
 Type holding all configuration parameters for this algorithm. More...
 
class  GainFluctuator
 
class  TimeToTickAndSubtickConverter
 Functor to convert tick point into a tick number and a subsample index. More...
 

Public Types

using microseconds = util::quantities::microsecond
 
using nanoseconds = util::quantities::nanosecond
 
using hertz = util::quantities::hertz
 
using megahertz = util::quantities::megahertz
 
using picocoulomb = util::quantities::picocoulomb
 
using tick = util::quantities::tick
 
using ADCcount = DiscretePhotoelectronPulse::ADCcount
 
using time_interval = detinfo::timescales::time_interval
 
using optical_tick = detinfo::timescales::optical_tick
 

Public Member Functions

 PMTsimulationAlg (ConfigurationParameters_t const &config)
 Constructor. More...
 
std::tuple< std::vector
< raw::OpDetWaveform >
, std::optional
< sim::SimPhotons > > 
simulate (sim::SimPhotons const &photons, sim::SimPhotonsLite const &lite_photons)
 Returns the waveforms originating from simulated photons. More...
 
template<typename Stream >
void printConfiguration (Stream &&out, std::string indent="") const
 Prints the configuration into the specified output stream. More...
 

Private Types

using OpDetWaveformMaker_t = icarus::opdet::OpDetWaveformMakerClass< ADCcount >
 
using Waveform_t = OpDetWaveformMaker_t::WaveformData_t
 Type internally used for storing waveforms. More...
 
using WaveformValue_t = ADCcount::value_t
 Numeric type in waveforms. More...
 
using PulseSampling_t = DiscretePhotoelectronPulse::Subsample_t
 Type of sampled pulse shape: sequence of samples, one per tick. More...
 
using NoiseAdderFunc_t = void(PMTsimulationAlg::*)(Waveform_t &) const
 Type of member function to add electronics noise. More...
 

Private Member Functions

auto makeGainFluctuator () const
 Returns a configured gain fluctuator object. More...
 
Waveform_t CreateFullWaveform (sim::SimPhotons const &photons, sim::SimPhotonsLite const &lite_photons, std::optional< sim::SimPhotons > &photons_used) const
 Creates raw::OpDetWaveform objects from simulated photoelectrons. More...
 
std::vector< raw::OpDetWaveformCreateFixedSizeOpDetWaveforms (raw::Channel_t opChannel, Waveform_t const &waveform) const
 Creates raw::OpDetWaveform objects from a waveform data. More...
 
template<typename Combine >
void AddPulseShape (PulseSampling_t const &pulse, Waveform_t &wave, tick const time_bin, Combine combination) const
 Adds a pulse to a waveform, starting at a given tick. More...
 
void AddPhotoelectrons (PulseSampling_t const &pulse, Waveform_t &wave, tick const time_bin, WaveformValue_t const n) const
 Adds a number of pulses to a waveform, starting at a given tick. More...
 
void AddNoise (Waveform_t &wave) const
 
void AddNoise_faster (Waveform_t &wave) const
 Same as AddNoise() but using an alternative generator. More...
 
void AddDarkNoise (Waveform_t &wave) const
 
std::vector< optical_tickFindTriggers (Waveform_t const &wvfm) const
 Ticks in the specified waveform where some signal activity starts. More...
 
std::vector< optical_tickCreateBeamGateTriggers () const
 Generate periodic interest points regardless the actual activity. More...
 
bool KicksPhotoelectron () const
 Returns a random response whether a photon generates a photoelectron. More...
 
std::pair< ADCcount, ADCcountsaturationRange () const
 Returns the ADC range allowed for photoelectron saturation. More...
 
void ApplySaturation (Waveform_t &waveform, std::pair< ADCcount, ADCcount > const &range) const
 
void ApplySaturation (Waveform_t &waveform) const
 Applies the configured photoelectron saturation on the waveform. More...
 

Static Private Member Functions

static void ClipWaveform (Waveform_t &waveform, ADCcount min, ADCcount max)
 Forces waveform ADC within the min to max range (max included). More...
 

Private Attributes

ConfigurationParameters_t fParams
 Complete algorithm configuration. More...
 
double fQE
 PMT quantum efficiency. More...
 
megahertz fSampling
 Wave sampling frequency [MHz]. More...
 
std::size_t fNsamples
 Samples per waveform. More...
 
DiscretePhotoelectronPulse wsp
 
NoiseAdderFunc_t const fNoiseAdder
 Single photon pulse (sampled). More...
 

Static Private Attributes

static util::FastAndPoorGauss< 32768U, float > const fFastGauss
 

Detailed Description

Algorithm class for the full simulation of PMT channels.

The algorithm creates simulated PMT waveforms as read out by ICARUS, including the generation of trigger primitives. Contributions to the waveforms include:

The algorithm processes an optical channel at a time, independently and uncorrelated from the other channels. For each channel, multiple waveforms may be generated according to the readout parameters.

Activity sources

Physical photons

Photons are read from sim::SimPhotons data objects, each one pertaining a single optical detector channel. Each photon on the channel is assumed to have successfully reached the external surface of the photocathode, with the wavelength shifter. Depending on the upstream simulation, and in particular on the photon visibility library settings, the photon might have also already passed the wavelength shifting and even triggered the conversion to a detectable photoelectron.

Quantum efficiency is simulated to determine if each photon converts into a photoelectron on the internal side of the photocathode. The target quantum efficiency is specified via the QE configuration parameter. It is assumed that some level of quantum efficiency has already been simulated upstream: more precisely, that the quantum efficiency already applied is in the amount returned by detinfo::LArProperties::ScintPreScale(). Therefore:

  1. the quantum efficiency applied here is only the residual one to go from detinfo::LArProperties::ScintPreScale() to the value in QE
  2. there is no implement here to increase quantum efficiency, i.e. QE must not exceed detinfo::LArProperties::ScintPreScale()
  3. if the configuration specifies a target quantum efficiency QE larger than the one applied upstream (detinfo::LArProperties::ScintPreScale()), a warning message is printed, and no change to quantum efficiency is performed

Note that if the upstream code has not applied any quantum efficiency, the configuration should give a detinfo::LArProperties::ScintPreScale() of 1.0.

Note
If the photon visibility library already includes the probability of the photon converting to a photoelectron, the quantum efficiency check here should be skipped by setting the efficiency to 1.

For each converting photon, a photoelectron is added to the channel by placing a template waveform shape into the channel waveform.

The timestamp of each waveform is based on the same scale as the trigger time, as defined by detinfo::DetectorClocks::TriggerTime(). On that scale, the timestamp pins down the time of the first sample of the waveform. Note that this is typically earlier than when the actual signal starts. More precisely, the signal is defined to start at an interest point (see FindTriggers() for their definition), and the waveform starts (at tick #0) earlier than that by a fraction PreTrigFraction of the readout window size ReadoutWindowSize (both are configuration parameters of the algorithm), allowing for that amount of pre-trigger data.

The configuration parameter TriggerOffsetPMT describes how much earlier than the trigger time the optical readout has started. Note that if an interest point (see above) happens early after optical readout has started, there might be not enough data to fill the pre-trigger data. In such case, the interest point will just be located earlier than usual within the final waveform. This situation may be caused for example by asynchronous physics events like scintillation light from cosmic rays or radioactive decay of the detector materials, or from a fluctuation of the noise.

Photoelectrons

The response of the PMT to a single photoelectron is passed to the algorithm as a full blown function of type SinglePhotonResponseFunc_t. The function needs to be valid for the lifetime of the algorithm, since the algorithm refers to without owning it, and it is expected not to change during that time. See icarus::opdet::SimPMTIcarus

To account for gain fluctuations, that shape is considered to correspond to a nominal gain (PMTspecs.gain configuration parameter), which is then fluctuated to obtain the effective gain. This feature can be disabled by setting configuration parameter FluctuateGain to false. The approximation used here is that the fluctuation is entirely due to the first stage of multiplication. The gain on the first stage is described as a random variable with Poisson distribution around the mean gain. The gain on a single photoelectron at the first stage is, in fact, an integral number in each case. The time spread of the signal may be increased by the difference in time of the different branches of the multiplication avalanche. Therefore, increasing or decreasing the number of branches, as it is done by changing the gain of the first stage, the time evolution of the signal will also be likewise affected. At this time we do not take this aspect into account in the simulation. For the nominal settings of a Hamamatsu 5912 photomultiplier (gain 10 ^7^, high multiplication on the first stage) the gain at the first stage is around 20, causing a fluctuation of about 20%. If the multiplication were equally distributed across the stages, that fluctuation would be almost 45%.

The first stage gain is computed by icarus::opdet::PMTsimulationAlg::ConfigurationParameters_t::PMTspecs_t::multiplicationStageGain().

Dark noise

Dark noise, i.e. the noise originating by "spontaneous" emission of a photoelectron in the photocathode without any external stimulation, is simulated by randomly extracting the time such emission happens. Each emission causes a photoelectron template waveform to be added at the extracted time. The rate of dark noise emission is set by configuration with DarkNoiseRate parameter.

Electronics noise

Electronics noise is described by Gaussian fluctuations of a given standard deviation, controlled by the configuration parameter AmpNoise. No noise correlation is simulated neither in time nor in space.

Configuration

PMT specifications

PMT specifications are used to evaluate the variance of the gain. The details of the calculation are documented in icarus::opdet::PMTsimulationAlg::ConfigurationParameters_t::PMTspecs_t::multiplicationStageGain().

The available parameters include:

Random number generators

Three independent random engines are currently used in the simulation:

Structure of the algorithm

This section needs completion.

The algorithm is serviceable immediately after construction. Construction relies on a custom parameter data structure.

An utility, PMTsimulationAlgMaker, splits the set up in two parts:

  1. configuration, where the full set of parameters is learned;
  2. set up, where service providers, random number engines and the external single photon response function are acquired.

This is supposed to make the creation of the filling of parameter structure and the creation of the algorithm easier. At that point, a single sim::SimPhotons can be processed (simulate()) at a time, or multiple at the same time (see the multithreading notes below).

The function used to describe the single particle response is customizable and it must in fact be specified by the caller, since there is no default form. The function must implement the PulseFunction_t interface.

Multithreading notes

The algorithm processes one channel at a time, and it does not depend on event-level information; therefore, the same algorithm object can be used to process many events in sequence. On the other hand, multithreading is impaired by the random number generation, in the sense that multithreading will break reproducibility if the random engine is not magically thread-resistant.

If the set up is event-dependent, then this object can't be used for multiple events at the same time. There is no global state, so at least different instances of the algorithm can be run at the same time. In fact, the creation of an algorithm is expected to take negligible time compared to its run time on a single event, and it is conceivable to create a new algorithm instance for each event.

Definition at line 341 of file PMTsimulationAlg.h.

Member Typedef Documentation

Definition at line 350 of file PMTsimulationAlg.h.

Definition at line 346 of file PMTsimulationAlg.h.

Definition at line 347 of file PMTsimulationAlg.h.

Definition at line 344 of file PMTsimulationAlg.h.

Definition at line 345 of file PMTsimulationAlg.h.

using icarus::opdet::PMTsimulationAlg::NoiseAdderFunc_t = void (PMTsimulationAlg::*)(Waveform_t&) const
private

Type of member function to add electronics noise.

Definition at line 531 of file PMTsimulationAlg.h.

Definition at line 521 of file PMTsimulationAlg.h.

Definition at line 353 of file PMTsimulationAlg.h.

Definition at line 348 of file PMTsimulationAlg.h.

Type of sampled pulse shape: sequence of samples, one per tick.

Definition at line 528 of file PMTsimulationAlg.h.

Definition at line 349 of file PMTsimulationAlg.h.

Definition at line 352 of file PMTsimulationAlg.h.

Type internally used for storing waveforms.

Definition at line 524 of file PMTsimulationAlg.h.

Numeric type in waveforms.

Definition at line 525 of file PMTsimulationAlg.h.

Constructor & Destructor Documentation

icarus::opdet::PMTsimulationAlg::PMTsimulationAlg ( ConfigurationParameters_t const &  config)

Constructor.

Definition at line 102 of file PMTsimulationAlg.cxx.

103  : fParams(config)
106  , fNsamples(fParams.readoutEnablePeriod * fSampling) // us * MHz cancels out
107  , wsp( // NOTE: wsp amplitude already includes sign from polarity
109  fSampling,
110  fParams.pulseSubsamples, // tick subsampling
111  1.0e-4_ADCf // stop sampling when ADC counts are below this value
112  )
116  )
117 {
118  using namespace util::quantities::electronics_literals;
119 
120  // mf::LogDebug("PMTsimulationAlg") << "Sampling = " << fSampling << std::endl;
121 
122  // shape of single pulse
123 
124  // Correction due to scalling factor applied during simulation if any
125  mf::LogDebug("PMTsimulationAlg") << "PMT corrected efficiency = " << fQE;
126 
127  if (fQE >= 1.0001) {
128  mf::LogWarning("PMTsimulationAlg")
129  << "WARNING: Quantum efficiency set in the configuration (QE="
130  << fParams.QEbase << ") seems to be too large!"
131  "\nThe photon visibility library is assumed to already include a"
132  " quantum efficiency of " << fParams.larProp->ScintPreScale() <<
133  " (`ScintPreScale` setting of `LArProperties` service)"
134  " and here we are requesting a higher one."
135  "\nThis configuration is not supported by `PMTsimulationAlg`,"
136  " and this simulation will effectively apply a quantum efficiency of "
138  ;
139  }
140 
141  // check that the sampled waveform has a sufficiently large range, so that
142  // tails are below 10^-3 ADC counts (absolute value);
143  // if this test fails, it's better to reduce the threshold in wsp constructor
144  // (for analytical pulses converging to 0) or have a longer sampling that does
145  // converge to 0 (10^-3 ADC is quite low though).
146  wsp.checkRange(1.0e-3_ADCf, "PMTsimulationAlg");
147 
148 } // icarus::opdet::PMTsimulationAlg::PMTsimulationAlg()
unsigned int pulseSubsamples
Number of tick subsamples.
double QEbase
Uncorrected PMT quantum efficiency.
bool useFastElectronicsNoise
Whether to use fast generator for electronics noise.
DiscretePhotoelectronPulse wsp
std::size_t fNsamples
Samples per waveform.
SinglePhotonResponseFunc_t const * pulseFunction
Single photon response function.
bool checkRange(ADCcount limit, std::string const &outputCat) const
Checks that the waveform tails not sampled are negligible.
microseconds readoutEnablePeriod
Time (us) for which pmt readout is enabled.
ElecClock const & OpticalClock() const noexcept
Borrow a const Optical clock with time set to Trigger time [us].
ConfigurationParameters_t fParams
Complete algorithm configuration.
virtual double ScintPreScale(bool prescale=true) const =0
megahertz fSampling
Wave sampling frequency [MHz].
double fQE
PMT quantum efficiency.
NoiseAdderFunc_t const fNoiseAdder
Single photon pulse (sampled).
do i e
constexpr double Frequency() const
Frequency in MHz.
Definition: ElecClock.h:191
detinfo::LArProperties const * larProp
LarProperties service provider.
void AddNoise(Waveform_t &wave) const
void AddNoise_faster(Waveform_t &wave) const
Same as AddNoise() but using an alternative generator.

Member Function Documentation

void icarus::opdet::PMTsimulationAlg::AddDarkNoise ( Waveform_t wave) const
private

Definition at line 595 of file PMTsimulationAlg.cxx.

595  {
596  /*
597  * We assume leakage current ("dark noise") is completely stochastic and
598  * distributed uniformly in time with a fixed and known rate.
599  *
600  * In these condition, the time between two consecutive events follows an
601  * exponential distribution with as decay constant the same rate.
602  * We extact all the leakage events (including the first one) according to
603  * that distribution.
604  *
605  * We follow the "standard" approach of subsampling of tick as for the
606  * photoelectron.
607  *
608  */
609  using namespace util::quantities::frequency_literals;
610 
611  if (fParams.darkNoiseRate <= 0.0_Hz) return; // no dark noise
612 
613  // CLHEP random objects do not understand quantities, so we use scalars;
614  // we choose to work with nanosecond
615  CLHEP::RandExponential random(*(fParams.darkNoiseRandomEngine),
616  (1.0 / fParams.darkNoiseRate).convertInto<nanoseconds>().value());
617 
618  // time to stop at: full duration of the waveform
619  nanoseconds const maxTime = static_cast<double>(wave.size()) / fSampling;
620 
621  // the time of first leakage event:
622  nanoseconds darkNoiseTime { random.fire() };
623 
624  TimeToTickAndSubtickConverter const toTickAndSubtick(wsp.nSubsamples());
625 
626  auto gainFluctuation = makeGainFluctuator();
627 
628  MF_LOG_TRACE("PMTsimulationAlg")
629  << "Adding dark noise (" << fParams.darkNoiseRate << ") up to " << maxTime;
630 
631  while (darkNoiseTime < maxTime) {
632 
633  auto const [ tick, subtick ] = toTickAndSubtick(darkNoiseTime * fSampling);
634 
635  double const n = gainFluctuation(1.0); // leakage is one photoelectron
636  MF_LOG_TRACE("PMTsimulationAlg")
637  << " * at " << darkNoiseTime << " (" << tick << ", subsample " << subtick
638  << ") x" << n;
639 
641  (wsp.subsample(subtick), wave, tick, static_cast<WaveformValue_t>(n));
642 
643  // time of the next leakage event:
644  darkNoiseTime += nanoseconds{ random.fire() };
645 
646  } // while
647 
648 } // icarus::opdet::PMTsimulationAlg::AddDarkNoise()
util::quantities::nanosecond nanoseconds
void AddPhotoelectrons(PulseSampling_t const &pulse, Waveform_t &wave, tick const time_bin, WaveformValue_t const n) const
Adds a number of pulses to a waveform, starting at a given tick.
constexpr value_t value() const
Returns the value of the quantity.
Definition: quantities.h:609
DiscretePhotoelectronPulse wsp
ADCcount::value_t WaveformValue_t
Numeric type in waveforms.
CLHEP::HepRandomEngine * darkNoiseRandomEngine
Dark noise random stream engine.
ConfigurationParameters_t fParams
Complete algorithm configuration.
auto makeGainFluctuator() const
Returns a configured gain fluctuator object.
megahertz fSampling
Wave sampling frequency [MHz].
decltype(auto) subsample(SubsampleIndex_t i) const
Returns the subsample specified by index i, undefined if invalid.
void icarus::opdet::PMTsimulationAlg::AddNoise ( Waveform_t wave) const
private

Definition at line 558 of file PMTsimulationAlg.cxx.

558  {
559 
560  CLHEP::RandGaussQ random
562  for(auto& sample: wave) {
563  ADCcount const noise { static_cast<float>(random.fire()) }; // Gaussian noise
564  sample += noise;
565  } // for sample
566 
567 } // PMTsimulationAlg::AddNoise()
DiscretePhotoelectronPulse::ADCcount ADCcount
constexpr value_t value() const
Returns the value of the quantity.
Definition: quantities.h:609
ConfigurationParameters_t fParams
Complete algorithm configuration.
CLHEP::HepRandomEngine * elecNoiseRandomEngine
Electronics noise random stream engine.
void icarus::opdet::PMTsimulationAlg::AddNoise_faster ( Waveform_t wave) const
private

Same as AddNoise() but using an alternative generator.

Definition at line 571 of file PMTsimulationAlg.cxx.

571  {
572 
573  /*
574  * Compared to AddNoise(), we use a somehow faster random generator;
575  * to squeeze the CPU cycles, we avoid the CLHEP interface as much as
576  * possible; the random number from the engine is immediately converted
577  * to single precision, and the rest of the math happens in there as well.
578  * No virtual interfaces nor indirection is involved within this function
579  * (except for CLHEP random engine). We generate a normal variable _z_
580  * (standard deviation 1, mean 0) and we just scale it to the desired
581  * standard deviation, not bothering to add the mean offset of 0.
582  * Note that unless the random engine is multi-thread safe, this function
583  * won't gain anything from multi-threading.
584  */
585  auto& engine = *fParams.elecNoiseRandomEngine;
586 
587  for(auto& sample: wave) {
588  sample += fParams.ampNoise * fFastGauss(engine.flat()); // Gaussian noise
589  } // for sample
590 
591 } // PMTsimulationAlg::AddNoise_faster()
ConfigurationParameters_t fParams
Complete algorithm configuration.
CLHEP::HepRandomEngine * elecNoiseRandomEngine
Electronics noise random stream engine.
static util::FastAndPoorGauss< 32768U, float > const fFastGauss
void icarus::opdet::PMTsimulationAlg::AddPhotoelectrons ( PulseSampling_t const &  pulse,
Waveform_t wave,
tick const  time_bin,
WaveformValue_t const  n 
) const
private

Adds a number of pulses to a waveform, starting at a given tick.

Parameters
pulsethe sampling to add, scaled, to the waveform
wavethe waveform the pulses will be added to
time_binthe tick of the waveform where the pulses start being added
nthe number of pulses added (it may be fractional)

All the samples of pulse are scaled by the factor n and then added to the sampling waveform wave, starting from the time_bin sample of this waveform.

The pulse samples are a sequence of ADC counts describing the single photoelectron pulse shape. The waveform is also a sequence of samples representing a optical detector channel digitized waveform, starting at tick #0.

Definition at line 517 of file PMTsimulationAlg.cxx.

520  {
521 
522  if (n == 0.0) return;
523 
524  if (n == 1.0) {
525  // simple addition
526  AddPulseShape(pulse, wave, time_bin, std::plus<ADCcount>());
527  }
528  else {
529  // multiply each `pulse` sample by `n`:
530  AddPulseShape(pulse, wave, time_bin, [n](auto a, auto b) { return a+n*b; });
531  }
532 
533 } // icarus::opdet::PMTsimulationAlg::AddPhotoelectrons()
process_name gaushit a
void AddPulseShape(PulseSampling_t const &pulse, Waveform_t &wave, tick const time_bin, Combine combination) const
Adds a pulse to a waveform, starting at a given tick.
template<typename Combine >
void icarus::opdet::PMTsimulationAlg::AddPulseShape ( PulseSampling_t const &  pulse,
Waveform_t wave,
tick const  time_bin,
Combine  combination 
) const
private

Adds a pulse to a waveform, starting at a given tick.

Template Parameters
Combinebinary operation combining two ADC counts into one
Parameters
pulsethe sampling to add to the waveform
wavethe waveform the pulse will be added to
time_binthe tick of the waveform where the pulse starts
combinationhow to combine the pulse and the waveform

This is the internal implementation of AddPhotoelectrons().

The combination functor behaves as a binary function taking the existing wave sample and the sample from the pulse at the same time and returning their combination as a new sample value.

Definition at line 538 of file PMTsimulationAlg.cxx.

542 {
543  std::size_t const min = time_bin.value();
544  std::size_t const max = std::min(min + pulse.size(), fNsamples);
545  if (min >= max) return;
546 
548  wave.begin() + min, wave.begin() + max,
549  pulse.begin(),
550  wave.begin() + min,
551  combination
552  );
553 
554 } // icarus::opdet::PMTsimulationAlg::AddPulseShape()
static constexpr Sample_t transform(Sample_t sample)
std::size_t fNsamples
Samples per waveform.
void icarus::opdet::PMTsimulationAlg::ApplySaturation ( Waveform_t waveform,
std::pair< ADCcount, ADCcount > const &  range 
) const
private

Applies the configured photoelectron saturation on the waveform, only if the saturation is cutting into the digitisation range.

Definition at line 681 of file PMTsimulationAlg.cxx.

682 {
683  //
684  // simple sharp capping/cupping of ADC values
685  //
686  auto const boundaries = saturationRange();
687 
688  // saturation is out of boundaries: do not apply it.
689  if ((boundaries.first <= range.first) && (boundaries.second >= range.second))
690  return;
691 
692  ClipWaveform(waveform, boundaries.first, boundaries.second);
693 
694 } // icarus::opdet::PMTsimulationAlg::ApplySaturation(range)
std::pair< ADCcount, ADCcount > saturationRange() const
Returns the ADC range allowed for photoelectron saturation.
static void ClipWaveform(Waveform_t &waveform, ADCcount min, ADCcount max)
Forces waveform ADC within the min to max range (max included).
void icarus::opdet::PMTsimulationAlg::ApplySaturation ( Waveform_t waveform) const
private

Applies the configured photoelectron saturation on the waveform.

Definition at line 668 of file PMTsimulationAlg.cxx.

669 {
670  //
671  // simple sharp capping/cupping of ADC values
672  //
673  auto const boundaries = saturationRange();
674  ClipWaveform(waveform, boundaries.first, boundaries.second);
675 
676 } // icarus::opdet::PMTsimulationAlg::ApplySaturation()
std::pair< ADCcount, ADCcount > saturationRange() const
Returns the ADC range allowed for photoelectron saturation.
static void ClipWaveform(Waveform_t &waveform, ADCcount min, ADCcount max)
Forces waveform ADC within the min to max range (max included).
void icarus::opdet::PMTsimulationAlg::ClipWaveform ( Waveform_t waveform,
ADCcount  min,
ADCcount  max 
)
staticprivate

Forces waveform ADC within the min to max range (max included).

Definition at line 700 of file PMTsimulationAlg.cxx.

701 {
702  using ClamperFunc_t = std::function<ADCcount(ADCcount)>;
703  ClamperFunc_t const clamper =
704  (min == std::numeric_limits<ADCcount>::min())
705  ? ((max == std::numeric_limits<ADCcount>::max())
706  ? ClamperFunc_t{ [](ADCcount s){ return s; } }
707  : ClamperFunc_t{ [max](ADCcount s){ return std::min(s, max); } }
708  )
709  : ((max == std::numeric_limits<ADCcount>::max())
710  ? ClamperFunc_t{ [min](ADCcount s){ return std::max(s, min); } }
711  : ClamperFunc_t{ [min,max](ADCcount s){ return std::clamp(s, min, max); } }
712  )
713  ;
714 
715  std::transform(waveform.cbegin(), waveform.cend(), waveform.begin(), clamper);
716 
717 } // icarus::opdet::PMTsimulationAlg::ClipWaveform()
static constexpr Sample_t transform(Sample_t sample)
DiscretePhotoelectronPulse::ADCcount ADCcount
then echo File list $list not found else cat $list while read file do echo $file sed s
Definition: file_to_url.sh:60
auto icarus::opdet::PMTsimulationAlg::CreateBeamGateTriggers ( ) const
private

Generate periodic interest points regardless the actual activity.

Returns
a collection of ticks where we pretend interesting activity to be
See Also
FindTriggers()

This methods emits a list of interest points according to the algorithm configuration. More precisely, if CreateBeamGateTriggers is configured true, BeamGateTriggerNReps interest points are generated at BeamGateTriggerRepPeriod intervals, starting from the beam gate time as defined by detinfo::DetectorClocks::BeamGateTime().

See FindTriggers() for the meaning of "interest point".

Note
It is assumed that tick 0 happens at a time defined by triggerOffsetPMT configuration parameter after the trigger (but since the value of that parameter is expected to be negative, tick 0 effectively happens before the trigger).

Definition at line 341 of file PMTsimulationAlg.cxx.

343  {
344  using namespace util::quantities::time_literals;
346 
347  detinfo::DetectorTimings const& timings
349 
350  std::vector<optical_tick> trigger_locations;
351  trigger_locations.reserve(fParams.beamGateTriggerNReps);
352  trigger_time const beamTime = timings.toTriggerTime(timings.BeamGateTime());
353 
354  for(unsigned int i_trig=0; i_trig<fParams.beamGateTriggerNReps; ++i_trig) {
355  trigger_time const trig_time
356  = beamTime
359  ;
360  if (trig_time < 0_us) continue;
361  if (trig_time > fParams.readoutEnablePeriod) break;
362  trigger_locations.push_back
363  (optical_tick::castFrom(trig_time.quantity()*fSampling));
364  }
365  return trigger_locations;
366  }
size_t beamGateTriggerNReps
Number of beamgate trigger reps to produce.
timescale_traits< TriggerTimeCategory >::time_point_t trigger_time
A point in time on the trigger time scale.
detinfo::DetectorTimings makeDetectorTimings(detinfo::DetectorClocksData const &detClocks)
microseconds readoutEnablePeriod
Time (us) for which pmt readout is enabled.
microseconds triggerOffsetPMT
Time relative to trigger when PMT readout starts TODO make it a trigger_time point.
ConfigurationParameters_t fParams
Complete algorithm configuration.
megahertz fSampling
Wave sampling frequency [MHz].
A class exposing an upgraded interface of detinfo::DetectorClocksData.
microseconds beamGateTriggerRepPeriod
Repetition Period (us) for BeamGateTriggers TODO make this a time_interval.
std::vector< raw::OpDetWaveform > icarus::opdet::PMTsimulationAlg::CreateFixedSizeOpDetWaveforms ( raw::Channel_t  opChannel,
Waveform_t const &  waveform 
) const
private

Creates raw::OpDetWaveform objects from a waveform data.

Parameters
opChannelnumber of optical detector channel the data belongs to
waveformthe waveform data
Returns
a collection of raw::OpDetWaveform

The waveform data is a sequence of samples on the optical detector channel, starting at the beginning of the optical time clock, that is set by the algorithm configuration as the global hardware trigger time (configured in detinfo::DetectorClocks) and an offset. It may be obtained from CreateFullWaveform().

Multiple waveforms may be returned for a single channel, since a form of zero suppression is applied by the simulated hardware. The waveforms are guaranteed to be non-overlapping, non-contiguous and sorted with increasing timestamp.

The procedure attempts to mimic the working mode of CAEN V1730B boards as described on page 32 of the manual "UM2792_V1730_V1725_rev2.pdf" in SBN DocDB 15024.

Algorithm details

In the board readout language, the "trigger" is the per-channel information that the signal on the channel has passed the threshold.

It is critical to understand the details of the generation of the triggers. At the time of writing, the algorithm in FindTriggers() emits a trigger every time the signal passes from under threshold to beyond threshold. Assuming that the threshold is at less than one photoelectron, this kind of behavior implies that every scintillation photons arriving on the tail of the first (large?) signal will add a new waveform in tail. It is not clear what happens if two more triggers happen while the window from the first trigger is still open.

The assumptions in the code include:

  1. overlapped triggers are not discarded (p. 32);
  2. board is set for self-trigger (p. 38);
  3. each waveform has a fixed duration (except the ones overlapping the previous one);
  4. when a trigger starts the recording window on a channel, only the last trigger happening during that window ("overlapping"), if any, is kept.
  5. at decoding time, contiguous buffers are merged in a single waveform

The fixed duration of the waveform if the sum of pre- and post-trigger window length. If during this window another trigger is emitted, a new window will follow the current one and it will be long enough to contain all the post-trigger data.

Definition at line 409 of file PMTsimulationAlg.cxx.

410 {
411  /*
412  * Plan:
413  *
414  * 1. set up
415  * 2. get the trigger points of the waveform
416  * 3. define the size of data around each trigger to commit to waveforms
417  * (also merge contiguous and overlapping intervals)
418  * 4. create the actual `raw::OpDetWaveform` objects
419  *
420  */
421 
422  //
423  // parameters check and setup
424  //
425 
426  // not a big deal if this assertion fails, but a bit more care needs to be
427  // taken in comparisons and subtractions
428  static_assert(
429  std::is_signed_v<optical_tick::value_t>,
430  "This algorithm requires tick type to be signed."
431  );
432 
433  using namespace detinfo::timescales; // electronics_time, time_interval, ...
434 
435  auto const pretrigSize = optical_time_ticks::castFrom(fParams.pretrigSize());
436  auto const posttrigSize = optical_time_ticks::castFrom(fParams.posttrigSize());
437 
438  detinfo::DetectorTimings const& timings
440 
441  // first viable tick number: since this is the item index in `wvfm`, it's 0
442  optical_tick const firstTick { 0 };
443 
444  // use hardware trigger time plus the configured offset as waveform start time
445  OpDetWaveformMaker_t createOpDetWaveform {
446  waveform,
447  timings.TriggerTime() + time_interval{ fParams.triggerOffsetPMT },
448  1.0 / fSampling
449  };
450 
451  //
452  // get the PMT channel triggers to consider
453  //
454 
455  // prepare the set of triggers
456  std::vector<optical_tick> const trigger_locations = FindTriggers(waveform);
457  auto const tend = trigger_locations.end();
458 
459  // find the first viable trigger
460  auto tooEarlyTrigger = [earliest = firstTick + pretrigSize](optical_tick t)
461  { return t < earliest; };
462  auto iNextTrigger
463  = std::find_if_not(trigger_locations.begin(), tend, tooEarlyTrigger);
464 
465  //
466  // collect all buffer ranges and merge them
467  //
468  using BufferRange_t = OpDetWaveformMaker_t::BufferRange_t;
469  auto makeBuffer
470  = [pretrigSize, posttrigSize](optical_tick triggerTime) -> BufferRange_t
471  { return { triggerTime - pretrigSize, triggerTime + posttrigSize }; }
472  ;
473 
474  std::vector<BufferRange_t> buffers;
475  buffers.reserve(std::distance(iNextTrigger, tend)); // worst case
476 
477  auto earliestBufferStart { firstTick };
478  while (iNextTrigger != tend) {
479 
480  BufferRange_t const buffer = makeBuffer(*iNextTrigger);
481 
482  if (buffer.first < earliestBufferStart) { // extend the previous buffer
483  assert(!buffers.empty()); // guaranteed because we skipped early triggers
484  buffers.back().second = buffer.second;
485  }
486  else buffers.emplace_back(buffer);
487 
488  earliestBufferStart = buffer.second;
489 
490  ++iNextTrigger;
491  } // while
492 
493  //
494  // turn each buffer into a waveform
495  //
496  MF_LOG_TRACE("PMTsimulationAlg")
497  << "Channel #" << opChannel << ": " << buffers.size() << " waveforms for "
498  << trigger_locations.size() << " triggers"
499  ;
500  std::vector<raw::OpDetWaveform> output_opdets;
501  for (BufferRange_t const& buffer: buffers) {
502 
503  output_opdets.push_back(createOpDetWaveform(opChannel, buffer));
504 
505  } // for buffers
506 
507  return output_opdets;
508 } // icarus::opdet::PMTsimulationAlg::CreateFixedSizeOpDetWaveforms()
icarus::opdet::OpDetWaveformMakerClass< ADCcount > OpDetWaveformMaker_t
std::vector< optical_tick > FindTriggers(Waveform_t const &wvfm) const
Ticks in the specified waveform where some signal activity starts.
double distance(geo::Point_t const &point, CathodeDesc_t const &cathode)
Returns the distance of a point from the cathode.
detinfo::DetectorTimings makeDetectorTimings(detinfo::DetectorClocksData const &detClocks)
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
ConfigurationParameters_t fParams
Complete algorithm configuration.
timescale_traits< OpticalTimeCategory >::tick_t optical_tick
megahertz fSampling
Wave sampling frequency [MHz].
A class exposing an upgraded interface of detinfo::DetectorClocksData.
std::pair< detinfo::timescales::optical_tick, detinfo::timescales::optical_tick > BufferRange_t
auto icarus::opdet::PMTsimulationAlg::CreateFullWaveform ( sim::SimPhotons const &  photons,
sim::SimPhotonsLite const &  lite_photons,
std::optional< sim::SimPhotons > &  photons_used 
) const
private

Creates raw::OpDetWaveform objects from simulated photoelectrons.

Parameters
photonsthe simulated list of photoelectrons
photons_used(output) list of used photoelectrons
Returns
a collection of digitised raw::OpDetWaveform objects

This function performs the digitization of a optical detector channel which is collecting the photoelectrons in the photons list.

The photoelectrons are already screened for quantum efficiency: all of them are considered for use. It is still possible, if a reduction of the quantum efficiency is requested, that some of them are discarded.

The photons_used output argument is constructed and filled only if the configuration of the algorithm requires the creation of a list of used photons.

Definition at line 185 of file PMTsimulationAlg.cxx.

189 {
190 
191  using namespace util::quantities::time_literals;
192  using namespace util::quantities::frequency_literals;
193  using namespace util::quantities::electronics_literals;
194  using namespace detinfo::timescales;
195 
196  detinfo::DetectorTimings const& timings
198 
199  tick const endSample = tick::castFrom(fNsamples);
200 
201  //
202  // collect the amount of photoelectrons arriving at each subtick;
203  // the waveform is split in groups of photons at the same relative subtick
204  // (i.e. first all the photons on the first subtick of a tick, then
205  // all the photons on the second subtick of a tick, and so on);
206  // storage is by subtick group (vector index is the subtick), then by
207  // tick (unordered map index is the tick number).
208  //
209  std::vector<std::unordered_map<tick, unsigned int>> peMaps
210  (wsp.nSubsamples());
211 
212  // returns tick and relative subtick number
213  TimeToTickAndSubtickConverter const toTickAndSubtick(peMaps.size());
214 
215 // auto start = std::chrono::high_resolution_clock::now();
216 
217  if (photons_used) {
218  photons_used->clear();
219  photons_used->SetChannel(photons.OpChannel());
220  }
221  for(auto const& ph : photons) {
222  if (!KicksPhotoelectron()) continue;
223 
224  if (photons_used) photons_used->push_back(ph); // copy
225 
226  simulation_time const photonTime { ph.Time };
227 
228  trigger_time const mytime
229  = timings.toTriggerTime(photonTime)
231  ;
232  if ((mytime < 0.0_us) || (mytime >= fParams.readoutEnablePeriod)) continue;
233 
234  auto const [ tick, subtick ]
235  = toTickAndSubtick(mytime.quantity() * fSampling);
236  /*
237  mf::LogTrace("PMTsimulationAlg")
238  << "Photon at " << photonTime << ", optical time " << mytime
239  << " => tick " << tick_d
240  << " => sample " << tick << " subsample " << subtick
241  ;
242  */
243  if (tick >= endSample) continue;
244  ++peMaps[subtick][tick];
245  } // for photons
246 
247 // auto end = std::chrono::high_resolution_clock::now();
248 // std::chrono::duration<double> diff = end-start;
249 // std::cout << "\tcollected pes... " << photons.OpChannel() << " " << diff.count() << std::endl;
250 // start=std::chrono::high_resolution_clock::now();
251 
252  // Add SimPhotonsLite. Loop over geant ticks (=ns).
253 
254  for(auto const& [ time_ns, nphotons ]: lite_photons.DetectedPhotons) {
255 
256  // Count photoelectrons.
257 
258  unsigned int nPE = 0;
259  for(int i=0; i<nphotons; ++i) {
260  if(KicksPhotoelectron())
261  ++nPE;
262  }
263 
264  // Convert photon time bin to ticks.
265 
266  simulation_time const photonTime { time_ns + 0.5 };
267  trigger_time const mytime
268  = timings.toTriggerTime(photonTime)
270  if ((mytime < 0.0_us) || (mytime >= fParams.readoutEnablePeriod)) continue;
271 
272  auto const [ tick, subtick ]
273  = toTickAndSubtick(mytime.quantity() * fSampling);
274  if (tick < endSample)
275  peMaps[subtick][tick] += nPE;
276  }
277 
278  //
279  // add the collected photoelectrons to the waveform
280  //
282 
283  unsigned int nTotalPE [[gnu::unused]] = 0U; // unused if not in `debug` mode
284  double nTotalEffectivePE [[gnu::unused]] = 0U; // unused if not in `debug` mode
285 
286  auto gainFluctuation = makeGainFluctuator();
287 
288  // go though all subsamples (starting each at a fraction of a tick)
289  for (auto const& [ iSubsample, peMap ]: util::enumerate(peMaps)) {
290 
291  // this is the waveform sampling for the selected subsample:
292  auto const& subsample = wsp.subsample(iSubsample);
293 
294  for (auto const& [ startTick, nPE ]: peMap) {
295  nTotalPE += nPE;
296 
297  double const nEffectivePE = gainFluctuation(nPE);
298  nTotalEffectivePE += nEffectivePE;
299 
301  subsample, waveform, startTick,
302  static_cast<WaveformValue_t>(nEffectivePE)
303  );
304  // std::cout << "Channel=" << photons.OpChannel() << ", subsample=" << iSubsample << ", tick=" << startTick << ", nPE=" << nPE << ", ePE=" << nEffectivePE << std::endl;
305 
306  } // for sample
307  } // for subsamples
308  MF_LOG_TRACE("PMTsimulationAlg")
309  << nTotalPE << " photoelectrons at "
310  << std::accumulate(
311  peMaps.begin(), peMaps.end(), 0U,
312  [](unsigned int n, auto const& map){ return n + map.size(); }
313  )
314  << " times in channel " << photons.OpChannel()
315  ;
316 
317 // end=std::chrono::high_resolution_clock::now(); diff = end-start;
318 // std::cout << "\tadded pes... " << photons.OpChannel() << " " << diff.count() << std::endl;
319 // start=std::chrono::high_resolution_clock::now();
320 
321  if(fParams.ampNoise > 0.0_ADCf) (this->*fNoiseAdder)(waveform);
322  if(fParams.darkNoiseRate > 0.0_Hz) AddDarkNoise(waveform);
323 
324 // end=std::chrono::high_resolution_clock::now(); diff = end-start;
325 // std::cout << "\tadded noise... " << photons.OpChannel() << " " << diff.count() << std::endl;
326 // start=std::chrono::high_resolution_clock::now();
327 
328  // saturation in terms of photoelectrons (sharp);
329  auto const ADCrange = fParams.ADCrange();
330  ApplySaturation(waveform, ADCrange);
331 
332  // clip to the ADC range, 0 -- 2
333  ClipWaveform(waveform, ADCrange.first, ADCrange.second);
334 
335 // end=std::chrono::high_resolution_clock::now(); diff = end-start;
336 // std::cout << "\tadded saturation... " << photons.OpChannel() << " " << diff.count() << std::endl;
337 
338  return waveform;
339  } // CreateFullWaveform()
void AddPhotoelectrons(PulseSampling_t const &pulse, Waveform_t &wave, tick const time_bin, WaveformValue_t const n) const
Adds a number of pulses to a waveform, starting at a given tick.
static constexpr quantity_t castFrom(U value)
Returns a new quantity initialized with the specified value.
Definition: quantities.h:825
DiscretePhotoelectronPulse wsp
auto enumerate(Iterables &&...iterables)
Range-for loop helper tracking the number of iteration.
Definition: enumerate.h:69
std::size_t fNsamples
Samples per waveform.
OpDetWaveformMaker_t::WaveformData_t Waveform_t
Type internally used for storing waveforms.
timescale_traits< TriggerTimeCategory >::time_point_t trigger_time
A point in time on the trigger time scale.
void ApplySaturation(Waveform_t &waveform, std::pair< ADCcount, ADCcount > const &range) const
void AddDarkNoise(Waveform_t &wave) const
bool KicksPhotoelectron() const
Returns a random response whether a photon generates a photoelectron.
timescale_traits< SimulationTimeCategory >::time_point_t simulation_time
A point in time on the simulation time scale.
detinfo::DetectorTimings makeDetectorTimings(detinfo::DetectorClocksData const &detClocks)
microseconds readoutEnablePeriod
Time (us) for which pmt readout is enabled.
microseconds triggerOffsetPMT
Time relative to trigger when PMT readout starts TODO make it a trigger_time point.
ConfigurationParameters_t fParams
Complete algorithm configuration.
auto makeGainFluctuator() const
Returns a configured gain fluctuator object.
std::pair< ADCcount, ADCcount > ADCrange() const
static void ClipWaveform(Waveform_t &waveform, ADCcount min, ADCcount max)
Forces waveform ADC within the min to max range (max included).
megahertz fSampling
Wave sampling frequency [MHz].
NoiseAdderFunc_t const fNoiseAdder
Single photon pulse (sampled).
A class exposing an upgraded interface of detinfo::DetectorClocksData.
decltype(auto) subsample(SubsampleIndex_t i) const
Returns the subsample specified by index i, undefined if invalid.
auto icarus::opdet::PMTsimulationAlg::FindTriggers ( Waveform_t const &  wvfm) const
private

Ticks in the specified waveform where some signal activity starts.

Returns
a collection of ticks with interesting activity, sorted
See Also
CreateBeamGateTriggers()

We define an "interest point" a time when some activity in the waveform is considered interesting enough to be recorded. This method returns a list of interest points, in the form of the index they are located at in the waveform wvfm.

In general (but with the exception noted below), a time becomes an interest point if the sample recorded at that time is above the threshold set by the configuration parameter ThresholdADC.

These interest points are local readout triggers that drive zero suppression on the optical readout channel and that are not necessarily causing any level of event trigger.

This method also adds the mandatory beam gate interest points as explained in CreateBeamGateTriggers() documentation. These are additional interest points that are added independently of whether there is actual interesting activity in there.

Definition at line 368 of file PMTsimulationAlg.cxx.

370  {
371  std::vector<optical_tick> trigger_locations;
372 
373  // find all ticks at which we would trigger readout
374  bool above_thresh=false;
375  for(size_t i_t=0; i_t<wvfm.size(); ++i_t){
376 
377  auto const val { fParams.pulsePolarity* (wvfm[i_t]-fParams.baseline) };
378 
379  if(!above_thresh && val>=fParams.thresholdADC){
380  above_thresh=true;
381  trigger_locations.push_back(optical_tick::castFrom(i_t));
382  }
383  else if(above_thresh && val<fParams.thresholdADC){
384  above_thresh=false;
385  }
386 
387  }//end loop over waveform
388 
389  // next, add the triggers injected at beam gate time
391  auto beamGateTriggers = CreateBeamGateTriggers();
392 
393  // insert the new triggers and sort them
394  trigger_locations.insert(trigger_locations.end(),
395  beamGateTriggers.begin(), beamGateTriggers.end());
396  std::inplace_merge(
397  trigger_locations.begin(),
398  trigger_locations.end() - beamGateTriggers.size(),
399  trigger_locations.end()
400  );
401  }
402 
403  return trigger_locations;
404  }
bool createBeamGateTriggers
Option to create unbiased readout around beam spill.
ADCcount thresholdADC
ADC Threshold for self-triggered readout.
std::vector< optical_tick > CreateBeamGateTriggers() const
Generate periodic interest points regardless the actual activity.
ConfigurationParameters_t fParams
Complete algorithm configuration.
int pulsePolarity
Pulse polarity (=1 for positive, =-1 for negative)
bool icarus::opdet::PMTsimulationAlg::KicksPhotoelectron ( ) const
private

Returns a random response whether a photon generates a photoelectron.

Definition at line 512 of file PMTsimulationAlg.cxx.

513  { return CLHEP::RandFlat::shoot(fParams.randomEngine) < fQE; }
ConfigurationParameters_t fParams
Complete algorithm configuration.
CLHEP::HepRandomEngine * randomEngine
Main random stream engine.
double fQE
PMT quantum efficiency.
auto icarus::opdet::PMTsimulationAlg::makeGainFluctuator ( ) const
private

Returns a configured gain fluctuator object.

Definition at line 169 of file PMTsimulationAlg.cxx.

169  {
170 
171  using Fluctuator_t = GainFluctuator<CLHEP::RandPoisson>;
172 
174  double const refGain = fParams.PMTspecs.firstStageGain();
175  return Fluctuator_t
176  { refGain, CLHEP::RandPoisson{ *fParams.gainRandomEngine, refGain } };
177  }
178  else return Fluctuator_t{}; // default-constructed does not fluctuate anything
179 
180 } // icarus::opdet::PMTsimulationAlg::makeGainFluctuator()
CLHEP::HepRandomEngine * gainRandomEngine
Random stream engine for gain fluctuations.
ConfigurationParameters_t fParams
Complete algorithm configuration.
double firstStageGain() const
Returns the gain from the first stage of PMT multiplication.
template<typename Stream >
void icarus::opdet::PMTsimulationAlg::printConfiguration ( Stream &&  out,
std::string  indent = "" 
) const

Prints the configuration into the specified output stream.

Definition at line 1064 of file PMTsimulationAlg.h.

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 
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()
unsigned int pulseSubsamples
Number of tick subsamples.
size_t beamGateTriggerNReps
Number of beamgate trigger reps to produce.
float pretrigFraction
Fraction of window size to be before &quot;trigger&quot;.
void dump(Stream &&out, std::string const &indent, std::string const &firstIndent) const
Prints on stream the parameters of this shape.
bool useFastElectronicsNoise
Whether to use fast generator for electronics noise.
bool createBeamGateTriggers
Option to create unbiased readout around beam spill.
DiscretePhotoelectronPulse wsp
ADCcount thresholdADC
ADC Threshold for self-triggered readout.
std::size_t fNsamples
Samples per waveform.
size_t readoutWindowSize
ReadoutWindowSize in samples.
unsigned int ADCbits
Number of bits of the digitizer.
ConfigurationParameters_t fParams
Complete algorithm configuration.
std::pair< ADCcount, ADCcount > ADCrange() const
int pulsePolarity
Pulse polarity (=1 for positive, =-1 for negative)
megahertz fSampling
Wave sampling frequency [MHz].
bool trackSelectedPhotons
Whether to track the scintillation photons used.
microseconds beamGateTriggerRepPeriod
Repetition Period (us) for BeamGateTriggers TODO make this a time_interval.
double firstStageGain() const
Returns the gain from the first stage of PMT multiplication.
auto icarus::opdet::PMTsimulationAlg::saturationRange ( ) const
private

Returns the ADC range allowed for photoelectron saturation.

Definition at line 653 of file PMTsimulationAlg.cxx.

655 {
656  std::pair<ADCcount, ADCcount> range = fParams.ADCrange();
657  if (fParams.saturation > 0) {
658  ADCcount const saturationLevel
660  ((fParams.pulsePolarity > 0)? range.second: range.first) = saturationLevel;
661  }
662  return range;
663 } // icarus::opdet::PMTsimulationAlg::saturationRange()
ADCcount peakAmplitude() const
Returns the peak amplitude in ADC counts.
DiscretePhotoelectronPulse::ADCcount ADCcount
DiscretePhotoelectronPulse wsp
ConfigurationParameters_t fParams
Complete algorithm configuration.
std::pair< ADCcount, ADCcount > ADCrange() const
int pulsePolarity
Pulse polarity (=1 for positive, =-1 for negative)
std::tuple< std::vector< raw::OpDetWaveform >, std::optional< sim::SimPhotons > > icarus::opdet::PMTsimulationAlg::simulate ( sim::SimPhotons const &  photons,
sim::SimPhotonsLite const &  lite_photons 
)

Returns the waveforms originating from simulated photons.

Parameters
photonsall the photons simulated to land on the channel
Returns
a list of optical waveforms, response to those photons, and which photons were used (if requested)

Due to threshold readout, a single channel may result in multiple waveforms, which are all on the same channel but disjunct in time.

The second element of the return value is optional and filled only if the trackSelectedPhotons configuration parameter is set to true. In that case, the returned sim::SimPhotons contains a copy of each of the photons contributing to any of the waveforms.

Definition at line 153 of file PMTsimulationAlg.cxx.

155 {
156  std::optional<sim::SimPhotons> photons_used;
157 
158  Waveform_t const waveform = CreateFullWaveform(photons, lite_photons, photons_used);
159 
160  return {
161  CreateFixedSizeOpDetWaveforms(photons.OpChannel(), waveform),
162  std::move(photons_used)
163  };
164 
165 } // icarus::opdet::PMTsimulationAlg::simulate()
std::vector< raw::OpDetWaveform > CreateFixedSizeOpDetWaveforms(raw::Channel_t opChannel, Waveform_t const &waveform) const
Creates raw::OpDetWaveform objects from a waveform data.
Waveform_t CreateFullWaveform(sim::SimPhotons const &photons, sim::SimPhotonsLite const &lite_photons, std::optional< sim::SimPhotons > &photons_used) const
Creates raw::OpDetWaveform objects from simulated photoelectrons.
OpDetWaveformMaker_t::WaveformData_t Waveform_t
Type internally used for storing waveforms.

Member Data Documentation

util::FastAndPoorGauss< 32768U, float > const icarus::opdet::PMTsimulationAlg::fFastGauss
staticprivate

Definition at line 589 of file PMTsimulationAlg.h.

NoiseAdderFunc_t const icarus::opdet::PMTsimulationAlg::fNoiseAdder
private

Single photon pulse (sampled).

Selected electronics noise method. Transformation uniform to Gaussian for electronics noise.

Definition at line 586 of file PMTsimulationAlg.h.

std::size_t icarus::opdet::PMTsimulationAlg::fNsamples
private

Samples per waveform.

Definition at line 582 of file PMTsimulationAlg.h.

ConfigurationParameters_t icarus::opdet::PMTsimulationAlg::fParams
private

Complete algorithm configuration.

Definition at line 578 of file PMTsimulationAlg.h.

double icarus::opdet::PMTsimulationAlg::fQE
private

PMT quantum efficiency.

Definition at line 580 of file PMTsimulationAlg.h.

megahertz icarus::opdet::PMTsimulationAlg::fSampling
private

Wave sampling frequency [MHz].

Definition at line 581 of file PMTsimulationAlg.h.

DiscretePhotoelectronPulse icarus::opdet::PMTsimulationAlg::wsp
private

Definition at line 584 of file PMTsimulationAlg.h.


The documentation for this class was generated from the following files: