SIPMOpSensorSim_module.cc
Go to the documentation of this file.
1 //=========================================================
2 // SIPMOpSensorSim_module.cc
3 // This module produces detected photons (creating OpDetDivRec)
4 // from photon detectors taking OpDetBacktrackerRecords as input.
5 // Applies quantum efficiency and cross-talk
6 //
7 // Gleb Sinev, Duke, 2015
8 // Anne Christensen, CSU, 2019
9 // Alex Himmel, FNAL, 2021
10 //=========================================================
11 
12 #ifndef SIPMOpSensorSim_h
13 #define SIPMOpSensorSim_h
14 
15 // Framework includes
16 
25 #include "fhiclcpp/ParameterSet.h"
26 #include "art_root_io/TFileService.h" //vitor
27 #include "art_root_io/TFileDirectory.h"
28 
29 
30 
31 // ART extensions
32 #include "nurandom/RandomUtils/NuRandomService.h"
33 
34 // LArSoft includes
35 
48 
49 
50 // CLHEP includes
51 
52 #include "CLHEP/Random/RandExponential.h"
53 #include "CLHEP/Random/RandFlat.h"
54 #include "CLHEP/Random/RandPoissonQ.h"
55 
56 // C++ includes
57 
58 #include <vector>
59 #include <map>
60 #include <cmath>
61 #include <memory>
62 
63 // ROOT includes
64 
65 #include "TTree.h"
66 
67 namespace opdet {
68 
70 
71  public:
72 
73  struct Config {
74  using Name = fhicl::Name;
76  fhicl::Atom<art::InputTag> InputTag { Name("InputTag"), Comment("Input tag for OpDetBacktrackerRecords") };
77  fhicl::Atom<double> QuantumEfficiency { Name("QuantumEfficiency"), Comment("Probabilityof recording a photon") };
78  fhicl::Atom<double> DarkNoiseRate { Name("DarkNoiseRate"), Comment("Rate in Hz") };
79  fhicl::Atom<double> CrossTalk { Name("CrossTalk"), Comment("Cross talk (1->2 PE) probability") };
80  fhicl::Atom<double> Correction { Name("Correction"), Comment("Adjust the amount of total light. Kept seprate from QE for clarity."), 1};
81  fhicl::OptionalAtom<double> LateLightCorrection { Name("LateLightCorrection"), Comment("Adjust the amount of late light")};
82  fhicl::OptionalAtom<double> LateLightBoundary { Name("LateLightBoundary"), Comment("Boundary that defines late light (ns)") };
83 
84  };
86 
87  explicit SIPMOpSensorSim(Parameters const & config);
88  void produce(art::Event&) override;
89 
90  private:
91 
92  // The parameters read from the FHiCL file
93  art::InputTag fInputTag; // Input tag for OpDet collection
95  double fQE;
96  double fDarkNoiseRate; // In Hz
97  double fCrossTalk; // Probability of SiPM producing 2 PE signal
98 
99  double fTimeBegin; // Earliest and latest possible times for reading out data
100  double fTimeEnd; // Used for defining the dark noise range
101 
102  // unused double fCorrection; // Correct light yield. Kept separate for clarity
103  bool fCorrectLateLight; // Do we apply a late light correction?
104  double fLateLightCorrection; // How much to correct the late light
105  double fLateLightBoundary; // What is the boundary which defines late light?
106 
107  // Random number engines
108  CLHEP::HepRandomEngine& fSIPMEngine;
109  CLHEP::RandExponential fRandExponential;
110  CLHEP::RandFlat fRandFlat;
111  CLHEP::RandPoissonQ fRandPoissPhot;
112 
113  // Produce waveform on one of the optical detectors
114  // These cannot be const due to the random number generators
115  void PhotonsToPE(sim::OpDetBacktrackerRecord const& btr, sim::OpDetDivRec& dr_plusnoise);
117  unsigned short CrossTalk();
118 
119  void SetBeginEndTimes();
120 
121  };
122 }
123 
124 #endif
125 
126 namespace opdet {
127 
129 
130 }
131 
132 namespace opdet {
133 
134  //---------------------------------------------------------------------------
135  // Constructor
137  : art::EDProducer{config}
138  , fInputTag{ config().InputTag()}
139  , fInputToken{ consumes< std::vector<sim::OpDetBacktrackerRecord> >(fInputTag) }
141  , fCrossTalk{ config().CrossTalk()}
143  "HepJamesRandom",
144  "sipm",
145  config.get_PSet(),
146  "SeedSiPM"))
150  {
151 
152  // This module produces (infrastructure piece)
153  produces< std::vector< sim::OpDetDivRec > >();
154 
155 
156  if (fDarkNoiseRate < 0.0) {
158  << "fDarkNoiseRate: " << fDarkNoiseRate << '\n'
159  << "Dark noise rate should be non-negative!\n";
160  }
161 
162 
163  double tempQE = config().QuantumEfficiency() * config().Correction();
164  if (tempQE < 0 || tempQE > 1) {
166  << "QuantumEfficiency in SIPMSensorSim: " << config().QuantumEfficiency() << "\n"
167  << " with correction: " << config().Correction() << "\n"
168  << " leading to total efficiency: " << tempQE << "\n"
169  << "It should be between 0 and 1!\n";
170  }
171 
172  // Correct out the prescaling applied during simulation
173  auto const *LarProp = lar::providerFrom<detinfo::LArPropertiesService>();
174  fQE = tempQE / LarProp->ScintPreScale();
175 
176  if (fQE > 1.0001 ) {
178  << "QuantumEfficiency in SIPMSensorSim: " << config().QuantumEfficiency() << "\n"
179  << " with correction: " << config().Correction() << "\n"
180  << " leading to total efficiency: " << tempQE << "\n"
181  << "is too large.\n"
182  << "It is larger than the prescaling applied during simulation, " << LarProp->ScintPreScale() << ".\n"
183  << "Final QE must be equal to or smaller than the QE applied at simulation time.\n";
184  }
185 
186 
187  // Check for non-trivial channel mapping which is not supported
189  for (unsigned int opDet = 0; opDet < geometry->NOpDets() ; ++opDet) {
190  if (geometry->NOpHardwareChannels(opDet) > 1)
192  << "OpDet #" << opDet << " has " << geometry->NOpHardwareChannels(opDet)
193  << " channels associated with it. \n"
194  << "This kind of channel mapping is not supported by SIPMOpSensorSim.\n"
195  << "You need to use the legacy OpDetDigitizerDUNE instead.\n";
196  }
197 
198  // Set time ranges if needed for dark noise
199  if (fDarkNoiseRate > 0) SetBeginEndTimes();
200 
201  // Check for optional parameters for adjusting late light.
202  // Apply adjustments if both are given.
203  // Throw an error if only one is specified.
204  bool haveCorrection = config().LateLightCorrection(fLateLightCorrection);
205  bool haveBoundary = config().LateLightBoundary(fLateLightBoundary);
206  fCorrectLateLight = haveBoundary && haveCorrection;
207 
208  if (haveBoundary != haveCorrection) {
210  << "Must specify both LateLightCorrection and LateLightBoundary in order to apply correction.\n"
211  << "Leave both out to not adjust late light.\n";
212  }
213 
214  if (fCorrectLateLight && fLateLightCorrection*tempQE > LarProp->ScintPreScale())
215  {
217  << "QuantumEfficiency in SIPMSensorSim: " << config().QuantumEfficiency() << "\n"
218  << " with correction: " << config().Correction() << "\n"
219  << " and late light correction: " << fLateLightCorrection << "\n"
220  << " leading to total efficiency: " << tempQE * fLateLightCorrection << "\n"
221  << "is too large.\n"
222  << "It is larger than the prescaling applied during simulation, " << LarProp->ScintPreScale() << ".\n"
223  << "Final QE must be equal to or smaller than the QE applied at simulation time.\n";
224  }
225  }
226 
227 
228  //---------------------------------------------------------------------------
230  {
231  // A pointer that will store produced OpDetDivRec
232  auto OpDetDivRecPtr = std::make_unique< std::vector< sim::OpDetDivRec > >();
233 
234  // Get OpDetBacktrackerRecord from the event
235  auto const & btr_handle = event.getValidHandle(fInputToken);
236 
237  // For every optical detector:
238  for (auto const& btr : *btr_handle) {
239  int opDet = btr.OpDetNum();
240 
241  auto DivRecPlusNoise = sim::OpDetDivRec(opDet);
242 
243  PhotonsToPE(btr, DivRecPlusNoise);
244 
245  // Generate dark noise
246  if (fDarkNoiseRate > 0.0) AddDarkNoise(DivRecPlusNoise);
247 
248  // AddAfterPulsing();
249 
250  OpDetDivRecPtr->emplace_back(DivRecPlusNoise);
251  }
252 
253  event.put(std::move(OpDetDivRecPtr));
254  }
255 
256  //---------------------------------------------------------------------------
258  sim::OpDetDivRec& dr_plusnoise)
259  {
260  // Don't do anything without any records
261  if (btr.timePDclockSDPsMap().size() == 0)
262  return;
263 
264  // Get the earliest time in the BTR
265  double firstTime = btr.timePDclockSDPsMap()[0].first;
266  if (fCorrectLateLight) {
267  for (auto time_sdps : btr.timePDclockSDPsMap()) {
268  double time = btr.timePDclockSDPsMap()[0].first;
269  if (time < firstTime) firstTime = time;
270  }
271  }
272 
273  // Loop through times in vector< pair< arrival time (in ns), vector< SDP > > >
274  for (auto const& [time, sdps]: btr.timePDclockSDPsMap()) {
275 
276  double lateScale = 1.;
277  if (fCorrectLateLight) {
278  if (time > firstTime + fLateLightBoundary)
279  lateScale = fLateLightCorrection;
280  }
281 
282  // Loop through SDPs
283  for(auto const& sdp : sdps) {
284 
285  // Reduce true photons by QE, poisson-fluctuate
286  int nphot = fRandPoissPhot.fire(fQE * (double)sdp.numPhotons * lateScale);
287 
288  // For each true photon detected
289  for(int truePh=0; truePh<nphot; ++truePh) {
290 
291  // Determine actual PE with Cross Talk
292  unsigned int PE = 1+CrossTalk();
293  for(unsigned int i = 0; i < PE; i++) {
294  // Add to collection
295  dr_plusnoise.AddPhoton(btr.OpDetNum(), // Channel
296  sdp.trackID, // TrackID
297  time); // Time
298  }
299  }
300  }
301  }
302  }
303 
304  //---------------------------------------------------------------------------
305  unsigned short SIPMOpSensorSim::CrossTalk()
306  {
307  // Use recursion to allow cross talk to create cross talk
308  if (fCrossTalk <= 0.0) return 0;
309  else if (fRandFlat.fire(1.0) > fCrossTalk) return 0;
310  else return 1+CrossTalk();
311  }
312 
313  //---------------------------------------------------------------------------
315  {
316 
317  double tau = 1.0/fDarkNoiseRate*1.e6; // Typical time between Dark counts in us
318  // Multiply by 10^6 since fDarkNoiseRate is in Hz
319 
320  double darkNoiseTime = fRandExponential.fire(tau) + fTimeBegin;
321  while (darkNoiseTime < fTimeEnd) {
322  //Currently using all zeros as an indicator of Dark Noise for the trackID.
323  int PE = 1+CrossTalk();
324  for(int j = 0; j < PE; j++) {
325  dr_plusnoise.AddPhoton(dr_plusnoise.OpDetNum(), 0, darkNoiseTime);
326  }
327 
328  // Find next time to simulate a single PE pulse
329  darkNoiseTime += fRandExponential.fire(tau);
330  }
331  }
332 
333  //---------------------------------------------------------------------------
335  {
336 
337  // Get the window start/end time from the detector properties services
338  auto const clockData = art::ServiceHandle<detinfo::DetectorClocksService const>()->DataForJob();
339  auto const detProp = art::ServiceHandle<detinfo::DetectorPropertiesService const>()->DataForJob(clockData);
340  auto const geometry = art::ServiceHandle< geo::Geometry >();
341 
342  double maxDrift = 0.0;
343  for (geo::TPCGeo const& tpc : geometry->IterateTPCs())
344  if (maxDrift < tpc.DriftDistance()) maxDrift = tpc.DriftDistance();
345 
346  // Start at -1 drift window
347  fTimeBegin = -1*maxDrift/detProp.DriftVelocity();
348 
349  // Take the TPC readout window size and convert
350  // to us with the electronics clock frequency
351  fTimeEnd = detProp.ReadOutWindowSize() / clockData.TPCClock().Frequency();
352  }
353 
354 
355 } // end opdet namespace
base_engine_t & createEngine(seed_t seed)
CLHEP::HepRandomEngine & fSIPMEngine
Store parameters for running LArG4.
fhicl::Atom< art::InputTag > InputTag
EDProducer(fhicl::ParameterSet const &pset)
Definition: EDProducer.h:20
Geometry information for a single TPC.
Definition: TPCGeo.h:38
ChannelGroupService::Name Name
unsigned int NOpHardwareChannels(int opDet) const
Energy deposited on a readout Optical Detector by simulated tracks.
SIPMOpSensorSim(Parameters const &config)
fhicl::OptionalAtom< double > LateLightCorrection
Simulation objects for optical detectors.
int OpDetNum() const
Returns the readout Optical Detector this object describes.
#define DEFINE_ART_MODULE(klass)
Definition: ModuleMacros.h:67
static Config * config
Definition: config.cpp:1054
def move(depos, offset)
Definition: depos.py:107
void AddDarkNoise(sim::OpDetDivRec &)
CLHEP::RandPoissonQ fRandPoissPhot
void produce(art::Event &) override
art::ProductToken< std::vector< sim::OpDetBacktrackerRecord > > fInputToken
int OpDetNum() const
Definition: OpDetDivRec.h:102
unsigned int NOpDets() const
Number of OpDets in the whole detector.
fhicl::OptionalAtom< double > LateLightBoundary
cet::coded_exception< errors::ErrorCodes, ExceptionDetail::translate > Exception
Definition: Exception.h:66
#define Comment
CLHEP::RandExponential fRandExponential
Tools and modules for checking out the basics of the Monte Carlo.
timePDclockSDPs_t const & timePDclockSDPsMap() const
Returns all the deposited energy information as stored.
void AddPhoton(int opchan, int tid, OpDet_Time_Chans::stored_time_t pdTime)
Definition: OpDetDivRec.cxx:25
Event finding and building.
void PhotonsToPE(sim::OpDetBacktrackerRecord const &btr, sim::OpDetDivRec &dr_plusnoise)