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
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
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