LArSoft  v10_04_05
Liquid Argon Software toolkit - https://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.
30 //
31 // Aug 2023 L Paulucci
32 // Adding a fhicl parameter for generic path to radiogen files
34 
35 // C++ includes.
36 #include <cassert>
37 #include <cmath>
38 #include <iterator>
39 #include <memory>
40 #include <regex>
41 #include <string>
42 #include <sys/stat.h>
43 #include <utility> // std::pair<>, std::tuple<>
44 #include <vector>
45 
46 // Framework includes
53 #include "cetlib/exempt_ptr.h"
54 #include "cetlib/search_path.h"
55 #include "cetlib_except/exception.h"
56 #include "fhiclcpp/ParameterSet.h"
58 
59 // nurandom includes
61 
62 // nusimdata, nugen includes
66 
67 // lar includes
75 #include "larcorealg/Geometry/geo_vectors_utils.h" // geo::...::makeFromCoords()
84 
85 // root includes
86 
87 #include "TFile.h"
88 #include "TGenPhaseSpace.h"
89 #include "TGeoBBox.h"
90 #include "TGeoManager.h"
91 #include "TGeoMaterial.h"
92 #include "TGeoNode.h"
93 #include "TGeoVolume.h"
94 #include "TGraph.h"
95 #include "TH1.h"
96 #include "TH1D.h"
97 #include "TLorentzVector.h"
98 #include "TMath.h"
99 
100 #include "range/v3/view/enumerate.hpp"
101 
102 #include "CLHEP/Random/RandFlat.h"
103 #include "CLHEP/Random/RandPoisson.h"
104 
105 namespace simb {
106  class MCTruth;
107 }
108 
109 namespace evgen {
110 
194  class RadioGen : public art::EDProducer {
195  public:
196  explicit RadioGen(fhicl::ParameterSet const& pset);
197 
198  private:
199  // This is called for each event.
200  void produce(art::Event& evt);
201  void beginRun(art::Run& run);
202 
203  typedef int
204  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
205  typedef double
206  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
207 
210  std::size_t addvolume(std::string const& volumeName);
211 
213  void SampleOne(unsigned int i, simb::MCTruth& mct);
214 
215  TLorentzVector dirCalc(double p, double m);
216 
217  void readfile(std::string nuclide, std::string const& filename, std::string const& PathToFile);
218  void samplespectrum(std::string nuclide, int& itype, double& t, double& m, double& p);
219 
220  void Ar42Gamma2(std::vector<std::tuple<ti_PDGID, td_Mass, TLorentzVector>>& v_prods);
221  void Ar42Gamma3(std::vector<std::tuple<ti_PDGID, td_Mass, TLorentzVector>>& v_prods);
222  void Ar42Gamma4(std::vector<std::tuple<ti_PDGID, td_Mass, TLorentzVector>>& v_prods);
223  void Ar42Gamma5(std::vector<std::tuple<ti_PDGID, td_Mass, TLorentzVector>>& v_prods);
224 
225  // recoded so as to use the LArSoft-managed random number generator
226  double samplefromth1d(TH1D& hist);
227 
229  template <typename Stream>
230  void dumpNuclideSettings(Stream&& out,
231  std::size_t iNucl,
232  std::string const& volumeName = {}) const;
233 
235  std::pair<double, double> defaulttimewindow() const;
236 
237  // itype = pdg code: 1000020040: alpha. itype=11: beta. -11: positron, itype=22: gamma. -1: error
238  // t is the kinetic energy in GeV (= E-m)
239  // m = mass in GeV
240  // p = momentum in GeV
241 
242  //double betaphasespace(double mass, double q); // older parameterization.
243 
244  // the generator randomly samples points in a rectangular prism of space and time, and only selects those points in
245  // volumes with materials that match the regexes in fMaterial. One can use wildcards * and ? for broader matches.
246 
247  std::vector<std::string> fNuclide;
248  std::vector<std::string>
250  std::vector<double> fBq;
251  std::vector<double> fT0;
252  std::vector<double> fT1;
253  std::vector<double> fX0;
254  std::vector<double> fY0;
255  std::vector<double> fZ0;
256  std::vector<double> fX1;
257  std::vector<double> fY1;
258  std::vector<double> fZ1;
261  std::string fPathToFile;
262 
263  // leftovers from the phase space generator
264  // const double gevperamu = 0.931494061;
265  // TGenPhaseSpace rg; // put this here so we don't constantly construct and destruct it
266 
267  std::vector<std::string> spectrumname;
268  std::vector<std::unique_ptr<TH1D>> alphaspectrum;
269  std::vector<double> alphaintegral;
270  std::vector<std::unique_ptr<TH1D>> betaspectrum;
271  std::vector<double> betaintegral;
272  std::vector<std::unique_ptr<TH1D>> gammaspectrum;
273  std::vector<double> gammaintegral;
274  std::vector<std::unique_ptr<TH1D>> neutronspectrum;
275  std::vector<double> neutronintegral;
276  CLHEP::HepRandomEngine& fEngine;
277  };
278 }
279 
280 namespace {
281  constexpr double m_e = 0.000510998928; // mass of electron in GeV
282  constexpr double m_alpha = 3.727379240; // mass of an alpha particle in GeV
283  constexpr double m_neutron = 0.9395654133; // mass of a neutron in GeV
284 }
285 
286 namespace evgen {
287 
288  //____________________________________________________________________________
289  RadioGen::RadioGen(fhicl::ParameterSet const& pset)
290  : EDProducer{pset}
291  , fX0{pset.get<std::vector<double>>("X0", {})}
292  , fY0{pset.get<std::vector<double>>("Y0", {})}
293  , fZ0{pset.get<std::vector<double>>("Z0", {})}
294  , fX1{pset.get<std::vector<double>>("X1", {})}
295  , fY1{pset.get<std::vector<double>>("Y1", {})}
296  , fZ1{pset.get<std::vector<double>>("Z1", {})}
297  , fIsFirstSignalSpecial{pset.get<bool>("IsFirstSignalSpecial", false)}
298  // create a default random engine; obtain the random seed from NuRandomService,
299  // unless overridden in configuration with key "Seed"
300  , fEngine(art::ServiceHandle<rndm::NuRandomService>()->registerAndSeedEngine(createEngine(0),
301  pset,
302  "Seed"))
303  {
304  produces<std::vector<simb::MCTruth>>();
305  produces<sumdata::RunData, art::InRun>();
306 
307  auto const fPathToFile = pset.get<std::string>("PathToFile", "Radionuclides/");
308  auto const nuclide = pset.get<std::vector<std::string>>("Nuclide");
309  auto const material = pset.get<std::vector<std::string>>("Material");
310  auto const Bq = pset.get<std::vector<double>>("BqPercc");
311  auto t0 = pset.get<std::vector<double>>("T0", {});
312  auto t1 = pset.get<std::vector<double>>("T1", {});
313 
314  if (fPathToFile.find_last_of('/') !=
315  fPathToFile.length() - 1) { //Checking that last character in path is a forward slash
316  throw cet::exception("RadioGen")
317  << "Last entry of the path to radiological files needs to be a forward slash ('/')\n";
318  }
319 
320  if (fT0.empty() || fT1.empty()) { // better be both empty...
321  if (!fT0.empty() || !fT1.empty()) {
323  << "RadioGen T0 and T1 need to be both non-empty, or both empty"
324  " (now T0 has "
325  << fT0.size() << " entries and T1 has " << fT0.size() << ")\n";
326  }
327  auto const [defaultT0, defaultT1] = defaulttimewindow();
328  t0.push_back(defaultT0);
329  t1.push_back(defaultT1);
330  }
331 
332  //
333  // First, the materials are assigned to the coordinates explicitly
334  // configured; then, each of the remaining materials is assigned to one
335  // of the named volumes.
336  // TODO: reaction to mismatches is "not so great".
337  //
338  mf::LogInfo("RadioGen") << "Configuring activity:";
339  for (std::size_t iCoord : util::counter(fX0.size())) {
340 
341  fNuclide.push_back(nuclide.at(iCoord));
342  fMaterial.push_back(material.at(iCoord));
343  fBq.push_back(Bq.at(iCoord));
344  // replicate the last timing if none is specified
345  fT0.push_back(t0[std::min(iCoord, t0.size() - 1U)]);
346  fT1.push_back(t1[std::min(iCoord, t1.size() - 1U)]);
347 
348  dumpNuclideSettings(mf::LogVerbatim("RadioGen"), iCoord);
349 
350  } // for
351 
352  std::size_t iNext = fX0.size();
353  auto const volumes = pset.get<std::vector<std::string>>("Volumes", {});
354  for (auto&& [iVolume, volName] : volumes | ranges::views::enumerate) {
355  // this expands the coordinate vectors
356  auto const nVolumes = addvolume(volName);
357  if (nVolumes == 0) {
359  << "No volume named '" << volName << "' was found!\n";
360  }
361 
362  std::size_t const iVolFirst = fNuclide.size();
363  for (auto iVolInstance : util::counter(nVolumes)) {
364  fNuclide.push_back(nuclide.at(iNext));
365  fMaterial.push_back(material.at(iNext));
366  fBq.push_back(Bq.at(iNext));
367  // replicate the last timing if none is specified
368  fT0.push_back(t0[std::min(iNext, t0.size() - 1U)]);
369  fT1.push_back(t1[std::min(iNext, t1.size() - 1U)]);
370 
371  dumpNuclideSettings(mf::LogVerbatim("RadioGen"), iVolFirst + iVolInstance, volName);
372  }
373  ++iNext;
374  } // for
375 
376  // check for consistency of vector sizes
377  unsigned int nsize = fNuclide.size();
378  if (nsize == 0) {
379  throw art::Exception(art::errors::Configuration) << "No nuclide configured!\n";
380  }
381 
382  if (fMaterial.size() != nsize)
383  throw cet::exception("RadioGen") << "Different size Material vector and Nuclide vector\n";
384  if (fBq.size() != nsize)
385  throw cet::exception("RadioGen") << "Different size Bq vector and Nuclide vector\n";
386  if (fT0.size() != nsize)
387  throw cet::exception("RadioGen") << "Different size T0 vector and Nuclide vector\n";
388  if (fT1.size() != nsize)
389  throw cet::exception("RadioGen") << "Different size T1 vector and Nuclide vector\n";
390  if (fX0.size() != nsize)
391  throw cet::exception("RadioGen") << "Different size X0 vector and Nuclide vector\n";
392  if (fY0.size() != nsize)
393  throw cet::exception("RadioGen") << "Different size Y0 vector and Nuclide vector\n";
394  if (fZ0.size() != nsize)
395  throw cet::exception("RadioGen") << "Different size Z0 vector and Nuclide vector\n";
396  if (fX1.size() != nsize)
397  throw cet::exception("RadioGen") << "Different size X1 vector and Nuclide vector\n";
398  if (fY1.size() != nsize)
399  throw cet::exception("RadioGen") << "Different size Y1 vector and Nuclide vector\n";
400  if (fZ1.size() != nsize)
401  throw cet::exception("RadioGen") << "Different size Z1 vector and Nuclide vector\n";
402 
403  for (std::string& nuclideName : fNuclide) {
404  if (nuclideName == "39Ar") { readfile("39Ar", "Argon_39.root", fPathToFile); }
405  else if (nuclideName == "60Co") {
406  readfile("60Co", "Cobalt_60.root", fPathToFile);
407  }
408  else if (nuclideName == "85Kr") {
409  readfile("85Kr", "Krypton_85.root", fPathToFile);
410  }
411  else if (nuclideName == "40K") {
412  readfile("40K", "Potassium_40.root", fPathToFile);
413  }
414  else if (nuclideName == "232Th") {
415  readfile("232Th", "Thorium_232.root", fPathToFile);
416  }
417  else if (nuclideName == "238U") {
418  readfile("238U", "Uranium_238.root", fPathToFile);
419  }
420  else if (nuclideName == "222Rn") {
421  continue;
422  } //Rn222 is handled separately later
423  else if (nuclideName == "59Ni") {
424  continue;
425  } //Rn222 is handled separately later
426  else if (nuclideName == "42Ar") {
427  readfile(
428  "42Ar_1",
429  "Argon_42_1.root",
430  fPathToFile); //Each possible beta decay mode of Ar42 is given it's own .root file for now.
431  readfile(
432  "42Ar_2",
433  "Argon_42_2.root",
434  fPathToFile); //This allows us to know which decay chain to follow for the dexcitation gammas.
435  readfile(
436  "42Ar_3",
437  "Argon_42_3.root",
438  fPathToFile); //The dexcitation gammas are not included in the root files as we want to
439  readfile(
440  "42Ar_4",
441  "Argon_42_4.root",
442  fPathToFile); //probabilistically simulate the correct coincident gammas, which we cannot guarantee
443  readfile("42Ar_5", "Argon_42_5.root", fPathToFile); //by sampling a histogram.
444  continue;
445  } //Ar42 is handeled separately later
446  else {
447  std::string searchName = nuclideName;
448  searchName += ".root";
449  readfile(nuclideName, searchName, fPathToFile);
450  }
451  }
452  }
453 
454  //____________________________________________________________________________
456  {
458  run.put(std::make_unique<sumdata::RunData>(geo->DetectorName()), art::fullRun());
459  }
460 
461  //____________________________________________________________________________
463  {
465  std::unique_ptr<std::vector<simb::MCTruth>> truthcol(new std::vector<simb::MCTruth>);
466 
467  simb::MCTruth truth;
469 
470  trackidcounter = -1;
471  for (unsigned int i = 0; i < fNuclide.size(); ++i) {
472  SampleOne(i, truth);
473  } //end loop over nuclides
474 
475  MF_LOG_DEBUG("RadioGen") << truth;
476  truthcol->push_back(truth);
477  evt.put(std::move(truthcol));
478  }
479 
480  //____________________________________________________________________________
481  std::size_t RadioGen::addvolume(std::string const& volumeName)
482  {
483 
484  auto const& geom = *(lar::providerFrom<geo::Geometry>());
485 
486  std::vector<geo::GeoNodePath> volumePaths;
487  auto findVolume = [&volumePaths, volumeName](auto& path) {
488  if (path.current()->GetVolume()->GetName() == volumeName) volumePaths.push_back(path);
489  return true;
490  };
491 
492  geo::ROOTGeometryNavigator navigator{*(geom.ROOTGeoManager())};
493 
494  navigator.apply(findVolume);
495 
496  for (geo::GeoNodePath const& path : volumePaths) {
497 
498  //
499  // find the coordinates of the volume in local coordinates
500  //
501  TGeoShape const* pShape = path.current()->GetVolume()->GetShape();
502  auto pBox = dynamic_cast<TGeoBBox const*>(pShape);
503  if (!pBox) {
504  throw cet::exception("RadioGen") << "Volume '" << path.current()->GetName() << "' is a "
505  << pShape->IsA()->GetName() << ", not a TGeoBBox.\n";
506  }
507 
508  geo::Point_t const origin = geo::vect::makeFromCoords<geo::Point_t>(pBox->GetOrigin());
509  geo::Vector_t const diag = {pBox->GetDX(), pBox->GetDY(), pBox->GetDZ()};
510 
511  //
512  // convert to world coordinates
513  //
514 
515  auto const trans = path.currentTransformation<geo::TransformationMatrix>();
516 
517  geo::Point_t min, max;
518  trans.Transform(origin - diag, min);
519  trans.Transform(origin + diag, max);
520 
521  //
522  // add to the coordinates
523  //
524  fX0.push_back(std::min(min.X(), max.X()));
525  fY0.push_back(std::min(min.Y(), max.Y()));
526  fZ0.push_back(std::min(min.Z(), max.Z()));
527  fX1.push_back(std::max(min.X(), max.X()));
528  fY1.push_back(std::max(min.Y(), max.Y()));
529  fZ1.push_back(std::max(min.Z(), max.Z()));
530 
531  } // for
532 
533  return volumePaths.size();
534  } // RadioGen::addvolume()
535 
536  //____________________________________________________________________________
537  // Generate radioactive decays per nuclide per volume according to the FCL parameters
538 
539  void RadioGen::SampleOne(unsigned int i, simb::MCTruth& mct)
540  {
542  TGeoManager* geomanager = geo->ROOTGeoManager();
543 
544  CLHEP::RandFlat flat(fEngine);
545  CLHEP::RandPoisson poisson(fEngine);
546 
547  // figure out how many decays to generate, assuming that the entire prism consists of the radioactive material.
548  // we will skip over decays in other materials later.
549 
550  double rate =
551  fabs(fBq[i] * (fT1[i] - fT0[i]) * (fX1[i] - fX0[i]) * (fY1[i] - fY0[i]) * (fZ1[i] - fZ0[i])) /
552  1.0E9;
553  long ndecays = poisson.shoot(rate);
554 
555  std::regex const re_material{fMaterial[i]};
556  for (unsigned int idecay = 0; idecay < ndecays; idecay++) {
557  // generate just one particle at a time. Need a little recoding if a radioactive
558  // decay generates multiple daughters that need simulation
559  // uniformly distributed in position and time
560  //
561  // JStock: Leaving this as a single position for the decay products. For now I will assume they all come from the same spot.
562  TLorentzVector pos(
563  fX0[i] + flat.fire() * (fX1[i] - fX0[i]),
564  fY0[i] + flat.fire() * (fY1[i] - fY0[i]),
565  fZ0[i] + flat.fire() * (fZ1[i] - fZ0[i]),
566  (idecay == 0 && fIsFirstSignalSpecial) ? 0 : (fT0[i] + flat.fire() * (fT1[i] - fT0[i])));
567 
568  // discard decays that are not in the proper material
569  std::string volmaterial =
570  geomanager->FindNode(pos.X(), pos.Y(), pos.Z())->GetMedium()->GetMaterial()->GetName();
571  if (!std::regex_match(volmaterial, re_material)) continue;
572 
573  //Moved pdgid into the next statement, so that it is localized.
574  // electron=11, photon=22, alpha = 1000020040, neutron = 2112
575 
576  //JStock: Allow us to have different particles from the same decay. This requires multiple momenta.
577  std::vector<std::tuple<ti_PDGID, td_Mass, TLorentzVector>>
578  v_prods; //(First is for PDGID, second is mass, third is Momentum)
579 
580  if (fNuclide[i] == "222Rn") // Treat 222Rn separately
581  {
582  double p = 0;
583  double t = 0.00548952;
584  td_Mass m = m_alpha;
585  ti_PDGID pdgid = 1000020040; //td_Mass = double. ti_PDGID = int;
586  double energy = t + m;
587  double p2 = energy * energy - m * m;
588  if (p2 > 0)
589  p = TMath::Sqrt(p2);
590  else
591  p = 0;
592  v_prods.emplace_back(pdgid, m, dirCalc(p, m));
593  } //End special case RN222
594  else if (
595  fNuclide[i] ==
596  "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.
597  double p = 0.008997;
598  td_Mass m = 0;
599  ti_PDGID pdgid =
600  22; // td_Mas=double. ti_PDFID=int. Assigning p directly, as t=p for gammas.
601  v_prods.emplace_back(pdgid, m, dirCalc(p, m));
602  } //end special case Ni59 calibration source
603  else if (fNuclide[i] == "42Ar") { // Spot for special treatment of Ar42.
604  double p = 0;
605  double t = 0;
606  td_Mass m = 0;
607  ti_PDGID pdgid = 0; //td_Mass = double. ti_PDGID = int;
608  double bSelect = flat.fire(); //Make this a random number from 0 to 1.
609  if (bSelect < 0.819) { //beta channel 1. No Gamma. beta Q value 3525.22 keV
610  samplespectrum("42Ar_1", pdgid, t, m, p);
611  v_prods.emplace_back(pdgid, m, dirCalc(p, m));
612  //No gamma here.
613  }
614  else if (bSelect < 0.9954) { //beta channel 2. 1 Gamma (1524.6 keV). beta Q value 2000.62
615  samplespectrum("42Ar_2", pdgid, t, m, p);
616  v_prods.emplace_back(pdgid, m, dirCalc(p, m));
617  Ar42Gamma2(v_prods);
618  }
619  else if (
620  bSelect <
621  0.9988) { //beta channel 3. 1 Gamma Channel. 312.6 keV + gamma 2. beta Q value 1688.02 keV
622  samplespectrum("42Ar_3", pdgid, t, m, p);
623  v_prods.emplace_back(pdgid, m, dirCalc(p, m));
624  Ar42Gamma3(v_prods);
625  }
626  else if (
627  bSelect <
628  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
629  samplespectrum("42Ar_4", pdgid, t, m, p);
630  v_prods.emplace_back(pdgid, m, dirCalc(p, m));
631  Ar42Gamma4(v_prods);
632  }
633  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
634  samplespectrum("42Ar_5", pdgid, t, m, p);
635  v_prods.emplace_back(pdgid, m, dirCalc(p, m));
636  Ar42Gamma5(v_prods);
637  }
638  //Add beta.
639  //Call gamma function for beta mode.
640  }
641  else { //General Case.
642  double p = 0;
643  double t = 0;
644  td_Mass m = 0;
645  ti_PDGID pdgid = 0; //td_Mass = double. ti_PDGID = int;
646  samplespectrum(fNuclide[i], pdgid, t, m, p);
647  std::tuple<ti_PDGID, td_Mass, TLorentzVector> partMassMom =
648  std::make_tuple(pdgid, m, dirCalc(p, m));
649  v_prods.push_back(partMassMom);
650  } //end else (not RN or other special case
651 
652  //JStock: Modify this to now loop over the v_prods.
653  for (auto prodEntry : v_prods) {
654  // set track id to a negative serial number as these are all primary particles and have id <= 0
655  int trackid = trackidcounter;
656  ti_PDGID pdgid = std::get<0>(prodEntry);
657  td_Mass m = std::get<1>(prodEntry);
658  TLorentzVector pvec = std::get<2>(prodEntry);
659  trackidcounter--;
660  std::string primary("primary");
661 
662  // alpha particles need a little help since they're not in the TDatabasePDG table
663  // // so don't rely so heavily on default arguments to the MCParticle constructor
664  if (pdgid == 1000020040) {
665  simb::MCParticle part(trackid, pdgid, primary, -1, m, 1);
666  part.AddTrajectoryPoint(pos, pvec);
667  mct.Add(part);
668  } // end "If alpha"
669  else {
670  simb::MCParticle part(trackid, pdgid, primary);
671  part.AddTrajectoryPoint(pos, pvec);
672  mct.Add(part);
673  } // end All standard cases.
674  } //End Loop over all particles produces in this single decay.
675  }
676  }
677 
678  //Calculate an arbitrary direction with a given magnitude p
679  TLorentzVector RadioGen::dirCalc(double p, double m)
680  {
681  CLHEP::RandFlat flat(fEngine);
682  // isotropic production angle for the decay product
683  double costheta = (2.0 * flat.fire() - 1.0);
684  if (costheta < -1.0) costheta = -1.0;
685  if (costheta > 1.0) costheta = 1.0;
686  double const sintheta = sqrt(1.0 - costheta * costheta);
687  double const phi = 2.0 * M_PI * flat.fire();
688  return TLorentzVector{p * sintheta * std::cos(phi),
689  p * sintheta * std::sin(phi),
690  p * costheta,
691  std::sqrt(p * p + m * m)};
692  }
693 
694  // only reads those files that are on the fNuclide list. Copy information from the TGraphs to TH1D's
695 
696  void RadioGen::readfile(std::string nuclide,
697  std::string const& filename,
698  std::string const& PathToFile)
699  {
700  bool found{false};
701  std::regex const re_argon{"42Ar.*"};
702  for (size_t i = 0; i < fNuclide.size(); i++) {
703  if (
704  fNuclide[i] ==
705  nuclide) { //This check makes sure that the nuclide we are searching for is in fact in our fNuclide list. Ar42 handeled separately.
706  found = true;
707  break;
708  } //End If nuclide is in our list. Next is the special case of Ar42
709  else if (std::regex_match(nuclide, re_argon) && fNuclide[i] == "42Ar") {
710  found = true;
711  break;
712  }
713  }
714  if (!found) return;
715 
716  Bool_t addStatus = TH1::AddDirectoryStatus();
717  TH1::AddDirectory(
718  kFALSE); // cloned histograms go in memory, and aren't deleted when files are closed.
719  // be sure to restore this state when we're out of the routine.
720 
721  spectrumname.push_back(nuclide);
722  cet::search_path sp("FW_SEARCH_PATH");
723  std::string fn2 = PathToFile;
724  fn2 += filename;
725  std::string fullname;
726  sp.find_file(fn2, fullname);
727  struct stat sb;
728  if (fullname.empty() || stat(fullname.c_str(), &sb) != 0)
729  throw cet::exception("RadioGen")
730  << "Input spectrum file " << fn2 << " not found in FW_SEARCH_PATH!\n";
731 
732  TFile f(fullname.c_str(), "READ");
733  TGraph* alphagraph = (TGraph*)f.Get("Alphas");
734  TGraph* betagraph = (TGraph*)f.Get("Betas");
735  TGraph* gammagraph = (TGraph*)f.Get("Gammas");
736  TGraph* neutrongraph = (TGraph*)f.Get("Neutrons");
737 
738  if (alphagraph) {
739  int np = alphagraph->GetN();
740  double* y = alphagraph->GetY();
741  std::string name;
742  name = "RadioGen_";
743  name += nuclide;
744  name += "_Alpha";
745  auto alphahist = std::make_unique<TH1D>(name.c_str(), "Alpha Spectrum", np, 0, np);
746  for (int i = 0; i < np; i++) {
747  alphahist->SetBinContent(i + 1, y[i]);
748  alphahist->SetBinError(i + 1, 0);
749  }
750  alphaintegral.push_back(alphahist->Integral());
751  alphaspectrum.push_back(move(alphahist));
752  }
753  else {
754  alphaintegral.push_back(0);
755  alphaspectrum.push_back(nullptr);
756  }
757 
758  if (betagraph) {
759  int np = betagraph->GetN();
760 
761  double* y = betagraph->GetY();
762  std::string name;
763  name = "RadioGen_";
764  name += nuclide;
765  name += "_Beta";
766  auto betahist = std::make_unique<TH1D>(name.c_str(), "Beta Spectrum", np, 0, np);
767  for (int i = 0; i < np; i++) {
768  betahist->SetBinContent(i + 1, y[i]);
769  betahist->SetBinError(i + 1, 0);
770  }
771  betaintegral.push_back(betahist->Integral());
772  betaspectrum.push_back(move(betahist));
773  }
774  else {
775  betaintegral.push_back(0);
776  betaspectrum.push_back(nullptr);
777  }
778 
779  if (gammagraph) {
780  int np = gammagraph->GetN();
781  double* y = gammagraph->GetY();
782  std::string name;
783  name = "RadioGen_";
784  name += nuclide;
785  name += "_Gamma";
786  auto gammahist = std::make_unique<TH1D>(name.c_str(), "Gamma Spectrum", np, 0, np);
787  for (int i = 0; i < np; i++) {
788  gammahist->SetBinContent(i + 1, y[i]);
789  gammahist->SetBinError(i + 1, 0);
790  }
791  gammaintegral.push_back(gammahist->Integral());
792  gammaspectrum.push_back(move(gammahist));
793  }
794  else {
795  gammaintegral.push_back(0);
796  gammaspectrum.push_back(nullptr);
797  }
798 
799  if (neutrongraph) {
800  int np = neutrongraph->GetN();
801  double* y = neutrongraph->GetY();
802  std::string name;
803  name = "RadioGen_";
804  name += nuclide;
805  name += "_Neutron";
806  auto neutronhist = std::make_unique<TH1D>(name.c_str(), "Neutron Spectrum", np, 0, np);
807  for (int i = 0; i < np; i++) {
808  neutronhist->SetBinContent(i + 1, y[i]);
809  neutronhist->SetBinError(i + 1, 0);
810  }
811  neutronintegral.push_back(neutronhist->Integral());
812  neutronspectrum.push_back(move(neutronhist));
813  }
814  else {
815  neutronintegral.push_back(0);
816  neutronspectrum.push_back(nullptr);
817  }
818 
819  f.Close();
820  TH1::AddDirectory(addStatus);
821 
822  double total =
823  alphaintegral.back() + betaintegral.back() + gammaintegral.back() + neutronintegral.back();
824  if (total > 0) {
825  alphaintegral.back() /= total;
826  betaintegral.back() /= total;
827  gammaintegral.back() /= total;
828  neutronintegral.back() /= total;
829  }
830  }
831 
832  void RadioGen::samplespectrum(std::string nuclide, int& itype, double& t, double& m, double& p)
833  {
834  CLHEP::RandFlat flat(fEngine);
835 
836  int inuc = -1;
837  for (size_t i = 0; i < spectrumname.size(); i++) {
838  if (nuclide == spectrumname[i]) {
839  inuc = i;
840  break;
841  }
842  }
843  if (inuc == -1) {
844  t = 0; // throw an exception in the future
845  itype = 0;
846  throw cet::exception("RadioGen") << "Ununderstood nuclide: " << nuclide << "\n";
847  }
848 
849  double rtype = flat.fire();
850 
851  itype = -1;
852  m = 0;
853  p = 0;
854  for (
855  int itry = 0; itry < 10;
856  itry++) // maybe a tiny normalization issue with a sum of 0.99999999999 or something, so try a few times.
857  {
858  if (rtype <= alphaintegral[inuc] && alphaspectrum[inuc] != nullptr) {
859  itype = 1000020040; // alpha
860  m = m_alpha;
861  t = samplefromth1d(*alphaspectrum[inuc]) / 1000000.0;
862  }
863  else if (rtype <= alphaintegral[inuc] + betaintegral[inuc] && betaspectrum[inuc] != nullptr) {
864  itype = 11; // beta
865  m = m_e;
866  t = samplefromth1d(*betaspectrum[inuc]) / 1000000.0;
867  }
868  else if (rtype <= alphaintegral[inuc] + betaintegral[inuc] + gammaintegral[inuc] &&
869  gammaspectrum[inuc] != nullptr) {
870  itype = 22; // gamma
871  m = 0;
872  t = samplefromth1d(*gammaspectrum[inuc]) / 1000000.0;
873  }
874  else if (neutronspectrum[inuc] != nullptr) {
875  itype = 2112;
876  m = m_neutron;
877  t = samplefromth1d(*neutronspectrum[inuc]) / 1000000.0;
878  }
879  if (itype >= 0) break;
880  }
881  if (itype == -1) {
882  throw cet::exception("RadioGen")
883  << "Normalization problem with nuclide: " << nuclide << "\n";
884  }
885  double e = t + m;
886  p = e * e - m * m;
887  if (p >= 0) { p = TMath::Sqrt(p); }
888  else {
889  p = 0;
890  }
891  }
892 
893  // this is just a copy of TH1::GetRandom that uses the art-managed CLHEP random number generator instead of gRandom
894  // and a better handling of negative bin contents
895 
897  {
898  CLHEP::RandFlat flat(fEngine);
899 
900  int nbinsx = hist.GetNbinsX();
901  std::vector<double> partialsum;
902  partialsum.resize(nbinsx + 1);
903  partialsum[0] = 0;
904 
905  for (int i = 1; i <= nbinsx; i++) {
906  double hc = hist.GetBinContent(i);
907  if (hc < 0)
908  throw cet::exception("RadioGen") << "Negative bin: " << i << " " << hist.GetName() << "\n";
909  partialsum[i] = partialsum[i - 1] + hc;
910  }
911  double integral = partialsum[nbinsx];
912  if (integral == 0) return 0;
913  // normalize to unit sum
914  for (int i = 1; i <= nbinsx; i++)
915  partialsum[i] /= integral;
916 
917  double r1 = flat.fire();
918  int ibin = TMath::BinarySearch(nbinsx, &(partialsum[0]), r1);
919  Double_t x = hist.GetBinLowEdge(ibin + 1);
920  if (r1 > partialsum[ibin]) {
921  x += hist.GetBinWidth(ibin + 1) * (r1 - partialsum[ibin]) /
922  (partialsum[ibin + 1] - partialsum[ibin]);
923  }
924  return x;
925  }
926 
927  //Ar42 uses BNL tables for K-42 from Aug 2017
928  //beta channel 1. No Gamma. beta Q value 3525.22 keV
929  //beta channel 2. 1 Gamma (1524.6 keV). beta Q value 2000.62
930  //beta channel 3. 1 Gamma Channel. 312.6 keV + gamma 2. beta Q value 1688.02 keV
931  //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
932  //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
933  //No Ar42Gamma1 as beta channel 1 does not produce a dexcitation gamma.
934  void RadioGen::Ar42Gamma2(std::vector<std::tuple<ti_PDGID, td_Mass, TLorentzVector>>& v_prods)
935  {
936  ti_PDGID pdgid = 22;
937  td_Mass m = 0.0; //we are writing gammas
938  std::vector<double> vd_p = {.0015246}; //Momentum in GeV
939  for (auto p : vd_p) {
940  v_prods.emplace_back(pdgid, m, dirCalc(p, m));
941  }
942  }
943 
944  void RadioGen::Ar42Gamma3(std::vector<std::tuple<ti_PDGID, td_Mass, TLorentzVector>>& v_prods)
945  {
946  ti_PDGID pdgid = 22;
947  td_Mass m = 0.0; //we are writing gammas
948  std::vector<double> vd_p = {.0003126};
949  for (auto p : vd_p) {
950  v_prods.emplace_back(pdgid, m, dirCalc(p, m));
951  }
952  Ar42Gamma2(v_prods);
953  }
954 
955  void RadioGen::Ar42Gamma4(std::vector<std::tuple<ti_PDGID, td_Mass, TLorentzVector>>& v_prods)
956  {
957  CLHEP::RandFlat flat(fEngine);
958  ti_PDGID pdgid = 22;
959  td_Mass m = 0.0; //we are writing gammas
960  double chan1 = (0.052 / (0.052 + 0.020));
961  if (flat.fire() < chan1) {
962  std::vector<double> vd_p = {.0008997}; //Momentum in GeV
963  for (auto p : vd_p) {
964  v_prods.emplace_back(pdgid, m, dirCalc(p, m));
965  }
966  Ar42Gamma2(v_prods);
967  }
968  else {
969  std::vector<double> vd_p = {.0024243}; //Momentum in GeV
970  for (auto p : vd_p) {
971  v_prods.emplace_back(pdgid, m, dirCalc(p, m));
972  }
973  }
974  }
975 
976  void RadioGen::Ar42Gamma5(std::vector<std::tuple<ti_PDGID, td_Mass, TLorentzVector>>& v_prods)
977  {
978  CLHEP::RandFlat flat(fEngine);
979  ti_PDGID pdgid = 22;
980  td_Mass m = 0.0; //we are writing gammas
981  double chan1 = (0.0033 / (0.0033 + 0.0201 + 0.041));
982  double chan2 = (0.0201 / (0.0033 + 0.0201 + 0.041));
983  double chanPick = flat.fire();
984  if (chanPick < chan1) {
985  std::vector<double> vd_p = {0.000692, 0.001228}; //Momentum in GeV
986  for (auto p : vd_p) {
987  v_prods.emplace_back(pdgid, m, dirCalc(p, m));
988  }
989  Ar42Gamma2(v_prods);
990  }
991  else if (chanPick < (chan1 + chan2)) {
992  std::vector<double> vd_p = {0.0010212}; //Momentum in GeV
993  for (auto p : vd_p) {
994  v_prods.emplace_back(pdgid, m, dirCalc(p, m));
995  }
996  Ar42Gamma4(v_prods);
997  }
998  else {
999  std::vector<double> vd_p = {0.0019208}; //Momentum in GeV
1000  for (auto p : vd_p) {
1001  v_prods.emplace_back(pdgid, m, dirCalc(p, m));
1002  }
1003  Ar42Gamma2(v_prods);
1004  }
1005  }
1006 
1007  // phase space generator for beta decay -- keep it as a comment in case we ever want to revive it
1008 
1009  // double RadioGen::betaphasespace(double mass, double q)
1010  //{
1011  // CLHEP::RandFlat flat(fEngine);
1012  // double p = 0;
1013  // double mi = mass+q+m_e;
1014  // TLorentzVector p0(0,0,0,mi); // pre-decay four-vector
1015  // double masses[3] = {0,m_e,mass}; // neutrino, electron, nucleus
1016  // rg.SetDecay(p0,3,masses);
1017  // double wmax = rg.GetWtMax();
1018  // for (int igen=0;igen<1000;igen++) // cap the retries at 1000
1019  // {
1020  // double weight = rg.Generate(); // want to unweight these if possible
1021  // TLorentzVector *e4v = rg.GetDecay(1); // get electron four-vector
1022  // p = e4v->P();
1023  // if (weight >= wmax * flat.fire()) break;
1024  // }
1025  //return p;
1026  //}
1027 
1028  //____________________________________________________________________________
1029  template <typename Stream>
1031  std::size_t iNucl,
1032  std::string const& volumeName /* = {} */) const
1033  {
1034 
1035  out << "[#" << iNucl << "] " << fNuclide[iNucl] << " (" << fBq[iNucl] << " Bq/cm^3)"
1036  << " in " << fMaterial[iNucl] << " from " << fT0[iNucl] << " to " << fT1[iNucl]
1037  << " ns in ( " << fX0[iNucl] << ", " << fY0[iNucl] << ", " << fZ0[iNucl] << ") to ( "
1038  << fX1[iNucl] << ", " << fY1[iNucl] << ", " << fZ1[iNucl] << ")";
1039  if (!volumeName.empty()) out << " (\"" << volumeName << "\")";
1040 
1041  } // RadioGen::dumpNuclideSettings()
1042 
1043  //____________________________________________________________________________
1044  std::pair<double, double> RadioGen::defaulttimewindow() const
1045  {
1046  // values are in simulation time scale (and therefore in [ns])
1047  using namespace detinfo::timescales;
1048 
1049  auto const& timings = detinfo::makeDetectorTimings(
1051  detinfo::DetectorPropertiesData const& detInfo =
1053 
1054  //
1055  // we take a number of (TPC electronics) ticks before the trigger time,
1056  // and we go to a number of ticks after the trigger time;
1057  // that shift is one readout window size
1058  //
1059 
1060  auto const trigTimeTick = timings.toTick<electronics_tick>(timings.TriggerTime());
1061  electronics_time_ticks const beforeTicks{-static_cast<int>(detInfo.ReadOutWindowSize())};
1062  electronics_time_ticks const afterTicks{detInfo.NumberTimeSamples()};
1063 
1064  return {double(timings.toTimeScale<simulation_time>(trigTimeTick + beforeTicks)),
1065  double(timings.toTimeScale<simulation_time>(trigTimeTick + afterTicks))};
1066  } // RadioGen::defaulttimewindow()
1067 
1068 } //end namespace evgen
1069 
Float_t x
Definition: compare.C:6
code to link reconstructed objects back to the MC truth information
base_engine_t & createEngine(seed_t seed)
std::vector< std::unique_ptr< TH1D > > gammaspectrum
TLorentzVector dirCalc(double p, double m)
Utilities related to art service access.
timescale_traits< ElectronicsTimeCategory >::tick_t electronics_tick
A point on the electronics time scale expressed in its ticks.
TTree * t1
Definition: plottest35.C:26
void AddTrajectoryPoint(TLorentzVector const &position, TLorentzVector const &momentum)
Definition: MCParticle.h:258
constexpr auto fullRun()
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
std::vector< double > fZ0
Bottom corner z position (cm) in world coordinates.
void SetOrigin(simb::Origin_t origin)
Definition: MCTruth.h:82
Executes an operation on all the nodes of the ROOT geometry.
std::vector< std::string > fNuclide
List of nuclides to simulate. Example: "39Ar".
MaybeLogger_< ELseverityLevel::ELsev_info, false > LogInfo
std::pair< double, double > defaulttimewindow() const
Returns the start and end of the readout window.
Float_t y
Definition: compare.C:6
bool apply(GeoNodePath &path, Op &&op) const
Applies the specified operation to all nodes under the path.
Class representing a path in ROOT geometry.
PutHandle< PROD > put(std::unique_ptr< PROD > &&edp, std::string const &instance={})
Definition: Run.h:121
Class representing a path in ROOT geometry.
void Ar42Gamma5(std::vector< std::tuple< ti_PDGID, td_Mass, TLorentzVector >> &v_prods)
Particle class.
void dumpNuclideSettings(Stream &&out, std::size_t iNucl, std::string const &volumeName={}) const
Prints the settings for the specified nuclide and volume.
TGeoManager * ROOTGeoManager() const
Access to the ROOT geometry description manager.
std::vector< double > neutronintegral
Definition: Run.h:37
std::vector< std::string > spectrumname
Interface to detinfo::DetectorClocks.
TFile f
Definition: plotHisto.C:6
PutHandle< PROD > put(std::unique_ptr< PROD > &&edp, std::string const &instance={})
Definition: Event.h:77
auto vector(Vector const &v)
Returns a manipulator which will print the specified array.
Definition: DumpUtils.h:289
Access the description of the physical detector geometry.
auto enumerate(Iterables &&...iterables)
Range-for loop helper tracking the number of iteration.
Definition: enumerate.h:49
TString part[npart]
Definition: Style.C:32
std::vector< std::unique_ptr< TH1D > > alphaspectrum
std::vector< double > fT1
End of time window to simulate in ns.
auto counter(T begin, T end)
Returns an object to iterate values from begin to end in a range-for loop.
Definition: counter.h:292
std::vector< double > fY1
Top corner y position (cm) in world coordinates.
timescale_traits< ElectronicsTimeCategory >::tick_interval_t electronics_time_ticks
An interval on the electronics time scale expressed in its ticks.
#define DEFINE_ART_MODULE(klass)
Definition: ModuleMacros.h:65
Definitions of geometry vector data types.
timescale_traits< SimulationTimeCategory >::time_point_t simulation_time
A point in time on the simulation time scale.
std::vector< double > fZ1
Top corner z position (cm) in world coordinates.
single particles thrown at the detector
Definition: MCTruth.h:26
detinfo::DetectorTimings makeDetectorTimings(detinfo::DetectorClocksData const &detClocks)
int trackidcounter
Serial number for the MC track ID.
double energy
Definition: plottest35.C:25
Utilities to extend the interface of geometry vectors.This library provides facilities that can be us...
void SampleOne(unsigned int i, simb::MCTruth &mct)
Checks that the node represents a box well aligned to world frame axes.
void Ar42Gamma3(std::vector< std::tuple< ti_PDGID, td_Mass, TLorentzVector >> &v_prods)
void readfile(std::string nuclide, std::string const &filename, std::string const &PathToFile)
Test of util::counter and support utilities.
void beginRun(art::Run &run)
An art service to assist in the distribution of guaranteed unique seeds to all engines within an art ...
Module to generate particles created by radiological decay, patterned off of SingleGen.
ART objects.
double samplefromth1d(TH1D &hist)
std::vector< double > alphaintegral
cet::coded_exception< errors::ErrorCodes, ExceptionDetail::translate > Exception
Definition: Exception.h:66
std::size_t addvolume(std::string const &volumeName)
void samplespectrum(std::string nuclide, int &itype, double &t, double &m, double &p)
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
std::vector< double > betaintegral
void Add(simb::MCParticle const &part)
Definition: MCTruth.h:80
void Ar42Gamma2(std::vector< std::tuple< ti_PDGID, td_Mass, TLorentzVector >> &v_prods)
Selection of the type of transformation matrix used in geometry.
TH2F * hist
Definition: plot.C:134
Int_t nbinsx
Definition: plot.C:23
std::string fPathToFile
CLHEP::HepRandomEngine & fEngine
#define MF_LOG_DEBUG(id)
std::vector< double > fT0
Beginning of time window to simulate in ns.
Representation of a node and its ancestry.
Definition: GeoNodePath.h:34
void Ar42Gamma4(std::vector< std::tuple< ti_PDGID, td_Mass, TLorentzVector >> &v_prods)
std::vector< std::unique_ptr< TH1D > > neutronspectrum
std::vector< std::string > fMaterial
List of regexes of materials in which to generate the decays. Example: "LAr".
std::string const & DetectorName() const
Returns a string with the name of the detector, as configured.
Definition: GeometryCore.h:140
std::vector< double > fX0
Bottom corner x position (cm) in world coordinates.
TCEvent evt
Definition: DataStructs.cxx:8
Event generator information.
Definition: MCTruth.h:32
Float_t e
Definition: plot.C:35
std::vector< double > gammaintegral
std::vector< double > fBq
Radioactivity in Becquerels (decay per sec) per cubic cm.
ROOT libraries.
Event Generation using GENIE, cosmics or single particles.
Namespace including different time scales as defined in LArSoft.
std::vector< std::unique_ptr< TH1D > > betaspectrum
std::vector< double > fX1
Top corner x position (cm) in world coordinates.
art framework interface to geometry description
ROOT::Math::Transform3D TransformationMatrix
Type of transformation matrix used in geometry.
constexpr Point origin()
Returns a origin position with a point of the specified type.
Definition: geo_vectors.h:229
cet::coded_exception< error, detail::translate > exception
Definition: exception.h:33
std::vector< double > fY0
Bottom corner y position (cm) in world coordinates.
void produce(art::Event &evt)