OpFastScintillation.cxx
Go to the documentation of this file.
1 // Class adapted for LArSoft by Ben Jones, MIT 10/10/12
2 //
3 // This class is a physics process based on the standard Geant4
4 // scintillation process.
5 //
6 // It has been stripped down and adapted to form the backbone of
7 // the LArG4 fast optical simulation. Photons, instead of being
8 // produced and added to the geant4 particle stack, are logged
9 // and used to predict the visibility of this step to each PMT in
10 // the detector.
11 //
12 // The photonvisibilityservice looks up the visibility of the relevant
13 // xyz point, and if a photon is detected at a given PMT, one OnePhoton
14 // object is logged in the OpDetPhotonTable
15 //
16 // At the end of the event, the OpDetPhotonTable is read out
17 // by LArG4, and detected photons are stored in the event.
18 //
19 // This process can be used alongside the standard Cerenkov process,
20 // which does step geant4 opticalphotons. Both the fast scintillation
21 // table and the geant4 sensitive detectors are read out by LArG4 to
22 // produce a combined SimPhoton collection.
23 //
24 // Added disclaimer : This code is gross. Thats basically because
25 // it adheres to the original, gross Geant4 implementation.
26 //
27 //
28 // ********************************************************************
29 // * License and Disclaimer *
30 // * *
31 // * The Geant4 software is copyright of the Copyright Holders of *
32 // * the Geant4 Collaboration. It is provided under the terms and *
33 // * conditions of the Geant4 Software License, included in the file *
34 // * LICENSE and available at http://cern.ch/geant4/license . These *
35 // * include a list of copyright holders. *
36 // * *
37 // * Neither the authors of this software system, nor their employing *
38 // * institutes,nor the agencies providing financial support for this *
39 // * work make any representation or warranty, express or implied, *
40 // * regarding this software system or assume any liability for its *
41 // * use. Please see the license in the file LICENSE and URL above *
42 // * for the full disclaimer and the limitation of liability. *
43 // * *
44 // * This code implementation is the result of the scientific and *
45 // * technical work of the GEANT4 collaboration. *
46 // * By using, copying, modifying or distributing the software (or *
47 // * any work based on the software) you agree to acknowledge its *
48 // * use in resulting scientific publications, and indicate your *
49 // * acceptance of all terms of the Geant4 Software license. *
50 // ********************************************************************
51 //
52 //
53 // GEANT4 tag $Name: not supported by cvs2svn $
54 //
55 ////////////////////////////////////////////////////////////////////////
56 // Scintillation Light Class Implementation
57 ////////////////////////////////////////////////////////////////////////
58 //
59 // File: OpFastScintillation.cc
60 // Description: RestDiscrete Process - Generation of Scintillation Photons
61 // Version: 1.0
62 // Created: 1998-11-07
63 // Author: Peter Gumplinger
64 // Updated: 2010-10-20 Allow the scintillation yield to be a function
65 // of energy deposited by particle type
66 // Thanks to Zach Hartwig (Department of Nuclear
67 // Science and Engineeering - MIT)
68 // 2010-09-22 by Peter Gumplinger
69 // > scintillation rise time included, thanks to
70 // > Martin Goettlich/DESY
71 // 2005-08-17 by Peter Gumplinger
72 // > change variable name MeanNumPhotons -> MeanNumberOfPhotons
73 // 2005-07-28 by Peter Gumplinger
74 // > add G4ProcessType to constructor
75 // 2004-08-05 by Peter Gumplinger
76 // > changed StronglyForced back to Forced in GetMeanLifeTime
77 // 2002-11-21 by Peter Gumplinger
78 // > change to use G4Poisson for small MeanNumberOfPhotons
79 // 2002-11-07 by Peter Gumplinger
80 // > now allow for fast and slow scintillation component
81 // 2002-11-05 by Peter Gumplinger
82 // > now use scintillation constants from G4Material
83 // 2002-05-09 by Peter Gumplinger
84 // > use only the PostStepPoint location for the origin of
85 // scintillation photons when energy is lost to the medium
86 // by a neutral particle
87 // 2000-09-18 by Peter Gumplinger
88 // > change: aSecondaryPosition=x0+rand*aStep.GetDeltaPosition();
89 // aSecondaryTrack->SetTouchable(0);
90 // 2001-09-17, migration of Materials to pure STL (mma)
91 // 2003-06-03, V.Ivanchenko fix compilation warnings
92 //
93 // mail: gum@triumf.ca
94 //
95 ////////////////////////////////////////////////////////////////////////
96 
97 #include "Geant4/G4Alpha.hh"
98 #include "Geant4/G4DynamicParticle.hh"
99 #include "Geant4/G4Electron.hh"
100 #include "Geant4/G4ExceptionSeverity.hh"
101 #include "Geant4/G4Gamma.hh"
102 #include "Geant4/G4KaonMinus.hh"
103 #include "Geant4/G4KaonPlus.hh"
104 #include "Geant4/G4Material.hh"
105 #include "Geant4/G4MaterialPropertiesTable.hh"
106 #include "Geant4/G4MaterialPropertyVector.hh"
107 #include "Geant4/G4MaterialTable.hh"
108 #include "Geant4/G4MuonMinus.hh"
109 #include "Geant4/G4MuonPlus.hh"
110 #include "Geant4/G4ParticleChange.hh"
111 #include "Geant4/G4PhysicsVector.hh"
112 #include "Geant4/G4PionMinus.hh"
113 #include "Geant4/G4PionPlus.hh"
114 #include "Geant4/G4Poisson.hh"
115 #include "Geant4/G4Proton.hh"
116 #include "Geant4/G4Step.hh"
117 #include "Geant4/G4StepPoint.hh"
118 #include "Geant4/G4Track.hh"
119 #include "Geant4/G4VPhysicalVolume.hh"
120 #include "Geant4/G4ios.hh"
121 #include "Geant4/Randomize.hh"
122 #include "Geant4/globals.hh"
123 
135 
137 #include "larcorealg/Geometry/geo_vectors_utils.h" // geo::vect::fillCoords()
138 #include "larcorealg/Geometry/geo_vectors_utils_TVector.h" // geo::vect::toTVector3()
140 
141 // support libraries
142 #include "cetlib_except/exception.h"
144 
145 #include "TMath.h"
146 #include "TRandom3.h"
147 #include "TGeoSphere.h"
148 
149 #include <cassert>
150 #include <cmath>
151 #include <limits>
152 
153 #include "boost/math/special_functions/ellint_1.hpp"
154 #include "boost/math/special_functions/ellint_3.hpp"
155 
156 // Define a new policy *not* internally promoting RealType to double:
157 typedef boost::math::policies::policy<
158  // boost::math::policies::digits10<8>,
159  boost::math::policies::promote_double<false>>
161 
162 namespace larg4 {
163 
164  /////////////////////////
165  // Class Implementation
166  /////////////////////////
167 
168  //////////////
169  // Operators
170  //////////////
171 
172  // OpFastScintillation::operator=(const OpFastScintillation &right)
173  // {
174  // }
175 
176  /////////////////
177  // Constructors
178  /////////////////
179 
180  OpFastScintillation::OpFastScintillation(const G4String& processName, G4ProcessType type)
181  : G4VRestDiscreteProcess(processName, type)
182  , fActiveVolumes{extractActiveVolumes(*(lar::providerFrom<geo::Geometry>()))}
183  , bPropagate(!(art::ServiceHandle<sim::LArG4Parameters const>()->NoPhotonPropagation()))
186  // for now, limit to the active volume only if semi-analytic model is used
188  {
189  SetProcessSubType(25); // TODO: unhardcode
190  fTrackSecondariesFirst = false;
191  fFiniteRiseTime = false;
192  YieldFactor = 1.0;
193  ExcitationRatio = 1.0;
194 
195  const detinfo::LArProperties* larp = lar::providerFrom<detinfo::LArPropertiesService>();
196 
198 
199  if (verboseLevel > 0) { G4cout << GetProcessName() << " is created " << G4endl; }
200 
202  emSaturation = NULL;
203 
204  if (bPropagate) {
205  assert(fPVS);
206 
207  // Loading the position of each optical channel, neccessary for the parametrizatiuons of Nhits and prop-time
208  geo::GeometryCore const& geom = *(lar::providerFrom<geo::Geometry>());
209 
210  {
211  auto log = mf::LogTrace("OpFastScintillation")
212  << "OpFastScintillation: active volume boundaries from " << fActiveVolumes.size()
213  << " volumes:";
214  for (auto const& [iCryo, box] : util::enumerate(fActiveVolumes)) {
215  log << "\n - C:" << iCryo << ": " << box.Min() << " -- " << box.Max() << " cm";
216  } // for
217  log << "\n (scintillation photons are propagated "
218  << (fOnlyActiveVolume ? "only from active volumes" : "from anywhere") << ")";
219  } // local scope
220 
221  if (usesSemiAnalyticModel() && (geom.Ncryostats() > 1U)) {
222  if (fOnlyOneCryostat) {
223  mf::LogWarning("OpFastScintillation")
224  << std::string(80, '=') << "\nA detector with " << geom.Ncryostats()
225  << " cryostats is configured"
226  << " , and semi-analytic model is requested for scintillation photon propagation."
227  << " THIS CONFIGURATION IS NOT SUPPORTED and it is open to bugs"
228  << " (e.g. scintillation may be detected only in cryostat #0)."
229  << "\nThis would be normally a fatal error, but it has been forcibly overridden."
230  << "\n"
231  << std::string(80, '=');
232  }
233  else {
235  << "Photon propagation via semi-analytic model is not supported yet"
236  << " on detectors with more than one cryostat.";
237  }
238  } // if
239 
240  geo::Point_t const Cathode_centre{geom.TPC(0, 0).GetCathodeCenter().X(),
241  fActiveVolumes[0].CenterY(),
242  fActiveVolumes[0].CenterZ()};
243  mf::LogTrace("OpFastScintillation") << "Cathode_centre: " << Cathode_centre << " cm";
244 
245  // std::cout << "\nInitialize acos_arr with " << acos_bins+1
246  // << " hence with a resolution of " << 1./acos_bins << std::endl;
247  // for(size_t i=0; i<=acos_bins; ++i){
248  // acos_arr[i] = std::acos(i/double(acos_bins));
249  // }
250 
251  for (size_t const i : util::counter(fPVS->NOpChannels())) {
252  geo::OpDetGeo const& opDet = geom.OpDetGeoFromOpDet(i);
253  fOpDetCenter.push_back(opDet.GetCenter());
254 
255  if (dynamic_cast<TGeoSphere const*>(opDet.Shape()) != nullptr) { // sphere/dome
256  fOpDetType.push_back(1); // Dome PMTs
257  fOpDetLength.push_back(-1);
258  fOpDetHeight.push_back(-1);
259  }
260  else if (opDet.isBar()) { // box
261  fOpDetType.push_back(0); //Arapucas
262  fOpDetLength.push_back(opDet.Length());
263  fOpDetHeight.push_back(opDet.Height());
264  }
265  else { // disk
266  fOpDetType.push_back(2); // Disk PMTs
267  // std::cout<<"Radio: "<<geom.OpDetGeoFromOpDet(i).RMax()<<std::endl;
268  fOpDetLength.push_back(-1);
269  fOpDetHeight.push_back(-1);
270  }
271  }
272 
273  if (fPVS->IncludePropTime()) {
274  std::cout << "Using parameterisation of timings." << std::endl;
275  //OLD VUV time parapetrization (to be removed soon)
276  //fPVS->SetDirectLightPropFunctions(functions_vuv, fd_break, fd_max, ftf1_sampling_factor);
277  //fPVS->SetReflectedCOLightPropFunctions(functions_vis, ft0_max, ft0_break_point);
278  //New VUV time parapetrization
280  fstep_size,
281  fmax_d,
282  fmin_d,
287 
288  // create vector of empty TF1s that will be replaces with the parameterisations that are generated as they are required
289  // default TF1() constructor gives function with 0 dimensions, can then check numDim to qucikly see if a parameterisation has been generated
290  const size_t num_params = (fmax_d - fmin_d) / fstep_size; // for d < fmin_d, no parameterisaton, a delta function is used instead
291  size_t num_angles = std::round(90/fangle_bin_timing_vuv);
292  VUV_timing = std::vector(num_angles, std::vector(num_params, TF1()));
293 
294  // initialise vectors to contain range parameterisations sampled to in each case
295  // when using TF1->GetRandom(xmin,xmax), must be in same range otherwise sampling is regenerated, this is the slow part!
296  VUV_max = std::vector(num_angles, std::vector(num_params, 0.0));
297  VUV_min = std::vector(num_angles, std::vector(num_params, 0.0));
298 
299  // VIS time parameterisation
300  if (fPVS->StoreReflected()) {
301  // load parameters
305  ftau_pars,
306  fvis_vmean,
308  }
309  }
310  if (usesSemiAnalyticModel()) {
311  mf::LogVerbatim("OpFastScintillation")
312  << "OpFastScintillation: using semi-analytic model for number of hits";
313 
314  // LAr absorption length in cm
315  std::map<double, double> abs_length_spectrum =
316  lar::providerFrom<detinfo::LArPropertiesService>()->AbsLengthSpectrum();
317  std::vector<double> x_v, y_v;
318  for (auto elem : abs_length_spectrum) {
319  x_v.push_back(elem.first);
320  y_v.push_back(elem.second);
321  }
322  fL_abs_vuv =
323  std::round(interpolate(x_v, y_v, 9.7, false)); // 9.7 eV: peak of VUV emission spectrum
324 
325  // Load Gaisser-Hillas corrections for VUV semi-analytic hits
326  std::cout << "Loading the GH corrections" << std::endl;
330  fradius
331  );
332  if (!fIsFlatPDCorr && !fIsDomePDCorr) {
333  throw cet::exception("OpFastScintillation")
334  << "Both isFlatPDCorr and isDomePDCorr parameters are false, at least one type of parameterisation is required for the semi-analytic light simulation." << "\n";
335  }
336  if (fIsFlatPDCorr) {
340  );
341  }
342  if (fIsDomePDCorr) {
346  );
347  }
348  // cathode center coordinates required for corrections
349  fcathode_centre = geom.TPC(0, 0).GetCathodeCenter();
350  fcathode_centre[1] = fActiveVolumes[0].CenterY();
351  fcathode_centre[2] = fActiveVolumes[0].CenterZ(); // to get full cathode dimension rather than just single tpc
352 
353 
354  if (fPVS->StoreReflected()) {
355  fStoreReflected = true;
356  // Load corrections for VIS semi-anlytic hits
357  std::cout << "Loading visible light corrections" << std::endl;
359  fradius
360  );
361  if (fIsFlatPDCorr) {
365  );
366  }
367  if (fIsDomePDCorr) {
371  );
372  }
373 
374  // cathode dimensions
375  fcathode_ydimension = fActiveVolumes[0].SizeY();
376  fcathode_zdimension = fActiveVolumes[0].SizeZ();
377 
378  // set cathode plane struct for solid angle function
382  }
383  else
384  fStoreReflected = false;
385  }
386  }
387  tpbemission = lar::providerFrom<detinfo::LArPropertiesService>()->TpbEm();
388  std::vector<double> parent;
389  parent.reserve(tpbemission.size());
390  for (auto iter = tpbemission.begin(); iter != tpbemission.end(); ++iter) {
391  parent.push_back(iter->second);
392  }
393  fTPBEm = std::make_unique<CLHEP::RandGeneral>
394  (parent.data(), parent.size());
395  }
396 
397  ////////////////
398  // Destructors
399  ////////////////
401  {
402  if (theFastIntegralTable) theFastIntegralTable->clearAndDestroy();
403  if (theSlowIntegralTable) theSlowIntegralTable->clearAndDestroy();
404  }
405 
406  ////////////
407  // Methods
408  ////////////
409 
410  // AtRestDoIt
411  // ----------
412  //
413  G4VParticleChange*
414  OpFastScintillation::AtRestDoIt(const G4Track& aTrack, const G4Step& aStep)
415  // This routine simply calls the equivalent PostStepDoIt since all the
416  // necessary information resides in aStep.GetTotalEnergyDeposit()
417  {
418  return OpFastScintillation::PostStepDoIt(aTrack, aStep);
419  }
420 
421  // PostStepDoIt
422  // -------------
423  //
424  G4VParticleChange*
425  OpFastScintillation::PostStepDoIt(const G4Track& aTrack, const G4Step& aStep)
426  // This routine is called for each tracking step of a charged particle
427  // in a scintillator. A Poisson/Gauss-distributed number of photons is
428  // generated according to the scintillation yield formula, distributed
429  // evenly along the track segment and uniformly into 4pi.
430  {
431  aParticleChange.Initialize(aTrack);
432  // Check that we are in a material with a properties table, if not
433  // just return
434  const G4Material* aMaterial = aTrack.GetMaterial();
435  G4MaterialPropertiesTable* aMaterialPropertiesTable = aMaterial->GetMaterialPropertiesTable();
436  if (!aMaterialPropertiesTable) return G4VRestDiscreteProcess::PostStepDoIt(aTrack, aStep);
437 
438  G4StepPoint* pPreStepPoint = aStep.GetPreStepPoint();
439  G4ThreeVector x0 = pPreStepPoint->GetPosition();
440  G4ThreeVector p0 = aStep.GetDeltaPosition().unit();
441 
442  ///////////////////////////////////////////////////////////////////////////////////
443  // This is the old G4 way - but we do things differently - Ben J, Oct Nov 2012.
444  ///////////////////////////////////////////////////////////////////////////////////
445  //
446  // if (MeanNumberOfPhotons > 10.)
447  // {
448  // G4double sigma = ResolutionScale * std::sqrt(MeanNumberOfPhotons);
449  // NumPhotons = G4int(G4RandGauss::shoot(MeanNumberOfPhotons,sigma)+0.5);
450  // }
451  // else
452  // {
453  // NumPhotons = G4int(G4Poisson(MeanNumberOfPhotons));
454  // }
455  //
456  //
457  //
458  // if (NumPhotons <= 0)
459  // {
460  // // return unchanged particle and no secondaries
461  //
462  // aParticleChange.SetNumberOfSecondaries(0);
463  //
464  // return G4VRestDiscreteProcess::PostStepDoIt(aTrack, aStep);
465  // }
466  //
467  //
468  // aParticleChange.SetNumberOfSecondaries(NumPhotons);
469  //
470  //
471  // if (fTrackSecondariesFirst) {
472  // if (aTrack.GetTrackStatus() == fAlive )
473  // aParticleChange.ProposeTrackStatus(fSuspend);
474  // }
475  //
476  //
477  //
478  //
479  ////////////////////////////////////////////////////////////////////////////////////
480  //
481 
482  ////////////////////////////////////////////////////////////////////////////////////
483  // The fast sim way - Ben J, Nov 2012
484  ////////////////////////////////////////////////////////////////////////////////////
485  //
486  //
487  // We don't want to produce any trackable G4 secondaries
488  aParticleChange.SetNumberOfSecondaries(0);
489 
490  // Retrieve the Scintillation Integral for this material
491  // new G4PhysicsOrderedFreeVector allocated to hold CII's
492 
493  // Some explanation for later improvements to scint yield code:
494  //
495  // What does G4 do here?
496  // It produces light in 2 steps, fast (scnt=1) then slow (scnt=2)
497  //
498  // The ratio of slow photons to fast photons is related by the yieldratio
499  // parameter. G4's poisson fluctuating scheme is a bit different to ours
500  // - we should check that they are equivalent.
501  //
502  // G4 poisson fluctuates the number of initial photons then divides them
503  // with a constant factor between fast + slow, whereas we poisson
504  // fluctuate separateyly the fast and slow detection numbers.
505  //
506 
507  // get the number of photons produced from the IonizationAndScintillation
508  // singleton
510  double MeanNumberOfPhotons =
512  // double stepEnergy = larg4::IonizationAndScintillation::Instance()->VisibleEnergyDeposit()/CLHEP::MeV;
513  RecordPhotonsProduced(aStep, MeanNumberOfPhotons); //, stepEnergy);
514  if (verboseLevel > 0) {
515  G4cout << "\n Exiting from OpFastScintillation::DoIt -- NumberOfSecondaries = "
516  << aParticleChange.GetNumberOfSecondaries() << G4endl;
517  }
518  return G4VRestDiscreteProcess::PostStepDoIt(aTrack, aStep);
519  }
520 
521  void
523  {
524  if (step.GetTotalEnergyDeposit() <= 0) return;
525 
527  -1,
528  -1,
529  1.0, //scintillation yield
530  (double)(step.GetTotalEnergyDeposit() / CLHEP::MeV), //energy in MeV
531  (float)(step.GetPreStepPoint()->GetPosition().x() / CLHEP::cm),
532  (float)(step.GetPreStepPoint()->GetPosition().y() / CLHEP::cm),
533  (float)(step.GetPreStepPoint()->GetPosition().z() / CLHEP::cm),
534  (float)(step.GetPostStepPoint()->GetPosition().x() / CLHEP::cm),
535  (float)(step.GetPostStepPoint()->GetPosition().y() / CLHEP::cm),
536  (float)(step.GetPostStepPoint()->GetPosition().z() / CLHEP::cm),
537  (double)(step.GetPreStepPoint()->GetGlobalTime()),
538  (double)(step.GetPostStepPoint()->GetGlobalTime()),
539  //step.GetTrack()->GetTrackID(),
541  step.GetTrack()->GetParticleDefinition()->GetPDGEncoding(),
542  step.GetPreStepPoint()->GetPhysicalVolume()->GetName());
543  }
544 
546  double MeanNumberOfPhotons) //, double stepEnergy)
547  {
548  // make sure that whatever happens afterwards, the energy deposition is stored
550  if (lgp->FillSimEnergyDeposits()) ProcessStep(aStep);
551 
552  // Get the pointer to the fast scintillation table
554 
555  const G4Track* aTrack = aStep.GetTrack();
556 
557  G4StepPoint const* pPreStepPoint = aStep.GetPreStepPoint();
558  // unused G4StepPoint const* pPostStepPoint = aStep.GetPostStepPoint();
559 
560  const G4DynamicParticle* aParticle = aTrack->GetDynamicParticle();
561  const G4Material* aMaterial = aTrack->GetMaterial();
562 
563  G4int materialIndex = aMaterial->GetIndex();
564  G4int tracknumber = aStep.GetTrack()->GetTrackID();
565 
566  G4ThreeVector x0 = pPreStepPoint->GetPosition();
567  G4ThreeVector p0 = aStep.GetDeltaPosition().unit();
568  //G4double t0 = pPreStepPoint->GetGlobalTime() - fGlobalTimeOffset;
569  G4double t0 = pPreStepPoint->GetGlobalTime();
570 
571  G4MaterialPropertiesTable* aMaterialPropertiesTable = aMaterial->GetMaterialPropertiesTable();
572 
573  G4MaterialPropertyVector* Fast_Intensity =
574  aMaterialPropertiesTable->GetProperty("FASTCOMPONENT");
575  G4MaterialPropertyVector* Slow_Intensity =
576  aMaterialPropertiesTable->GetProperty("SLOWCOMPONENT");
577 
578  if (!Fast_Intensity && !Slow_Intensity) return 1;
579 
580  if (!bPropagate) return 0;
581 
582  // Get the visibility vector for this point
583  assert(fPVS);
584 
585  G4int nscnt = 1;
586  if (Fast_Intensity && Slow_Intensity) nscnt = 2;
587 
588  double Num = 0;
589  double YieldRatio = 0;
590 
592  // The scintillation response is a function of the energy
593  // deposited by particle types.
594 
595  // Get the definition of the current particle
596  G4ParticleDefinition* pDef = aParticle->GetDefinition();
597 
598  // Obtain the G4MaterialPropertyVectory containing the
599  // scintillation light yield as a function of the deposited
600  // energy for the current particle type
601 
602  // Protons
603  if (pDef == G4Proton::ProtonDefinition()) {
604  YieldRatio = aMaterialPropertiesTable->GetConstProperty("PROTONYIELDRATIO");
605  }
606  // Muons
607  else if (pDef == G4MuonPlus::MuonPlusDefinition() ||
608  pDef == G4MuonMinus::MuonMinusDefinition()) {
609  YieldRatio = aMaterialPropertiesTable->GetConstProperty("MUONYIELDRATIO");
610  }
611  // Pions
612  else if (pDef == G4PionPlus::PionPlusDefinition() ||
613  pDef == G4PionMinus::PionMinusDefinition()) {
614  YieldRatio = aMaterialPropertiesTable->GetConstProperty("PIONYIELDRATIO");
615  }
616  // Kaons
617  else if (pDef == G4KaonPlus::KaonPlusDefinition() ||
618  pDef == G4KaonMinus::KaonMinusDefinition()) {
619  YieldRatio = aMaterialPropertiesTable->GetConstProperty("KAONYIELDRATIO");
620  }
621  // Alphas
622  else if (pDef == G4Alpha::AlphaDefinition()) {
623  YieldRatio = aMaterialPropertiesTable->GetConstProperty("ALPHAYIELDRATIO");
624  }
625  // Electrons (must also account for shell-binding energy
626  // attributed to gamma from standard PhotoElectricEffect)
627  else if (pDef == G4Electron::ElectronDefinition() || pDef == G4Gamma::GammaDefinition()) {
628  YieldRatio = aMaterialPropertiesTable->GetConstProperty("ELECTRONYIELDRATIO");
629  }
630  // Default for particles not enumerated/listed above
631  else {
632  YieldRatio = aMaterialPropertiesTable->GetConstProperty("ELECTRONYIELDRATIO");
633  }
634  // If the user has not specified yields for (p,d,t,a,carbon)
635  // then these unspecified particles will default to the
636  // electron's scintillation yield
637  if (YieldRatio == 0) {
638  YieldRatio = aMaterialPropertiesTable->GetConstProperty("ELECTRONYIELDRATIO");
639  }
640  }
641 
642  geo::Point_t const ScintPoint = {x0[0] / CLHEP::cm, x0[1] / CLHEP::cm, x0[2] / CLHEP::cm};
643  if (fOnlyActiveVolume && !isScintInActiveVolume(ScintPoint)) return 0;
644  const phot::MappedCounts_t& Visibilities = fPVS->GetAllVisibilities(ScintPoint);
645 
646  phot::MappedCounts_t ReflVisibilities;
647 
648  // Store timing information in the object for use in propagationTime method
649  if (fPVS->StoreReflected()) {
650  ReflVisibilities = fPVS->GetAllVisibilities(ScintPoint, true);
651  if (fPVS->StoreReflT0()) ReflT0s = fPVS->GetReflT0s(ScintPoint);
652  }
653  if (fPVS->IncludeParPropTime()) { ParPropTimeTF1 = fPVS->GetTimingTF1(ScintPoint); }
654 
655  /*
656  // For Kazu to debug # photons generated using csv file, by default should be commented out
657  // but don't remove as it's useful. Separated portion of code relevant to this block
658  // is labeled as "CASE-DEBUG DO NOT REMOVE THIS COMMENT"
659  // (2017 May 2nd ... "kazuhiro at nevis dot columbia dot edu")
660  //
661  static bool first_time=false;
662  if(!first_time) {
663  std::cout<<"LOGMEid,pdg,mass,ke,te,de,x,y,z,t,dr,mean_npe,gen_npe,det_npe"<<std::endl;
664  first_time=true;
665  }
666 
667  std::cout<<"LOGME"
668  <<aStep.GetTrack()->GetTrackID()<<","
669  <<aParticle->GetDefinition()->GetPDGEncoding()<<","
670  <<aParticle->GetDefinition()->GetPDGMass()/CLHEP::MeV<<","
671  <<pPreStepPoint->GetKineticEnergy()<<","
672  <<pPreStepPoint->GetTotalEnergy()<<","
673  <<aStep.GetTotalEnergyDeposit()<<","
674  <<ScintPoint<<","<<t0<<","
675  <<aStep.GetDeltaPosition().mag()<<","
676  <<MeanNumberOfPhotons<<","<<std::flush;
677 
678  double gen_photon_ctr=0;
679  double det_photon_ctr=0;
680  */
681  for (G4int scnt = 1; scnt <= nscnt; scnt++) {
682  G4double ScintillationTime = 0. * CLHEP::ns;
683  G4double ScintillationRiseTime = 0. * CLHEP::ns;
684  G4PhysicsOrderedFreeVector* ScintillationIntegral = NULL;
685  if (scnt == 1) {
686  if (nscnt == 1) {
687  if (Fast_Intensity) {
688  ScintillationTime = aMaterialPropertiesTable->GetConstProperty("FASTTIMECONSTANT");
689  if (fFiniteRiseTime) {
690  ScintillationRiseTime =
691  aMaterialPropertiesTable->GetConstProperty("FASTSCINTILLATIONRISETIME");
692  }
693  ScintillationIntegral =
694  (G4PhysicsOrderedFreeVector*)((*theFastIntegralTable)(materialIndex));
695  }
696  if (Slow_Intensity) {
697  ScintillationTime = aMaterialPropertiesTable->GetConstProperty("SLOWTIMECONSTANT");
698  if (fFiniteRiseTime) {
699  ScintillationRiseTime =
700  aMaterialPropertiesTable->GetConstProperty("SLOWSCINTILLATIONRISETIME");
701  }
702  ScintillationIntegral =
703  (G4PhysicsOrderedFreeVector*)((*theSlowIntegralTable)(materialIndex));
704  }
705  } //endif nscnt=1
706  else {
707  if (YieldRatio == 0)
708  YieldRatio = aMaterialPropertiesTable->GetConstProperty("YIELDRATIO");
709 
710  if (ExcitationRatio == 1.0) { Num = std::min(YieldRatio, 1.0) * MeanNumberOfPhotons; }
711  else {
712  Num = std::min(ExcitationRatio, 1.0) * MeanNumberOfPhotons;
713  }
714  ScintillationTime = aMaterialPropertiesTable->GetConstProperty("FASTTIMECONSTANT");
715  if (fFiniteRiseTime) {
716  ScintillationRiseTime =
717  aMaterialPropertiesTable->GetConstProperty("FASTSCINTILLATIONRISETIME");
718  }
719  ScintillationIntegral =
720  (G4PhysicsOrderedFreeVector*)((*theFastIntegralTable)(materialIndex));
721  } //endif nscnt!=1
722  } //end scnt=1
723 
724  else {
725  Num = MeanNumberOfPhotons - Num;
726  ScintillationTime = aMaterialPropertiesTable->GetConstProperty("SLOWTIMECONSTANT");
727  if (fFiniteRiseTime) {
728  ScintillationRiseTime =
729  aMaterialPropertiesTable->GetConstProperty("SLOWSCINTILLATIONRISETIME");
730  }
731  ScintillationIntegral =
732  (G4PhysicsOrderedFreeVector*)((*theSlowIntegralTable)(materialIndex));
733  }
734 
735  if (!ScintillationIntegral) continue;
736  //gen_photon_ctr += Num; // CASE-DEBUG DO NOT REMOVE THIS COMMENT
737  // Max Scintillation Integral
738  // G4double CIImax = ScintillationIntegral->GetMaxValue();
739  //std::cout << "++++++++++++" << Num << "++++++++++" << std::endl;
740 
741  // here we go: now if visibilities are invalid, we are in trouble
742  //if (!Visibilities && (fPVS->NOpChannels() > 0)) {
743  // throw cet::exception("OpFastScintillator")
744  // << "Photon library does not cover point " << ScintPoint << " cm.\n";
745  //}
746 
747  if (!Visibilities && !usesSemiAnalyticModel()) continue;
748 
749  // detected photons from direct light
750  std::map<size_t, int> DetectedNum;
751  if (Visibilities && !usesSemiAnalyticModel()) {
752  for (size_t const OpDet : util::counter(fPVS->NOpChannels())) {
753  if (fOpaqueCathode && !isOpDetInSameTPC(ScintPoint, fOpDetCenter.at(OpDet))) continue;
754  int const DetThis = std::round(G4Poisson(Visibilities[OpDet] * Num));
755  if (DetThis > 0) DetectedNum[OpDet] = DetThis;
756  }
757  }
758  else {
759  detectedDirectHits(DetectedNum, Num, ScintPoint);
760  }
761 
762  // detected photons from reflected light
763  std::map<size_t, int> ReflDetectedNum;
764  if (fPVS->StoreReflected()) {
765  if (!usesSemiAnalyticModel()) {
766  for (size_t const OpDet : util::counter(fPVS->NOpChannels())) {
767  if (fOpaqueCathode && !isOpDetInSameTPC(ScintPoint, fOpDetCenter.at(OpDet))) continue;
768  int const ReflDetThis = std::round(G4Poisson(ReflVisibilities[OpDet] * Num));
769  if (ReflDetThis > 0) ReflDetectedNum[OpDet] = ReflDetThis;
770  }
771  }
772  else {
773  detectedReflecHits(ReflDetectedNum, Num, ScintPoint);
774  }
775  }
776 
777  std::vector<double> arrival_time_dist;
778  // Now we run through each PMT figuring out num of detected photons
779  for (size_t Reflected = 0; Reflected <= 1; ++Reflected) {
780  // Only do the reflected loop if we have reflected visibilities
781  if (Reflected && !fPVS->StoreReflected()) continue;
782 
785  if (Reflected) {
786  itstart = ReflDetectedNum.begin();
787  itend = ReflDetectedNum.end();
788  }
789  else {
790  itstart = DetectedNum.begin();
791  itend = DetectedNum.end();
792  }
793  for (auto itdetphot = itstart; itdetphot != itend; ++itdetphot) {
794  const size_t OpChannel = itdetphot->first;
795  const int NPhotons = itdetphot->second;
796 
797  // Set up the OpDetBTR information
798  sim::OpDetBacktrackerRecord tmpOpDetBTRecord(OpChannel);
799  int thisG4TrackID = ParticleListAction::GetCurrentTrackID();
800  double xyzPos[3];
801  average_position(aStep, xyzPos);
802  double Edeposited = 0;
804  //We use this when it is the only sensical information. It may be of limited use to end users.
805  Edeposited = aStep.GetTotalEnergyDeposit();
806  }
807  else if (emSaturation) {
808  //If Birk Coefficient used, log VisibleEnergies.
809  Edeposited =
811  }
812  else {
813  //We use this when it is the only sensical information. It may be of limited use to end users.
814  Edeposited = aStep.GetTotalEnergyDeposit();
815  }
816 
817  // Get the transport time distribution
818  arrival_time_dist.resize(NPhotons);
819  propagationTime(arrival_time_dist, x0, OpChannel, Reflected);
820 
821  //We need to split the energy up by the number of photons so that we never try to write a 0 energy.
822  Edeposited = Edeposited / double(NPhotons);
823 
824  // Loop through the photons
825  for (G4int i = 0; i < NPhotons; ++i) {
826  //std::cout<<"VUV time correction: "<<arrival_time_dist[i]<<std::endl;
827  G4double Time = t0 + scint_time(aStep, ScintillationTime, ScintillationRiseTime) +
828  arrival_time_dist[i] * CLHEP::ns;
829 
830  // Always store the BTR
831  tmpOpDetBTRecord.AddScintillationPhotons(thisG4TrackID, Time, 1, xyzPos, Edeposited);
832 
833  // Store as lite photon or as OnePhoton
834  if (lgp->UseLitePhotons()) {
835  fst->AddLitePhoton(OpChannel, static_cast<int>(Time), 1, Reflected);
836  }
837  else {
838  // The sim photon in this case stores its production point and time
839  TVector3 PhotonPosition(x0[0], x0[1], x0[2]);
840 
841  float PhotonEnergy = 0;
842  if (Reflected)
843  PhotonEnergy = reemission_energy() * CLHEP::eV;
844  else
845  PhotonEnergy = 9.7 * CLHEP::eV; // 9.7 eV peak of VUV emission spectrum
846 
847  // Make a photon object for the collection
848  sim::OnePhoton PhotToAdd;
849  PhotToAdd.InitialPosition = PhotonPosition;
850  PhotToAdd.Energy = PhotonEnergy;
851  PhotToAdd.Time = Time;
852  PhotToAdd.SetInSD = false;
853  PhotToAdd.MotherTrackID = tracknumber;
854 
855  fst->AddPhoton(OpChannel, std::move(PhotToAdd), Reflected);
856  }
857  }
858  fst->AddOpDetBacktrackerRecord(tmpOpDetBTRecord, Reflected);
859  }
860  }
861  }
862  //std::cout<<gen_photon_ctr<<","<<det_photon_ctr<<std::endl; // CASE-DEBUG DO NOT REMOVE THIS COMMENT
863  return 0;
864  }
865 
866  // BuildThePhysicsTable for the scintillation process
867  // --------------------------------------------------
868  //
869  void
871  {
873 
874  const G4MaterialTable* theMaterialTable = G4Material::GetMaterialTable();
875  G4int numOfMaterials = G4Material::GetNumberOfMaterials();
876 
877  // create new physics table
879  theFastIntegralTable = std::make_unique<G4PhysicsTable>(numOfMaterials);
881  theSlowIntegralTable = std::make_unique<G4PhysicsTable>(numOfMaterials);
882 
883  // loop for materials
884  for (G4int i = 0; i < numOfMaterials; i++) {
885  G4PhysicsOrderedFreeVector* aPhysicsOrderedFreeVector = new G4PhysicsOrderedFreeVector();
886  G4PhysicsOrderedFreeVector* bPhysicsOrderedFreeVector = new G4PhysicsOrderedFreeVector();
887 
888  // Retrieve vector of scintillation wavelength intensity for
889  // the material from the material's optical properties table.
890  G4Material* aMaterial = (*theMaterialTable)[i];
891 
892  G4MaterialPropertiesTable* aMaterialPropertiesTable = aMaterial->GetMaterialPropertiesTable();
893 
894  if (aMaterialPropertiesTable) {
895 
896  G4MaterialPropertyVector* theFastLightVector =
897  aMaterialPropertiesTable->GetProperty("FASTCOMPONENT");
898 
899  if (theFastLightVector) {
900  // Retrieve the first intensity point in vector
901  // of (photon energy, intensity) pairs
902  G4double currentIN = (*theFastLightVector)[0];
903  if (currentIN >= 0.0) {
904  // Create first (photon energy, Scintillation
905  // Integral pair
906  G4double currentPM = theFastLightVector->Energy(0);
907  G4double currentCII = 0.0;
908 
909  aPhysicsOrderedFreeVector->InsertValues(currentPM, currentCII);
910 
911  // Set previous values to current ones prior to loop
912  G4double prevPM = currentPM;
913  G4double prevCII = currentCII;
914  G4double prevIN = currentIN;
915 
916  // loop over all (photon energy, intensity)
917  // pairs stored for this material
918  for (size_t i = 1; i < theFastLightVector->GetVectorLength(); i++) {
919  currentPM = theFastLightVector->Energy(i);
920  currentIN = (*theFastLightVector)[i];
921 
922  currentCII = 0.5 * (prevIN + currentIN);
923 
924  currentCII = prevCII + (currentPM - prevPM) * currentCII;
925 
926  aPhysicsOrderedFreeVector->InsertValues(currentPM, currentCII);
927 
928  prevPM = currentPM;
929  prevCII = currentCII;
930  prevIN = currentIN;
931  }
932  }
933  }
934 
935  G4MaterialPropertyVector* theSlowLightVector =
936  aMaterialPropertiesTable->GetProperty("SLOWCOMPONENT");
937  if (theSlowLightVector) {
938  // Retrieve the first intensity point in vector
939  // of (photon energy, intensity) pairs
940  G4double currentIN = (*theSlowLightVector)[0];
941  if (currentIN >= 0.0) {
942  // Create first (photon energy, Scintillation
943  // Integral pair
944  G4double currentPM = theSlowLightVector->Energy(0);
945  G4double currentCII = 0.0;
946 
947  bPhysicsOrderedFreeVector->InsertValues(currentPM, currentCII);
948 
949  // Set previous values to current ones prior to loop
950  G4double prevPM = currentPM;
951  G4double prevCII = currentCII;
952  G4double prevIN = currentIN;
953 
954  // loop over all (photon energy, intensity)
955  // pairs stored for this material
956  for (size_t i = 1; i < theSlowLightVector->GetVectorLength(); i++) {
957  currentPM = theSlowLightVector->Energy(i);
958  currentIN = (*theSlowLightVector)[i];
959 
960  currentCII = 0.5 * (prevIN + currentIN);
961 
962  currentCII = prevCII + (currentPM - prevPM) * currentCII;
963 
964  bPhysicsOrderedFreeVector->InsertValues(currentPM, currentCII);
965 
966  prevPM = currentPM;
967  prevCII = currentCII;
968  prevIN = currentIN;
969  }
970  }
971  }
972  }
973  // The scintillation integral(s) for a given material
974  // will be inserted in the table(s) according to the
975  // position of the material in the material table.
976  theFastIntegralTable->insertAt(i, aPhysicsOrderedFreeVector);
977  theSlowIntegralTable->insertAt(i, bPhysicsOrderedFreeVector);
978  }
979  }
980 
981  // Called by the user to set the scintillation yield as a function
982  // of energy deposited by particle type
983  void
985  {
986  if (emSaturation) {
987  G4Exception("OpFastScintillation::SetScintillationByParticleType",
988  "Scint02",
989  JustWarning,
990  "Redefinition: Birks Saturation is replaced by ScintillationByParticleType!");
992  }
993  scintillationByParticleType = scintType;
994  }
995 
996  G4double
997  OpFastScintillation::GetMeanFreePath(const G4Track&, G4double, G4ForceCondition* condition)
998  {
999  *condition = StronglyForced;
1000  return DBL_MAX;
1001  }
1002 
1003  G4double
1004  OpFastScintillation::GetMeanLifeTime(const G4Track&, G4ForceCondition* condition)
1005  {
1006  *condition = Forced;
1007  return DBL_MAX;
1008  }
1009 
1010  G4double
1011  OpFastScintillation::scint_time(const G4Step& aStep,
1012  G4double ScintillationTime,
1013  G4double ScintillationRiseTime) const
1014  {
1015  G4StepPoint const* pPreStepPoint = aStep.GetPreStepPoint();
1016  G4StepPoint const* pPostStepPoint = aStep.GetPostStepPoint();
1017  G4double avgVelocity = (pPreStepPoint->GetVelocity() + pPostStepPoint->GetVelocity()) / 2.;
1018  G4double deltaTime = aStep.GetStepLength() / avgVelocity;
1019  if (ScintillationRiseTime == 0.0) {
1020  deltaTime = deltaTime - ScintillationTime * std::log(G4UniformRand());
1021  }
1022  else {
1023  deltaTime = deltaTime + sample_time(ScintillationRiseTime, ScintillationTime);
1024  }
1025  return deltaTime;
1026  }
1027 
1028  void OpFastScintillation::propagationTime(std::vector<double>& arrival_time_dist,
1029  G4ThreeVector x0,
1030  const size_t OpChannel,
1031  bool Reflected) //const
1032  {
1033  assert(fPVS);
1035  throw cet::exception("OpFastScintillation")
1036  << "Cannot have both propagation time models simultaneously.";
1037  }
1038  else if (fPVS->IncludeParPropTime() &&
1039  !(ParPropTimeTF1 && (ParPropTimeTF1[OpChannel].GetNdim() == 1))) {
1040  //Warning: TF1::GetNdim()==1 will tell us if the TF1 is really defined or it is the default one.
1041  //This will fix a segfault when using timing and interpolation.
1042  G4cout << "WARNING: Requested parameterized timing, but no function found. Not applying "
1043  "propagation time."
1044  << G4endl;
1045  }
1046  else if (fPVS->IncludeParPropTime()) {
1047  if (Reflected)
1048  throw cet::exception("OpFastScintillation")
1049  << "No parameterized propagation time for reflected light";
1050  for (size_t i = 0; i < arrival_time_dist.size(); ++i) {
1051  arrival_time_dist[i] = ParPropTimeTF1[OpChannel].GetRandom();
1052  }
1053  }
1054  else if (fPVS->IncludePropTime()) {
1055  // Get VUV photons arrival time distribution from the parametrization
1056  geo::Point_t const& opDetCenter = fOpDetCenter.at(OpChannel);
1057  if (!Reflected) {
1058  const G4ThreeVector OpDetPoint(
1059  opDetCenter.X() * CLHEP::cm, opDetCenter.Y() * CLHEP::cm, opDetCenter.Z() * CLHEP::cm);
1060  double distance_in_cm = (x0 - OpDetPoint).mag() / CLHEP::cm; // this must be in CENTIMETERS!
1061  double cosine = std::abs(x0[0]*CLHEP::cm - OpDetPoint[0]*CLHEP::cm) / distance_in_cm;
1062  double theta = fast_acos(cosine)*180./CLHEP::pi;
1063  int angle_bin = theta/fangle_bin_timing_vuv;
1064  getVUVTimes(arrival_time_dist, distance_in_cm, angle_bin); // in ns
1065  }
1066  else {
1067  TVector3 const ScintPoint(x0[0] / CLHEP::cm, x0[1] / CLHEP::cm, x0[2] / CLHEP::cm); // in cm
1068  getVISTimes(arrival_time_dist, ScintPoint, geo::vect::toTVector3(opDetCenter)); // in ns
1069  }
1070  }
1071  }
1072 
1073  G4double
1074  OpFastScintillation::sample_time(const G4double tau1, const G4double tau2) const
1075  {
1076  // tau1: rise time and tau2: decay time
1077  while (1) {
1078  // two random numbers
1079  G4double ran1 = G4UniformRand();
1080  G4double ran2 = G4UniformRand();
1081  //
1082  // exponential distribution as envelope function: very efficient
1083  //
1084  G4double d = (tau1 + tau2) / tau2;
1085  // make sure the envelope function is
1086  // always larger than the bi-exponential
1087  G4double t = -1.0 * tau2 * std::log(1 - ran1);
1088  G4double g = d * single_exp(t, tau2);
1089  if (ran2 <= bi_exp(t, tau1, tau2) / g) return t;
1090  }
1091  return -1.0;
1092  }
1093 
1094  double
1096  {
1097  return fTPBEm->fire() * ((*(--tpbemission.end())).first - (*tpbemission.begin()).first) +
1098  (*tpbemission.begin()).first;
1099  }
1100 
1101  void
1102  OpFastScintillation::average_position(G4Step const& aStep, double* xyzPos) const
1103  {
1104  CLHEP::Hep3Vector prePoint = (aStep.GetPreStepPoint())->GetPosition();
1105  CLHEP::Hep3Vector postPoint = (aStep.GetPostStepPoint())->GetPosition();
1106  xyzPos[0] = (((prePoint.getX() + postPoint.getX()) / 2.0) / CLHEP::cm);
1107  xyzPos[1] = (((prePoint.getY() + postPoint.getY()) / 2.0) / CLHEP::cm);
1108  xyzPos[2] = (((prePoint.getZ() + postPoint.getZ()) / 2.0) / CLHEP::cm);
1109  }
1110 
1111  // ======TIMING PARAMETRIZATION===== //
1112  /*
1113  // Parametrization of the VUV light timing (result from direct transport + Rayleigh scattering ONLY)
1114  // using a landau + expo function.The function below returns the arrival time distribution given the
1115  // distance IN CENTIMETERS between the scintillation/ionization point and the optical detectotr.
1116  std::vector<double> OpFastScintillation::GetVUVTime(double distance, int number_photons) {
1117 
1118  //-----Distances in cm and times in ns-----//
1119  //gRandom->SetSeed(0);
1120  std::vector<double> arrival_time_distrb;
1121  arrival_time_distrb.clear();
1122 
1123  // Parametrization data:
1124  if(distance < 10 || distance > fd_max) {
1125  G4cout<<"WARNING: Parametrization of Direct Light not fully reliable"<<G4endl;
1126  G4cout<<"Too close/far to the PMT -> set 0 VUV photons(?)!!!!!!"<<G4endl;
1127  return arrival_time_distrb;
1128  }
1129  //signals (remember this is transportation) no longer than 1us
1130  const double signal_t_range = 1000.;
1131  const double vuv_vgroup = 10.13;//cm/ns
1132  double t_direct = distance/vuv_vgroup;
1133  // Defining the two functions (Landau + Exponential) describing the timing vs distance
1134  double pars_landau[3] = {functions_vuv[1]->Eval(distance), functions_vuv[2]->Eval(distance),
1135  std::pow(10.,functions_vuv[0]->Eval(distance))};
1136  if(distance > fd_break) {
1137  pars_landau[0]=functions_vuv[6]->Eval(distance);
1138  pars_landau[1]=functions_vuv[2]->Eval(fd_break);
1139  pars_landau[2]=std::pow(10.,functions_vuv[5]->Eval(distance));
1140  }
1141  TF1 *flandau = new TF1("flandau","[2]*TMath::Landau(x,[0],[1])",0,signal_t_range/2);
1142  flandau->SetParameters(pars_landau);
1143  double pars_expo[2] = {functions_vuv[3]->Eval(distance), functions_vuv[4]->Eval(distance)};
1144  if(distance > (fd_break - 50.)) {
1145  pars_expo[0] = functions_vuv[7]->Eval(distance);
1146  pars_expo[1] = functions_vuv[4]->Eval(fd_break - 50.);
1147  }
1148  TF1 *fexpo = new TF1("fexpo","expo",0, signal_t_range/2);
1149  fexpo->SetParameters(pars_expo);
1150  //this is to find the intersection point between the two functions:
1151  TF1 *fint = new TF1("fint","finter_d",flandau->GetMaximumX(),3*t_direct,5);
1152  double parsInt[5] = {pars_landau[0], pars_landau[1], pars_landau[2], pars_expo[0], pars_expo[1]};
1153  fint->SetParameters(parsInt);
1154  double t_int = fint->GetMinimumX();
1155  double minVal = fint->Eval(t_int);
1156  //the functions must intersect!!!
1157  if(minVal>0.015)
1158  G4cout<<"WARNING: Parametrization of Direct Light discontinuous (landau + expo)!!!!!!"<<G4endl;
1159 
1160  TF1 *fVUVTiming = new TF1("fTiming","LandauPlusExpoFinal",0,signal_t_range,6);
1161  double parsfinal[6] = {t_int, pars_landau[0], pars_landau[1], pars_landau[2], pars_expo[0], pars_expo[1]};
1162  fVUVTiming->SetParameters(parsfinal);
1163  // Set the number of points used to sample the function
1164 
1165  int f_sampling = 1000;
1166  if(distance < 50)
1167  f_sampling *= ftf1_sampling_factor;
1168  fVUVTiming->SetNpx(f_sampling);
1169 
1170  for(int i=0; i<number_photons; i++)
1171  arrival_time_distrb.push_back(fVUVTiming->GetRandom());
1172 
1173  //deleting ...
1174  delete flandau;
1175  delete fexpo;
1176  delete fint;
1177  delete fVUVTiming;
1178  //G4cout<<"BEAMAUS timing distribution hecha"<<G4endl;
1179  return arrival_time_distrb;
1180  }
1181 
1182  // Parametrization of the Visible light timing (result from direct transport + Rayleigh scattering ONLY)
1183  // using a landau + exponential function. The function below returns the arrival time distribution given the
1184  // time of the first visible photon in the PMT. The light generated has been reflected by the cathode ONLY.
1185  std::vector<double> OpFastScintillation::GetVisibleTimeOnlyCathode(double t0, int number_photons) {
1186  //-----Distances in cm and times in ns-----//
1187  //gRandom->SetSeed(0);
1188 
1189  std::vector<double> arrival_time_distrb;
1190  arrival_time_distrb.clear();
1191  // Parametrization data:
1192 
1193  if(t0 < 8 || t0 > ft0_max) {
1194  G4cout<<"WARNING: Parametrization of Cathode-Only reflected Light not fully reliable"<<G4endl;
1195  G4cout<<"Too close/far to the PMT -> set 0 Visible photons(?)!!!!!!"<<G4endl;
1196  return arrival_time_distrb;
1197  }
1198  //signals (remember this is transportation) no longer than 1us
1199  const double signal_t_range = 1000.;
1200  double pars_landau[3] = {functions_vis[1]->Eval(t0), functions_vis[2]->Eval(t0),
1201  std::pow(10.,functions_vis[0]->Eval(t0))};
1202  double pars_expo[2] = {functions_vis[3]->Eval(t0), functions_vis[4]->Eval(t0)};
1203  if(t0 > ft0_break_point) {
1204  pars_landau[0] = -0.798934 + 1.06216*t0;
1205  pars_landau[1] = functions_vis[2]->Eval(ft0_break_point);
1206  pars_landau[2] = std::pow(10.,functions_vis[0]->Eval(ft0_break_point));
1207  pars_expo[0] = functions_vis[3]->Eval(ft0_break_point);
1208  pars_expo[1] = functions_vis[4]->Eval(ft0_break_point);
1209  }
1210 
1211  // Defining the two functions (Landau + Exponential) describing the timing vs t0
1212  TF1 *flandau = new TF1("flandau","[2]*TMath::Landau(x,[0],[1])",0,signal_t_range/2);
1213  flandau->SetParameters(pars_landau);
1214  TF1 *fexpo = new TF1("fexpo","expo",0, signal_t_range/2);
1215  fexpo->SetParameters(pars_expo);
1216  //this is to find the intersection point between the two functions:
1217  TF1 *fint = new TF1("fint",finter_d,flandau->GetMaximumX(),2*t0,5);
1218  double parsInt[5] = {pars_landau[0], pars_landau[1], pars_landau[2], pars_expo[0], pars_expo[1]};
1219  fint->SetParameters(parsInt);
1220  double t_int = fint->GetMinimumX();
1221  double minVal = fint->Eval(t_int);
1222  //the functions must intersect!!!
1223  if(minVal>0.015)
1224  G4cout<<"WARNING: Parametrization of Direct Light discontinuous (landau + expo)!!!!!!"<<G4endl;
1225 
1226  TF1 *fVisTiming = new TF1("fTiming",LandauPlusExpoFinal,0,signal_t_range,6);
1227  double parsfinal[6] = {t_int, pars_landau[0], pars_landau[1], pars_landau[2], pars_expo[0], pars_expo[1]};
1228  fVisTiming->SetParameters(parsfinal);
1229  // Set the number of points used to sample the function
1230 
1231  int f_sampling = 1000;
1232  if(t0 < 20)
1233  f_sampling *= ftf1_sampling_factor;
1234  fVisTiming->SetNpx(f_sampling);
1235 
1236  for(int i=0; i<number_photons; i++)
1237  arrival_time_distrb.push_back(fVisTiming->GetRandom());
1238 
1239  //deleting ...
1240 
1241  delete flandau;
1242  delete fexpo;
1243  delete fint;
1244  delete fVisTiming;
1245  //G4cout<<"Timing distribution BEAMAUS!"<<G4endl;
1246  return arrival_time_distrb;
1247  }*/
1248 
1249  // New Parametrization code
1250  // parameterisation generation function
1251  void
1252  OpFastScintillation::generateParam(const size_t index, const size_t angle_bin)
1253  {
1254  // get distance
1255  double distance_in_cm = (index * fstep_size) + fmin_d;
1256 
1257  // time range
1258  const double signal_t_range = 5000.; // TODO: unhardcode
1259 
1260  // parameterisation TF1
1261  TF1 fVUVTiming;
1262 
1263  // For very short distances the time correction is just a shift
1264  double t_direct_mean = distance_in_cm / fvuv_vgroup_mean;
1265  double t_direct_min = distance_in_cm / fvuv_vgroup_max;
1266 
1267  // Defining the model function(s) describing the photon transportation timing vs distance
1268  // Getting the landau parameters from the time parametrization
1269  std::array<double, 3> pars_landau;
1270  interpolate3(pars_landau,
1271  fparameters[0][0],
1272  fparameters[2][angle_bin],
1273  fparameters[3][angle_bin],
1274  fparameters[1][angle_bin],
1275  distance_in_cm,
1276  true);
1277  // Deciding which time model to use (depends on the distance)
1278  // defining useful times for the VUV arrival time shapes
1279  if (distance_in_cm >= finflexion_point_distance) {
1280  double pars_far[4] = {t_direct_min, pars_landau[0], pars_landau[1], pars_landau[2]};
1281  // Set model: Landau
1282  fVUVTiming = TF1("fVUVTiming", model_far, 0, signal_t_range, 4);
1283  fVUVTiming.SetParameters(pars_far);
1284  }
1285  else {
1286  // Set model: Landau + Exponential
1287  fVUVTiming = TF1("fVUVTiming", model_close, 0, signal_t_range, 7);
1288  // Exponential parameters
1289  double pars_expo[2];
1290  // Getting the exponential parameters from the time parametrization
1291  pars_expo[1] = interpolate(fparameters[4][0], fparameters[5][angle_bin], distance_in_cm, true);
1292  pars_expo[0] = interpolate(fparameters[4][0], fparameters[6][angle_bin], distance_in_cm, true);
1293  pars_expo[0] *= pars_landau[2];
1294  pars_expo[0] = std::log(pars_expo[0]);
1295  // this is to find the intersection point between the two functions:
1296  TF1 fint = TF1("fint", finter_d, pars_landau[0], 4 * t_direct_mean, 5);
1297  double parsInt[5] = {
1298  pars_landau[0], pars_landau[1], pars_landau[2], pars_expo[0], pars_expo[1]};
1299  fint.SetParameters(parsInt);
1300  double t_int = fint.GetMinimumX();
1301  double minVal = fint.Eval(t_int);
1302  // the functions must intersect - output warning if they don't
1303  if (minVal > 0.015) {
1304  std::cout << "WARNING: Parametrization of VUV light discontinuous for distance = "
1305  << distance_in_cm << std::endl;
1306  std::cout << "WARNING: This shouldn't be happening " << std::endl;
1307  }
1308  double parsfinal[7] = {t_int,
1309  pars_landau[0],
1310  pars_landau[1],
1311  pars_landau[2],
1312  pars_expo[0],
1313  pars_expo[1],
1314  t_direct_min};
1315  fVUVTiming.SetParameters(parsfinal);
1316  }
1317 
1318  // set the number of points used to sample parameterisation
1319  // for shorter distances, peak is sharper so more sensitive sampling required
1320  int fsampling; // TODO: unhardcode
1321  if (distance_in_cm < 50) { fsampling = 10000; }
1322  else if (distance_in_cm < 100) {
1323  fsampling = 5000;
1324  }
1325  else {
1326  fsampling = 1000;
1327  }
1328  fVUVTiming.SetNpx(fsampling);
1329 
1330  // calculate max and min distance relevant to sample parameterisation
1331  // max
1332  const size_t nq_max = 1;
1333  double xq_max[nq_max];
1334  double yq_max[nq_max];
1335  xq_max[0] = 0.995; // include 99.5%
1336  fVUVTiming.GetQuantiles(nq_max, yq_max, xq_max);
1337  double max = yq_max[0];
1338  // min
1339  double min = t_direct_min;
1340 
1341  // store TF1 and min/max, this allows identical TF1 to be used every time sampling
1342  // the first call of GetRandom generates the timing sampling and stores it in the TF1 object, this is the slow part
1343  // all subsequent calls check if it has been generated previously and are ~100+ times quicker
1344  VUV_timing[angle_bin][index] = fVUVTiming;
1345  VUV_max[angle_bin][index] = max;
1346  VUV_min[angle_bin][index] = min;
1347  }
1348 
1349  // VUV arrival times calculation function
1350  void
1351  OpFastScintillation::getVUVTimes(std::vector<double>& arrivalTimes, const double distance, const size_t angle_bin)
1352  {
1353  if (distance < fmin_d) {
1354  // times are fixed shift i.e. direct path only
1355  double t_prop_correction = distance / fvuv_vgroup_mean;
1356  for (size_t i = 0; i < arrivalTimes.size(); ++i) {
1357  arrivalTimes[i] = t_prop_correction;
1358  }
1359  }
1360  else { // distance >= fmin_d
1361  // determine nearest parameterisation in discretisation
1362  int index = std::round((distance - fmin_d) / fstep_size);
1363  // check whether required parameterisation has been generated, generating if not
1364  if (VUV_timing[angle_bin][index].GetNdim() == 0) { generateParam(index, angle_bin); }
1365  // randomly sample parameterisation for each photon
1366  for (size_t i = 0; i < arrivalTimes.size(); ++i) {
1367  arrivalTimes[i] = VUV_timing[angle_bin][index].GetRandom(VUV_min[angle_bin][index], VUV_max[angle_bin][index]);
1368  }
1369  }
1370  }
1371 
1372  // VIS arrival times calculation functions
1373  void
1374  OpFastScintillation::getVISTimes(std::vector<double>& arrivalTimes,
1375  const TVector3 &ScintPoint,
1376  const TVector3 &OpDetPoint)
1377  {
1378  // *************************************************************************************************
1379  // Calculation of earliest arrival times and corresponding unsmeared distribution
1380  // *************************************************************************************************
1381 
1382  // set plane_depth for correct TPC:
1383  double plane_depth;
1384  if (ScintPoint[0] < 0) { plane_depth = -fplane_depth; }
1385  else {
1386  plane_depth = fplane_depth;
1387  }
1388 
1389  // calculate point of reflection for shortest path
1390  TVector3 bounce_point(plane_depth,ScintPoint[1],ScintPoint[2]);
1391 
1392  // calculate distance travelled by VUV light and by vis light
1393  double VUVdist = (bounce_point - ScintPoint).Mag();
1394  double Visdist = (OpDetPoint - bounce_point).Mag();
1395 
1396  // calculate times taken by VUV part of path
1397  int angle_bin_vuv = 0; // on-axis by definition
1398  getVUVTimes(arrivalTimes, VUVdist, angle_bin_vuv);
1399 
1400  // add visible direct path transport time
1401  for (size_t i = 0; i < arrivalTimes.size(); ++i) {
1402  arrivalTimes[i] += Visdist / fvis_vmean;
1403  }
1404 
1405  // *************************************************************************************************
1406  // Smearing of arrival time distribution
1407  // *************************************************************************************************
1408  // calculate fastest time possible
1409  // vis part
1410  double vis_time = Visdist / fvis_vmean;
1411  // vuv part
1412  double vuv_time;
1413  if (VUVdist < fmin_d) {
1414  vuv_time = VUVdist / fvuv_vgroup_max;
1415  }
1416  else {
1417  // find index of required parameterisation
1418  const size_t index = std::round((VUVdist - fmin_d) / fstep_size);
1419  // find shortest time
1420  vuv_time = VUV_min[angle_bin_vuv][index];
1421  }
1422  // sum
1423  double fastest_time = vis_time + vuv_time;
1424 
1425  // calculate angle theta between bound_point and optical detector
1426  double cosine_theta = std::abs(OpDetPoint[0] - bounce_point[0]) / Visdist;
1427  double theta = fast_acos(cosine_theta) * 180. / CLHEP::pi;
1428 
1429  // determine smearing parameters using interpolation of generated points:
1430  // 1). tau = exponential smearing factor, varies with distance and angle
1431  // 2). cutoff = largest smeared time allowed, preventing excessively large
1432  // times caused by exponential distance to cathode
1433  double distance_cathode_plane = std::abs(plane_depth - ScintPoint[0]);
1434  // angular bin
1435  size_t theta_bin = theta / fangle_bin_timing_vis;
1436  // radial distance from centre of TPC (y,z plane)
1437  double r = std::sqrt(std::pow(ScintPoint[1] - fcathode_centre[1], 2) + std::pow(ScintPoint[2] - fcathode_centre[2], 2));
1438 
1439  // cut-off and tau
1440  // cut-off
1441  // interpolate in d_c for each r bin
1442  std::vector<double> interp_vals(fcut_off_pars[theta_bin].size(), 0.0);
1443  for (size_t i = 0; i < fcut_off_pars[theta_bin].size(); i++){
1444  interp_vals[i] = interpolate(fdistances_refl, fcut_off_pars[theta_bin][i], distance_cathode_plane, true);
1445  }
1446  // interpolate in r
1447  double cutoff = interpolate(fradial_distances_refl, interp_vals, r, true);
1448 
1449  // tau
1450  // interpolate in x for each r bin
1451  std::vector<double> interp_vals_tau(ftau_pars[theta_bin].size(), 0.0);
1452  for (size_t i = 0; i < ftau_pars[theta_bin].size(); i++){
1453  interp_vals_tau[i] = interpolate(fdistances_refl, ftau_pars[theta_bin][i], distance_cathode_plane, true);
1454  }
1455  // interpolate in r
1456  double tau = interpolate(fradial_distances_refl, interp_vals_tau, r, true);
1457 
1458  if (tau < 0) { tau = 0; } // failsafe if tau extrapolate goes wrong
1459 
1460  // apply smearing:
1461  for (size_t i = 0; i < arrivalTimes.size(); ++i) {
1462  double arrival_time_smeared;
1463  // if time is already greater than cutoff, do not apply smearing
1464  if (arrivalTimes[i] >= cutoff) { continue; }
1465  // otherwise smear
1466  else {
1467  unsigned int counter = 0;
1468  // loop until time generated is within cutoff limit
1469  // most are within single attempt, very few take more than two
1470  do {
1471  // don't attempt smearings too many times
1472  if (counter >= 10) { // TODO: unhardcode
1473  arrival_time_smeared = arrivalTimes[i]; // don't smear
1474  break;
1475  }
1476  else {
1477  // generate random number in appropriate range
1478  double x = gRandom->Uniform(0.5, 1.0); // TODO: unhardcode
1479  // apply the exponential smearing
1480  arrival_time_smeared =
1481  arrivalTimes[i] + (arrivalTimes[i] - fastest_time) * (std::pow(x, -tau) - 1);
1482  }
1483  counter++;
1484  } while (arrival_time_smeared > cutoff);
1485  }
1486  arrivalTimes[i] = arrival_time_smeared;
1487  }
1488  }
1489 
1490  // ---------------------------------------------------------------------------
1491  bool
1493  {
1494  return fUseNhitsModel;
1495  } // OpFastScintillation::usesSemiAnalyticModel()
1496 
1497  // ---------------------------------------------------------------------------
1498  void
1499  OpFastScintillation::detectedDirectHits(std::map<size_t, int>& DetectedNum,
1500  const double Num,
1501  geo::Point_t const& ScintPoint) const
1502  {
1503  for (size_t const OpDet : util::counter(fPVS->NOpChannels())) {
1504  if (!isOpDetInSameTPC(ScintPoint, fOpDetCenter.at(OpDet))) continue;
1505 
1506  // set detector struct for solid angle function
1508  fOpDetHeight.at(OpDet), fOpDetLength.at(OpDet),
1509  fOpDetCenter.at(OpDet), fOpDetType.at(OpDet)};
1510  const int DetThis = VUVHits(Num, ScintPoint, op);
1511  if (DetThis > 0) {
1512  DetectedNum[OpDet] = DetThis;
1513  // mf::LogInfo("OpFastScintillation") << "FastScint: " <<
1514  // // it->second<<" " << Num << " " << DetThisPMT;
1515  //det_photon_ctr += DetThisPMT; // CASE-DEBUG DO NOT REMOVE THIS COMMENT
1516  }
1517  }
1518  }
1519 
1520  void
1521  OpFastScintillation::detectedReflecHits(std::map<size_t, int>& ReflDetectedNum,
1522  const double Num,
1523  geo::Point_t const& ScintPoint) const
1524  {
1525  // 1). calculate total number of hits of VUV photons on
1526  // reflective foils via solid angle + Gaisser-Hillas
1527  // corrections:
1528 
1529  // set plane_depth for correct TPC:
1530  double plane_depth;
1531  if (ScintPoint.X() < 0.) { plane_depth = -fplane_depth; }
1532  else {
1533  plane_depth = fplane_depth;
1534  }
1535 
1536  // get scintpoint coords relative to centre of cathode plane
1537  std::array<double, 3> ScintPoint_relative = {std::abs(ScintPoint.X() - plane_depth),
1538  std::abs(ScintPoint.Y() - fcathode_centre[1]),
1539  std::abs(ScintPoint.Z() - fcathode_centre[2])};
1540  // calculate solid angle of cathode from the scintillation point
1541  double solid_angle_cathode = Rectangle_SolidAngle(fcathode_plane, ScintPoint_relative);
1542 
1543  // calculate distance and angle between ScintPoint and hotspot
1544  // vast majority of hits in hotspot region directly infront of scintpoint,
1545  // therefore consider attenuation for this distance and on axis GH instead of for the centre coordinate
1546  double distance_cathode = std::abs(plane_depth - ScintPoint.X());
1547  // calculate hits on cathode plane via geometric acceptance
1548  double cathode_hits_geo = std::exp(-1. * distance_cathode / fL_abs_vuv) *
1549  (solid_angle_cathode / (4. * CLHEP::pi)) * Num;
1550  // determine Gaisser-Hillas correction including border effects
1551  // use flat correction
1552  double r = std::sqrt(std::pow(ScintPoint.Y() - fcathode_centre[1], 2) + std::pow(ScintPoint.Z() - fcathode_centre[2], 2));
1553  double pars_ini[4] = {0, 0, 0, 0};
1554  double s1 = 0; double s2 = 0; double s3 = 0;
1555  if(fIsFlatPDCorr) {
1556  pars_ini[0] = fGHvuvpars_flat[0][0];
1557  pars_ini[1] = fGHvuvpars_flat[1][0];
1558  pars_ini[2] = fGHvuvpars_flat[2][0];
1559  pars_ini[3] = fGHvuvpars_flat[3][0];
1563  }
1564  else std::cout << "Error: flat optical detector VUV correction required for reflected semi-analytic hits." << std::endl;
1565 
1566  // add border correction
1567  pars_ini[0] = pars_ini[0] + s1 * r;
1568  pars_ini[1] = pars_ini[1] + s2 * r;
1569  pars_ini[2] = pars_ini[2] + s3 * r;
1570  pars_ini[3] = pars_ini[3];
1571 
1572 
1573  // calculate corrected number of hits
1574  double GH_correction = Gaisser_Hillas(distance_cathode, pars_ini);
1575  const double cathode_hits_rec = GH_correction * cathode_hits_geo;
1576 
1577  // detemine hits on each PD
1578  const std::array<double, 3> hotspot = {plane_depth, ScintPoint.Y(), ScintPoint.Z()};
1579  for (size_t const OpDet : util::counter(fPVS->NOpChannels())) {
1580  if (!isOpDetInSameTPC(ScintPoint, fOpDetCenter.at(OpDet))) continue;
1581 
1582  // set detector struct for solid angle function
1584  fOpDetHeight.at(OpDet), fOpDetLength.at(OpDet),
1585  fOpDetCenter.at(OpDet), fOpDetType.at(OpDet)};
1586 
1587  int const ReflDetThis =
1588  VISHits(ScintPoint, op, cathode_hits_rec, hotspot);
1589  if (ReflDetThis > 0) { ReflDetectedNum[OpDet] = ReflDetThis; }
1590  }
1591  }
1592 
1593  // VUV semi-analytic hits calculation
1594  int
1595  OpFastScintillation::VUVHits(const double Nphotons_created,
1596  geo::Point_t const& ScintPoint_v,
1597  OpticalDetector const& opDet) const
1598  {
1599  // the interface has been converted into geo::Point_t, the implementation not yet
1600  std::array<double, 3U> ScintPoint;
1601  std::array<double, 3U> OpDetPoint;
1602  geo::vect::fillCoords(ScintPoint, ScintPoint_v);
1603  geo::vect::fillCoords(OpDetPoint, opDet.OpDetPoint);
1604 
1605  // distance and angle between ScintPoint and OpDetPoint
1606  double distance = dist(&ScintPoint[0], &OpDetPoint[0], 3);
1607  double cosine = std::abs(ScintPoint[0] - OpDetPoint[0]) / distance;
1608  // double theta = std::acos(cosine) * 180. / CLHEP::pi;
1609  double theta = fast_acos(cosine) * 180. / CLHEP::pi;
1610 
1611  // calculate solid angle:
1612  double solid_angle = 0;
1613  // Arapucas/Bars (rectangle)
1614  if (opDet.type == 0) {
1615  // get scintillation point coordinates relative to arapuca window centre
1616  std::array<double, 3> ScintPoint_rel = {std::abs(ScintPoint[0] - OpDetPoint[0]),
1617  std::abs(ScintPoint[1] - OpDetPoint[1]),
1618  std::abs(ScintPoint[2] - OpDetPoint[2])};
1619  // calculate solid angle
1620  solid_angle = Rectangle_SolidAngle(Dims{opDet.h, opDet.w}, ScintPoint_rel);
1621  }
1622  // PMTs (dome)
1623  else if (opDet.type == 1) {
1624  solid_angle = Omega_Dome_Model(distance, theta);
1625  }
1626  // PMTs (disk approximation)
1627  else if (opDet.type == 2) {
1628  // offset in z-y plane
1629  double d = dist(&ScintPoint[1], &OpDetPoint[1], 2);
1630  // drift distance (in x)
1631  double h = std::abs(ScintPoint[0] - OpDetPoint[0]);
1632  // Solid angle of a disk
1633  solid_angle = Disk_SolidAngle(d, h, fradius);
1634  }
1635  else {
1636  std::cout << "Error: Invalid optical detector type. 0 = rectangular, 1 = dome, 2 = disk" << std::endl;
1637  }
1638 
1639  // calculate number of photons hits by geometric acceptance: accounting for solid angle and LAr absorbtion length
1640  double hits_geo = std::exp(-1. * distance / fL_abs_vuv) * (solid_angle / (4 * CLHEP::pi)) * Nphotons_created;
1641 
1642  // apply Gaisser-Hillas correction for Rayleigh scattering distance
1643  // and angular dependence offset angle bin
1644  const size_t j = (theta / fdelta_angulo_vuv);
1645 
1646  // determine GH parameters, accounting for border effects
1647  // radial distance from centre of detector (Y-Z)
1648  double r = dist(ScintPoint, fcathode_centre, 2, 1);
1649  double pars_ini[4] = {0, 0, 0, 0};
1650  double s1 = 0; double s2 = 0; double s3 = 0;
1651  // flat PDs
1652  if ((opDet.type == 0 || opDet.type == 2) && fIsFlatPDCorr){
1653  pars_ini[0] = fGHvuvpars_flat[0][j];
1654  pars_ini[1] = fGHvuvpars_flat[1][j];
1655  pars_ini[2] = fGHvuvpars_flat[2][j];
1656  pars_ini[3] = fGHvuvpars_flat[3][j];
1657  s1 = interpolate( fborder_corr_angulo_flat, fborder_corr_flat[0], theta, true);
1658  s2 = interpolate( fborder_corr_angulo_flat, fborder_corr_flat[1], theta, true);
1659  s3 = interpolate( fborder_corr_angulo_flat, fborder_corr_flat[2], theta, true);
1660  }
1661  // dome PDs
1662  else if (opDet.type == 1 && fIsDomePDCorr) {
1663  pars_ini[0] = fGHvuvpars_dome[0][j];
1664  pars_ini[1] = fGHvuvpars_dome[1][j];
1665  pars_ini[2] = fGHvuvpars_dome[2][j];
1666  pars_ini[3] = fGHvuvpars_dome[3][j];
1667  s1 = interpolate( fborder_corr_angulo_dome, fborder_corr_dome[0], theta, true);
1668  s2 = interpolate( fborder_corr_angulo_dome, fborder_corr_dome[1], theta, true);
1669  s3 = interpolate( fborder_corr_angulo_dome, fborder_corr_dome[2], theta, true);
1670  }
1671  else std::cout << "Error: Invalid optical detector type. 0 = rectangular, 1 = dome, 2 = disk. Or corrections for chosen optical detector type missing." << std::endl;
1672 
1673  // add border correction
1674  pars_ini[0] = pars_ini[0] + s1 * r;
1675  pars_ini[1] = pars_ini[1] + s2 * r;
1676  pars_ini[2] = pars_ini[2] + s3 * r;
1677  pars_ini[3] = pars_ini[3];
1678 
1679  // calculate correction
1680  double GH_correction = Gaisser_Hillas(distance, pars_ini);
1681 
1682  // calculate number photons
1683  double hits_rec = GH_correction * hits_geo / cosine;
1684  int hits_vuv = std::round(G4Poisson(hits_rec));
1685 
1686  return hits_vuv;
1687  }
1688 
1689  // VIS hits semi-analytic model calculation
1690  int
1692  OpticalDetector const& opDet,
1693  const double cathode_hits_rec,
1694  const std::array<double, 3> hotspot) const
1695  {
1696  // 1). calculate total number of hits of VUV photons on reflective
1697  // foils via solid angle + Gaisser-Hillas corrections.
1698  // Done outside as it doesn't depend on OpDetPoint
1699 
1700  // set plane_depth for correct TPC:
1701  double plane_depth;
1702  if (ScintPoint_v.X() < 0.) { plane_depth = -fplane_depth; }
1703  else {
1704  plane_depth = fplane_depth;
1705  }
1706 
1707  // 2). calculate number of these hits which reach the optical
1708  // detector from the hotspot via solid angle
1709 
1710  // the interface has been converted into geo::Point_t, the implementation not yet
1711  std::array<double, 3U> ScintPoint;
1712  std::array<double, 3U> OpDetPoint;
1713  geo::vect::fillCoords(ScintPoint, ScintPoint_v);
1714  geo::vect::fillCoords(OpDetPoint, opDet.OpDetPoint);
1715 
1716  // calculate distances and angles for application of corrections
1717  // distance from hotspot to optical detector
1718  double distance_vis = dist(&hotspot[0], &OpDetPoint[0], 3);
1719  // angle between hotspot and optical detector
1720  double cosine_vis = std::abs(hotspot[0] - OpDetPoint[0]) / distance_vis;
1721  // double theta_vis = std::acos(cosine_vis) * 180. / CLHEP::pi;
1722  double theta_vis = fast_acos(cosine_vis) * 180. / CLHEP::pi;
1723 
1724  // calculate solid angle of optical detector
1725  double solid_angle_detector = 0;
1726  // rectangular aperture
1727  if (opDet.type == 0) {
1728  // get hotspot coordinates relative to opDet
1729  std::array<double, 3> emission_relative = {std::abs(hotspot[0] - OpDetPoint[0]),
1730  std::abs(hotspot[1] - OpDetPoint[1]),
1731  std::abs(hotspot[2] - OpDetPoint[2])};
1732  // calculate solid angle
1733  solid_angle_detector = Rectangle_SolidAngle(Dims{opDet.h, opDet.w}, emission_relative);
1734  }
1735  // dome aperture
1736  else if (opDet.type == 1) {
1737  solid_angle_detector = Omega_Dome_Model(distance_vis, theta_vis);
1738  }
1739  // disk aperture
1740  else if (opDet.type == 2) {
1741  // offset in z-y plane
1742  double d = dist(&hotspot[1], &OpDetPoint[1], 2);
1743  // drift distance (in x)
1744  double h = std::abs(hotspot[0] - OpDetPoint[0]);
1745  // calculate solid angle
1746  solid_angle_detector = Disk_SolidAngle(d, h, fradius);
1747  }
1748  else {
1749  std::cout << "Error: Invalid optical detector type. 0 = rectangular, 1 = dome, 2 = disk" << std::endl;
1750  }
1751 
1752  // calculate number of hits via geometeric acceptance
1753  double hits_geo = (solid_angle_detector / (2. * CLHEP::pi)) * cathode_hits_rec; // 2*pi due to presence of reflective foils
1754 
1755  // determine correction factor, depending on PD type
1756  const size_t k = (theta_vis / fdelta_angulo_vis); // off-set angle bin
1757  double r = dist(ScintPoint, fcathode_centre, 2, 1); // radial distance from centre of detector (Y-Z)
1758  double d_c = std::abs(ScintPoint[0] - plane_depth); // distance to cathode
1759  double border_correction = 0;
1760  // flat PDs
1761  if ((opDet.type == 0 || opDet.type == 2) && fIsFlatPDCorr){
1762  // interpolate in d_c for each r bin
1763  const size_t nbins_r = fvispars_flat[k].size();
1764  std::vector<double> interp_vals(nbins_r, 0.0);
1765  {
1766  size_t idx = 0;
1767  size_t size = fvis_distances_x_flat.size();
1768  if (d_c >= fvis_distances_x_flat[size - 2])
1769  idx = size - 2;
1770  else {
1771  while (d_c > fvis_distances_x_flat[idx + 1])
1772  idx++;
1773  }
1774  for (size_t i = 0; i < nbins_r; ++i) {
1775  interp_vals[i] = interpolate(fvis_distances_x_flat,
1776  fvispars_flat[k][i],
1777  d_c,
1778  false,
1779  idx);
1780  }
1781  }
1782  // interpolate in r
1783  border_correction = interpolate(fvis_distances_r_flat, interp_vals, r, false);
1784  }
1785  // dome PDs
1786  else if (opDet.type == 1 && fIsDomePDCorr) {
1787  // interpolate in d_c for each r bin
1788  const size_t nbins_r = fvispars_dome[k].size();
1789  std::vector<double> interp_vals(nbins_r, 0.0);
1790  {
1791  size_t idx = 0;
1792  size_t size = fvis_distances_x_dome.size();
1793  if (d_c >= fvis_distances_x_dome[size - 2])
1794  idx = size - 2;
1795  else {
1796  while (d_c > fvis_distances_x_dome[idx + 1])
1797  idx++;
1798  }
1799  for (size_t i = 0; i < nbins_r; ++i) {
1800  interp_vals[i] = interpolate(fvis_distances_x_dome,
1801  fvispars_dome[k][i],
1802  d_c,
1803  false,
1804  idx);
1805  }
1806  }
1807  // interpolate in r
1808  border_correction = interpolate(fvis_distances_r_dome, interp_vals, r, false);
1809  }
1810  else {
1811  std::cout << "Error: Invalid optical detector type. 0 = rectangular, 1 = dome, 2 = disk. Or corrections for chosen optical detector type missing." << std::endl;
1812  }
1813 
1814  // apply correction factor
1815  double hits_rec = border_correction * hits_geo / cosine_vis;
1816  double hits_vis = std::round(G4Poisson(hits_rec));
1817 
1818  return hits_vis;
1819  }
1820 
1821  bool
1823  geo::Point_t const& OpDetPoint) const
1824  {
1825  // check optical channel is in same TPC as scintillation light, if not return 0 hits
1826  // temporary method working for SBND, uBooNE, DUNE 1x2x6; to be replaced to work in full DUNE geometry
1827  // check x coordinate has same sign or is close to zero, otherwise return 0 hits
1828  if (((ScintPoint.X() < 0.) != (OpDetPoint.X() < 0.)) &&
1829  std::abs(OpDetPoint.X()) > 10.) { // TODO: unhardcode
1830  return false;
1831  }
1832  return true;
1833  }
1834 
1835  bool
1837  {
1838  //semi-analytic approach only works in the active volume
1839  return fActiveVolumes[0].ContainsPosition(ScintPoint);
1840  }
1841 
1842  G4double
1843  OpFastScintillation::single_exp(const G4double t, const G4double tau2) const
1844  {
1845  return std::exp(-1.0 * t / tau2) / tau2;
1846  }
1847 
1848  G4double
1849  OpFastScintillation::bi_exp(const G4double t, const G4double tau1, const G4double tau2) const
1850  { // TODO: what's up with this? ... / tau2 / tau2 ...
1851  return std::exp(-1.0 * t / tau2) * (1 - std::exp(-1.0 * t / tau1)) / tau2 / tau2 *
1852  (tau1 + tau2);
1853  }
1854 
1855  G4double
1856  OpFastScintillation::Gaisser_Hillas(const double x, const double* par) const
1857  {
1858  double X_mu_0 = par[3];
1859  double Normalization = par[0];
1860  double Diff = par[1] - X_mu_0;
1861  double Term = std::pow((x - X_mu_0) / Diff, Diff / par[2]);
1862  double Exponential = std::exp((par[1] - x) / par[2]);
1863  return (Normalization * Term * Exponential);
1864  }
1865 
1866  double
1867  finter_d(double* x, double* par)
1868  {
1869  double y1 = par[2] * TMath::Landau(x[0], par[0], par[1]);
1870  double y2 = TMath::Exp(par[3] + x[0] * par[4]);
1871  return TMath::Abs(y1 - y2);
1872  }
1873 
1874  double
1875  LandauPlusExpoFinal(double* x, double* par)
1876  {
1877  // par0 = joining point
1878  // par1 = Landau MPV
1879  // par2 = Landau widt
1880  // par3 = normalization
1881  // par4 = Expo cte
1882  // par5 = Expo tau
1883  double y1 = par[3] * TMath::Landau(x[0], par[1], par[2]);
1884  double y2 = TMath::Exp(par[4] + x[0] * par[5]);
1885  if (x[0] > par[0]) y1 = 0.;
1886  if (x[0] < par[0]) y2 = 0.;
1887  return (y1 + y2);
1888  }
1889 
1890  double
1891  finter_r(double* x, double* par)
1892  {
1893  double y1 = par[2] * TMath::Landau(x[0], par[0], par[1]);
1894  double y2 = par[5] * TMath::Landau(x[0], par[3], par[4]);
1895  return TMath::Abs(y1 - y2);
1896  }
1897 
1898  double
1899  model_close(double* x, double* par)
1900  {
1901  // par0 = joining point
1902  // par1 = Landau MPV
1903  // par2 = Landau width
1904  // par3 = normalization
1905  // par4 = Expo cte
1906  // par5 = Expo tau
1907  // par6 = t_min
1908  double y1 = par[3] * TMath::Landau(x[0], par[1], par[2]);
1909  double y2 = TMath::Exp(par[4] + x[0] * par[5]);
1910  if (x[0] <= par[6] || x[0] > par[0]) y1 = 0.;
1911  if (x[0] < par[0]) y2 = 0.;
1912  return (y1 + y2);
1913  }
1914 
1915  double
1916  model_far(double* x, double* par)
1917  {
1918  // par1 = Landau MPV
1919  // par2 = Landau width
1920  // par3 = normalization
1921  // par0 = t_min
1922  double y = par[3] * TMath::Landau(x[0], par[1], par[2]);
1923  if (x[0] <= par[0]) y = 0.;
1924  return y;
1925  }
1926 
1927  //======================================================================
1928  // Returns interpolated value at x from parallel arrays ( xData, yData )
1929  // Assumes that xData has at least two elements, is sorted and is strictly monotonic increasing
1930  // boolean argument extrapolate determines behaviour beyond ends of array (if needed)
1931  double
1932  OpFastScintillation::interpolate(const std::vector<double>& xData,
1933  const std::vector<double>& yData,
1934  double x,
1935  bool extrapolate,
1936  size_t i) const
1937  {
1938  if (i == 0) {
1939  size_t size = xData.size();
1940  if (x >= xData[size - 2]) { // special case: beyond right end
1941  i = size - 2;
1942  }
1943  else {
1944  while (x > xData[i + 1])
1945  i++;
1946  }
1947  }
1948  double xL = xData[i];
1949  double xR = xData[i + 1];
1950  double yL = yData[i];
1951  double yR = yData[i + 1]; // points on either side (unless beyond ends)
1952  if (!extrapolate) { // if beyond ends of array and not extrapolating
1953  if (x < xL) return yL;
1954  if (x > xR) return yL;
1955  }
1956  const double dydx = (yR - yL) / (xR - xL); // gradient
1957  return yL + dydx * (x - xL); // linear interpolation
1958  }
1959 
1960  void
1961  OpFastScintillation::interpolate3(std::array<double, 3>& inter,
1962  const std::vector<double>& xData,
1963  const std::vector<double>& yData1,
1964  const std::vector<double>& yData2,
1965  const std::vector<double>& yData3,
1966  double x,
1967  bool extrapolate) const
1968  {
1969  size_t size = xData.size();
1970  size_t i = 0; // find left end of interval for interpolation
1971  if (x >= xData[size - 2]) { // special case: beyond right end
1972  i = size - 2;
1973  }
1974  else {
1975  while (x > xData[i + 1])
1976  i++;
1977  }
1978  double xL = xData[i];
1979  double xR = xData[i + 1]; // points on either side (unless beyond ends)
1980  double yL1 = yData1[i];
1981  double yR1 = yData1[i + 1];
1982  double yL2 = yData2[i];
1983  double yR2 = yData2[i + 1];
1984  double yL3 = yData3[i];
1985  double yR3 = yData3[i + 1];
1986 
1987  if (!extrapolate) { // if beyond ends of array and not extrapolating
1988  if (x < xL) {
1989  inter[0] = yL1;
1990  inter[1] = yL2;
1991  inter[2] = yL3;
1992  return;
1993  }
1994  if (x > xR) {
1995  inter[0] = yL1;
1996  inter[1] = yL2;
1997  inter[2] = yL3;
1998  return;
1999  }
2000  }
2001  const double m = (x - xL) / (xR - xL);
2002  inter[0] = m * (yR1 - yL1) + yL1;
2003  inter[1] = m * (yR2 - yL2) + yL2;
2004  inter[2] = m * (yR3 - yL3) + yL3;
2005  }
2006 
2007  // solid angle of circular aperture
2008  // TODO: allow greater tolerance in comparisons, by default its using:
2009  // std::numeric_limits<double>::epsilon(): 2.22045e-16
2010  // that's an unrealistic small number, better setting
2011  // constexpr double tolerance = 0.0000001; // 1 nm
2012  double
2013  OpFastScintillation::Disk_SolidAngle(const double d, const double h, const double b) const
2014  {
2015  if (b <= 0. || d < 0. || h <= 0.) return 0.;
2016  const double leg2 = (b + d) * (b + d);
2017  const double aa = std::sqrt(h * h / (h * h + leg2));
2018  if (isApproximatelyZero(d)) { return 2. * CLHEP::pi * (1. - aa); }
2019  double bb = 2. * std::sqrt(b * d / (h * h + leg2));
2020  double cc = 4. * b * d / leg2;
2021 
2022  if (isDefinitelyGreaterThan(d, b)) {
2023  try {
2024  return 2. * aa *
2025  (std::sqrt(1. - cc) * boost::math::ellint_3(bb, cc, noLDoublePromote()) -
2026  boost::math::ellint_1(bb, noLDoublePromote()));
2027  }
2028  catch (std::domain_error& e) {
2029  if (isApproximatelyEqual(d, b, 1e-9)) {
2030  mf::LogWarning("OpFastScintillation")
2031  << "Elliptic Integral in Disk_SolidAngle() given parameters outside domain."
2032  << "\nbb: " << bb << "\ncc: " << cc << "\nException message: " << e.what()
2033  << "\nRelax condition and carry on.";
2034  return CLHEP::pi - 2. * aa * boost::math::ellint_1(bb, noLDoublePromote());
2035  }
2036  else {
2037  mf::LogError("OpFastScintillation")
2038  << "Elliptic Integral inside Disk_SolidAngle() given parameters outside domain.\n"
2039  << "\nbb: " << bb << "\ncc: " << cc << "Exception message: " << e.what();
2040  return 0.;
2041  }
2042  }
2043  }
2044  if (isDefinitelyLessThan(d, b)) {
2045  try {
2046  return 2. * CLHEP::pi -
2047  2. * aa *
2048  (boost::math::ellint_1(bb, noLDoublePromote()) +
2049  std::sqrt(1. - cc) * boost::math::ellint_3(bb, cc, noLDoublePromote()));
2050  }
2051  catch (std::domain_error& e) {
2052  if (isApproximatelyEqual(d, b, 1e-9)) {
2053  mf::LogWarning("OpFastScintillation")
2054  << "Elliptic Integral in Disk_SolidAngle() given parameters outside domain."
2055  << "\nbb: " << bb << "\ncc: " << cc << "\nException message: " << e.what()
2056  << "\nRelax condition and carry on.";
2057  return CLHEP::pi - 2. * aa * boost::math::ellint_1(bb, noLDoublePromote());
2058  }
2059  else {
2060  mf::LogError("OpFastScintillation")
2061  << "Elliptic Integral inside Disk_SolidAngle() given parameters outside domain.\n"
2062  << "\nbb: " << bb << "\ncc: " << cc << "Exception message: " << e.what();
2063  return 0.;
2064  }
2065  }
2066  }
2067  if (isApproximatelyEqual(d, b)) {
2068  return CLHEP::pi - 2. * aa * boost::math::ellint_1(bb, noLDoublePromote());
2069  }
2070  return 0.;
2071  }
2072 
2073  // solid angle of rectangular aperture
2074  double
2075  OpFastScintillation::Rectangle_SolidAngle(const double a, const double b, const double d) const
2076  {
2077  double aa = a / (2. * d);
2078  double bb = b / (2. * d);
2079  double aux = (1. + aa * aa + bb * bb) / ((1. + aa * aa) * (1. + bb * bb));
2080  // return 4 * std::acos(std::sqrt(aux));
2081  return 4. * fast_acos(std::sqrt(aux));
2082  }
2083 
2084  // TODO: allow greater tolerance in comparisons, see note above on Disk_SolidAngle()
2085  double
2086  OpFastScintillation::Rectangle_SolidAngle(Dims const& o, const std::array<double, 3> v) const
2087  {
2088  // v is the position of the track segment with respect to
2089  // the center position of the arapuca window
2090 
2091  // arapuca plane fixed in x direction
2092  if (isApproximatelyZero(v[1]) && isApproximatelyZero(v[2])) {
2093  return Rectangle_SolidAngle(o.h, o.w, v[0]);
2094  }
2095  if (isDefinitelyGreaterThan(v[1], o.h * .5) && isDefinitelyGreaterThan(v[2], o.w * .5)) {
2096  double A = v[1] - o.h * .5;
2097  double B = v[2] - o.w * .5;
2098  double to_return = (Rectangle_SolidAngle(2. * (A + o.h), 2. * (B + o.w), v[0]) -
2099  Rectangle_SolidAngle(2. * A, 2. * (B + o.w), v[0]) -
2100  Rectangle_SolidAngle(2. * (A + o.h), 2. * B, v[0]) +
2101  Rectangle_SolidAngle(2. * A, 2. * B, v[0])) *
2102  .25;
2103  return to_return;
2104  }
2105  if ((v[1] <= o.h * .5) && (v[2] <= o.w * .5)) {
2106  double A = -v[1] + o.h * .5;
2107  double B = -v[2] + o.w * .5;
2108  double to_return = (Rectangle_SolidAngle(2. * (o.h - A), 2. * (o.w - B), v[0]) +
2109  Rectangle_SolidAngle(2. * A, 2. * (o.w - B), v[0]) +
2110  Rectangle_SolidAngle(2. * (o.h - A), 2. * B, v[0]) +
2111  Rectangle_SolidAngle(2. * A, 2. * B, v[0])) *
2112  .25;
2113  return to_return;
2114  }
2115  if (isDefinitelyGreaterThan(v[1], o.h * .5) && (v[2] <= o.w * .5)) {
2116  double A = v[1] - o.h * .5;
2117  double B = -v[2] + o.w * .5;
2118  double to_return = (Rectangle_SolidAngle(2. * (A + o.h), 2. * (o.w - B), v[0]) -
2119  Rectangle_SolidAngle(2. * A, 2. * (o.w - B), v[0]) +
2120  Rectangle_SolidAngle(2. * (A + o.h), 2. * B, v[0]) -
2121  Rectangle_SolidAngle(2. * A, 2. * B, v[0])) *
2122  .25;
2123  return to_return;
2124  }
2125  if ((v[1] <= o.h * .5) && isDefinitelyGreaterThan(v[2], o.w * .5)) {
2126  double A = -v[1] + o.h * .5;
2127  double B = v[2] - o.w * .5;
2128  double to_return = (Rectangle_SolidAngle(2. * (o.h - A), 2. * (B + o.w), v[0]) -
2129  Rectangle_SolidAngle(2. * (o.h - A), 2. * B, v[0]) +
2130  Rectangle_SolidAngle(2. * A, 2. * (B + o.w), v[0]) -
2131  Rectangle_SolidAngle(2. * A, 2. * B, v[0])) *
2132  .25;
2133  return to_return;
2134  }
2135  // error message if none of these cases, i.e. something has gone wrong!
2136  // std::cout << "Warning: invalid solid angle call." << std::endl;
2137  return 0.;
2138  }
2139 
2140  double OpFastScintillation::Omega_Dome_Model(const double distance, const double theta) const {
2141  // this function calculates the solid angle of a semi-sphere of radius b,
2142  // as a correction to the analytic formula of the on-axix solid angle,
2143  // as we move off-axis an angle theta. We have used 9-angular bins
2144  // with delta_theta width.
2145 
2146  // par0 = Radius correction close
2147  // par1 = Radius correction far
2148  // par2 = breaking distance betwween "close" and "far"
2149 
2150  double par0[9] = {0., 0., 0., 0., 0., 0.597542, 1.00872, 1.46993, 2.04221};
2151  double par1[9] = {0, 0, 0.19569, 0.300449, 0.555598, 0.854939, 1.39166, 2.19141, 2.57732};
2152  const double delta_theta = 10.;
2153  int j = int(theta/delta_theta);
2154  // PMT radius
2155  const double b = fradius; // cm
2156  // distance form which the model parameters break (empirical value)
2157  const double d_break = 5*b; //par2
2158 
2159  if(distance >= d_break) {
2160  double R_apparent_far = b - par1[j];
2161  return (2*CLHEP::pi * (1 - std::sqrt(1 - std::pow(R_apparent_far/distance,2))));
2162 
2163  }
2164  else {
2165  double R_apparent_close = b - par0[j];
2166  return (2*CLHEP::pi * (1 - std::sqrt(1 - std::pow(R_apparent_close/distance,2))));
2167  }
2168 }
2169 
2170  // ---------------------------------------------------------------------------
2171  std::vector<geo::BoxBoundedGeo>
2173  {
2174  std::vector<geo::BoxBoundedGeo> activeVolumes;
2175  activeVolumes.reserve(geom.Ncryostats());
2176 
2177  for (geo::CryostatGeo const& cryo : geom.IterateCryostats()) {
2178 
2179  // can't use it default-constructed since it would always include origin
2180  geo::BoxBoundedGeo box{cryo.TPC(0).ActiveBoundingBox()};
2181 
2182  for (geo::TPCGeo const& TPC : cryo.IterateTPCs())
2183  box.ExtendToInclude(TPC.ActiveBoundingBox());
2184 
2185  activeVolumes.push_back(std::move(box));
2186 
2187  } // for cryostats
2188 
2189  return activeVolumes;
2190  } // OpFastScintillation::extractActiveVolumes()
2191 
2192  // ---------------------------------------------------------------------------
2193 
2194  double
2195  fast_acos(double x)
2196  {
2197  double negate = double(x < 0);
2198  x = std::abs(x);
2199  x -= double(x > 1.0) * (x - 1.0); // <- equivalent to min(1.0,x), but faster
2200  double ret = -0.0187293;
2201  ret = ret * x;
2202  ret = ret + 0.0742610;
2203  ret = ret * x;
2204  ret = ret - 0.2121144;
2205  ret = ret * x;
2206  ret = ret + 1.5707288;
2207  ret = ret * std::sqrt(1.0 - x);
2208  ret = ret - 2. * negate * ret;
2209  return negate * 3.14159265358979 + ret;
2210  }
2211 
2212 }
static constexpr double cm
Definition: Units.h:68
code to link reconstructed objects back to the MC truth information
void LoadTimingsForVISPar(std::vector< double > &distances, std::vector< double > &radial_distances, std::vector< std::vector< std::vector< double >>> &cut_off, std::vector< std::vector< std::vector< double >>> &tau, double &vis_vmean, double &angle_bin_timing_vis) const
Store parameters for running LArG4.
Index OpChannel(Index detNum, Index channel)
MaybeLogger_< ELseverityLevel::ELsev_info, true > LogVerbatim
Utilities to extend the interface of geometry vectors.
std::vector< std::vector< std::vector< double > > > fvispars_dome
void detectedDirectHits(std::map< size_t, int > &DetectedNum, const double Num, geo::Point_t const &ScintPoint) const
OpDetGeo const & OpDetGeoFromOpDet(unsigned int OpDet) const
Returns the geo::OpDetGeo object for the given detector number.
void detectedReflecHits(std::map< size_t, int > &ReflDetectedNum, const double Num, geo::Point_t const &ScintPoint) const
void average_position(G4Step const &aStep, double *xzyPos) const
bool isScintInActiveVolume(geo::Point_t const &ScintPoint)
static constexpr double g
Definition: Units.h:144
std::vector< geo::Point_t > fOpDetCenter
G4double bi_exp(const G4double t, const G4double tau1, const G4double tau2) const
void LoadTimingsForVUVPar(std::vector< std::vector< double >>(&v)[7], double &step_size, double &max_d, double &min_d, double &vuv_vgroup_mean, double &vuv_vgroup_max, double &inflexion_point_distance, double &angle_bin_timing_vuv) const
Definition of util::enumerate().
std::string string
Definition: nybbler.cc:12
G4double Gaisser_Hillas(const double x, const double *par) const
std::vector< double > fOpDetHeight
static std::vector< geo::BoxBoundedGeo > extractActiveVolumes(geo::GeometryCore const &geom)
void propagationTime(std::vector< double > &arrival_time_dist, G4ThreeVector x0, const size_t OpChannel, bool Reflected=false)
bool isBar() const
Returns whether the detector shape is a bar (TGeoBBox).
Definition: OpDetGeo.h:195
bool const fOnlyActiveVolume
Whether photon propagation is performed only from active volumes.
int VISHits(geo::Point_t const &ScintPoint, OpticalDetector const &opDet, const double cathode_hits_rec, const std::array< double, 3 > hotspot) const
Point GetCathodeCenter() const
Definition: TPCGeo.h:298
std::map< double, double > tpbemission
constexpr T pow(T x)
Definition: pow.h:72
Geometry information for a single TPC.
Definition: TPCGeo.h:38
All information of a photon entering the sensitive optical detector volume.
Definition: SimPhotons.h:64
struct vector vector
std::vector< std::vector< std::vector< double > > > fvispars_flat
bool isOpDetInSameTPC(geo::Point_t const &ScintPoint, geo::Point_t const &OpDetPoint) const
Geant4 interface.
void LoadVisParsFlat(std::vector< double > &vis_distances_x_flat, std::vector< double > &vis_distances_r_flat, std::vector< std::vector< std::vector< double >>> &vispars_flat) const
MappedFunctions_t GetTimingTF1(Point const &p) const
std::vector< double > fvis_distances_r_flat
void ProcessStep(const G4Step &step)
void GetCenter(double *xyz, double localz=0.0) const
Definition: OpDetGeo.cxx:40
G4double tau1[100]
Definition: G4S2Light.cc:61
double finter_d(double *x, double *par)
std::vector< double > fdistances_refl
std::unique_ptr< G4PhysicsTable > theSlowIntegralTable
intermediate_table::const_iterator const_iterator
double model_far(double *x, double *par)
std::vector< std::vector< TF1 > > VUV_timing
std::vector< std::vector< double > > fparameters[7]
static constexpr double MeV
Definition: Units.h:129
MaybeLogger_< ELseverityLevel::ELsev_error, false > LogError
Geometry information for a single cryostat.
Definition: CryostatGeo.h:43
unsigned int Ncryostats() const
Returns the number of cryostats in the detector.
double model_close(double *x, double *par)
void LoadGHDome(std::vector< std::vector< double >> &GHvuvpars_dome, std::vector< double > &border_corr_angulo_dome, std::vector< std::vector< double >> &border_corr_dome) const
std::vector< std::vector< double > > fGHvuvpars_flat
double finter_r(double *x, double *par)
std::vector< std::vector< double > > VUV_min
G4double scint_time(const G4Step &aStep, G4double ScintillationTime, G4double ScintillationRiseTime) const
Energy deposited on a readout Optical Detector by simulated tracks.
void getVISTimes(std::vector< double > &arrivalTimes, const TVector3 &ScintPoint, const TVector3 &OpDetPoint)
geo::Point_t InitialPosition
Scintillation position in world coordinates [cm].
Definition: SimPhotons.h:67
OpFastScintillation(const G4String &processName="Scintillation", G4ProcessType type=fElectromagnetic)
art framework interface to geometry description
std::vector< std::vector< double > > fGHvuvpars_dome
auto enumerate(Iterables &&...iterables)
Range-for loop helper tracking the number of iteration.
Definition: enumerate.h:69
void LoadVUVSemiAnalyticProperties(bool &isFlatPDCorr, bool &isDomePDCorr, double &delta_angulo_vuv, double &radius) const
int VUVHits(const double Nphotons_created, geo::Point_t const &ScintPoint, OpticalDetector const &opDet) const
unsigned int fillCoords(Coords &dest, Vector const &src)
Fills a coordinate array with the coordinates of a vector.
std::vector< double > fborder_corr_angulo_dome
decltype(auto) constexpr size(T &&obj)
ADL-aware version of std::size.
Definition: StdUtils.h:92
virtual G4VParticleChange * AtRestDoIt(const G4Track &aTrack, const G4Step &aStep)
bool const bPropagate
Whether propagation of photons is enabled.
std::vector< std::vector< double > > fborder_corr_dome
T abs(T value)
std::vector< std::vector< double > > fborder_corr_flat
Simulation objects for optical detectors.
std::vector< double > fvis_distances_x_flat
void LoadGHFlat(std::vector< std::vector< double >> &GHvuvpars_flat, std::vector< double > &border_corr_angulo_flat, std::vector< std::vector< double >> &border_corr_flat) const
auto counter(T begin, T end)
Returns an object to iterate values from begin to end in a range-for loop.
Definition: counter.h:285
const double e
bool usesSemiAnalyticModel() const
Returns whether the semi-analytic visibility parametrization is being used.
MappedT0s_t GetReflT0s(Point const &p) const
void LoadVisParsDome(std::vector< double > &vis_distances_x_dome, std::vector< double > &vis_distances_r_dome, std::vector< std::vector< std::vector< double >>> &vispars_dome) const
std::vector< double > fborder_corr_angulo_flat
void AddPhoton(size_t opchannel, sim::OnePhoton &&photon, bool Reflected=false)
std::vector< double > fOpDetLength
static constexpr double eV
Definition: Units.h:127
void SetScintillationByParticleType(const G4bool)
bool RecordPhotonsProduced(const G4Step &aStep, double N)
boost::math::policies::policy< boost::math::policies::promote_double< false > > noLDoublePromote
const double a
def move(depos, offset)
Definition: depos.py:107
std::unique_ptr< CLHEP::RandGeneral > fTPBEm
std::vector< geo::BoxBoundedGeo > const fActiveVolumes
std::vector< std::vector< std::vector< double > > > ftau_pars
double Length() const
Definition: OpDetGeo.h:81
static IonizationAndScintillation * Instance()
bool FillSimEnergyDeposits() const
void AddOpDetBacktrackerRecord(sim::OpDetBacktrackerRecord soc, bool Reflected=false)
Test of util::counter and support utilities.
double distance(double x1, double y1, double z1, double x2, double y2, double z2)
static int max(int a, int b)
void AddLitePhoton(int opchannel, int time, int nphotons, bool Reflected=false)
Description of geometry of one entire detector.
static constexpr bool isDefinitelyGreaterThan(TReal a, TReal b, TReal tolerance=std::numeric_limits< TReal >::epsilon())
MappedCounts_t GetAllVisibilities(Point const &p, bool wantReflected=false) const
G4double sample_time(const G4double tau1, const G4double tau2) const
Specializations of geo_vectors_utils.h for ROOT old vector types.
void AddScintillationPhotons(TrackID_t trackID, timePDclock_t timePDclock, double numberPhotons, double const *xyz, double energy)
Add scintillation photons and energy to this OpticalDetector.
std::vector< double > fradial_distances_refl
ROOT::Math::PositionVector3D< ROOT::Math::Cartesian3D< double >, ROOT::Math::GlobalCoordinateSystemTag > Point_t
Type for representation of position in physical 3D space.
Definition: geo_vectors.h:184
Encapsulate the geometry of an optical detector.
IteratorBox< cryostat_iterator,&GeometryCore::begin_cryostat,&GeometryCore::end_cryostat > IterateCryostats() const
Enables ranged-for loops on all cryostats of the detector.
std::vector< double > fvis_distances_r_dome
cet::coded_exception< errors::ErrorCodes, ExceptionDetail::translate > Exception
Definition: Exception.h:66
static OpDetPhotonTable * Instance(bool LitePhotons=false)
void interpolate3(std::array< double, 3 > &inter, const std::vector< double > &xData, const std::vector< double > &yData1, const std::vector< double > &yData2, const std::vector< double > &yData3, double x, bool extrapolate) const
void LoadVisSemiAnalyticProperties(double &delta_angulo_vis, double &radius) const
std::vector< std::vector< std::vector< double > > > fcut_off_pars
A base class aware of world box coordinatesAn object describing a simple shape can inherit from this ...
Definition: BoxBoundedGeo.h:33
bool const fUseNhitsModel
Whether the semi-analytic model is being used for photon visibility.
A container for photon visibility mapping data.
T min(sqlite3 *const db, std::string const &table_name, std::string const &column_name)
Definition: statistics.h:55
static constexpr bool isApproximatelyEqual(TReal a, TReal b, TReal tolerance=std::numeric_limits< TReal >::epsilon())
std::vector< double > fvis_distances_x_dome
double Rectangle_SolidAngle(const double a, const double b, const double d) const
bool SetInSD
Whether the photon reaches the sensitive detector.
Definition: SimPhotons.h:88
void getVUVTimes(std::vector< double > &arrivalTimes, const double distance_in_cm, const size_t angle_bin)
constexpr double dist(const TReal *x, const TReal *y, const unsigned int dimension)
double Omega_Dome_Model(const double distance, const double theta) const
TVector3 toTVector3(Vector const &v)
Converts a vector into a TVector3.
double Disk_SolidAngle(const double d, const double h, const double b) const
double interpolate(const std::vector< double > &xData, const std::vector< double > &yData, double x, bool extrapolate, size_t i=0) const
MaybeLogger_< ELseverityLevel::ELsev_warning, false > LogWarning
void generateParam(const size_t index, const size_t angle_bin)
virtual G4VParticleChange * PostStepDoIt(const G4Track &aTrack, const G4Step &aStep)
void AddEnergyDeposit(int n_photon, int n_elec, double scint_yield, double energy, float start_x, float start_y, float start_z, float end_x, float end_y, float end_z, double start_time, double end_time, int trackid, int pdgcode, std::string const &vol="EMPTY")
void ExtendToInclude(Coord_t x, Coord_t y, Coord_t z)
Extends the current box to also include the specified point.
phot::PhotonVisibilityService const *const fPVS
Photon visibility service instance.
bool const fOpaqueCathode
Whether the cathodes are fully opaque; currently hard coded "no".
std::vector< std::vector< double > > VUV_max
static bool * b
Definition: config.cpp:1043
list x
Definition: train.py:276
TPCGeo const & TPC(unsigned int const tpc=0, unsigned int const cstat=0) const
Returns the specified TPC.
float pi
Definition: units.py:11
MaybeLogger_< ELseverityLevel::ELsev_success, true > LogTrace
float Energy
Scintillation photon energy [GeV].
Definition: SimPhotons.h:82
G4double GetMeanFreePath(const G4Track &aTrack, G4double, G4ForceCondition *)
G4double GetMeanLifeTime(const G4Track &aTrack, G4ForceCondition *)
static constexpr bool isDefinitelyLessThan(TReal a, TReal b, TReal tolerance=std::numeric_limits< TReal >::epsilon())
phot::MappedFunctions_t ParPropTimeTF1
virtual bool ScintByParticleType() const =0
double Height() const
Definition: OpDetGeo.h:83
static constexpr bool isApproximatelyZero(TReal a, TReal tolerance=std::numeric_limits< TReal >::epsilon())
QAsciiDict< Entry > ns
TGeoShape const * Shape() const
Returns the geometry object as TGeoShape.
Definition: OpDetGeo.h:154
def parent(G, child, parent_type)
Definition: graph.py:67
G4double single_exp(const G4double t, const G4double tau2) const
cet::coded_exception< error, detail::translate > exception
Definition: exception.h:33
bool UseLitePhotons() const
QTextStream & endl(QTextStream &s)
double LandauPlusExpoFinal(double *x, double *par)
double fast_acos(double x)
std::unique_ptr< G4PhysicsTable > theFastIntegralTable
int MotherTrackID
ID of the GEANT4 track causing the scintillation.
Definition: SimPhotons.h:85