Skip to content

Commit 61350cd

Browse files
sawenzelbenedikt-voelkelAnthonySwaingalickazuzanna
authored
Integrate geometry step killing (#15684)
This commit integrates the geometry-step killing mechanism originally studied by Benedikt Voelkel and Anthony Swain. A step filter is loaded from a ROOT macro named by SimCutParams.stepFilteringMacro and evaluated for every step in addition to the built-in z/R cut, and MCReplayParam.allowStopTrack lets a replay act on it. Both are unset by default. The code is taken from #12119 https://github.com/benedikt-voelkel/AliceO2/tree/geom-cuts https://github.com/benedikt-voelkel/AliceO2/tree/envelope https://github.com/AnthonySwain/AliceO2/tree/ParticleSpecificMaps and the studies behind it are presented in B. Voelkel, Geometry cuts in MC transport, WP12/13, 11.10.2023 https://indico.cern.ch/event/1334852/contributions/5620122/ A. Swain, Geometric hyperparameter optimisation of ALICE MC transport https://indico.cern.ch/event/1323900/contributions/5571214/ CERN-STUDENTS-Note-2023-164 In addition, we provide run/SimExamples/Geometry_StepFiltering, which runs the whole chain from a reference simulation to a hull-filtered replay, and makeKeepStepCylinders.macro, which is able to rederive the geometry hull from a closed geometry to some extent. Thanks to @galickazuzanna for help in compiling this commit. Co-authored-by: Benedikt Volkel <benedikt-voelkel@users.noreply.github.com> Co-authored-by: Anthony Swain <AnthonySwain@users.noreply.github.com> Co-authored-by: Zuzanna Galicka <galickazuzanna@users.noreply.github.com>
1 parent ff94004 commit 61350cd

12 files changed

Lines changed: 904 additions & 2 deletions

File tree

Common/SimConfig/include/SimConfig/SimParams.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ namespace conf
2323
// (mostly used in O2MCApplication stepping)
2424
struct SimCutParams : public o2::conf::ConfigurableParamHelper<SimCutParams> {
2525
bool stepFiltering = true; // if we activate the step filtering in O2BaseMCApplication
26+
std::string stepFilteringMacro = ""; // ROOT macro providing keepStep(); empty = built-in z/R cut
2627
bool stepTrackRefHook = false; // if we create track references during generic stepping
2728
std::string stepTrackRefHookFile = "${O2_ROOT}/share/Detectors/gconfig/StandardSteppingTrackRefHook.macro"; // the standard code holding the TrackRef callback
2829

Detectors/gconfig/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,5 @@ o2_add_test_root_macro(g3Config.C
6565

6666

6767
o2_data_file(COPY data DESTINATION Detectors/gconfig/)
68-
install(FILES src/StandardSteppingTrackRefHook.macro src/FlukaRuntimeConfig.macro DESTINATION share/Detectors/gconfig/)
68+
install(FILES src/StandardSteppingTrackRefHook.macro src/FlukaRuntimeConfig.macro
69+
src/KeepStepCylinders.macro DESTINATION share/Detectors/gconfig/)

Detectors/gconfig/include/SimSetup/MCReplayParam.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ struct MCReplayParam : public o2::conf::ConfigurableParamHelper<MCReplayParam> {
2929
std::string stepFilename = "MCStepLoggerOutput.root"; // filename where to find the stepTreename
3030
float energyCut = -1.; // minimum energy required for a step to continue tracking
3131
std::string cutFile = "";
32+
bool allowStopTrack = false;
3233
O2ParamDef(MCReplayParam, "MCReplayParam");
3334
};
3435
} // end namespace o2
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Generated by run/SimExamples/Geometry_StepFiltering/makeKeepStepCylinders.macro
2+
// -- do not edit by hand.
3+
// Geometry: o2sim_geometry.root
4+
// Envelope: max radius of sensitive volumes and of material with rho > 0.01 g/cm3,
5+
// excluding modules HALL,CAVE,
6+
// sampled at 30000 z slices x 128 phi directions, margin 2% + 2 cm.
7+
// Steps outside the z range below are always kept.
8+
9+
o2::steer::O2MCApplicationBase::KeepStepFcn keepStep()
10+
{
11+
const float rSq[] = {50.0f * 50.0f, 176.0f * 176.0f, 437.0f * 437.0f, 51.0f * 51.0f, 420.0f * 420.0f, 48.0f * 48.0f, 378.0f * 378.0f, 576.0f * 576.0f, 330.0f * 330.0f, 899.0f * 899.0f, 97.0f * 97.0f, 50.0f * 50.0f};
12+
const float edges[] = {-15000.0f, -1890.0f, -1740.0f, -1690.0f, -1640.0f, -1390.0f, -1330.0f, -1200.0f, -830.0f, -720.0f, 720.0f, 1200.0f, 15000.0f};
13+
return [rSq, edges](TVirtualMC const* mc) {
14+
float x, y, z;
15+
mc->TrackPosition(x, y, z);
16+
if (z < edges[0] || z >= edges[12]) {
17+
return true; // beyond the traced region, e.g. the ZDC tunnel
18+
}
19+
for (auto i = 0U; i < 12; ++i) {
20+
if (edges[i + 1] > z && z >= edges[i]) {
21+
return (x * x + y * y) < rSq[i];
22+
}
23+
}
24+
return true;
25+
};
26+
}

Detectors/gconfig/src/MCReplayConfig.cxx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ void Config()
3939
replay->SetCut("CUTALLE", params.energyCut);
4040
replay->cutsFromConfig(params.cutFile);
4141
replay->blockSetProcessesCuts();
42+
replay->allowStopTrack(params.allowStopTrack);
4243
}
4344

4445
void MCReplayConfig()

Steer/include/Steer/O2MCApplicationBase.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,14 @@ namespace steer
3535
class O2MCApplicationBase : public FairMCApplication
3636
{
3737
public:
38-
O2MCApplicationBase() : FairMCApplication(), mCutParams(o2::conf::SimCutParams::Instance()) { initTrackRefHook(); }
38+
O2MCApplicationBase() : FairMCApplication(), mCutParams(o2::conf::SimCutParams::Instance())
39+
{
40+
initStepFilterHook();
41+
initTrackRefHook();
42+
}
3943
O2MCApplicationBase(const char* name, const char* title, TObjArray* ModList, const char* MatName) : FairMCApplication(name, title, ModList, MatName), mCutParams(o2::conf::SimCutParams::Instance())
4044
{
45+
initStepFilterHook();
4146
initTrackRefHook();
4247
}
4348

@@ -57,6 +62,7 @@ class O2MCApplicationBase : public FairMCApplication
5762
double TrackingZmax() const override { return mCutParams.maxAbsZTracking; }
5863

5964
typedef std::function<void(TVirtualMC const*)> TrackRefFcn;
65+
typedef std::function<bool(TVirtualMC const*)> KeepStepFcn;
6066

6167
void fixTGeoRuntimeShapes();
6268

@@ -77,6 +83,11 @@ class O2MCApplicationBase : public FairMCApplication
7783
void finishEventCommon();
7884
TrackRefFcn mTrackRefFcn; // a function hook that gets (optionally) called during Stepping
7985
void initTrackRefHook();
86+
/// an optional extra per-step criterion, loaded from
87+
/// SimCutParams.stepFilteringMacro; only consulted if mHasStepFilterMacro
88+
KeepStepFcn mKeepStepFcn;
89+
bool mHasStepFilterMacro = false;
90+
void initStepFilterHook();
8091

8192
ClassDefOverride(O2MCApplicationBase, 1);
8293
};

Steer/src/O2MCApplication.cxx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,14 @@ void O2MCApplicationBase::Stepping()
115115
}
116116
}
117117

118+
// an additional, user-provided criterion; only consulted when one is
119+
// configured, so that SimCutParams.stepFilteringMacro being unset leaves the
120+
// code above as the whole of the geometry cut
121+
if (mHasStepFilterMacro && !mKeepStepFcn(fMC)) {
122+
fMC->StopTrack();
123+
return;
124+
}
125+
118126
if (mCutParams.stepTrackRefHook) {
119127
mTrackRefFcn(fMC);
120128
}
@@ -239,6 +247,28 @@ void O2MCApplicationBase::ConstructGeometry()
239247
}
240248
}
241249

250+
void O2MCApplicationBase::initStepFilterHook()
251+
{
252+
if (mCutParams.stepFilteringMacro.empty()) {
253+
return;
254+
}
255+
const auto macro = o2::utils::expandShellVarsInFileName(mCutParams.stepFilteringMacro);
256+
if (!std::filesystem::exists(macro)) {
257+
LOG(error) << "Macro for step filtering does not exist at " << macro << "; ignoring it";
258+
return;
259+
}
260+
LOG(info) << "Initializing step filtering from macro " << macro;
261+
mKeepStepFcn = o2::conf::GetFromMacro<KeepStepFcn>(macro, "keepStep()",
262+
"o2::steer::O2MCApplicationBase::KeepStepFcn",
263+
"o2mc_stepping_keep_step");
264+
if (!mKeepStepFcn) {
265+
LOG(error) << "Could not set up keepStep() from " << macro << "; ignoring it";
266+
return;
267+
}
268+
mHasStepFilterMacro = true;
269+
LOG(info) << "Step filtering initialized from macro " << macro;
270+
}
271+
242272
void O2MCApplicationBase::InitGeometry()
243273
{
244274
// load special cuts which might be given from the outside first.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Geometry step filtering
2+
3+
Stops transport of a track once it steps outside a hull wrapped around the
4+
detector, and derives that hull from the geometry.
5+
6+
`run.sh` runs the whole chain: a reference simulation with the MCStepLogger, the
7+
derivation of a hull from its geometry, a check that every recorded hit lies
8+
inside that hull, and two MCReplay runs — one without cuts, one with the hull —
9+
whose step and hit counts can be compared.
10+
11+
| file | |
12+
|---|---|
13+
| `makeKeepStepCylinders.macro` | derives the hull and writes a `keepStep()` macro |
14+
| `checkKeepStepCylinders.macro` | verifies that no recorded hit lies outside a hull |
15+
| `countHits.macro` | hits per detector of an `o2-sim` output |
16+
17+
The hull is applied through `SimCutParams.stepFilteringMacro`, in addition to
18+
the built-in z/R cut, so it can only remove steps. A ready-made one is installed
19+
at `$O2_ROOT/share/Detectors/gconfig/KeepStepCylinders.macro`; regenerate it
20+
whenever the geometry changes.
21+
22+
`MCReplayParam.allowStopTrack=true` is what lets a replay act on the
23+
`StopTrack()` calls the hull makes.
24+
25+
Measured on 10 pp minimum-bias events, Pythia8 and Geant4, with the hull
26+
generated from the geometry of the run itself:
27+
28+
| module list | steps removed | hits lost |
29+
|---|---|---|
30+
| default minus ZDC | about 5 % | 0.18 % |
31+
| default | about 2 % | 0.20 % |
32+
33+
Hits exclude FT0, which draws a random number while creating hits and does not
34+
reproduce under replay.
35+
36+
**Read the replay's step accounting with care.** `MCReplayParam.allowStopTrack`
37+
makes a replay honour *every* `StopTrack()` of a replayed step, not only the
38+
ones the hull causes. FT0's photocathode efficiency and HMPID's Fresnel loss
39+
stop tracks on a random draw, and those draws do not repeat on replay, so a
40+
replay with `allowStopTrack=true` and a `keepStep()` that returns `true`
41+
already reports about 8 % of steps skipped with no cut in play; running it with
42+
`--skipModules ZDC FT0 HMP` reports exactly zero. Use the replay only as a
43+
difference against such a no-op macro, and expect that difference to still read
44+
a little high, because a secondary is dropped whenever its parent was skipped
45+
before it was born, which approximates rather than reproduces what Geant4 does.
46+
47+
The number to quote comes from a real Geant4 pair with `SimCutParams.trackSeed`
48+
on, summing the per-event `This event/chunk did N steps`. Without per-track
49+
seeding the two runs diverge into different physics and the comparison is
50+
worthless: on one sample it returned 0.19 % where the truth was 4.4 %.
51+
52+
Do not expect the step reduction to become a CPU reduction. Without ZDC,
53+
measured as user time over sequential runs on an idle machine with the same
54+
generator seed:
55+
56+
| run | Geant4 steps | CPU |
57+
|---|---|---|
58+
| no macro | 9 855 230 | 88.7 s |
59+
| macro returning `true` | 9 855 230 | 91.1 s |
60+
| the hull | 9 380 571 | 90.4 s |
61+
62+
Two things follow. Reaching a cling-compiled `keepStep()` through a
63+
`std::function` costs about 235 ns per step on its own -- the middle row removes
64+
no steps at all -- while the cylinder scan itself is negligible next to it. And
65+
the steps the hull removes are cheap ones: the ~5 % of steps it removes are
66+
worth only about 0.7 % of the runtime, so even at zero hook overhead the gain
67+
here would be small. The macro is compiled once at start-up, not per step.
68+
69+
Evaluating a cylinder set natively rather than through a macro would remove the
70+
overhead; whether a geometry hull is worth it without ZDC is a separate
71+
question.
72+
73+
Background: A. Swain, *Geometric Hyperparameter Optimisation of ALICE Monte
74+
Carlo Transport Simulations*, CERN-STUDENTS-Note-2023-164, and B. Völkel,
75+
*Geometry cuts in MC transport*, WP12/13 meeting, 11.10.2023
76+
(https://indico.cern.ch/event/1334852/contributions/5620122/).
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
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+
/// \file checkKeepStepCylinders.macro
13+
/// \brief Verify that a generated hull contains every recorded hit
14+
///
15+
/// A hull that excludes a position where a hit was produced will lose that hit
16+
/// in a real run. This reads the hull straight out of a generated
17+
/// KeepStepCylinders.macro and tests it against the hit positions of an
18+
/// o2-sim output, per detector. It is a necessary condition, cheap to run, and
19+
/// independent of the replay machinery.
20+
///
21+
/// root -l -b -q 'checkKeepStepCylinders.macro("KeepStepCylinders.macro", "o2sim")'
22+
23+
#include <TFile.h>
24+
#include <TTree.h>
25+
#include <TSystem.h>
26+
#include <TSystemDirectory.h>
27+
#include <TString.h>
28+
#include <TH1.h>
29+
#include <TObjArray.h>
30+
#include <TBranch.h>
31+
32+
#include <cstdio>
33+
#include <fstream>
34+
#include <regex>
35+
#include <string>
36+
#include <vector>
37+
38+
namespace
39+
{
40+
/// pull the rSq[] and edges[] initialisers out of a generated macro
41+
bool readHull(const char* macro, std::vector<double>& rad, std::vector<double>& edges)
42+
{
43+
std::ifstream in(macro);
44+
if (!in) {
45+
printf("[check] cannot open %s\n", macro);
46+
return false;
47+
}
48+
std::string all((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
49+
auto grab = [&all](const char* name, std::vector<double>& out, bool sqrtIt) {
50+
std::smatch m;
51+
std::regex e(std::string(name) + R"(\[\]\s*=\s*\{([^}]*)\})");
52+
if (!std::regex_search(all, m, e)) {
53+
return false;
54+
}
55+
std::string body = m[1].str();
56+
std::regex num(R"(-?[0-9]+(\.[0-9]+)?)");
57+
for (auto it = std::sregex_iterator(body.begin(), body.end(), num); it != std::sregex_iterator(); ++it) {
58+
out.push_back(std::stod(it->str()));
59+
}
60+
if (sqrtIt) { // rSq is written as "R.0f * R.0f", so every value appears twice
61+
std::vector<double> half;
62+
for (size_t i = 0; i < out.size(); i += 2) {
63+
half.push_back(out[i]);
64+
}
65+
out.swap(half);
66+
}
67+
return true;
68+
};
69+
return grab("rSq", rad, true) && grab("edges", edges, false);
70+
}
71+
} // namespace
72+
73+
void checkKeepStepCylinders(const char* macro = "KeepStepCylinders.macro",
74+
const char* prefix = "o2sim")
75+
{
76+
std::vector<double> rad, edges;
77+
if (!readHull(macro, rad, edges) || edges.size() != rad.size() + 1) {
78+
printf("[check] could not parse a hull out of %s\n", macro);
79+
return;
80+
}
81+
printf("[check] hull from %s:\n", macro);
82+
for (size_t k = 0; k < rad.size(); ++k) {
83+
printf(" z in [%8.1f, %8.1f) R < %6.1f\n", edges[k], edges[k + 1], rad[k]);
84+
}
85+
86+
auto inside = [&](double x, double y, double z) {
87+
if (z < edges.front() || z >= edges.back()) {
88+
return true;
89+
}
90+
for (size_t k = 0; k < rad.size(); ++k) {
91+
if (z >= edges[k] && z < edges[k + 1]) {
92+
return (x * x + y * y) < rad[k] * rad[k];
93+
}
94+
}
95+
return true;
96+
};
97+
98+
TString dir = gSystem->DirName(prefix);
99+
TString base = gSystem->BaseName(prefix);
100+
TSystemDirectory sdir(dir, dir);
101+
TList* files = sdir.GetListOfFiles();
102+
if (!files) {
103+
printf("[check] no such directory %s\n", dir.Data());
104+
return;
105+
}
106+
long long nTot = 0, nOut = 0;
107+
TIter next(files);
108+
while (auto* f = (TSystemFile*)next()) {
109+
TString name = f->GetName();
110+
if (!name.BeginsWith(base + "_Hits") || !name.EndsWith(".root")) {
111+
continue;
112+
}
113+
TString det = name;
114+
det.ReplaceAll(base + "_Hits", "");
115+
det.ReplaceAll(".root", "");
116+
TFile* file = TFile::Open(dir + "/" + name);
117+
TTree* t = file ? (TTree*)file->Get("o2sim") : nullptr;
118+
if (!t) {
119+
if (file) {
120+
file->Close();
121+
}
122+
continue;
123+
}
124+
// BasicXYZxHit and friends store the position as a ROOT::Math::PositionVector3D
125+
TString br;
126+
for (int i = 0; i < t->GetListOfBranches()->GetEntries(); ++i) {
127+
TString b = t->GetListOfBranches()->At(i)->GetName();
128+
if (b.Contains("Hit")) {
129+
br = b;
130+
break;
131+
}
132+
}
133+
long long n = 0, bad = 0;
134+
const TString pos = br + ".mPos.fCoordinates.f";
135+
if (!br.IsNull() && t->GetLeaf(pos + "X")) {
136+
const TString expr = Form("%sX:%sY:%sZ", pos.Data(), pos.Data(), pos.Data());
137+
// one hit is one value, not one entry: the value buffer has to be sized
138+
// to the number of selected values or GetV1..3 return truncated arrays
139+
Long64_t sel = t->Draw(expr, "", "goff");
140+
if (sel > t->GetEstimate()) {
141+
t->SetEstimate(sel + 1);
142+
sel = t->Draw(expr, "", "goff");
143+
}
144+
const double *vx = t->GetV1(), *vy = t->GetV2(), *vz = t->GetV3();
145+
for (Long64_t i = 0; i < sel; ++i) {
146+
++n;
147+
if (!inside(vx[i], vy[i], vz[i])) {
148+
++bad;
149+
}
150+
}
151+
}
152+
if (n > 0) {
153+
printf("[check] %-5s hits %10lld outside hull %8lld (%.3f%%)%s\n",
154+
det.Data(), n, bad, 100. * bad / n, bad ? " <-- HULL TOO SMALL" : "");
155+
nTot += n;
156+
nOut += bad;
157+
} else {
158+
printf("[check] %-5s no position leaf (%s), skipped\n", det.Data(), br.Data());
159+
}
160+
file->Close();
161+
}
162+
printf("[check] TOTAL hits %lld, outside hull %lld (%.4f%%)\n", nTot, nOut,
163+
nTot ? 100. * nOut / nTot : 0.);
164+
}

0 commit comments

Comments
 (0)