Project
Loading...
Searching...
No Matches
O2SolidHarness.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
16
17#include "TClass.h"
18#include "TFile.h"
19#include "TGeoMatrix.h"
20#include "TKey.h"
21
22#include <algorithm>
23#include <chrono>
24#include <cmath>
25#include <cstring>
26#include <memory>
27#include <random>
28
29namespace o2
30{
31namespace cad
32{
33namespace harness
34{
35
36namespace
37{
38
39// iact = 3, the convention O2Tessellated documents, for every shape.
40constexpr Int_t kIact = 3;
41
42Point3D add(const Point3D& a, const Point3D& b) { return {a[0] + b[0], a[1] + b[1], a[2] + b[2]}; }
43Point3D sub(const Point3D& a, const Point3D& b) { return {a[0] - b[0], a[1] - b[1], a[2] - b[2]}; }
44Point3D scale(const Point3D& a, double s) { return {a[0] * s, a[1] * s, a[2] * s}; }
45double normSq(const Point3D& a) { return a[0] * a[0] + a[1] * a[1] + a[2] * a[2]; }
46
47Point3D sampleUniform(std::mt19937_64& rng, const Point3D& lo, const Point3D& hi)
48{
49 std::uniform_real_distribution<double> ux(lo[0], hi[0]);
50 std::uniform_real_distribution<double> uy(lo[1], hi[1]);
51 std::uniform_real_distribution<double> uz(lo[2], hi[2]);
52 return {ux(rng), uy(rng), uz(rng)};
53}
54
55Point3D isotropicDir(std::mt19937_64& rng)
56{
57 std::uniform_real_distribution<double> uCos(-1., 1.);
58 std::uniform_real_distribution<double> uPhi(0., 2. * M_PI);
59 const double cosTheta = uCos(rng);
60 const double sinTheta = std::sqrt(std::max(0., 1. - cosTheta * cosTheta));
61 const double phi = uPhi(rng);
62 return {sinTheta * std::cos(phi), sinTheta * std::sin(phi), cosTheta};
63}
64
65bool isBig(double d) { return d >= 0.9 * TGeoShape::Big(); }
66
67} // namespace
68
69namespace detail
70{
71uint64_t mixDouble(uint64_t acc, double value)
72{
73 uint64_t bits = 0;
74 std::memcpy(&bits, &value, sizeof(bits));
75 bits += 0x9e3779b97f4a7c15ULL + (acc << 6) + (acc >> 2);
76 return acc ^ bits;
77}
78} // namespace detail
79
80SampleSet generateSamples(const TGeoShape* reference, const Point3D& bboxMin, const Point3D& bboxMax,
81 const SampleConfig& cfg)
82{
83 SampleSet out;
84 out.bboxMin = bboxMin;
85 out.bboxMax = bboxMax;
86
87 const Point3D center = scale(add(bboxMin, bboxMax), 0.5);
88 const Point3D halfExtent = scale(sub(bboxMax, bboxMin), 0.5);
89 const Point3D inflatedLo = sub(center, scale(halfExtent, 1. + cfg.bboxInflate));
90 const Point3D inflatedHi = add(center, scale(halfExtent, 1. + cfg.bboxInflate));
91
92 double band = cfg.boundaryBand;
93 if (band < 0.) {
94 const double diag = std::sqrt(normSq(sub(bboxMax, bboxMin)));
95 band = 1.e-3 * diag;
96 }
97
98 std::mt19937_64 rng(cfg.seed);
99
100 out.bulkPoints.reserve(cfg.nBulk);
101 for (int i = 0; i < cfg.nBulk; ++i) {
102 out.bulkPoints.push_back(sampleUniform(rng, inflatedLo, inflatedHi));
103 }
104
105 out.boundaryPoints.reserve(cfg.nBoundary);
106 {
107 const long long budget = static_cast<long long>(cfg.nBoundary) * cfg.maxRejectionAttempts;
108 long long attempts = 0;
109 while (static_cast<int>(out.boundaryPoints.size()) < cfg.nBoundary && attempts < budget) {
110 ++attempts;
111 const Point3D p = sampleUniform(rng, bboxMin, bboxMax);
112 const bool in = reference->Contains(p.data());
113 const double s = reference->Safety(p.data(), in);
114 if (s < band) {
115 out.boundaryPoints.push_back(p);
116 }
117 }
118 }
119
120 out.insidePoints.reserve(cfg.nInside);
121 {
122 const long long budget = static_cast<long long>(cfg.nInside) * cfg.maxRejectionAttempts;
123 long long attempts = 0;
124 while (static_cast<int>(out.insidePoints.size()) < cfg.nInside && attempts < budget) {
125 ++attempts;
126 const Point3D p = sampleUniform(rng, bboxMin, bboxMax);
127 if (reference->Contains(p.data())) {
128 out.insidePoints.push_back(p);
129 }
130 }
131 }
132
133 out.outsideRays.reserve(cfg.nOutsideRays);
134 {
135 std::uniform_real_distribution<double> u01(0., 1.);
136 const long long budget = static_cast<long long>(cfg.nOutsideRays) * cfg.maxRejectionAttempts;
137 long long attempts = 0;
138 while (static_cast<int>(out.outsideRays.size()) < cfg.nOutsideRays && attempts < budget) {
139 ++attempts;
140 const Point3D origin = sampleUniform(rng, inflatedLo, inflatedHi);
141 if (reference->Contains(origin.data())) {
142 continue;
143 }
144 Point3D dir;
145 if (u01(rng) < cfg.aimedRayFraction) {
146 Point3D target = sampleUniform(rng, bboxMin, bboxMax);
147 Point3D delta = sub(target, origin);
148 double len = std::sqrt(normSq(delta));
149 if (len < 1.e-12) {
150 dir = isotropicDir(rng);
151 } else {
152 dir = scale(delta, 1. / len);
153 }
154 } else {
155 dir = isotropicDir(rng);
156 }
157 out.outsideRays.push_back({origin, dir});
158 }
159 }
160
161 out.insideRays.reserve(cfg.nInsideRays);
162 {
163 const long long budget = static_cast<long long>(cfg.nInsideRays) * cfg.maxRejectionAttempts;
164 long long attempts = 0;
165 while (static_cast<int>(out.insideRays.size()) < cfg.nInsideRays && attempts < budget) {
166 ++attempts;
167 const Point3D origin = sampleUniform(rng, bboxMin, bboxMax);
168 if (!reference->Contains(origin.data())) {
169 continue;
170 }
171 out.insideRays.push_back({origin, isotropicDir(rng)});
172 }
173 }
174
175 return out;
176}
177
178// ---- Validation ----------------------------------------------------------------------------------
179
180namespace
181{
182
183enum class MismatchClass { WithinBand,
184 MissedSurface,
185 Unexplained };
186
187void recordOffender(ValidationResult& result, const ValidationOptions& opt, Offender&& off,
188 MismatchClass mismatchClass)
189{
190 switch (mismatchClass) {
191 case MismatchClass::WithinBand:
192 ++result.nMismatchWithinBand;
193 break;
194 case MismatchClass::MissedSurface:
195 ++result.nMismatchMissedSurface;
196 break;
197 case MismatchClass::Unexplained:
198 ++result.nMismatchUnexplained;
199 break;
200 }
201 result.worstDeviation = std::max(result.worstDeviation, std::fabs(off.deviation));
202 result.worstOffenders.push_back(std::move(off));
203 std::sort(result.worstOffenders.begin(), result.worstOffenders.end(),
204 [](const Offender& a, const Offender& b) { return std::fabs(a.deviation) > std::fabs(b.deviation); });
205 if (result.worstOffenders.size() > opt.maxOffenders) {
206 result.worstOffenders.resize(opt.maxOffenders);
207 }
208}
209
211double allowedCrossingShift(const TGeoShape* normalSource, const Point3D& probePoint,
212 const Point3D& dir, const ValidationOptions& opt, double& cosIncidence)
213{
214 cosIncidence = 1.;
215 if (normalSource != nullptr) {
216 double normal[3] = {0., 0., 0.};
217 normalSource->ComputeNormal(probePoint.data(), dir.data(), normal);
218 const double normalNorm =
219 std::sqrt(normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2]);
220 if (normalNorm > 0.) {
221 const double dotProduct =
222 (normal[0] * dir[0] + normal[1] * dir[1] + normal[2] * dir[2]) / normalNorm;
223 cosIncidence = std::fabs(dotProduct);
224 }
225 }
226 const double effectiveCosine = std::max(cosIncidence, opt.minIncidenceCosine);
227 return std::max(opt.distanceTolerance, opt.meshBand / effectiveCosine);
228}
229
232MismatchClass classifyDistanceMismatch(const TGeoShape* reference, const Ray& ray, double dc,
233 double dr, bool dcBig, bool drBig,
234 const ValidationOptions& opt, double& cosIncidence)
235{
236 cosIncidence = 1.;
237 // One side found a crossing where the other found none. No amount of surface uncertainty
238 // explains a missing wall, so this can never be counted as "within band".
239 if (dcBig != drBig) {
240 return MismatchClass::MissedSurface;
241 }
242 const Point3D probePoint = add(ray.origin, scale(ray.dir, dr));
243 const double allowed = allowedCrossingShift(reference, probePoint, ray.dir, opt, cosIncidence);
244 return std::fabs(dc - dr) <= allowed ? MismatchClass::WithinBand : MismatchClass::Unexplained;
245}
246
247} // namespace
248
249ValidationResult validateContains(const TGeoShape* candidate, const TGeoShape* reference,
250 const std::vector<Point3D>& points, const ValidationOptions& opt)
251{
253 result.nSamples = points.size();
254 for (const auto& p : points) {
255 const bool bc = candidate->Contains(p.data());
256 const bool br = reference->Contains(p.data());
257 if (bc == br) {
258 ++result.nAgree;
259 continue;
260 }
261 const double refSafety = reference->Safety(p.data(), br);
262 Offender off;
263 off.point = p;
264 off.candidateValue = bc ? 1. : 0.;
265 off.referenceValue = br ? 1. : 0.;
266 off.deviation = refSafety; // rank Contains mismatches by how deep into the "unambiguous" region they are
267 off.referenceSafety = refSafety;
268 // A point closer to the reference surface than the reference's own positional uncertainty
269 // genuinely has no defined reference answer; further out, the reference is authoritative.
270 recordOffender(result, opt, std::move(off),
271 refSafety < opt.meshBand ? MismatchClass::WithinBand : MismatchClass::Unexplained);
272 }
273 return result;
274}
275
276namespace
277{
279ValidationResult validateDistance(const TGeoShape* candidate, const TGeoShape* reference,
280 const std::vector<Ray>& rays, const ValidationOptions& opt, bool inside)
281{
282 const auto distance = [&](const TGeoShape* shape, const Ray& r) {
283 return inside ? shape->DistFromInside(r.origin.data(), r.dir.data(), kIact, opt.stepmax)
284 : shape->DistFromOutside(r.origin.data(), r.dir.data(), kIact, opt.stepmax);
285 };
286 ValidationResult result;
287 result.nSamples = rays.size();
288 for (const auto& r : rays) {
289 const double dc = distance(candidate, r);
290 const double dr = distance(reference, r);
291 const bool dcBig = isBig(dc);
292 const bool drBig = isBig(dr);
293 if (dcBig && drBig) {
294 ++result.nAgree;
295 continue;
296 }
297 if (!dcBig && !drBig && std::fabs(dc - dr) <= opt.distanceTolerance) {
298 ++result.nAgree;
299 continue;
300 }
301 double cosIncidence = 1.;
302 const MismatchClass mismatchClass =
303 classifyDistanceMismatch(reference, r, dc, dr, dcBig, drBig, opt, cosIncidence);
304 Offender off;
305 off.point = r.origin;
306 off.dir = r.dir;
307 off.candidateValue = dcBig ? opt.stepmax : dc;
308 off.referenceValue = drBig ? opt.stepmax : dr;
309 off.deviation = off.candidateValue - off.referenceValue;
310 off.incidenceCosine = cosIncidence;
311 recordOffender(result, opt, std::move(off), mismatchClass);
312 }
313 return result;
314}
315} // namespace
316
317ValidationResult validateDistFromOutside(const TGeoShape* candidate, const TGeoShape* reference,
318 const std::vector<Ray>& rays, const ValidationOptions& opt)
319{
320 return validateDistance(candidate, reference, rays, opt, false);
321}
322
323ValidationResult validateDistFromInside(const TGeoShape* candidate, const TGeoShape* reference,
324 const std::vector<Ray>& rays, const ValidationOptions& opt)
325{
326 return validateDistance(candidate, reference, rays, opt, true);
327}
328
329ValidationResult validateSafety(const TGeoShape* shape, const std::vector<Point3D>& points,
330 const ValidationOptions& opt)
331{
332 static const std::array<Point3D, 6> kProbeDirs = {
333 Point3D{1., 0., 0.}, Point3D{-1., 0., 0.}, Point3D{0., 1., 0.},
334 Point3D{0., -1., 0.}, Point3D{0., 0., 1.}, Point3D{0., 0., -1.}};
335
337 result.nSamples = points.size();
338 for (const auto& p : points) {
339 const bool in = shape->Contains(p.data());
340 const double s = shape->Safety(p.data(), in);
341
342 double minProbed = TGeoShape::Big();
343 for (const auto& d : kProbeDirs) {
344 const double dist = in ? shape->DistFromInside(p.data(), d.data(), kIact, opt.stepmax)
345 : shape->DistFromOutside(p.data(), d.data(), kIact, opt.stepmax);
346 minProbed = std::min(minProbed, isBig(dist) ? opt.stepmax : dist);
347 }
348
349 const bool violatesLowerBound = s < -opt.distanceTolerance;
350 const bool violatesUpperBound = s > minProbed + opt.distanceTolerance;
351 if (!violatesLowerBound && !violatesUpperBound) {
352 ++result.nAgree;
353 continue;
354 }
355 Offender off;
356 off.point = p;
357 off.candidateValue = s;
358 off.referenceValue = minProbed;
359 off.deviation = s - minProbed;
360 off.referenceSafety = s;
361 recordOffender(result, opt, std::move(off), MismatchClass::Unexplained);
362 }
363 return result;
364}
365
366// ---- Validation against an external oracle ---------------------------------------------------------
367
368namespace
369{
371constexpr double kUnknownDistance = -1.;
372
373double oracleDistanceAt(const std::vector<double>& distances, size_t index)
374{
375 return index < distances.size() ? distances[index] : kUnknownDistance;
376}
377} // namespace
378
380 const std::vector<Point3D>& points,
381 const std::vector<int>& oracleState,
382 const std::vector<double>& oracleBoundaryDistance,
383 const ValidationOptions& opt)
384{
386 result.nSamples = points.size();
387 for (size_t index = 0; index < points.size(); ++index) {
388 const int state = index < oracleState.size() ? oracleState[index] : -1;
389 const double boundaryDistance = oracleDistanceAt(oracleBoundaryDistance, index);
390 // the oracle abstains on the boundary or within the model tolerance of it
391 if (state < 0 || (boundaryDistance >= 0. && boundaryDistance < opt.meshBand)) {
392 ++result.nNoVerdict;
393 continue;
394 }
395 const bool candidateInside = candidate->Contains(points[index].data());
396 if (candidateInside == (state == 1)) {
397 ++result.nAgree;
398 continue;
399 }
400 Offender off;
401 off.point = points[index];
402 off.candidateValue = candidateInside ? 1. : 0.;
403 off.referenceValue = state == 1 ? 1. : 0.;
404 // Rank by how far into unambiguous territory the disagreement sits: a wrong answer 1 cm from
405 // any surface is a different animal from one 1 um away.
406 off.deviation = boundaryDistance >= 0. ? boundaryDistance : 0.;
407 off.referenceSafety = boundaryDistance;
408 recordOffender(result, opt, std::move(off), MismatchClass::Unexplained);
409 }
410 return result;
411}
412
414 const std::vector<Ray>& rays,
415 const std::vector<double>& oracleDistance,
416 bool wantInside, const ValidationOptions& opt,
417 const std::vector<int>& oracleOriginState)
418{
420 result.nSamples = rays.size();
421 for (size_t index = 0; index < rays.size(); ++index) {
422 if (index >= oracleDistance.size()) {
423 ++result.nNoVerdict;
424 continue;
425 }
426 // the oracle's own origin classification decides which entry point is defined here
427 bool askInside = wantInside;
428 if (index < oracleOriginState.size()) {
429 const int state = oracleOriginState[index];
430 if (state < 0) {
431 ++result.nNoVerdict; // origin ON the boundary: neither entry point is defined
432 continue;
433 }
434 askInside = state == 1;
435 if (askInside != wantInside) {
436 ++result.nRelabelled;
437 }
438 }
439 const auto& ray = rays[index];
440 const double dc = askInside
441 ? candidate->DistFromInside(ray.origin.data(), ray.dir.data(), kIact, opt.stepmax)
442 : candidate->DistFromOutside(ray.origin.data(), ray.dir.data(), kIact, opt.stepmax);
443 const double dr = oracleDistance[index];
444 const bool dcBig = isBig(dc);
445 const bool drBig = isBig(dr);
446 if (dcBig && drBig) {
447 ++result.nAgree;
448 continue;
449 }
450 if (!dcBig && !drBig && std::fabs(dc - dr) <= opt.distanceTolerance) {
451 ++result.nAgree;
452 continue;
453 }
454 // no reference shape to take a normal from: the strict perpendicular allowance applies
455 double cosIncidence = 1.;
456 const MismatchClass mismatchClass =
457 classifyDistanceMismatch(nullptr, ray, dc, dr, dcBig, drBig, opt, cosIncidence);
458 Offender off;
459 off.point = ray.origin;
460 off.dir = ray.dir;
461 off.candidateValue = dcBig ? opt.stepmax : dc;
462 off.referenceValue = drBig ? opt.stepmax : dr;
464 off.incidenceCosine = cosIncidence;
465 recordOffender(result, opt, std::move(off), mismatchClass);
466 }
467 return result;
468}
469
471 const std::vector<Point3D>& points,
472 const std::vector<double>& oracleBoundaryDistance,
473 const ValidationOptions& opt)
474{
476 result.nSamples = points.size();
477 for (size_t index = 0; index < points.size(); ++index) {
478 const double trueDistance = oracleDistanceAt(oracleBoundaryDistance, index);
479 if (trueDistance < 0.) {
480 ++result.nNoVerdict;
481 continue;
482 }
483 const bool inside = candidate->Contains(points[index].data());
484 const double safety = candidate->Safety(points[index].data(), inside);
485 // Safety must be a non-negative lower bound on the true distance
486 const bool violatesLowerBound = safety < -opt.distanceTolerance;
487 const bool violatesUpperBound = safety > trueDistance + opt.distanceTolerance;
488 if (!violatesLowerBound && !violatesUpperBound) {
489 ++result.nAgree;
490 continue;
491 }
492 Offender off;
493 off.point = points[index];
494 off.candidateValue = safety;
495 off.referenceValue = trueDistance;
496 off.deviation = safety - trueDistance;
497 off.referenceSafety = trueDistance;
498 recordOffender(result, opt, std::move(off), MismatchClass::Unexplained);
499 }
500 return result;
501}
502
503// ---- Timing --------------------------------------------------------------------------------------
504
505namespace
506{
508template <typename PointKernel>
509TimingResult timePointKernel(const std::vector<Point3D>& points, int warmupRepeats, int timedRepeats,
510 PointKernel&& kernel)
511{
512 for (int warmup = 0; warmup < warmupRepeats; ++warmup) {
513 for (const auto& point : points) {
514 volatile double sink = kernel(point);
515 (void)sink;
516 }
517 }
518 uint64_t checksum = 0;
519 const auto start = std::chrono::steady_clock::now();
520 for (int repeat = 0; repeat < timedRepeats; ++repeat) {
521 for (const auto& point : points) {
522 checksum = detail::mixDouble(checksum, kernel(point));
523 }
524 }
525 const auto stop = std::chrono::steady_clock::now();
526 TimingResult result;
527 result.nCalls = points.size() * static_cast<size_t>(timedRepeats);
528 const double nanoseconds = std::chrono::duration<double, std::nano>(stop - start).count();
529 result.nsPerCall = result.nCalls > 0 ? nanoseconds / static_cast<double>(result.nCalls) : 0.;
530 result.checksum = checksum;
531 return result;
532}
533} // namespace
534
535TimingResult timeContains(const TGeoShape* shape, const std::vector<Point3D>& points, int warmupRepeats,
536 int timedRepeats)
537{
538 return timePointKernel(points, warmupRepeats, timedRepeats,
539 [&](const Point3D& p) { return shape->Contains(p.data()) ? 1. : 0.; });
540}
541
542TimingResult timeDistFromOutside(const TGeoShape* shape, const std::vector<Ray>& rays, int warmupRepeats,
543 int timedRepeats, double stepmax)
544{
545 return timeRayKernel(rays, warmupRepeats, timedRepeats, [&](const Point3D& origin, const Point3D& dir) {
546 return shape->DistFromOutside(origin.data(), dir.data(), kIact, stepmax);
547 });
548}
549
550TimingResult timeDistFromInside(const TGeoShape* shape, const std::vector<Ray>& rays, int warmupRepeats,
551 int timedRepeats)
552{
553 return timeRayKernel(rays, warmupRepeats, timedRepeats, [&](const Point3D& origin, const Point3D& dir) {
554 return shape->DistFromInside(origin.data(), dir.data(), kIact, TGeoShape::Big());
555 });
556}
557
558TimingResult timeSafety(const TGeoShape* shape, const std::vector<Point3D>& points, int warmupRepeats,
559 int timedRepeats)
560{
561 return timePointKernel(points, warmupRepeats, timedRepeats,
562 [&](const Point3D& p) { return shape->Safety(p.data(), shape->Contains(p.data())); });
563}
564
565// ---- The `shape_<part>.root` sidecar -------------------------------------------------------------
566
567namespace
568{
571constexpr const char* kShapeKeyName = "shape";
574constexpr const char* kPlacementKeyName = "placement";
575} // namespace
576
577TGeoShape* loadShapeFromRootFile(const std::string& path, std::string* error)
578{
579 const auto fail = [error](const std::string& why) -> TGeoShape* {
580 if (error != nullptr) {
581 *error = why;
582 }
583 return nullptr;
584 };
585 std::unique_ptr<TFile> file(TFile::Open(path.c_str(), "READ"));
586 if (!file || file->IsZombie()) {
587 return fail(path + ": cannot be opened as a ROOT file");
588 }
589 TObject* object = file->Get<TObject>(kShapeKeyName);
590 if (object == nullptr) {
591 // fall back to the first TGeoShape-derived key; emitters must write "shape"
592 TIter next(file->GetListOfKeys());
593 while (auto* key = static_cast<TKey*>(next())) {
594 TClass* cl = TClass::GetClass(key->GetClassName());
595 if (cl != nullptr && cl->InheritsFrom(TGeoShape::Class())) {
596 object = key->ReadObj();
597 break;
598 }
599 }
600 }
601 if (object == nullptr) {
602 return fail(path + ": holds no object inheriting from TGeoShape (expected key \"" +
603 kShapeKeyName + "\")");
604 }
605 auto* shape = dynamic_cast<TGeoShape*>(object);
606 if (shape == nullptr) {
607 const std::string className = object->ClassName();
608 delete object;
609 return fail(path + ": key \"" + kShapeKeyName + "\" holds a " + className +
610 ", which does not inherit from TGeoShape");
611 }
612 // An O2FlatCSG read from a file was closed by the `#pragma read` rule in CADSupportLinkDef.h;
613 // one that is still open refused, which means a broken file.
614 if (auto* flat = dynamic_cast<O2FlatCSG*>(shape); flat != nullptr && !flat->IsClosed()) {
615 delete shape;
616 return fail(path +
617 ": the O2FlatCSG it holds refused to close, so its sub-cell boxes could "
618 "not be rebuilt (see the Error above)");
619 }
620 // The object was read out of a TDirectory but is not a TDirectory-owned type (TGeoShape is not
621 // a histogram/tree), so we own it and it stays valid past the file's destruction.
622 return shape;
623}
624
625TGeoHMatrix* loadShapePlacementFromRootFile(const std::string& path)
626{
627 std::unique_ptr<TFile> file(TFile::Open(path.c_str(), "READ"));
628 if (!file || file->IsZombie()) {
629 return nullptr;
630 }
631 auto* stored = file->Get<TGeoHMatrix>(kPlacementKeyName);
632 if (stored == nullptr) {
633 return nullptr;
634 }
635 // copied out rather than detached from the file
636 auto* placement = new TGeoHMatrix(*stored);
637 return placement;
638}
639
640bool saveShapeToRootFile(const std::string& path, const TGeoShape& shape, std::string* error)
641{
642 return saveShapeToRootFile(path, shape, nullptr, error);
643}
644
645bool saveShapeToRootFile(const std::string& path, const TGeoShape& shape,
646 const TGeoMatrix* placement, std::string* error)
647{
648 std::unique_ptr<TFile> file(TFile::Open(path.c_str(), "RECREATE"));
649 if (!file || file->IsZombie()) {
650 if (error != nullptr) {
651 *error = path + ": cannot be opened for writing";
652 }
653 return false;
654 }
655 const int written = file->WriteTObject(&shape, kShapeKeyName);
656 // an identity placement is not written: no key means the identity
657 if (placement != nullptr && !placement->IsIdentity()) {
658 TGeoHMatrix stored(*placement);
659 stored.SetName(kPlacementKeyName);
660 file->WriteTObject(&stored, kPlacementKeyName);
661 }
662 file->Close();
663 if (written <= 0) {
664 if (error != nullptr) {
665 *error = path + ": WriteTObject wrote 0 bytes";
666 }
667 return false;
668 }
669 return true;
670}
671
672} // namespace harness
673} // namespace cad
674} // namespace o2
header::DataOrigin origin
benchmark::State & state
uint64_t bc
Definition RawEventData.h:5
int32_t i
float center
Validation and timing harness for TGeoShape navigation, typed on plain TGeoShape*.
StringRef key
bool IsClosed() const
Definition O2FlatCSG.h:89
GLuint64EXT * result
Definition glcorearb.h:5662
GLuint index
Definition glcorearb.h:781
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLsizei GLsizei GLfloat distance
Definition glcorearb.h:5506
GLsizei const GLfloat * value
Definition glcorearb.h:819
GLenum target
Definition glcorearb.h:1641
GLboolean * data
Definition glcorearb.h:298
GLenum GLint GLenum GLsizei GLsizei GLsizei GLint GLsizei const void * bits
Definition glcorearb.h:4150
typedef void(APIENTRYP PFNGLCULLFACEPROC)(GLenum mode)
GLsizei const GLchar *const * path
Definition glcorearb.h:3591
GLuint object
Definition glcorearb.h:4041
GLboolean r
Definition glcorearb.h:1233
GLuint start
Definition glcorearb.h:469
GLenum GLenum GLsizei len
Definition glcorearb.h:4232
GLboolean GLboolean GLboolean GLboolean a
Definition glcorearb.h:1233
uint64_t mixDouble(uint64_t acc, double value)
bool saveShapeToRootFile(const std::string &path, const TGeoShape &shape, std::string *error=nullptr)
Write a shape sidecar, with placement under "placement" unless it is null or the identity.
ValidationResult validateContainsAgainstOracle(const TGeoShape *candidate, const std::vector< Point3D > &points, const std::vector< int > &oracleState, const std::vector< double > &oracleBoundaryDistance, const ValidationOptions &opt={})
oracleState: 1 inside, 0 outside, -1 declined; oracleBoundaryDistance may cover only a prefix of poin...
SampleSet generateSamples(const TGeoShape *reference, const Point3D &bboxMin, const Point3D &bboxMax, const SampleConfig &cfg={})
A deterministic sample set from cfg.seed and the bbox; reference, the trusted mesh,...
ValidationResult validateSafetyAgainstOracle(const TGeoShape *candidate, const std::vector< Point3D > &points, const std::vector< double > &oracleBoundaryDistance, const ValidationOptions &opt={})
Safety's contract against the oracle's exact distance: 0 <= safety <= trueDistance.
TimingResult timeDistFromInside(const TGeoShape *shape, const std::vector< Ray > &rays, int warmupRepeats, int timedRepeats)
TimingResult timeDistFromOutside(const TGeoShape *shape, const std::vector< Ray > &rays, int warmupRepeats, int timedRepeats, double stepmax=TGeoShape::Big())
ValidationResult validateSafety(const TGeoShape *shape, const std::vector< Point3D > &points, const ValidationOptions &opt={})
Check one shape's Safety() lower-bound contract against its own DistFrom* along six probe directions;...
ValidationResult validateDistFromInside(const TGeoShape *candidate, const TGeoShape *reference, const std::vector< Ray > &rays, const ValidationOptions &opt={})
ValidationResult validateDistanceAgainstOracle(const TGeoShape *candidate, const std::vector< Ray > &rays, const std::vector< double > &oracleDistance, bool wantInside, const ValidationOptions &opt={}, const std::vector< int > &oracleOriginState={})
TimingResult timeContains(const TGeoShape *shape, const std::vector< Point3D > &points, int warmupRepeats, int timedRepeats)
TGeoHMatrix * loadShapePlacementFromRootFile(const std::string &path)
Read the shape's placement, or nullptr when there is none, meaning the identity. The caller owns it.
TGeoShape * loadShapeFromRootFile(const std::string &path, std::string *error=nullptr)
Read the single TGeoShape of a shape_<part>.root sidecar; nullptr on failure, with the reason in *err...
ValidationResult validateContains(const TGeoShape *candidate, const TGeoShape *reference, const std::vector< Point3D > &points, const ValidationOptions &opt={})
TimingResult timeRayKernel(const std::vector< Ray > &rays, int warmupRepeats, int timedRepeats, RayKernel &&kernel)
Time a per-ray kernel kernel(origin, dir) exactly like the timeDistFrom* functions,...
ValidationResult validateDistFromOutside(const TGeoShape *candidate, const TGeoShape *reference, const std::vector< Ray > &rays, const ValidationOptions &opt={})
TimingResult timeSafety(const TGeoShape *shape, const std::vector< Point3D > &points, int warmupRepeats, int timedRepeats)
std::array< double, 3 > Point3D
double normSq(const Vec3 &vector)
a couple of static helper functions to create timestamp values for CCDB queries or override obsolete ...
Parameters of generateSamples; the counts are targets, and a category may come back short.
int nInsideRays
rays from inside origins, for DistFromInside
int nOutsideRays
rays from outside origins, for DistFromOutside
int nBulk
uniform points over the inflated bbox
double boundaryBand
absolute distance (cm); <0 auto-picks 1e-3 * bbox diagonal
int nBoundary
points within boundaryBand of the reference surface
int maxRejectionAttempts
attempts per accepted sample before giving up on that category
uint64_t seed
every SampleSet is fully determined by this and the bbox
int nInside
points accepted by the reference Contains()
double bboxInflate
fractional bbox half-extent padding for bulk/outside sampling
std::vector< Point3D > boundaryPoints
std::vector< Point3D > bulkPoints
std::vector< Ray > outsideRays
std::vector< Ray > insideRays
std::vector< Point3D > insidePoints
double distanceTolerance
absolute agreement tolerance for distances (cm)