Skip to content

Commit 8be434d

Browse files
committed
Trigger a fast simulation model on its envelope, not on its region
This makes a model cover a whole module, which the region mechanism alone cannot express. - A Geant4 region in O2 is always "every volume of a given material": the VMC special cuts make every logical volume a root of its own material's region, and Geant4 stops propagating a region at any such daughter. - G4FastTrack::GetEnvelopeSolid() therefore returns one absorber piece, and FastSimModel no longer uses it. - G4.fastSimEnvelope names the volume a model stands in for. Containment and the exit distance are read from the track's own touchable, which already carries the full ancestry and the transform of every level. - ModelTrigger now requires geometric containment in that envelope, which also excludes volumes that merely share a material with it, such as the absorber's support cradle. - The regions needed for Geant4 to consult the model are derived by walking the envelope's subtree and collecting its media, so no list is maintained by hand. The walk happens in CreateUserPostDetConstruction, the one point after the geometry is built and before the media are turned into regions. - G4.fastSimRegions still overrides the walk with an explicit list.
1 parent 2b7e283 commit 8be434d

8 files changed

Lines changed: 315 additions & 31 deletions

File tree

Common/SimConfig/include/SimConfig/G4Params.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ struct G4Params : public o2::conf::ConfigurableParamHelper<G4Params> {
5858
// Fast simulation. Empty fastSimModels (the default) disables the feature
5959
// entirely; see Detectors/gconfig/include/SimSetup/G4FastSimulation.h.
6060
std::string fastSimModels = ""; // comma-separated model names to activate
61-
std::string fastSimRegions = ""; // tracking media the models apply to (wildcards allowed)
61+
std::string fastSimEnvelope = ""; // volume a model stands in for, e.g. AFaM; the media of its
62+
// subtree are collected automatically
63+
std::string fastSimRegions = ""; // optional explicit media, overriding the subtree walk
6264
float fastSimMinEnergy = 1.f; // GeV; below this the detailed transport runs
6365
O2ParamDef(G4Params, "G4");
6466
};

Detectors/FastSim/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
o2_add_library(FastSim
1313
SOURCES src/FastSimModel.cxx
14+
src/FastSimRegions.cxx
1415
src/ToyAbsorberFastSim.cxx
1516
src/G4FastSimulation.cxx
1617
PUBLIC_LINK_LIBRARIES MC::Geant4VMC MC::Geant4 O2::SimConfig

Detectors/FastSim/include/FastSim/FastSimModel.h

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ struct FastSimInput {
3939
double kineticEnergy = 0.; ///< GeV
4040
double mass = 0.; ///< GeV
4141
double time = 0.; ///< ns
42-
double exitDistance = 0.; ///< cm from `position` to the envelope surface along `direction`
42+
double exitDistance = 0.; ///< cm from `position` to the ENVELOPE surface along `direction`
4343
};
4444

4545
/// One particle leaving the envelope.
@@ -55,10 +55,23 @@ struct FastSimOutput {
5555
/// zero-length steps before it gets out. Models should emit just beyond it.
5656
constexpr double kSurfaceEpsilonCm = 1e-5;
5757

58+
/// Base class for fast simulation models.
59+
///
60+
/// The model is attached to regions (see G4FastSimulation.h) purely so that
61+
/// Geant4 consults it; what it measures against is the ENVELOPE VOLUME named
62+
/// below, which is normally the mother volume of a whole module. The two are
63+
/// deliberately separate, because a Geant4 region in O2 can only ever be "every
64+
/// volume of a given material" -- the VMC special cuts make every logical volume
65+
/// a root of its own material's region, and Geant4 stops propagating a region at
66+
/// any such daughter. So `G4FastTrack::GetEnvelopeSolid()` would hand back one
67+
/// absorber piece rather than the absorber, and this class does not use it.
68+
///
69+
/// Containment and the exit distance are taken from the track's own touchable,
70+
/// which already carries the full ancestry and the transform of every level.
5871
class FastSimModel : public G4VFastSimulationModel
5972
{
6073
public:
61-
FastSimModel(const G4String& name, double minEnergyGeV);
74+
FastSimModel(const G4String& name, const G4String& envelopeVolume, double minEnergyGeV);
6275

6376
G4bool IsApplicable(const G4ParticleDefinition& particle) override;
6477
G4bool ModelTrigger(const G4FastTrack& fastTrack) override;
@@ -74,7 +87,13 @@ class FastSimModel : public G4VFastSimulationModel
7487
virtual std::vector<FastSimOutput> sample(const FastSimInput& input) const = 0;
7588

7689
private:
90+
/// The track's ancestry level at which the envelope volume sits, or -1 when
91+
/// the track is not inside it at all.
92+
int envelopeDepth(const G4Track* track) const;
93+
94+
G4String mEnvelope; ///< logical volume the model measures against
7795
double mMinEnergy = 0.; ///< internal Geant4 units; below this the detailed transport runs
96+
mutable bool mWarned = false;
7897
};
7998

8099
} // namespace o2::fastsim
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright 2019-2026 CERN and copyright holders of ALICE O2.
2+
// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3+
// All rights not expressly granted are reserved.
4+
//
5+
// This software is distributed under the terms of the GNU General Public
6+
// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7+
//
8+
// In applying this license CERN does not waive the privileges and immunities
9+
// granted to it by virtue of its status as an Intergovernmental Organization
10+
// or submit itself to any jurisdiction.
11+
12+
#ifndef O2_FASTSIM_REGIONS_H_
13+
#define O2_FASTSIM_REGIONS_H_
14+
15+
/// Deriving a model's regions from its envelope volume.
16+
///
17+
/// Geant4-VMC attaches a fast simulation model to regions, and in O2 a region
18+
/// can only be "every volume of a given material" (see FastSimModel.h). To have
19+
/// the model consulted everywhere inside a module, it must therefore be attached
20+
/// to every material that module is built from -- which is a list nobody should
21+
/// maintain by hand, because it changes whenever the geometry does.
22+
///
23+
/// So walk the envelope's subtree and collect the media as they actually are.
24+
25+
#include "TG4VUserPostDetConstruction.h"
26+
27+
#include <set>
28+
#include <string>
29+
#include <vector>
30+
31+
class TGeoVolume;
32+
33+
namespace o2::fastsim
34+
{
35+
36+
/// Every tracking medium used by `volume` or anything below it.
37+
/// Takes a non-const pointer because TGeo's accessors are not const.
38+
std::set<std::string> mediaInSubtree(TGeoVolume* volume);
39+
40+
/// Same, looked up by volume name in the current TGeo geometry. Empty if there
41+
/// is no such volume.
42+
std::set<std::string> mediaInSubtree(const std::string& volumeName);
43+
44+
/// Sets each model's regions from its envelope, in the one window where that is
45+
/// possible: after the geometry is built and before Geant4-VMC turns the media
46+
/// into regions.
47+
class FastSimRegionConstruction : public TG4VUserPostDetConstruction
48+
{
49+
public:
50+
struct ModelRegions {
51+
std::string model;
52+
std::string envelope; ///< volume whose subtree supplies the media
53+
std::string regions; ///< explicit media, used instead of the walk if given
54+
};
55+
56+
explicit FastSimRegionConstruction(std::vector<ModelRegions> models);
57+
void Construct() override;
58+
59+
private:
60+
std::vector<ModelRegions> mModels;
61+
};
62+
63+
} // namespace o2::fastsim
64+
65+
#endif // O2_FASTSIM_REGIONS_H_

Detectors/FastSim/include/FastSim/G4FastSimulation.h

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,20 @@
1818
///
1919
/// o2-sim -n 10 -g pythia8pp -e TGeant4 -m PIPE ABSO
2020
/// --configKeyValues "G4.fastSimModels=toyAbsorber;
21-
/// G4.fastSimRegions=ABSO_AIR_ENVELOPE"
21+
/// G4.fastSimEnvelope=AFaM"
2222
///
23-
/// Regions are selected by TRACKING MEDIUM name (wildcards allowed), which
24-
/// Geant4-VMC maps to the medium's MATERIAL; every volume of that material joins
25-
/// the region. That is why the absorber mother volume AFaM carries a material of
26-
/// its own (see Detectors/Passive/src/Absorber.cxx). Selection by volume is not
27-
/// available: the VMC special cuts already root every logical volume in a
28-
/// per-material region, and Geant4 allows a volume in only one region.
23+
/// `G4.fastSimEnvelope` names the VOLUME the model stands in for. The regions
24+
/// Geant4 needs in order to consult the model are derived from it by walking its
25+
/// subtree and collecting the media (FastSimRegions.h) -- a region in O2 can only
26+
/// be "every volume of a given material", so covering a module means naming all
27+
/// of its materials, and that list should not be maintained by hand.
28+
///
29+
/// `G4.fastSimRegions` overrides the walk with an explicit space-separated list
30+
/// of media, for when a model should see less than a whole subtree.
2931

3032
#include "TG4RunConfiguration.h"
3133
#include "TG4VUserFastSimulation.h"
34+
#include "TG4VUserPostDetConstruction.h"
3235

3336
#include <string>
3437
#include <vector>
@@ -40,12 +43,13 @@ namespace o2::fastsim
4043
class G4FastSimulation : public TG4VUserFastSimulation
4144
{
4245
public:
43-
G4FastSimulation(std::vector<std::string> models, const std::string& regions,
46+
G4FastSimulation(std::vector<std::string> models, const std::string& envelope,
4447
double minEnergyGeV);
4548
void Construct() override;
4649

4750
private:
4851
std::vector<std::string> mModels;
52+
std::string mEnvelope;
4953
double mMinEnergy = 1.;
5054
};
5155

@@ -56,6 +60,7 @@ class G4RunConfiguration : public TG4RunConfiguration
5660
public:
5761
using TG4RunConfiguration::TG4RunConfiguration;
5862
TG4VUserFastSimulation* CreateUserFastSimulation() override;
63+
TG4VUserPostDetConstruction* CreateUserPostDetConstruction() override;
5964
};
6065

6166
} // namespace o2::fastsim

Detectors/FastSim/src/FastSimModel.cxx

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,51 @@
2121
#include <G4SystemOfUnits.hh>
2222
#include <G4ThreeVector.hh>
2323
#include <G4Track.hh>
24+
#include <G4NavigationHistory.hh>
25+
#include <G4TouchableHistory.hh>
26+
#include <G4VPhysicalVolume.hh>
2427
#include <G4VSolid.hh>
28+
#include <G4VTouchable.hh>
2529

2630
#include <algorithm>
2731

2832
namespace o2::fastsim
2933
{
3034

3135
//_____________________________________________________________________________
32-
FastSimModel::FastSimModel(const G4String& name, double minEnergyGeV)
33-
: G4VFastSimulationModel(name), mMinEnergy(minEnergyGeV * CLHEP::GeV)
36+
FastSimModel::FastSimModel(const G4String& name, const G4String& envelopeVolume,
37+
double minEnergyGeV)
38+
: G4VFastSimulationModel(name),
39+
mEnvelope(envelopeVolume),
40+
mMinEnergy(minEnergyGeV * CLHEP::GeV)
3441
{
3542
}
3643

44+
//_____________________________________________________________________________
45+
int FastSimModel::envelopeDepth(const G4Track* track) const
46+
{
47+
/// Where the envelope volume sits in the track's ancestry, or -1 if the track
48+
/// is not inside it.
49+
///
50+
/// The touchable is the cheapest exact answer to "is this track inside the
51+
/// module": it is the navigator's own record of the volume and every one of
52+
/// its ancestors, so no geometry lookup, no cached transform and no name list
53+
/// is needed, and it stays correct if the envelope is ever placed more than
54+
/// once.
55+
const G4VTouchable* touchable = track->GetTouchable();
56+
if (touchable == nullptr) {
57+
return -1;
58+
}
59+
const G4int depth = touchable->GetHistoryDepth();
60+
for (G4int level = 0; level <= depth; ++level) {
61+
const G4VPhysicalVolume* volume = touchable->GetVolume(level);
62+
if (volume != nullptr && volume->GetLogicalVolume()->GetName() == mEnvelope) {
63+
return level;
64+
}
65+
}
66+
return -1;
67+
}
68+
3769
//_____________________________________________________________________________
3870
G4bool FastSimModel::IsApplicable(const G4ParticleDefinition&)
3971
{
@@ -45,9 +77,28 @@ G4bool FastSimModel::IsApplicable(const G4ParticleDefinition&)
4577
//_____________________________________________________________________________
4678
G4bool FastSimModel::ModelTrigger(const G4FastTrack& fastTrack)
4779
{
80+
const G4Track* track = fastTrack.GetPrimaryTrack();
81+
4882
// Below the threshold the detailed transport is cheap and a surrogate would
4983
// be extrapolating.
50-
return fastTrack.GetPrimaryTrack()->GetKineticEnergy() > mMinEnergy;
84+
if (track->GetKineticEnergy() <= mMinEnergy) {
85+
return false;
86+
}
87+
88+
// Geometric containment rather than a name list. This is what excludes, for
89+
// instance, the absorber's steel support cradle: it shares its material with
90+
// parts of the absorber, so no selection by material can separate them, but
91+
// it sits outside the envelope and so fails here.
92+
if (envelopeDepth(track) < 0) {
93+
if (!mWarned) {
94+
mWarned = true;
95+
LOG(warn) << "fast simulation: model " << GetName() << " was consulted for a track "
96+
<< "outside its envelope '" << mEnvelope << "'; the region selection is "
97+
<< "wider than the envelope, which is allowed but wasteful";
98+
}
99+
return false;
100+
}
101+
return true;
51102
}
52103

53104
//_____________________________________________________________________________
@@ -68,9 +119,17 @@ void FastSimModel::DoIt(const G4FastTrack& fastTrack, G4FastStep& fastStep)
68119
input.kineticEnergy = track->GetKineticEnergy() / CLHEP::GeV;
69120
input.mass = track->GetDefinition()->GetPDGMass() / CLHEP::GeV;
70121
input.time = track->GetGlobalTime() / CLHEP::ns;
71-
input.exitDistance = fastTrack.GetEnvelopeSolid()->DistanceToOut(
72-
fastTrack.GetPrimaryTrackLocalPosition(),
73-
fastTrack.GetPrimaryTrackLocalDirection()) /
122+
// Deliberately NOT GetEnvelopeSolid(): that is the region's root volume, i.e.
123+
// one absorber piece. Measure against the envelope volume instead, using the
124+
// transform the touchable already holds for that level.
125+
const G4int level = envelopeDepth(track);
126+
const G4VTouchable* touchable = track->GetTouchable();
127+
const G4AffineTransform& toLocal =
128+
touchable->GetHistory()->GetTransform(touchable->GetHistoryDepth() - level);
129+
const G4VSolid* envelopeSolid = touchable->GetVolume(level)->GetLogicalVolume()->GetSolid();
130+
131+
input.exitDistance = envelopeSolid->DistanceToOut(toLocal.TransformPoint(position),
132+
toLocal.TransformAxis(direction)) /
74133
CLHEP::cm;
75134

76135
const std::vector<FastSimOutput> outgoing = sample(input);

0 commit comments

Comments
 (0)