29#include "TGeoCompositeShape.h"
30#include "TGeoMatrix.h"
31#include "TGeoScaledShape.h"
33#include <nlohmann/json.hpp>
52using json = nlohmann::json;
62 std::string explicitSurfaces;
63 std::string explicitFacets;
64 std::string partsPattern;
68 std::set<std::string> only = {
"contains",
"distout",
"distin",
"safety"};
69 bool loopCrosscheck =
false;
70 bool pruningAb =
false;
75 std::string dumpSamples;
76 std::string refAnswers;
77 std::string loadSamples;
78 bool edgeIdentity =
false;
79 std::string explicitShape;
98std::string deriveShapeSidecarPath(
const std::string& surfacesPath)
100 const auto slash = surfacesPath.find_last_of(
'/');
101 const std::string dir = slash == std::string::npos ? std::string() : surfacesPath.substr(0, slash + 1);
102 std::string base = slash == std::string::npos ? surfacesPath : surfacesPath.substr(slash + 1);
103 const std::string prefix =
"surfaces_";
104 const std::string suffix =
".bin";
105 if (base.rfind(prefix, 0) != 0 || base.size() <= prefix.size() + suffix.size() ||
106 base.compare(base.size() - suffix.size(), suffix.size(), suffix) != 0) {
109 const std::string stem = base.substr(prefix.size(), base.size() - prefix.size() - suffix.size());
110 return dir +
"shape_" + stem +
".root";
118 std::ifstream probe(
path);
119 return static_cast<bool>(probe);
122void printUsage(
const char* argv0)
124 std::cout <<
"Usage: " << argv0 <<
" --db <dir> [--parts <substring>] [--points N] [--rays N] [--seed N]\n"
125 " [--only contains,distout,distin,safety] [--loop-crosscheck]\n"
126 " [--pruning-ab] [--json <out.json>] [--warmup N] [--repeat N]\n"
128 << argv0 <<
" --surfaces <file> --facets <file> [--shape <file>] [options as above]\n\n"
129 " Every representation a part has is scored side by side against the same oracle answers:\n"
130 " surface surfaces_<part>.bin -> O2BVHSurfaceSolid (the historical candidate)\n"
131 " mesh facets_<part>.bin -> O2Tessellated (also the sampling reference)\n"
132 " shape shape_<part>.root -> any TGeoShape (the CSG emitter's hand-over)\n"
133 " The `shape` sidecar is one ROOT file holding one TGeoShape-derived object under the key\n"
134 " \"shape\", in cm, plus an OPTIONAL TGeoHMatrix under the key \"placement\" taking it from\n"
135 " its own frame into the part's; absent means identity, and points and rays are transformed\n"
136 " into the shape's frame before it is asked. See CADSupport/O2SolidHarness.h.\n\n"
137 " --loop-crosscheck also run the surface solid's non-BVH _Loop twins and require exact\n"
138 " agreement; this is the correctness guard that does not involve the mesh\n"
139 " --pruning-ab re-run the distance kernels with ray tmax pruning disabled, reporting\n"
140 " the BVH candidate counts and ns/call both ways (prices the optimization)\n"
141 " --rims list every trim loop, not only the ones that are not cleanly matched;\n"
142 " the same records go into --json unconditionally\n"
143 " --dump-samples D write each part's sample set to D/samples_<part>.json\n"
144 " --load-samples D read each part's sample set from D/samples_<part>.json instead of\n"
145 " generating it. The generator derives its points from the *mesh*, so two\n"
146 " runs on differently-tessellated shapes cannot be compared point by\n"
147 " point; loading a frozen (and, for a transformed shape, transformed) set\n"
148 " removes the mesh from the comparison entirely. --points/--rays/--seed\n"
149 " are then ignored and the file's counts are used.\n"
150 " --edge-identity report the sidecar-v3 edge-identity block (source-edge counts and the\n"
151 " max shared-edge deviation) on stdout; it is always in --json\n"
152 " --ref-answers D validate against D/answers_<part>.json instead of the mesh; those are\n"
153 " produced by Detectors/CADSupport/validation/occtOracle.py from the part's .brep, so a\n"
154 " disagreement outside the model tolerance is a defect, not chording\n\n"
155 "OCCT oracle round trip:\n"
157 << argv0 <<
" --db <db> --dump-samples /tmp/o\n"
158 " occtOracle.py --brep <part>.brep --samples /tmp/o/samples_<part>.json \\\n"
159 " --out /tmp/o/answers_<part>.json\n"
161 << argv0 <<
" --db <db> --ref-answers /tmp/o\n\n"
162 "perf record entry point (single kernel, one part):\n"
164 << argv0 <<
" --db <db> --parts ExcavatorArm --only distout --rays 200000\n";
167std::set<std::string> splitCsv(
const std::string& s)
169 std::set<std::string> out;
170 std::stringstream ss(s);
172 while (std::getline(ss, tok,
',')) {
180bool parseArgs(
int argc,
char** argv,
Options& opt)
182 for (
int i = 1;
i < argc; ++
i) {
183 const std::string
a = argv[
i];
184 auto next = [&](
const char* flag) -> std::string {
186 throw std::runtime_error(std::string(
"missing value for ") + flag);
191 opt.db = next(
"--db");
192 }
else if (
a ==
"--surfaces") {
193 opt.explicitSurfaces = next(
"--surfaces");
194 }
else if (
a ==
"--facets") {
195 opt.explicitFacets = next(
"--facets");
196 }
else if (
a ==
"--shape") {
197 opt.explicitShape = next(
"--shape");
198 }
else if (
a ==
"--parts") {
199 opt.partsPattern = next(
"--parts");
200 }
else if (
a ==
"--points") {
201 opt.points = std::stoi(next(
"--points"));
202 }
else if (
a ==
"--rays") {
203 opt.rays = std::stoi(next(
"--rays"));
204 }
else if (
a ==
"--seed") {
205 opt.
seed = std::stoull(next(
"--seed"));
206 }
else if (
a ==
"--only") {
207 opt.only = splitCsv(next(
"--only"));
208 }
else if (
a ==
"--loop-crosscheck") {
209 opt.loopCrosscheck =
true;
210 }
else if (
a ==
"--pruning-ab") {
211 opt.pruningAb =
true;
212 }
else if (
a ==
"--rims") {
214 }
else if (
a ==
"--json") {
215 opt.jsonOut = next(
"--json");
216 }
else if (
a ==
"--warmup") {
217 opt.warmup = std::stoi(next(
"--warmup"));
218 }
else if (
a ==
"--repeat") {
219 opt.repeat = std::stoi(next(
"--repeat"));
220 }
else if (
a ==
"--dump-samples") {
221 opt.dumpSamples = next(
"--dump-samples");
222 }
else if (
a ==
"--ref-answers") {
223 opt.refAnswers = next(
"--ref-answers");
224 }
else if (
a ==
"--load-samples") {
225 opt.loadSamples = next(
"--load-samples");
226 }
else if (
a ==
"--edge-identity") {
227 opt.edgeIdentity =
true;
228 }
else if (
a ==
"-h" ||
a ==
"--help") {
232 throw std::runtime_error(
"unrecognized option: " +
a);
235 if (opt.db.empty() && (opt.explicitSurfaces.empty() || opt.explicitFacets.empty())) {
236 throw std::runtime_error(
"either --db <dir> or both --surfaces/--facets are required");
241std::vector<Part> collectParts(
const Options& opt)
243 std::vector<Part> parts;
244 if (!opt.explicitSurfaces.empty()) {
245 Part part{
"adhoc",
"adhoc", opt.explicitSurfaces, opt.explicitFacets, opt.explicitShape};
246 if (part.shape.empty()) {
247 part.shape = deriveShapeSidecarPath(part.surfaces);
249 parts.push_back(std::move(part));
252 const std::string manifestPath = opt.db +
"/manifest.json";
253 std::ifstream in(manifestPath);
255 throw std::runtime_error(
"cannot open " + manifestPath);
259 for (
const auto& p : manifest.at(
"parts")) {
261 part.id = p.at(
"id").get<std::string>();
262 part.model = p.at(
"model").get<std::string>();
263 part.surfaces = p.at(
"surfaces").get<std::string>();
264 part.facets = p.at(
"facets").get<std::string>();
265 part.shape = p.value(
"shape", std::string());
266 if (part.shape.empty()) {
267 part.shape = deriveShapeSidecarPath(part.surfaces);
269 if (!opt.partsPattern.empty()) {
270 const bool idMatch = part.id.find(opt.partsPattern) != std::string::npos;
271 const bool modelMatch = part.model.find(opt.partsPattern) != std::string::npos;
272 if (!idMatch && !modelMatch) {
276 parts.push_back(std::move(part));
284 j[
"nSamples"] =
r.nSamples;
285 j[
"nAgree"] =
r.nAgree;
286 j[
"nMismatchWithinBand"] =
r.nMismatchWithinBand;
287 j[
"nMismatchMissedSurface"] =
r.nMismatchMissedSurface;
288 j[
"nMismatchUnexplained"] =
r.nMismatchUnexplained;
289 j[
"nNoVerdict"] =
r.nNoVerdict;
290 j[
"nRelabelled"] =
r.nRelabelled;
291 j[
"worstDeviation"] =
r.worstDeviation;
292 json offenders = json::array();
293 for (
const auto& o :
r.worstOffenders) {
294 offenders.push_back({{
"point", {o.point[0], o.point[1], o.point[2]}},
295 {
"dir", {o.dir[0], o.dir[1], o.dir[2]}},
296 {
"candidateValue", o.candidateValue},
297 {
"referenceValue", o.referenceValue},
298 {
"deviation", o.deviation},
299 {
"referenceSafety", o.referenceSafety},
300 {
"incidenceCosine", o.incidenceCosine}});
302 j[
"worstOffenders"] = offenders;
308constexpr int kOracleFormatVersion = 1;
312std::string sanitizePartId(
const std::string&
id)
315 out.reserve(
id.
size());
316 for (
const char c :
id) {
317 out.push_back((std::isalnum(
static_cast<unsigned char>(
c)) ||
c ==
'-' ||
c ==
'.') ?
c :
'_');
322json pointsToJson(
const std::vector<Point3D>& points)
325 for (
const auto& p : points) {
326 array.push_back({p[0], p[1], p[2]});
331json raysToJson(
const std::vector<Ray>& rays)
334 for (
const auto&
r : rays) {
335 array.push_back({{
"o", {
r.origin[0],
r.origin[1],
r.origin[2]}},
336 {
"d", {
r.dir[0],
r.dir[1],
r.dir[2]}}});
344void writeSamples(
const std::string& dir,
const std::string& partId,
const SampleSet&
samples)
347 doc[
"version"] = kOracleFormatVersion;
348 doc[
"part"] = partId;
351 doc[
"points"] = {{
"bulk", pointsToJson(
samples.bulkPoints)},
352 {
"boundary", pointsToJson(
samples.boundaryPoints)},
353 {
"inside", pointsToJson(
samples.insidePoints)}};
354 doc[
"rays"] = {{
"outside", raysToJson(
samples.outsideRays)},
355 {
"inside", raysToJson(
samples.insideRays)}};
356 const std::string
path = dir +
"/samples_" + sanitizePartId(partId) +
".json";
357 std::ofstream out(
path);
359 throw std::runtime_error(
"cannot write " +
path);
362 std::printf(
" wrote samples: %s\n",
path.c_str());
365std::vector<Point3D> pointsFromJson(
const json&
array)
367 std::vector<Point3D> points;
368 points.reserve(
array.size());
369 for (
const auto& p :
array) {
370 points.push_back(
Point3D{p.at(0).get<
double>(), p.at(1).get<
double>(), p.at(2).get<
double>()});
375std::vector<Ray> raysFromJson(
const json&
array)
377 std::vector<Ray> rays;
378 rays.reserve(
array.size());
379 for (
const auto&
r :
array) {
380 const auto& o =
r.at(
"o");
381 const auto& d =
r.at(
"d");
382 rays.push_back(
Ray{
Point3D{o.at(0).get<
double>(), o.at(1).get<
double>(), o.at(2).get<
double>()},
383 Point3D{d.at(0).get<
double>(), d.at(1).get<
double>(), d.at(2).get<
double>()}});
390SampleSet readSamples(
const std::string& dir,
const std::string& partId)
392 const std::string
path = dir +
"/samples_" + sanitizePartId(partId) +
".json";
393 std::ifstream in(
path);
395 throw std::runtime_error(
"cannot read " +
path);
399 const int version = doc.value(
"version", -1);
400 if (
version != kOracleFormatVersion) {
405 for (
int i = 0;
i < 3; ++
i) {
406 samples.bboxMin[
i] = doc.at(
"bboxMin").at(
i).get<
double>();
407 samples.bboxMax[
i] = doc.at(
"bboxMax").at(
i).get<
double>();
409 samples.bulkPoints = pointsFromJson(doc.at(
"points").at(
"bulk"));
410 samples.boundaryPoints = pointsFromJson(doc.at(
"points").at(
"boundary"));
411 samples.insidePoints = pointsFromJson(doc.at(
"points").at(
"inside"));
412 samples.outsideRays = raysFromJson(doc.at(
"rays").at(
"outside"));
413 samples.insideRays = raysFromJson(doc.at(
"rays").at(
"inside"));
414 std::printf(
" loaded samples: %s (bulk=%zu boundary=%zu inside=%zu outRays=%zu inRays=%zu)\n",
421struct OracleAnswers {
423 double tolerance = 0.;
424 double capacity = 0.;
428 bool hasBbox =
false;
431 std::map<std::string, std::vector<int>> containsState;
434 std::map<std::string, std::vector<int>> originContains;
435 std::map<std::string, std::vector<double>> boundaryDistance;
436 std::map<std::string, std::vector<double>> distOutside;
437 std::map<std::string, std::vector<double>> distInside;
441std::map<std::string, std::vector<T>> readColumns(
const json& parent,
const char*
key)
443 std::map<std::string, std::vector<T>> columns;
444 if (!parent.contains(
key)) {
447 for (
const auto& [category,
values] : parent.at(
key).items()) {
448 columns[category] =
values.template get<std::vector<T>>();
453OracleAnswers loadOracleAnswers(
const std::string& dir,
const std::string& partId)
455 OracleAnswers answers;
456 const std::string
path = dir +
"/answers_" + sanitizePartId(partId) +
".json";
457 std::ifstream in(
path);
459 std::printf(
" oracle: no answers file %s, skipping oracle validation\n",
path.c_str());
464 const int version = doc.value(
"version", -1);
465 if (
version != kOracleFormatVersion) {
470 answers.tolerance = doc.value(
"tolerance", 0.);
471 answers.capacity = doc.value(
"capacity", 0.);
472 answers.valid = doc.value(
"valid",
false);
473 if (doc.contains(
"bboxMin") && doc.contains(
"bboxMax")) {
474 answers.hasBbox =
true;
475 for (
int i = 0;
i < 3; ++
i) {
476 answers.bboxMin[
i] = doc.at(
"bboxMin").at(
i).get<
double>();
477 answers.bboxMax[
i] = doc.at(
"bboxMax").at(
i).get<
double>();
480 answers.containsState = readColumns<int>(doc,
"contains");
481 answers.originContains = readColumns<int>(doc,
"originContains");
482 answers.boundaryDistance = readColumns<double>(doc,
"safetyUpperBound");
483 answers.distOutside = readColumns<double>(doc,
"distFromOutside");
484 answers.distInside = readColumns<double>(doc,
"distFromInside");
497std::vector<T> mergeCategories(
const std::map<std::string, std::vector<T>>& columns,
498 const std::array<size_t, 3>& categorySizes, T missing)
500 static constexpr std::array<const char*, 3> kOrder = {
"bulk",
"boundary",
"inside"};
501 std::vector<T> merged;
502 for (
size_t categoryIndex = 0; categoryIndex < kOrder.size(); ++categoryIndex) {
503 const size_t expected = categorySizes[categoryIndex];
504 const auto it = columns.find(kOrder[categoryIndex]);
505 const size_t available = it == columns.end() ? 0 : std::min(
expected, it->second.size());
506 for (
size_t i = 0;
i < available; ++
i) {
507 merged.push_back(it->second[
i]);
509 merged.insert(merged.end(),
expected - available, missing);
523 const size_t scored =
r.nSamples -
r.nNoVerdict;
524 const double agreePct = scored ? 100. *
static_cast<double>(
r.nAgree) /
static_cast<double>(scored) : 0.;
526 " %-10s scored=%-7zu agree=%6.2f%% mismatch(band=%zu,missed=%zu,unexplained=%zu)"
527 " noVerdict=%zu worstDev=%.6g\n",
528 name.c_str(), scored, agreePct,
r.nMismatchWithinBand,
r.nMismatchMissedSurface,
529 r.nMismatchUnexplained,
r.nNoVerdict,
r.worstDeviation);
530 if (
r.nRelabelled > 0) {
534 std::printf(
" %-10s relabelled=%zu ray(s) by the oracle's own origin classification\n",
535 name.c_str(),
r.nRelabelled);
537 if (
r.nMismatchUnexplained > 0 ||
r.nMismatchMissedSurface > 0) {
538 const size_t nShow = std::min<size_t>(3,
r.worstOffenders.size());
539 for (
size_t i = 0;
i < nShow; ++
i) {
540 const auto& o =
r.worstOffenders[
i];
541 std::printf(
" offender[%zu]: point=(%.6g,%.6g,%.6g) dir=(%.6g,%.6g,%.6g) cand=%.6g ref=%.6g dev=%.6g refSafety=%.6g\n",
542 i, o.point[0], o.point[1], o.point[2], o.dir[0], o.dir[1], o.dir[2], o.candidateValue,
543 o.referenceValue, o.deviation, o.referenceSafety);
551 std::printf(
" %-10s candidate=%9.1f ns/call reference=%9.1f ns/call ratio(cand/ref)=%.2fx\n",
name.c_str(),
560 const double speedup =
bvh.nsPerCall > 0. ? loop.
nsPerCall /
bvh.nsPerCall : 0.;
561 std::printf(
" %-10s BVH=%9.1f ns/call _Loop=%9.1f ns/call speedup(loop/bvh)=%.2fx\n",
name.c_str(),
565double toSeconds(std::chrono::steady_clock::time_point
t0, std::chrono::steady_clock::time_point
t1)
567 return std::chrono::duration<double>(
t1 -
t0).count();
582struct Representation {
585 const TGeoShape* shape =
nullptr;
588 const char* primitiveKind =
"";
599 const TGeoMatrix* placement =
nullptr;
605 if (placement ==
nullptr) {
609 placement->MasterToLocal(p.data(), out.data());
616Ray toLocal(
const TGeoMatrix* placement,
const Ray&
r)
618 if (placement ==
nullptr) {
622 placement->MasterToLocal(
r.origin.data(), out.origin.data());
623 placement->MasterToLocalVect(
r.dir.data(), out.dir.data());
631std::vector<T> toLocal(
const TGeoMatrix* placement,
const std::vector<T>& in)
633 if (placement ==
nullptr) {
637 out.reserve(in.size());
638 for (
const auto& item : in) {
639 out.push_back(toLocal(placement, item));
654 const char* method =
"root-analytic";
655 bool comparable =
true;
658bool usesMonteCarloCapacity(
const TGeoShape* shape)
660 if (shape ==
nullptr) {
663 if (shape->InheritsFrom(TGeoCompositeShape::Class())) {
668 if (
const auto* scaled =
dynamic_cast<const TGeoScaledShape*
>(shape)) {
669 return usesMonteCarloCapacity(scaled->GetShape());
674CapacityKind capacityKindOf(
const Representation& rep)
676 if (rep.surfaceSolid !=
nullptr) {
678 return {
"exact-divergence",
true};
680 if (
dynamic_cast<const O2Tessellated*
>(rep.shape) !=
nullptr) {
683 return {
"mesh-divergence",
true};
685 if (usesMonteCarloCapacity(rep.shape)) {
686 return {
"root-montecarlo",
false};
688 return {
"root-analytic",
true};
705double bboxDeviationFromOracle(
const TGeoShape* shape,
const OracleAnswers& oracle,
706 const TGeoMatrix* placement =
nullptr)
708 if (!oracle.hasBbox) {
711 const auto*
box =
dynamic_cast<const TGeoBBox*
>(shape);
712 if (
box ==
nullptr) {
715 const double half[3] = {
box->GetDX(),
box->GetDY(),
box->GetDZ()};
718 for (
int i = 0;
i < 3; ++
i) {
722 if (placement !=
nullptr) {
723 double outLo[3] = {1.e300, 1.e300, 1.e300};
724 double outHi[3] = {-1.e300, -1.e300, -1.e300};
725 for (
int corner = 0; corner < 8; ++corner) {
726 const double local[3] = {(corner & 1) ? hi[0] : lo[0], (corner & 2) ? hi[1] : lo[1],
727 (corner & 4) ? hi[2] : lo[2]};
729 placement->LocalToMaster(local, master);
730 for (
int i = 0;
i < 3; ++
i) {
731 outLo[
i] = std::min(outLo[
i], master[
i]);
732 outHi[
i] = std::max(outHi[
i], master[
i]);
735 std::copy(std::begin(outLo), std::end(outLo), std::begin(lo));
736 std::copy(std::begin(outHi), std::end(outHi), std::begin(hi));
739 for (
int i = 0;
i < 3; ++
i) {
740 worst = std::max(worst, std::fabs(lo[
i] - oracle.bboxMin[
i]));
741 worst = std::max(worst, std::fabs(hi[
i] - oracle.bboxMax[
i]));
752json scoreAgainstOracle(
const TGeoShape* candidate,
const OracleAnswers& oracle,
754 const std::vector<int>& containsState,
755 const std::vector<double>& boundaryDistance,
const SampleSet& samplesIn,
756 const std::set<std::string>& only,
const std::string&
label,
757 const std::string& capacityLabel,
const TGeoMatrix* placement =
nullptr)
762 const std::vector<Point3D> localPoints = toLocal(placement, allPointsIn);
763 const std::vector<Ray> localOutsideRays = toLocal(placement, samplesIn.
outsideRays);
764 const std::vector<Ray> localInsideRays = toLocal(placement, samplesIn.
insideRays);
765 const std::vector<Point3D>& allPoints = placement !=
nullptr ? localPoints : allPointsIn;
766 const std::vector<Ray>& outsideRays =
767 placement !=
nullptr ? localOutsideRays : samplesIn.
outsideRays;
768 const std::vector<Ray>& insideRays = placement !=
nullptr ? localInsideRays : samplesIn.
insideRays;
770 oracleJson[
"tolerance"] = oracle.tolerance;
771 oracleJson[
"capacity"] = oracle.capacity;
772 oracleJson[
"valid"] = oracle.valid;
773 const double capacity = candidate->Capacity();
774 oracleJson[
"capacityCandidate"] = capacity;
775 oracleJson[
"capacityRelativeDeviation"] =
776 oracle.capacity != 0. ? (capacity - oracle.capacity) / oracle.capacity : 0.;
777 std::printf(
" %s: capacity candidate=%.6g reference=%.6g relDev=%.3g\n", capacityLabel.c_str(),
778 capacity, oracle.capacity, oracleJson[
"capacityRelativeDeviation"].get<
double>());
780 if (only.count(
"contains")) {
783 printValidation(
label +
":contains",
v);
784 oracleJson[
"contains"] = validationToJson(
v);
786 const auto originStateFor = [&oracle](
const char* category) {
787 const auto it = oracle.originContains.find(category);
788 return it == oracle.originContains.end() ? std::vector<int>{} : it->second;
790 if (only.count(
"distout")) {
791 const auto it = oracle.distOutside.find(
"outside");
792 if (it != oracle.distOutside.end()) {
795 originStateFor(
"outside"));
796 printValidation(
label +
":distout",
v);
797 oracleJson[
"distout"] = validationToJson(
v);
800 if (only.count(
"distin")) {
801 const auto it = oracle.distInside.find(
"inside");
802 if (it != oracle.distInside.end()) {
804 true, oracleOpt, originStateFor(
"inside"));
805 printValidation(
label +
":distin",
v);
806 oracleJson[
"distin"] = validationToJson(
v);
809 if (only.count(
"safety")) {
811 printValidation(
label +
":safety",
v);
812 oracleJson[
"safety"] = validationToJson(
v);
820size_t countDisagreements(
const json& oracleJson)
823 for (
const char*
key : {
"contains",
"distout",
"distin",
"safety"}) {
824 if (!oracleJson.contains(
key)) {
827 const auto& column = oracleJson.at(
key);
828 bad += column.value(
"nMismatchUnexplained",
size_t{0});
829 bad += column.value(
"nMismatchMissedSurface",
size_t{0});
840 if (!parseArgs(argc, argv, opt)) {
843 }
catch (
const std::exception& e) {
844 std::cerr <<
"error: " << e.what() <<
"\n";
849 std::vector<Part> parts;
851 parts = collectParts(opt);
852 }
catch (
const std::exception& e) {
853 std::cerr <<
"error: " << e.what() <<
"\n";
857 std::cerr <<
"no parts matched (pattern='" << opt.partsPattern <<
"')\n";
861 json jsonReport = json::array();
862 std::vector<std::string> unreliableParts;
864 for (
const auto& part : parts) {
865 std::printf(
"=== %s (%s) ===\n", part.id.c_str(), part.model.c_str());
869 std::cerr <<
" skip: LoadSurfaceSolid failed for " << part.surfaces <<
"\n";
872 auto t0 = std::chrono::steady_clock::now();
873 surf.CloseShape(
true);
874 auto t1 = std::chrono::steady_clock::now();
875 const double surfCloseSeconds = toSeconds(
t0,
t1);
879 std::cerr <<
" skip: LoadFacetSolid failed for " << part.facets <<
"\n";
882 t0 = std::chrono::steady_clock::now();
884 t1 = std::chrono::steady_clock::now();
885 const double meshCloseSeconds = toSeconds(
t0,
t1);
887 const TGeoShape* candidate = &
surf;
894 std::vector<Representation> representations;
895 representations.push_back({
"surface", part.surfaces, &
surf, &
surf,
surf.GetNsurfaces(),
"patches"});
896 representations.push_back({
"mesh", part.facets, &mesh,
nullptr, mesh.
GetNfacets(),
"triangles"});
897 std::unique_ptr<TGeoShape> rootShape;
898 std::unique_ptr<TGeoHMatrix> rootShapePlacement;
900 std::string shapeError;
904 std::printf(
" shape sidecar: %s -> %s \"%s\"%s\n", part.shape.c_str(),
905 rootShape->ClassName(), rootShape->GetName(),
906 rootShapePlacement ?
" (placed: queries are transformed into its own frame)"
908 representations.push_back({
"shape", part.shape, rootShape.get(),
nullptr, -1,
909 rootShape->ClassName(), rootShapePlacement.get()});
911 std::printf(
" shape sidecar: *** %s\n", shapeError.c_str());
915 const Point3D bboxMin{mesh.GetOrigin()[0] - mesh.GetDX(), mesh.GetOrigin()[1] - mesh.GetDY(),
916 mesh.GetOrigin()[2] - mesh.GetDZ()};
917 const Point3D bboxMax{mesh.GetOrigin()[0] + mesh.GetDX(), mesh.GetOrigin()[1] + mesh.GetDY(),
918 mesh.GetOrigin()[2] + mesh.GetDZ()};
920 std::printf(
" surfaces=%d triangles=%d closeShape: surface=%.4fs mesh=%.4fs\n",
surf.GetNsurfaces(),
921 mesh.
GetNfacets(), surfCloseSeconds, meshCloseSeconds);
924 const auto reliability =
surf.GetNavigationReliability();
926 const bool navigable =
surf.IsNavigable();
927 std::printf(
" navigation: %s%s (boundary=%d non-manifold=%d reversed=%d)\n", reliabilityName,
928 navigable ?
"" :
" *** UNRELIABLE: results below are not a measurement of accuracy ***",
929 surf.GetBoundaryEdgeCount(),
surf.GetNonManifoldEdgeCount(),
surf.GetReversedEdgeCount());
934 " rim isolation: max %.3g cm (chord resolution %.3g cm, declared tolerance %.3g cm); rims %d "
935 "(matched=%d boundary=%d non-manifold=%d reversed=%d), open %.3g of %.3g cm\n",
936 surf.GetMaxRimIsolation(),
surf.GetRimChordResolution(),
surf.GetRimMatchTolerance(),
surf.GetRimCount(),
937 surf.GetMatchedRimCount(),
surf.GetBoundaryRimCount(),
surf.GetNonManifoldRimCount(),
938 surf.GetReversedRimCount(),
surf.GetUnmatchedRimLength(),
surf.GetTotalRimLength());
944 if (opt.edgeIdentity) {
945 if (
surf.HasEdgeIdentity()) {
947 " edge identity: %d source edge(s) (shared=%d boundary=%d non-manifold=%d "
948 "reversed=%d degenerate=%d), max shared-edge deviation %.4g cm\n",
949 surf.GetSourceEdgeCount(),
surf.GetSharedSourceEdgeCount(),
950 surf.GetBoundarySourceEdgeCount(),
surf.GetNonManifoldSourceEdgeCount(),
951 surf.GetReversedSourceEdgeCount(),
surf.GetDegenerateSourceEdgeCount(),
952 surf.GetMaxSharedEdgeDeviation());
954 std::printf(
" edge identity: absent (sidecar predates v3); closure fell back to proximity\n");
958 json rimsJson = json::array();
959 for (
const auto& rim :
surf.GetRimReports()) {
961 const bool clean = rim.state == O2BVHSurfaceSolid::NavigationReliability::Reliable;
962 if (opt.allRims || !clean) {
964 " rim face=%d loop=%d %s %s: %d chords, %.4g cm (%d chords / %.4g cm unmatched); "
965 "loneliest chord %.3g cm from face %d at (%.4g, %.4g, %.4g)\n",
966 rim.surface, rim.rimOnSurface, rim.closed ?
"closed" :
"OPEN-CHAIN", stateName, rim.chords,
967 rim.length, rim.unmatchedChords, rim.unmatchedLength, rim.maxIsolation, rim.maxIsolationFace,
968 rim.maxIsolationPoint[0], rim.maxIsolationPoint[1], rim.maxIsolationPoint[2]);
970 rimsJson.push_back({{
"face", rim.surface},
971 {
"loop", rim.rimOnSurface},
972 {
"state", stateName},
973 {
"closed", rim.closed},
974 {
"chords", rim.chords},
975 {
"unmatchedChords", rim.unmatchedChords},
976 {
"length", rim.length},
977 {
"unmatchedLength", rim.unmatchedLength},
978 {
"maxIsolation", rim.maxIsolation},
979 {
"maxIsolationFace", rim.maxIsolationFace},
980 {
"maxIsolationPoint", rim.maxIsolationPoint}});
983 unreliableParts.push_back(part.id +
" (" + reliabilityName +
")");
987 cfg.
nBulk = opt.points;
989 cfg.
nInside = std::max(1, opt.points / 2);
994 : readSamples(opt.loadSamples, part.id);
996 long long candidatesSampled = 0;
997 const size_t nProbe = std::min<size_t>(200,
samples.outsideRays.size());
998 for (
size_t i = 0;
i < nProbe; ++
i) {
1000 const int n =
surf.CountBVHRayCandidates(
r.origin,
r.dir);
1002 candidatesSampled +=
n;
1005 std::printf(
" BVH ray candidates: sum=%lld over %zu probe rays\n", candidatesSampled, nProbe);
1008 partJson[
"id"] = part.id;
1009 partJson[
"model"] = part.model;
1010 partJson[
"nSurfaces"] =
surf.GetNsurfaces();
1012 partJson[
"closeShapeSecondsSurface"] = surfCloseSeconds;
1013 partJson[
"closeShapeSecondsMesh"] = meshCloseSeconds;
1014 partJson[
"bvhRayCandidatesSampled"] = candidatesSampled;
1015 partJson[
"bvhRayCandidatesProbeRays"] = nProbe;
1016 partJson[
"navigation"] = {{
"reliability", reliabilityName},
1017 {
"navigable", navigable},
1018 {
"boundaryEdges",
surf.GetBoundaryEdgeCount()},
1019 {
"nonManifoldEdges",
surf.GetNonManifoldEdgeCount()},
1020 {
"reversedEdges",
surf.GetReversedEdgeCount()},
1021 {
"maxRimIsolation",
surf.GetMaxRimIsolation()},
1022 {
"rimChordResolution",
surf.GetRimChordResolution()},
1023 {
"rimMatchTolerance",
surf.GetRimMatchTolerance()},
1024 {
"totalRimLength",
surf.GetTotalRimLength()},
1025 {
"unmatchedRimLength",
surf.GetUnmatchedRimLength()},
1026 {
"rims",
surf.GetRimCount()},
1027 {
"matchedRims",
surf.GetMatchedRimCount()},
1028 {
"boundaryRims",
surf.GetBoundaryRimCount()},
1029 {
"nonManifoldRims",
surf.GetNonManifoldRimCount()},
1030 {
"reversedRims",
surf.GetReversedRimCount()},
1031 {
"hasEdgeIdentity",
surf.HasEdgeIdentity()},
1032 {
"sourceEdges",
surf.GetSourceEdgeCount()},
1033 {
"sharedSourceEdges",
surf.GetSharedSourceEdgeCount()},
1034 {
"boundarySourceEdges",
surf.GetBoundarySourceEdgeCount()},
1035 {
"nonManifoldSourceEdges",
surf.GetNonManifoldSourceEdgeCount()},
1036 {
"reversedSourceEdges",
surf.GetReversedSourceEdgeCount()},
1037 {
"degenerateSourceEdges",
surf.GetDegenerateSourceEdgeCount()},
1038 {
"maxSharedEdgeDeviation",
surf.GetMaxSharedEdgeDeviation()},
1039 {
"rimDetail", rimsJson}};
1041 std::vector<Point3D> allPoints =
samples.bulkPoints;
1042 allPoints.insert(allPoints.end(),
samples.boundaryPoints.begin(),
samples.boundaryPoints.end());
1043 allPoints.insert(allPoints.end(),
samples.insidePoints.begin(),
samples.insidePoints.end());
1044 const std::array<size_t, 3> categorySizes{
samples.bulkPoints.size(),
samples.boundaryPoints.size(),
1047 if (!opt.dumpSamples.empty()) {
1048 writeSamples(opt.dumpSamples, part.id,
samples);
1054 if (!opt.refAnswers.empty()) {
1055 const OracleAnswers oracle = loadOracleAnswers(opt.refAnswers, part.id);
1061 const auto boundaryDistance =
1062 mergeCategories<double>(oracle.boundaryDistance, categorySizes, -1.);
1063 const auto containsState = mergeCategories<int>(oracle.containsState, categorySizes, -1);
1065 std::printf(
" oracle: %s tolerance=%.3g capacity=%.6g cm^3 (band=%.3g)\n",
1066 oracle.valid ?
"valid" :
"*** NOT BRepCheck-VALID ***", oracle.tolerance,
1067 oracle.capacity, oracleOpt.
meshBand);
1072 json oracleJson = scoreAgainstOracle(candidate, oracle, oracleOpt, allPoints, containsState,
1073 boundaryDistance,
samples, opt.only,
"O",
"oracle");
1074 partJson[
"oracle"] = oracleJson;
1080 json representationsJson = json::array();
1081 for (
const auto& rep : representations) {
1082 const bool isSurface = rep.surfaceSolid !=
nullptr;
1084 repJson[
"name"] = rep.name;
1085 repJson[
"source"] = rep.source;
1086 repJson[
"shapeClass"] = rep.shape->ClassName();
1087 if (rep.primitives >= 0) {
1088 repJson[
"primitives"] = rep.primitives;
1089 repJson[
"primitiveKind"] = rep.primitiveKind;
1091 const auto capacityKind = capacityKindOf(rep);
1092 repJson[
"capacityMethod"] = capacityKind.method;
1093 repJson[
"capacityComparable"] = capacityKind.comparable;
1096 repJson[
"bboxDeviationFromOracle"] =
1097 bboxDeviationFromOracle(rep.shape, oracle, rep.placement);
1100 if (rep.placement !=
nullptr) {
1101 const double* rot = rep.placement->GetRotationMatrix();
1102 const double* tr = rep.placement->GetTranslation();
1103 repJson[
"placement"] = {{rot[0], rot[1], rot[2], tr[0]},
1104 {rot[3], rot[4], rot[5], tr[1]},
1105 {rot[6], rot[7], rot[8], tr[2]}};
1107 repJson[
"placement"] =
nullptr;
1115 repJson[
"closureApplicable"] = isSurface;
1117 repJson[
"reliability"] = reliabilityName;
1118 repJson[
"navigable"] = navigable;
1119 }
else if (rep.name ==
"mesh") {
1129 repJson[
"oracle"] = oracleJson;
1131 std::printf(
" --- representation '%s' (%s) against the same oracle answers ---\n",
1132 rep.name.c_str(), rep.shape->ClassName());
1133 repJson[
"oracle"] = scoreAgainstOracle(rep.shape, oracle, oracleOpt, allPoints,
1134 containsState, boundaryDistance,
samples,
1135 opt.only,
"R:" + rep.name,
1136 "oracle[" + rep.name +
"]", rep.placement);
1138 repJson[
"disagreements"] = countDisagreements(repJson[
"oracle"]);
1139 representationsJson.push_back(std::move(repJson));
1141 partJson[
"representations"] = std::move(representationsJson);
1145 if (opt.only.count(
"contains")) {
1147 printValidation(
"contains",
v);
1148 auto tc =
timeContains(candidate, allPoints, opt.warmup, opt.repeat);
1150 printTiming(
"contains", tc, tr);
1151 partJson[
"contains"] = {{
"validation", validationToJson(
v)},
1152 {
"timingCandidate", timingToJson(tc)},
1153 {
"timingReference", timingToJson(tr)}};
1155 if (opt.only.count(
"distout")) {
1157 printValidation(
"distout",
v);
1160 printTiming(
"distout", tc, tr);
1164 return surf.DistFromOutside_Loop(o.data(), d.data());
1166 printLoopSpeedup(
"distout", tc, tl);
1167 partJson[
"distout"] = {{
"validation", validationToJson(
v)},
1168 {
"timingCandidate", timingToJson(tc)},
1169 {
"timingReference", timingToJson(tr)},
1170 {
"timingCandidateLoop", timingToJson(tl)}};
1172 if (opt.only.count(
"distin")) {
1174 printValidation(
"distin",
v);
1177 printTiming(
"distin", tc, tr);
1180 return surf.DistFromInside_Loop(o.data(), d.data());
1182 printLoopSpeedup(
"distin", tc, tl);
1183 partJson[
"distin"] = {{
"validation", validationToJson(
v)},
1184 {
"timingCandidate", timingToJson(tc)},
1185 {
"timingReference", timingToJson(tr)},
1186 {
"timingCandidateLoop", timingToJson(tl)}};
1188 if (opt.only.count(
"safety")) {
1193 printValidation(
"safety(cand)", vc);
1194 printValidation(
"safety(ref)", vr);
1195 auto tc =
timeSafety(candidate, allPoints, opt.warmup, opt.repeat);
1197 printTiming(
"safety", tc, tr);
1198 partJson[
"safety"] = {{
"validationCandidate", validationToJson(vc)},
1199 {
"validationReference", validationToJson(vr)},
1200 {
"timingCandidate", timingToJson(tc)},
1201 {
"timingReference", timingToJson(tr)}};
1204 if (opt.loopCrosscheck) {
1210 size_t containsAgree = 0;
1211 size_t crossingDumps = 0;
1212 constexpr size_t kMaxCrossingDumps = 3;
1213 std::vector<O2BVHSurfaceSolid::ContainsCrossing> bvhCrossings;
1214 std::vector<O2BVHSurfaceSolid::ContainsCrossing> loopCrossings;
1215 for (
const auto& p : allPoints) {
1216 if (
surf.Contains(p.data()) ==
surf.Contains_Loop(p.data())) {
1223 if (crossingDumps++ >= kMaxCrossingDumps) {
1226 surf.DescribeContainsCrossings(p, bvhCrossings, loopCrossings);
1227 std::printf(
" BVH!=Loop at (%.9g,%.9g,%.9g): BVH=%d (%zu crossings) Loop=%d (%zu crossings)\n",
1228 p[0], p[1], p[2],
static_cast<int>(
surf.Contains(p.data())), bvhCrossings.size(),
1229 static_cast<int>(
surf.Contains_Loop(p.data())), loopCrossings.size());
1230 const size_t nShow = std::max(bvhCrossings.size(), loopCrossings.size());
1231 for (
size_t i = 0;
i < nShow; ++
i) {
1232 const char* bvhKind =
i < bvhCrossings.size()
1233 ? (bvhCrossings[
i].normalAlignment < 0. ?
"ENTER" :
"EXIT ")
1235 const char* loopKind =
i < loopCrossings.size()
1236 ? (loopCrossings[
i].normalAlignment < 0. ?
"ENTER" :
"EXIT ")
1238 const double bvhT =
i < bvhCrossings.size() ? bvhCrossings[
i].distance : -1.;
1239 const double loopT =
i < loopCrossings.size() ? loopCrossings[
i].distance : -1.;
1240 std::printf(
" [%2zu] BVH %s t=%-18.12g Loop %s t=%-18.12g%s\n",
i, bvhKind, bvhT,
1242 (
i < bvhCrossings.size() &&
i < loopCrossings.size() &&
1243 std::fabs(bvhT - loopT) > 1.e-12)
1248 std::printf(
" loop-crosscheck contains: BVH==Loop for %zu/%zu points\n", containsAgree, allPoints.size());
1249 partJson[
"loopCrosscheckContains"] = {{
"agree", containsAgree}, {
"total", allPoints.size()}};
1251 size_t outAgree = 0;
1252 double worstOutDeviation = 0.;
1253 for (
const auto&
r :
samples.outsideRays) {
1254 const double bvh =
surf.DistFromOutside(
r.origin.data(),
r.dir.data(), 3);
1255 const double loop =
surf.DistFromOutside_Loop(
r.origin.data(),
r.dir.data());
1259 worstOutDeviation = std::max(worstOutDeviation, std::fabs(
bvh - loop));
1262 std::printf(
" loop-crosscheck distout : BVH==Loop for %zu/%zu rays (worstDev=%.6g)\n", outAgree,
1263 samples.outsideRays.size(), worstOutDeviation);
1264 partJson[
"loopCrosscheckDistOutside"] = {
1265 {
"agree", outAgree}, {
"total",
samples.outsideRays.size()}, {
"worstDeviation", worstOutDeviation}};
1268 double worstInDeviation = 0.;
1269 for (
const auto&
r :
samples.insideRays) {
1270 const double bvh =
surf.DistFromInside(
r.origin.data(),
r.dir.data(), 3);
1271 const double loop =
surf.DistFromInside_Loop(
r.origin.data(),
r.dir.data());
1275 worstInDeviation = std::max(worstInDeviation, std::fabs(
bvh - loop));
1278 std::printf(
" loop-crosscheck distin : BVH==Loop for %zu/%zu rays (worstDev=%.6g)\n", inAgree,
1279 samples.insideRays.size(), worstInDeviation);
1280 partJson[
"loopCrosscheckDistInside"] = {
1281 {
"agree", inAgree}, {
"total",
samples.insideRays.size()}, {
"worstDeviation", worstInDeviation}};
1284 if (opt.pruningAb) {
1290 size_t identical = 0;
1291 std::vector<double> prunedValues;
1292 prunedValues.reserve(
samples.outsideRays.size());
1296 for (
const auto&
r :
samples.outsideRays) {
1297 prunedValues.push_back(
surf.DistFromOutside(
r.origin.data(),
r.dir.data(), 3));
1304 for (
size_t i = 0;
i <
samples.outsideRays.size(); ++
i) {
1306 if (
surf.DistFromOutside(
r.origin.data(),
r.dir.data(), 3) == prunedValues[
i]) {
1314 const double candidateRatio =
1315 unprunedCandidates > 0 ?
static_cast<double>(prunedCandidates) /
static_cast<double>(unprunedCandidates) : 0.;
1316 const double speedup = tPruned.nsPerCall > 0. ? tUnpruned.nsPerCall / tPruned.nsPerCall : 0.;
1317 std::printf(
" tmax-pruning A/B (distout, %zu rays): identical=%zu/%zu\n",
samples.outsideRays.size(), identical,
1319 std::printf(
" candidates: pruned=%lld unpruned=%lld (%.1f%% of the work)\n", prunedCandidates,
1320 unprunedCandidates, 100. * candidateRatio);
1321 std::printf(
" time : pruned=%9.1f ns/call unpruned=%9.1f ns/call speedup=%.2fx\n",
1322 tPruned.nsPerCall, tUnpruned.nsPerCall, speedup);
1324 pruningJson[
"identical"] = identical;
1325 pruningJson[
"total"] =
samples.outsideRays.size();
1326 pruningJson[
"candidatesPruned"] = prunedCandidates;
1327 pruningJson[
"candidatesUnpruned"] = unprunedCandidates;
1328 pruningJson[
"timingPruned"] = timingToJson(tPruned);
1329 pruningJson[
"timingUnpruned"] = timingToJson(tUnpruned);
1330 partJson[
"tmaxPruningAB"] = std::move(pruningJson);
1333 jsonReport.push_back(std::move(partJson));
1339 if (!unreliableParts.empty()) {
1341 "\n*** %zu of %zu part(s) are NOT navigable; their accuracy columns above measure an\n"
1342 "*** undefined answer, not the exact solid's error.\n",
1343 unreliableParts.size(), parts.size());
1344 for (
const auto&
id : unreliableParts) {
1345 std::printf(
"*** %s\n",
id.c_str());
1348 std::printf(
"\nAll %zu part(s) closed consistently oriented manifolds: navigation results are meaningful.\n",
1356 bool anyRepresentations =
false;
1357 for (
const auto& partJson : jsonReport) {
1358 anyRepresentations = anyRepresentations || partJson.contains(
"representations");
1360 if (anyRepresentations) {
1361 std::printf(
"\n=== REPRESENTATION SCORECARD (disagreements outside tolerance, all four columns) ===\n");
1362 for (
const auto& partJson : jsonReport) {
1363 if (!partJson.contains(
"representations")) {
1366 std::printf(
" %-46s", partJson.at(
"id").get<std::string>().c_str());
1367 for (
const auto& rep : partJson.at(
"representations")) {
1368 const double capacityDeviation =
1369 rep.at(
"oracle").value(
"capacityRelativeDeviation", 0.);
1370 const bool capacityComparable = rep.value(
"capacityComparable",
false);
1371 char capacityText[32];
1372 if (capacityComparable) {
1373 std::snprintf(capacityText,
sizeof(capacityText),
"%.2g", std::fabs(capacityDeviation));
1375 std::snprintf(capacityText,
sizeof(capacityText),
"n/a");
1377 std::printf(
" %s=%zu (cap %s)", rep.at(
"name").get<std::string>().c_str(),
1378 rep.value(
"disagreements",
size_t{0}), capacityText);
1384 if (!opt.jsonOut.empty()) {
1385 std::ofstream out(opt.jsonOut);
1386 out << jsonReport.dump(1);
1387 std::printf(
"\nWrote %s\n", opt.jsonOut.c_str());
Validation and timing harness for TGeoShape navigation, typed on plain TGeoShape*.
bool fileExists(const char *filename)
void CloseShape(bool check=true, bool fixFlipped=true, bool verbose=true)
Close the shape: calculate bounding box and compact vertices.
bool IsClosedBody() const
static void SetRayTMaxPruning(bool enable)
Ray tmax tightening in the distance queries, on by default; it never changes an answer....
static long long GetRayCandidateCount()
static const char * GetNavigationReliabilityName(NavigationReliability reliability)
static void ResetRayCandidateCounter()
Per-thread count of surfaces handed to the BVH leaf callback by DistFrom* since the last reset.
GLuint const GLchar * name
GLsizei GLsizei GLchar * source
GLenum GLsizei GLsizei GLint * values
GLuint GLsizei const GLchar * label
GLsizei const GLchar *const * path
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat t0
GLboolean GLboolean GLboolean GLboolean a
GLsizei const GLint * box
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat t1
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
bool LoadFacetSolid(const std::string &file, o2::base::O2Tessellated &solid)
bool LoadSurfaceSolid(const std::string &file, O2BVHSurfaceSolid &solid)
std::string to_string(gsl::span< T, Size > span)
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
int nBoundary
points within boundaryBand of the reference surface
uint64_t seed
every SampleSet is fully determined by this and the bbox
int nInside
points accepted by the reference Contains()
std::vector< Ray > outsideRays
std::vector< Ray > insideRays
uint64_t checksum
accumulated from the results so the optimizer cannot elide the calls
double distanceTolerance
absolute agreement tolerance for distances (cm)
std::map< std::string, ID > expected