ParticleListAction.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 ////////////////////////////////////////////////////////////////////////
6 
7 #include "nug4/G4Base/PrimaryParticleInformation.h"
10 #include "Geometry/GeometryGAr.h"
11 
15 
16 #include "Geant4/G4Event.hh"
17 #include "Geant4/G4Track.hh"
18 #include "Geant4/G4ThreeVector.hh"
19 #include "Geant4/G4ParticleDefinition.hh"
20 #include "Geant4/G4PrimaryParticle.hh"
21 #include "Geant4/G4DynamicParticle.hh"
22 #include "Geant4/G4VUserPrimaryParticleInformation.hh"
23 #include "Geant4/G4Step.hh"
24 #include "Geant4/G4StepPoint.hh"
25 #include "Geant4/G4VProcess.hh"
26 #include "Geant4/G4String.hh"
27 
28 #include <TGeoManager.h>
29 #include <TGeoMaterial.h>
30 #include <TGeoNode.h>
31 #include <TLorentzVector.h>
32 #include <TString.h>
33 
34 #include <algorithm>
35 #include <regex>
36 
37 //unused const G4bool debug = false;
38 
39 namespace gar {
40  namespace garg4 {
41 
42  // Initialize static members.
45 
46  //----------------------------------------------------------------------------
47  // Dropped particle test
49  {
50  return !p || p->Trajectory().empty();
51  } // ParticleListAction::isDropped()
52 
53  //----------------------------------------------------------------------------
54  // Constructor.
56  bool storeTrajectories,
57  bool keepEMShowerDaughters,
58  std::string EMShowerDaughterMatRegex )
59  : fEnergyCut (energyCut)
60  , fParticleList (new sim::ParticleList())
61  , fstoreTrajectories (storeTrajectories)
62  , fKeepEMShowerDaughters(keepEMShowerDaughters)
63  , fEMShowerDaughterMatRegex(EMShowerDaughterMatRegex)
64  {
65  fParentIDMap.clear();
66  }
67 
68  //----------------------------------------------------------------------------
69  // Destructor.
71  {
72  // Delete anything that we created with "new'.
73  delete fParticleList;
74  }
75 
76  //----------------------------------------------------------------------------
77  // Begin the event
79  {
80  // Clear any previous particle information.
82  fParticleList->clear();
83  fParentIDMap.clear();
84  fTrackIDToMCTruthIndex.clear();
86 
87  }
88 
89  //-------------------------------------------------------------
90  // figure out the ultimate parentage of the particle with track ID
91  // trackid. Assume assume that the current track id has already
92  // been added to the fParentIDMap
93  int ParticleListAction::GetParentage(int trackid) const
94  {
95  int parentid = sdp::NoParticleId;
96 
97  // search the fParentIDMap recursively until we have the parent id
98  // of the first EM particle that led to this one
100  while( itr != fParentIDMap.end() ){
101  MF_LOG_DEBUG("ParticleListAction")
102  << "parentage for " << trackid
103  << " " << (*itr).second;
104 
105  // set the parentid to the current parent ID, when the loop ends
106  // this id will be the first EM particle
107  parentid = (*itr).second;
108  itr = fParentIDMap.find(parentid);
109  }
110  MF_LOG_DEBUG("ParticleListAction") << "final parent ID " << parentid;
111 
112  return parentid;
113  }
114 
115  //----------------------------------------------------------------------------
116  // Create our initial simb::MCParticle object and add it to the sim::ParticleList.
117  void ParticleListAction::PreTrackingAction(const G4Track* track)
118  {
119  // Particle type.
120  G4ParticleDefinition* particleDefinition = track->GetDefinition();
121  G4int pdgCode = particleDefinition->GetPDGEncoding();
122 
124  TGeoManager *geomanager = geo->ROOTGeoManager();
125 
126  // Get Geant4's ID number for this track. This will be the same
127  // ID number that we'll use in the ParticleList.
128  G4int trackID = track->GetTrackID() + fTrackIDOffset;
129  fCurrentTrackID = trackID;
130  size_t mcTruthIndex = 0;
131 
132  // And the particle's parent:
133  G4int parentID = track->GetParentID() + fTrackIDOffset;
134 
135  std::string process_name = "unknown";
136 
137  // Is there an MCTruth object associated with this G4Track? We
138  // have to go up a "chain" of information to find out:
139  const G4DynamicParticle* dynamicParticle = track->GetDynamicParticle();
140  const G4PrimaryParticle* primaryParticle = dynamicParticle->GetPrimaryParticle();
141  if ( primaryParticle ){
142  const G4VUserPrimaryParticleInformation* gppi = primaryParticle->GetUserInformation();
143  const g4b::PrimaryParticleInformation* ppi = dynamic_cast<const g4b::PrimaryParticleInformation*>(gppi);
144  if ( ppi != 0 ){
145  // If we've made it this far, a PrimaryParticleInformation
146  // object exists and we are using a primary particle, set the
147  // process name accordingly
148  process_name = "primary";
149 
150  // primary particles should have parentID = 0, even if there
151  // are multiple MCTruths for this event
152  parentID = 0;
153 
154  mcTruthIndex = ppi->MCTruthIndex();
155  } // end else no primary particle information
156  } // Is there a G4PrimaryParticle?
157  // If this is not a primary particle...
158  else{
159  // check if this particle was made in an EM shower, don't put it in the particle
160  // list as we don't care about secondaries, tertiaries, etc for these showers
161  // figure out what process is making this track - skip it if it is
162  // one of pair production, compton scattering, photoelectric effect
163  // bremstrahlung, annihilation, any ionization - reduces memory and the
164  // volume of the output files.
165 
166  // trj Dec. 4, 2019 -- add in a feature to optionally skip EM shower daughter MCParticle recording
167  // in dense media. This is so we can keep the shower daughters in TPC gas but not the calorimeters or rock
168 
169  process_name = track->GetCreatorProcess()->GetProcessName();
170  bool is_em_shower_daughter = ( process_name.find("conv") != std::string::npos ||
171  process_name.find("LowEnConversion") != std::string::npos ||
172  process_name.find("Pair") != std::string::npos ||
173  process_name.find("compt") != std::string::npos ||
174  process_name.find("Compt") != std::string::npos ||
175  process_name.find("Brem") != std::string::npos ||
176  process_name.find("phot") != std::string::npos ||
177  process_name.find("Photo") != std::string::npos ||
178  process_name.find("hIoni") != std::string::npos ||
179  process_name.find("eIoni") != std::string::npos ||
180  process_name.find("ionIoni") != std::string::npos ||
181  (process_name.find("Ion") != std::string::npos &&
182  process_name.find("mu") != std::string::npos) ||
183  process_name.find("annihil") != std::string::npos );
184  const G4ThreeVector& trackpos = track->GetPosition();
185  std::string matname = "";
186  auto geonode = geomanager->FindNode(trackpos[0]/CLHEP::cm,
187  trackpos[1]/CLHEP::cm,
188  trackpos[2]/CLHEP::cm);
189  if (geonode)
190  {
191  matname = geonode->GetMedium()->GetMaterial()->GetName();
192  }
193 
194  std::regex const re_material(fEMShowerDaughterMatRegex);
195 
196  if( is_em_shower_daughter &&
198  ! std::regex_match(matname, re_material)))
199  {
200 
201  // figure out the ultimate parentage of this particle
202  // first add this track id and its parent to the fParentIDMap
203  fParentIDMap[trackID] = parentID;
204 
205  fCurrentTrackID = -1 * this->GetParentage(trackID);
206 
207  // check that fCurrentTrackID is in the particle list - it is possible
208  // that this particle's parent is a particle that did not get tracked.
209  // An example is a partent that was made due to muMinusCaptureAtRest
210  // and the daughter was made by the phot process. The parent likely
211  // isn't saved in the particle list because it is below the energy cut
212  // which will put a bogus track id value into the list if we don't check it.
213  if(!fParticleList->KnownParticle(fCurrentTrackID))
215 
216  // clear current particle as we are not stepping this particle and
217  // adding trajectory points to it
219  return;
220 
221  } // end if not keeping EM shower daughters or skipping some processes
222 
223 
224  // Check the energy of the particle. If it falls below the energy
225  // cut, don't add it to our list.
226  G4double energy = track->GetKineticEnergy();
227  if( energy * CLHEP::MeV / CLHEP::GeV < fEnergyCut ){
229 
230  // do add the particle to the parent id map though
231  // and set the current track id to be it's ultimate parent
232  fParentIDMap[trackID] = parentID;
233 
234  fCurrentTrackID = -1 * this->GetParentage(trackID);
235 
236  return;
237  }
238 
239  //std::cout << "In GArG4 particle list action: ->" << matname << "<- " << fEMShowerDaughterMatRegex << std::endl;
240  //std::cout << "track position: " << trackpos[0]/CLHEP::cm << " " << trackpos[1]/CLHEP::cm << " " << trackpos[2]/CLHEP::cm << std::endl;
241  //std::cout << "Keeping mcparticle " << process_name << " " << energy << std::endl;
242 
243 
244  // check to see if the parent particle has been stored in the particle navigator
245  // if not, then see if it is possible to walk up the fParentIDMap to find the
246  // ultimate parent of this particle. Use that ID as the parent ID for this
247  // particle
248  if( !fParticleList->KnownParticle(parentID) ){
249  // do add the particle to the parent id map
250  // just in case it makes a daughter that we have to track as well
251  fParentIDMap[trackID] = parentID;
252  int pid = this->GetParentage(parentID);
253 
254  // if we still can't find the parent in the particle navigator,
255  // we have to give up
256  if( !fParticleList->KnownParticle(pid) ){
257  MF_LOG_WARNING("ParticleListAction")
258  << "can't find parent id: "
259  << parentID
260  << " in the particle list, or fParentIDMap."
261  << " Make "
262  << parentID
263  << " the mother ID for"
264  << " track ID "
265  << fCurrentTrackID
266  << " in the hope that it will aid debugging.";
267  }
268  else
269  parentID = pid;
270  }
271 
272  // Attempt to find the MCTruth index corresponding to the
273  // current particle. If the fCurrentTrackID is not in the
274  // map try the parent ID, if that is not there, throw an
275  // exception
277  mcTruthIndex = fTrackIDToMCTruthIndex.at(fCurrentTrackID);
278  else if(fTrackIDToMCTruthIndex.count(parentID) > 0 )
279  mcTruthIndex = fTrackIDToMCTruthIndex.at(parentID);
280  else
281  throw cet::exception("ParticleListAction")
282  << "Cannot find MCTruth index for track id "
283  << fCurrentTrackID
284  << " or "
285  << parentID;
286 
287  }// end if not a primary particle
288 
289  // This is probably the PDG mass, but just in case:
290  double mass = dynamicParticle->GetMass() * CLHEP::MeV / CLHEP::GeV;
291 
292  // Create the sim::Particle object.
295  pdgCode,
296  process_name,
297  parentID,
298  mass);
299 
300  // if we are not filtering, we have a decision already
301  if (!fFilter) fCurrentParticle.keep = true;
302 
303  // Polarization.
304  const G4ThreeVector& polarization = track->GetPolarization();
305  fCurrentParticle.particle->SetPolarization( TVector3(polarization.x(),
306  polarization.y(),
307  polarization.z()) );
308 
309  // Save the particle in the ParticleList.
311 
312  MF_LOG_DEBUG("ParticleListAction")
313  << "There are now "
314  << fParticleList->size()
315  << " particles in the list";
316 
318  MF_LOG_WARNING("ParticleListAction")
319  << "attempting to put "
320  << fCurrentTrackID
321  << " into fTrackIDToMCTruthIndex map "
322  << " particle is\n"
324 
325  fTrackIDToMCTruthIndex[fCurrentTrackID] = mcTruthIndex;
326 
327  }
328 
329  //----------------------------------------------------------------------------
330  void ParticleListAction::PostTrackingAction( const G4Track* aTrack)
331  {
332  if (!fCurrentParticle.hasParticle()) return;
333 
334  // if we have found no reason to keep it, drop it!
335  // (we might still need parentage information though)
336  if (!fCurrentParticle.keep) {
337 
338  MF_LOG_VERBATIM("ParticleListAction")
339  << "dropping particle with track id "
341 
343  // after the particle is archived, it is deleted
345  return;
346  }
347 
348  if(aTrack){
349  fCurrentParticle.particle->SetWeight(aTrack->GetWeight());
350  if (aTrack->GetStep()->GetPostStepPoint()->GetProcessDefinedStep())
351  {
352  G4String process = aTrack->GetStep()->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName();
354  }
355  }
356 
357  return;
358  }
359 
360  //----------------------------------------------------------------------------
361  // With every step, add to the particle's trajectory.
363  {
364 
365  if ( !fCurrentParticle.hasParticle() ) return;
366 
367  // For the most part, we just want to add the post-step
368  // information to the particle's trajectory. There's one
369  // exception: In PreTrackingAction, the correct time information
370  // is not available. So add the correct vertex information here.
371 
373 
374  // Get the pre/along-step information from the G4Step.
375  const G4StepPoint* preStepPoint = step->GetPreStepPoint();
376 
377  const G4ThreeVector position = preStepPoint->GetPosition();
378  G4double time = preStepPoint->GetGlobalTime();
379 
380  // Remember that GArSoft uses cm, ns, GeV.
381  TLorentzVector fourPos(position.x() / CLHEP::cm,
382  position.y() / CLHEP::cm,
383  position.z() / CLHEP::cm,
384  time);
385 
386  const G4ThreeVector momentum = preStepPoint->GetMomentum();
387  const G4double energy = preStepPoint->GetTotalEnergy();
388  TLorentzVector fourMom(momentum.x() * CLHEP::MeV / CLHEP::GeV,
389  momentum.y() * CLHEP::MeV / CLHEP::GeV,
390  momentum.z() * CLHEP::MeV / CLHEP::GeV,
391  energy * CLHEP::MeV / CLHEP::GeV);
392 
393  // Add the first point in the trajectory.
394  AddPointToCurrentParticle( fourPos, fourMom, "Start" );
395 
396  } // end if this is the first step
397 
398  if (step->GetPostStepPoint()->GetProcessDefinedStep())
399  {
400  G4String process = step->GetPostStepPoint()->GetProcessDefinedStep()->GetProcessName();
401 
402  // We store the initial creation point of the particle
403  // and its final position (ie where it has no more energy, or at least < 1 eV) no matter
404  // what, but whether we store the rest of the trajectory depends
405  // on the process, and on a user switch.
406  if ( fstoreTrajectories ){
407  // Get the post-step information from the G4Step.
408  const G4StepPoint* postStepPoint = step->GetPostStepPoint();
409 
410  const G4ThreeVector position = postStepPoint->GetPosition();
411  G4double time = postStepPoint->GetGlobalTime();
412 
413  // Remember that GArSoft uses cm, ns, GeV.
414  TLorentzVector fourPos(position.x() / CLHEP::cm,
415  position.y() / CLHEP::cm,
416  position.z() / CLHEP::cm,
417  time);
418 
419  const G4ThreeVector momentum = postStepPoint->GetMomentum();
420  const G4double energy = postStepPoint->GetTotalEnergy();
421  TLorentzVector fourMom( momentum.x() * CLHEP::MeV / CLHEP::GeV,
422  momentum.y() * CLHEP::MeV / CLHEP::GeV,
423  momentum.z() * CLHEP::MeV / CLHEP::GeV,
424  energy * CLHEP::MeV / CLHEP::GeV );
425 
426  // Add another point in the trajectory.
427  AddPointToCurrentParticle( fourPos, fourMom, std::string(process) );
428 
429  }
430  }
431  }
432 
433  //----------------------------------------------------------------------------
434  /// Utility class for the EndOfEventAction method: update the
435  /// daughter relationships in the particle list.
437  : public std::unary_function<sim::ParticleList::value_type, void>
438  {
439  public:
441  : particleList(0)
442  {}
443  void SetParticleList( sim::ParticleList* p ) { particleList = p; }
444  void operator()( sim::ParticleList::value_type& particleListEntry )
445  {
446  // We're looking at this Particle in the list.
447  int particleID = particleListEntry.first;
448 
449  // The parent ID of this particle;
450  // we ask the particle list since the particle itself might have been lost
451  // ("archived"), but the particle list still holds the information we need
452  int parentID = particleList->GetMotherOf(particleID);
453 
454  // If the parentID <= 0, this is a primary particle.
455  if ( parentID <= 0 ) return;
456 
457  // If we get here, this particle is somebody's daughter. Add
458  // it to the list of daughter particles for that parent.
459 
460  // Get the parent particle from the list.
461  sim::ParticleList::iterator parentEntry = particleList->find( parentID );
462 
463  if ( parentEntry == particleList->end() ){
464  // We have an "orphan": a particle whose parent isn't
465  // recorded in the particle list. This is not signficant;
466  // it's possible for a particle not to be saved in the list
467  // because it failed an energy cut, but for it to have a
468  // daughter that passed the cut (e.g., a nuclear decay).
469  return;
470  }
471  if ( !parentEntry->second ) return; // particle archived, nothing to update
472 
473  // Add the current particle to the daughter list of the
474  // parent.
475  simb::MCParticle* parent = (*parentEntry).second;
476  parent->AddDaughter( particleID );
477  }
478  private:
479  sim::ParticleList* particleList;
480  };
481 
482  //----------------------------------------------------------------------------
483  // There's one last thing to do: All the particles have their
484  // parent IDs set (in PostTrackingAction), but we haven't set the
485  // daughters yet. That's done in this method.
487  {
488  // Set up the utility class for the "for_each" algorithm. (We only
489  // need a separate set-up for the utility class because we need to
490  // give it the pointer to the particle list. We're using the STL
491  // "for_each" instead of the C++ "for loop" because it's supposed
492  // to be faster.
493  UpdateDaughterInformation updateDaughterInformation;
494  updateDaughterInformation.SetParticleList( fParticleList );
495 
496  // Update the daughter information for each particle in the list.
497  std::for_each(fParticleList->begin(),
498  fParticleList->end(),
499  updateDaughterInformation);
500  }
501 
502  //----------------------------------------------------------------------------
503  // Returns the ParticleList accumulated during the current event.
504  sim::ParticleList* ParticleListAction::GetList() const
505  {
506  // check if the ParticleNavigator has entries, and if
507  // so grab the highest track id value from it to
508  // add to the fTrackIDOffset
509  int highestID = 0;
510  for(auto pn = fParticleList->begin(); pn != fParticleList->end(); ++pn)
511  if( (*pn).first > highestID ) highestID = (*pn).first;
512 
513  fTrackIDOffset = highestID + 1;
514 
515  return fParticleList;
516  }
517 
518  //----------------------------------------------------------------------------
519  // Yields the ParticleList accumulated during the current event.
520  sim::ParticleList&& ParticleListAction::YieldList()
521  {
522  // check if the ParticleNavigator has entries, and if
523  // so grab the highest track id value from it to
524  // add to the fTrackIDOffset
525  int highestID = 0;
526  for(auto pn = fParticleList->begin(); pn != fParticleList->end(); ++pn)
527  if( (*pn).first > highestID ) highestID = (*pn).first;
528 
529  fTrackIDOffset = highestID + 1;
530 
531  return std::move(*fParticleList);
532  } // ParticleList&& ParticleListAction::YieldList()
533 
534  //-------------------------------------------------------------
535  std::map<int, size_t> ParticleListAction::TrackIDToMCTruthIndexMap() const
536  {
537  return fTrackIDToMCTruthIndex;
538  }
539 
540  //----------------------------------------------------------------------------
542  TLorentzVector const& mom,
543  std::string const& process)
544  {
545 
546  // Add the first point in the trajectory.
547  fCurrentParticle.particle->AddTrajectoryPoint(pos, mom, process);
548 
549  // also see if we can decide to keep the particle
550  if (!fCurrentParticle.keep)
551  fCurrentParticle.keep = fFilter->mustKeep(pos);
552 
553  } // ParticleListAction::AddPointToCurrentParticle()
554 
555 
556  //----------------------------------------------------------------------------
557 
558  } // namespace garg4
559 } // namepsace gar
static constexpr double cm
Definition: Units.h:68
virtual void EndOfEventAction(const G4Event *)
intermediate_table::iterator iterator
unsigned int NumberTrajectoryPoints() const
Definition: MCParticle.h:218
int GetParentage(int trackid) const
void AddDaughter(const int trackID)
Definition: MCParticle.h:268
void AddTrajectoryPoint(TLorentzVector const &position, TLorentzVector const &momentum)
Definition: MCParticle.h:257
std::string string
Definition: nybbler.cc:12
const simb::MCTrajectory & Trajectory() const
Definition: MCParticle.h:253
std::map< int, int > fParentIDMap
key is current track ID, value is parent ID
std::unique_ptr< PositionInVolumeFilter > fFilter
filter for particles to be kept
intermediate_table::const_iterator const_iterator
std::map< int, size_t > TrackIDToMCTruthIndexMap() const
static constexpr double MeV
Definition: Units.h:129
virtual void BeginOfEventAction(const G4Event *)
bool empty() const
Definition: MCTrajectory.h:167
static const int NoParticleId
Definition: sim.h:30
int TrackId() const
Definition: MCParticle.h:210
std::string fEMShowerDaughterMatRegex
if keeping EM shower daughters, save only in media matching this
G4bool fstoreTrajectories
Whether to store particle trajectories with each particle.
def process(f, kind)
Definition: search.py:254
virtual void SteppingAction(const G4Step *)
void SetPolarization(const TVector3 &p)
Definition: MCParticle.h:269
virtual void PreTrackingAction(const G4Track *)
static constexpr double GeV
Definition: Units.h:28
def move(depos, offset)
Definition: depos.py:107
std::map< int, size_t > fTrackIDToMCTruthIndex
map track ID to index of MCTruth in input list
p
Definition: test.py:223
void clear()
Resets the information (does not release memory it does not own)
Code to link reconstructed objects back to the MC truth information.
void SetWeight(double wt)
Definition: MCParticle.h:271
void SetEndProcess(std::string s)
Definition: MCParticle.cxx:105
void AddPointToCurrentParticle(TLorentzVector const &pos, TLorentzVector const &mom, std::string const &process)
Adds a trajectory point to the current particle, and runs the filter.
virtual void PostTrackingAction(const G4Track *)
General GArSoft Utilities.
#define MF_LOG_VERBATIM(category)
void operator()(sim::ParticleList::value_type &particleListEntry)
bool keep
if there was decision to keep
sim::ParticleList * GetList() const
bool fKeepEMShowerDaughters
whether to keep EM shower secondaries, tertiaries, etc
#define MF_LOG_DEBUG(id)
simb::MCParticle * particle
simple structure representing particle
ParticleListAction(double energyCut, bool storeTrajectories=false, bool keepEMShowerDaughters=false, std::string EMShowerDaughterMatRegex=".*")
#define MF_LOG_WARNING(category)
def momentum(x1, x2, x3, scale=1.)
LArSoft geometry interface.
Definition: ChannelGeo.h:16
art framework interface to geometry description
static bool IsDropped(simb::MCParticle const *p)
returns whether the specified particle has been marked as dropped
void SetParticleList(sim::ParticleList *p)
def parent(G, child, parent_type)
Definition: graph.py:67
cet::coded_exception< error, detail::translate > exception
Definition: exception.h:33
bool hasParticle() const
Returns whether there is a particle.