Skip to content

Commit 24d46a0

Browse files
sawenzelclaude
andcommitted
Add a lighter VecGeom navigator that adopts the propagated state
This adds G4.vecgeomNavigator=kPropagated, a second VecGeom navigator kept for comparison. - It adopts the state VecGeom propagated during the step instead of relocating at the boundary. - It returns zero safety for the step after a crossing and nudges stuck steps forward. - The default navigator becomes G4.vecgeomNavigator=kRelocating. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 9ae3442 commit 24d46a0

6 files changed

Lines changed: 325 additions & 5 deletions

File tree

‎Common/SimConfig/include/SimConfig/G4Params.h‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ enum class EG4Nav {
4141
kVecGeom = 2 /* navigate with VecGeom, on the G4 geometry built from TGeo */
4242
};
4343

44+
// the Geant4 navigator used with navmode kVecGeom
45+
enum class EVecGeomNav {
46+
kRelocating = 0, /* relocates at the boundary locate, blocking the volume just left (default) */
47+
kPropagated = 1 /* adopts the state VecGeom propagated during the step; less work per crossing */
48+
};
49+
4450
// parameters to influence the G4 engine
4551
struct G4Params : public o2::conf::ConfigurableParamHelper<G4Params> {
4652
EG4Physics physicsmode = EG4Physics::kFTFP_BERT_EMV_optical; // default physics mode with which to configure G4
@@ -51,6 +57,8 @@ struct G4Params : public o2::conf::ConfigurableParamHelper<G4Params> {
5157
EG4Nav navmode = EG4Nav::kTGeo; // geometry navigation mode (default TGeo)
5258

5359
// Settings for navmode == kVecGeom; ignored otherwise.
60+
// which of the two VecGeom navigators
61+
EVecGeomNav vecgeomNavigator = EVecGeomNav::kRelocating;
5462
double vecgeomPushDepth = 1.e-9; // cm; how far past a face, measured across it, a boundary
5563
// point is pushed before it is located
5664
bool vecgeomZeroSafety = false; // answer zero to every safety query; conservative, but it

‎Detectors/gconfig/CMakeLists.txt‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ set(G4SETUP_SOURCES src/G4Config.cxx src/G4RunConfiguration.cxx src/G4LocalField
3131
src/VecGeomNavigation.cxx)
3232
if(G4SETUP_WITH_VECGEOM)
3333
list(APPEND G4SETUP_SOURCES src/VecGeomG4Map.cxx src/VecGeomChecks.cxx src/VecGeomG4NavigatorBase.cxx
34-
src/VecGeomG4Navigator.cxx)
34+
src/VecGeomG4Navigator.cxx
35+
src/VecGeomG4PropagatingNavigator.cxx)
3536
endif()
3637

3738
o2_add_library(G4Setup

‎Detectors/gconfig/src/VecGeomG4Navigator.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ namespace o2::simsetup
1919

2020
/// A Geant4 tracking navigator that answers every navigation query from VecGeom while keeping the
2121
/// Geant4 navigation history, the touchable the scoring code reads, in step with the VecGeom state.
22-
/// It works as G4VecGeomNav's
22+
/// Selected with G4.vecgeomNavigator=kRelocating, the default. It works as G4VecGeomNav's
2323
/// TG4VecGeomNavigator does:
2424
///
2525
/// - ComputeStep leaves the current volume alone. It records whether the step ends on a boundary
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
// Copyright 2019-2020 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+
#include "VecGeomG4PropagatingNavigator.h"
13+
14+
#include "G4TouchableHistory.hh"
15+
#include "G4VPhysicalVolume.hh"
16+
17+
#include <VecGeom/base/Transformation3D.h>
18+
#include <VecGeom/management/GeoManager.h>
19+
#include <VecGeom/navigation/GlobalLocator.h>
20+
#include <VecGeom/navigation/VNavigator.h>
21+
#include <VecGeom/navigation/VSafetyEstimator.h>
22+
#include <VecGeom/volumes/LogicalVolume.h>
23+
#include <VecGeom/volumes/PlacedVolume.h>
24+
25+
#include <fairlogger/Logger.h>
26+
27+
#include <cmath>
28+
29+
namespace
30+
{
31+
/// Geant4 abandons a track after ~50 zero steps, so a stalled step is nudged forward by this much
32+
/// (in Geant4 units) rather than returned as zero.
33+
const double kNudge = 1.e-3;
34+
} // namespace
35+
36+
namespace o2::simsetup
37+
{
38+
39+
VecGeomG4PropagatingNavigator::VecGeomG4PropagatingNavigator(VecGeomG4Map const& map, bool zeroSafety)
40+
: VecGeomG4NavigatorBase(map), mZeroSafety(zeroSafety)
41+
{
42+
}
43+
44+
VecGeomG4PropagatingNavigator::~VecGeomG4PropagatingNavigator()
45+
{
46+
LOG(info) << "VecGeom navigation: " << mNudgedSteps << " stalled steps nudged forward, " << mGlobalRelocates
47+
<< " relocations restarted from the world";
48+
}
49+
50+
G4double VecGeomG4PropagatingNavigator::ComputeStep(const G4ThreeVector& globalPoint, const G4ThreeVector& direction,
51+
const G4double proposedStepLength, G4double& newSafety)
52+
{
53+
newSafety = 0.;
54+
mLastDirection = direction;
55+
mHaveNextState = false;
56+
57+
auto const* top = mCurState.Top();
58+
if (top == nullptr) { // the track is outside the world
59+
mWouldEnter = mWouldExit = false;
60+
return kInfinity;
61+
}
62+
auto const* navigator = top->GetLogicalVolume()->GetNavigator();
63+
64+
double limit = proposedStepLength * kG4ToVG;
65+
if (!(limit < vecgeom::kInfLength)) {
66+
limit = vecgeom::kInfLength;
67+
}
68+
69+
// VecGeom's own combined entry point, rather than a step followed by a relocation of our own: it
70+
// is the one that knows how to descend through an assembly, whose placed volume can never be the
71+
// answer because it has no DistanceToOut.
72+
double vgSafety = 0.;
73+
const double vgStep = navigator->ComputeStepAndSafetyAndPropagatedState(
74+
toVG(globalPoint), toDir(direction), limit, mCurState, mNextState, !mOnBoundary && !mZeroSafety, vgSafety);
75+
mOnBoundary = false;
76+
newSafety = (vgSafety > 0. && !mZeroSafety) ? vgSafety * kVGToG4 : 0.;
77+
78+
G4double step = vgStep * kVGToG4;
79+
if (mNextState.IsOnBoundary()) {
80+
// Entering a daughter deepens the state, possibly by more than one level when an assembly
81+
// stands in between; leaving the current volume does not.
82+
mWouldEnter = mNextState.GetCurrentLevel() > mCurState.GetCurrentLevel();
83+
mWouldExit = !mWouldEnter;
84+
mNextPoint = globalPoint + step * direction;
85+
mHaveNextState = true;
86+
} else {
87+
mWouldEnter = mWouldExit = false;
88+
step = kInfinity;
89+
}
90+
91+
if (vgStep < 0.) {
92+
// A negative distance means the state and the point disagree. Nudge forward and relocate from
93+
// the world on the next call rather than propagating the inconsistency.
94+
mForceReInit = true;
95+
mHaveNextState = false;
96+
++mNudgedSteps;
97+
mZeroSteps = 0;
98+
return kNudge;
99+
}
100+
if (step < 1.e-10) {
101+
if (++mZeroSteps > 4) {
102+
mForceReInit = true;
103+
mHaveNextState = false;
104+
++mNudgedSteps;
105+
return kNudge;
106+
}
107+
} else {
108+
mForceReInit = false;
109+
mZeroSteps = 0;
110+
}
111+
return step;
112+
}
113+
114+
G4VPhysicalVolume* VecGeomG4PropagatingNavigator::ResetHierarchyAndLocate(const G4ThreeVector&, const G4ThreeVector&,
115+
const G4TouchableHistory& history)
116+
{
117+
// Geant4 hands back a touchable it saved earlier, e.g. when resuming a track whose secondaries
118+
// were followed first. The VecGeom state is rebuilt from it.
119+
fEnteredDaughter = false;
120+
fExitedMother = false;
121+
mWouldEnter = false;
122+
mWouldExit = false;
123+
mOnBoundary = false;
124+
mHaveNextState = false;
125+
fHistory = *history.GetHistory();
126+
if (!stateFromHistory(mCurState) && fHistory.GetVolume(0) != nullptr) {
127+
LOG(fatal) << "Geant4 handed back a touchable that matches no VecGeom path";
128+
}
129+
mPrevState = mCurState;
130+
return fHistory.GetTopVolume();
131+
}
132+
133+
G4VPhysicalVolume* VecGeomG4PropagatingNavigator::LocateGlobalPointAndSetup(const G4ThreeVector& point,
134+
const G4ThreeVector*,
135+
const G4bool relativeSearch, const G4bool)
136+
{
137+
bool onBoundary = fWasLimitedByGeometry;
138+
if (mHaveNextState && point.diff2(mNextPoint) < 1.e-16) {
139+
onBoundary = true;
140+
}
141+
142+
mPrevState = mCurState;
143+
144+
if (!mForceReInit && relativeSearch && onBoundary && mHaveNextState) {
145+
// The state on the far side of the boundary was already worked out, and relocated, by the step
146+
// that found it. Adopting it is cheaper than locating again.
147+
mCurState = mNextState;
148+
} else if (mForceReInit || !relativeSearch || onBoundary) {
149+
mCurState.Clear();
150+
vecgeom::GlobalLocator::LocateGlobalPoint(vecgeom::GeoManager::Instance().GetWorld(), toVG(point), mCurState, true);
151+
mForceReInit = false;
152+
++mGlobalRelocates;
153+
}
154+
// Otherwise the point only moved inside the volume the state already names.
155+
156+
auto* target = historyFromState(mCurState);
157+
mCrossed = onBoundary;
158+
if (onBoundary) {
159+
fExitedMother = mWouldExit;
160+
fEnteredDaughter = mWouldEnter;
161+
mOnBoundary = true;
162+
}
163+
mHaveNextState = false;
164+
return target;
165+
}
166+
167+
void VecGeomG4PropagatingNavigator::LocateGlobalPointWithinVolume(const G4ThreeVector&)
168+
{
169+
// The track moved inside the volume it is already in, so only the boundary flags change.
170+
mWouldEnter = false;
171+
mWouldExit = false;
172+
mOnBoundary = false;
173+
mCrossed = false;
174+
mHaveNextState = false;
175+
fEnteredDaughter = false;
176+
fExitedMother = false;
177+
}
178+
179+
G4double VecGeomG4PropagatingNavigator::ComputeSafety(const G4ThreeVector& globalPoint, const G4double, const G4bool)
180+
{
181+
if (mZeroSafety || mOnBoundary || mCrossed || fEnteredDaughter || fExitedMother || mWouldEnter || mWouldExit) {
182+
return 0.;
183+
}
184+
auto const* top = mCurState.Top();
185+
if (top == nullptr) {
186+
return 0.;
187+
}
188+
const double safety = top->GetLogicalVolume()->GetSafetyEstimator()->ComputeSafety(toVG(globalPoint), mCurState);
189+
return (safety > 0.) ? safety * kVGToG4 : 0.;
190+
}
191+
192+
G4ThreeVector VecGeomG4PropagatingNavigator::GetGlobalExitNormal(const G4ThreeVector& point, G4bool* valid)
193+
{
194+
// The surface just crossed belongs to the volume that was left when the step exited a mother, and
195+
// to the volume that was entered when it entered a daughter.
196+
auto const& state = mWouldExit ? mPrevState : mCurState;
197+
auto const* volume = state.Top();
198+
if (volume == nullptr) {
199+
*valid = false;
200+
return G4ThreeVector(0., 0., 1.);
201+
}
202+
203+
vecgeom::Transformation3D m;
204+
state.TopMatrix(m);
205+
V3 normal;
206+
volume->Normal(m.Transform(toVG(point)), normal);
207+
V3 global = m.InverseTransformDirection(normal);
208+
209+
// Oriented along the direction of motion, as TGeo's FindNormalFast does.
210+
const V3 dir = toDir(mLastDirection);
211+
if (global.Dot(dir) < 0.) {
212+
global = -global;
213+
}
214+
// VecGeom's Normal() also answers whether the point was on the surface; Geant4 hands back points a
215+
// few nanometres off the face, so only a degenerate vector is refused.
216+
const double mag2 = global.Mag2();
217+
*valid = std::isfinite(mag2) && mag2 > 0.25;
218+
return G4ThreeVector(global[0], global[1], global[2]);
219+
}
220+
221+
G4ThreeVector VecGeomG4PropagatingNavigator::GetLocalExitNormal(G4bool* valid)
222+
{
223+
// By convention the local normal is expressed in the frame of the final volume.
224+
const G4ThreeVector global = GetGlobalExitNormal(mNextPoint, valid);
225+
vecgeom::Transformation3D m;
226+
mCurState.TopMatrix(m);
227+
const V3 local = m.TransformDirection(toDir(global));
228+
return G4ThreeVector(local[0], local[1], local[2]);
229+
}
230+
231+
} // namespace o2::simsetup
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright 2019-2020 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_SIMSETUP_VECGEOMG4PROPAGATINGNAVIGATOR_H_
13+
#define O2_SIMSETUP_VECGEOMG4PROPAGATINGNAVIGATOR_H_
14+
15+
#include "VecGeomG4NavigatorBase.h"
16+
17+
namespace o2::simsetup
18+
{
19+
20+
/// A lighter VecGeom navigator for Geant4, selected with G4.vecgeomNavigator=kPropagated. ComputeStep
21+
/// uses VecGeom's combined step-and-relocate, and a boundary locate adopts the state that step
22+
/// propagated instead of locating again. Safety is zero for the whole step after a crossing, a stuck
23+
/// step is nudged forward, and the exit normal follows the direction of motion. It does less work per
24+
/// step and per crossing than VecGeomG4Navigator.
25+
class VecGeomG4PropagatingNavigator : public VecGeomG4NavigatorBase
26+
{
27+
public:
28+
VecGeomG4PropagatingNavigator(VecGeomG4Map const& map, bool zeroSafety);
29+
~VecGeomG4PropagatingNavigator() override;
30+
31+
G4double ComputeStep(const G4ThreeVector& globalPoint, const G4ThreeVector& direction,
32+
const G4double proposedStepLength, G4double& newSafety) override;
33+
34+
G4VPhysicalVolume* ResetHierarchyAndLocate(const G4ThreeVector& point, const G4ThreeVector& direction,
35+
const G4TouchableHistory& history) override;
36+
37+
G4VPhysicalVolume* LocateGlobalPointAndSetup(const G4ThreeVector& point, const G4ThreeVector* direction = nullptr,
38+
const G4bool relativeSearch = true,
39+
const G4bool ignoreDirection = true) override;
40+
41+
void LocateGlobalPointWithinVolume(const G4ThreeVector& position) override;
42+
43+
G4double ComputeSafety(const G4ThreeVector& globalPoint, const G4double proposedMaxLength = DBL_MAX,
44+
const G4bool keepState = true) override;
45+
46+
G4ThreeVector GetLocalExitNormal(G4bool* valid) override;
47+
G4ThreeVector GetGlobalExitNormal(const G4ThreeVector& point, G4bool* valid) override;
48+
49+
private:
50+
vecgeom::NavigationState mCurState; ///< where the track is now
51+
vecgeom::NavigationState mNextState; ///< where the last computed step would put it
52+
vecgeom::NavigationState mPrevState; ///< where it was before the last boundary crossing
53+
54+
bool mZeroSafety = false;
55+
bool mHaveNextState = false; ///< mNextState holds the result of a geometry-limited step
56+
57+
G4ThreeVector mNextPoint{-1e8, -1e8, -1e8}; ///< where the last computed step ends
58+
G4ThreeVector mLastDirection{0, 0, 1}; ///< direction of the last computed step
59+
60+
bool mWouldEnter = false; ///< the last step ends by entering a daughter
61+
bool mWouldExit = false; ///< the last step ends by leaving the current volume
62+
bool mOnBoundary = false; ///< the current point sits on a boundary
63+
bool mForceReInit = false; ///< next locate must start from the world, the state is suspect
64+
bool mCrossed = false; ///< the last locate acted on a boundary crossing
65+
66+
int mZeroSteps = 0;
67+
long mNudgedSteps = 0;
68+
long mGlobalRelocates = 0;
69+
};
70+
71+
} // namespace o2::simsetup
72+
73+
#endif

‎Detectors/gconfig/src/VecGeomNavigation.cxx‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "VecGeomChecks.h"
2727
#include "VecGeomG4Map.h"
2828
#include "VecGeomG4Navigator.h"
29+
#include "VecGeomG4PropagatingNavigator.h"
2930

3031
#include "TG4RootDetectorConstruction.h"
3132
#include "TG4RootNavMgr.h"
@@ -72,8 +73,8 @@ void installVecGeomNavigator()
7273
"the TGeant4 engine has been created";
7374
}
7475

75-
if (!g4Params.vecgeomFlattenAssemblies) {
76-
LOG(fatal) << "G4.vecgeomNavigator=kStrict needs G4.vecgeomFlattenAssemblies=true: it enters a daughter by "
76+
if (!g4Params.vecgeomFlattenAssemblies && g4Params.vecgeomNavigator == o2::conf::EVecGeomNav::kRelocating) {
77+
LOG(fatal) << "G4.vecgeomNavigator=kRelocating needs G4.vecgeomFlattenAssemblies=true: it enters a daughter by "
7778
"locating inside it, which an assembly cannot answer";
7879
}
7980

@@ -104,7 +105,13 @@ void installVecGeomNavigator()
104105
checkVecGeomLocation(static_cast<std::size_t>(g4Params.vecgeomCheckLocation));
105106
}
106107

107-
auto* navigator = new VecGeomG4Navigator(map, g4Params.vecgeomPushDepth, g4Params.vecgeomZeroSafety);
108+
G4Navigator* navigator = nullptr;
109+
if (g4Params.vecgeomNavigator == o2::conf::EVecGeomNav::kPropagated) {
110+
LOG(info) << "VecGeom navigation: propagating navigator";
111+
navigator = new VecGeomG4PropagatingNavigator(map, g4Params.vecgeomZeroSafety);
112+
} else {
113+
navigator = new VecGeomG4Navigator(map, g4Params.vecgeomPushDepth, g4Params.vecgeomZeroSafety);
114+
}
108115
navigator->SetWorldVolume(detConstruction->GetTopPV());
109116

110117
// Same sequence TG4RootNavMgr::SetNavigator uses, run here because by the time the engine

0 commit comments

Comments
 (0)