Skip to content

Commit 0a43242

Browse files
sawenzelclaude
andcommitted
Monopoles: changes from further review
- Register mplIoni through G4PhysicsListHelper. - Move the TPC Bethe-Bloch computation back into the branch that uses it. - Run the ITS monopole PDG lookup only when G4.monopole is set. - Define the monopole particles only when G4.monopole is set. - Make non-positive monopole magnetic charge or mass fatal. - Make a mismatch between G4.monopoleMass and TDatabasePDG fatal. - Warn when the TPC drift field is zero. - Align the species comment with the code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 26f3cee commit 0a43242

8 files changed

Lines changed: 72 additions & 29 deletions

File tree

‎DataFormats/simulation/include/SimulationDataFormat/MonopoleParticles.h‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@
2424
namespace o2::sim
2525
{
2626

27-
/// monopole carrying equal electric and magnetic charge
27+
/// Both species are electrically neutral for now, and the transport gives them the
28+
/// same magnetic charge, with its sign taken from the PDG sign.
29+
/// monopole intended to carry equal electric and magnetic charge
2830
constexpr int MonopolePdgSymm = 4110000;
29-
/// monopole carrying opposite electric and magnetic charge
31+
/// monopole intended to carry opposite electric and magnetic charge (not yet distinguished)
3032
constexpr int MonopolePdgAsymm = 4120000;
3133

3234
/// Default monopole mass in GeV.

‎DataFormats/simulation/include/SimulationDataFormat/O2DatabasePDG.h‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#ifndef O2_O2DATABASEPDG_H
1717
#define O2_O2DATABASEPDG_H
1818

19+
#include <cmath>
1920
#include <string>
2021
#include "TDatabasePDG.h"
2122
#include "TParticlePDG.h"
@@ -54,6 +55,19 @@ class O2DatabasePDG
5455
double monopoleMass = o2::sim::MonopoleMassDefaultGeV);
5556
static void addParticlesFromExternalFile(TDatabasePDG* db);
5657

58+
// true if all monopole species are registered with the given mass in GeV; TDatabasePDG
59+
// keeps the first registration, so an earlier call with another mass makes this false
60+
static bool hasMonopoleMass(TDatabasePDG* db, double monopoleMass)
61+
{
62+
for (int pdg : {o2::sim::MonopolePdgSymm, -o2::sim::MonopolePdgSymm, o2::sim::MonopolePdgAsymm, -o2::sim::MonopolePdgAsymm}) {
63+
const auto* particle = db->GetParticle(pdg);
64+
if (particle == nullptr || std::abs(particle->Mass() - monopoleMass) > 1.e-6 * monopoleMass) {
65+
return false;
66+
}
67+
}
68+
return true;
69+
}
70+
5771
// get particle's (if any) mass
5872
static Double_t MassImpl(TParticlePDG* particle, bool& success)
5973
{

‎Detectors/ITSMFT/ITS/simulation/src/Detector.cxx‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "DetectorsBase/Stack.h"
2626
#include "SimulationDataFormat/TrackReference.h"
2727
#include "SimulationDataFormat/MonopoleParticles.h"
28+
#include "SimConfig/G4Params.h"
2829
#include "fairlogger/Logger.h" // for LOG, LOG_IF
2930

3031
// FairRoot includes
@@ -321,11 +322,12 @@ Bool_t Detector::ProcessHits(FairVolume* vol)
321322
// This method is called from the MC stepping
322323
// Electrically neutral magnetic monopoles deposit energy in the
323324
// silicon through G4mplIonisation (Ahlen stopping power), so they must not be
324-
// rejected by the electric-charge gate. PDG lookup
325-
// never runs for ordinary charged production.
325+
// rejected by the electric-charge gate. The PDG lookup only runs for neutral
326+
// particles in runs with monopole physics enabled.
326327
// To-do: handle dyons.
328+
static const bool sMonopoleIonisation = o2::conf::G4Params::Instance().monopole;
327329
const bool isNeutral = (fMC->TrackCharge() == 0);
328-
const bool isMonopole = isNeutral && o2::sim::isMonopole(fMC->TrackPid());
330+
const bool isMonopole = isNeutral && sMonopoleIonisation && o2::sim::isMonopole(fMC->TrackPid());
329331
if (isNeutral && !isMonopole) {
330332
return kFALSE;
331333
}
@@ -394,9 +396,7 @@ Bool_t Detector::ProcessHits(FairVolume* vol)
394396
mTrackData.mHitStarted = true;
395397
}
396398
if (stopHit) {
397-
// A monopole reaches this point even when no ionisation process is attached to
398-
// it (G4.monopole=0), in which case it crosses the sensor depositing nothing.
399-
// Storing such empty hits would only inflate the hit file, so they are skipped
399+
// A monopole that deposited nothing in the sensor leaves no hit
400400
if (isMonopole && mTrackData.mEnergyLoss <= 0.) {
401401
return kFALSE;
402402
}

‎Detectors/TPC/simulation/src/Detector.cxx‎

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -216,18 +216,6 @@ Bool_t Detector::ProcessHits(FairVolume* vol)
216216
Int_t numberOfElectrons = 0;
217217
// I.H. - the type expected in addHit is float
218218

219-
// ---| Stepsize in cm |---
220-
const double stepSize = fMC->TrackStep();
221-
222-
double betaGamma = momentum.P() / fMC->TrackMass();
223-
betaGamma = TMath::Max(betaGamma, 7.e-3); // protection against too small bg
224-
225-
// ---| number of primary ionisations per cm |---
226-
const double primaryElectronsPerCM =
227-
gasParam.Nprim * BetheBlochAleph(static_cast<float>(betaGamma), gasParam.BetheBlochParam[0],
228-
gasParam.BetheBlochParam[1], gasParam.BetheBlochParam[2],
229-
gasParam.BetheBlochParam[3], gasParam.BetheBlochParam[4]);
230-
231219
// use Geant4 energy deposit directly for ionisation (Kr-83m calibration simulations)
232220
if (detParam.UseGeant4Edep) {
233221
// We have multiple collisions and add fluctuations: smear nel using
@@ -248,6 +236,18 @@ Bool_t Detector::ProcessHits(FairVolume* vol)
248236
// 2^24 (16777216) ==> largest integer a IEEE-754 float can represent exactly
249237
numberOfElectrons = TMath::Min(numberOfElectrons, 16777216);
250238
} else {
239+
// ---| Stepsize in cm |---
240+
const double stepSize = fMC->TrackStep();
241+
242+
double betaGamma = momentum.P() / fMC->TrackMass();
243+
betaGamma = TMath::Max(betaGamma, 7.e-3); // protection against too small bg
244+
245+
// ---| number of primary ionisations per cm |---
246+
const double primaryElectronsPerCM =
247+
gasParam.Nprim * BetheBlochAleph(static_cast<float>(betaGamma), gasParam.BetheBlochParam[0],
248+
gasParam.BetheBlochParam[1], gasParam.BetheBlochParam[2],
249+
gasParam.BetheBlochParam[3], gasParam.BetheBlochParam[4]);
250+
251251
// ---| mean number of collisions and random for this event |---
252252
const double meanNcoll = stepSize * trackCharge * trackCharge * primaryElectronsPerCM;
253253
const int nColl = static_cast<int>(fMC->GetRandom()->Poisson(meanNcoll));

‎Detectors/gconfig/src/O2MonopolePhysics.cxx‎

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
#include <G4VUserPhysicsList.hh>
4646
#include <G4ParticleTable.hh>
4747
#include <G4ParticleDefinition.hh>
48+
#include <G4PhysicsListHelper.hh>
4849
#include <G4ProcessManager.hh>
4950
#include <G4ProcessVector.hh>
5051
#include <G4VEnergyLossProcess.hh>
@@ -92,7 +93,8 @@ inline bool isMonopolePDG(int pdg) { return o2::sim::isMonopole(pdg); }
9293
/// so that G4Transportation queries the field for it
9394
constexpr G4double gMonopoleFieldGateMoment = 1.0e-20;
9495

95-
// Extent of the TPC drift region
96+
// Extent of the TPC field cage, i.e. the region with the drift field (smaller than
97+
// the TPC_Drift volume, which extends beyond the endcaps and the field-cage rods)
9698
constexpr G4double gTPCFieldCageRMin = 83.5 * CLHEP::cm;
9799
constexpr G4double gTPCFieldCageRMax = 254.5 * CLHEP::cm;
98100
constexpr G4double gTPCFieldCageZMax = 249.525 * CLHEP::cm;
@@ -107,6 +109,11 @@ inline double tpcDriftFieldMagnitude()
107109
}
108110
try {
109111
const double valueKVPerCm = o2::conf::ConfigurableParam::getValueAs<float>("TPCGEMParam.ElectricField[0]");
112+
if (valueKVPerCm <= 0.) {
113+
LOG(warn) << "O2MonopolePhysics: TPCGEMParam.ElectricField[0] = " << valueKVPerCm
114+
<< " kV/cm; the monopole is not coupled to a drift field";
115+
return 0.;
116+
}
110117
return valueKVPerCm * CLHEP::kilovolt / CLHEP::cm;
111118
} catch (...) {
112119
LOG(warn) << "O2MonopolePhysics: the TPC is in the geometry but TPCGEMParam is not "
@@ -159,6 +166,9 @@ class O2MonopoleEquation : public G4EquationOfMotion
159166
// Non-zero only while an O2 monopole is being transported. The sign follows
160167
// the PDG sign, so monopole and anti-monopole are pushed in opposite
161168
// directions along B.
169+
// TODO: both species are electrically neutral today, so their magnetic sign is
170+
// the same function of the PDG sign; once they carry electric charge the
171+
// asymmetric species (4120000) needs the opposite relative sign.
162172
double signedMagneticCharge = 0.;
163173
int pdg = 0;
164174
if (const G4Track* track = currentTrack()) {
@@ -462,9 +472,8 @@ class O2MonopolePhysics : public G4VUserPhysicsList
462472
pmanager->RemoveProcess(proc);
463473
}
464474
}
465-
// Ordering (AtRest, AlongStep, PostStep) = (-1, 1, 1) as in the Geant4
466-
// monopole example: continuous-and-discrete energy loss, not active at rest.
467-
pmanager->AddProcess(new G4mplIonisation(mMagneticCharge), -1, 1, 1);
475+
// Ordered by G4PhysicsListHelper from the process subtype, as in the Geant4 monopole example
476+
G4PhysicsListHelper::GetPhysicsListHelper()->RegisterProcess(new G4mplIonisation(mMagneticCharge), particle);
468477

469478
// G4Transportation only looks up the field when the particle has a
470479
// non-zero electric charge or a non-zero magnetic moment (μ) and a monopole has
@@ -542,6 +551,11 @@ TG4RunConfiguration* createMonopoleRunConfiguration(const TString& userGeometry,
542551
// One Dirac charge g_D = eplus / (2*alpha) ~= 68.5 eplus (Dirac quantisation).
543552
// magneticChargeDirac counts Dirac charges (1.0 == classic single monopole),
544553
// matching the "g_1" convention used by the O2 monopole generator input.
554+
// G4mplIonisation silently replaces a zero charge with one Dirac charge, while the
555+
// equation of motion would use the zero, so non-positive values are rejected
556+
if (!(magneticChargeDirac > 0.)) {
557+
LOG(fatal) << "O2MonopolePhysics: G4.monopoleMagneticCharge must be positive, got " << magneticChargeDirac;
558+
}
545559
const double gDirac = CLHEP::eplus / (2.0 * CLHEP::fine_structure_const);
546560
const double magneticChargeEplus = magneticChargeDirac * gDirac;
547561
LOG(info) << "Monopole ionisation enabled: magnetic charge = " << magneticChargeDirac

‎Generators/src/GeneratorFactory.cxx‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,11 @@ void GeneratorFactory::setPrimaryGenerator(o2::conf::SimConfig const& conf, Fair
8888
/** generators **/
8989

9090
// Monopole configurable mass added to the PDG database
91-
o2::O2DatabasePDG::addALICEParticles(TDatabasePDG::Instance(),
92-
o2::conf::G4Params::Instance().monopoleMass);
91+
const double monopoleMass = o2::conf::G4Params::Instance().monopoleMass;
92+
o2::O2DatabasePDG::addALICEParticles(TDatabasePDG::Instance(), monopoleMass);
93+
if (!o2::O2DatabasePDG::hasMonopoleMass(TDatabasePDG::Instance(), monopoleMass)) {
94+
LOG(fatal) << "Monopoles were registered in TDatabasePDG with a mass other than G4.monopoleMass = " << monopoleMass << " GeV";
95+
}
9396
auto genconfig = conf.getGenerator();
9497
#if defined(GENERATORS_WITH_PYTHIA8) && defined(GENERATORS_WITH_HEPMC3)
9598
if (GeneratorHybridParam::Instance().switchExtToHybrid && (genconfig.compare("external") == 0 || genconfig.compare("extgen") == 0)) {

‎Steer/src/O2MCApplication.cxx‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1752,11 +1752,18 @@ void addSpecialParticles()
17521752
TVirtualMC::GetMC()->DefineParticle(900000020, "Sexaquark", kPTUndefined, 2.0, 0.0, 4.35e+17, "Hadron", 0.0, 0, 1, 0, 0, 0, 0, 0, 2, kTRUE);
17531753
TVirtualMC::GetMC()->DefineParticle(-900000020, "AntiSexaquark", kPTUndefined, 2.0, 0.0, 4.35e+17, "Hadron", 0.0, 0, 1, 0, 0, 0, 0, 0, -2, kTRUE);
17541754

1755-
// BSM Monopoles
1755+
// BSM Monopoles, defined only when their physics is enabled (G4.monopole=1), so that a
1756+
// monopole primary without it is reported by the engine instead of crossing the detector unseen.
17561757
// The transported mass has to match the one the generator used; see
17571758
// G4Params.monopoleMass and o2::sim::MonopoleMassDefaultGeV.
17581759
// To-do: find a way to define multiple masses for monopoles species
1760+
if (!o2::conf::G4Params::Instance().monopole) {
1761+
return;
1762+
}
17591763
const double monopoleMass = o2::conf::G4Params::Instance().monopoleMass;
1764+
if (!(monopoleMass > 0.)) {
1765+
LOG(fatal) << "G4.monopoleMass must be positive, got " << monopoleMass;
1766+
}
17601767
// Symmetric monopoles: same electric and magnetic charge
17611768
TVirtualMC::GetMC()->DefineParticle(4110000, "Monopole_symm", kPTHadron, monopoleMass, 0.0, 1e10, "BSM", 0.0, 0, 0, 0, 0, 0, 0, 0, 0, kTRUE);
17621769
TVirtualMC::GetMC()->DefineParticle(-4110000, "AntiMonopole_symm", kPTHadron, monopoleMass, 0.0, 1e10, "BSM", 0.0, 0, 0, 0, 0, 0, 0, 0, 0, kTRUE);

‎macro/o2sim.C‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,11 @@ FairRunSim* o2sim_init(bool asservice, bool evalmat = false)
182182
// The monopole mass has to be the one the transport uses; in the worker
183183
// (asservice) the GeneratorFactory call is skipped, so this is the only
184184
// place that registers it.
185-
o2::O2DatabasePDG::addALICEParticles(TDatabasePDG::Instance(),
186-
o2::conf::G4Params::Instance().monopoleMass);
185+
const double monopoleMass = o2::conf::G4Params::Instance().monopoleMass;
186+
o2::O2DatabasePDG::addALICEParticles(TDatabasePDG::Instance(), monopoleMass);
187+
if (!o2::O2DatabasePDG::hasMonopoleMass(TDatabasePDG::Instance(), monopoleMass)) {
188+
LOG(fatal) << "Monopoles were registered in TDatabasePDG with a mass other than G4.monopoleMass = " << monopoleMass << " GeV";
189+
}
187190

188191
long runStart = timestamp;
189192
{

0 commit comments

Comments
 (0)