From 8c83cd4df59acdd3d11bfb13890bd1247e3e448c Mon Sep 17 00:00:00 2001
From: Dave Casper <dcasper@uci.edu>
Date: Sat, 11 Jun 2022 13:31:41 -0700
Subject: [PATCH] Handle heavy flavor and tau decays in G4 with Pythia8

---
 .../Pythia8Decayer/CMakeLists.txt             |  31 ++++
 .../python/Pythia8DecayerConfigNew.py         |  11 ++
 .../Pythia8Decayer/src/Pythia8Decayer.cxx     | 136 ++++++++++++++++++
 .../Pythia8Decayer/src/Pythia8Decayer.h       |  63 ++++++++
 .../src/Pythia8DecayerPhysicsTool.cxx         | 108 ++++++++++++++
 .../src/Pythia8DecayerPhysicsTool.h           |  43 ++++++
 .../src/components/Pythia8Decayer_entries.cxx |   4 +
 .../python/G4FaserServicesConfigNew.py        |   6 +-
 8 files changed, 400 insertions(+), 2 deletions(-)
 create mode 100644 Simulation/G4Extensions/Pythia8Decayer/CMakeLists.txt
 create mode 100644 Simulation/G4Extensions/Pythia8Decayer/python/Pythia8DecayerConfigNew.py
 create mode 100644 Simulation/G4Extensions/Pythia8Decayer/src/Pythia8Decayer.cxx
 create mode 100644 Simulation/G4Extensions/Pythia8Decayer/src/Pythia8Decayer.h
 create mode 100644 Simulation/G4Extensions/Pythia8Decayer/src/Pythia8DecayerPhysicsTool.cxx
 create mode 100644 Simulation/G4Extensions/Pythia8Decayer/src/Pythia8DecayerPhysicsTool.h
 create mode 100644 Simulation/G4Extensions/Pythia8Decayer/src/components/Pythia8Decayer_entries.cxx

diff --git a/Simulation/G4Extensions/Pythia8Decayer/CMakeLists.txt b/Simulation/G4Extensions/Pythia8Decayer/CMakeLists.txt
new file mode 100644
index 00000000..901e0e3e
--- /dev/null
+++ b/Simulation/G4Extensions/Pythia8Decayer/CMakeLists.txt
@@ -0,0 +1,31 @@
+################################################################################
+# Package: Pythia8Decayer
+################################################################################
+
+# Declare the package name:
+atlas_subdir( Pythia8Decayer )
+
+#if( NOT GENERATIONBASE )
+
+    # External dependencies:
+    find_package( CLHEP )
+    find_package( Geant4 )
+    find_package( XercesC )
+    find_package( Pythia8 )
+
+    # Component(s) in the package:
+    atlas_add_library( Pythia8DecayerLib
+                       src/*.cxx
+                       NO_PUBLIC_HEADERS
+                       PRIVATE_INCLUDE_DIRS ${GEANT4_INCLUDE_DIRS} ${XERCESC_INCLUDE_DIRS} ${CLHEP_INCLUDE_DIRS} ${PYTHIA8_INCLUDE_DIRS}
+                       PRIVATE_LINK_LIBRARIES ${GEANT4_LIBRARIES} ${XERCESC_LIBRARIES} ${CLHEP_LIBRARIES} ${PYTHIA8_LIBRARIES} GaudiKernel AthenaBaseComps G4AtlasInterfaces G4AtlasToolsLib Pythia8_iLib )
+
+    atlas_add_component( Pythia8Decayer
+                         src/components/*.cxx
+                         INCLUDE_DIRS ${GEANT4_INCLUDE_DIRS} ${XERCESC_INCLUDE_DIRS} ${CLHEP_INCLUDE_DIRS} ${PYTHIA8_INCLUDE_DIRS}
+                         LINK_LIBRARIES ${GEANT4_LIBRARIES} ${XERCESC_LIBRARIES} ${CLHEP_LIBRARIES} ${PYTHIA8_LIBRARIES} Pythia8DecayerLib AthenaBaseComps G4AtlasInterfaces )       
+#endif()
+
+# Install files from the package:
+atlas_install_python_modules( python/*.py POST_BUILD_CMD ${ATLAS_FLAKE8} )
+#atlas_install_joboptions( share/*.py POST_BUILD_CMD ${ATLAS_FLAKE8} --extend-ignore=F401,F821 )
diff --git a/Simulation/G4Extensions/Pythia8Decayer/python/Pythia8DecayerConfigNew.py b/Simulation/G4Extensions/Pythia8Decayer/python/Pythia8DecayerConfigNew.py
new file mode 100644
index 00000000..6085f5cc
--- /dev/null
+++ b/Simulation/G4Extensions/Pythia8Decayer/python/Pythia8DecayerConfigNew.py
@@ -0,0 +1,11 @@
+# Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
+
+from AthenaConfiguration.ComponentFactory import CompFactory
+from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
+from AthenaCommon import Logging
+import os
+
+def Pythia8DecayerPhysicsToolCfg(flags, name='Pythia8DecayerPhysicsTool', **kwargs):
+    result = ComponentAccumulator()
+    result.setPrivateTools( CompFactory.Pythia8DecayerPhysicsTool(name,**kwargs) )
+    return result
diff --git a/Simulation/G4Extensions/Pythia8Decayer/src/Pythia8Decayer.cxx b/Simulation/G4Extensions/Pythia8Decayer/src/Pythia8Decayer.cxx
new file mode 100644
index 00000000..7047c554
--- /dev/null
+++ b/Simulation/G4Extensions/Pythia8Decayer/src/Pythia8Decayer.cxx
@@ -0,0 +1,136 @@
+/*
+  Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+*/
+
+// Header for my class
+#include "Pythia8Decayer.h"
+
+// The actual decayers.  Singleton classes, not toolhandles
+// #include "Pythia8Instance.h"
+
+// For passing things around
+#include "CLHEP/Vector/LorentzVector.h"
+#include "G4Track.hh"
+#include "G4DynamicParticle.hh"
+#include "G4ParticleTable.hh"
+#include "G4DecayProducts.hh"
+
+Pythia8Decayer::Pythia8Decayer( const std::string s )
+ : G4VExtDecayer(s)
+{
+  // In the constructor, make a decayer instance, so that it's initialized here and not in the event loop
+  std::string docstring = Pythia8_i::xmlpath();
+  m_decayer = std::make_unique<Pythia8::Pythia>(docstring);
+
+  m_decayer->readString("ProcessLevel:all = off"); 
+
+  m_decayer->readString("ProcessLevel:resonanceDecays=on");
+    
+  // shut off Pythia8 (default) verbosty
+  //
+  m_decayer->readString("Init:showAllSettings=false");
+  m_decayer->readString("Init:showChangedSettings=false");
+  m_decayer->readString("Init:showAllParticleData=false");
+  m_decayer->readString("Init:showChangedParticleData=false");
+  //
+  // specify how many Py8 events to print out, at either level
+  // in this particular case print out a maximum of 10 events
+  //
+  m_decayer->readString("Next:numberShowProcess = 0" );
+  m_decayer->readString("Next:numberShowEvent = 10");
+           
+  m_decayer->init();
+   
+  // shut off decays of pi0's as we want Geant4 to handle them
+  // if other immediate decay products should be handled by Geant4,
+  // their respective decay modes should be shut off as well
+  //
+  m_decayer->readString("111:onMode = off");
+}
+
+G4DecayProducts* Pythia8Decayer::ImportDecayProducts(const G4Track& aTrack){
+
+   m_decayer->event.reset();
+   
+   G4DecayProducts* dproducts = nullptr;   
+   
+   G4ParticleDefinition* pd = aTrack.GetDefinition();
+   int    pdgid   = pd->GetPDGEncoding();
+   
+   // check if pdgid is consistent with Pythia8 particle table
+   //   
+   if ( !m_decayer->particleData.findParticle( pdgid ) )
+   {
+      G4cout << " can NOT find pdgid = " << pdgid 
+             << " in Pythia8::ParticleData" << G4endl;
+      return dproducts;
+   }
+   
+   if ( !m_decayer->particleData.canDecay(pdgid) )
+   {
+      G4cout << " Particle of pdgid = " << pdgid 
+             << " can NOT be decayed by Pythia8" << G4endl;
+      return dproducts;
+   }
+   
+   // NOTE: Energy should be in GeV 
+
+   m_decayer->event.append( pdgid, 1, 0, 0, 
+                           aTrack.GetMomentum().x() / CLHEP::GeV, 
+                           aTrack.GetMomentum().y() / CLHEP::GeV,  
+                           aTrack.GetMomentum().z() / CLHEP::GeV,
+                           aTrack.GetDynamicParticle()->GetTotalEnergy() / CLHEP::GeV,
+                           pd->GetPDGMass() / CLHEP::GeV );
+
+   // specify polarization, if any
+   
+   // NOTE: while in Py8 polarization is a double variable , 
+   //       in reality it's expected to be -1, 0., or 1 in case of "external" tau's, 
+   //       similar to LHA SPINUP; see Particle Decays, Hadron and Tau Decays in docs at
+   //       https://pythia.org/manuals/pythia8305/Welcome.html
+   //       so it's not able to handle anything like 0.99, thus we're rounding off    
+   m_decayer->event.back().pol( 
+      round( std::cos( aTrack.GetPolarization().angle( aTrack.GetMomentumDirection() ) )
+      ) 
+   );
+
+   int npart_before_decay = m_decayer->event.size();
+   
+   m_decayer->next();
+   
+   int npart_after_decay = m_decayer->event.size();
+   
+   // create & fill up decay products
+   //
+   dproducts = new G4DecayProducts(*(aTrack.GetDynamicParticle()));
+   
+   // create G4DynamicParticle out of each m_decayer->event entry (except the 1st one)
+   // and push into dproducts
+   
+   for ( int ip=npart_before_decay; ip<npart_after_decay; ++ip )
+   {
+      
+      // only select final state decay products (direct or via subsequent decays);
+      // skip all others
+      //
+      // NOTE: in general, final state decays products will have 
+      //       positive status code between 91 and 99 
+      //       (in case such information could be of interest in the future)
+      //
+      if ( m_decayer->event[ip].status() < 0 ) continue;
+            
+      // should we also skip neutrinos ???
+      // if so, skip products with abs(m_decayer->event[ip].id()) of 12, 14, or 16
+            
+      G4ParticleDefinition* pddec = 
+         G4ParticleTable::GetParticleTable()->FindParticle( m_decayer->event[ip].id() );
+      if ( !pddec ) continue; // maybe we should print out a warning !
+      G4ThreeVector momentum = G4ThreeVector( m_decayer->event[ip].px() * CLHEP::GeV,
+                                              m_decayer->event[ip].py() * CLHEP::GeV,
+                                              m_decayer->event[ip].pz() * CLHEP::GeV ); 
+      dproducts->PushProducts( new G4DynamicParticle( pddec, momentum) ); 
+   }
+   dproducts->DumpInfo();
+   return dproducts;
+
+}
diff --git a/Simulation/G4Extensions/Pythia8Decayer/src/Pythia8Decayer.h b/Simulation/G4Extensions/Pythia8Decayer/src/Pythia8Decayer.h
new file mode 100644
index 00000000..61b52afc
--- /dev/null
+++ b/Simulation/G4Extensions/Pythia8Decayer/src/Pythia8Decayer.h
@@ -0,0 +1,63 @@
+//
+// ********************************************************************
+// * License and Disclaimer                                           *
+// *                                                                  *
+// * The  Geant4 software  is  copyright of the Copyright Holders  of *
+// * the Geant4 Collaboration.  It is provided  under  the terms  and *
+// * conditions of the Geant4 Software License,  included in the file *
+// * LICENSE and available at  http://cern.ch/geant4/license .  These *
+// * include a list of copyright holders.                             *
+// *                                                                  *
+// * Neither the authors of this software system, nor their employing *
+// * institutes,nor the agencies providing financial support for this *
+// * work  make  any representation or  warranty, express or implied, *
+// * regarding  this  software system or assume any liability for its *
+// * use.  Please see the license in the file  LICENSE  and URL above *
+// * for the full disclaimer and the limitation of liability.         *
+// *                                                                  *
+// * This  code  implementation is the result of  the  scientific and *
+// * technical work of the GEANT4 collaboration.                      *
+// * By using,  copying,  modifying or  distributing the software (or *
+// * any work based  on the software)  you  agree  to acknowledge its *
+// * use  in  resulting  scientific  publications,  and indicate your *
+// * acceptance of all terms of the Geant4 Software license.          *
+// ********************************************************************
+//
+//
+/// \file eventgenerator/pythia/pythia8decayer/include/Py8Decayer.hh
+/// \brief Definition of the Py8Decayer class
+///
+/// \author J. Yarba; FNAL
+///
+
+#ifndef Pythia8Decayer_H
+#define Pythia8Decayer_H
+
+#include "G4VExtDecayer.hh"
+#include "globals.hh"
+
+#include "Pythia8_i/Pythia8_i.h"
+
+class G4Track;
+class G4DecayProducts;
+
+class Pythia8Decayer : public G4VExtDecayer
+{
+  
+   public:
+
+      //ctor & dtor
+      Pythia8Decayer( const std::string s );
+
+      virtual G4DecayProducts* ImportDecayProducts(const G4Track&);
+    
+   private:
+   
+      // data members
+      std::unique_ptr<Pythia8::Pythia> m_decayer;
+
+};
+
+//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......
+
+#endif
diff --git a/Simulation/G4Extensions/Pythia8Decayer/src/Pythia8DecayerPhysicsTool.cxx b/Simulation/G4Extensions/Pythia8Decayer/src/Pythia8DecayerPhysicsTool.cxx
new file mode 100644
index 00000000..750272b2
--- /dev/null
+++ b/Simulation/G4Extensions/Pythia8Decayer/src/Pythia8DecayerPhysicsTool.cxx
@@ -0,0 +1,108 @@
+/*
+  Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
+*/
+
+// class header
+#include "Pythia8DecayerPhysicsTool.h"
+// package headers
+#include "Pythia8Decayer.h"
+// Geant4 headers
+#include "globals.hh"
+#include "G4ParticleTable.hh"
+#include "G4VProcess.hh"
+#include "G4ProcessManager.hh"
+#include "G4Decay.hh"
+#include "G4DecayTable.hh"
+
+// STL headers
+#include <string>
+
+//=============================================================================
+// Standard constructor, initializes variables
+//=============================================================================
+Pythia8DecayerPhysicsTool::Pythia8DecayerPhysicsTool( const std::string& type,
+                                                      const std::string& nam,
+                                                      const IInterface* parent )
+  : base_class ( type, nam , parent )
+{
+}
+
+//=============================================================================
+// Destructor
+//=============================================================================
+
+Pythia8DecayerPhysicsTool::~Pythia8DecayerPhysicsTool()
+{
+}
+
+//=============================================================================
+// Initialize
+//=============================================================================
+StatusCode Pythia8DecayerPhysicsTool::initialize( )
+{
+  ATH_MSG_DEBUG("Pythia8DecayerPhysicsTool initialize( )");
+  this->SetPhysicsName(name());
+  return StatusCode::SUCCESS;
+}
+
+Pythia8DecayerPhysicsTool* Pythia8DecayerPhysicsTool::GetPhysicsOption()
+{
+  return this;
+}
+
+
+void Pythia8DecayerPhysicsTool::ConstructParticle()
+{
+    // nothing to do here
+}
+void Pythia8DecayerPhysicsTool::ConstructProcess()
+{
+  ATH_MSG_DEBUG("Pythia8DecayerPhysicsTool::ConstructProcess() called ");
+
+   Pythia8Decayer* extDecayer = new Pythia8Decayer(name());
+
+   auto particleIterator=GetParticleIterator();
+   particleIterator->reset();
+   while ((*particleIterator)())
+   {    
+      G4ParticleDefinition* particle = particleIterator->value();
+      G4ProcessManager* pmanager = particle->GetProcessManager();    
+      G4ProcessVector* processVector = pmanager->GetProcessList();
+      for ( size_t i=0; i<processVector->length(); ++i ) 
+      {    
+         G4Decay* decay = dynamic_cast<G4Decay*>((*processVector)[i]);
+         if ( decay ) 
+         {
+            // remove native/existing decay table for
+            // a)tau's 
+            // b) B+/- 
+            // and replace with external decayer
+            if ( std::abs(particle->GetPDGEncoding()) == 15 ||
+                 std::abs(particle->GetPDGEncoding()) == 521 )
+            {
+               if ( particle->GetDecayTable() )
+               {
+                  delete particle->GetDecayTable();
+                  particle->SetDecayTable(nullptr);
+               }
+               decay->SetExtDecayer(extDecayer);
+               G4cout << "Setting ext decayer for: " 
+                <<  particleIterator->value()->GetParticleName()
+                << G4endl;
+            }
+            // now set external decayer to all particles 
+            // that don't yet have a decay table
+            if ( !particle->GetDecayTable() )
+            {
+               decay->SetExtDecayer(extDecayer);
+               G4cout << "Setting ext decayer for: " 
+                <<  particleIterator->value()->GetParticleName()
+                << G4endl;
+            }
+         }
+      }              
+   }
+
+   return;
+   
+}
diff --git a/Simulation/G4Extensions/Pythia8Decayer/src/Pythia8DecayerPhysicsTool.h b/Simulation/G4Extensions/Pythia8Decayer/src/Pythia8DecayerPhysicsTool.h
new file mode 100644
index 00000000..9acd13bd
--- /dev/null
+++ b/Simulation/G4Extensions/Pythia8Decayer/src/Pythia8DecayerPhysicsTool.h
@@ -0,0 +1,43 @@
+/*
+  Copyright (C) 2002-2022 CERN for the benefit of the ATLAS and FASER collaborations
+*/
+
+#ifndef PYTHIA8DECAYER_PYTHIA8DECAYERPHYSICSTOOL_H
+#define PYTHIA8DECAYER_PYTHIA8DECAYERPHYSICSTOOL_H
+
+// Include files
+#include "AthenaBaseComps/AthAlgTool.h"
+#include "G4AtlasInterfaces/IPhysicsOptionTool.h"
+#include "G4VPhysicsConstructor.hh"
+
+/** @class Pythia8DecayerPhysicsTool Pythia8DecayerPhysicsTool.h "Pythia8Decayer/Pythia8DecayerPhysicsTool.h"
+ *
+ *
+ *
+ *  @author Edoardo Farina (modified for FASER by D. Casper)
+ *  @date  2015-05-14
+ */
+class Pythia8DecayerPhysicsTool :  public G4VPhysicsConstructor, public extends<AthAlgTool, IPhysicsOptionTool>
+{
+public:
+  /// Standard constructor
+  Pythia8DecayerPhysicsTool( const std::string& type , const std::string& name,
+                             const IInterface* parent ) ;
+
+  virtual ~Pythia8DecayerPhysicsTool( ); ///< Destructor
+
+  /// Initialize method
+  virtual StatusCode initialize( ) ;
+  virtual void ConstructParticle();
+  virtual void ConstructProcess();
+
+  /** Implements
+   */
+
+  virtual Pythia8DecayerPhysicsTool* GetPhysicsOption();
+
+};
+
+
+
+#endif
diff --git a/Simulation/G4Extensions/Pythia8Decayer/src/components/Pythia8Decayer_entries.cxx b/Simulation/G4Extensions/Pythia8Decayer/src/components/Pythia8Decayer_entries.cxx
new file mode 100644
index 00000000..8bbd4660
--- /dev/null
+++ b/Simulation/G4Extensions/Pythia8Decayer/src/components/Pythia8Decayer_entries.cxx
@@ -0,0 +1,4 @@
+#include "../Pythia8DecayerPhysicsTool.h"
+
+DECLARE_COMPONENT( Pythia8DecayerPhysicsTool )
+
diff --git a/Simulation/G4Faser/G4FaserServices/python/G4FaserServicesConfigNew.py b/Simulation/G4Faser/G4FaserServices/python/G4FaserServicesConfigNew.py
index 839a4fda..1b17627a 100644
--- a/Simulation/G4Faser/G4FaserServices/python/G4FaserServicesConfigNew.py
+++ b/Simulation/G4Faser/G4FaserServices/python/G4FaserServicesConfigNew.py
@@ -5,6 +5,8 @@ from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
 
 DetectorGeometrySvc, G4AtlasSvc, G4GeometryNotifierSvc, PhysicsListSvc=CompFactory.getComps("DetectorGeometrySvc","G4AtlasSvc","G4GeometryNotifierSvc","PhysicsListSvc",)
 from G4FaserTools.G4GeometryToolConfig import G4AtlasDetectorConstructionToolCfg
+from G4StepLimitation.G4StepLimitationConfigNew import G4StepLimitationToolCfg
+from Pythia8Decayer.Pythia8DecayerConfigNew import Pythia8DecayerPhysicsToolCfg
 
 def DetectorGeometrySvcCfg(ConfigFlags, name="DetectorGeometrySvc", **kwargs):
     result = ComponentAccumulator()
@@ -34,9 +36,9 @@ def G4GeometryNotifierSvcCfg(ConfigFlags, name="G4GeometryNotifierSvc", **kwargs
 
 def PhysicsListSvcCfg(ConfigFlags, name="PhysicsListSvc", **kwargs):
     result = ComponentAccumulator()
-    G4StepLimitationTool = CompFactory.G4StepLimitationTool
-    PhysOptionList = [G4StepLimitationTool("G4StepLimitationTool")]
+    PhysOptionList = [ result.popToolsAndMerge(G4StepLimitationToolCfg(ConfigFlags)) ]    
     #PhysOptionList += ConfigFlags.Sim.PhysicsOptions # FIXME Missing functionality
+    PhysOptionList += [ result.popToolsAndMerge(Pythia8DecayerPhysicsToolCfg(ConfigFlags)) ]
     PhysDecaysList = []
     kwargs.setdefault("PhysOption", PhysOptionList)
     kwargs.setdefault("PhysicsDecay", PhysDecaysList)
-- 
GitLab