19#include "TGeoMatrix.h"
40constexpr Int_t kIact = 3;
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)};
55Point3D isotropicDir(std::mt19937_64& rng)
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);
65bool isBig(
double d) {
return d >= 0.9 * TGeoShape::Big(); }
75 bits += 0x9e3779b97f4a7c15ULL + (acc << 6) + (acc >> 2);
88 const Point3D halfExtent = scale(sub(bboxMax, bboxMin), 0.5);
94 const double diag = std::sqrt(normSq(sub(bboxMax, bboxMin)));
98 std::mt19937_64 rng(cfg.
seed);
101 for (
int i = 0;
i < cfg.
nBulk; ++
i) {
102 out.
bulkPoints.push_back(sampleUniform(rng, inflatedLo, inflatedHi));
108 long long attempts = 0;
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);
123 long long attempts = 0;
126 const Point3D p = sampleUniform(rng, bboxMin, bboxMax);
135 std::uniform_real_distribution<double> u01(0., 1.);
137 long long attempts = 0;
140 const Point3D origin = sampleUniform(rng, inflatedLo, inflatedHi);
148 double len = std::sqrt(normSq(delta));
150 dir = isotropicDir(rng);
152 dir = scale(delta, 1. /
len);
155 dir = isotropicDir(rng);
164 long long attempts = 0;
183enum class MismatchClass { WithinBand,
187void recordOffender(ValidationResult&
result,
const ValidationOptions& opt, Offender&& off,
188 MismatchClass mismatchClass)
190 switch (mismatchClass) {
191 case MismatchClass::WithinBand:
192 ++
result.nMismatchWithinBand;
194 case MismatchClass::MissedSurface:
195 ++
result.nMismatchMissedSurface;
197 case MismatchClass::Unexplained:
198 ++
result.nMismatchUnexplained;
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);
211double allowedCrossingShift(
const TGeoShape* normalSource,
const Point3D& probePoint,
212 const Point3D& dir,
const ValidationOptions& opt,
double& cosIncidence)
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);
226 const double effectiveCosine = std::max(cosIncidence, opt.minIncidenceCosine);
227 return std::max(opt.distanceTolerance, opt.meshBand / effectiveCosine);
232MismatchClass classifyDistanceMismatch(
const TGeoShape*
reference,
const Ray& ray,
double dc,
233 double dr,
bool dcBig,
bool drBig,
234 const ValidationOptions& opt,
double& cosIncidence)
239 if (dcBig != drBig) {
240 return MismatchClass::MissedSurface;
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;
254 for (
const auto& p : points) {
255 const bool bc = candidate->Contains(p.data());
256 const bool br =
reference->Contains(p.data());
261 const double refSafety =
reference->Safety(p.data(), br);
270 recordOffender(
result, opt, std::move(off),
271 refSafety < opt.
meshBand ? MismatchClass::WithinBand : MismatchClass::Unexplained);
279ValidationResult validateDistance(
const TGeoShape* candidate,
const TGeoShape*
reference,
280 const std::vector<Ray>& rays,
const ValidationOptions& opt,
bool inside)
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);
287 result.nSamples = rays.size();
288 for (
const auto&
r : rays) {
289 const double dc =
distance(candidate,
r);
291 const bool dcBig = isBig(dc);
292 const bool drBig = isBig(dr);
293 if (dcBig && drBig) {
297 if (!dcBig && !drBig && std::fabs(dc - dr) <= opt.distanceTolerance) {
301 double cosIncidence = 1.;
302 const MismatchClass mismatchClass =
303 classifyDistanceMismatch(
reference,
r, dc, dr, dcBig, drBig, opt, cosIncidence);
305 off.point =
r.origin;
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);
320 return validateDistance(candidate,
reference, rays, opt,
false);
326 return validateDistance(candidate,
reference, rays, opt,
true);
332 static const std::array<Point3D, 6> kProbeDirs = {
338 for (
const auto& p : points) {
339 const bool in = shape->Contains(p.data());
340 const double s = shape->Safety(p.data(), in);
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);
351 if (!violatesLowerBound && !violatesUpperBound) {
361 recordOffender(
result, opt, std::move(off), MismatchClass::Unexplained);
371constexpr double kUnknownDistance = -1.;
373double oracleDistanceAt(
const std::vector<double>& distances,
size_t index)
375 return index < distances.size() ? distances[
index] : kUnknownDistance;
380 const std::vector<Point3D>& points,
381 const std::vector<int>& oracleState,
382 const std::vector<double>& oracleBoundaryDistance,
388 const int state =
index < oracleState.size() ? oracleState[
index] : -1;
389 const double boundaryDistance = oracleDistanceAt(oracleBoundaryDistance,
index);
391 if (
state < 0 || (boundaryDistance >= 0. && boundaryDistance < opt.
meshBand)) {
395 const bool candidateInside = candidate->Contains(points[
index].
data());
396 if (candidateInside == (
state == 1)) {
406 off.
deviation = boundaryDistance >= 0. ? boundaryDistance : 0.;
408 recordOffender(
result, opt, std::move(off), MismatchClass::Unexplained);
414 const std::vector<Ray>& rays,
415 const std::vector<double>& oracleDistance,
417 const std::vector<int>& oracleOriginState)
422 if (
index >= oracleDistance.size()) {
427 bool askInside = wantInside;
428 if (
index < oracleOriginState.size()) {
434 askInside =
state == 1;
435 if (askInside != wantInside) {
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) {
455 double cosIncidence = 1.;
456 const MismatchClass mismatchClass =
457 classifyDistanceMismatch(
nullptr, ray, dc, dr, dcBig, drBig, opt, cosIncidence);
459 off.
point = ray.origin;
465 recordOffender(
result, opt, std::move(off), mismatchClass);
471 const std::vector<Point3D>& points,
472 const std::vector<double>& oracleBoundaryDistance,
478 const double trueDistance = oracleDistanceAt(oracleBoundaryDistance,
index);
479 if (trueDistance < 0.) {
483 const bool inside = candidate->Contains(points[
index].
data());
484 const double safety = candidate->Safety(points[
index].
data(), inside);
488 if (!violatesLowerBound && !violatesUpperBound) {
498 recordOffender(
result, opt, std::move(off), MismatchClass::Unexplained);
508template <
typename Po
intKernel>
509TimingResult timePointKernel(
const std::vector<Point3D>& points,
int warmupRepeats,
int timedRepeats,
510 PointKernel&& kernel)
512 for (
int warmup = 0; warmup < warmupRepeats; ++warmup) {
513 for (
const auto& point : points) {
514 volatile double sink = kernel(point);
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) {
525 const auto stop = std::chrono::steady_clock::now();
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;
538 return timePointKernel(points, warmupRepeats, timedRepeats,
539 [&](
const Point3D& p) {
return shape->Contains(p.data()) ? 1. : 0.; });
543 int timedRepeats,
double stepmax)
546 return shape->DistFromOutside(
origin.data(), dir.data(), kIact, stepmax);
554 return shape->DistFromInside(
origin.data(), dir.data(), kIact, TGeoShape::Big());
561 return timePointKernel(points, warmupRepeats, timedRepeats,
562 [&](
const Point3D& p) {
return shape->Safety(p.data(), shape->Contains(p.data())); });
571constexpr const char* kShapeKeyName =
"shape";
574constexpr const char* kPlacementKeyName =
"placement";
579 const auto fail = [error](
const std::string& why) -> TGeoShape* {
580 if (error !=
nullptr) {
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");
590 if (
object ==
nullptr) {
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();
601 if (
object ==
nullptr) {
602 return fail(
path +
": holds no object inheriting from TGeoShape (expected key \"" +
603 kShapeKeyName +
"\")");
605 auto* shape =
dynamic_cast<TGeoShape*
>(
object);
606 if (shape ==
nullptr) {
607 const std::string className =
object->ClassName();
609 return fail(
path +
": key \"" + kShapeKeyName +
"\" holds a " + className +
610 ", which does not inherit from TGeoShape");
614 if (
auto* flat =
dynamic_cast<O2FlatCSG*
>(shape); flat !=
nullptr && !flat->
IsClosed()) {
617 ": the O2FlatCSG it holds refused to close, so its sub-cell boxes could "
618 "not be rebuilt (see the Error above)");
627 std::unique_ptr<TFile> file(TFile::Open(
path.c_str(),
"READ"));
628 if (!file || file->IsZombie()) {
631 auto* stored = file->Get<TGeoHMatrix>(kPlacementKeyName);
632 if (stored ==
nullptr) {
636 auto* placement =
new TGeoHMatrix(*stored);
646 const TGeoMatrix* placement, std::string* error)
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";
655 const int written = file->WriteTObject(&shape, kShapeKeyName);
657 if (placement !=
nullptr && !placement->IsIdentity()) {
658 TGeoHMatrix stored(*placement);
659 stored.SetName(kPlacementKeyName);
660 file->WriteTObject(&stored, kPlacementKeyName);
664 if (error !=
nullptr) {
665 *error =
path +
": WriteTObject wrote 0 bytes";
header::DataOrigin origin
Validation and timing harness for TGeoShape navigation, typed on plain TGeoShape*.
GLboolean GLboolean GLboolean b
GLsizei GLsizei GLfloat distance
GLsizei const GLfloat * value
GLenum GLint GLenum GLsizei GLsizei GLsizei GLint GLsizei const void * bits
typedef void(APIENTRYP PFNGLCULLFACEPROC)(GLenum mode)
GLsizei const GLchar *const * path
GLenum GLenum GLsizei len
GLboolean GLboolean GLboolean GLboolean a
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)