24 #include "cetlib_except/exception.h"
27 #include "CLHEP/Random/RandFlat.h"
28 #include "CLHEP/Random/RandPoisson.h"
29 #include "CLHEP/Random/RandGaussQ.h"
30 #include "CLHEP/Random/RandExponential.h"
34 #include <unordered_map>
43 #if __cplusplus < 202002L // C++20?
50 {
return std::forward<T>(v); }
55 # error("Replace util::identity with std::identity (#include <functional>)")
83 = std::pow(mu / std::pow(prodRho, k), 1.0/static_cast<double>(N));
84 return aVk * std::pow(voltageDistribution.at(i - 1),
k);
91 (std::vector<double>&& Rs)
93 voltageDistribution = std::move(Rs);
94 double total = std::accumulate
95 (voltageDistribution.begin(), voltageDistribution.end(), 0.0);
96 for (
auto& R: voltageDistribution) R /= total;
118 using namespace util::quantities::electronics_literals;
125 mf::LogDebug(
"PMTsimulationAlg") <<
"PMT corrected efficiency = " <<
fQE;
128 mf::LogWarning(
"PMTsimulationAlg")
129 <<
"WARNING: Quantum efficiency set in the configuration (QE="
131 "\nThe photon visibility library is assumed to already include a"
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 "
152 std::tuple<std::vector<raw::OpDetWaveform>, std::optional<sim::SimPhotons>>
156 std::optional<sim::SimPhotons> photons_used;
162 std::move(photons_used)
178 else return Fluctuator_t{};
187 std::optional<sim::SimPhotons>& photons_used)
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;
209 std::vector<std::unordered_map<tick, unsigned int>> peMaps
218 photons_used->clear();
219 photons_used->SetChannel(photons.
OpChannel());
221 for(
auto const& ph : photons) {
224 if (photons_used) photons_used->push_back(ph);
229 = timings.toTriggerTime(photonTime)
234 auto const [
tick, subtick ]
235 = toTickAndSubtick(mytime.quantity() *
fSampling);
243 if (
tick >= endSample)
continue;
244 ++peMaps[subtick][
tick];
258 unsigned int nPE = 0;
259 for(
int i=0; i<nphotons; ++i) {
268 = timings.toTriggerTime(photonTime)
272 auto const [
tick, subtick ]
273 = toTickAndSubtick(mytime.quantity() *
fSampling);
274 if (
tick < endSample)
275 peMaps[subtick][
tick] += nPE;
283 unsigned int nTotalPE [[gnu::unused]] = 0U;
284 double nTotalEffectivePE [[gnu::unused]] = 0U;
294 for (
auto const& [ startTick, nPE ]: peMap) {
297 double const nEffectivePE = gainFluctuation(nPE);
298 nTotalEffectivePE += nEffectivePE;
301 subsample, waveform, startTick,
302 static_cast<WaveformValue_t>(nEffectivePE)
308 MF_LOG_TRACE(
"PMTsimulationAlg")
309 << nTotalPE <<
" photoelectrons at "
311 peMaps.begin(), peMaps.end(), 0U,
312 [](
unsigned int n,
auto const& map){
return n + map.size(); }
314 <<
" times in channel " << photons.OpChannel()
344 using namespace util::quantities::time_literals;
350 std::vector<optical_tick> trigger_locations;
352 trigger_time const beamTime = timings.toTriggerTime(timings.BeamGateTime());
360 if (trig_time < 0_us)
continue;
362 trigger_locations.push_back
363 (optical_tick::castFrom(trig_time.quantity()*
fSampling));
365 return trigger_locations;
369 -> std::vector<optical_tick>
371 std::vector<optical_tick> trigger_locations;
374 bool above_thresh=
false;
375 for(
size_t i_t=0; i_t<wvfm.size(); ++i_t){
381 trigger_locations.push_back(optical_tick::castFrom(i_t));
394 trigger_locations.insert(trigger_locations.end(),
395 beamGateTriggers.begin(), beamGateTriggers.end());
397 trigger_locations.begin(),
398 trigger_locations.end() - beamGateTriggers.size(),
399 trigger_locations.end()
403 return trigger_locations;
407 std::vector<raw::OpDetWaveform>
429 std::is_signed_v<optical_tick::value_t>,
430 "This algorithm requires tick type to be signed."
433 using namespace detinfo::timescales;
456 std::vector<optical_tick>
const trigger_locations =
FindTriggers(waveform);
457 auto const tend = trigger_locations.end();
461 {
return t < earliest; };
463 = std::find_if_not(trigger_locations.begin(), tend, tooEarlyTrigger);
474 std::vector<BufferRange_t> buffers;
477 auto earliestBufferStart { firstTick };
478 while (iNextTrigger != tend) {
480 BufferRange_t
const buffer = makeBuffer(*iNextTrigger);
482 if (buffer.first < earliestBufferStart) {
483 assert(!buffers.empty());
484 buffers.back().second = buffer.second;
486 else buffers.emplace_back(buffer);
488 earliestBufferStart = buffer.second;
496 MF_LOG_TRACE(
"PMTsimulationAlg")
497 <<
"Channel #" << opChannel <<
": " << buffers.size() <<
" waveforms for "
498 << trigger_locations.size() <<
" triggers"
500 std::vector<raw::OpDetWaveform> output_opdets;
501 for (BufferRange_t
const& buffer: buffers) {
503 output_opdets.push_back(createOpDetWaveform(opChannel, buffer));
507 return output_opdets;
522 if (n == 0.0)
return;
530 AddPulseShape(pulse, wave, time_bin, [n](
auto a,
auto b) {
return a+n*b; });
537 template <
typename Combine>
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;
548 wave.begin() + min, wave.begin() + max,
560 CLHEP::RandGaussQ random
562 for(
auto& sample: wave) {
563 ADCcount const noise {
static_cast<float>(random.fire()) };
587 for(
auto& sample: wave) {
609 using namespace util::quantities::frequency_literals;
628 MF_LOG_TRACE(
"PMTsimulationAlg")
631 while (darkNoiseTime < maxTime) {
633 auto const [
tick, subtick ] = toTickAndSubtick(darkNoiseTime *
fSampling);
635 double const n = gainFluctuation(1.0);
636 MF_LOG_TRACE(
"PMTsimulationAlg")
637 <<
" * at " << darkNoiseTime <<
" (" <<
tick <<
", subsample " << subtick
658 ADCcount
const saturationLevel
674 ClipWaveform(waveform, boundaries.first, boundaries.second);
681 (
Waveform_t& waveform, std::pair<ADCcount, ADCcount>
const& range)
const
689 if ((boundaries.first <= range.first) && (boundaries.second >= range.second))
692 ClipWaveform(waveform, boundaries.first, boundaries.second);
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); } }
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); } }
715 std::transform(waveform.cbegin(), waveform.cend(), waveform.begin(), clamper);
721 auto icarus::opdet::PMTsimulationAlg::TimeToTickAndSubtickConverter::operator()
722 (
double const tick_d)
const -> std::tuple<tick, SubsampleIndex_t>
724 double const tickNumber_d = std::floor(
tick_d);
725 double const subtick = std::floor((
tick_d - tickNumber_d) * fNSubsamples);
734 template <
typename Rand>
739 ? (fRandomGain->fire(
n * fReferenceGain) / fReferenceGain):
n;
762 fBaseConfig.ADCbits = config.
ADCBits();
771 fBaseConfig.saturation = config.
Saturation().value_or(0);
772 fBaseConfig.QEbase = config.
QE();
773 fBaseConfig.PMTspecs.dynodeK =
PMTspecs.DynodeK();
776 fBaseConfig.PMTspecs.gain =
PMTspecs.Gain();
806 if (
std::abs(fBaseConfig.pulsePolarity) != 1.0) {
807 throw cet::exception(
"PMTsimulationAlg")
808 <<
"Pulse polarity settings can be only +1.0 or -1.0 (got: "
809 << fBaseConfig.pulsePolarity <<
")\n"
813 if (fBaseConfig.doGainFluctuations) {
814 double const mu0 = fBaseConfig.PMTspecs.firstStageGain();
815 if (!std::isnormal(mu0) || (mu0 < 0.0)) {
816 cet::exception
e(
"PMTsimulationAlg");
817 e <<
"PMT gain " << fBaseConfig.PMTspecs.gain
818 <<
", dynode resistance values {";
819 for (
double rho: fBaseConfig.PMTspecs.voltageDistribution)
821 e <<
" } and dynode k constant " << fBaseConfig.PMTspecs.dynodeK
822 <<
" resulted in an invalid gain " << mu0
823 <<
" for the first multiplication stage!\n";
832 std::unique_ptr<icarus::opdet::PMTsimulationAlg>
837 CLHEP::HepRandomEngine& mainRandomEngine,
843 return std::make_unique<PMTsimulationAlg>(makeParams(
846 mainRandomEngine, darkNoiseRandomEngine, elecNoiseRandomEngine,
858 CLHEP::HepRandomEngine& mainRandomEngine,
864 using namespace util::quantities::electronics_literals;
869 auto params = fBaseConfig;
877 params.pulseFunction = &SPRfunction;
879 params.randomEngine = &mainRandomEngine;
880 params.gainRandomEngine = params.randomEngine;
889 bool const expectedNegativePolarity
890 = (SPRfunction.peakAmplitude() < 0.0_ADCf);
892 if (std::signbit(params.pulsePolarity) != expectedNegativePolarity) {
893 throw cet::exception(
"PMTsimulationAlg")
894 <<
"Inconsistent settings: pulse polarity declared "
895 << params.pulsePolarity <<
", but photoelectron waveform amplitude is "
896 << SPRfunction.peakAmplitude()
PMTspecs_t PMTspecs
PMT specifications.
double std(const std::vector< short > &wf, const double ped_mean, size_t start, size_t nsample)
unsigned int pulseSubsamples
Number of tick subsamples.
size_t beamGateTriggerNReps
Number of beamgate trigger reps to produce.
fhicl::Atom< bool > CreateBeamGateTriggers
fhicl::Atom< float > ThresholdADC
unsigned int nDynodes() const
Number of dynodes in the PMTs.
double QEbase
Uncorrected PMT quantum efficiency.
PMTsimulationAlg::ConfigurationParameters_t makeParams(detinfo::LArProperties const &larProp, detinfo::DetectorClocksData const &clockData, SinglePhotonResponseFunc_t const &SPRfunction, CLHEP::HepRandomEngine &mainRandomEngine, CLHEP::HepRandomEngine &darkNoiseRandomEngine, CLHEP::HepRandomEngine &elecNoiseRandomEngine, bool trackSelectedPhotons=false) const
Returns a data structure to construct the algorithm.
int OpChannel() const
Returns the optical channel number this object is associated to.
double dynodeK
Gain from stage with voltage dV is proportional to dV^K.
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.
microsecond_as<> microsecond
Type of time stored in microseconds, in double precision.
fhicl::Atom< bool > FastElectronicsNoise
Functor to convert tick point into a tick number and a subsample index.
fhicl::Atom< microseconds > TriggerOffsetPMT
ADCcount peakAmplitude() const
Returns the peak amplitude in ADC counts.
DiscretePhotoelectronPulse::Subsample_t PulseSampling_t
Type of sampled pulse shape: sequence of samples, one per tick.
DiscretePhotoelectronPulse::ADCcount ADCcount
bool useFastElectronicsNoise
Whether to use fast generator for electronics noise.
std::size_t pretrigSize() const
fhicl::Atom< hertz > DarkNoiseRate
static constexpr quantity_t castFrom(U value)
Returns a new quantity initialized with the specified value.
bool createBeamGateTriggers
Option to create unbiased readout around beam spill.
fhicl::Atom< bool > FluctuateGain
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.
std::vector< raw::OpDetWaveform > CreateFixedSizeOpDetWaveforms(raw::Channel_t opChannel, Waveform_t const &waveform) const
Creates raw::OpDetWaveform objects from a waveform data.
constexpr value_t value() const
Returns the value of the quantity.
picocoulomb_as<> picocoulomb
Type of charge stored in picocoulomb, in double precision.
DiscretePhotoelectronPulse wsp
CLHEP::HepRandomEngine * gainRandomEngine
Random stream engine for gain fluctuations.
Substitute for C++20 std::identity.
ADCcount thresholdADC
ADC Threshold for self-triggered readout.
util::quantities::tick tick
void setVoltageDistribution(std::vector< double > &&Rs)
Sets voltageDistribution by stealing and normalizing Rs.
std::unique_ptr< PMTsimulationAlg > operator()(detinfo::LArProperties const &larProp, detinfo::DetectorClocksData const &detClocks, SinglePhotonResponseFunc_t const &SPRfunction, CLHEP::HepRandomEngine &mainRandomEngine, CLHEP::HepRandomEngine &darkNoiseRandomEngine, CLHEP::HepRandomEngine &elecNoiseRandomEngine, bool trackSelectedPhotons=false) const
Creates and returns a new algorithm instance.
auto enumerate(Iterables &&...iterables)
Range-for loop helper tracking the number of iteration.
std::vector< optical_tick > FindTriggers(Waveform_t const &wvfm) const
Ticks in the specified waveform where some signal activity starts.
std::pair< ADCcount, ADCcount > saturationRange() const
Returns the ADC range allowed for photoelectron saturation.
std::map< int, int > DetectedPhotons
Number of photons detected at each given time: time tick -> photons.
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.
std::size_t fNsamples
Samples per waveform.
Interface to detinfo::DetectorClocks.
OpDetWaveformMaker_t::WaveformData_t Waveform_t
Type internally used for storing waveforms.
tick_as< double > tick_d
Tick number, represented by double.
fhicl::Atom< double > ReadoutWindowSize
std::size_t posttrigSize() const
SinglePhotonResponseFunc_t const * pulseFunction
Single photon response function.
auto vector(Vector const &v)
Returns a manipulator which will print the specified array.
timescale_traits< TriggerTimeCategory >::time_point_t trigger_time
A point in time on the trigger time scale.
ADCcount::value_t WaveformValue_t
Numeric type in waveforms.
PMTsimulationAlg(ConfigurationParameters_t const &config)
Constructor.
void ApplySaturation(Waveform_t &waveform, std::pair< ADCcount, ADCcount > const &range) const
std::vector< optical_tick > CreateBeamGateTriggers() const
Generate periodic interest points regardless the actual activity.
double multiplicationStageGain(unsigned int i=1) const
Returns the gain of the specified multiplication stage.
double distance(geo::Point_t const &point, CathodeDesc_t const &cathode)
Returns the distance of a point from the cathode.
fhicl::Atom< microseconds > ReadoutEnablePeriod
double gain
Total typical gain of a PMT.
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.
bool checkRange(ADCcount limit, std::string const &outputCat) const
Checks that the waveform tails not sampled are negligible.
CLHEP::HepRandomEngine * darkNoiseRandomEngine
Dark noise random stream engine.
A value measured in the specified unit.
detinfo::DetectorTimings makeDetectorTimings(detinfo::DetectorClocksData const &detClocks)
microseconds readoutEnablePeriod
Time (us) for which pmt readout is enabled.
util::quantities::hertz hertz
megahertz_as<> megahertz
Type of frequency stored in megahertz, in double precision.
Interface for a function describing a pulse from a photoelectron.
fhicl::Atom< float > Baseline
Test of util::counter and support utilities.
PMTsimulationAlgMaker(Config const &config)
Constructor.
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.
fhicl::Atom< unsigned int > ADCBits
fhicl::Atom< microseconds > BeamGateTriggerRepPeriod
detinfo::timescales::optical_tick optical_tick
fhicl::Atom< int > PulsePolarity
ElecClock const & OpticalClock() const noexcept
Borrow a const Optical clock with time set to Trigger time [us].
ConfigurationParameters_t fParams
Complete algorithm configuration.
auto makeGainFluctuator() const
Returns a configured gain fluctuator object.
Pulse from one photoelectron as two half Gaussian functions.
std::vector< double > voltageDistribution
virtual double ScintPreScale(bool prescale=true) const =0
SubsampleIndex_t nSubsamples() const
CLHEP::HepRandomEngine * randomEngine
Main random stream engine.
Collection of photons which recorded on one channel.
Compact representation of photons on a channel.
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.
std::pair< ADCcount, ADCcount > ADCrange() const
Contains all timing reference information for the detector.
static void ClipWaveform(Waveform_t &waveform, ADCcount min, ADCcount max)
Forces waveform ADC within the min to max range (max included).
fhicl::Atom< double > PreTrigFraction
int pulsePolarity
Pulse polarity (=1 for positive, =-1 for negative)
megahertz fSampling
Wave sampling frequency [MHz].
double fQE
PMT quantum efficiency.
then echo File list $list not found else cat $list while read file do echo $file sed s
NoiseAdderFunc_t const fNoiseAdder
Single photon pulse (sampled).
A class exposing an upgraded interface of detinfo::DetectorClocksData.
process_name largeant stream1 can override from command line with o or output physics producers generator N
constexpr T && operator()(T &&v) const noexcept
Functions pulling in STL customization if available.
nanosecond_as<> nanosecond
Type of time stored in nanoseconds, in double precision.
Type holding all configuration parameters for this algorithm.
Algorithms for the simulation of ICARUS PMT channels.
constexpr double Frequency() const
Frequency in MHz.
detinfo::LArProperties const * larProp
LarProperties service provider.
fhicl::Atom< std::size_t > BeamGateTriggerNReps
bool trackSelectedPhotons
Whether to track the scintillation photons used.
void AddNoise(Waveform_t &wave) const
fhicl::Atom< unsigned int > PulseSubsamples
DiscretePhotoelectronPulse::SubsampleIndex_t SubsampleIndex_t
decltype(auto) subsample(SubsampleIndex_t i) const
Returns the subsample specified by index i, undefined if invalid.
microseconds beamGateTriggerRepPeriod
Repetition Period (us) for BeamGateTriggers TODO make this a time_interval.
CLHEP::HepRandomEngine * elecNoiseRandomEngine
Electronics noise random stream engine.
double firstStageGain() const
Returns the gain from the first stage of PMT multiplication.
void AddNoise_faster(Waveform_t &wave) const
Same as AddNoise() but using an alternative generator.
hertz_as<> hertz
Type of frequency stored in hertz, in double precision.
fhicl::Atom< double > AmpNoise
fhicl::Table< PMTspecConfig > PMTspecs
static util::FastAndPoorGauss< 32768U, float > const fFastGauss
fhicl::OptionalAtom< float > Saturation
Main algorithm FHiCL configuration.
detinfo::DetectorClocksData const * clockData