ATLAS Offline Software
Loading...
Searching...
No Matches
G4ProcessHelper.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
3*/
4
5#include "G4ProcessHelper.hh"
6#include "G4Track.hh"
7#include "G4Element.hh"
8#include "G4DynamicParticle.hh"
9#include "G4ParticleDefinition.hh"
10#include "G4Exception.hh"
12
13#include "CustomParticle.h"
15#include "G4ParticleTable.hh"
16#include "G4DecayTable.hh"
17#include "CLHEP/Random/RandFlat.h"
18#include "CLHEP/Units/PhysicalConstants.h"
19#include <iostream>
20#include <fstream>
21#include <stdexcept>
22
24
25G4ProcessHelper::G4ProcessHelper()
26{
27 G4cout << "G4ProcessHelper constructor: start" << G4endl;
29 particleTable = G4ParticleTable::GetParticleTable();
30 theProton = particleTable->FindParticle("proton");
31 theNeutron = particleTable->FindParticle("neutron");
32
33
34 // I opted for string based read-in, as it would make physics debugging easier later on
35
36 std::ifstream process_stream ("ProcessList.txt");
37 G4String line;
38 while(getline(process_stream,line)){
39 std::vector<G4String> tokens;
40
41 //Getting a line
42 ReadAndParse(line,tokens,"#");
43
44 //Important info
45 G4String incident = tokens[0];
46 // G4cout << "Incident particle: " << incident << G4endl;
47 G4ParticleDefinition* incidentDef = particleTable->FindParticle(incident);
48 G4int incidentPDG = incidentDef->GetPDGEncoding();
49 m_knownParticles[incidentDef]=true;
50
51 G4String target = tokens[1];
52 // G4cout << "Target particle: " << target << G4endl;
53
54 // Making a ReactionProduct
55 ReactionProduct prod;
56 for (unsigned int i = 2; i != tokens.size();i++){
57 G4String part = tokens[i];
58 if (particleTable->contains(part))
59 {
60 prod.push_back(particleTable->FindParticle(part)->GetPDGEncoding());
61 } else {
62 G4cout<<"Particle: "<<part<<" is unknown."<<G4endl;
63 G4Exception("G4ProcessHelper", "UnkownParticle", FatalException,
64 "Initialization: The reaction product list contained an unknown particle");
65 }
66 }
67 if (target == "proton"){
68 pReactionMap[incidentPDG].push_back(std::move(prod));
69 } else if (target == "neutron") {
70 nReactionMap[incidentPDG].push_back(std::move(prod));
71 } else {
72 G4Exception("G4ProcessHelper", "IllegalTarget", FatalException,
73 "Initialization: The reaction product list contained an illegal target particle");
74 }
75 }
76
77 process_stream.close();
78 G4cout << "Found " << pReactionMap.size() << " proton interactions and " << nReactionMap.size() << " neutron interactions in ProcessList.txt." << G4endl;
79
80 G4ParticleTable::G4PTblDicIterator* theParticleIterator;
81 theParticleIterator = particleTable->GetIterator();
82
83 theParticleIterator->reset();
84 while( (*theParticleIterator)() ){
85 G4DecayTable* table = theParticleIterator->value()->GetDecayTable();
86 SetCustomParticleLifeTime(theParticleIterator->value(), table);
87 }
88 theParticleIterator->reset();
89 G4cout << "G4ProcessHelper constructor: end" << G4endl;
90 return;
91}
92
93
94void G4ProcessHelper::SetCustomParticleLifeTime(G4ParticleDefinition* aPart, bool decaysIdentified) const
95{
96 CustomParticle* particle = dynamic_cast<CustomParticle*>(aPart);
97 std::string name = aPart->GetParticleName();
98 if (particle) {
99 if (m_parameters->DoDecays() == 1){
100 // Make them decay immediately!!
101 particle->SetPDGStable(false);
102 particle->SetPDGLifeTime(m_parameters->Lifetime()); // Lifetime of nano-seconds
103 G4cout<<"Forcing a decay for "<<name<<G4endl;
104 G4cout<<"Lifetime of: "<<name<<" set to: "<<particle->GetPDGLifeTime()/CLHEP::ns<<" ns."<<G4endl;
105 G4cout<<"Stable: "<<particle->GetPDGStable()<<G4endl;
106 }
107 else if (decaysIdentified && name.find("cloud")>name.size()) {
108 particle->SetPDGLifeTime(m_parameters->Lifetime()); // Lifetime of seconds
109 particle->SetPDGStable(false);
110 G4cout<<"Lifetime of: "<<name<<" set to: "<<particle->GetPDGLifeTime()/CLHEP::s<<" s."<<G4endl;
111 G4cout<<"Stable: "<<particle->GetPDGStable()<<G4endl;
112 }
113 }
114 G4cout << "Done with particle " << name << G4endl;
115}
116
117
118const G4ProcessHelper* G4ProcessHelper::Instance()
119{
120 static const G4ProcessHelper instance;
121 return &instance;
122}
123
124
125G4bool G4ProcessHelper::ApplicabilityTester(const G4ParticleDefinition& aPart) const {
126 try {
127 return m_knownParticles.at(&aPart);
128 }
129 catch (const std::out_of_range& e) {
130 return false;
131 }
132}
133
134G4double G4ProcessHelper::GetInclusiveCrossSection(const G4DynamicParticle *aParticle,
135 const G4Element *anElement) const {
136 //We really do need a dedicated class to handle the cross sections. They might not always be constant
137
138 //Disassemble the PDG-code
139 const G4int thePDGCode = aParticle->GetDefinition()->GetPDGEncoding();
140 const double boost = (aParticle->GetKineticEnergy()+aParticle->GetMass())/aParticle->GetMass();
141 G4double theXsec = 0;
142 G4String name = aParticle->GetDefinition()->GetParticleName();
143
144 if(!m_parameters->ReggeModel()){
145 // Flat cross section
146 if (MC::isRGlueball(thePDGCode)) {
147 theXsec = 24 * CLHEP::millibarn;
148 } else {
149 std::vector<G4int> nq=MC::containedQuarks(thePDGCode);
150 for (const G4int & quark : nq) {
151 // 12 mb taken from asymptotic pion-nucleon scattering cross sections
152 if (quark == MC::DQUARK || quark == MC::UQUARK) theXsec += 12 * CLHEP::millibarn;
153 // 6 mb taken from asymptotic kaon-nucleon scattering cross sections
154 // No data for D or B, so setting to behave like a kaon
155 if (MC::isStrange(quark) || MC::isCharm(quark) || MC::isBottom(quark)) theXsec += 6 * CLHEP::millibarn;
156 }
157 }
158 } else {
159 // From Eur. Phys. J. C (2010) 66: 493-501
160 // DOI 10.1140/epjc/s10052-010-1262-1
161 double R = Regge(boost);
162 double P = Pom(boost);
163 const bool containsSquark(MC::hasSquark(thePDGCode, MC::BQUARK) || MC::hasSquark(thePDGCode, MC::TQUARK));
164 if (containsSquark) {
165 if (MC::isRBaryon(thePDGCode)) { // ~q q q
166 theXsec = (thePDGCode > 0) ? 2*P*CLHEP::millibarn : (2*(P+R)+30/sqrt(boost))*CLHEP::millibarn;
167 }
168 else if (MC::isRMeson(thePDGCode)) { // ~q qbar
169 theXsec = (thePDGCode > 0) ? (P+R)*CLHEP::millibarn : P*CLHEP::millibarn;
170 }
171 }
172 else {
173 if (MC::isRBaryon(thePDGCode)) { theXsec=3*P*CLHEP::millibarn; } // ~g q q q
174 else if (MC::isRMeson(thePDGCode) || MC::isRGlueball(thePDGCode)) { theXsec=(R+2*P)*CLHEP::millibarn; } // ~g q qbar or ~g g or ~g g g
175 }
176 }
177
178
179
180 //Adding resonance
181
182 if(m_parameters->Resonant())
183 {
184 // Described in Section 5.1 of http://r-hadrons.web.cern.ch/r-hadrons/download/mackeprang_thesis.pdf
185 // mentioned but dismissed in Section 3.3 of https://arxiv.org/pdf/hep-ex/0404001.pdf
186 double e_0 = m_parameters->ResonanceEnergy() + aParticle->GetDefinition()->GetPDGMass(); //Now total energy
187
188 e_0 = sqrt(aParticle->GetDefinition()->GetPDGMass()*aParticle->GetDefinition()->GetPDGMass()
189 + theProton->GetPDGMass()*theProton->GetPDGMass()
190 + 2.*e_0*theProton->GetPDGMass());
191 const double sqrts=sqrt(aParticle->GetDefinition()->GetPDGMass()*aParticle->GetDefinition()->GetPDGMass()
192 + theProton->GetPDGMass()*theProton->GetPDGMass() + 2*aParticle->GetTotalEnergy()*theProton->GetPDGMass());
193 const double gamma = m_parameters->Gamma();
194 const double res_result = m_parameters->Amplitude()*(gamma*gamma/4.)/((sqrts-e_0)*(sqrts-e_0)+(gamma*gamma/4.));//Non-relativistic Breit Wigner
195
196 theXsec += res_result;
197 }
198
199
200 return theXsec * pow(anElement->GetN(),0.7)*1.25 * m_parameters->XsecMultiplier();// * 0.523598775598299;
201
202}
203
204ReactionProduct G4ProcessHelper::GetFinalState(const G4Track& aTrack, G4ParticleDefinition*& aTarget) const {
205 return GetFinalStateInternal(aTrack,aTarget,false);
206}
207
208// Version where we know if we baryonize already
209ReactionProduct G4ProcessHelper::GetFinalStateInternal(const G4Track& aTrack,G4ParticleDefinition*& aTarget, const bool baryonize_failed) const {
210
211 const G4DynamicParticle* aDynamicParticle = aTrack.GetDynamicParticle();
212
213 //-----------------------------------------------
214 // Choose n / p as target
215 // and get ReactionProductList pointer
216 //-----------------------------------------------
217
218 G4Material* aMaterial = aTrack.GetMaterial();
219 const G4ElementVector* theElementVector = aMaterial->GetElementVector() ;
220 const G4double* NbOfAtomsPerVolume = aMaterial->GetVecNbOfAtomsPerVolume();
221
222 G4double NumberOfProtons=0;
223 G4double NumberOfNucleons=0;
224
225 for ( size_t elm=0 ; elm < aMaterial->GetNumberOfElements() ; elm++ )
226 {
227 //Summing number of protons per unit volume
228 NumberOfProtons += NbOfAtomsPerVolume[elm]*(*theElementVector)[elm]->GetZ();
229 //Summing nucleons (not neutrons)
230 NumberOfNucleons += NbOfAtomsPerVolume[elm]*(*theElementVector)[elm]->GetN();
231 }
232
233 const ReactionMap* reactionMap{};
234 if (NumberOfNucleons>0 && CLHEP::RandFlat::shoot()<NumberOfProtons/NumberOfNucleons) {
235 reactionMap = &pReactionMap;
236 aTarget = theProton;
237 } else {
238 reactionMap = &nReactionMap;
239 aTarget = theNeutron;
240 }
241
242 G4int theIncidentPDG = aDynamicParticle->GetDefinition()->GetPDGEncoding();
243 const bool containsSquark(MC::hasSquark(theIncidentPDG, MC::BQUARK) || MC::hasSquark(theIncidentPDG, MC::TQUARK));
244 if (m_parameters->ReggeModel()
245 && MC::isRMeson(theIncidentPDG) && containsSquark
246 && CLHEP::RandFlat::shoot()*m_parameters->Mixing()>0.5
247 && aDynamicParticle->GetDefinition()->GetPDGCharge()==0.
248 )
249 {
250 // G4cout<<"Oscillating..."<<G4endl;
251 theIncidentPDG *= -1;
252 }
253
254
255 bool baryonise=false;
256
257 if (!baryonize_failed
258 && m_parameters->ReggeModel()
259 && CLHEP::RandFlat::shoot()>0.9
260 && MC::isRMeson(theIncidentPDG) &&
261 ( (theIncidentPDG > 0) || !containsSquark )
262 )
263 {
264 baryonise=true;
265 }
266
267 // Reference directly to the ReactionProductList we are looking at. Makes life easier :-)
268 const ReactionProductList& aReactionProductList = reactionMap->at(theIncidentPDG);
269
270 //-----------------------------------------------
271 // Count processes
272 // kinematic check
273 // compute number of 2 -> 2 and 2 -> 3 processes
274 //-----------------------------------------------
275
276 G4int N22 = 0; //Number of 2 -> 2 processes
277 G4int N23 = 0; //Number of 2 -> 3 processes. Couldn't think of more informative names
278
279 //This is the list to be populated
280 ReactionProductList theReactionProductList;
281 std::vector<bool> theChargeChangeList;
282
283 for (const ReactionProduct& prod : aReactionProductList) {
284 const G4int secondaries = prod.size();
285 // If the reaction is not possible we will not consider it
286 /* if(ReactionIsPossible(*prod_it,aDynamicParticle)
287 &&(
288 !baryonise||(baryonise&&ReactionGivesBaryon(*prod_it))
289 ))*/
290 if (ReactionIsPossible(prod,*aTarget,aDynamicParticle) &&
291 (
292 (baryonise && ReactionGivesBaryon(prod)) ||
293 (!baryonise && !ReactionGivesBaryon(prod)) ||
294 MC::isRBaryon(theIncidentPDG) ||
295 !m_parameters->ReggeModel()
296 )
297 )
298 {
299 // The reaction is possible. Let's store and count it
300 theReactionProductList.push_back(prod);
301 if (secondaries == 2) {
302 N22++;
303 } else if (secondaries ==3) {
304 N23++;
305 } else {
306 G4cerr << "ReactionProduct has unsupported number of secondaries: "<<secondaries<<G4endl;
307 }
308 }
309 }
310
311 if (theReactionProductList.size()==0 && baryonize_failed) {
312 G4Exception("G4ProcessHelper", "NoProcessPossible", FatalException,
313 "GetFinalState: No process could be selected from the given list.");
314 } else if (theReactionProductList.size()==0 && !baryonize_failed) {
315 // Baryonization had not yet failed -- try again
316 G4cout << "G4ProcessHelper::GetFinalStateInternal WARNING Could not select an appropriate process in first pass" << G4endl;
317 return GetFinalStateInternal(aTrack,aTarget,true);
318 }
319
320 // For the Regge model no phase space considerations. We pick a process at random
321 if (m_parameters->ReggeModel()) {
322 const int n_rps = theReactionProductList.size();
323 const int select = static_cast<int>(CLHEP::RandFlat::shoot()*n_rps);
324 // G4cout<<"Possible: "<<n_rps<<", chosen: "<<select<<G4endl;
325 return theReactionProductList[select];
326 }
327
328 // Fill a probability map. Remember total probability
329 // 2->2 is 0.15*1/n_22 2->3 uses phase space
330 G4double p22 = 0.15;
331 G4double p23 = 1-p22; // :-)
332
333 std::vector<G4double> Probabilities;
334 std::vector<G4bool> TwotoThreeFlag;
335
336 G4double CumulatedProbability = 0;
337
338 // To each ReactionProduct we assign a cumulated probability and a flag
339 // discerning between 2 -> 2 and 2 -> 3
340 for (unsigned int i = 0; i != theReactionProductList.size(); i++){
341 if (theReactionProductList[i].size() == 2) {
342 if(0==N22) { throw std::runtime_error("G4ProcessHelper::GetFinalState: N22 is zero!"); }
343 CumulatedProbability += p22/N22;
344 TwotoThreeFlag.push_back(false);
345 } else {
346 if(0==N23) { throw std::runtime_error("G4ProcessHelper::GetFinalState: N23 is zero!"); }
347 CumulatedProbability += p23/N23;
348 TwotoThreeFlag.push_back(true);
349 }
350 Probabilities.push_back(CumulatedProbability);
351 }
352
353 //Renormalising probabilities
354 for (std::vector<G4double>::iterator it = Probabilities.begin();
355 it != Probabilities.end();
356 ++it)
357 {
358 *it /= CumulatedProbability;
359 }
360
361 // Choosing ReactionProduct
362
363 G4bool selected = false;
364 G4int tries = 0;
365 // ReactionProductList::iterator prod_it;
366
367 //Keep looping over the list until we have a choice, or until we have tried 100 times
368 unsigned int i=0;
369 while(!selected && tries < 100){
370 i=0;
371 G4double dice = CLHEP::RandFlat::shoot();
372 while( i<theReactionProductList.size() && dice>Probabilities[i] ){
373 i++;
374 }
375
376 if(!TwotoThreeFlag[i]) {
377 // 2 -> 2 processes are chosen immediately
378 selected = true;
379 } else {
380 // 2 -> 3 processes require a phase space lookup
381 if (PhaseSpace(theReactionProductList[i],*aTarget,aDynamicParticle)>CLHEP::RandFlat::shoot()) selected = true;
382 }
383 // double suppressionfactor=0.5;
384 auto table ATLAS_THREAD_SAFE = particleTable; // safe because table has been loaded by now
385 if(selected && table->FindParticle(theReactionProductList[i][0])->GetPDGCharge()!=aDynamicParticle->GetDefinition()->GetPDGCharge())
386 {
387 /*
388 G4cout<<"Incoming particle "<<aDynamicParticle->GetDefinition()->GetParticleName()
389 <<" has charge "<<aDynamicParticle->GetDefinition()->GetPDGCharge()<<G4endl;
390 G4cout<<"Suggested particle "<<particleTable->FindParticle(theReactionProductList[i][0])->GetParticleName()
391 <<" has charge "<<particleTable->FindParticle(theReactionProductList[i][0])->GetPDGCharge()<<G4endl;
392 */
393 if(CLHEP::RandFlat::shoot()<m_parameters->SuppressionFactor()) selected = false;
394 }
395 tries++;
396 // G4cout<<"Tries: "<<tries<<G4endl;
397 }
398 if(tries>=100) G4cerr<<"Could not select process!!!!"<<G4endl;
399
400 //Return the chosen ReactionProduct
401 return theReactionProductList[i];
402}
403
404G4double G4ProcessHelper::ReactionProductMass(const ReactionProduct& aReaction,const G4ParticleDefinition& aTarget,const G4DynamicParticle* aDynamicParticle) const{
405 // Incident energy:
406 const G4double E_incident = aDynamicParticle->GetTotalEnergy();
407 //G4cout<<"Total energy: "<<E_incident<<" Kinetic: "<<aDynamicParticle->GetKineticEnergy()<<G4endl;
408 // sqrt(s)= sqrt(m_1^2 + m_2^2 + 2 E_1 m_2)
409 const G4double m_1 = aDynamicParticle->GetDefinition()->GetPDGMass();
410 const G4double m_2 = aTarget.GetPDGMass();
411 //G4cout<<"M_R: "<<m_1/CLHEP::GeV<<" GeV, M_np: "<<m_2/CLHEP::GeV<<" GeV"<<G4endl;
412 const G4double sqrts = sqrt(m_1*m_1 + m_2*(m_2 + 2 * E_incident));
413 //G4cout<<"sqrt(s) = "<<sqrts/CLHEP::GeV<<" GeV"<<G4endl;
414 // Sum of rest masses after reaction:
415 G4double M_after = 0;
416 for (const auto& product_pdg_id : aReaction) {
417 //G4cout<<"Mass contrib: "<<(particleTable->FindParticle(production_pdg_id)->GetPDGMass())/CLHEP::MeV<<" MeV"<<G4endl;
418 auto table ATLAS_THREAD_SAFE = particleTable; // safe because table has been loaded by now
419 M_after += table->FindParticle(product_pdg_id)->GetPDGMass();
420 }
421 //G4cout<<"Intending to return this ReactionProductMass: " << sqrts << " - " << M_after << " MeV"<<G4endl;
422 return sqrts - M_after;
423}
424
425G4bool G4ProcessHelper::ReactionIsPossible(const ReactionProduct& aReaction,const G4ParticleDefinition& aTarget,const G4DynamicParticle* aDynamicParticle) const{
426 if (ReactionProductMass(aReaction,aTarget,aDynamicParticle)>0) return true;
427 return false;
428}
429
430G4bool G4ProcessHelper::ReactionGivesBaryon(const ReactionProduct& aReaction) const{
431 for (const auto& product_pdg_id : aReaction) {
432 if (MC::isRBaryon(product_pdg_id)) return true;
433 }
434 return false;
435}
436
437G4double G4ProcessHelper::PhaseSpace(const ReactionProduct& aReaction,const G4ParticleDefinition& aTarget,const G4DynamicParticle* aDynamicParticle) const {
438 const G4double qValue = ReactionProductMass(aReaction,aTarget,aDynamicParticle);
439 // Eq 4 of https://arxiv.org/pdf/hep-ex/0404001.pdf
440 const G4double phi = sqrt(1+qValue/(2*0.139*CLHEP::GeV))*pow(qValue/(1.1*CLHEP::GeV),3./2.);
441 return (phi/(1+phi));
442}
443
444void G4ProcessHelper::ReadAndParse(const G4String& str,
445 std::vector<G4String>& tokens,
446 const G4String& delimiters) const
447{
448 // Skip delimiters at beginning.
449 G4String::size_type lastPos = str.find_first_not_of(delimiters, 0);
450 if (lastPos==G4String::npos) return;
451
452 // Find first "non-delimiter".
453 G4String::size_type pos = str.find_first_of(delimiters, lastPos);
454
455 while (G4String::npos != pos || G4String::npos != lastPos)
456 {
457 //Skipping leading / trailing whitespaces
458 G4String temp = str.substr(lastPos, pos - lastPos);
459 while(temp.c_str()[0] == ' ') temp.erase(0,1);
460 while(temp[temp.size()-1] == ' ') temp.erase(temp.size()-1,1);
461
462 // Found a token, add it to the vector.
463 tokens.push_back(std::move(temp));
464
465 // Skip delimiters. Note the "not_of"
466 lastPos = str.find_first_not_of(delimiters, pos);
467 if (lastPos==G4String::npos) continue;
468
469 // Find next "non-delimiter"
470 pos = str.find_first_of(delimiters, lastPos);
471 }
472}
473
474double G4ProcessHelper::Regge(const double boost) const
475{
476 // https://link.springer.com/content/pdf/10.1140%2Fepjc%2Fs10052-010-1262-1.pdf Eq 1
477 // Originally from https://arxiv.org/pdf/0710.3930.pdf
478 const double a=2.165635078566177;
479 const double b=0.1467453738547229;
480 const double c=-0.9607903711871166;
481 return 1.5*exp(a+b/boost+c*log(boost));
482}
483
484
485double G4ProcessHelper::Pom(const double boost) const
486{
487 // https://link.springer.com/content/pdf/10.1140%2Fepjc%2Fs10052-010-1262-1.pdf Eq 2
488 // Originally from https://arxiv.org/pdf/0710.3930.pdf
489 const double a=4.138224000651535;
490 const double b=1.50377557581421;
491 const double c=-0.05449742257808247;
492 const double d=0.0008221235048211401;
493 return a + b*sqrt(boost) + c*boost + d*pow(boost,1.5);
494}
Scalar phi() const
phi method
ATLAS-specific HepMC functions.
static Double_t a
static Double_t P(Double_t *tt, Double_t *par)
std::map< std::string, double > instance
constexpr int pow(int base, int exp) noexcept
Define macros for attributes used to control the static checker.
#define ATLAS_THREAD_SAFE
static const PhysicsConfigurationHelper * Instance()
double R(const INavigable4Momentum *p1, const double v_eta, const double v_phi)
void select(const xAOD::IParticle *particle, const float coneSize, const xAOD::CaloClusterContainer *clusters, std::vector< bool > &mask)
static const int UQUARK
static const int DQUARK
bool isCharm(const T &p)
bool isBottom(const T &p)
static const int TQUARK
bool isRMeson(const T &p)
bool isStrange(const T &p)
bool isRBaryon(const T &p)
bool isRGlueball(const T &p)
PDG rule 11g: Within several scenarios of new physics, it is possible to have colored particles suffici...
bool hasSquark(const T &p, const int &q)
static const int BQUARK
std::vector< int > containedQuarks(const T &p)
constexpr ParticleHypothesis particle[PARTICLEHYPOTHESES]
the array of masses