Skip to content

Fast simulation hook for Geant4, with a toy absorber model - #56

Closed
sawenzel wants to merge 9 commits into
tmp/abso-fastsim-basefrom
sawenzel/abso-fastsim-envelope
Closed

Fast simulation hook for Geant4, with a toy absorber model#56
sawenzel wants to merge 9 commits into
tmp/abso-fastsim-basefrom
sawenzel/abso-fastsim-envelope

Conversation

@sawenzel

Copy link
Copy Markdown
Owner

Draft, for line-by-line review inside the fork. Not aimed at AliceO2Group/AliceO2 yet — the base is a temporary branch pinned at dev@aecea1d229, so this diff is exactly the six commits and no CI runs.

What it does

Adds the Geant4 fast-simulation hook that O2 was missing, plus one toy model, in a new Detectors/FastSim module. A model replaces the detailed transport through a region of the geometry by a function from the particle that enters it to the particles that leave it.

Off by default. With G4.fastSimModels empty the run configuration returns nullptr and the behaviour is identical to before.

o2-sim -n 10 -g pythia8pp -e TGeant4 -m PIPE ABSO \
  --configKeyValues "G4.fastSimModels=toyAbsorber;G4.fastSimEnvelope=AFaM"

The one design point worth reviewing carefully

A Geant4 region in O2 can only ever be "every volume of a given material": the VMC special cuts (TG4RegionsManager) make every logical volume a root of its own material's region, and Geant4 stops propagating a region at any such daughter. So G4FastTrack::GetEnvelopeSolid() hands back one absorber piece, not the absorber, and a region rooted at AFaM would contain AFaM alone — which tracks skip entirely, because its daughters touch its surface.

The branch therefore separates the two jobs the region was doing:

  • where the model is consulted — derived by walking the envelope's subtree and collecting its media (FastSimRegions.cxx), in CreateUserPostDetConstruction(), the one window after the geometry exists and before the media become regions. 14 media under AFaM, no hand-maintained list.
  • what the model measures against — the envelope volume named by G4.fastSimEnvelope, via the track's own touchable, which already carries the full ancestry and each level's transform. GetEnvelopeSolid() is deliberately unused.

ModelTrigger requires geometric containment in the envelope, which also excludes the steel support cradle — it shares its material with the end plate, so no material selection could separate them.

The real fix is upstream: if TG4RegionsManager stopped claiming every volume as a region root, envelopeDepth() and the whole walk would delete themselves.

Evidence

Built complete (4548/4548 targets, no errors) against O2PDPSuite/daily-20260819-0000-1 on an EPN node, and run. A 20 GeV muon fired into the muon-arm acceptance with /tracking/verbose 1:

   10     54.4   -0.137     -900  1.99e+04   0.0749      287       902   AFaMgRing Transportation
   11     54.4   -0.137     -900         0 1.99e+04  4.1e+03     5e+03   AFaMgRing G4FastSimulationManagerProcess

One step of 4.1 m across the whole absorber, replacing ~20 muIoni steps through graphite, concrete and steel. The emitted particle lands on the stack correctly: daughter of the primary, born at z = −499 cm (the downstream face), carrying the attenuated energy.

Known limitations, deliberately not fixed here

  • The toy model is a placeholder. sample() returns one particle with an exponentially attenuated energy; a real absorber makes a shower. It exists to exercise the machinery.
  • No CPU measurement. The step count shows transport being replaced; a timing number needs a workload where the absorber is on the critical path.
  • Fast-sim secondaries get kPNull (31). geant4_vmc maps FASTSIM_ManagerProcess (subtype 301) to kPNull, the same code it uses for a dozen other things, so provenance is not recoverable from getProcess(). kPUserDefined (47) is free; that is a second small upstream request.
  • ABSO_AIR_ENVELOPE is no longer load-bearing under this scheme (commit 1). Kept because it is what the upstream fix would consume, but it is now a choice.

///
/// A fast simulation model replaces the detailed transport through a region of
/// the geometry by a function from the particle that enters it to the particles
/// that leave it. `DoIt()` below is the plumbing and is the same for every

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is DoIt API from Geant4_VMC? If yes, simply say so. ; If it's a new invention I would call it differently "PerformFastSim" or similar.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Geant4's own — G4VFastSimulationModel::DoIt. Comment now says so, so no rename.

/// A fast simulation model replaces the detailed transport through a region of
/// the geometry by a function from the particle that enters it to the particles
/// that leave it. `DoIt()` below is the plumbing and is the same for every
/// model; a model implements `sample()` and nothing else.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that may not be true. A model can still overwrite DoIt.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. I see it has final so forget about it

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, and I removed the final (see the other thread) so this is now genuinely possible. Reworded.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reopening it in your favour: I dropped the final. A model that does not fit the common shape can override DoIt rather than work around it.


/// Measures the distance to the envelope surface, asks `sample()` what comes
/// out, kills the incident particle, stacks the result and books the energy
/// difference as a deposit.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if we want to have the distance to envelope always measured. It might not be needed. Should we simply say "Wraps common logic of fast sim actions. Calls actual specific model via sample"?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Taken. Comment is now "Wraps the common logic of a fast simulation action and delegates the physics to sample()", plus a note that it can be overridden.

/// the track is not inside it at all.
int envelopeDepth(const G4Track* track) const;

G4String mEnvelope; ///< logical volume the model measures against

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"measures against" ---> encloses?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to "encloses".

Comment thread Detectors/FastSim/include/FastSim/G4FastSimulation.h
};

/// The one hook O2 was missing. Returns nullptr when no model is configured,
/// which is exactly the behaviour before this file existed.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should never make comments refering to old missing stuff. Simply say what this is

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair. Now: "Supplies Geant4-VMC with the fast simulation models and their regions. Returns nullptr when no model is configured, so nothing is set up."

Comment thread Detectors/FastSim/include/FastSim/ToyAbsorberFastSim.h
Comment thread Detectors/FastSim/src/FastSimModel.cxx
// in its direction with the energy attenuated over the path through the
// envelope. A trained model returns a shower here instead.
const double kinetic = input.kineticEnergy * std::exp(-input.exitDistance / kAbsorptionLengthCm);
if (kinetic <= 0.) {

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will never fire, right?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, dead code — ModelTrigger only calls the model above its threshold and an exponential of a finite path cannot reach zero. Removed, with a one-line comment saying why it is always positive.

Comment thread run/SimExamples/FastSim_Absorber/README.md Outdated
@sawenzel sawenzel closed this Aug 21, 2026
sawenzel and others added 9 commits September 19, 2026 03:21
This adds a dedicated air material and medium for AFaM so that the front
absorber can be addressed as a single region.

- AFaM shared ABSO_AIR_C0 with AFaAcc, which is one of its own daughters.
- Geant4-VMC selects fast-simulation regions by material name and puts every
  volume of that material into the region, so a volume can only be a region of
  its own if its material is its own.
- ABSO_AIR_ENVELOPE0$ has the composition and density of ABSO_AIR0$ and takes
  the same global cuts and processes, so the physics is unchanged.
- No other code refers to ABSO medium index 20.
This commit provides the fast-simulation hook that O2 was missing, in a new
Detectors/FastSim module, plus one toy model that exercises it end to end. The
feature does nothing unless G4.fastSimModels names a model.

- o2::fastsim::G4RunConfiguration overrides CreateUserFastSimulation, which is
  the only piece geant4_vmc needed and O2 did not supply.
- TG4FastSimulationPhysics is already registered unconditionally by
  TG4SpecialPhysicsList, so the specialProcess string is unchanged.
- FastSimModel::DoIt is shared plumbing: it measures the distance to the
  envelope surface, kills the incident particle, stacks what comes back and
  books the energy difference as a deposit.
- A model implements sample(), which maps the particle that entered to the
  particles that leave.
- ToyAbsorberFastSim returns one particle carrying on in the incident direction
  with an exponentially attenuated energy.
- Regions are named as tracking media in G4.fastSimRegions.
- With G4.fastSimModels empty the run configuration returns nullptr and the
  behaviour is identical to before.

Usage:

  o2-sim -n 10 -g pythia8pp -e TGeant4 -m PIPE ABSO \
    --configKeyValues "G4.fastSimModels=toyAbsorber;G4.fastSimRegions=ABSO_AIR_ENVELOPE"

The steps inside the region disappear from the step log, which is the saving:
a fast step defaults to AvoidHitInvocation, so Geant4 does not call the
sensitive detector and TVirtualMCApplication::Stepping() is not invoked. That
is correct for a passive envelope, which has no hits to lose.
This adds run/SimExamples/FastSim_Absorber, which runs the same five events
through PIPE and ABSO twice, once with detailed transport and once with the
toy model, and compares the number of tracks.

- run.sh performs both simulations and the comparison.
- countTracks.macro reports tracks per event of an o2-sim output.
- README.md says how the feature is switched on, what the toy model does and
  why the region is named after a tracking medium.
This adds the numbers from a first run of the example, and says what they do
and do not show.

- Five pp events with PIPE and ABSO: 3544 tracks per event with detailed
  transport, 3160 with the toy model.
- Transport time is 29.8 s against 28.0 s, which is not a performance claim:
  only the forward cone of a minimum-bias pp event enters the absorber, so most
  of the transport in this setup happens outside the region.
- The geant4_vmc line confirming that the tracking medium resolved to its
  material is quoted, because a wrong medium name fails silently.
This documents a limitation found by running the example, and withdraws the
reading of its track counts.

- ABSO_AIR_ENVELOPE selects a region containing AFaM and nothing else: 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.
- AFaM's daughters touch its surface, so a track entering the absorber lands in
  a daughter and never has AFaM as its volume.
- A 20 GeV muon with /tracking/verbose 1 steps through the absorber identically
  with and without the fast simulation, showing only muIoni, eIoni,
  Transportation and specialCutForElectron.
- The two runs' random sequences diverge before the absorber, so the track
  counts previously recorded do not measure the model.
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.
- DoIt is Geant4's own entry point and is no longer final, so a model that does
  not fit the common shape can replace it rather than work around it.
- Its comment now says what it is: it wraps the logic common to every model and
  delegates the physics to sample().
- The example passed G4.fastSimRegions=ABSO_AIR_ENVELOPE, which overrides the
  envelope walk with a region containing only AFaM and reproduces exactly the
  behaviour this branch fixes. It now passes G4.fastSimEnvelope=AFaM.
- Drops an unreachable guard in the toy model: ModelTrigger only calls it above
  its threshold and an exponential of a finite path cannot reach zero.
- Comment wording throughout: say what a class is rather than what O2 used to
  lack, and "encloses" rather than "measures against".
The previous text described the behaviour of the branch before the envelope
existed, so most of it was wrong rather than merely stale.

- run.sh now fixes the seed in both runs. Without it o2-sim picks one per run,
  the two simulations see different primaries, and the difference between them
  is mostly different events: an unseeded pair read 2.25x faster, a seeded pair
  reads 5%.
- Measured on five pp events with PIPE and ABSO: 1547 tracks per event with
  detailed transport against 1144 with the toy model, 14.9 s against 14.2 s.
- Says why a quarter fewer tracks buys five percent of wall clock, and that
  neither number is a performance result or a physics validation.
- Records the two properties that surprise a reader of the output: a fast step
  does not call the sensitive detector, and its secondaries carry kPNull.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants