Skip to content
Snippets Groups Projects
Geant4ParticleHandler.cpp 29 KiB
Newer Older
//==========================================================================
Markus Frank's avatar
Markus Frank committed
//  AIDA Detector description implementation 
//--------------------------------------------------------------------------
Markus Frank's avatar
Markus Frank committed
// Copyright (C) Organisation europeenne pour la Recherche nucleaire (CERN)
// For the licensing terms see $DD4hepINSTALL/LICENSE.
// For the list of contributors see $DD4hepINSTALL/doc/CREDITS.
// Author     : M.Frank
//
//==========================================================================

// Framework include files
#include <DD4hep/Primitives.h>
#include <DD4hep/InstanceCount.h>
#include <DDG4/Geant4StepHandler.h>
#include <DDG4/Geant4TrackHandler.h>
#include <DDG4/Geant4EventAction.h>
#include <DDG4/Geant4SensDetAction.h>
#include <DDG4/Geant4TrackingAction.h>
#include <DDG4/Geant4SteppingAction.h>
#include <DDG4/Geant4ParticleHandler.h>
#include <DDG4/Geant4ParticleInformation.h>
#include <DDG4/Geant4UserParticleHandler.h>
#include <G4Step.hh>
#include <G4Track.hh>
#include <G4Event.hh>
#include <G4TrackStatus.hh>
#include <G4PrimaryVertex.hh>
#include <G4PrimaryParticle.hh>
#include <G4TrackingManager.hh>
#include <G4ParticleDefinition.hh>
#include <CLHEP/Units/SystemOfUnits.h>
#include <set>
#include <stdexcept>

using namespace std;
Markus Frank's avatar
Markus Frank committed
using namespace dd4hep;
using namespace dd4hep::sim;
Markus Frank's avatar
Markus Frank committed
typedef detail::ReferenceBitMask<int> PropertyMask;
/// Standard constructor
Geant4ParticleHandler::Geant4ParticleHandler(Geant4Context* ctxt, const string& nam)
  : Geant4GeneratorAction(ctxt,nam), Geant4MonteCarloTruth(),
    m_ownsParticles(false), m_userHandler(0), m_primaryMap(0)
  InstanceCount::increment(this);
  //generatorAction().adopt(this);
  eventAction().callAtBegin(this,    &Geant4ParticleHandler::beginEvent);
  eventAction().callAtEnd(this,      &Geant4ParticleHandler::endEvent);
  trackingAction().callAtFinal(this, &Geant4ParticleHandler::end,CallbackSequence::FRONT);
  trackingAction().callUpFront(this, &Geant4ParticleHandler::begin,CallbackSequence::FRONT);
  steppingAction().call(this,        &Geant4ParticleHandler::step);
  declareProperty("PrintEndTracking",      m_printEndTracking = false);
  declareProperty("PrintStartTracking",    m_printStartTracking = false);
  declareProperty("KeepAllParticles",      m_keepAll = false);
  declareProperty("SaveProcesses",         m_processNames);
  declareProperty("MinimalKineticEnergy",  m_kinEnergyCut = 100e0*CLHEP::MeV);
  declareProperty("MinDistToParentVertex", m_minDistToParentVertex = 2.2e-14*CLHEP::mm);//default tolerance for g4ThreeVector isNear
  m_needsControl = true;
/// No default constructor
Geant4ParticleHandler::Geant4ParticleHandler()
  : Geant4GeneratorAction(0,""), Geant4MonteCarloTruth(),
    m_ownsParticles(false), m_userHandler(0), m_primaryMap(0)
{
  m_globalParticleID = 0;
  declareProperty("PrintEndTracking",      m_printEndTracking = false);
  declareProperty("PrintStartTracking",    m_printStartTracking = false);
  declareProperty("KeepAllParticles",      m_keepAll = false);
  declareProperty("SaveProcesses",         m_processNames);
  declareProperty("MinimalKineticEnergy",  m_kinEnergyCut = 100e0*CLHEP::MeV);
  declareProperty("MinDistToParentVertex", m_minDistToParentVertex = 2.2e-14*CLHEP::mm);//default tolerance for g4ThreeVector isNear
  m_needsControl = true;
/// Default destructor
Geant4ParticleHandler::~Geant4ParticleHandler()  {
Markus Frank's avatar
Markus Frank committed
  detail::releasePtr(m_userHandler);
  InstanceCount::decrement(this);
}

/// No assignment operator
Geant4ParticleHandler& Geant4ParticleHandler::operator=(const Geant4ParticleHandler&) {
  return *this;
/// Adopt the user particle handler
bool Geant4ParticleHandler::adopt(Geant4Action* action)    {
  if ( action )   {
    if ( !m_userHandler )  {
      if ( Geant4UserParticleHandler* h = dynamic_cast<Geant4UserParticleHandler*>(action) )  {
        m_userHandler = h;
        m_userHandler->addRef();
        return true;
      except("Cannot add an invalid user particle handler object [Invalid-object-type].");
    except("Cannot add an user particle handler object [Object-exists].");
  except("Cannot add an invalid user particle handler object [NULL-object].");
  return false;
}

/// Clear particle maps
void Geant4ParticleHandler::clear()  {
Markus Frank's avatar
Markus Frank committed
  detail::releaseObjects(m_particleMap);
  m_particleMap.clear();
  // m_suspendedPM should already be empty and cleared...
  assert(m_suspendedPM.empty() && "There was something wrong with the particle record treatment, please open a bug report!");
  m_equivalentTracks.clear();
}

/// Mark a Geant4 track to be kept for later MC truth analysis
void Geant4ParticleHandler::mark(const G4Track* track, int reason)   {
  if ( track )   {
    if ( reason != 0 )  {
      PropertyMask(m_currTrack.reason).set(reason);
      return;
    }
  except("Cannot mark the G4Track if the pointer is invalid!");
}

/// Store a track produced in a step to be kept for later MC truth analysis
void Geant4ParticleHandler::mark(const G4Step* step_value, int reason)   {
  if ( step_value )  {
    mark(step_value->GetTrack(),reason);
  except("Cannot mark the G4Track if the step-pointer is invalid!");
}

/// Mark a Geant4 track of the step to be kept for later MC truth analysis
void Geant4ParticleHandler::mark(const G4Step* step_value)   {
  if ( step_value )  {
    mark(step_value->GetTrack());
  except("Cannot mark the G4Track if the step-pointer is invalid!");
}

/// Mark a Geant4 track of the step to be kept for later MC truth analysis
void Geant4ParticleHandler::mark(const G4Track* track)   {
  PropertyMask mask(m_currTrack.reason);
  mask.set(G4PARTICLE_CREATED_HIT);
  /// Check if the track origines from the calorimeter.
  // If yes, flag it, because it is a candidate for removal.
  G4LogicalVolume*       vol = track->GetVolume()->GetLogicalVolume();
  G4VSensitiveDetector*   g4 = vol->GetSensitiveDetector();
  Geant4ActionSD* sd = dynamic_cast<Geant4ActionSD*>(g4);
  string typ = sd ? sd->sensitiveType() : string();
  if ( typ == "calorimeter" )
    mask.set(G4PARTICLE_CREATED_CALORIMETER_HIT);
  else if ( typ == "tracker" )
    mask.set(G4PARTICLE_CREATED_TRACKER_HIT);
  else // Assume by default "tracker"
    mask.set(G4PARTICLE_CREATED_TRACKER_HIT);

  //Geant4ParticleHandle(&m_currTrack).dump4(outputLevel(),vol->GetName(),"hit created by particle");
}

/// Event generation action callback
void Geant4ParticleHandler::operator()(G4Event* event)  {
  typedef Geant4MonteCarloTruth _MC;
  debug("+++ Event:%d Add EVENT extension of type Geant4ParticleHandler.....",event->GetEventID());
  context()->event().addExtension((_MC*)this, false);
  /// Call the user particle handler
  if ( m_userHandler )  {
    m_userHandler->generate(event, this);
  }
/// User stepping callback
void Geant4ParticleHandler::step(const G4Step* step_value, G4SteppingManager* mgr)   {
  typedef vector<const G4Track*> _Sec;
  ++m_currTrack.steps;
  if ( (m_currTrack.reason&G4PARTICLE_ABOVE_ENERGY_THRESHOLD) )  {
    //
    // Tracks below the energy threshold are NOT stored.
    // If these tracks produce hits or are selected due to another signature,
    // this criterium will anyhow take precedence.
    //
    const _Sec* sec=step_value->GetSecondaryInCurrentStep();
      PropertyMask(m_currTrack.reason).set(G4PARTICLE_HAS_SECONDARIES);
    }
  }
  /// Update of the particle using the user handler
  if ( m_userHandler )  {
    m_userHandler->step(step_value, mgr, m_currTrack);
}

/// Pre-track action callback
void Geant4ParticleHandler::begin(const G4Track* track)   {
Markus Frank's avatar
Markus Frank committed
  Geant4TrackHandler   h(track);
  double               kine = h.kineticEnergy();
  G4ThreeVector        mom = h.momentum();
  const G4ThreeVector& v = h.vertex();
Markus Frank's avatar
Markus Frank committed
  int                  reason = (kine > m_kinEnergyCut) ? G4PARTICLE_ABOVE_ENERGY_THRESHOLD : 0;
  const G4PrimaryParticle* prim = h.primary();
  // if particles are not tracked to the end, we pick up where we stopped previously
  if (m_haveSuspended) {
    //primary particles are already in the particle map, we don't have to store them in another map
    auto existingParticle = m_particleMap.find(h.id());
    if(existingParticle != m_particleMap.end()) {
      m_currTrack.get_data(*(existingParticle->second));
      return;
    }
    //other particles might not be in the particleMap yet, so we take them from here
    existingParticle = m_suspendedPM.find(h.id());
    if(existingParticle != m_suspendedPM.end()) {
      m_currTrack.get_data(*(existingParticle->second));
      // make sure we delete a suspended particle in the map, fill it back later...
      delete (*existingParticle).second;
      m_suspendedPM.erase(existingParticle);
      return;
    }
    prim_part = m_primaryMap->get(prim);
    if ( !prim_part )  {
      except("+++ Tracking preaction: Primary particle without generator particle!");
    }
    reason |= (G4PARTICLE_PRIMARY|G4PARTICLE_ABOVE_ENERGY_THRESHOLD);
    m_particleMap[h.id()] = prim_part->addRef();
  }
  if ( prim_part )   {
    m_currTrack.id           = prim_part->id;
    m_currTrack.reason       = prim_part->reason|reason;
    m_currTrack.mask         = prim_part->mask;
    m_currTrack.status       = prim_part->status;
    m_currTrack.genStatus    = prim_part->genStatus;
    m_currTrack.spin[0]      = prim_part->spin[0];
    m_currTrack.spin[1]      = prim_part->spin[1];
    m_currTrack.spin[2]      = prim_part->spin[2];
    m_currTrack.colorFlow[0] = prim_part->colorFlow[0];
    m_currTrack.colorFlow[1] = prim_part->colorFlow[1];
    m_currTrack.parents      = prim_part->parents;
    m_currTrack.daughters    = prim_part->daughters;
    m_currTrack.pdgID        = prim_part->pdgID;
    m_currTrack.mass         = prim_part->mass;
    m_currTrack.charge       = int(3.0 * h.charge());
  }
  else  {
    m_currTrack.id           = m_globalParticleID;
    m_currTrack.reason       = reason;
    m_currTrack.mask         = 0;
    m_currTrack.status       = G4PARTICLE_SIM_CREATED;
    m_currTrack.spin[0]      = 0;
    m_currTrack.spin[1]      = 0;
    m_currTrack.spin[2]      = 0;
    m_currTrack.colorFlow[0] = 0;
    m_currTrack.colorFlow[1] = 0;
    m_currTrack.parents.clear();
Markus Frank's avatar
Markus Frank committed
    m_currTrack.pdgID        = h.pdgID();
    m_currTrack.mass         = h.mass();
    m_currTrack.charge       = int(3.0 * h.charge());
  m_currTrack.steps       = 0;
  m_currTrack.secondaries = 0;
  m_currTrack.g4Parent    = h.parent();
  m_currTrack.originalG4ID= h.id();
  m_currTrack.process     = h.creatorProcess();
  m_currTrack.time        = h.globalTime();
  m_currTrack.vsx         = v.x();
  m_currTrack.vsy         = v.y();
  m_currTrack.vsz         = v.z();
  m_currTrack.vex         = 0.0;
  m_currTrack.vey         = 0.0;
  m_currTrack.vez         = 0.0;
Markus Frank's avatar
Markus Frank committed
  m_currTrack.psx         = mom.x();
  m_currTrack.psy         = mom.y();
  m_currTrack.psz         = mom.z();
  m_currTrack.pex         = 0.0;
  m_currTrack.pey         = 0.0;
  m_currTrack.pez         = 0.0;
  // If the creator process of the track is in the list of process products to be kept, set the proper flag
  if ( m_currTrack.process )  {
    Processes::iterator i=find(m_processNames.begin(),m_processNames.end(),m_currTrack.process->GetProcessName());
    if ( i != m_processNames.end() )  {
      PropertyMask(m_currTrack.reason).set(G4PARTICLE_KEEP_PROCESS);
    }
  }
  if ( m_keepAll )  {
    PropertyMask(m_currTrack.reason).set(G4PARTICLE_KEEP_ALWAYS);
  /// Initial update of the particle using the user handler
  if ( m_userHandler )  {
    m_userHandler->begin(track, m_currTrack);
  }
}

/// Post-track action callback
void Geant4ParticleHandler::end(const G4Track* track)   {
  Geant4TrackHandler h(track);
  Geant4ParticleHandle ph(&m_currTrack);
  int track_reason = m_currTrack.reason;
  PropertyMask mask(m_currTrack.reason);
  // Update vertex end point and final momentum
Markus Frank's avatar
Markus Frank committed
  G4ThreeVector mom = track->GetMomentum();
  const G4ThreeVector& pos = track->GetPosition();
  ph->pex = mom.x();
  ph->pey = mom.y();
  ph->pez = mom.z();
  ph->vex = pos.x();
  ph->vey = pos.y();
  ph->vez = pos.z();
  // Set the simulator status bits
  PropertyMask simStatus(m_currTrack.status);

  // check if the last step ended on the worldVolume boundary
  const G4Step* theLastStep = track->GetStep();
  G4StepPoint* theLastPostStepPoint = NULL;
  if(theLastStep) theLastPostStepPoint = theLastStep->GetPostStepPoint();
  if( theLastPostStepPoint &&
      ( theLastPostStepPoint->GetStepStatus() == fWorldBoundary //particle left world volume
	//|| theLastPostStepPoint->GetStepStatus() == fGeomBoundary
      )
    ) {
    simStatus.set(G4PARTICLE_SIM_LEFT_DETECTOR);
  }

  if(track->GetKineticEnergy() <= 0.) {
    simStatus.set(G4PARTICLE_SIM_STOPPED);
  }

  /// Final update of the particle using the user handler
  if ( m_userHandler )  {
    m_userHandler->end(track, m_currTrack);
  // These are candidate tracks with a probability to be stored due to their properties:
  // - primary particle
  // - hits created
  // - secondaries
  // - above energy threshold
  // - to be kept due to user information of type 'Geant4ParticleInformation' stored in the G4Track
  Geant4ParticleInformation* track_info =
    dynamic_cast<Geant4ParticleInformation*>(track->GetUserInformation());
  if ( !mask.isNull() || track_info )   {
    m_equivalentTracks[g4_id] = g4_id;
    ParticleMap::iterator ip = m_particleMap.find(g4_id);
    if ( mask.isSet(G4PARTICLE_PRIMARY) )   {
      ph.dump2(outputLevel()-1,name(),"Add Primary",h.id(),ip!=m_particleMap.end());
    }
    // Create a new MC particle from the current track information saved in the pre-tracking action
    Particle* part = 0;
    if ( ip==m_particleMap.end() ) part = m_particleMap[g4_id] = new Particle();
    else part = (*ip).second;
    if ( track_info )  {
      mask.set(G4PARTICLE_KEEP_USER);
      part->extension.reset(track_info->release());
    }
    part->get_data(m_currTrack);
  }
  else   {
    // These are tracks without any special properties.
    //
    // We will not store them on the record, but have to memorise the
    // track identifier in order to restore the history for the created hits.
    int pid = m_currTrack.g4Parent;
    m_equivalentTracks[g4_id] = pid;
    // Need to find the last stored particle and OR this particle's mask
    // with the mask of the last stored particle
    auto iend = m_equivalentTracks.end(), iequiv=m_equivalentTracks.end();
    ParticleMap::iterator ip;
    for(ip=m_particleMap.find(pid); ip == m_particleMap.end(); ip=m_particleMap.find(pid))  {
      if (iequiv=m_equivalentTracks.find(pid); iequiv == iend) break;  // ERROR
      pid = (*iequiv).second;
    }
    if ( ip != m_particleMap.end() )
      (*ip).second->reason |= track_reason;
    else
      ph.dumpWithVertex(outputLevel()+3,name(),"FATAL: No real particle parent present");

  if(track->GetTrackStatus() == fSuspend) {
    m_haveSuspended = true;
    //track is already in particle map, we pick it up from there in begin again
    if(m_particleMap.find(g4_id) != m_particleMap.end()) return;
    //track is not already stored, keep it in special map
    auto iPart = m_suspendedPM.emplace(g4_id, new Particle());
    (iPart.first->second)->get_data(m_currTrack);
    return; // we trust that we eventually return to this function with another status and go on then
  }

/// Pre-event action callback
void Geant4ParticleHandler::beginEvent(const G4Event* event)  {
  Geant4PrimaryInteraction* interaction = context()->event().extension<Geant4PrimaryInteraction>();
  info("+++ Event %d Begin event action. Access event related information.",event->GetEventID());
  m_primaryMap = context()->event().extension<Geant4PrimaryMap>();
  m_globalParticleID = interaction->nextPID();
  m_particleMap.clear();
  m_equivalentTracks.clear();
  /// Call the user particle handler
  if ( m_userHandler )  {
    m_userHandler->begin(event);
  }
/// Debugging: Dump Geant4 particle map
void Geant4ParticleHandler::dumpMap(const char* tag)  const  {
  const string& n = name();
  Geant4ParticleHandle::header4(INFO,n,tag);
  for(ParticleMap::const_iterator iend=m_particleMap.end(), i=m_particleMap.begin(); i!=iend; ++i)  {
    Geant4ParticleHandle((*i).second).dump4(INFO,n,tag);
/// Post-event action callback
void Geant4ParticleHandler::endEvent(const G4Event* event)  {
  int count = 0;
    if ( level <= VERBOSE ) dumpMap("Particle  ");
    debug("+++ Iteration:%d Tracks:%d Equivalents:%d",++count,m_particleMap.size(),m_equivalentTracks.size());
  } while( recombineParents() > 0 );
  if ( level <= VERBOSE ) dumpMap(  "Recombined");
  // Rebase the simulated tracks, so that they fit to the generator particles
  rebaseSimulatedTracks(0);
  if ( level <= VERBOSE ) dumpMap(  "Rebased   ");
  // Consistency check....
  checkConsistency();
  /// Call the user particle handler
  if ( m_userHandler )  {
    m_userHandler->end(event);
  }
  setVertexEndpointBit();

  // Now export the data to the final record.
  Geant4ParticleMap* part_map = context()->event().extension<Geant4ParticleMap>();
  part_map->adopt(m_particleMap, m_equivalentTracks);
  m_primaryMap = 0;
  clear();
}

/// Rebase the simulated tracks, so that they fit to the generator particles
void Geant4ParticleHandler::rebaseSimulatedTracks(int )   {
  /// No we have to update the map of equivalent tracks and assign the 'equivalentTrack' entry
  TrackEquivalents equivalents, orgParticles;
  ParticleMap      finalParticles;
  ParticleMap::const_iterator ipar, iend, i;
  int count;

  Geant4PrimaryInteraction* interaction = context()->event().extension<Geant4PrimaryInteraction>();
  ParticleMap& pm = interaction->particles;

  // (1.0) Copy the pre-defined particle mapping for the simulated tracks
  //       It is assumed the mapping is ZERO based without holes.
  for(count = 0, iend=pm.end(), i=pm.begin(); i!=iend; ++i)  {
    Particle* p = (*i).second;
    orgParticles[p->id] = p->id;
    finalParticles[p->id] = p;
    if ( p->id > count ) count = p->id;
    if ( (p->reason&G4PARTICLE_PRIMARY) != G4PARTICLE_PRIMARY )  {
      p->addRef();
    }
  }
  // (1.1) Define the new particle mapping for the simulated tracks
  for(++count, iend=m_particleMap.end(), i=m_particleMap.begin(); i!=iend; ++i)  {
    Particle* p = (*i).second;
    if ( (p->reason&G4PARTICLE_PRIMARY) != G4PARTICLE_PRIMARY )  {
      //if ( orgParticles.find(p->id) == orgParticles.end() )  {
      orgParticles[p->id] = count;
      finalParticles[count] = p;
      p->id = count;
      ++count;
    }
  }
  // (2) Re-evaluate the corresponding geant4 track equivalents using the new mapping
  for(TrackEquivalents::iterator ie=m_equivalentTracks.begin(),ie_end=m_equivalentTracks.end(); ie!=ie_end; ++ie)  {
    int g4_equiv = (*ie).first;
    while( (ipar=m_particleMap.find(g4_equiv)) == m_particleMap.end() )  {
      TrackEquivalents::const_iterator iequiv = m_equivalentTracks.find(g4_equiv);
        break;  // ERROR !! Will be handled by printout below because ipar==end()
    TrackEquivalents::mapped_type equiv = (*ie).second;
    if ( ipar != m_particleMap.end() )   {
      Geant4ParticleHandle p = (*ipar).second;
      equivalents[(*ie).first] = p->id;  // requires (1) to be filled properly!
      const G4ParticleDefinition* def = p.definition();
Markus Frank's avatar
Markus Frank committed
      int pdg = int(fabs(def->GetPDGEncoding())+0.1);
      if ( pdg != 0 && pdg<36 && !(pdg > 10 && pdg < 17) && pdg != 22 )  {
        error("+++ ERROR: Geant4 particle for track:%d last known is:%d -- is gluon or quark!",equiv,g4_equiv);
Markus Frank's avatar
Markus Frank committed
      }
      pdg = int(fabs(p->pdgID)+0.1);
      if ( pdg != 0 && pdg<36 && !(pdg > 10 && pdg < 17) && pdg != 22 )  {
        error("+++ ERROR(2): Geant4 particle for track:%d last known is:%d -- is gluon or quark!",equiv,g4_equiv);
Markus Frank's avatar
Markus Frank committed
      }
Markus Frank's avatar
Markus Frank committed
    else   {
      error("+++ No Equivalent particle for track:%d last known is:%d",equiv,g4_equiv);
Markus Frank's avatar
Markus Frank committed
    }
  // (3) Compute the particle's parents and daughters.
  //     Replace the original Geant4 track with the
  //     equivalent particle still present in the record.
  // Note:
  //     We rely here on the ordering of the particles accoding to their
  //     Processing by Geant4 to establish mother daughter relationships.
  //     == > use finalParticles map and NOT m_particleMap.
  int equiv_id = -1;
Markus Frank's avatar
Markus Frank committed
  for( auto& part : finalParticles )  {
    auto& p = part.second;
    if ( p->g4Parent > 0 )  {
      TrackEquivalents::iterator iequ = equivalents.find(p->g4Parent);
      if ( iequ != equivalents.end() )  {
        equiv_id = (*iequ).second;//equivalents[p->g4Parent];
        if ( (ipar=finalParticles.find(equiv_id)) != finalParticles.end() )  {
          Particle* q = (*ipar).second;
          bool      prim = (p->reason&G4PARTICLE_PRIMARY) == G4PARTICLE_PRIMARY;
          // We assume that the mother daughter relationship
          // is filled by the event readers!
          if ( !prim )  {
            p->parents.insert(q->id);
          }
          if ( !p->parents.empty() )  {
            int parent_id = (*p->parents.begin());
            if ( parent_id == q->id )
              q->daughters.insert(p->id);
            else if ( !prim )
              error("+++ Inconsistency in equivalent record! Parent: %d Daughter:%d",q->id, p->id);
          }
          else   {
            error("+++ Inconsistency in parent relashionship: %d NO parent!", p->id);
          }
          continue;
        }
      error("+++ Inconsistency in particle record: Geant4 parent %d "
            "of particle %d not in record of final particles!",
            p->g4Parent,p->id);
    }
  }
#if 0
  for(iend=finalParticles.end(), i=finalParticles.begin(); i!=iend; ++i)  {
    Particle* p = (*i).second;
    if ( p->g4Parent > 0 )  {
      int parent_id = (*p->parents.begin());
      if ( (ipar=finalParticles.find(parent_id)) != finalParticles.end() )  {
        Particle* q = (*ipar).second;
        // Generator particles have a proper history.
        // We only deal with particles, which are not of MC origin.
        //p->parents.insert(q->id);
        if ( parent_id == q->id )
          q->daughters.insert(p->id);
        else
          error("+++ Inconsistency in equivalent record! Parent: %d Daughter:%d",q->id, p->id);
        continue;
      error("+++ Inconsistency in particle record: Geant4 parent %d "
            "of particle %d not in record of final particles!",
            p->g4Parent,p->id);
  m_equivalentTracks = equivalents;
  m_particleMap = finalParticles;
Markus Frank's avatar
Markus Frank committed
/// Default callback to be answered if the particle should be kept if NO user handler is installed
bool Geant4ParticleHandler::defaultKeepParticle(Particle& particle)   {
  PropertyMask mask(particle.reason);
  bool secondaries    =  mask.isSet(G4PARTICLE_HAS_SECONDARIES);
  bool tracker_track  =  mask.isSet(G4PARTICLE_CREATED_TRACKER_HIT);
  bool calo_track     =  mask.isSet(G4PARTICLE_CREATED_CALORIMETER_HIT);
  bool hits_produced  =  mask.isSet(G4PARTICLE_CREATED_HIT);
  bool low_energy     = !mask.isSet(G4PARTICLE_ABOVE_ENERGY_THRESHOLD);

  /// Remove this track if it has not created a hit and the energy is below threshold
  if ( mask.isNull() || (secondaries && low_energy && !hits_produced) )  {
    return true;
  }
  /// Remove this track if the energy is below threshold. Reassign hits to parent.
  else if ( !hits_produced && low_energy )  {
    return true;
  }
  /// Remove this track if the origine is in the calorimeter. Reassign hits to parent.
  else if ( !tracker_track && calo_track && low_energy )  {
    return true;
  }
  else  {
    //printout(INFO,name(),"+++ Track: %d should be kept for no obvious reason....",id);
  }
  return false;
}

/// Clean the monte carlo record. Remove all unwanted stuff.
/// This is the core of the object executed at the end of each event action.
int Geant4ParticleHandler::recombineParents()  {
  set<int> remove;
  /// Need to start from BACK, to clean first the latest produced stuff.
  for(ParticleMap::reverse_iterator i=m_particleMap.rbegin(); i!=m_particleMap.rend(); ++i)  {
Markus Frank's avatar
Markus Frank committed
    PropertyMask mask(p->reason);
    // Allow the user to force the particle handling either by
    // or the reason mask with G4PARTICLE_KEEP_USER or
    // to set the reason mask to NULL in order to drop it.
Markus Frank's avatar
Markus Frank committed
    //
    // If the mask entry is set to G4PARTICLE_FORCE_KILL
    // or is set to NULL, the particle is ALWAYS removed
    //
    // Note: This may override all other decisions!
Markus Frank's avatar
Markus Frank committed
    bool remove_me = m_userHandler ? m_userHandler->keepParticle(*p) : defaultKeepParticle(*p);
Markus Frank's avatar
Markus Frank committed
    // Now look at the property mask of the particle
    if ( mask.isNull() || mask.isSet(G4PARTICLE_FORCE_KILL) )  {
      remove_me = true;
    }
    else if ( mask.isSet(G4PARTICLE_KEEP_USER) )  {
      /// If user decides it must be kept, it MUST be kept!
      mask.set(G4PARTICLE_KEEP_USER);
      continue;
    }
    else if ( mask.isSet(G4PARTICLE_PRIMARY) )   {
      /// Primary particles MUST be kept!
      continue;
    }
    else if ( mask.isSet(G4PARTICLE_KEEP_ALWAYS) )   {
      continue;
    }
    else if ( mask.isSet(G4PARTICLE_KEEP_PARENT) )  {
      //continue;
    }
    else if ( mask.isSet(G4PARTICLE_KEEP_PROCESS) )  {
      if(ParticleMap::iterator ip = m_particleMap.find(p->g4Parent); ip != m_particleMap.end() )   {
        Particle* parent_part = (*ip).second;
        PropertyMask parent_mask(parent_part->reason);
        if ( parent_mask.isSet(G4PARTICLE_ABOVE_ENERGY_THRESHOLD) )   {
          parent_mask.set(G4PARTICLE_KEEP_PARENT);
          continue;
        }
Markus Frank's avatar
Markus Frank committed
      // Low energy stuff. Remove it. Reassign to parent.
      //remove_me = true;
    }
Markus Frank's avatar
Markus Frank committed
    /// Remove this track from the list and also do the cleanup in the parent's children list
    if ( remove_me )  {
      int g4_id = (*i).first;
      remove.insert(g4_id);
      m_equivalentTracks[g4_id] = p->g4Parent;
      if(ParticleMap::iterator ip = m_particleMap.find(p->g4Parent); ip != m_particleMap.end() )   {
        Particle* parent_part = (*ip).second;
        PropertyMask(parent_part->reason).set(mask.value());
        parent_part->steps += p->steps;
        parent_part->secondaries += p->secondaries;
        /// Update of the particle using the user handler
        if ( m_userHandler )  {
          m_userHandler->combine(*p, *parent_part);
        }
  for( int r : remove )  {
    if( auto ir = m_particleMap.find(r); ir != m_particleMap.end() )  {
      m_particleMap.erase(ir);
    }
  }
  return int(remove.size());
/// Check the record consistency
void Geant4ParticleHandler::checkConsistency()  const   {
  int num_errors = 0;

  /// First check the consistency of the particle map itself
Markus Frank's avatar
Markus Frank committed
  for(const auto& part : m_particleMap )  {
    Geant4Particle* particle = part.second;
    Geant4ParticleHandle p(particle);
    PropertyMask mask(p->reason);
    PropertyMask status(p->status);
    set<int>& daughters = p->daughters;
    ParticleMap::const_iterator j;
    // For all particles, the set of daughters must be contained in the record.
    for( int id_dau : daughters )   {
      if ( j=m_particleMap.find(id_dau); j == m_particleMap.end() )   {
        ++num_errors;
        error("+++ Particle:%d Daughter %d is not in particle map!",p->id,id_dau);
    // We assume that particles from the generator have consistent parents
    // For all other particles except the primaries, the parent must be contained in the record.
    if ( !mask.isSet(G4PARTICLE_PRIMARY) && !status.anySet(G4PARTICLE_GEN_STATUS) )  {
Markus Frank's avatar
Markus Frank committed
      bool in_map = false, in_parent_list = false;
      int  parent_id = -1;
      if( auto eq_it=m_equivalentTracks.find(p->g4Parent); eq_it != m_equivalentTracks.end() )   {
        in_map    = (j=m_particleMap.find(parent_id)) != m_particleMap.end();
        in_parent_list = p->parents.find(parent_id) != p->parents.end();
Markus Frank's avatar
Markus Frank committed
      }
      if ( !in_map || !in_parent_list )  {
        char parent_list[1024];
        parent_list[0] = 0;
        ++num_errors;
        p.dumpWithMomentum(ERROR,name(),"INCONSISTENCY");
        for( int ip : p->parents )
          ::snprintf(parent_list+strlen(parent_list),sizeof(parent_list)-strlen(parent_list),"%d ",ip);
        error("+++ Particle:%d Parent %d (G4id:%d)  In record:%s In parent list:%s [%s]",
              p->id,parent_id,p->g4Parent,yes_no(in_map),yes_no(in_parent_list),parent_list);
  if ( num_errors > 0 )  {
    except("+++ Consistency check failed. Found %d problems.",num_errors);

void Geant4ParticleHandler::setVertexEndpointBit() {
Markus Frank's avatar
Markus Frank committed
  for( auto& part : m_particleMap )   {
    auto* p = part.second;
    if( !p->parents.empty() )   {
      Geant4Particle *parent(m_particleMap[ *p->parents.begin() ]);
      const double X( parent->vex - p->vsx );
      const double Y( parent->vey - p->vsy );
      const double Z( parent->vez - p->vsz );
      if( sqrt(X*X + Y*Y + Z*Z) > m_minDistToParentVertex ){
        PropertyMask(p->status).set(G4PARTICLE_SIM_PARENT_RADIATED);
      }