LArSoft  v07_13_02
Liquid Argon Software toolkit - http://larsoft.org/
RadioGen_module.cc
Go to the documentation of this file.
1 // Rn222 generation feature added by gleb.sinev@duke.edu
9 // (based on a generator by jason.stock@mines.sdsmt.edu)
10 // JStock. Added preliminary changes to get ready for Ar42 implimentation. This includes allowing for multiple particles from the same decay.
11 // Ar42 generation added by JStock (jason.stock@mines.sdsmt.edu).
12 // Ar42 is designed to handle 5 separate different
13 // beta decay modes, each with it's own chains of
14 // possible dexcitation gammas. Because of the
15 // high energies, and the relatively high rate
16 // expected in the DUNE FD, these chains are
17 // completely simulated instead of relying on the
18 // existing machinery in this module. To make the
19 // treatment of multiple decay products and
20 // dexcitation chains generally available would
21 // require a significant redesign of the module
22 // and possibly of the .root files data structure
23 // for each radiological.
24 //
25 // Dec 01, 2017 JStock
26 // Adding the ability to make 8.997 MeV Gammas for
27 // Ni59 Calibration sources. This is another "hacky"
28 // fix to something that really deserves a more
29 // elegant and comprehensive solution.
31 #ifndef EVGEN_RADIOLOGICAL
32 #define EVGEN_RADIOLOGICAL
33 
34 // C++ includes.
35 #include <iostream>
36 #include <sstream>
37 #include <string>
38 #include <regex>
39 #include <cmath>
40 #include <memory>
41 #include <iterator>
42 #include <vector>
43 #include <sys/stat.h>
44 #include <TGeoManager.h>
45 #include <TGeoMaterial.h>
46 #include <TGeoNode.h>
47 
48 // Framework includes
51 #include "fhiclcpp/ParameterSet.h"
59 #include "cetlib_except/exception.h"
60 #include "cetlib/search_path.h"
61 
62 // art extensions
64 
65 // nutools includes
69 
70 // lar includes
73 
74 // root includes
75 
76 #include "TLorentzVector.h"
77 #include "TVector3.h"
78 #include "TGenPhaseSpace.h"
79 #include "TMath.h"
80 #include "TFile.h"
81 #include "TH1.h"
82 #include "TH1D.h"
83 #include "TGraph.h"
84 
85 #include "CLHEP/Random/RandFlat.h"
86 #include "CLHEP/Random/RandPoisson.h"
87 
88 namespace simb { class MCTruth; }
89 
90 namespace evgen {
91 
94 
95 
96 
97  class RadioGen : public art::EDProducer {
98 
99  public:
100  explicit RadioGen(fhicl::ParameterSet const& pset);
101  virtual ~RadioGen();
102 
103  // This is called for each event.
104  void produce(art::Event& evt);
105  void beginRun(art::Run& run);
106  void reconfigure(fhicl::ParameterSet const& p);
107 
108  private:
109 
110  typedef int ti_PDGID; // These typedefs may look odd, and unecessary. I chose to use them to make the tuples I use later more readable. ti, type integer :JStock
111  typedef double td_Mass; // These typedefs may look odd, and unecessary. I chose to use them to make the tuples I use later more readable. td, type double :JStock
112 
113  void SampleOne(unsigned int i,
114  simb::MCTruth &mct);
115 
116  TLorentzVector dirCalc(double p, double m);
117 
118  void readfile(std::string nuclide, std::string filename);
119  void samplespectrum(std::string nuclide, int &itype, double &t, double &m, double &p);
120 
121  void Ar42Gamma2(std::vector<std::tuple<ti_PDGID, td_Mass, TLorentzVector>>& v_prods);
122  void Ar42Gamma3(std::vector<std::tuple<ti_PDGID, td_Mass, TLorentzVector>>& v_prods);
123  void Ar42Gamma4(std::vector<std::tuple<ti_PDGID, td_Mass, TLorentzVector>>& v_prods);
124  void Ar42Gamma5(std::vector<std::tuple<ti_PDGID, td_Mass, TLorentzVector>>& v_prods);
125 
126  // recoded so as to use the LArSoft-managed random number generator
127  double samplefromth1d(TH1D *hist);
128 
129  // itype = pdg code: 1000020040: alpha. itype=11: beta. -11: positron, itype=22: gamma. -1: error
130  // t is the kinetic energy in GeV (= E-m)
131  // m = mass in GeV
132  // p = momentum in GeV
133 
134  //double betaphasespace(double mass, double q); // older parameterization.
135 
136  // the generator randomly samples points in a rectangular prism of space and time, and only selects those points in
137  // volumes with materials that match the regexes in fMaterial. One can use wildcards * and ? for broader matches.
138 
139  std::vector<std::string> fNuclide;
140  std::vector<std::string> fMaterial;
141  std::vector<double> fBq;
142  std::vector<double> fT0;
143  std::vector<double> fT1;
144  std::vector<double> fX0;
145  std::vector<double> fY0;
146  std::vector<double> fZ0;
147  std::vector<double> fX1;
148  std::vector<double> fY1;
149  std::vector<double> fZ1;
152 
153 
154  // leftovers from the phase space generator
155  // const double gevperamu = 0.931494061;
156  // TGenPhaseSpace rg; // put this here so we don't constantly construct and destruct it
157 
158  const double m_e = 0.000510998928; // mass of electron in GeV
159  const double m_alpha = 3.727379240; // mass of an alpha particle in GeV
160  const double m_neutron = 0.9395654133; // mass of a neutron in GeV
161 
162  std::vector<std::string> spectrumname;
163  std::vector<TH1D*> alphaspectrum;
164  std::vector<double> alphaintegral;
165  std::vector<TH1D*> betaspectrum;
166  std::vector<double> betaintegral;
167  std::vector<TH1D*> gammaspectrum;
168  std::vector<double> gammaintegral;
169  std::vector<TH1D*> neutronspectrum;
170  std::vector<double> neutronintegral;
171 
172  };
173 }
174 
175 namespace evgen{
176 
177  //____________________________________________________________________________
178  RadioGen::RadioGen(fhicl::ParameterSet const& pset)
179  {
180 
181  this->reconfigure(pset);
182 
183  // create a default random engine; obtain the random seed from NuRandomService,
184  // unless overridden in configuration with key "Seed"
186  ->createEngine(*this, pset, "Seed");
187 
188  produces< std::vector<simb::MCTruth> >();
189  produces< sumdata::RunData, art::InRun >();
190 
191  }
192 
193  //____________________________________________________________________________
194  RadioGen::~RadioGen()
195  {
196  }
197 
198  //____________________________________________________________________________
199  void RadioGen::reconfigure(fhicl::ParameterSet const& p)
200  {
201  // do not put seed in reconfigure because we don't want to reset
202  // the seed midstream -- same as SingleGen
203 
204  fNuclide = p.get< std::vector<std::string>>("Nuclide");
205  fMaterial = p.get< std::vector<std::string>>("Material");
206  fBq = p.get< std::vector<double> >("BqPercc");
207  fT0 = p.get< std::vector<double> >("T0");
208  fT1 = p.get< std::vector<double> >("T1");
209  fX0 = p.get< std::vector<double> >("X0");
210  fY0 = p.get< std::vector<double> >("Y0");
211  fZ0 = p.get< std::vector<double> >("Z0");
212  fX1 = p.get< std::vector<double> >("X1");
213  fY1 = p.get< std::vector<double> >("Y1");
214  fZ1 = p.get< std::vector<double> >("Z1");
215  fIsFirstSignalSpecial = p.get< bool >("IsFirstSignalSpecial", false);
216 
217  // check for consistency of vector sizes
218 
219  unsigned int nsize = fNuclide.size();
220  if ( fMaterial.size() != nsize ) throw cet::exception("RadioGen") << "Different size Material vector and Nuclide vector\n";
221  if ( fBq.size() != nsize ) throw cet::exception("RadioGen") << "Different size Bq vector and Nuclide vector\n";
222  if ( fT0.size() != nsize ) throw cet::exception("RadioGen") << "Different size T0 vector and Nuclide vector\n";
223  if ( fT1.size() != nsize ) throw cet::exception("RadioGen") << "Different size T1 vector and Nuclide vector\n";
224  if ( fX0.size() != nsize ) throw cet::exception("RadioGen") << "Different size X0 vector and Nuclide vector\n";
225  if ( fY0.size() != nsize ) throw cet::exception("RadioGen") << "Different size Y0 vector and Nuclide vector\n";
226  if ( fZ0.size() != nsize ) throw cet::exception("RadioGen") << "Different size Z0 vector and Nuclide vector\n";
227  if ( fX1.size() != nsize ) throw cet::exception("RadioGen") << "Different size X1 vector and Nuclide vector\n";
228  if ( fY1.size() != nsize ) throw cet::exception("RadioGen") << "Different size Y1 vector and Nuclide vector\n";
229  if ( fZ1.size() != nsize ) throw cet::exception("RadioGen") << "Different size Z1 vector and Nuclide vector\n";
230 
231  for(std::string & nuclideName : fNuclide){
232  if(nuclideName=="39Ar" ){readfile("39Ar","Argon_39.root") ;}
233  else if(nuclideName=="60Co" ){readfile("60Co","Cobalt_60.root") ;}
234  else if(nuclideName=="85Kr" ){readfile("85Kr","Krypton_85.root") ;}
235  else if(nuclideName=="40K" ){readfile("40K","Potassium_40.root") ;}
236  else if(nuclideName=="232Th"){readfile("232Th","Thorium_232.root");}
237  else if(nuclideName=="238U" ){readfile("238U","Uranium_238.root") ;}
238  else if(nuclideName=="222Rn"){continue;} //Rn222 is handeled separately later
239  else if(nuclideName=="59Ni"){continue;} //Rn222 is handeled separately later
240  else if(nuclideName=="42Ar" ){
241  readfile("42Ar_1", "Argon_42_1.root"); //Each possible beta decay mode of Ar42 is given it's own .root file for now.
242  readfile("42Ar_2", "Argon_42_2.root"); //This allows us to know which decay chain to follow for the dexcitation gammas.
243  readfile("42Ar_3", "Argon_42_3.root"); //The dexcitation gammas are not included in the root files as we want to
244  readfile("42Ar_4", "Argon_42_4.root"); //probabilistically simulate the correct coincident gammas, which we cannot guarantee
245  readfile("42Ar_5", "Argon_42_5.root"); //by sampling a histogram.
246  continue;
247  } //Ar42 is handeled separately later
248  else{
249  std::string searchName = nuclideName;
250  searchName+=".root";
251  readfile(nuclideName,searchName);
252  }
253  }
254 
255  return;
256  }
257 
258  //____________________________________________________________________________
259  void RadioGen::beginRun(art::Run& run)
260  {
261 
262  // grab the geometry object to see what geometry we are using
263 
265  std::unique_ptr<sumdata::RunData> runcol(new sumdata::RunData(geo->DetectorName()));
266  run.put(std::move(runcol));
267 
268  return;
269  }
270 
271  //____________________________________________________________________________
272  void RadioGen::produce(art::Event& evt)
273  {
274 
276  std::unique_ptr< std::vector<simb::MCTruth> > truthcol(new std::vector<simb::MCTruth>);
277 
278  simb::MCTruth truth;
280 
281  trackidcounter = -1;
282  for (unsigned int i=0; i<fNuclide.size(); ++i) {
283  SampleOne(i,truth);
284  }//end loop over nuclides
285 
286  LOG_DEBUG("RadioGen") << truth;
287  truthcol->push_back(truth);
288  evt.put(std::move(truthcol));
289  return;
290  }
291 
292  //____________________________________________________________________________
293  // Generate radioactive decays per nuclide per volume according to the FCL parameters
294 
295  void RadioGen::SampleOne(unsigned int i, simb::MCTruth &mct){
296 
298  TGeoManager *geomanager = geo->ROOTGeoManager();
299 
300  // get the random number generator service and make some CLHEP generators
302  CLHEP::HepRandomEngine &engine = rng->getEngine();
303  CLHEP::RandFlat flat(engine);
304  CLHEP::RandPoisson poisson(engine);
305 
306  // figure out how many decays to generate, assuming that the entire prism consists of the radioactive material.
307  // we will skip over decays in other materials later.
308 
309  double rate = fabs( fBq[i] * (fT1[i] - fT0[i]) * (fX1[i] - fX0[i]) * (fY1[i] - fY0[i]) * (fZ1[i] - fZ0[i]) ) / 1.0E9;
310  long ndecays = poisson.shoot(rate);
311 
312  for (unsigned int idecay=0; idecay<ndecays; idecay++)
313  {
314  // generate just one particle at a time. Need a little recoding if a radioactive
315  // decay generates multiple daughters that need simulation
316  // uniformly distributed in position and time
317  //
318  // JStock: Leaving this as a single position for the decay products. For now I will assume they all come from the same spot.
319  TLorentzVector pos( fX0[i] + flat.fire()*(fX1[i] - fX0[i]),
320  fY0[i] + flat.fire()*(fY1[i] - fY0[i]),
321  fZ0[i] + flat.fire()*(fZ1[i] - fZ0[i]),
322  (idecay==0 && fIsFirstSignalSpecial) ? 0 : ( fT0[i] + flat.fire()*(fT1[i] - fT0[i]) ) );
323  //fT0[i] + flat.fire()*(fT1[i] - fT0[i]) );
324 
325  // discard decays that are not in the proper material
326  std::string volmaterial = geomanager->FindNode(pos.X(),pos.Y(),pos.Z())->GetMedium()->GetMaterial()->GetName();
327  //mf::LogDebug("RadioGen") << "Position: " << pos.X() << " " << pos.Y() << " " << pos.Z() << " " << pos.T() << " " << volmaterial << " " << fMaterial[i] << std::endl;
328  if ( ! std::regex_match(volmaterial, std::regex(fMaterial[i])) ) continue;
329  //mf::LogDebug("RadioGen") << "Decay accepted" << std::endl;
330 
331  //Moved pdgid into the next statement, so that it is localized.
332  // electron=11, photon=22, alpha = 1000020040, neutron = 2112
333 
334  //JStock: Allow us to have different particles from the same decay. This requires multiple momenta.
335  std::vector<std::tuple<ti_PDGID, td_Mass, TLorentzVector>> v_prods; //(First is for PDGID, second is mass, third is Momentum)
336 
337  if (fNuclide[i] == "222Rn") // Treat 222Rn separately
338  {
339  double p=0; double t=0.00548952; td_Mass m=m_alpha; ti_PDGID pdgid=1000020040; //td_Mass = double. ti_PDGID = int;
340  double energy = t + m;
341  double p2 = energy*energy - m*m;
342  if (p2 > 0) p = TMath::Sqrt(p2);
343  else p = 0;
344  //Make TLorentzVector and push it to the back of v_prods.
345  std::tuple<ti_PDGID, td_Mass, TLorentzVector> partMassMom = std::make_tuple(pdgid, m, dirCalc(p,m));
346  v_prods.push_back(partMassMom);
347  }//End special case RN222
348  else if(fNuclide[i] == "59Ni"){ //Treat 59Ni Calibration Source separately (as I haven't made a spectrum for it, and ultimately it should be handeled with multiple particle outputs.
349  double p=0.008997; td_Mass m=0; ti_PDGID pdgid=22; // td_Mas=double. ti_PDFID=int. Assigning p directly, as t=p for gammas.
350  std::tuple<ti_PDGID, td_Mass, TLorentzVector> partMassMom = std::make_tuple(pdgid, m, dirCalc(p,m));
351  v_prods.push_back(partMassMom);
352  }//end special case Ni59 calibration source
353  else if(fNuclide[i] == "42Ar"){ // Spot for special treatment of Ar42.
354  double p=0; double t=0; td_Mass m = 0; ti_PDGID pdgid=0; //td_Mass = double. ti_PDGID = int;
355  double bSelect = flat.fire(); //Make this a random number from 0 to 1.
356  if(bSelect<0.819){ //beta channel 1. No Gamma. beta Q value 3525.22 keV
357  samplespectrum("42Ar_1", pdgid, t, m, p);
358  std::tuple<ti_PDGID, td_Mass, TLorentzVector> partMassMom = std::make_tuple(pdgid, m, dirCalc(p,m));
359  v_prods.push_back(partMassMom);
360  //No gamma here.
361  }else if(bSelect<0.9954){ //beta channel 2. 1 Gamma (1524.6 keV). beta Q value 2000.62
362  samplespectrum("42Ar_2", pdgid, t, m, p);
363  std::tuple<ti_PDGID, td_Mass, TLorentzVector> partMassMom = std::make_tuple(pdgid, m, dirCalc(p,m));
364  v_prods.push_back(partMassMom);
365  Ar42Gamma2(v_prods);
366  }else if(bSelect<0.9988){ //beta channel 3. 1 Gamma Channel. 312.6 keV + gamma 2. beta Q value 1688.02 keV
367  samplespectrum("42Ar_3", pdgid, t, m, p);
368  std::tuple<ti_PDGID, td_Mass, TLorentzVector> partMassMom = std::make_tuple(pdgid, m, dirCalc(p,m));
369  v_prods.push_back(partMassMom);
370  Ar42Gamma3(v_prods);
371  }else if(bSelect<0.9993){ //beta channel 4. 2 Gamma Channels. Either 899.7 keV (i 0.052) + gamma 2 or 2424.3 keV (i 0.020). beta Q value 1100.92 keV
372  samplespectrum("42Ar_4", pdgid, t, m, p);
373  std::tuple<ti_PDGID, td_Mass, TLorentzVector> partMassMom = std::make_tuple(pdgid, m, dirCalc(p,m));
374  v_prods.push_back(partMassMom);
375  Ar42Gamma4(v_prods);
376  }else{ //beta channel 5. 3 gamma channels. 692.0 keV + 1228.0 keV + Gamma 2 (i 0.0033) ||OR|| 1021.2 keV + gamma 4 (i 0.0201) ||OR|| 1920.8 keV + gamma 2 (i 0.041). beta Q value 79.82 keV
377  samplespectrum("42Ar_5", pdgid, t, m, p);
378  std::tuple<ti_PDGID, td_Mass, TLorentzVector> partMassMom = std::make_tuple(pdgid, m, dirCalc(p,m));
379  v_prods.push_back(partMassMom);
380  Ar42Gamma5(v_prods);
381  }
382  //Add beta.
383  //Call gamma function for beta mode.
384  }
385  else{ //General Case.
386  double p=0; double t=0; td_Mass m = 0; ti_PDGID pdgid=0; //td_Mass = double. ti_PDGID = int;
387  samplespectrum(fNuclide[i],pdgid,t,m,p);
388  std::tuple<ti_PDGID, td_Mass, TLorentzVector> partMassMom = std::make_tuple(pdgid, m, dirCalc(p,m));
389  v_prods.push_back(partMassMom);
390  }//end else (not RN or other special case
391 
392  //JStock: Modify this to now loop over the v_prods.
393  for(auto prodEntry : v_prods){
394  // set track id to a negative serial number as these are all primary particles and have id <= 0
395  int trackid = trackidcounter;
396  ti_PDGID pdgid = std::get<0>(prodEntry);
397  td_Mass m = std::get<1>(prodEntry);
398  TLorentzVector pvec = std::get<2>(prodEntry);
399  trackidcounter--;
400  std::string primary("primary");
401 
402  // alpha particles need a little help since they're not in the TDatabasePDG table
403  // // so don't rely so heavily on default arguments to the MCParticle constructor
404  if (pdgid == 1000020040){
405  simb::MCParticle part(trackid, pdgid, primary,-1,m,1);
406  part.AddTrajectoryPoint(pos, pvec);
407  mct.Add(part);
408  }// end "If alpha"
409  else{
410  simb::MCParticle part(trackid, pdgid, primary);
411  part.AddTrajectoryPoint(pos, pvec);
412  mct.Add(part);
413  }// end All standard cases.
414  }//End Loop over all particles produces in this single decay.
415  }
416  }
417 
418  //Calculate an arbitrary direction with a given magnitude p
419  TLorentzVector RadioGen::dirCalc(double p, double m){
421  CLHEP::HepRandomEngine &engine = rng->getEngine();
422  CLHEP::RandFlat flat(engine);
423  // isotropic production angle for the decay product
424  double costheta = (2.0*flat.fire() - 1.0);
425  if (costheta < -1.0) costheta = -1.0;
426  if (costheta > 1.0) costheta = 1.0;
427  double sintheta = sqrt(1.0-costheta*costheta);
428  double phi = 2.0*M_PI*flat.fire();
429  TLorentzVector pvec(p*sintheta*std::cos(phi),
430  p*sintheta*std::sin(phi),
431  p*costheta,
432  std::sqrt(p*p+m*m));
433  return pvec;
434  }
435 
436 
437  // only reads those files that are on the fNuclide list. Copy information from the TGraphs to TH1D's
438 
439  void RadioGen::readfile(std::string nuclide, std::string filename)
440  {
441  int ifound = 0;
442  for (size_t i=0; i<fNuclide.size(); i++)
443  {
444  if (fNuclide[i] == nuclide){ //This check makes sure that the nuclide we are searching for is in fact in our fNuclide list. Ar42 handeled separately.
445  ifound = 1;
446  break;
447  } //End If nuclide is in our list. Next is the special case of Ar42
448  else if ( (std::regex_match(nuclide, std::regex( "42Ar.*" )) ) && fNuclide[i]=="42Ar" ){
449  ifound = 1;
450  break;
451  }
452  }
453  if (ifound == 0) return;
454 
455  Bool_t addStatus = TH1::AddDirectoryStatus();
456  TH1::AddDirectory(kFALSE); // cloned histograms go in memory, and aren't deleted when files are closed.
457  // be sure to restore this state when we're out of the routine.
458 
459 
460  spectrumname.push_back(nuclide);
461  cet::search_path sp("FW_SEARCH_PATH");
462  std::string fn2 = "Radionuclides/";
463  fn2 += filename;
464  std::string fullname;
465  sp.find_file(fn2, fullname);
466  struct stat sb;
467  if (fullname.empty() || stat(fullname.c_str(), &sb)!=0)
468  throw cet::exception("RadioGen") << "Input spectrum file "
469  << fn2
470  << " not found in FW_SEARCH_PATH!\n";
471 
472  TFile f(fullname.c_str(),"READ");
473  TGraph *alphagraph = (TGraph*) f.Get("Alphas");
474  TGraph *betagraph = (TGraph*) f.Get("Betas");
475  TGraph *gammagraph = (TGraph*) f.Get("Gammas");
476  TGraph *neutrongraph = (TGraph*) f.Get("Neutrons");
477 
478  if (alphagraph)
479  {
480  int np = alphagraph->GetN();
481  double *y = alphagraph->GetY();
482  std::string name;
483  name = "RadioGen_";
484  name += nuclide;
485  name += "_Alpha";
486  TH1D *alphahist = (TH1D*) new TH1D(name.c_str(),"Alpha Spectrum",np,0,np);
487  for (int i=0; i<np; i++)
488  {
489  alphahist->SetBinContent(i+1,y[i]);
490  alphahist->SetBinError(i+1,0);
491  }
492  alphaspectrum.push_back(alphahist);
493  alphaintegral.push_back(alphahist->Integral());
494  }
495  else
496  {
497  alphaspectrum.push_back(0);
498  alphaintegral.push_back(0);
499  }
500 
501 
502  if (betagraph)
503  {
504  int np = betagraph->GetN();
505 
506  double *y = betagraph->GetY();
507  std::string name;
508  name = "RadioGen_";
509  name += nuclide;
510  name += "_Beta";
511  TH1D *betahist = (TH1D*) new TH1D(name.c_str(),"Beta Spectrum",np,0,np);
512 
513  for (int i=0; i<np; i++)
514  {
515  betahist->SetBinContent(i+1,y[i]);
516  betahist->SetBinError(i+1,0);
517  }
518  betaspectrum.push_back(betahist);
519  betaintegral.push_back(betahist->Integral());
520  }
521  else
522  {
523  betaspectrum.push_back(0);
524  betaintegral.push_back(0);
525  }
526 
527  if (gammagraph)
528  {
529  int np = gammagraph->GetN();
530  double *y = gammagraph->GetY();
531  std::string name;
532  name = "RadioGen_";
533  name += nuclide;
534  name += "_Gamma";
535  TH1D *gammahist = (TH1D*) new TH1D(name.c_str(),"Gamma Spectrum",np,0,np);
536  for (int i=0; i<np; i++)
537  {
538  gammahist->SetBinContent(i+1,y[i]);
539  gammahist->SetBinError(i+1,0);
540  }
541  gammaspectrum.push_back(gammahist);
542  gammaintegral.push_back(gammahist->Integral());
543  }
544  else
545  {
546  gammaspectrum.push_back(0);
547  gammaintegral.push_back(0);
548  }
549 
550  if (neutrongraph)
551  {
552  int np = neutrongraph->GetN();
553  double *y = neutrongraph->GetY();
554  std::string name;
555  name = "RadioGen_";
556  name += nuclide;
557  name += "_Neutron";
558  TH1D *neutronhist = (TH1D*) new TH1D(name.c_str(),"Neutron Spectrum",np,0,np);
559  for (int i=0; i<np; i++)
560  {
561  neutronhist->SetBinContent(i+1,y[i]);
562  neutronhist->SetBinError(i+1,0);
563  }
564  neutronspectrum.push_back(neutronhist);
565  neutronintegral.push_back(neutronhist->Integral());
566  }
567  else
568  {
569  neutronspectrum.push_back(0);
570  neutronintegral.push_back(0);
571  }
572 
573  f.Close();
574  TH1::AddDirectory(addStatus);
575 
576  double total = alphaintegral.back() + betaintegral.back() + gammaintegral.back() + neutronintegral.back();
577  if (total>0)
578  {
579  alphaintegral.back() /= total;
580  betaintegral.back() /= total;
581  gammaintegral.back() /= total;
582  neutronintegral.back() /= total;
583  }
584  }
585 
586 
587  void RadioGen::samplespectrum(std::string nuclide, int &itype, double &t, double &m, double &p)
588  {
589 
591  CLHEP::HepRandomEngine &engine = rng->getEngine();
592  CLHEP::RandFlat flat(engine);
593 
594  int inuc = -1;
595  for (size_t i=0; i<spectrumname.size(); i++)
596  {
597  if (nuclide == spectrumname[i])
598  {
599  inuc = i;
600  break;
601  }
602  }
603  if (inuc == -1)
604  {
605  t=0; // throw an exception in the future
606  itype = 0;
607  throw cet::exception("RadioGen") << "Ununderstood nuclide: " << nuclide << "\n";
608  }
609 
610  double rtype = flat.fire();
611 
612  itype = -1;
613  m = 0;
614  p = 0;
615  for (int itry=0;itry<10;itry++) // maybe a tiny normalization issue with a sum of 0.99999999999 or something, so try a few times.
616  {
617  if (rtype <= alphaintegral[inuc] && alphaspectrum[inuc] != 0)
618  {
619  itype = 1000020040; // alpha
620  m = m_alpha;
621  t = samplefromth1d(alphaspectrum[inuc])/1000000.0;
622  }
623  else if (rtype <= alphaintegral[inuc]+betaintegral[inuc] && betaspectrum[inuc] != 0)
624  {
625  itype = 11; // beta
626  m = m_e;
627  t = samplefromth1d(betaspectrum[inuc])/1000000.0;
628  }
629  else if ( rtype <= alphaintegral[inuc] + betaintegral[inuc] + gammaintegral[inuc] && gammaspectrum[inuc] != 0)
630  {
631  itype = 22; // gamma
632  m = 0;
633  t = samplefromth1d(gammaspectrum[inuc])/1000000.0;
634  }
635  else if( neutronspectrum[inuc] != 0)
636  {
637  itype = 2112;
638  m = m_neutron;
639  t = samplefromth1d(neutronspectrum[inuc])/1000000.0;
640  }
641  if (itype >= 0) break;
642  }
643  if (itype == -1)
644  {
645  throw cet::exception("RadioGen") << "Normalization problem with nuclide: " << nuclide << "\n";
646  }
647  double e = t + m;
648  p = e*e - m*m;
649  if (p>=0)
650  { p = TMath::Sqrt(p); }
651  else
652  { p=0; }
653  }
654 
655  // this is just a copy of TH1::GetRandom that uses the art-managed CLHEP random number generator instead of gRandom
656  // and a better handling of negative bin contents
657 
658  double RadioGen::samplefromth1d(TH1D *hist)
659  {
661  CLHEP::HepRandomEngine &engine = rng->getEngine();
662  CLHEP::RandFlat flat(engine);
663 
664  int nbinsx = hist->GetNbinsX();
665  std::vector<double> partialsum;
666  partialsum.resize(nbinsx+1);
667  partialsum[0] = 0;
668 
669  for (int i=1;i<=nbinsx;i++)
670  {
671  double hc = hist->GetBinContent(i);
672  if ( hc < 0) throw cet::exception("RadioGen") << "Negative bin: " << i << " " << hist->GetName() << "\n";
673  partialsum[i] = partialsum[i-1] + hc;
674  }
675  double integral = partialsum[nbinsx];
676  if (integral == 0) return 0;
677  // normalize to unit sum
678  for (int i=1;i<=nbinsx;i++) partialsum[i] /= integral;
679 
680  double r1 = flat.fire();
681  int ibin = TMath::BinarySearch(nbinsx,&(partialsum[0]),r1);
682  Double_t x = hist->GetBinLowEdge(ibin+1);
683  if (r1 > partialsum[ibin]) x +=
684  hist->GetBinWidth(ibin+1)*(r1-partialsum[ibin])/(partialsum[ibin+1] - partialsum[ibin]);
685  return x;
686  }
687 
688 
689  //Ar42 uses BNL tables for K-42 from Aug 2017
690  //beta channel 1. No Gamma. beta Q value 3525.22 keV
691  //beta channel 2. 1 Gamma (1524.6 keV). beta Q value 2000.62
692  //beta channel 3. 1 Gamma Channel. 312.6 keV + gamma 2. beta Q value 1688.02 keV
693  //beta channel 4. 2 Gamma Channels. Either 899.7 keV (i 0.052) + gamma 2 or 2424.3 keV (i 0.020). beta Q value 1100.92 keV
694  //beta channel 5. 3 gamma channels. 692.0 keV + 1228.0 keV + Gamma 2 (i 0.0033) ||OR|| 1021.2 keV + gamma 4 (i 0.0201) ||OR|| 1920.8 keV + gamma 2 (i 0.041). beta Q value 79.82 keV
695  //No Ar42Gamma1 as beta channel 1 does not produce a dexcitation gamma.
696  void RadioGen::Ar42Gamma2(std::vector<std::tuple<ti_PDGID, td_Mass, TLorentzVector>>& v_prods){
697  ti_PDGID pdgid = 22; td_Mass m = 0.0; //we are writing gammas
698  std::vector<double> vd_p = {.0015246};//Momentum in GeV
699  for(auto p : vd_p){
700  std::tuple<ti_PDGID, td_Mass, TLorentzVector> prods = std::make_tuple(pdgid, m, dirCalc(p,m));
701  v_prods.push_back(prods);
702  }
703  }
704  void RadioGen::Ar42Gamma3(std::vector<std::tuple<ti_PDGID, td_Mass, TLorentzVector>>& v_prods){
705  ti_PDGID pdgid = 22; td_Mass m = 0.0; //we are writing gammas
706  std::vector<double> vd_p = {.0003126};
707  for(auto p : vd_p){
708  std::tuple<ti_PDGID, td_Mass, TLorentzVector> prods = std::make_tuple(pdgid, m, dirCalc(p,m));
709  v_prods.push_back(prods);
710  }
711  Ar42Gamma2(v_prods);
712  }
713  void RadioGen::Ar42Gamma4(std::vector<std::tuple<ti_PDGID, td_Mass, TLorentzVector>>& v_prods){
715  CLHEP::HepRandomEngine &engine = rng->getEngine();
716  CLHEP::RandFlat flat(engine);
717  ti_PDGID pdgid = 22; td_Mass m = 0.0; //we are writing gammas
718  double chan1 = (0.052 / (0.052+0.020) );
719  if(flat.fire()<chan1){
720  std::vector<double> vd_p = {.0008997};//Momentum in GeV
721  for(auto p : vd_p){
722  std::tuple<ti_PDGID, td_Mass, TLorentzVector> prods = std::make_tuple(pdgid, m, dirCalc(p,m));
723  v_prods.push_back(prods);
724  }
725  Ar42Gamma2(v_prods);
726  }else{
727  std::vector<double> vd_p = {.0024243};//Momentum in GeV
728  for(auto p : vd_p){
729  std::tuple<ti_PDGID, td_Mass, TLorentzVector> prods = std::make_tuple(pdgid, m, dirCalc(p,m));
730  v_prods.push_back(prods);
731  }
732  }
733  }
734  void RadioGen::Ar42Gamma5(std::vector<std::tuple<ti_PDGID, td_Mass, TLorentzVector>>& v_prods){
736  CLHEP::HepRandomEngine &engine = rng->getEngine();
737  CLHEP::RandFlat flat(engine);
738  ti_PDGID pdgid = 22; td_Mass m = 0.0; //we are writing gammas
739  double chan1 = ( 0.0033 / (0.0033 + 0.0201 + 0.041) ); double chan2 = ( 0.0201 / (0.0033 + 0.0201 + 0.041) );
740  double chanPick = flat.fire();
741  if(chanPick < chan1){
742  std::vector<double> vd_p = {0.000692, 0.001228};//Momentum in GeV
743  for(auto p : vd_p){
744  std::tuple<ti_PDGID, td_Mass, TLorentzVector> prods = std::make_tuple(pdgid, m, dirCalc(p,m));
745  v_prods.push_back(prods);
746  }
747  Ar42Gamma2(v_prods);
748  }else if (chanPick<(chan1+chan2)){
749  std::vector<double> vd_p = {0.0010212};//Momentum in GeV
750  for(auto p : vd_p){
751  std::tuple<ti_PDGID, td_Mass, TLorentzVector> prods = std::make_tuple(pdgid, m, dirCalc(p,m));
752  v_prods.push_back(prods);
753  }
754  Ar42Gamma4(v_prods);
755  }else{
756  std::vector<double> vd_p = {0.0019208};//Momentum in GeV
757  for(auto p : vd_p){
758  std::tuple<ti_PDGID, td_Mass, TLorentzVector> prods = std::make_tuple(pdgid, m, dirCalc(p,m));
759  v_prods.push_back(prods);
760  }
761  Ar42Gamma2(v_prods);
762  }
763  }
764 
765  // phase space generator for beta decay -- keep it as a comment in case we ever want to revive it
766 
767  // double RadioGen::betaphasespace(double mass, double q)
768  //{
769  // art::ServiceHandle<art::RandomNumberGenerator> rng;
770  // CLHEP::HepRandomEngine &engine = rng->getEngine();
771  // CLHEP::RandFlat flat(engine);
772  // double p = 0;
773  // double mi = mass+q+m_e;
774  // TLorentzVector p0(0,0,0,mi); // pre-decay four-vector
775  // double masses[3] = {0,m_e,mass}; // neutrino, electron, nucleus
776  // rg.SetDecay(p0,3,masses);
777  // double wmax = rg.GetWtMax();
778  // for (int igen=0;igen<1000;igen++) // cap the retries at 1000
779  // {
780  // double weight = rg.Generate(); // want to unweight these if possible
781  // TLorentzVector *e4v = rg.GetDecay(1); // get electron four-vector
782  // p = e4v->P();
783  // if (weight >= wmax * flat.fire()) break;
784  // }
785  //return p;
786  //}
787 
788 
789 
790 
791 }//end namespace evgen
792 
793 namespace evgen{
794 
796 
797 }//end namespace evgen
798 
799 #endif
800 
Float_t x
Definition: compare.C:6
void AddTrajectoryPoint(TLorentzVector const &position, TLorentzVector const &momentum)
Definition: MCParticle.h:261
std::vector< double > fZ0
Bottom corner z position (cm) in world coordinates.
void SetOrigin(simb::Origin_t origin)
Definition: MCTruth.h:78
std::vector< std::string > fNuclide
List of nuclides to simulate. Example: "39Ar".
Float_t y
Definition: compare.C:6
art::ProductID put(std::unique_ptr< PROD > &&)
Definition: Run.h:148
std::vector< TH1D * > alphaspectrum
Particle class.
void Add(simb::MCParticle &part)
Definition: MCTruth.h:77
TGeoManager * ROOTGeoManager() const
Access to the ROOT geometry description manager.
std::vector< double > neutronintegral
Definition: Run.h:30
std::vector< std::string > spectrumname
std::vector< TH1D * > neutronspectrum
TFile f
Definition: plotHisto.C:6
ProductID put(std::unique_ptr< PROD > &&product)
Definition: Event.h:102
auto vector(Vector const &v)
Returns a manipulator which will print the specified array.
Definition: DumpUtils.h:265
std::vector< TH1D * > betaspectrum
base_engine_t & getEngine() const
std::vector< double > fT1
End of time window to simulate in ns.
std::vector< double > fY1
Top corner y position (cm) in world coordinates.
#define DEFINE_ART_MODULE(klass)
Definition: ModuleMacros.h:42
std::string DetectorName() const
Returns a string with the name of the detector, as configured.
std::vector< double > fZ1
Top corner z position (cm) in world coordinates.
single particles thrown at the detector
Definition: MCTruth.h:24
T get(std::string const &key) const
Definition: ParameterSet.h:231
TString part[npart]
Definition: Style.C:32
int trackidcounter
Serial number for the MC track ID.
double energy
Definition: plottest35.C:25
An art service to assist in the distribution of guaranteed unique seeds to all engines within an art ...
Framework includes.
std::vector< double > alphaintegral
std::vector< double > betaintegral
TH2F * hist
Definition: plot.C:136
std::vector< double > fT0
Beginning of time window to simulate in ns.
#define LOG_DEBUG(id)
std::vector< std::string > fMaterial
List of regexes of materials in which to generate the decays. Example: "LAr".
std::vector< double > fX0
Bottom corner x position (cm) in world coordinates.
TCEvent evt
Definition: DataStructs.cxx:5
Event generator information.
Definition: MCTruth.h:30
Float_t e
Definition: plot.C:34
std::vector< double > gammaintegral
std::vector< double > fBq
Radioactivity in Becquerels (decay per sec) per cubic cm.
Namespace collecting geometry-related classes utilities.
Event Generation using GENIE, cosmics or single particles.
std::vector< double > fX1
Top corner x position (cm) in world coordinates.
art framework interface to geometry description
cet::coded_exception< error, detail::translate > exception
Definition: exception.h:33
std::vector< TH1D * > gammaspectrum
std::vector< double > fY0
Bottom corner y position (cm) in world coordinates.