LArSoft  v09_90_00
Liquid Argon Software toolkit - https://larsoft.org/
ISCalcNESTLAr.cxx
Go to the documentation of this file.
1 // Class: ISCalcNESTLAr
3 // Plugin Type: Algorithm
4 // File: ISCalcNESTLAr.cxx
5 // Description:
6 // Aug. 30 by Mu Wei
8 
15 
16 #include "CLHEP/Random/RandFlat.h"
17 #include "CLHEP/Random/RandGauss.h"
18 #include "CLHEP/Units/SystemOfUnits.h"
19 
20 #include <algorithm>
21 
22 namespace {
23  constexpr double LAr_Z{18};
24  constexpr double Density_LAr{1.393};
25 
26  constexpr double scint_yield{1.0 / (19.5 * CLHEP::eV)};
27  constexpr double resolution_scale{0.107}; // Doke 1976
28 }
29 
30 namespace larg4 {
31 
32  //----------------------------------------------------------------------------
33  ISCalcNESTLAr::ISCalcNESTLAr(CLHEP::HepRandomEngine& Engine)
34  : fEngine(Engine), fSCE{lar::providerFrom<spacecharge::SpaceChargeService>()}
35  {
36  std::cout << "ISCalcNESTLAr Initialize." << std::endl;
37  }
38 
39  //----------------------------------------------------------------------------
42  {
43  CLHEP::RandGauss GaussGen(fEngine);
44  CLHEP::RandFlat UniformGen(fEngine);
45 
46  double yieldFactor = 1.0; // default quenching factor, for electronic recoils
47  double excitationRatio =
48  0.21; // ratio for light particle in LAr, such as e-, mu-, Aprile et. al book
49 
50  double const energyDeposit = edep.Energy();
51  if (energyDeposit < 1 * CLHEP::eV) // too small energy deposition
52  {
53  return {0., 0., 0., 0.};
54  }
55 
56  int pdgcode = edep.PdgCode();
57 
58  geo::Length_t startx = edep.StartX();
59  geo::Length_t starty = edep.StartY();
60  geo::Length_t startz = edep.StartZ();
61  geo::Length_t endx = edep.EndX();
62  geo::Length_t endy = edep.EndY();
63  geo::Length_t endz = edep.EndZ();
64 
65  double DokeBirks[3];
66 
67  double eField = EFieldAtStep(detProp.Efield(), edep);
68  if (eField) {
69  DokeBirks[0] = 0.07 * pow((eField / 1.0e3), -0.85);
70  DokeBirks[2] = 0.00;
71  }
72  else {
73  DokeBirks[0] = 0.0003;
74  DokeBirks[2] = 0.75;
75  }
76 
77  double Density = detProp.Density() /
78  (CLHEP::g / CLHEP::cm3); // argon density at the temperature from Temperature()
79 
80  // nuclear recoil quenching "L" factor: total yield is
81  // reduced for nuclear recoil as per Lindhard theory
82  double epsilon = 11.5 * (energyDeposit / CLHEP::keV) * pow(LAr_Z, (-7. / 3.));
83 
84  if (pdgcode == 2112 || pdgcode == -2112) //nuclear recoil
85  {
86  yieldFactor = 0.23 * (1 + exp(-5 * epsilon)); //liquid argon L_eff
87  excitationRatio = 0.69337 + 0.3065 * exp(-0.008806 * pow(eField, 0.76313));
88  }
89 
90  // determine ultimate number of quanta from current E-deposition (ph+e-) total mean number of exc/ions
91  //the total number of either quanta produced is equal to product of the
92  //work function, the energy deposited, and yield reduction, for NR
93  double MeanNumQuanta = scint_yield * energyDeposit;
94  double sigma = sqrt(resolution_scale * MeanNumQuanta); //Fano
95  int NumQuanta = int(floor(GaussGen.fire(MeanNumQuanta, sigma) + 0.5));
96  double LeffVar = GaussGen.fire(yieldFactor, 0.25 * yieldFactor);
97  LeffVar = std::clamp(LeffVar, 0., 1.);
98 
99  if (yieldFactor < 1) //nuclear reocils
100  {
101  NumQuanta = BinomFluct(NumQuanta, LeffVar);
102  }
103 
104  //if Edep below work function, can't make any quanta, and if NumQuanta
105  //less than zero because Gaussian fluctuated low, update to zero
106  if (energyDeposit < 1 / scint_yield || NumQuanta < 0) { NumQuanta = 0; }
107 
108  // next section binomially assigns quanta to excitons and ions
109  int NumExcitons = BinomFluct(NumQuanta, excitationRatio / (1 + excitationRatio));
110  int NumIons = NumQuanta - NumExcitons;
111 
112  // this section calculates recombination following the modified Birks'Law of Doke, deposition by deposition,
113  // may be overridden later in code if a low enough energy necessitates switching to the
114  // Thomas-Imel box model for recombination instead (determined by site)
115  double dE = energyDeposit / CLHEP::MeV;
116  double dx = 0.0;
117  double LET = 0.0;
118  double recombProb;
119 
120  if (pdgcode != 11 && pdgcode != -11 && pdgcode != 13 &&
121  pdgcode != -13) //e-: 11, e+: -11, mu-: 13, mu+: -13
122  {
123  //in other words, if it's a gamma,ion,proton,alpha,pion,et al. do not
124  //use the step length provided by Geant4 because it's not relevant,
125  //instead calculate an estimated LET and range of the electrons that
126  //would have been produced if Geant4 could track them
127  LET = CalcElectronLET(1000 * dE);
128 
129  if (LET) {
130  dx = dE / (Density * LET); //find the range based on the LET
131  }
132 
133  if (abs(pdgcode) == 2112) //nuclear recoils
134  {
135  dx = 0;
136  }
137  }
138  else //normal case of an e-/+ energy deposition recorded by Geant
139  {
140  dx = std::hypot(startx - endx, starty - endy, startz - endz) / CLHEP::cm;
141  if (dx) {
142  LET = (dE / dx) * (1 / Density); //lin. energy xfer (prop. to dE/dx)
143  }
144  if (LET > 0 && dE > 0 && dx > 0) {
145  double ratio = CalcElectronLET(dE * 1e3) / LET;
146  if (ratio < 0.7 && pdgcode == 11) {
147  dx /= ratio;
148  LET *= ratio;
149  }
150  }
151  }
152 
153  DokeBirks[1] = DokeBirks[0] / (1 - DokeBirks[2]); //B=A/(1-C) (see paper) r
154  recombProb = (DokeBirks[0] * LET) / (1 + DokeBirks[1] * LET) +
155  DokeBirks[2]; //Doke/Birks' Law as spelled out in the NEST pape
156  recombProb *= (Density / Density_LAr);
157 
158  //check against unphysicality resulting from rounding errors
159  recombProb = std::clamp(recombProb, 0., 1.);
160 
161  //use binomial distribution to assign photons, electrons, where photons
162  //are excitons plus recombined ionization electrons, while final
163  //collected electrons are the "escape" (non-recombined) electrons
164  int const NumPhotons = NumExcitons + BinomFluct(NumIons, recombProb);
165  int const NumElectrons = NumQuanta - NumPhotons;
166 
167  return {energyDeposit,
168  static_cast<double>(NumElectrons),
169  static_cast<double>(NumPhotons),
170  GetScintYieldRatio(edep)};
171  }
172 
173  //----------------------------------------------------------------------------
174  int ISCalcNESTLAr::BinomFluct(int N0, double prob)
175  {
176  CLHEP::RandGauss GaussGen(fEngine);
177  CLHEP::RandFlat UniformGen(fEngine);
178 
179  double mean = N0 * prob;
180  double sigma = sqrt(N0 * prob * (1 - prob));
181  int N1 = 0;
182  if (prob == 0.00) { return N1; }
183  if (prob == 1.00) { return N0; }
184 
185  if (N0 < 10) {
186  for (int i = 0; i < N0; i++) {
187  if (UniformGen.fire() < prob) { N1++; }
188  }
189  }
190  else {
191  N1 = int(floor(GaussGen.fire(mean, sigma) + 0.5));
192  }
193  if (N1 > N0) { N1 = N0; }
194  if (N1 < 0) { N1 = 0; }
195  return N1;
196  }
197 
198  //----------------------------------------------------------------------------
200  {
201  double LET;
202 
203  if (E >= 1) {
204  LET = 116.70 - 162.97 * log10(E) + 99.361 * pow(log10(E), 2) - 33.405 * pow(log10(E), 3) +
205  6.5069 * pow(log10(E), 4) - 0.69334 * pow(log10(E), 5) + .031563 * pow(log10(E), 6);
206  }
207  else if (E > 0 && E < 1) {
208  LET = 100;
209  }
210  else {
211  LET = 0;
212  }
213 
214  return LET;
215  }
216 
217  //----------------------------------------------------------------------------
219  {
220  geo::Point_t pos = edep.MidPoint();
221  double EField = efield;
222  geo::Vector_t eFieldOffsets;
223  if (fSCE->EnableSimEfieldSCE()) {
224  eFieldOffsets = fSCE->GetEfieldOffsets(pos);
225  EField =
226  std::sqrt((efield + efield * eFieldOffsets.X()) * (efield + efield * eFieldOffsets.X()) +
227  (efield * eFieldOffsets.Y() * efield * eFieldOffsets.Y()) +
228  (efield * eFieldOffsets.Z() * efield * eFieldOffsets.Z()));
229  }
230  return EField;
231  }
232 
233 }
geo::Length_t StartX() const
CLHEP::HepRandomEngine & fEngine
Definition: ISCalcNESTLAr.h:34
virtual bool EnableSimEfieldSCE() const =0
geo::Length_t EndZ() const
ISCalcNESTLAr(CLHEP::HepRandomEngine &fEngine)
#define Density_LAr
Definition: NestAlg.cxx:26
geo::Length_t EndY() const
Utilities related to art service access.
double Length_t
Type used for coordinates and distances. They are measured in centimeters.
Definition: geo_vectors.h:133
ROOT::Math::DisplacementVector3D< ROOT::Math::Cartesian3D< double >, ROOT::Math::GlobalCoordinateSystemTag > Vector_t
Type for representation of momenta in 3D space.
Definition: geo_vectors.h:160
const spacecharge::SpaceCharge * fSCE
Definition: ISCalcNESTLAr.h:35
constexpr auto abs(T v)
Returns the absolute value of the argument.
Geant4 interface.
geo::Length_t StartY() const
double Efield(unsigned int planegap=0) const
kV/cm
geo::Length_t EndX() const
Float_t E
Definition: plot.C:20
geo::Point_t MidPoint() const
double Density(double temperature=0.) const
Returns argon density at a given temperature.
int BinomFluct(int N0, double prob)
geo::Length_t StartZ() const
Double_t edep
Definition: macro.C:13
ISCalcData CalcIonAndScint(detinfo::DetectorPropertiesData const &detProp, sim::SimEnergyDeposit const &edep) override
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:180
virtual geo::Vector_t GetEfieldOffsets(geo::Point_t const &point) const =0
double mean(const std::vector< short > &wf, size_t start, size_t nsample)
Definition: UtilFunc.cxx:13
double CalcElectronLET(double E)
contains information for a single step in the detector simulation
Energy deposition in the active material.
double Energy() const
double GetScintYieldRatio(sim::SimEnergyDeposit const &edep)
Definition: ISCalc.cxx:41
double EFieldAtStep(double efield, sim::SimEnergyDeposit const &edep) override