WaveformDigitizerSim_module.cc
Go to the documentation of this file.
1 //=========================================================
2 // WaveformDigitizerSim_module.cc
3 // This module produces digitized waveforms (creating OpDetWaveform)
4 // from photon detectors taking OpDetDivRec as input.
5 // The only random numbers thrown are for line noise.
6 //
7 // Gleb Sinev, Duke, 2015
8 // Anne Christensen, CSU, 2019
9 // Alex Himmel, FNAL, 2021
10 //=========================================================
11 
12 #ifndef WaveformDigitizerSim_h
13 #define WaveformDigitizerSim_h
14 
15 // Framework includes
16 
25 #include "cetlib_except/exception.h"
27 
28 #include "fhiclcpp/ParameterSet.h"
30 #include "fhiclcpp/types/Atom.h"
33 
34 
35 // ART extensions
36 #include "nurandom/RandomUtils/NuRandomService.h"
37 
38 // LArSoft includes
39 
52 
53 // CLHEP includes
54 
55 #include "CLHEP/Random/RandGauss.h"
56 
57 // C++ includes
58 
59 #include <vector>
60 #include <map>
61 #include <cmath>
62 #include <memory>
63 #include <limits>
64 #include <iomanip>
65 
66 // ROOT includes
67 
68 #include "TTree.h"
69 
70 
71 namespace opdet {
72 
73  using std::vector;
74  using std::pair;
75  typedef std::vector< std::pair< size_t, size_t > > Ranges_t;
76 
77  class FocusList
78  {
79  public:
80  FocusList(size_t nSamples, size_t padding)
81  : fNSamples(nSamples), fPadding(padding)
82  {}
83 
84  void AddRange(size_t from, size_t to)
85  {
86  from -= std::min(from, fPadding);
87  to = std::min(to+fPadding, fNSamples-1);
88 
89  for(size_t i = 0; i < ranges.size(); ++i){
90  pair<size_t, size_t>& r = ranges[i];
91  // Completely nested, discard
92  if(from >= r.first && to <= r.second) return;
93  // Extend end
94  if(from >= r.first && from <= r.second){
95  r.second = to;
96  return;
97  }
98  // Extend front
99  if(to >= r.first && to <= r.second){
100  r.first = from;
101  return;
102  }
103  }
104  // Discontiguous, add
105  ranges.emplace_back(from, to);
106  }
107 
108  void everything()
109  {
110  ranges.clear();
111  ranges.emplace_back(0, fNSamples-1);
112  }
113 
114 
115  Ranges_t ranges;
116 
117  protected:
118  size_t fNSamples;
119  size_t fPadding;
120  };
121 
123 
124  public:
125 
126  struct Config {
127  using Name = fhicl::Name;
129 
130  // Inputs
131  fhicl::Sequence<art::InputTag> InputTags { Name("InputTags"), Comment("Input tags for OpDetDivRecs") };
132 
133  // Single photoelectron pulse parameters
134  fhicl::Atom<double> VoltageToADC { Name("VoltageToADC"),
135  Comment("mV per ADC count") };
136  fhicl::Atom<double> PulseLength { Name("PulseLength"),
137  Comment("1PE pulse length in us") };
138  fhicl::Atom<double> PeakTime { Name("PeakTime"),
139  Comment("Time when the pulse reaches its maximum in us") };
140  fhicl::Atom<double> MaxAmplitude { Name("MaxAmplitude"),
141  Comment("Maximum amplitude of the pulse in mV") };
142  fhicl::Atom<double> FrontTime { Name("FrontTime"),
143  Comment("Constant in the exponential function of the leading edge in us") };
144  fhicl::Atom<double> BackTime { Name("BackTime"),
145  Comment("Constant in the exponential function of the tail in us") };
146 
147  // Waveform output Settings
148  fhicl::Atom<size_t> Padding { Name("Padding"),
149  Comment("Minimum ticks around pulses to simulate")};
150  fhicl::Atom<size_t> ReadoutWindow { Name("ReadoutWindow"),
151  Comment("Total size of the triggered readout window in ticks")};
152  fhicl::Atom<size_t> PreTrigger { Name("PreTrigger"),
153  Comment("Length of the ReadoutWindow which comes before the trigger in ticks")};
154  fhicl::Atom<double> Threshold { Name("Threshold"),
155  Comment("Minimum threshold to trigger readout in the CFD (PE)")};
156  fhicl::Atom<size_t> Dwindow { Name("Dwindow"),
157  Comment("Sample time difference in the CFD trigger in ticks")};
158 
159  // Digitizer properties
160  fhicl::Atom<short> Pedestal { Name("Pedestal"),
161  Comment("Baseline pedestal in ADC counts")};
162  fhicl::Atom<double> LineNoiseRMS { Name("LineNoiseRMS"),
163  Comment("Pedestal RMS in ADC counts")};
164  fhicl::OptionalAtom<short> DynamicBitRange { Name("DynamicBitRange"),
165  Comment("Maximum number of bits in readout if specified.") };
166 
167  // Optional debugging settings
168  fhicl::OptionalAtom<double> TimeBegin { Name("TimeBegin"),
169  Comment("Override earliest allowed waveform time, default -1 drift window") };
170  fhicl::OptionalAtom<double> TimeEnd { Name("TimeEnd"),
171  Comment("Override latest allowed waveform time, default end of TPC readout") };
172  fhicl::Atom<bool> FullWaveformOutput { Name("FullWaveformOutput"),
173  Comment("Write out the whole waveform, slow with *large* output sizes. Default false."),
174  false };
175  };
177 
178  explicit WaveformDigitizerSim(Parameters const & config);
179  void produce(art::Event&);
180 
181  private:
182 
183  //////////////////////
184  // FHICL Parameters //
185  //////////////////////
186 
187  vector<art::InputTag> fInputTags;
188 
189  // Single photoelectron pulse parameters
191  double fPulseLength;
192  double fPeakTime;
194  double fFrontTime;
195  double fBackTime;
196 
197  // Waveform output settings
198  size_t fPadding;
200  size_t fPreTrigger;
201  double fThresholdPE;
202  size_t fDwindow;
203 
204  // Derived from the above
205 
206  // Digitizer properties
207  short fPedestal;
209  int fMaxSaturationCutOff; // derived from above
210 
211  // Optional debugging settings
212  double fTimeBegin;
213  double fTimeEnd;
215 
216 
217  ////////////////////////////////////
218  // Other Members set during setup //
219  ////////////////////////////////////
220 
221  // Random number engines
222  CLHEP::HepRandomEngine& fOpDigiEngine;
223  CLHEP::RandGauss fRandGauss;
224 
225  double fSampleFreqMHz;
228 
229  // Template for a single PE
231 
232 
233  /////////////////////
234  // Setup functions //
235  /////////////////////
236 
237  void CreateSinglePEWaveform();
238  double Pulse1PE(double time_in_us) const;
239  void SetDefaultBeginEndTimes();
240  void CheckFHiCLParameters() const;
241 
242 
243  ///////////////////////////////////
244  // Support functions for produce //
245  ///////////////////////////////////
246 
247  // Add photons to an existing waveform
248 
249  void AddPEsToWaveform(const sim::OpDetDivRec* dr_p,
250  vector<double> & pdWaveform,
251  FocusList & fls) const;
252 
253  // Vary the pedestal
254  void AddLineNoise(vector< double > & waveform,
255  const FocusList & fls);
256 
257  // Apply saturation and cast into shorts
258  // OpDetWaveform constructor requires this specific type
259  // (probably not for good reasons)
260  vector< uint16_t > Digitize(vector<double>::iterator itBegin,
261  vector<double>::iterator itEnd) const;
262 
263  // Constant fraction discriminator single channel trigger
264  template<typename T> Ranges_t CFDTrigger(vector<T> const& wf, const FocusList& fls) const;
265 
266 
267  // Functions to convert between time and ticks, being explicit about units
268  // Times within the event count from fBeginTime, Ticks count from 0
269  double Tick2us(size_t tick) const { return fTimeBegin + static_cast<double>(tick)/fSampleFreqMHz; };
270  double Tick2ns(size_t tick) const { return 1000.*Tick2us(tick); };
271  size_t us2Tick(double time_in_us) const { return static_cast<size_t>(std::round( (time_in_us-fTimeBegin)*fSampleFreqMHz) ); }
272  size_t ns2Tick(double time_in_ns) const { return us2Tick(time_in_ns/1000.); };
273 
274  };
275 
276 }
277 
278 #endif
279 
280 namespace opdet {
281 
283 
284 }
285 
286 namespace opdet {
287 
288  /////////////////////////////////////
289  // Constructor and setup functions //
290  /////////////////////////////////////
291 
292  //---------------------------------------------------------------------------
294  : EDProducer{config}
295  , fInputTags{ config().InputTags() }
296 
299  , fPeakTime{ config().PeakTime() }
301  , fFrontTime{ config().FrontTime() }
302  , fBackTime{ config().BackTime() }
303 
304  , fPadding{ config().Padding() }
306  , fPreTrigger{ config().PreTrigger() }
307  , fThresholdPE{ config().Threshold() }
308  , fDwindow{ config().Dwindow() }
309 
310  , fPedestal{ config().Pedestal() }
313 
315 
317  "HepJamesRandom",
318  "waveformdigi",
319  config.get_PSet(),
320  "SeedWaveformDigi") )
322  {
323 
324  // This module produces (infrastructure piece)
325  produces< vector< raw::OpDetWaveform > >();
326 
327  for (auto tag: fInputTags) {
328  consumes< vector< sim::OpDetDivRec > >(tag);
329  }
330 
331  mf::LogInfo logger("WaveformDigitizerSim");
332 
333  // Get the optical clock frequency
334  auto const clockData = art::ServiceHandle<detinfo::DetectorClocksService const>()->DataForJob();
335  fSampleFreqMHz = clockData.OpticalClock().Frequency();
336  logger << "Using a sampling frequency of " << fSampleFreqMHz << " MHz" << "\n";
337 
338 
339  // Creating a single photoelectron waveform template
340  // based on fhicl configuration
342  logger << "Requested pulse length of, " << fPulseLength << " us "
343  << "which is " << fPulseLengthTicks << " ticks" << "\n";
344  logger << "Requested PE threshold, " << std::fixed
346  << ", converted to ADC threshold "
347  << std::setprecision(0) << fThresholdADC << "\n";
348 
349 
350  // Set a dynamic range if given
351  short br;
352  if ( config().DynamicBitRange(br) ) {
353  fMaxSaturationCutOff = pow(2, br) - 1;
354  logger << "Limiting output to " << br << " bits" << "\n";
355  }
356 
357 
358  // Set earliest and latest allowed times
359  std::string tsource;
360  if (config().TimeBegin(fTimeBegin) && config().TimeEnd(fTimeEnd))
361  tsource = "override";
362  else {
363  tsource = "default";
365  }
366  logger << "Using " << tsource << " time limits on PD digitizer: " << fTimeBegin << " us to " << fTimeEnd << " us";
367 
368  // Check for valid configuration
370  }
371 
372  //---------------------------------------------------------------------------
374  {
375  double maxADC = 0.;
378 
379  for (size_t tick = 0; tick < fPulseLengthTicks; ++tick) {
380  double val = Pulse1PE(static_cast< double >(tick)/fSampleFreqMHz);
382  if (val > maxADC) maxADC = val;
383  }
384 
385  // Set the ADC threshold based on the PE threshold
386  fThresholdADC = fThresholdPE * maxADC;
387  }
388 
389  //---------------------------------------------------------------------------
390  double WaveformDigitizerSim::Pulse1PE(double time_in_us) const
391  {
392 
393  if (time_in_us < fPeakTime)
394  return (fVoltageToADC*fMaxAmplitude*std::exp((time_in_us - fPeakTime)/fFrontTime));
395  else
396  return (fVoltageToADC*fMaxAmplitude*std::exp(-(time_in_us - fPeakTime)/fBackTime));
397 
398  }
399 
400  //---------------------------------------------------------------------------
402  {
403  // A little wasteful to get clockData again, but only happens once per job
404  auto const clockData = art::ServiceHandle<detinfo::DetectorClocksService const>()->DataForJob();
405  auto const detProp = art::ServiceHandle<detinfo::DetectorPropertiesService const>()->DataForJob(clockData);
406 
407  // Take the TPC readout window size and convert to us with the electronics
408  // clock frequency, adding 5 us of padding
409  fTimeEnd = detProp.ReadOutWindowSize() / clockData.TPCClock().Frequency() + 5;
410 
411  // Assume the readout is symmetrical around 0
412  fTimeBegin = -1.*fTimeEnd;
413 
414  }
415 
416 
417  //---------------------------------------------------------------------------
419  {
420  // Check that we have input tags
421  if (fInputTags.size() == 0)
423  << "No input tags were given to WaveformDigitizerSim.\n";
424 
425  // Sanity check the line noise
426  if (fLineNoiseRMS < 0.0)
428  << "fLineNoiseRMS: " << fLineNoiseRMS << '\n'
429  << "Line noise RMS should be non-negative!\n";
430 
431  // Sanity check beginning and end times
432  if (fTimeBegin >= fTimeEnd) {
434  << "TimeBegin: " << fTimeBegin << " and " << "TimeEnd: " << fTimeEnd << '\n'
435  << "TimeBegin should be less than TimeEnd!\n";
436  }
437  }
438 
439 
440 
441 
442  /////////////////////////////////////////////////
443  // produce data products and support functions //
444  /////////////////////////////////////////////////
445 
446 
447  //---------------------------------------------------------------------------
449  {
450  // A pointer that will store produced OpDetWaveforms
451  auto wave_forms_p = std::make_unique< vector< raw::OpDetWaveform > >();
452 
453  // Total number of ticks in the whole event
454  unsigned int nSamples = (fTimeEnd - fTimeBegin)*fSampleFreqMHz;
455 
456  // First, pull all of the DivRec handles from the event, and collect up
457  // all DivRecs on the same channel into a single vector
458  std::map< int, vector<const sim::OpDetDivRec *> > DivRecsByChannel;
459  for (auto tag: fInputTags) {
460  auto dr_handle = event.getHandle< vector< sim::OpDetDivRec > >(tag);
461  if (!dr_handle) {
462  mf::LogWarning("WaveformDigitizerSim") << "Could not load OpDetDivRecs " << tag << ". Skipping.";
463  continue;
464  }
465  for (auto const& dr : *dr_handle) {
466  DivRecsByChannel[dr.OpDetNum()].push_back( &dr );
467  }
468  }
469 
470  // Now, loop through channels, treating all photons on a cha
471  for (auto const& [opDet, vDivRecs]: DivRecsByChannel)
472  {
473  // Create the empty waveform vector and focus list
474  vector< double > pdWaveform(nSamples, fPedestal);
475  FocusList fls(nSamples, fPadding);
476 
477  // Add a PE template to the waveform for each true photon
478  for (auto dr_p: vDivRecs) AddPEsToWaveform(dr_p, pdWaveform, fls);
479 
480  // So that line noise is added to all ticks in full output mode
481  if (fFullWaveformOutput) fls.everything();
482 
483  // Add line noise
484  AddLineNoise(pdWaveform, fls);
485 
486  if (fFullWaveformOutput) {
487  wave_forms_p->emplace_back(Tick2us(0), opDet, Digitize(pdWaveform.begin(), pdWaveform.end()));
488  }
489  else {
490  // Checking for tiggers on floats, rather than shorts.
491  // This is an approximation, but it saves making an extra copy
492  // of the waveform and makes the code easier to follow.
493  for ( auto t: CFDTrigger(pdWaveform, fls) ) {
494 
495  // Digitize and store
496  auto shortWF = Digitize(pdWaveform.begin()+t.first, pdWaveform.begin()+t.second+1);
497  wave_forms_p->emplace_back(Tick2us(t.first), opDet, shortWF);
498  }
499  }
500  }
501 
502  // Push the OpDetWaveforms into the event
503  event.put(std::move(wave_forms_p));
504 
505  }
506 
507  //---------------------------------------------------------------------------
509  vector<double>& pdWaveform,
510  FocusList& fls) const
511  {
512  // Vector of DivRec time bins (struct OpDet_Time_Chans)
513  for (auto odtc: dr_p->GetTimeChans()) {
514 
515  // Extract time for this odtc within the event
516  double photonTime_ns = odtc.time;
517  size_t timeBin = ns2Tick(photonTime_ns);
518 
519  // Check if the photon is inside the digitization range. If not, skip it.
520  if ( timeBin < 0 || timeBin >= pdWaveform.size() ) {
521  mf::LogWarning("WaveformDigitizerSim") << "Skipping a photon at " << photonTime_ns/1000. << " us, outside digitization window of " << fTimeBegin << " to " << fTimeEnd;
522  continue;
523  }
524 
525  // Loop through records at this time and count photons
526  int nPE = 0;
527  for (auto const& sdp : odtc.phots)
528  nPE += sdp.phot;
529 
530  // Add ticks until the end of the single PE waveform or end of the whole pdWaveform
531  size_t stop = std::min(fPulseLengthTicks, pdWaveform.size()-timeBin);
532 
533  // Add this range to the focus list
534  fls.AddRange(timeBin, timeBin+stop-1);
535 
536  // Add the nPE pulse to the waveform
537  for (size_t tick = 0; tick < stop; ++tick)
538  pdWaveform[timeBin+tick] += fSinglePEWaveform[tick]*nPE;
539  }
540  }
541 
542  //---------------------------------------------------------------------------
544  {
545  if (fLineNoiseRMS <= 0.0) return;
546 
547  for (auto p: fls.ranges) {
548  for(auto k = p.first; k <= p.second; ++k){
549  waveform[k] += fRandGauss.fire(0, fLineNoiseRMS);
550  }
551  }
552  }
553 
554  //---------------------------------------------------------------------------
556  {
557  for(auto it = itBegin; it != itEnd; ++it) {
558  if(*it > fMaxSaturationCutOff)
559  *it = fMaxSaturationCutOff;
560  }
561 
562  // Don't bother to round properly, it's faster this way
563  return vector< uint16_t >(itBegin, itEnd);
564  }
565 
566  //---------------------------------------------------------------------------
567  template<typename T>
568  Ranges_t WaveformDigitizerSim::CFDTrigger(vector<T> const& wf, const FocusList& fls) const
569  {
570 
571  Ranges_t readouts;
572 
573  for (auto range: fls.ranges) {
574  size_t wstart = -1;
575  size_t wend = -1;
576  bool fire = false;
577 
578  for (size_t tick = range.first; tick < range.second - fDwindow; ++tick) {
579 
580  // Fire CFD
581  if (wf[tick+fDwindow] - wf[tick] > fThresholdADC) {
582 
583  if (!fire) {
584  // Start a new readout window
585  fire = true;
586  wstart = tick-fPreTrigger;
587  wend = tick-fPreTrigger+fReadoutWindow;
588  }
589  else {
590  // Extend the current readout window
591  // Simplest to implement here, update to the
592  // actual DAPHNE algorithm once known
594  }
595 
596  }
597  else if (fire && tick >= wend) {
598  // We've reached the end of a window, save it
599  fire = false;
600  readouts.emplace_back(wstart, wend);
601  }
602 
603  }
604 
605  // Check for lingering window, add a final window
606  // up to the end of the waveform if so.
607  if (fire == true) {
608  readouts.emplace_back(wstart, range.second-1);
609  }
610  }
611 
612  return readouts;
613  }
614 
615 
616 
617 } // end namespace
618 
intermediate_table::iterator iterator
base_engine_t & createEngine(seed_t seed)
Store parameters for running LArG4.
size_t us2Tick(double time_in_us) const
std::string string
Definition: nybbler.cc:12
void AddRange(int from, int to)
constexpr T pow(T x)
Definition: pow.h:72
FocusList(size_t nSamples, size_t padding)
struct vector vector
ChannelGroupService::Name Name
std::vector< std::pair< size_t, size_t > > Ranges_t
Q_EXPORT QTSManip setprecision(int p)
Definition: qtextstream.h:343
std::vector< std::pair< int, int > > ranges
void AddLineNoise(vector< double > &waveform, const FocusList &fls)
Simulation objects for optical detectors.
#define DEFINE_ART_MODULE(klass)
Definition: ModuleMacros.h:67
Time_Chans_t const & GetTimeChans() const
Definition: OpDetDivRec.h:101
static Config * config
Definition: config.cpp:1054
def move(depos, offset)
Definition: depos.py:107
Ranges_t CFDTrigger(vector< T > const &wf, const FocusList &fls) const
size_t size
Definition: lodepng.cpp:55
size_t ns2Tick(double time_in_ns) const
tick_as<> tick
Tick number, represented by std::ptrdiff_t.
Definition: electronics.h:75
p
Definition: test.py:223
static int max(int a, int b)
void AddPEsToWaveform(const sim::OpDetDivRec *dr_p, vector< double > &pdWaveform, FocusList &fls) const
cet::coded_exception< errors::ErrorCodes, ExceptionDetail::translate > Exception
Definition: Exception.h:66
#define Comment
T min(sqlite3 *const db, std::string const &table_name, std::string const &column_name)
Definition: statistics.h:55
MaybeLogger_< ELseverityLevel::ELsev_warning, false > LogWarning
double Pulse1PE(double time_in_us) const
vector< uint16_t > Digitize(vector< double >::iterator itBegin, vector< double >::iterator itEnd) const
Tools and modules for checking out the basics of the Monte Carlo.
WaveformDigitizerSim(Parameters const &config)
fhicl::Sequence< art::InputTag > InputTags
Event finding and building.
void AddRange(size_t from, size_t to)