ParticleListAction_service.cxx
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////
2 /// \file ParticleListAction.cxx
3 /// \brief Use Geant4's user "hooks" to maintain a list of particles generated by Geant4.
4 ///
5 /// \author seligman@nevis.columbia.edu
6 ///
7 /// Design considerations
8 /// ---------------------
9 ///
10 /// This class relies on the MCTruth index from
11 /// g4b::PrimaryParticleInformation to operate correctly. This index
12 /// is an integer value that corresponds to an MCTruth object, as
13 /// accessed through art::Handle<std::vector<simb::MCTruth>> objects.
14 /// However, the order in which MCTruth objects are processed must be
15 /// consistent between this service and the MCTruthEventAction
16 /// service, which creates the PrimaryParticleInformation object,
17 /// otherwise the Assns objects created here will be incorrect.
18 ///
19 /// Through art 3.09, one can rely on the order returned by a given
20 /// Event::getMany call to be predictable and consistent within the
21 /// same program. However, this behavior should not necessarily be
22 /// relied upon, and a different implementation of this class would
23 /// insulate users from such details, making the implementation
24 /// simpler. One should determine whether storing an art::ProductID
25 /// object along with an MCTruthIndex might be more helpful.
26 ///
27 ////////////////////////////////////////////////////////////////////////
28 
32 #include "nug4/G4Base/PrimaryParticleInformation.h"
33 #include "nug4/ParticleNavigation/ParticleList.h"
36 
37 // Framework includes
42 #include "cetlib_except/exception.h"
43 
44 #include "Geant4/G4DynamicParticle.hh"
45 #include "Geant4/G4Event.hh"
46 #include "Geant4/G4ParticleDefinition.hh"
47 #include "Geant4/G4PrimaryParticle.hh"
48 #include "Geant4/G4Step.hh"
49 #include "Geant4/G4StepPoint.hh"
50 #include "Geant4/G4String.hh"
51 #include "Geant4/G4ThreeVector.hh"
52 #include "Geant4/G4Track.hh"
53 #include "Geant4/G4VProcess.hh"
54 #include "Geant4/G4VUserPrimaryParticleInformation.hh"
55 
56 #include "TLorentzVector.h"
57 
58 #include "range/v3/view.hpp"
59 
60 #include <algorithm>
61 #include <cassert>
62 #include <string>
63 
64 namespace larg4 {
65 
66  //----------------------------------------------------------------------------
67  // Constructor.
69  : artg4tk::EventActionBase("PLASEventActionBase")
70  , artg4tk::TrackingActionBase("PLASTrackingActionBase")
71  , artg4tk::SteppingActionBase("PLASSteppingActionBase")
72  , fenergyCut(p.get<double>("EnergyCut", 0.0 * CLHEP::GeV))
73  , fstoreTrajectories(p.get<bool>("storeTrajectories", true))
74  , fkeepGenTrajectories(p.get<std::vector<std::string>>("keepGenTrajectories", {}))
75  , fKeepEMShowerDaughters(p.get<bool>("keepEMShowerDaughters", true))
76  , fNotStoredPhysics(p.get<std::vector<std::string>>("NotStoredPhysics", {}))
77  , fkeepOnlyPrimaryFullTraj(p.get<bool>("keepOnlyPrimaryFullTrajectories", false))
78  , fSparsifyTrajectories(p.get<bool>("SparsifyTrajectories", false))
79  , fSparsifyMargin(p.get<double>("SparsifyMargin"))
80  , fKeepTransportation(p.get<bool>("KeepTransportation", false))
81  , fKeepSecondToLast(p.get<bool>("KeepSecondToLast", false))
82  {
83  // -- D.R. If a custom list of not storable physics is provided, use it, otherwise
84  // use the default list. This preserves the behavior of the keepEmShowerDaughters
85  // parameter
86  bool customNotStored = not fNotStoredPhysics.empty();
88  // -- Don't keep all processes
89  if (!customNotStored) // -- Don't keep but haven't provided a list
90  { // -- default list of not stored physics
91  fNotStoredPhysics = {"conv",
92  "LowEnConversion",
93  "Pair",
94  "compt",
95  "Compt",
96  "Brem",
97  "phot",
98  "Photo",
99  "Ion",
100  "annihil"};
101  }
102 
103  std::stringstream sstored;
104  sstored << "The full tracking information will not be stored for particles"
105  << " resulting from the following processes: \n{ ";
106  for (auto const& i : fNotStoredPhysics) {
107  sstored << "\"" << i << "\" ";
108  fNotStoredCounterUMap.emplace(i, 0); // -- initialize counter
109  }
110  mf::LogInfo("ParticleListActionService") << sstored.str() << "}\n";
111  }
112  else { // -- Keep all processes
113  mf::LogInfo("ParticleListActionService")
114  << "Storing full tracking information for all processes. \n";
115  if (customNotStored) // -- custom list will be ignored
116  {
117  mf::LogWarning("StoredPhysics")
118  << "NotStoredPhysics provided, but will be ignored."
119  << " To use NotStoredPhysics, set keepEMShowerDaughters to false";
120  }
121  }
122 
123  // -- sparsify info
125  mf::LogInfo("ParticleListActionService")
126  << "Trajectory sparsification enabled with SparsifyMargin : " << fSparsifyMargin << "\n";
127  }
128 
129  //----------------------------------------------------------------------------
130  // Begin the event
131  void
133  {
134  // Clear any previous particle information.
136  fParticleList.clear();
137  fParentIDMap.clear();
138  fMCTIndexMap.clear();
139  fMCTPrimProcessKeepMap.clear();
141  fTrackIDOffset = 0;
142 
143  fPrimaryTruthMap.clear();
144  fMCTIndexToGeneratorMap.clear();
145  fNotStoredCounterUMap.clear();
146 
147  // -- D.R. If a custom list of keepGenTrajectories is provided, use it, otherwise
148  // keep or drop decision made based storeTrajectories parameter. This preserves
149  // the behavior of the storeTrajectories fhicl param
150  bool customKeepTraj = not fkeepGenTrajectories.empty();
151  if (!fstoreTrajectories) { // -- fstoreTrajectories : false
152  mf::LogDebug("beginOfEventAction::Generator") << "Trajectory points will not be stored.";
153  }
154  else if (!customKeepTraj) { // -- fstoretrajectories : true and empty keepGenTrajectories list
155  mf::LogDebug("beginOfEventAction::Generator")
156  << "keepGenTrajectories list is empty. Will"
157  << " store trajectory points for all generators";
158  }
159 
160  // -- D.R. determine mapping between MCTruthIndex(s) and generator(s) for later reference
161  size_t nKeep = 0;
162  std::string generator_name = "unknown";
163  for (size_t mcti = 0; mcti < fMCLists->size(); ++mcti) {
164 
165  std::stringstream sskeepgen;
166  sskeepgen << "MCTruth object summary :";
167  sskeepgen << "\n\tPrimary MCTIndex : " << mcti;
168 
169  // -- Obtain the generator (provenance) corresponding to the mctruth index:
170  auto const& mclistHandle = (*fMCLists)[mcti];
171  generator_name = mclistHandle.provenance()->inputTag().label();
172  sskeepgen << "\n\tProvenance/Generator : " << generator_name;
173 
174  G4bool keepGen = false;
175  if (fstoreTrajectories) // -- storeTrajectories set to true; check which
176  {
177  if (!customKeepTraj) { // -- no custom list, so keep all
178  keepGen = true;
179  nKeep++;
180  }
181  else { // -- custom list, so check the ones in the event against provided keep list
182  for (auto keepableGen : fkeepGenTrajectories) {
183  if (generator_name == keepableGen) { // -- exit upon finding match; false by default
184  keepGen = true;
185  nKeep++;
186  break;
187  }
188  }
189  }
190  }
191  fMCTIndexToGeneratorMap.emplace(mcti, std::make_pair(generator_name, keepGen));
192  sskeepgen << "\n\tTrajectory points storable : " << (keepGen ? "true" : "false") << "\n";
193  mf::LogDebug("beginOfEventAction::Generator") << sskeepgen.str();
194  }
195 
196  if (nKeep == 0 && customKeepTraj && fstoreTrajectories) {
197  mf::LogWarning("beginOfEventAction::keepableGenerators")
198  << "storeTrajectories"
199  << " set to true and a non-empty keepGenTrajectories list provided in configuration file, "
200  "but"
201  << " none of the generators in this list are present in the event! Double check list or "
202  "don't"
203  << " provide keepGenTrajectories in the configuration to keep all trajectories from all"
204  << " generator labels. This may be expected for generators that have a nonzero probability "
205  "of"
206  << " producing no particles (e.g. some radiologicals)";
207  }
208  }
209 
210  //-------------------------------------------------------------
211  // figure out the ultimate parentage of the particle with track ID
212  // trackid
213  // assume that the current track id has already been added to
214  // the fParentIDMap
215  int
217  {
218  int parentid = sim::NoParticleId;
219 
220  // search the fParentIDMap recursively until we have the parent id
221  // of the first EM particle that led to this one
222  auto itr = fParentIDMap.find(trackid);
223  while (itr != fParentIDMap.end()) {
224 
225  // set the parentid to the current parent ID, when the loop ends
226  // this id will be the first EM particle
227  parentid = (*itr).second;
228  itr = fParentIDMap.find(parentid);
229  }
230 
231  return parentid;
232  }
233 
234  //----------------------------------------------------------------------------
235  // Create our initial simb::MCParticle object and add it to the sim::ParticleList.
236  void
238  {
239  // Particle type.
240  G4ParticleDefinition* particleDefinition = track->GetDefinition();
241  G4int pdgCode = particleDefinition->GetPDGEncoding();
242 
243  // Get Geant4's ID number for this track. This will be the same
244  // ID number that we'll use in the ParticleList.
245  // It is offset by the number of tracks accumulated from the previous Geant4
246  // runs (if any)
247  int const trackID = track->GetTrackID() + fTrackIDOffset;
248  fCurrentTrackID = trackID;
249 
250  // And the particle's parent (same offset as above):
251  int parentID = track->GetParentID() + fTrackIDOffset;
252 
253  std::string process_name = "unknown";
254  std::string mct_primary_process = "unknown";
255  bool isFromMCTProcessPrimary = false;
256 
257  // Is there an MCTruth object associated with this G4Track? We
258  // have to go up a "chain" of information to find out:
259  const G4DynamicParticle* dynamicParticle = track->GetDynamicParticle();
260  const G4PrimaryParticle* primaryParticle = dynamicParticle->GetPrimaryParticle();
262  size_t primarymctIndex = 0;
263  if (primaryParticle != nullptr) {
264  const G4VUserPrimaryParticleInformation* gppi = primaryParticle->GetUserInformation();
265  const g4b::PrimaryParticleInformation* ppi =
266  dynamic_cast<const g4b::PrimaryParticleInformation*>(gppi);
267  if (ppi != nullptr) {
268  primaryIndex = ppi->MCParticleIndex();
269  primarymctIndex = ppi->MCTruthIndex();
270 
271  mct_primary_process = ppi->GetMCParticle()->Process();
272 
273  // If we've made it this far, a PrimaryParticleInformation
274  // object exists and we are using a primary particle, set the
275  // process name accordingly
276 
277  // -- D.R. : if process == "primary" exactly (this will most likely be the case), mark it as
278  // a primary with keepable trajectory for itself and its descendants.
279  // -- elsif it simply starts with "primary", accept it but mark it as non-keep
280  // -- else force it to be primary because we have determined that it is a primary particle
281  // This was the original behavior of the code i.e. process was _set_ to "primary"
282  // regardless of what the process name was in the generator MCTruth object.
283  //
284  // -- NOTE: This enforces a convention for process names assigned in the gen stage.
285  if (mct_primary_process.compare("primary") == 0) {
286  process_name = "primary";
287  isFromMCTProcessPrimary = true;
288  }
289  else if (mct_primary_process.find("primary") == 0) {
290  process_name = mct_primary_process;
291  isFromMCTProcessPrimary = false;
292  mf::LogDebug("PrimaryParticle") << "MCTruth primary process name contains \"primary\" "
293  << " but is not solely \"primary\" : " << process_name
294  << ".\nWill not store full set of trajectory points.";
295  }
296  else { // -- override it
297  process_name = "primary";
298  isFromMCTProcessPrimary = true;
299  mf::LogWarning("PrimaryParticle")
300  << "MCTruth primary process does not beging with string"
301  << " literal \"primary\" : " << process_name << "\nOVERRIDING it to \"primary\"";
302  }
303  // -- The process_name check is simply to allow an additional way to reduce memory usage,
304  // namely, by creating MCTruth input particles with multiple process labels (e.g. "primary"
305  // and "primaryBackground") that can be used to veto storage of trajectory points.
306 
307  // primary particles should have parentID = 0, even if there
308  // are multiple MCTruths for this event
309  parentID = 0;
310  } // end else no primary particle information
311  } // Is there a G4PrimaryParticle?
312  // If this is not a primary particle...
313  else {
314  // check if this particle was made in an undesirable process. For example:
315  // if one is not interested in EM shower particles, don't put it in the particle
316  // list as one wouldn't care about secondaries, tertiaries, etc. For these showers
317  // figure out what process is making this track - skip it if it is
318  // one of pair production, compton scattering, photoelectric effect
319  // bremstrahlung, annihilation, or ionization
320  process_name = track->GetCreatorProcess()->GetProcessName();
321  if (!fKeepEMShowerDaughters) {
322  bool notstore = false;
323  for (auto const& p : fNotStoredPhysics) {
324  if (process_name.find(p) != std::string::npos) {
325  notstore = true;
326  mf::LogDebug("NotStoredPhysics") << "Found process : " << process_name;
327 
328  int old = 0;
329  auto search = fNotStoredCounterUMap.find(p);
330  if (search != fNotStoredCounterUMap.end()) { old = search->second; }
331  fNotStoredCounterUMap.insert_or_assign(p, (old + 1));
332  break;
333  }
334  }
335 
336  if (notstore) {
337 
338  // figure out the ultimate parentage of this particle
339  // first add this track id and its parent to the fParentIDMap
340  fParentIDMap[trackID] = parentID;
341 
342  fCurrentTrackID = -1 * this->GetParentage(trackID);
343 
344  // check that fCurrentTrackID is in the particle list - it is possible
345  // that this particle's parent is a particle that did not get tracked.
346  // An example is a parent that was made due to muMinusCaptureAtRest
347  // and the daughter was made by the phot process. The parent likely
348  // isn't saved in the particle list because it is below the energy cut
349  // which will put a bogus track id value into the sim::IDE object for
350  // the sim::SimChannel if we don't check it.
352 
353  // clear current particle as we are not stepping this particle and
354  // adding trajectory points to it
356  return;
357  } // end if process matches an undesired process
358  } // end if keeping EM shower daughters
359 
360  // Check the energy of the particle. If it falls below the energy
361  // cut, don't add it to our list.
362  G4double energy = track->GetKineticEnergy();
363  if (energy < fenergyCut) {
365 
366  // do add the particle to the parent id map though
367  // and set the current track id to be it's ultimate parent
368  fParentIDMap[trackID] = parentID;
369  fCurrentTrackID = -1 * this->GetParentage(trackID);
370 
371  return;
372  }
373 
374  // check to see if the parent particle has been stored in the particle navigator
375  // if not, then see if it is possible to walk up the fParentIDMap to find the
376  // ultimate parent of this particle. Use that ID as the parent ID for this
377  // particle
378  if (!fParticleList.KnownParticle(parentID)) {
379  // do add the particle to the parent id map
380  // just in case it makes a daughter that we have to track as well
381  fParentIDMap[trackID] = parentID;
382  int pid = this->GetParentage(parentID);
383 
384  // if we still can't find the parent in the particle navigator,
385  // we have to give up
386  if (!fParticleList.KnownParticle(pid)) {
387  MF_LOG_WARNING("ParticleListActionService")
388  << "can't find parent id: " << parentID << " in the particle list, or fParentIDMap."
389  << " Make " << parentID << " the mother ID for"
390  << " track ID " << fCurrentTrackID << " in the hope that it will aid debugging.";
391  }
392  else
393  parentID = pid;
394  }
395 
396  // Once the parentID is secured, inherit the MCTruth Index
397  // which should have been set already
398  if (auto it = fMCTIndexMap.find(parentID); it != cend(fMCTIndexMap)) {
399  primarymctIndex = it->second;
400  }
401  else {
403  << "Could not locate MCT Index for parent trackID of " << parentID;
404  }
405 
406  // Inherit whether the parent is from a primary with MCTruth process_name == "primary"
407  isFromMCTProcessPrimary = fMCTPrimProcessKeepMap[parentID];
408  } // end if not a primary particle
409 
410  // This is probably the PDG mass, but just in case:
411  double mass = dynamicParticle->GetMass() / CLHEP::GeV;
412 
413  // Create the sim::Particle object.
416  new simb::MCParticle{trackID, pdgCode, process_name, parentID, mass};
417  fCurrentParticle.truthIndex = primaryIndex;
418 
419  fMCTIndexMap[trackID] = primarymctIndex;
420 
421  fMCTPrimProcessKeepMap[trackID] = isFromMCTProcessPrimary;
422 
423  // -- determine whether full set of trajectorie points should be stored or only the start and end points
425  (!fstoreTrajectories) ?
426  false : /*don't want trajectory points at all, bail*/
427  (!(fMCTIndexToGeneratorMap[primarymctIndex].second)) ?
428  false : /*particle is not from a storable generator*/
430  true : /*want all primaries tracked for a storable generator*/
431  (isFromMCTProcessPrimary) ?
432  true : /*only descendants from primaries with MCTruth process == "primary"*/
433  false; /*not from MCTruth process "primary"*/
434 
435  // Polarization.
436  const G4ThreeVector& polarization = track->GetPolarization();
438  TVector3{polarization.x(), polarization.y(), polarization.z()});
439 
440  // Save the particle in the ParticleList.
442  }
443 
444  //----------------------------------------------------------------------------
445  void
447  {
448  if (!fCurrentParticle.hasParticle()) return;
449 
450  if (aTrack) {
451  fCurrentParticle.particle->SetWeight(aTrack->GetWeight());
452 
453  // Get the post-step information from the G4Step.
454  const G4StepPoint* postStepPoint = aTrack->GetStep()->GetPostStepPoint();
455  if (!postStepPoint->GetProcessDefinedStep()) {
456  // Now we get to do some awkward cleanup because the
457  // fparticleList was augmented during the
458  // preUserTrackingAction. We cannot call 'Archive' because
459  // that only sets the mapped type of the entry to
460  // nullptr...which is really bad whenever we iterate through
461  // entries in the endOfEventAction and dereference the mapped
462  // type. We have to entirely erase the entry.
463  auto key_to_erase = fParticleList.key(fCurrentParticle.particle);
464  fParticleList.erase(key_to_erase);
465  // after the particle is archived, it is deleted
467  return;
468  }
469 
470  G4String process = postStepPoint->GetProcessDefinedStep()->GetProcessName();
472 
473  // -- D.R. Store the final point only for particles that have not had intermediate trajectory
474  // points saved. This avoids double counting the final trajectory point for particles from
475  // generators with storable trajectory points.
476 
478  const G4ThreeVector position = postStepPoint->GetPosition();
479  G4double time = postStepPoint->GetGlobalTime();
480 
481  // Remember that LArSoft uses cm, ns, GeV.
482  TLorentzVector fourPos(position.x() / CLHEP::cm,
483  position.y() / CLHEP::cm,
484  position.z() / CLHEP::cm,
485  time / CLHEP::ns);
486 
487  const G4ThreeVector momentum = postStepPoint->GetMomentum();
488  const G4double energy = postStepPoint->GetTotalEnergy();
489  TLorentzVector fourMom(momentum.x() / CLHEP::GeV,
490  momentum.y() / CLHEP::GeV,
491  momentum.z() / CLHEP::GeV,
492  energy / CLHEP::GeV);
493 
494  // Add another point in the trajectory.
495  AddPointToCurrentParticle(fourPos, fourMom, std::string(process));
496  }
497  // -- particle has a full trajectory, apply SparsifyTrajectory method if enabled
498  else if (fSparsifyTrajectories) {
500  }
501  }
502 
503  // store truth record pointer, only if it is available
504  if (fCurrentParticle.isPrimary()) {
506  }
507 
508  return;
509  }
510 
511  //----------------------------------------------------------------------------
512  // With every step, add to the particle's trajectory.
513  void
515  {
516  // N.B. G4 guarantees that following are non-null:
517  // - step
518  // - step->GetPostStepPoint()
519  if (!fCurrentParticle.hasParticle() || !step->GetPostStepPoint()->GetProcessDefinedStep()) {
520  return;
521  }
522  // Temporary fix for problem where DeltaTime on the first step
523  // of optical photon propagation is calculated incorrectly. -wforeman
524  double const globalTime = step->GetTrack()->GetGlobalTime();
525  double const velocity_G4 = step->GetTrack()->GetVelocity();
526  double const velocity_step = step->GetStepLength() / step->GetDeltaTime();
527  if ((step->GetTrack()->GetDefinition()->GetPDGEncoding() == 0) &&
528  fabs(velocity_G4 - velocity_step) > 0.0001) {
529  // Subtract the faulty step time from the global time,
530  // and add the correct step time based on G4 velocity.
531  step->GetPostStepPoint()->SetGlobalTime(globalTime - step->GetDeltaTime() +
532  step->GetStepLength() / velocity_G4);
533  }
534 
535  // For the most part, we just want to add the post-step
536  // information to the particle's trajectory. There's one
537  // exception: In PreTrackingAction, the correct time information
538  // is not available. So add the correct vertex information here.
539 
541 
542  // Get the pre/along-step information from the G4Step.
543  const G4StepPoint* preStepPoint = step->GetPreStepPoint();
544 
545  const G4ThreeVector position = preStepPoint->GetPosition();
546  G4double time = preStepPoint->GetGlobalTime();
547 
548  // Remember that LArSoft uses cm, ns, GeV.
549  TLorentzVector fourPos(position.x() / CLHEP::cm,
550  position.y() / CLHEP::cm,
551  position.z() / CLHEP::cm,
552  time / CLHEP::ns);
553 
554  const G4ThreeVector momentum = preStepPoint->GetMomentum();
555  const G4double energy = preStepPoint->GetTotalEnergy();
556  TLorentzVector fourMom(momentum.x() / CLHEP::GeV,
557  momentum.y() / CLHEP::GeV,
558  momentum.z() / CLHEP::GeV,
559  energy / CLHEP::GeV);
560 
561  // Add the first point in the trajectory.
562  AddPointToCurrentParticle(fourPos, fourMom, "Start");
563  } // end if this is the first step
564 
565  // At this point, the particle is being transported through the
566  // simulation. This method is being called for every voxel that
567  // the track passes through, but we don't want to update the
568  // trajectory information if we're just updating voxels. To check
569  // for this, look at the process name for the step, and compare it
570  // against the voxelization process name (set in PhysicsList.cxx).
571  G4String process = step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName();
572  G4bool ignoreProcess = process.contains("LArVoxel") || process.contains("OpDetReadout");
573 
574  /*
575  mf::LogDebug("ParticleListActionService::SteppingAction")
576  << ": DEBUG - process='"
577  << process << "'"
578  << " ignoreProcess=" << ignoreProcess
579  << " fstoreTrajectories="
580  << fstoreTrajectories;
581  */
582 
583  // We store the initial creation point of the particle
584  // and its final position (ie where it has no more energy, or at least < 1 eV) no matter
585  // what, but whether we store the rest of the trajectory depends
586  // on the process, and on a user switch.
587  // -- D.R. Store additional trajectory points only for desired generators and processes
588  if (!ignoreProcess && fCurrentParticle.keepFullTrajectory) {
589 
590  // Get the post-step information from the G4Step.
591  const G4StepPoint* postStepPoint = step->GetPostStepPoint();
592 
593  const G4ThreeVector position = postStepPoint->GetPosition();
594  G4double time = postStepPoint->GetGlobalTime();
595 
596  // Remember that LArSoft uses cm, ns, GeV.
597  TLorentzVector fourPos(position.x() / CLHEP::cm,
598  position.y() / CLHEP::cm,
599  position.z() / CLHEP::cm,
600  time / CLHEP::ns);
601 
602  const G4ThreeVector momentum = postStepPoint->GetMomentum();
603  const G4double energy = postStepPoint->GetTotalEnergy();
604  TLorentzVector fourMom(momentum.x() / CLHEP::GeV,
605  momentum.y() / CLHEP::GeV,
606  momentum.z() / CLHEP::GeV,
607  energy / CLHEP::GeV);
608 
609  // Add another point in the trajectory.
610  AddPointToCurrentParticle(fourPos, fourMom, std::string(process));
611  }
612  }
613 
614  //----------------------------------------------------------------------------
615  /// Utility class for the EndOfEventAction method: update the
616  /// daughter relationships in the particle list.
618  public:
619  explicit UpdateDaughterInformation(sim::ParticleList& p) : particleList{&p} {}
620  void
621  operator()(sim::ParticleList::value_type& particleListEntry) const
622  {
623  // We're looking at this Particle in the list.
624  int particleID = particleListEntry.first;
625 
626  // The parent ID of this particle;
627  // we ask the particle list since the particle itself might have been lost
628  // ("archived"), but the particle list still holds the information we need
629  int parentID = particleList->GetMotherOf(particleID);
630 
631  // If the parentID <= 0, this is a primary particle.
632  if (parentID <= 0) return;
633 
634  // If we get here, this particle is somebody's daughter. Add
635  // it to the list of daughter particles for that parent.
636 
637  // Get the parent particle from the list.
638  sim::ParticleList::iterator parentEntry = particleList->find(parentID);
639 
640  if (parentEntry == particleList->end()) {
641  // We have an "orphan": a particle whose parent isn't
642  // recorded in the particle list. This is not signficant;
643  // it's possible for a particle not to be saved in the list
644  // because it failed an energy cut, but for it to have a
645  // daughter that passed the cut (e.g., a nuclear decay).
646  return;
647  }
648  if (!parentEntry->second) return; // particle archived, nothing to update
649 
650  // Add the current particle to the daughter list of the parent.
651  simb::MCParticle* parent = parentEntry->second;
652  parent->AddDaughter(particleID);
653  }
654 
655  private:
656  sim::ParticleList* particleList;
657  };
658 
659  //----------------------------------------------------------------------------
662  {
663  auto const it = fPrimaryTruthMap.find(trackId);
664  return it == fPrimaryTruthMap.end() ? simb::NoGeneratedParticleIndex : it->second;
665  }
666 
667  //----------------------------------------------------------------------------
668  // Yields the ParticleList accumulated during the current event.
669  sim::ParticleList&&
671  {
672  // check if the ParticleNavigator has entries, and if
673  // so grab the highest track id value from it to
674  // add to the fTrackIDOffset
675  int highestID = 0;
676  for (auto pn = fParticleList.begin(); pn != fParticleList.end(); pn++)
677  if ((*pn).first > highestID) highestID = (*pn).first;
678 
679  //Only change the fTrackIDOffset if there is in fact a particle to add to the event
680  if ((fParticleList.size()) != 0) {
681  fTrackIDOffset = highestID + 1;
682  mf::LogDebug("YieldList:fTrackIDOffset")
683  << "highestID = " << highestID << "\nfTrackIDOffset= " << fTrackIDOffset;
684  }
685 
686  return std::move(fParticleList);
687  } // ParticleList&& ParticleListActionService::YieldList()
688 
689  //----------------------------------------------------------------------------
690  void
692  TLorentzVector const& mom,
693  std::string const& process)
694  {
695  // Add the first point in the trajectory.
697  }
698 
699  // Called at the end of each event. Call detectors to convert hits for the
700  // event and pass the call on to the action objects.
701  void
703  {
704  // -- End of Run Report
705  if (!fNotStoredCounterUMap.empty()) { // -- Only if there is something to report
706  std::stringstream sscounter;
707  sscounter << "Not Stored Process summary:";
708  for (auto const& [process, count] : fNotStoredCounterUMap) {
709  sscounter << "\n\t" << process << " : " << count;
710  }
711  mf::LogInfo("ParticleListActionService") << sscounter.str();
712  }
713 
714  partCol_ = std::make_unique<std::vector<simb::MCParticle>>();
715  tpassn_ =
716  std::make_unique<art::Assns<simb::MCTruth, simb::MCParticle, sim::GeneratedParticleInfo>>();
717  // Set up the utility class for the "for_each" algorithm. (We only
718  // need a separate set-up for the utility class because we need to
719  // give it the pointer to the particle list. We're using the STL
720  // "for_each" instead of the C++ "for loop" because it's supposed
721  // to be faster.
722  std::for_each(
724 
725  MF_LOG_INFO("endOfEventAction") << "MCTruth Handles Size: " << fMCLists->size();
726 
727  unsigned int nGeneratedParticles = 0;
728  unsigned int nMCTruths = 0;
729  sim::ParticleList particleList = YieldList();
730 
731  for (size_t mcl = 0; mcl < fMCLists->size(); ++mcl) {
732  auto const& mclistHandle = (*fMCLists)[mcl];
733  MF_LOG_INFO("endOfEventAction") << "mclistHandle Size: " << mclistHandle->size();
734 
735  for (size_t m = 0; m < mclistHandle->size(); ++m) {
736  art::Ptr<simb::MCTruth> mct(mclistHandle, m);
737 
738  MF_LOG_INFO("endOfEventAction") << "Found " << mct->NParticles() << " particles";
739 
740  for (simb::MCParticle* p : particleList | ranges::views::values) {
741  auto gen_index = fMCTIndexMap[p->TrackId()];
742  if (gen_index != nMCTruths) continue;
743  assert(p->NumberTrajectoryPoints() != 0ull);
744 
745  ++nGeneratedParticles;
746  sim::GeneratedParticleInfo const truthInfo{GetPrimaryTruthIndex(p->TrackId())};
747  if (!truthInfo.hasGeneratedParticleIndex() && p->Mother() == 0) {
748  MF_LOG_WARNING("endOfEventAction") << "No GeneratedParticleIndex()!";
749  // this means it's primary but with no information; logic error!!
751  << "Failed to match primary particle:\n"
752  << "\nwith particles from the truth record '" << mclistHandle.provenance()->inputTag()
753  << "':\n\n";
754  }
755 
756  partCol_->push_back(std::move(*p));
758  tpassn_->addSingle(mct, mcp_ptr, truthInfo);
759  }
760  mf::LogDebug("Offset") << "nGeneratedParticles = " << nGeneratedParticles;
761  ++nMCTruths;
762  }
763  }
764 
765  fTrackIDOffset = 0;
766  }
767 } // namespace LArG4
768 
static constexpr double cm
Definition: Units.h:68
intermediate_table::iterator iterator
unsigned int NumberTrajectoryPoints() const
Definition: MCParticle.h:218
simb::MCParticle * particle
simple structure representing particle
void AddDaughter(const int trackID)
Definition: MCParticle.h:268
void userSteppingAction(const G4Step *) override
ParticleListActionService(fhicl::ParameterSet const &)
void beginOfEventAction(const G4Event *) override
void AddTrajectoryPoint(TLorentzVector const &position, TLorentzVector const &momentum)
Definition: MCParticle.h:257
decltype(auto) constexpr cend(T &&obj)
ADL-aware version of std::cend.
Definition: StdUtils.h:87
void preUserTrackingAction(const G4Track *) override
bool fKeepEMShowerDaughters
whether to keep EM shower secondaries, tertiaries, etc
std::string string
Definition: nybbler.cc:12
MaybeLogger_< ELseverityLevel::ELsev_info, false > LogInfo
bool fKeepSecondToLast
tell whether or not to force keeping the second to last point
std::unique_ptr< art::Assns< simb::MCTruth, simb::MCParticle, sim::GeneratedParticleInfo > > tpassn_
Geant4 interface.
double globalTime
STL namespace.
double fSparsifyMargin
set the sparsification margin
void AddPointToCurrentParticle(TLorentzVector const &pos, TLorentzVector const &mom, std::string const &process)
Adds a trajectory point to the current particle, and runs the filter.
int NParticles() const
Definition: MCTruth.h:75
void postUserTrackingAction(const G4Track *) override
simb::GeneratedParticleIndex_t GetPrimaryTruthIndex(int trackId) const
Returns the index of primary truth (sim::NoGeneratorIndex if none).
int TrackId() const
Definition: MCParticle.h:210
bool fKeepTransportation
tell whether or not to keep the transportation process
std::unique_ptr< std::vector< simb::MCParticle > > partCol_
std::map< int, size_t > fMCTIndexMap
Map: particle track ID -> index of primary parent in std::vector<simb::MCTruth> object.
def process(f, kind)
Definition: search.py:254
constexpr GeneratedParticleIndex_t NoGeneratedParticleIndex
Constant representing the absence of generator truth information.
Definition: simb.h:34
bool fSparsifyTrajectories
help reduce the number of trajectory points.
void SetPolarization(const TVector3 &p)
Definition: MCParticle.h:269
static constexpr double GeV
Definition: Units.h:28
static const int NoParticleId
Definition: sim.h:28
def move(depos, offset)
Definition: depos.py:107
Use Geant4&#39;s user "hooks" to maintain a list of particles generated by Geant4.
std::map< size_t, std::pair< std::string, G4bool > > fMCTIndexToGeneratorMap
Map: MCTruthIndex -> generator, input label of generator and keepGenerator decision.
std::unordered_map< std::string, int > fNotStoredCounterUMap
Map: not stored process and counter.
p
Definition: test.py:223
Definition: search.py:1
double velocity_step
Q_UINT16 values[128]
void endOfEventAction(const G4Event *) override
#define MF_LOG_INFO(category)
bool isPrimary() const
Returns whether there is a particle.
void SetWeight(double wt)
Definition: MCParticle.h:271
void SetEndProcess(std::string s)
Definition: MCParticle.cxx:105
std::vector< std::string > fNotStoredPhysics
Physics processes that will not be stored.
cet::coded_exception< errors::ErrorCodes, ExceptionDetail::translate > Exception
Definition: Exception.h:66
#define DEFINE_ART_SERVICE(svc)
std::map< int, int > fParentIDMap
key is current track ID, value is parent ID
simb::GeneratedParticleIndex_t truthInfoIndex() const
Returns the index of the particle in the generator truth record.
art::EDProductGetter const * productGetter_
Contains data associated to particles from detector simulation.
void clear()
Resets the information (does not release memory it does not own)
std::vector< std::string > fkeepGenTrajectories
std::map< int, bool > fMCTPrimProcessKeepMap
Map: particle trakc ID -> boolean decision to keep or not full trajectory points. ...
simb::GeneratedParticleIndex_t truthIndex
Index of the particle in the original generator truth record.
bool hasParticle() const
Returns whether there is a particle.
MaybeLogger_< ELseverityLevel::ELsev_success, false > LogDebug
Contains information about a generated particle.
G4bool fstoreTrajectories
Whether to store particle trajectories with each particle.
MaybeLogger_< ELseverityLevel::ELsev_warning, false > LogWarning
std::map< int, simb::GeneratedParticleIndex_t > fPrimaryTruthMap
Map: particle track ID -> index of primary information in MC truth.
double velocity_G4
Tools and modules for checking out the basics of the Monte Carlo.
auto const & get(AssnsNode< L, R, D > const &r)
Definition: AssnsNode.h:115
#define MF_LOG_WARNING(category)
void operator()(sim::ParticleList::value_type &particleListEntry) const
def momentum(x1, x2, x3, scale=1.)
second_as<> second
Type of time stored in seconds, in double precision.
Definition: spacetime.h:85
void SparsifyTrajectory(double margin=0.1, bool keep_second_to_last=false)
Definition: MCParticle.h:265
std::vector< art::Handle< std::vector< simb::MCTruth > > > const * fMCLists
MCTruthCollection input lists.
int bool
Definition: qglobal.h:345
QAsciiDict< Entry > ns
def parent(G, child, parent_type)
Definition: graph.py:67
std::size_t GeneratedParticleIndex_t
Type of particle index in the generator truth record (simb::MCTruth).
Definition: simb.h:30