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