35#include <boost/property_tree/ptree.hpp>
37#include <TGeoManager.h>
39#include <fairlogger/Logger.h>
41#include "TG4ComposedPhysicsList.h"
44#include <G4VUserPhysicsList.hh>
45#include <G4ParticleTable.hh>
46#include <G4ParticleDefinition.hh>
47#include <G4PhysicsListHelper.hh>
48#include <G4ProcessManager.hh>
49#include <G4ProcessVector.hh>
50#include <G4mplIonisation.hh>
52#include <G4ChargeState.hh>
53#include <G4ChordFinder.hh>
54#include <G4ClassicalRK4.hh>
55#include <G4EquationOfMotion.hh>
56#include <G4FieldManager.hh>
57#include <G4MagneticField.hh>
58#include <G4PropagatorInField.hh>
60#include <G4CoupledTransportation.hh>
61#include <G4Transportation.hh>
62#include <G4TransportationManager.hh>
64#include <CLHEP/Units/SystemOfUnits.h>
65#include <CLHEP/Units/PhysicalConstants.h>
87constexpr G4double gMonopoleFieldGateMoment = 1.0e-20;
91constexpr G4double gTPCFieldCageRMin = 83.5 * CLHEP::cm;
92constexpr G4double gTPCFieldCageRMax = 254.5 * CLHEP::cm;
93constexpr G4double gTPCFieldCageZMax = 249.525 * CLHEP::cm;
96inline double tpcDriftFieldMagnitude()
98 if (gGeoManager ==
nullptr || gGeoManager->GetVolume(
"TPC_Drift") ==
nullptr) {
99 LOG(info) <<
"O2MonopolePhysics: no TPC in the geometry of this run, "
100 "the monopole is not coupled to a drift field";
104 const double valueKVPerCm = o2::conf::ConfigurableParam::getValueAs<float>(
"TPCGEMParam.ElectricField[0]");
105 if (valueKVPerCm <= 0.) {
106 LOG(warn) <<
"O2MonopolePhysics: TPCGEMParam.ElectricField[0] = " << valueKVPerCm
107 <<
" kV/cm; the monopole is not coupled to a drift field";
110 return valueKVPerCm * CLHEP::kilovolt / CLHEP::cm;
112 LOG(warn) <<
"O2MonopolePhysics: the TPC is in the geometry but TPCGEMParam is not "
113 "registered; no drift-field coupling for the monopole";
120inline bool tpcDriftField(
const G4double
position[3], G4double driftField, G4double E[3])
123 if (std::fabs(
z) >= gTPCFieldCageZMax) {
127 if (r2 < gTPCFieldCageRMin * gTPCFieldCageRMin || r2 > gTPCFieldCageRMax * gTPCFieldCageRMax) {
132 E[2] = (
z >= 0.) ? -driftField : driftField;
148 mMagneticChargeEplus(magneticChargeEplusUnits),
149 mTPCDriftField(tpcDriftFieldGeant4)
158 G4double , G4double particleMass)
override
160 mElCharge = CLHEP::eplus * particleChargeState.GetCharge() * CLHEP::c_light;
161 mMassCof = particleMass * particleMass;
162 mMagCharge = CLHEP::eplus * mMagneticChargeSign * mMagneticChargeEplus * CLHEP::c_light;
167 const G4double pSquared =
y[3] *
y[3] +
y[4] *
y[4] +
y[5] *
y[5];
168 if (pSquared <= 0.) {
169 for (
int i = 0;
i < 8; ++
i) {
174 const G4double energy = std::sqrt(pSquared + mMassCof);
175 const G4double pModuleInverse = 1.0 / std::sqrt(pSquared);
176 const G4double cofEl = mElCharge * pModuleInverse;
177 const G4double cofMag = mMagCharge * energy * pModuleInverse;
179 dydx[0] =
y[3] * pModuleInverse;
180 dydx[1] =
y[4] * pModuleInverse;
181 dydx[2] =
y[5] * pModuleInverse;
184 dydx[3] = cofMag *
B[0] + cofEl * (
y[4] *
B[2] -
y[5] *
B[1]);
185 dydx[4] = cofMag *
B[1] + cofEl * (
y[5] *
B[0] -
y[3] *
B[2]);
186 dydx[5] = cofMag *
B[2] + cofEl * (
y[3] *
B[1] -
y[4] *
B[0]);
190 if (mTPCDriftField > 0.) {
191 G4double E[3] = {0., 0., 0.};
192 if (tpcDriftField(
y, mTPCDriftField, E)) {
196 const G4double cofMagE = mMagCharge * pModuleInverse / CLHEP::c_light;
197 const G4double dEx = cofMagE * (
y[4] * E[2] -
y[5] * E[1]);
198 const G4double dEy = cofMagE * (
y[5] * E[0] -
y[3] * E[2]);
199 const G4double dEz = cofMagE * (
y[3] * E[1] -
y[4] * E[0]);
207 dydx[7] = energy * pModuleInverse / CLHEP::c_light;
211 double mMagneticChargeEplus;
212 int mMagneticChargeSign = 1;
213 G4double mTPCDriftField = 0.;
214 G4double mElCharge = 0.;
215 G4double mMagCharge = 0.;
216 G4double mMassCof = 0.;
247 auto* transportationManager = G4TransportationManager::GetTransportationManager();
248 auto* fieldManager = transportationManager !=
nullptr ? transportationManager->GetFieldManager() :
nullptr;
249 if (fieldManager ==
nullptr) {
250 LOG(fatal) <<
"O2MonopolePhysics: no G4FieldManager, the monopole equation of motion "
251 "cannot be installed; rerun with G4.monopole=0 if that is what you want";
254 auto* magneticField =
255 const_cast<G4MagneticField*
>(
dynamic_cast<const G4MagneticField*
>(fieldManager->GetDetectorField()));
256 if (magneticField ==
nullptr) {
257 LOG(fatal) <<
"O2MonopolePhysics: no magnetic field attached to the field manager, the "
258 "monopole equation of motion cannot be installed; rerun with G4.monopole=0 "
259 "if that is what you want";
263 auto* equation =
new O2MonopoleEquation(magneticField, magneticChargeEplusUnits, tpcDriftFieldGeant4);
267 auto* stepper =
new G4ClassicalRK4(equation, 8);
270 auto* previous = fieldManager->GetChordFinder();
271 const G4double stepMinimum = 1.0e-2 * CLHEP::mm;
272 auto* chordFinder =
new G4ChordFinder(magneticField, stepMinimum, stepper);
273 if (previous !=
nullptr) {
274 chordFinder->SetDeltaChord(previous->GetDeltaChord());
277 if (tpcDriftFieldGeant4 > 0.) {
278 LOG(info) <<
"O2MonopolePhysics: monopole equation of motion built (F = g*(B - v x E/c^2)), "
280 << magneticChargeEplusUnits <<
" eplus, TPC drift field = "
281 << tpcDriftFieldGeant4 / (CLHEP::volt / CLHEP::cm) <<
" V/cm";
283 LOG(info) <<
"O2MonopolePhysics: monopole equation of motion built (F = g*B), magnetic charge = "
284 << magneticChargeEplusUnits <<
" eplus (TPC drift field coupling disabled)";
286 return {chordFinder, equation};
313 :
G4Transportation(0), mChordFinder(fieldSetup.chordFinder), mEquation(fieldSetup.equation)
319 G4Transportation::StartTracking(track);
323 mChordFinder->ResetStepEstimate();
327 G4double currentMinimumStep, G4double& currentSafety,
328 G4GPILSelection* selection)
override
331 auto* fieldManager = GetPropagatorInField()->FindAndSetFieldManager(track.GetVolume());
332 if (fieldManager->GetDetectorField() ==
nullptr) {
334 return G4Transportation::AlongStepGetPhysicalInteractionLength(track, previousStepSize, currentMinimumStep,
335 currentSafety, selection);
344 const G4bool previousMoment = G4Transportation::EnableMagneticMoment(
true);
345 auto* previousChordFinder = fieldManager->GetChordFinder();
346 const G4bool previousChangesEnergy = fieldManager->DoesFieldChangeEnergy();
347 const G4double previousDeltaChord = mChordFinder->GetDeltaChord();
350 if (previousChordFinder !=
nullptr) {
351 mChordFinder->SetDeltaChord(previousChordFinder->GetDeltaChord());
353 fieldManager->SetChordFinder(mChordFinder);
355 fieldManager->SetFieldChangesEnergy(
true);
357 const G4double
length = G4Transportation::AlongStepGetPhysicalInteractionLength(
358 track, previousStepSize, currentMinimumStep, currentSafety, selection);
360 fieldManager->SetFieldChangesEnergy(previousChangesEnergy);
361 fieldManager->SetChordFinder(previousChordFinder);
362 mChordFinder->SetDeltaChord(previousDeltaChord);
363 G4Transportation::EnableMagneticMoment(previousMoment);
368 G4ChordFinder* mChordFinder;
379 G4VProcess* existing =
nullptr;
380 G4ProcessVector* plist = pmanager->GetProcessList();
381 for (G4int ip = 0; ip < static_cast<G4int>(plist->size()); ++ip) {
383 existing = (*plist)[ip];
387 if (existing ==
nullptr) {
388 LOG(fatal) <<
"O2MonopolePhysics: " << particle->GetParticleName()
389 <<
" has no transportation process to replace; the monopole could not be "
390 "coupled to the field";
393 if (
dynamic_cast<G4CoupledTransportation*
>(existing) !=
nullptr) {
397 LOG(fatal) <<
"O2MonopolePhysics: " << particle->GetParticleName()
398 <<
" uses G4CoupledTransportation (parallel worlds); the monopole transportation "
399 "does not support that. Rerun with G4.monopole=0 or without parallel worlds";
406 pmanager->RemoveProcess(existing);
409 pmanager->AddProcess(transportation);
417 pmanager->SetProcessOrderingToFirst(transportation, idxAlongStep);
418 pmanager->SetProcessOrderingToFirst(transportation, idxPostStep);
435 explicit O2MonopolePhysics(
double magneticChargeEplus) : mMagneticCharge(magneticChargeEplus) {}
445 auto* table = G4ParticleTable::GetParticleTable();
454 for (
int pdg : gMonopolePDGs) {
455 auto* particle = table->FindParticle(pdg);
456 if (particle ==
nullptr) {
459 LOG(info) <<
"O2MonopolePhysics: monopole PDG " << pdg
460 <<
" not present in the particle table, skipping";
463 auto* pmanager = particle->GetProcessManager();
464 if (pmanager ==
nullptr) {
465 LOG(warning) <<
"O2MonopolePhysics: no process manager for PDG " << pdg <<
", skipping";
471 G4PhysicsListHelper::GetPhysicsListHelper()->RegisterProcess(
new G4mplIonisation(mMagneticCharge), particle);
477 if (particle->GetPDGMagneticMoment() == 0.) {
478 particle->SetPDGMagneticMoment(gMonopoleFieldGateMoment);
490 LOG(info) <<
"O2MonopolePhysics: attached G4mplIonisation and monopole transport to "
491 << particle->GetParticleName() <<
" (PDG " << pdg
492 <<
"), magnetic charge = " << mMagneticCharge / CLHEP::eplus <<
" eplus";
494 if (nAttached == 0) {
495 LOG(warning) <<
"O2MonopolePhysics: no monopole particle found; no ionisation attached";
500 double mMagneticCharge;
512 const TString& specialProcess, Bool_t specialStacking,
513 Bool_t mtApplication,
double magneticChargeEplus)
514 :
o2::g4config::
G4RunConfiguration(userGeometry, physicsList, specialProcess, specialStacking, mtApplication),
515 mMagneticCharge(magneticChargeEplus)
521 G4VUserPhysicsList* physicsList = o2::g4config::G4RunConfiguration::CreatePhysicsList();
522 if (
auto* composed =
dynamic_cast<TG4ComposedPhysicsList*
>(physicsList)) {
524 LOG(info) <<
"O2G4RunConfiguration: monopole ionisation physics registered "
525 "on the composed physics list";
527 LOG(fatal) <<
"O2G4RunConfiguration: physics list is not a TG4ComposedPhysicsList, "
528 "monopole ionisation cannot be enabled; rerun with G4.monopole=0 if that "
535 double mMagneticCharge;
540 const TString& physicsList,
541 const TString& specialProcess,
542 bool specialStacking,
544 double magneticChargeDirac)
551 if (!(magneticChargeDirac > 0.)) {
552 LOG(fatal) <<
"O2MonopolePhysics: G4.monopoleMagneticCharge must be positive, got " << magneticChargeDirac;
554 const double gDirac = CLHEP::eplus / (2.0 * CLHEP::fine_structure_const);
555 const double magneticChargeEplus = magneticChargeDirac * gDirac;
556 LOG(info) <<
"Monopole ionisation enabled: magnetic charge = " << magneticChargeDirac
557 <<
" Dirac charge(s) = " << magneticChargeEplus / CLHEP::eplus <<
" eplus";
559 specialStacking, mtApplication, magneticChargeEplus);
Identification of the BSM magnetic monopoles across the simulation.
The Geant4 VMC run configuration of O2: adds the fast simulation and the local magnetic fields.
G4VUserPhysicsList * CreatePhysicsList() override
O2G4RunConfiguration(const TString &userGeometry, const TString &physicsList, const TString &specialProcess, Bool_t specialStacking, Bool_t mtApplication, double magneticChargeEplus)
Equation of motion implementing the dual Lorentz force of a magnetic charge.
void EvaluateRhsGivenB(const G4double y[], const G4double B[3], G4double dydx[]) const override
void SetChargeMomentumMass(G4ChargeState particleChargeState, G4double, G4double particleMass) override
void setMagneticChargeSign(int sign)
O2MonopoleEquation(G4Field *field, double magneticChargeEplusUnits, double tpcDriftFieldGeant4)
void ConstructParticle() override
void ConstructProcess() override
O2MonopolePhysics(double magneticChargeEplus)
void StartTracking(G4Track *track) override
O2MonopoleTransportation(const MonopoleFieldSetup &fieldSetup)
G4double AlongStepGetPhysicalInteractionLength(const G4Track &track, G4double previousStepSize, G4double currentMinimumStep, G4double ¤tSafety, G4GPILSelection *selection) override
GLuint GLsizei GLsizei * length
GLdouble GLdouble GLdouble z
void installMonopoleTransport(G4ProcessManager *pmanager, const G4ParticleDefinition *particle, const MonopoleFieldSetup &fieldSetup)
TG4RunConfiguration * createMonopoleRunConfiguration(const TString &userGeometry, const TString &physicsList, const TString &specialProcess, bool specialStacking, bool mtApplication, double magneticChargeDirac)
MonopoleFieldSetup buildMonopoleFieldSetup(double magneticChargeEplusUnits, double tpcDriftFieldGeant4)
constexpr int MonopolePdgSymm
constexpr int MonopolePdgAsymm
monopole intended to carry opposite electric and magnetic charge (not yet distinguished)
a couple of static helper functions to create timestamp values for CCDB queries or override obsolete ...
The chord finder integrating O2MonopoleEquation, and that equation.
O2MonopoleEquation * equation
to pass the sign of the magnetic charge at each step
G4ChordFinder * chordFinder
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"