Project
Loading...
Searching...
No Matches
runOverlapCensus.cxx
Go to the documentation of this file.
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.
13
24
28
29#include "TGeoBBox.h"
30#include "TGeoManager.h"
31#include "TGeoMatrix.h"
32#include "TGeoNode.h"
33#include "TGeoTube.h"
34#include "TGeoVolume.h"
35#include "TFile.h"
36
37#include <nlohmann/json.hpp>
38
39#include <algorithm>
40#include <cmath>
41#include <cstdio>
42#include <cstdlib>
43#include <cstring>
44#include <fstream>
45#include <iostream>
46#include <string>
47#include <vector>
48
49using namespace o2::cad;
50
51namespace
52{
53
54struct Options {
55 std::string geometry;
56 std::string topVolume;
57 std::string json;
58 std::vector<std::string> injections;
60 bool rootCheck = false;
61 int rootNmesh = 0;
62 double rootOvlp = 0.001;
63 bool selfTest = false;
64 bool listPairs = false;
65};
66
67void usage(const char* argv0)
68{
69 std::cout
70 << "usage: " << argv0 << " --geometry <geom.root> [options]\n"
71 << " or: " << argv0 << " --self-test\n\n"
72 << " --geometry PATH ROOT file holding a TGeoManager (as written by geom.C's\n"
73 << " build_and_export, or by any other producer)\n"
74 << " --top NAME volume whose daughters are censused (default: the top volume)\n"
75 << " --points N boundary points sampled per solid (default 20000). Coverage only:\n"
76 << " every individual answer is exact, so this bounds false NEGATIVES\n"
77 << " --tol CM depth below which a containment is a shared face, not an overlap\n"
78 << " (default 1e-6)\n"
79 << " --residual CM a sampled point further than this from its own solid's boundary is\n"
80 << " discarded rather than used as evidence (default 1e-6)\n"
81 << " --pad CM bounding-box inflation for the pairwise rejection (default 0.1).\n"
82 << " Scopes which DISJOINT pairs get measured; never hides an overlap\n"
83 << " --volume-samples N Monte-Carlo estimate of the shared volume of each illegal pair\n"
84 << " --inject NAME:DX,DY,DZ translate a node by (dx,dy,dz) cm before the census. The\n"
85 << " positive control; may be repeated\n"
86 << " --root-check [N] also run TGeoManager::CheckOverlaps for comparison, optionally\n"
87 << " after SetNmeshPoints(N)\n"
88 << " --root-ovlp CM the tolerance handed to CheckOverlaps (default 0.001)\n"
89 << " --list-pairs print every tested pair, not only the illegal ones\n"
90 << " --json PATH write the census as JSON\n"
91 << " --self-test analytic controls, no geometry file needed; exits non-zero on any\n"
92 << " failure\n\n"
93 << "Exit code is the number of illegal pairs, capped at 250; 251 on a usage or load error.\n";
94}
95
96bool parseInjection(const std::string& spec, std::string& name, double shift[3])
97{
98 const auto colon = spec.rfind(':');
99 if (colon == std::string::npos) {
100 return false;
101 }
102 name = spec.substr(0, colon);
103 return std::sscanf(spec.c_str() + colon + 1, "%lf,%lf,%lf", &shift[0], &shift[1], &shift[2]) == 3;
104}
105
107bool injectShift(TGeoVolume* mother, const std::string& nodeName, const double shift[3])
108{
109 for (int index = 0; index < mother->GetNdaughters(); ++index) {
110 TGeoNode* node = mother->GetNode(index);
111 if (nodeName != node->GetVolume()->GetName() && nodeName != node->GetName()) {
112 continue;
113 }
114 auto* nodeWithMatrix = dynamic_cast<TGeoNodeMatrix*>(node);
115 if (nodeWithMatrix == nullptr) {
116 return false;
117 }
118 auto* replacement = new TGeoHMatrix(*node->GetMatrix());
119 const double* translation = replacement->GetTranslation();
120 replacement->SetDx(translation[0] + shift[0]);
121 replacement->SetDy(translation[1] + shift[1]);
122 replacement->SetDz(translation[2] + shift[2]);
123 replacement->RegisterYourself();
124 nodeWithMatrix->SetMatrix(replacement);
125 return true;
126 }
127 return false;
128}
129
130void printCensus(const OverlapCensus& census, bool listPairs)
131{
132 std::printf("\n%d placed solids -> %d pairs; %d survive the bounding-box rejection (%.2f %%)\n", census.nSolids,
133 census.nPairsTotal, census.nPairsTested,
134 census.nPairsTotal > 0 ? 100. * census.nPairsTested / census.nPairsTotal : 0.);
135 std::printf("disjoint %d | touching %d | INTERPENETRATING %d | contained %d | extruding %d (%.1f s)\n",
136 census.nDisjoint, census.nTouching, census.nInterpenetrating, census.nContained, census.nExtruding,
137 census.elapsedSeconds);
138 std::printf("points rejected as not on their own solid: %d; worst accepted residual %.3e cm\n",
139 census.nPointsRejected, census.worstResidualCm);
140
141 std::printf("\n%-28s %-10s %10s %10s %8s %8s %6s\n", "solid", "shape", "requested", "accepted", "rejected",
142 "residual", "onSeg");
143 for (const auto& solid : census.solids) {
144 std::printf("%-28s %-10s %10d %10d %8d %8.1e %6s\n", solid.name.c_str(),
145 solid.shapeClass.size() > 10 ? solid.shapeClass.substr(solid.shapeClass.size() - 10).c_str()
146 : solid.shapeClass.c_str(),
147 solid.requested, solid.accepted, solid.rejected, solid.worstResidualCm,
148 solid.usedPointsOnSegments ? "yes" : "no");
149 }
150
151 std::printf("\n%-46s %-17s %13s %8s %8s %13s\n", "pair", "verdict", "depth(cm)", "AinB", "BinA", "sep/vol");
152 for (const auto& pair : census.pairs) {
153 const bool illegal =
154 pair.verdict == OverlapVerdict::Interpenetrating || pair.verdict == OverlapVerdict::Contained;
155 if (!listPairs && !illegal) {
156 continue;
157 }
158 char label[128];
159 std::snprintf(label, sizeof(label), "%s | %s", pair.nameA.c_str(), pair.nameB.c_str());
160 char trailer[64] = "";
161 if (pair.sharedVolumeCm3 >= 0.) {
162 std::snprintf(trailer, sizeof(trailer), "V=%.4e", pair.sharedVolumeCm3);
163 } else if (pair.separationCm >= 0.) {
164 std::snprintf(trailer, sizeof(trailer), "gap=%.4e", pair.separationCm);
165 }
166 std::printf("%-46s %-17s %13.6e %8d %8d %13s\n", label, OverlapVerdictName(pair.verdict), pair.depthCm,
167 pair.deepPointsAInsideB, pair.deepPointsBInsideA, trailer);
168 }
169 for (const auto& pair : census.extrusions) {
170 std::printf("%-46s %-17s %13.6e %8d %8s %13s\n", (pair.nameA + " extrudes " + pair.nameB).c_str(), "EXTRUSION",
171 pair.depthCm, pair.deepPointsAInsideB, "-", "");
172 }
173}
174
175nlohmann::json censusToJson(const OverlapCensus& census)
176{
177 nlohmann::json out;
178 out["nSolids"] = census.nSolids;
179 out["nPairsTotal"] = census.nPairsTotal;
180 out["nPairsTested"] = census.nPairsTested;
181 out["nDisjoint"] = census.nDisjoint;
182 out["nTouching"] = census.nTouching;
183 out["nInterpenetrating"] = census.nInterpenetrating;
184 out["nContained"] = census.nContained;
185 out["nExtruding"] = census.nExtruding;
186 out["illegal"] = census.illegalCount();
187 out["nPointsRejected"] = census.nPointsRejected;
188 out["worstResidualCm"] = census.worstResidualCm;
189 out["elapsedSeconds"] = census.elapsedSeconds;
190 for (const auto& solid : census.solids) {
191 out["solids"].push_back({{"name", solid.name},
192 {"shape", solid.shapeClass},
193 {"requested", solid.requested},
194 {"accepted", solid.accepted},
195 {"rejected", solid.rejected},
196 {"worstResidualCm", solid.worstResidualCm},
197 {"usedPointsOnSegments", solid.usedPointsOnSegments}});
198 }
199 auto pairJson = [](const OverlapPair& pair) {
200 return nlohmann::json{{"a", pair.nameA},
201 {"b", pair.nameB},
202 {"verdict", OverlapVerdictName(pair.verdict)},
203 {"depthCm", pair.depthCm},
204 {"deepestPoint", pair.deepestPoint},
205 {"deepestPointFrom", pair.deepestPointFrom},
206 {"pointsAInsideB", pair.pointsAInsideB},
207 {"pointsBInsideA", pair.pointsBInsideA},
208 {"deepPointsAInsideB", pair.deepPointsAInsideB},
209 {"deepPointsBInsideA", pair.deepPointsBInsideA},
210 {"sampledA", pair.sampledA},
211 {"sampledB", pair.sampledB},
212 {"separationCm", pair.separationCm},
213 {"sharedVolumeCm3", pair.sharedVolumeCm3},
214 {"sharedVolumeErrCm3", pair.sharedVolumeErrCm3},
215 {"sharedVolumeHits", pair.sharedVolumeHits}};
216 };
217 for (const auto& pair : census.pairs) {
218 out["pairs"].push_back(pairJson(pair));
219 }
220 for (const auto& pair : census.extrusions) {
221 out["extrusions"].push_back(pairJson(pair));
222 }
223 return out;
224}
225
226// ---------------------------------------------------------------------------------------------
227// Self-test: the three populations, built from arithmetic, with the controls that make them mean
228// something. No geometry file, no build directory, no model.
229// ---------------------------------------------------------------------------------------------
230
231int gChecks = 0;
232int gFailures = 0;
233
234void check(bool condition, const std::string& what)
235{
236 gChecks++;
237 if (!condition) {
238 gFailures++;
239 std::printf(" FAIL %s\n", what.c_str());
240 } else {
241 std::printf(" ok %s\n", what.c_str());
242 }
243}
244
245TGeoVolume* makeWorld(const char* name)
246{
247 auto* manager = new TGeoManager(name, name);
248 auto* material = new TGeoMaterial("vac", 0., 0., 0.);
249 auto* medium = new TGeoMedium("vac", 1, material);
250 auto* world = manager->MakeBox("world", medium, 100., 100., 100.);
251 manager->SetTopVolume(world);
252 return world;
253}
254
257O2BVHSurfaceSolid* makeSurfaceBox(const char* name, double halfX, double halfY, double halfZ)
258{
259 auto* solid = new O2BVHSurfaceSolid(name);
260 const double faces[6][3] = {{1., 0., 0.}, {-1., 0., 0.}, {0., 1., 0.}, {0., -1., 0.}, {0., 0., 1.}, {0., 0., -1.}};
261 const double half[3] = {halfX, halfY, halfZ};
262 for (const auto& normal : faces) {
263 const int axis = (normal[0] != 0.) ? 0 : ((normal[1] != 0.) ? 1 : 2);
264 const int axisU = (axis + 1) % 3;
265 const int axisV = (axis + 2) % 3;
267 origin[axis] = normal[axis] * half[axis];
268 O2BVHSurfaceSolid::Point3D directionU{0., 0., 0.};
269 O2BVHSurfaceSolid::Point3D directionV{0., 0., 0.};
270 directionU[axisU] = 1.;
271 directionV[axisV] = 1.;
272 // Wind the quad so its normal points out of the box.
273 const double sign = normal[axis];
274 std::vector<O2BVHSurfaceSolid::Point2D> wire;
275 const double extentU = half[axisU];
276 const double extentV = half[axisV];
277 if (sign > 0) {
278 wire = {{-extentU, -extentV}, {extentU, -extentV}, {extentU, extentV}, {-extentU, extentV}};
279 } else {
280 wire = {{-extentU, -extentV}, {-extentU, extentV}, {extentU, extentV}, {extentU, -extentV}};
281 }
282 solid->AddPlanarSurface(origin, directionU, directionV, wire, {});
283 }
284 solid->CloseShape(false);
285 return solid;
286}
287
288int selfTest()
289{
290 std::printf("== o2-bench-cadsupport-overlap self-test ==\n");
291
292 OverlapOptions options;
293 options.pointsPerSolid = 4000;
294 options.checkExtrusion = false;
295
296 // --- 1. Two boxes sharing a face exactly: TOUCHING, and it must not be called an overlap. ---
297 {
298 TGeoVolume* world = makeWorld("touch");
299 auto* left = new TGeoVolume("left", makeSurfaceBox("leftBox", 1., 1., 1.), world->GetMedium());
300 auto* right = new TGeoVolume("right", makeSurfaceBox("rightBox", 1., 1., 1.), world->GetMedium());
301 world->AddNode(left, 1, new TGeoTranslation(-1., 0., 0.));
302 world->AddNode(right, 1, new TGeoTranslation(1., 0., 0.));
303 gGeoManager->CloseGeometry();
304 const OverlapCensus census = CheckWorldOverlaps(world, options);
305 check(census.nPairsTested == 1, "touching: the pair survives the box rejection");
306 check(census.nTouching == 1 && census.nInterpenetrating == 0,
307 "touching: a shared face is TOUCHING, not an overlap");
308 check(!census.pairs.empty() && census.pairs[0].pointsAInsideB + census.pairs[0].pointsBInsideA > 0,
309 "touching: the check was capable of firing (points ARE found inside)");
310 check(!census.pairs.empty() && census.pairs[0].depthCm <= options.depthTolerance,
311 "touching: the depth is at the tolerance, i.e. zero");
312 delete gGeoManager;
313 }
314
315 // --- 2. The same two boxes moved 0.2 cm into each other: INTERPENETRATING, at that depth. ---
316 {
317 TGeoVolume* world = makeWorld("overlap");
318 auto* left = new TGeoVolume("left", makeSurfaceBox("leftBox", 1., 1., 1.), world->GetMedium());
319 auto* right = new TGeoVolume("right", makeSurfaceBox("rightBox", 1., 1., 1.), world->GetMedium());
320 world->AddNode(left, 1, new TGeoTranslation(-1., 0., 0.));
321 world->AddNode(right, 1, new TGeoTranslation(0.8, 0., 0.));
322 gGeoManager->CloseGeometry();
323 OverlapOptions withVolume = options;
324 withVolume.volumeSamples = 200000;
325 const OverlapCensus census = CheckWorldOverlaps(world, withVolume);
326 check(census.nInterpenetrating == 1, "injected 0.2 cm: INTERPENETRATING");
327 const double depth = census.pairs.empty() ? 0. : census.pairs[0].depthCm;
328 check(std::abs(depth - 0.2) < 1e-9,
329 "injected 0.2 cm: the depth is the injected displacement (" + std::to_string(depth) + ")");
330 const double volume = census.pairs.empty() ? -1. : census.pairs[0].sharedVolumeCm3;
331 check(std::abs(volume - 0.8) < 0.02,
332 "injected 0.2 cm: shared volume 0.2 x 2 x 2 = 0.8 cm3 (" + std::to_string(volume) + ")");
333 delete gGeoManager;
334 }
335
336 // --- 3. The negative control: the same two boxes 0.2 cm APART must not fire. ---
337 {
338 TGeoVolume* world = makeWorld("gap");
339 auto* left = new TGeoVolume("left", makeSurfaceBox("leftBox", 1., 1., 1.), world->GetMedium());
340 auto* right = new TGeoVolume("right", makeSurfaceBox("rightBox", 1., 1., 1.), world->GetMedium());
341 world->AddNode(left, 1, new TGeoTranslation(-1., 0., 0.));
342 world->AddNode(right, 1, new TGeoTranslation(1.2, 0., 0.));
343 gGeoManager->CloseGeometry();
344 const OverlapCensus census = CheckWorldOverlaps(world, options);
345 check(census.nDisjoint == 1 && census.illegalCount() == 0, "0.2 cm gap: disjoint, nothing flagged");
346 const double separation = census.pairs.empty() ? -1. : census.pairs[0].separationCm;
347 check(std::abs(separation - 0.2) < 1e-9,
348 "0.2 cm gap: the separation is recovered (" + std::to_string(separation) + ")");
349 delete gGeoManager;
350 }
351
352 // --- 4. A tenth of a micron: the tolerance is a decision, and it is measured, not assumed. ---
353 {
354 TGeoVolume* world = makeWorld("thin");
355 auto* left = new TGeoVolume("left", makeSurfaceBox("leftBox", 1., 1., 1.), world->GetMedium());
356 auto* right = new TGeoVolume("right", makeSurfaceBox("rightBox", 1., 1., 1.), world->GetMedium());
357 world->AddNode(left, 1, new TGeoTranslation(-1., 0., 0.));
358 world->AddNode(right, 1, new TGeoTranslation(1. - 1e-5, 0., 0.));
359 gGeoManager->CloseGeometry();
360 const OverlapCensus loose = CheckWorldOverlaps(world, options);
361 check(loose.nInterpenetrating == 1, "1e-5 cm interpenetration is resolved at the default 1e-6 tolerance");
362 OverlapOptions coarse = options;
363 coarse.depthTolerance = 1e-4;
364 const OverlapCensus blunted = CheckWorldOverlaps(world, coarse);
365 check(blunted.nInterpenetrating == 0 && blunted.nTouching == 1,
366 "CONTROL: at a 1e-4 tolerance the same 1e-5 interpenetration reads as touching");
367 delete gGeoManager;
368 }
369
370 // --- 5. Containment, which is legal only as a declared mother/daughter. ---
371 {
372 TGeoVolume* world = makeWorld("nested");
373 auto* outer = new TGeoVolume("outer", makeSurfaceBox("outerBox", 3., 3., 3.), world->GetMedium());
374 auto* inner = new TGeoVolume("inner", makeSurfaceBox("innerBox", 1., 1., 1.), world->GetMedium());
375 world->AddNode(outer, 1, new TGeoTranslation(0., 0., 0.));
376 world->AddNode(inner, 1, new TGeoTranslation(0., 0., 0.));
377 gGeoManager->CloseGeometry();
378 const OverlapCensus census = CheckWorldOverlaps(world, options);
379 check(census.nContained == 1, "a solid wholly inside another is CONTAINED, not merely overlapping");
380 delete gGeoManager;
381 }
382
383 // --- 6. A curved contact: a press fit exact in the model must not read as an overlap. This is
384 // the case ROOT's checker got wrong, and the sagitta of its 24-gon is 8.6e-3 cm. ---
385 {
386 TGeoVolume* world = makeWorld("press");
387 auto* pin = new TGeoVolume("pin", new TGeoTube("pinTube", 0., 1., 5.), world->GetMedium());
388 auto* sleeve = new TGeoVolume("sleeve", new TGeoTube("sleeveTube", 1., 2., 5.), world->GetMedium());
389 world->AddNode(pin, 1, new TGeoTranslation(0., 0., 0.));
390 world->AddNode(sleeve, 1, new TGeoTranslation(0., 0., 0.));
391 gGeoManager->CloseGeometry();
392 const OverlapCensus census = CheckWorldOverlaps(world, options);
393 check(census.nInterpenetrating == 0 && census.nTouching == 1,
394 "an exact press fit on a cylinder is TOUCHING, not an 8.6e-3 cm overlap");
395 const double depth = census.pairs.empty() ? -1. : census.pairs[0].depthCm;
396 check(depth < 1e-6, "press fit: depth " + std::to_string(depth) + " is below the 24-gon sagitta 8.6e-3 by 4 decades");
397 delete gGeoManager;
398 }
399
400 // --- 7. The residual filter: a point that is not on its own solid is not evidence. ---
401 {
402 auto* box = new TGeoBBox("residualBox", 1., 1., 1.);
403 std::vector<double> points;
404 int rejected = 0;
405 double worst = 0.;
406 const int accepted = SampleBoundaryPoints(box, 4000, 1e-6, points, rejected, worst);
407 check(accepted > 0 && rejected == 0, "TGeoBBox: every sampled point is on the box");
408 check(worst < 1e-9, "TGeoBBox: worst accepted residual " + std::to_string(worst) + " is at round-off");
409 }
410
411 // --- 8. The sampling contract on the shape this branch ships. ---
412 {
413 auto* solid = makeSurfaceBox("contractBox", 1., 2., 3.);
414 const int meshVertices = solid->GetNmeshVertices();
415 std::vector<double> buffer(3 * (meshVertices + 5000), -1.2345e33);
416 check(solid->GetPointsOnSegments(meshVertices + 5000, buffer.data()),
417 "GetPointsOnSegments fills the buffer when asked for more than the mesh");
418 int unfilled = 0;
419 double worst = 0.;
420 for (int index = 0; index < meshVertices + 5000; ++index) {
421 if (buffer[3 * index] == -1.2345e33) {
422 unfilled++;
423 continue;
424 }
425 worst = std::max(worst, solid->Safety(&buffer[3 * index], solid->Contains(&buffer[3 * index])));
426 }
427 check(unfilled == 0, "GetPointsOnSegments leaves no slot unwritten");
429 "every generated point is on the solid (worst " + std::to_string(worst) + ")");
430 check(!solid->GetPointsOnSegments(meshVertices - 1, buffer.data()),
431 "below the mesh size it declines, so ROOT falls back to the full exact vertex set");
432 delete solid;
433 }
434
435 std::printf("\n%d checks, %d failures\n", gChecks, gFailures);
436 return gFailures == 0 ? 0 : 1;
437}
438
439} // namespace
440
441int main(int argc, char** argv)
442{
443 Options options;
444 for (int index = 1; index < argc; ++index) {
445 const std::string argument = argv[index];
446 auto next = [&](const char* what) -> std::string {
447 if (index + 1 >= argc) {
448 std::cerr << "error: " << what << " needs a value\n";
449 std::exit(251);
450 }
451 return argv[++index];
452 };
453 if (argument == "-h" || argument == "--help") {
454 usage(argv[0]);
455 return 0;
456 } else if (argument == "--geometry") {
457 options.geometry = next("--geometry");
458 } else if (argument == "--top") {
459 options.topVolume = next("--top");
460 } else if (argument == "--points") {
461 options.check.pointsPerSolid = std::atoi(next("--points").c_str());
462 } else if (argument == "--tol") {
463 options.check.depthTolerance = std::atof(next("--tol").c_str());
464 } else if (argument == "--residual") {
465 options.check.residualTolerance = std::atof(next("--residual").c_str());
466 } else if (argument == "--pad") {
467 options.check.padCm = std::atof(next("--pad").c_str());
468 } else if (argument == "--volume-samples") {
469 options.check.volumeSamples = std::atoi(next("--volume-samples").c_str());
470 } else if (argument == "--inject") {
471 options.injections.push_back(next("--inject"));
472 } else if (argument == "--root-check") {
473 options.rootCheck = true;
474 if (index + 1 < argc && argv[index + 1][0] != '-') {
475 options.rootNmesh = std::atoi(argv[++index]);
476 }
477 } else if (argument == "--root-ovlp") {
478 options.rootOvlp = std::atof(next("--root-ovlp").c_str());
479 } else if (argument == "--list-pairs") {
480 options.listPairs = true;
481 } else if (argument == "--json") {
482 options.json = next("--json");
483 } else if (argument == "--self-test") {
484 options.selfTest = true;
485 } else {
486 std::cerr << "error: unknown argument " << argument << "\n";
487 usage(argv[0]);
488 return 251;
489 }
490 }
491
492 if (options.selfTest) {
493 return selfTest();
494 }
495 if (options.geometry.empty()) {
496 usage(argv[0]);
497 return 251;
498 }
499
500 TGeoManager::Import(options.geometry.c_str());
501 if (gGeoManager == nullptr) {
502 std::cerr << "error: no TGeoManager in " << options.geometry << "\n";
503 return 251;
504 }
505 TGeoVolume* top = options.topVolume.empty() ? gGeoManager->GetTopVolume()
506 : gGeoManager->GetVolume(options.topVolume.c_str());
507 if (top == nullptr) {
508 std::cerr << "error: no such volume: " << options.topVolume << "\n";
509 return 251;
510 }
511
512 for (const auto& specification : options.injections) {
513 std::string name;
514 double shift[3] = {0., 0., 0.};
515 if (!parseInjection(specification, name, shift)) {
516 std::cerr << "error: cannot parse --inject " << specification << " (expected NAME:DX,DY,DZ)\n";
517 return 251;
518 }
519 if (!injectShift(top, name, shift)) {
520 std::cerr << "error: --inject names no daughter of " << top->GetName() << ": " << name << "\n";
521 return 251;
522 }
523 std::printf("# injected: %s by (%g, %g, %g) cm\n", name.c_str(), shift[0], shift[1], shift[2]);
524 }
525
526 std::printf("# geometry %s, top volume %s, %d points per solid, depth tolerance %g cm, pad %g cm\n",
527 options.geometry.c_str(), top->GetName(), options.check.pointsPerSolid, options.check.depthTolerance,
528 options.check.padCm);
529
530 const OverlapCensus census = CheckWorldOverlaps(top, options.check);
531 printCensus(census, options.listPairs);
532
533 if (options.rootCheck) {
534 std::printf("\n== TGeoManager::CheckOverlaps, for comparison (nmesh %s, ovlp %g) ==\n",
535 options.rootNmesh > 0 ? std::to_string(options.rootNmesh).c_str() : "default", options.rootOvlp);
536 gGeoManager->GetGeomPainter();
537 if (options.rootNmesh > 0) {
538 gGeoManager->SetNmeshPoints(options.rootNmesh);
539 }
540 gGeoManager->CheckOverlaps(options.rootOvlp);
541 gGeoManager->PrintOverlaps();
542 }
543
544 if (!options.json.empty()) {
545 nlohmann::json out = censusToJson(census);
546 out["geometry"] = options.geometry;
547 out["top"] = top->GetName();
548 out["options"] = {{"pointsPerSolid", options.check.pointsPerSolid},
549 {"depthToleranceCm", options.check.depthTolerance},
550 {"residualToleranceCm", options.check.residualTolerance},
551 {"padCm", options.check.padCm},
552 {"volumeSamples", options.check.volumeSamples}};
553 out["injections"] = options.injections;
554 std::ofstream stream(options.json);
555 stream << out.dump(1) << "\n";
556 std::printf("\nwrote %s\n", options.json.c_str());
557 }
558
559 return std::min(census.illegalCount(), 250);
560}
header::DataOrigin origin
std::unique_ptr< expressions::Node > node
Timeframe settings Average distance in cm of events merged into Randomize distance around average distance of merged events Simulate correct bunch interactions instead of placing only the average number of events A value[n] sets TFlen for[n] collisions in Number of bunches per trainsort Number of bunch trains Instantaneous interaction rate Do not simulate border Total number of collisions to be placed in the interior of all time Do not select random but walk over array of events in stride steps h Event generator settings h configStandalone Use CPU for GPU Force usage of the specified GPU device no CPU fallback Continue processing after an error First event to Number of events to Number of iterations to Number of iterations to Number of initial iterations excluded from const char e
std::array< double, 3 > Point3D
static constexpr double kSurfacePointTolerance
GLuint buffer
Definition glcorearb.h:655
GLuint index
Definition glcorearb.h:781
GLdouble GLdouble GLdouble GLdouble top
Definition glcorearb.h:4077
GLuint const GLchar * name
Definition glcorearb.h:781
GLdouble GLdouble right
Definition glcorearb.h:4077
GLint left
Definition glcorearb.h:1979
GLuint GLsizei const GLchar * label
Definition glcorearb.h:2519
GLint GLint GLsizei GLsizei GLsizei depth
Definition glcorearb.h:470
GLuint GLuint stream
Definition glcorearb.h:1806
GLsizei const GLint * box
Definition glcorearb.h:4697
GLsizeiptr const void GLenum usage
Definition glcorearb.h:659
OverlapCensus CheckWorldOverlaps(const TGeoVolume *volume, const OverlapOptions &options=OverlapOptions())
Census every pair of volume's immediate daughters, and optionally each daughter against volume.
const char * OverlapVerdictName(OverlapVerdict verdict)
int SampleBoundaryPoints(const TGeoShape *shape, int npoints, double residualTolerance, std::vector< double > &points, int &rejected, double &worstResidual, bool *usedPointsOnSegments=nullptr)
void check(const std::vector< std::string > &arguments, const std::vector< ConfigParamSpec > &workflowOptions, const std::vector< DeviceSpec > &deviceSpecs, CheckMatrix &matrix)
std::string to_string(gsl::span< T, Size > span)
Definition common.h:52
nlohmann::json json
int nPairsTotal
N (N - 1) / 2.
int illegalCount() const
The one-line answer: nInterpenetrating + nContained + nExtruding.
int nPairsTested
after the bounding-box rejection
std::vector< OverlapPair > pairs
only the pairs that survived the bounding-box rejection
double depthTolerance
A containment shallower than this is a shared boundary, not an overlap. In cm.
int volumeSamples
Monte-Carlo samples for the shared volume of an illegal pair; 0, the default, disables the estimate.
One pair of placed solids, and everything measured about it.
#define main