44#include <TGeoManager.h>
45#include <TGeoMatrix.h>
46#include <TGeoMedium.h>
52#include <TGeoVolume.h>
56#include <boost/program_options.hpp>
57#include <nlohmann/json.hpp>
73using json = nlohmann::json;
83template <
typename... Args>
84std::string form(
const char*
fmt, Args... args)
88 return std::string(
buffer);
95 void operator()(
const std::string& line)
97 std::cout << line <<
'\n';
98 mLines.push_back(line);
100 void write(
const std::string&
path)
const
102 std::ofstream out(
path);
103 for (
const auto& line : mLines) {
109 std::vector<std::string> mLines;
113void progress(
const std::string& line) { std::cerr << line << std::endl; }
134constexpr const char* kFieldObjectKey =
"MagneticField";
135constexpr const char* kFieldProbeKey =
"ReferenceProbes";
140 const int n = probes.GetNrows() / 6;
143 for (
int i = 0;
i <
n; ++
i) {
144 double x[3] = {probes[6 *
i], probes[6 *
i + 1], probes[6 *
i + 2]};
145 double b[3] = {0., 0., 0.};
147 for (
int k = 0; k < 3; ++k) {
148 const double want = probes[6 *
i + 3 + k];
149 same = same && (
b[k] == want);
150 flipped = flipped && (
b[k] == -want);
153 return same ? 0 : (flipped ? 1 : -1);
158 TFile* file = TFile::Open(
path.c_str());
159 if (file ==
nullptr || file->IsZombie()) {
160 progress(
"error: cannot open field file " +
path);
164 auto* probes =
dynamic_cast<TVectorD*
>(file->Get(kFieldProbeKey));
165 if (field ==
nullptr) {
166 progress(form(
"error: no '%s' object in %s", kFieldObjectKey,
path.c_str()));
169 if (probes ==
nullptr) {
171 "error: no '%s' in %s -- the field cannot be verified against what was written, "
172 "and a silently inverted field is exactly what this check exists to catch. "
173 "Use --field-current instead.",
174 kFieldProbeKey,
path.c_str()));
186 int comparison = compareToProbes(field,
reference);
187 if (comparison == 1) {
190 comparison = compareToProbes(field,
reference);
191 if (comparison == 0) {
192 progress(
"field: polarity flip from the non-idempotent CreateField() detected and repaired");
195 if (comparison != 0) {
197 "error: the field reloaded from %s does not reproduce its own reference probes; "
198 "refusing to hand back a field that is not the one written",
202 progress(form(
"field: %s verified against %d reference probe vectors",
path.c_str(),
reference.GetNrows() / 6));
208 double point[3] = {
x,
y,
z};
209 double b[3] = {0., 0., 0.};
211 return std::sqrt(
b[0] *
b[0] +
b[1] *
b[1] +
b[2] *
b[2]);
216 return fieldMag(field,
r * std::cos(phi),
r * std::sin(phi),
z);
249constexpr int kMinPhiSamples = 24;
250constexpr double kPhiArcStep = 3.0;
251constexpr double kBisectionTol = 0.01;
252constexpr double kZStepCoarse = 1.0;
253constexpr double kZStepRefine = 0.1;
254constexpr double kScanRMax = 2100.;
255constexpr double kScanRMaxFine = 900.;
256constexpr double kTightMargin = 0.05;
257constexpr long kViolationScanPoints = 400000;
265 std::vector<Interval> iv;
269 double thresholdKG = 0.;
270 std::vector<Band> bands;
274 std::vector<Model> models;
275 double edgeUncertainty = kBisectionTol;
276 double marginStrict = 5.0;
277 double marginTight = kTightMargin;
278 double zmin = 0., zmax = 0., rmax = 0.;
279 std::string parameterisation;
285 double separation(
int t,
double vzmin,
double vzmax,
double vrmin,
double vrmax)
const
288 for (
const auto& band : models[t].bands) {
289 const double dz = std::max(0., std::max(band.zlo - vzmax, vzmin - band.zhi));
290 for (
const auto& iv : band.iv) {
291 const double lo = iv.lo - edgeUncertainty;
292 const double hi = iv.hi + edgeUncertainty;
293 const double dr = std::max(0., std::max(lo - vrmax, vrmin - hi));
294 best = std::min(best, std::sqrt(dz * dz + dr * dr));
300 return (best > 1e29) ? 1e30 : best;
303 bool supportAt(
int t,
double z,
double r)
const {
return separation(t,
z,
z,
r,
r) <= 0.; }
309 double penetration(
int t,
double vzmin,
double vzmax,
double vrmin,
double vrmax)
const
312 for (
const auto& band : models[t].bands) {
313 if (band.zlo > vzmax || band.zhi < vzmin) {
316 for (
const auto& iv : band.iv) {
317 const double lo = iv.lo - edgeUncertainty;
318 const double hi = iv.hi + edgeUncertainty;
319 if (lo > vrmax || hi < vrmin) {
322 worst = std::max(worst, std::min(hi - vrmin, vrmax - lo));
335 bool coveredBySupport(
int t,
double vzmin,
double vzmax,
double vrmin,
double vrmax)
const
337 std::vector<std::pair<double, double>> covering;
338 for (
const auto& band : models[t].bands) {
339 if (band.zhi < vzmin || band.zlo > vzmax) {
342 for (
const auto& iv : band.iv) {
343 if (iv.lo - edgeUncertainty <= vrmin && iv.hi + edgeUncertainty >= vrmax) {
344 covering.push_back({band.zlo, band.zhi});
349 std::sort(covering.begin(), covering.end());
350 double frontier = vzmin;
351 for (
const auto&
segment : covering) {
352 if (
segment.first > frontier + 1e-9) {
355 frontier = std::max(frontier,
segment.second);
356 if (frontier >= vzmax) {
360 return frontier >= vzmax;
364 bool inDomain(
double vzmin,
double vzmax,
double vrmax)
const
366 return vzmin >= zmin && vzmax <= zmax && vrmax <= rmax;
370int phiSamplesAt(
double r)
375 return std::max(kMinPhiSamples, (
int)std::ceil(2 * M_PI *
r / kPhiArcStep));
381 return fieldMag(field, 0., 0.,
z);
383 const int n = phiSamplesAt(
r);
385 for (
int i = 0;
i <
n; ++
i) {
386 worst = std::max(worst, fieldMagCyl(field,
r, 2 * M_PI *
i /
n,
z));
391std::vector<double> radialGrid()
393 std::vector<double> grid;
394 for (
double r = 0.;
r < 20.;
r += 0.1) {
397 for (
double r = 20.;
r < 100.;
r += 1.0) {
400 for (
double r = 100.;
r < 800.;
r += 2.0) {
403 for (
double r = 800.;
r <= kScanRMaxFine;
r += 10.0) {
407 for (
double r = kScanRMaxFine + 25.;
r <= kScanRMax;
r += 25.0) {
419 for (
int i = 0;
i < 60 && std::fabs(rOut - rIn) > kBisectionTol; ++
i) {
420 const double middle = 0.5 * (rOut + rIn);
421 if (maxFieldOverPhi(field, middle,
z) > threshold) {
427 return 0.5 * (rOut + rIn);
431 double z = 0., zlo = 0., zhi = 0.;
432 std::vector<std::vector<Interval>> iv;
439 slice.iv.resize(thresholds.size());
440 const std::vector<double> grid = radialGrid();
441 std::vector<double>
b(grid.size());
442 for (
size_t i = 0;
i < grid.size(); ++
i) {
443 b[
i] = maxFieldOverPhi(field, grid[
i],
z);
445 for (
size_t t = 0; t < thresholds.size(); ++t) {
447 Interval current{0., 0.};
448 for (
size_t i = 0;
i < grid.size(); ++
i) {
449 const bool above =
b[
i] > thresholds[t];
450 if (above && !open) {
451 current.lo = (
i > 0) ? std::max(0., bisectCrossing(field,
z, grid[
i - 1], grid[
i], thresholds[t])) : grid[
i];
453 }
else if (!above && open) {
454 current.hi = bisectCrossing(field,
z, grid[
i], grid[
i - 1], thresholds[t]);
455 slice.iv[t].push_back(current);
460 current.hi = grid.back();
461 slice.iv[t].push_back(current);
467void mergeIntervals(std::vector<Interval>& into,
const std::vector<Interval>& from)
469 into.insert(into.end(), from.begin(), from.end());
473 std::sort(into.begin(), into.end(), [](
const Interval&
a,
const Interval&
b) { return a.lo < b.lo; });
474 std::vector<Interval> merged{into.front()};
475 for (
size_t i = 1;
i < into.size(); ++
i) {
476 if (into[
i].lo <= merged.back().hi + 1e-9) {
477 merged.back().hi = std::max(merged.back().hi, into[
i].hi);
479 merged.push_back(into[
i]);
485bool sameStructure(
const std::vector<Interval>&
a,
const std::vector<Interval>&
b)
487 if (
a.size() !=
b.size()) {
490 for (
size_t i = 0;
i <
a.size(); ++
i) {
491 if (std::fabs(
a[
i].lo -
b[
i].lo) > 0.02 || std::fabs(
a[
i].hi -
b[
i].hi) > 0.02) {
498Support buildSupport(
o2::field::MagneticField* field,
const std::vector<double>& thresholds,
double zmin,
double zmax)
501 "support: scanning z %.0f..%.0f, dz %.1f cm refined to %.1f, r to %.0f cm, "
502 "phi by arc length <= %.1f cm (%d samples at r=200)",
503 zmin, zmax, kZStepCoarse, kZStepRefine, kScanRMax, kPhiArcStep, phiSamplesAt(200.)));
505 std::vector<Slice> slices;
507 bool havePrevious =
false;
508 for (
double z = zmin;
z <= zmax + 1e-9;
z += kZStepCoarse) {
509 Slice slice = sliceAt(field,
z, thresholds);
510 bool changed =
false;
511 for (
size_t t = 0; havePrevious && t < thresholds.size(); ++t) {
512 changed = changed || !sameStructure(previous.iv[t], slice.iv[t]);
515 for (
double zz = previous.z + kZStepRefine; zz <
z - 1e-9; zz += kZStepRefine) {
516 slices.push_back(sliceAt(field, zz, thresholds));
519 slices.push_back(slice);
522 if (std::fmod(
z - zmin, 500.) < kZStepCoarse / 2) {
523 progress(form(
"support: ... z = %.0f",
z));
527 for (
size_t i = 0;
i < slices.size(); ++
i) {
528 const double zPrev = (
i == 0) ? slices[
i].
z - kZStepCoarse : slices[
i - 1].z;
529 const double zNext = (
i + 1 == slices.size()) ? slices[
i].
z + kZStepCoarse : slices[
i + 1].z;
530 slices[
i].zlo = 0.5 * (zPrev + slices[
i].z);
531 slices[
i].zhi = 0.5 * (slices[
i].z + zNext);
542 support.models.resize(thresholds.size());
543 for (
size_t t = 0; t < thresholds.size(); ++t) {
544 support.models[t].thresholdKG = thresholds[t];
546 while (
i < slices.size()) {
548 while (
j + 1 < slices.size() && sameStructure(slices[
j].iv[t], slices[
j + 1].iv[t])) {
552 band.zlo = slices[
i].zlo - 0.5 * kZStepCoarse;
553 band.zhi = slices[
j].zhi + 0.5 * kZStepCoarse;
554 for (
size_t k =
i; k <=
j; ++k) {
555 mergeIntervals(band.iv, slices[k].iv[t]);
557 if (!band.iv.empty()) {
558 support.models[t].bands.push_back(band);
565 support.rmax = kScanRMax;
578 TRandom3 random(10001);
579 std::vector<long> violations(support.models.size(), 0);
580 std::vector<double> worst(support.models.size(), 0.);
581 std::vector<double> worstR(support.models.size(), 0.);
582 std::vector<double> worstZ(support.models.size(), 0.);
583 for (
long i = 0;
i < kViolationScanPoints; ++
i) {
586 const bool nearAxis = (
i % 2 == 1);
587 const double z = nearAxis ? random.Uniform(std::max(support.zmin, -2200.), std::min(support.zmax, 2200.))
588 : random.Uniform(support.zmin, support.zmax);
589 const double r = random.Uniform(0., nearAxis ? 20. : support.rmax);
590 const double b = fieldMagCyl(field,
r, random.Uniform(0., 2 * M_PI),
z);
591 for (
size_t t = 0; t < support.models.size(); ++t) {
592 if (
b > support.models[t].thresholdKG && !support.supportAt(t,
z,
r)) {
603 for (
size_t t = 0; t < support.models.size(); ++t) {
604 report(form(
" outer bound at %6.1f G: %ld / %ld sampled points with field outside every band%s",
605 support.models[t].thresholdKG * 1000., violations[t], kViolationScanPoints,
606 violations[t] == 0 ?
" (bound holds)" :
" <-- THE MODEL IS NOT AN OUTER BOUND"));
607 if (violations[t] != 0) {
608 report(form(
" worst: |B| = %.4f kG at r = %.3f, z = %.3f", worst[t], worstR[t], worstZ[t]));
615json supportToJson(
const Support& support,
const std::string& fieldSource)
617 const std::time_t now = std::time(
nullptr);
619 std::strftime(stamp,
sizeof(stamp),
"%Y-%m-%dT%H:%M:%S", std::gmtime(&now));
622 out[
"schema"] =
"o2-sim-geometry-doctor/field_support/1";
623 out[
"generated_utc"] = stamp;
624 out[
"field_source"] = fieldSource;
625 out[
"parameterisation"] = support.parameterisation;
626 out[
"units"] =
"kGauss, cm";
628 "Outer bound on the support of |B|, maximised over phi. A point outside every band, "
629 "after expanding intervals by edge_uncertainty_cm, has |B| <= threshold.";
630 out[
"resolution"] = {{
"dz_coarse", kZStepCoarse},
631 {
"dz_refine", kZStepRefine},
632 {
"dr_near_axis", 0.1},
633 {
"phi_arc_step_cm", kPhiArcStep},
634 {
"phi_min_samples", kMinPhiSamples},
635 {
"bisection_tol_cm", kBisectionTol},
636 {
"edge_uncertainty_cm", support.edgeUncertainty}};
637 out[
"domain"] = {{
"zmin", support.zmin}, {
"zmax", support.zmax}, {
"rmax", support.rmax}};
638 out[
"recommended_margins_cm"] = {{
"strict", support.marginStrict}, {
"tight", support.marginTight}};
639 out[
"models"] = json::array();
640 for (
const auto& model : support.models) {
642 m[
"threshold_kG"] = model.thresholdKG;
643 m[
"threshold_gauss"] = model.thresholdKG * 1000.;
644 m[
"n_bands"] = model.bands.size();
645 m[
"bands"] = json::array();
646 for (
const auto& band : model.bands) {
650 b[
"iv"] = json::array();
651 for (
const auto& iv : band.iv) {
652 b[
"iv"].push_back(json::array({iv.lo, iv.hi}));
654 m[
"bands"].push_back(
b);
656 out[
"models"].push_back(
m);
663bool supportFromJson(
const json& in, Support& support)
666 support.edgeUncertainty = in.at(
"resolution").at(
"edge_uncertainty_cm").get<
double>();
667 support.marginStrict = in.at(
"recommended_margins_cm").at(
"strict").get<
double>();
668 support.marginTight = in.at(
"recommended_margins_cm").at(
"tight").get<
double>();
669 support.zmin = in.at(
"domain").at(
"zmin").get<
double>();
670 support.zmax = in.at(
"domain").at(
"zmax").get<
double>();
671 support.rmax = in.at(
"domain").at(
"rmax").get<
double>();
672 support.parameterisation = in.value(
"parameterisation", std::string());
673 for (
const auto&
m : in.at(
"models")) {
675 model.thresholdKG =
m.at(
"threshold_kG").get<
double>();
676 for (
const auto&
b :
m.at(
"bands")) {
678 band.zlo =
b.at(
"zlo").get<
double>();
679 band.zhi =
b.at(
"zhi").get<
double>();
680 for (
const auto& iv :
b.at(
"iv")) {
681 band.iv.push_back({iv.at(0).get<
double>(), iv.at(1).get<
double>()});
683 model.bands.push_back(band);
685 support.models.push_back(model);
687 }
catch (
const std::exception& e) {
688 progress(std::string(
"error: cannot read the support model: ") + e.what());
691 if (support.models.empty() || support.models.front().bands.empty()) {
693 "error: the support model is empty -- refusing to continue, since an empty model "
694 "would declare the whole geometry field-free");
704constexpr int kMaxDepth = 14;
705constexpr size_t kMaxRows = 400000;
706constexpr double kSampleDr = 0.05;
707constexpr double kSampleArc = 0.5;
708constexpr double kSampleDzMax = 2.0;
709constexpr long kMaxSamplesPerRow = 4000000;
712 std::string
path, lv, medium, mother, shape;
713 std::string effectiveMother;
714 std::string verdict =
"UNCLASSIFIED";
716 int copyNo = 0, nDaughters = 0,
depth = 0;
717 bool sensitive =
false, assembly =
false, approximateExtent =
false, resolved =
true;
718 double zmin = 0., zmax = 0., rmin = 0., rmax = 0.;
719 double separation = -1., penetration = 0.;
720 double maxB = -1., minB = -1.;
722 bool wholeVolumeSampled =
false;
723 double wholeMaxB = -1., wholeMinB = -1.;
724 TGeoNode*
node =
nullptr;
728bool isOutFamily(
const std::string& verdict)
730 return verdict ==
"OUT" || verdict ==
"OUT_TIGHT" || verdict ==
"OUT_BOUNDARY";
737bool isInFamily(
const std::string& verdict)
739 return verdict ==
"IN" || verdict ==
"IN_COVERED" || verdict ==
"UNKNOWN" || verdict ==
"OUTSIDE_DOMAIN";
750bool shapeRadii(TGeoShape* shape,
double& rmin,
double& rmax)
752 if (
auto* pgon =
dynamic_cast<TGeoPgon*
>(shape)) {
755 for (
int i = 0;
i < pgon->GetNz(); ++
i) {
756 rmin = std::min(rmin, pgon->GetRmin(
i));
757 rmax = std::max(rmax, pgon->GetRmax(
i));
759 const double edges = pgon->GetNedges() > 2 ? pgon->GetNedges() : 3;
760 rmax /= std::cos(M_PI / edges);
763 if (
auto* pcon =
dynamic_cast<TGeoPcon*
>(shape)) {
766 for (
int i = 0;
i < pcon->GetNz(); ++
i) {
767 rmin = std::min(rmin, pcon->GetRmin(
i));
768 rmax = std::max(rmax, pcon->GetRmax(
i));
772 if (
auto* cone =
dynamic_cast<TGeoCone*
>(shape)) {
773 rmin = std::min(cone->GetRmin1(), cone->GetRmin2());
774 rmax = std::max(cone->GetRmax1(), cone->GetRmax2());
777 if (
auto* eltu =
dynamic_cast<TGeoEltu*
>(shape)) {
779 rmax = std::max(eltu->GetA(), eltu->GetB());
782 if (
auto* tube =
dynamic_cast<TGeoTube*
>(shape)) {
783 rmin = tube->GetRmin();
784 rmax = tube->GetRmax();
790bool zPreserving(
const TGeoHMatrix&
m)
792 const Double_t*
r =
m.GetRotationMatrix();
793 return std::fabs(std::fabs(
r[8]) - 1.) < 1e-9 && std::fabs(
r[2]) < 1e-9 && std::fabs(
r[5]) < 1e-9 &&
794 std::fabs(
r[6]) < 1e-9 && std::fabs(
r[7]) < 1e-9;
797void extentOf(TGeoNode*
node,
const TGeoHMatrix& matrix, Row&
row)
799 TGeoShape* shape =
node->GetVolume()->GetShape();
801 if (
box ==
nullptr) {
807 row.approximateExtent =
true;
814 const double dx =
box->GetDX(), dy =
box->GetDY(), dz =
box->GetDZ();
816 for (
int i = 0;
i < 8; ++
i) {
817 double local[3] = {
origin[0] + ((
i & 1) ? dx : -dx),
origin[1] + ((
i & 2) ? dy : -dy),
818 origin[2] + ((
i & 4) ? dz : -dz)};
820 matrix.LocalToMaster(local, global);
821 row.zmin = std::min(
row.zmin, global[2]);
822 row.zmax = std::max(
row.zmax, global[2]);
823 boxRmax = std::max(boxRmax, std::hypot(global[0], global[1]));
826 const double* translation = matrix.GetTranslation();
827 const double offAxis = std::hypot(translation[0] +
origin[0], translation[1] +
origin[1]);
828 double localRmin = 0., localRmax = 0.;
829 if (zPreserving(matrix) && shapeRadii(shape, localRmin, localRmax)) {
830 row.rmin = std::max(0., localRmin - offAxis);
831 row.rmax = localRmax + offAxis;
832 row.approximateExtent =
false;
835 row.rmin = (offAxis <= std::hypot(dx, dy)) ? 0. : std::max(0., offAxis - std::hypot(dx, dy));
836 row.approximateExtent =
true;
844struct ContainerProposal {
845 std::string mother, motherPath;
846 double zlo, zhi, rmax;
848 bool clearedByStrictMargin;
854 std::vector<const Row*> out, in;
862 mThreshold = support.models.front().thresholdKG;
865 void walk(TGeoNode*
node) { walk(
node,
nullptr, TGeoHMatrix(), 0,
"",
""); }
869 const std::vector<Row>&
rows()
const {
return mRows; }
870 size_t nPruned()
const {
return mPruned; }
871 const std::vector<const Row*>& reverseAudit()
const {
return mReverse; }
872 const std::vector<SharedVolume>& sharedVolumes()
const {
return mShared; }
873 const std::vector<const Row*>& straddlingMothers()
const {
return mStraddling; }
874 const std::vector<ContainerProposal>& containers()
const {
return mContainers; }
875 bool hasSensitive(TGeoVolume* volume);
878 void walk(TGeoNode*
node, TGeoVolume* mother,
const TGeoHMatrix& parent,
int depth,
const std::string&
path,
879 const std::string& effectiveMother);
880 void classify(Row&
row);
881 void disproofScan(Row&
row);
882 void wholeVolumeScan(Row&
row);
883 bool ownMaterialAt(
const Row&
row,
const double* global)
const;
886 const Support& mSupport;
888 std::vector<Row> mRows;
889 std::map<TGeoVolume*, int> mSensitiveCache;
892 std::vector<const Row*> mReverse;
893 std::vector<SharedVolume> mShared;
894 std::vector<const Row*> mStraddling;
895 std::vector<ContainerProposal> mContainers;
898bool Doctor::hasSensitive(TGeoVolume* volume)
900 auto cached = mSensitiveCache.find(volume);
901 if (cached != mSensitiveCache.end() && cached->second >= 0) {
902 return cached->second == 1;
904 mSensitiveCache[volume] = 0;
905 auto* medium = volume->GetMedium();
906 bool found = medium !=
nullptr && medium->GetParam(0) != 0.;
907 for (
int i = 0;
i < volume->GetNdaughters() && !found; ++
i) {
908 found = hasSensitive(volume->GetNode(
i)->GetVolume());
910 mSensitiveCache[volume] = found ? 1 : 0;
920bool isStructural(TGeoNode*
node, TGeoVolume* mother)
922 if (mother ==
nullptr) {
925 TGeoVolume* volume =
node->GetVolume();
926 if (volume->IsAssembly()) {
929 auto* mine = volume->GetMedium();
930 auto* theirs = mother->GetMedium();
931 return mine !=
nullptr && theirs !=
nullptr && std::strcmp(mine->GetName(), theirs->GetName()) == 0;
934void Doctor::walk(TGeoNode*
node, TGeoVolume* mother,
const TGeoHMatrix& parent,
int depth,
const std::string&
path,
935 const std::string& effectiveMother)
937 if (mRows.size() >= kMaxRows ||
depth > kMaxDepth) {
940 TGeoHMatrix here = parent;
941 here.Multiply(
node->GetMatrix());
942 TGeoVolume* volume =
node->GetVolume();
947 row.lv = volume->GetName();
948 row.mother = mother !=
nullptr ? mother->GetName() :
"";
949 row.effectiveMother = effectiveMother;
950 row.shape = volume->GetShape()->ClassName();
951 row.copyNo =
node->GetNumber();
952 row.nDaughters = volume->GetNdaughters();
954 row.assembly = volume->IsAssembly();
955 auto* medium = volume->GetMedium();
956 row.medium = medium !=
nullptr ? medium->GetName() :
"(none)";
957 row.ifield = medium !=
nullptr ? (
int)medium->GetParam(1) : -1;
958 row.sensitive = medium !=
nullptr && medium->GetParam(0) != 0.;
962 mRows.push_back(
row);
964 if (hasSensitive(volume) && !isStructural(
node, mother)) {
968 for (
int i = 0;
i <
node->GetNdaughters(); ++
i) {
969 walk(
node->GetDaughter(
i), volume, here,
depth + 1, myPath,
row.assembly ? effectiveMother : myPath);
978bool insideAnyDaughter(TGeoVolume* volume,
const double* local)
980 for (
int i = 0;
i < volume->GetNdaughters(); ++
i) {
981 TGeoNode* daughter = volume->GetNode(
i);
982 double inDaughter[3];
983 daughter->GetMatrix()->MasterToLocal(local, inDaughter);
984 if (!daughter->GetVolume()->GetShape()->Contains(inDaughter)) {
987 if (daughter->GetVolume()->IsAssembly()) {
988 if (insideAnyDaughter(daughter->GetVolume(), inDaughter)) {
998bool Doctor::ownMaterialAt(
const Row&
row,
const double* global)
const
1001 row.matrix.MasterToLocal(global, local);
1002 if (!
row.node->GetVolume()->GetShape()->Contains(local)) {
1005 return !insideAnyDaughter(
row.node->GetVolume(), local);
1008double thinnestDaughter(TGeoVolume* volume)
1010 double thinnest = 1e30;
1011 for (
int i = 0;
i < volume->GetNdaughters(); ++
i) {
1012 auto*
box =
dynamic_cast<TGeoBBox*
>(volume->GetNode(
i)->GetVolume()->GetShape());
1013 if (
box !=
nullptr) {
1014 thinnest = std::min(thinnest, 2 * std::min(
box->GetDX(), std::min(
box->GetDY(),
box->GetDZ())));
1023void Doctor::disproofScan(Row&
row)
1025 const double thinnest = thinnestDaughter(
row.node->GetVolume());
1026 row.resolved = (
row.nDaughters == 0) || (kSampleDr <= std::max(0.1, thinnest));
1027 long budget = kMaxSamplesPerRow;
1028 for (
const auto& band : mSupport.models.front().bands) {
1029 const double z0 = std::max(
row.zmin, band.zlo);
1030 const double z1 = std::min(
row.zmax, band.zhi);
1034 for (
const auto& iv : band.iv) {
1035 const double r0 = std::max(
row.rmin, iv.lo - mSupport.edgeUncertainty);
1036 const double r1 = std::min(
row.rmax, iv.hi + mSupport.edgeUncertainty);
1040 const double dz = std::min(kSampleDzMax, std::max(0.25, (z1 - z0) / 200.));
1041 for (
double z = z0;
z <= z1 + 1e-9 && budget > 0;
z += dz) {
1042 for (
double r = r0;
r <= r1 + 1e-9 && budget > 0;
r += kSampleDr) {
1043 const int nphi = (
r <= 0.) ? 1 : std::max(8, (
int)std::ceil(2 * M_PI *
r / kSampleArc));
1044 for (
int i = 0; i < nphi && budget > 0; ++
i) {
1045 const double phi = 2 * M_PI *
i / nphi;
1046 double global[3] = {
r * std::cos(phi),
r * std::sin(phi),
z};
1048 if (!ownMaterialAt(
row, global)) {
1052 const double b = fieldMag(mField, global[0], global[1], global[2]);
1053 row.maxB = std::max(
row.maxB,
b);
1054 row.minB = (
row.minB < 0.) ?
b : std::min(
row.minB,
b);
1061 row.resolved =
false;
1068void Doctor::wholeVolumeScan(Row&
row)
1070 row.wholeVolumeSampled =
true;
1071 const double dz = std::max(1.0, (
row.zmax -
row.zmin) / 300.);
1072 const double dr = std::max(0.5, (
row.rmax -
row.rmin) / 200.);
1073 long budget = 2000000;
1074 for (
double z =
row.zmin;
z <=
row.zmax && budget > 0;
z += dz) {
1075 for (
double r =
row.rmin;
r <=
row.rmax && budget > 0;
r += dr) {
1076 const int nphi = (
r <= 0.) ? 1 : std::max(8, (
int)std::ceil(2 * M_PI *
r / std::max(2.0, dr)));
1077 for (
int i = 0; i < nphi && budget > 0; ++
i) {
1078 const double phi = 2 * M_PI *
i / nphi;
1079 double global[3] = {
r * std::cos(phi),
r * std::sin(phi),
z};
1081 if (!ownMaterialAt(
row, global)) {
1084 const double b = fieldMag(mField, global[0], global[1], global[2]);
1085 row.wholeMaxB = std::max(
row.wholeMaxB,
b);
1086 row.wholeMinB = (
row.wholeMinB < 0.) ?
b : std::min(
row.wholeMinB,
b);
1092void Doctor::classify(Row&
row)
1095 row.verdict =
"ASSEMBLY";
1098 if (!mSupport.inDomain(
row.zmin,
row.zmax,
row.rmax)) {
1099 row.verdict =
"OUTSIDE_DOMAIN";
1102 row.separation = mSupport.separation(0,
row.zmin,
row.zmax,
row.rmin,
row.rmax);
1103 if (
row.separation >= mSupport.marginStrict) {
1104 row.verdict =
"OUT";
1107 if (
row.separation >= mSupport.marginTight) {
1108 row.verdict =
"OUT_TIGHT";
1111 row.penetration = mSupport.penetration(0,
row.zmin,
row.zmax,
row.rmin,
row.rmax);
1112 if (mSupport.coveredBySupport(0,
row.zmin,
row.zmax,
row.rmin,
row.rmax)) {
1113 row.verdict =
"IN_COVERED";
1117 if (
row.maxB > mThreshold) {
1119 }
else if (
row.separation > 0. ||
row.penetration <= 2 * mSupport.edgeUncertainty) {
1123 row.verdict =
"OUT_BOUNDARY";
1125 row.verdict =
"UNKNOWN";
1129void Doctor::classifyAll()
1132 for (
auto&
row : mRows) {
1134 if (++
done % 50000 == 0) {
1135 progress(form(
"classify: %zu / %zu placements",
done, mRows.size()));
1138 for (
auto&
row : mRows) {
1141 if (
row.nDaughters > 0 && !
row.assembly &&
1142 (
row.verdict ==
"IN" ||
row.verdict ==
"UNKNOWN" ||
row.verdict ==
"OUT_BOUNDARY")) {
1143 wholeVolumeScan(
row);
1148void Doctor::findFindings()
1155 for (
auto&
row : mRows) {
1156 if (
row.assembly ||
row.ifield != 0 ||
row.medium ==
"dummy" ||
row.medium ==
"(none)") {
1159 if (isInFamily(
row.verdict)) {
1160 if (!
row.wholeVolumeSampled) {
1161 wholeVolumeScan(
row);
1163 mReverse.push_back(&
row);
1168 std::map<std::string, std::vector<const Row*>> byVolume;
1169 for (
const auto&
row : mRows) {
1170 if (!
row.assembly) {
1171 byVolume[
row.lv].push_back(&
row);
1174 for (
const auto&
entry : byVolume) {
1175 SharedVolume shared;
1176 shared.lv =
entry.first;
1177 for (
const auto*
row :
entry.second) {
1178 if (isOutFamily(
row->verdict)) {
1179 shared.out.push_back(
row);
1180 }
else if (isInFamily(
row->verdict)) {
1181 shared.in.push_back(
row);
1184 if (!shared.out.empty() && !shared.in.empty()) {
1185 mShared.push_back(shared);
1188 std::sort(mShared.begin(), mShared.end(),
1189 [](
const SharedVolume&
a,
const SharedVolume&
b) { return a.out.size() > b.out.size(); });
1192 for (
const auto&
row : mRows) {
1193 if (
row.nDaughters > 0 && !
row.assembly &&
row.wholeVolumeSampled &&
row.wholeMaxB > mThreshold &&
1194 row.wholeMinB <= mThreshold) {
1195 mStraddling.push_back(&
row);
1207 for (
const auto* mother : mStraddling) {
1208 std::vector<const Row*> kids;
1209 for (
const auto&
row : mRows) {
1210 if (
row.effectiveMother == mother->path && !
row.assembly) {
1211 kids.push_back(&
row);
1214 std::sort(kids.begin(), kids.end(), [](
const Row*
a,
const Row*
b) { return a->zmin < b->zmin; });
1217 while (
i < kids.size()) {
1218 if (!isOutFamily(kids[
i]->verdict)) {
1223 double zlo = kids[
i]->zmin, zhi = kids[
i]->zmax, rmax = kids[
i]->rmax;
1224 while (
j + 1 < kids.size() && isOutFamily(kids[
j + 1]->verdict)) {
1226 zlo = std::min(zlo, kids[
j]->zmin);
1227 zhi = std::max(zhi, kids[
j]->zmax);
1228 rmax = std::max(rmax, kids[
j]->rmax);
1230 const int n = (
int)(
j -
i + 1);
1237 double clampedLo = zlo, clampedHi = zhi;
1238 std::vector<const Row*> intruders;
1239 for (
size_t k = 0; k < kids.size(); ++k) {
1240 if (k >=
i && k <=
j) {
1243 const Row*
other = kids[k];
1244 if (
other->zmax <= zlo + 0.01 ||
other->zmin >= zhi - 0.01 ||
other->rmin >= rmax - 0.01) {
1247 intruders.push_back(
other);
1249 for (
const Row*
other : intruders) {
1250 if (
other->zmin <= clampedLo + 1e-9 &&
other->zmax > clampedLo) {
1251 clampedLo =
other->zmax;
1253 if (
other->zmax >= clampedHi - 1e-9 &&
other->zmin < clampedHi) {
1254 clampedHi =
other->zmin;
1257 bool residual =
false;
1258 for (
const Row*
other : intruders) {
1259 residual = residual || (
other->zmax > clampedLo + 0.01 &&
other->zmin < clampedHi - 0.01 &&
1260 other->rmin < rmax - 0.01);
1263 const double separation =
1264 (clampedHi > clampedLo) ? mSupport.separation(0, clampedLo, clampedHi, 0., rmax) : -1.;
1265 if (
n >= 2 && separation >= mSupport.marginTight && !residual) {
1266 ContainerProposal proposal;
1267 proposal.mother = mother->lv;
1268 proposal.motherPath = mother->path;
1269 proposal.zlo = clampedLo;
1270 proposal.zhi = clampedHi;
1271 proposal.rmax = rmax;
1272 proposal.nDaughters =
n;
1273 proposal.clearedByStrictMargin = separation >= mSupport.marginStrict;
1274 proposal.sensitive =
false;
1275 for (
size_t k =
i; k <=
j; ++k) {
1276 proposal.sensitive = proposal.sensitive || hasSensitive(kids[k]->
node->GetVolume());
1278 mContainers.push_back(proposal);
1289void writePlacementCsv(
const std::vector<Row>&
rows,
const std::string&
path)
1291 std::FILE* out = std::fopen(
path.c_str(),
"w");
1292 if (out ==
nullptr) {
1293 progress(
"error: cannot write " +
path);
1297 "path,lv,medium,ifield,shape,mother,copy,ndaughters,sensitive,assembly,approx,"
1298 "zmin,zmax,rmin,rmax,verdict,separation_cm,penetration_cm,maxB_kG,nsampled\n");
1299 for (
const auto&
row :
rows) {
1300 std::fprintf(out,
"%s,%s,%s,%d,%s,%s,%d,%d,%d,%d,%d,%.3f,%.3f,%.3f,%.3f,%s,%.4f,%.4f,%.6f,%ld\n",
row.path.c_str(),
1301 row.lv.c_str(),
row.medium.c_str(),
row.ifield,
row.shape.c_str(),
row.mother.c_str(),
row.copyNo,
1302 row.nDaughters, (
int)
row.sensitive, (
int)
row.assembly, (
int)
row.approximateExtent,
row.zmin,
row.zmax,
1308json placementJson(
const Row&
row)
1310 return json{{
"path",
row.path},
1311 {
"copy",
row.copyNo},
1312 {
"z", json::array({
row.zmin,
row.zmax})},
1313 {
"r", json::array({
row.rmin,
row.rmax})},
1314 {
"verdict",
row.verdict},
1315 {
"separation_cm",
row.separation},
1316 {
"max_B_kG", std::max(
row.maxB,
row.wholeMaxB)}};
1319json proposalsToJson(Doctor& doctor,
const Support& support,
const std::string& geometryFile,
1320 const std::string& fieldSource)
1322 const std::time_t now = std::time(
nullptr);
1324 std::strftime(stamp,
sizeof(stamp),
"%Y-%m-%dT%H:%M:%S", std::gmtime(&now));
1327 out[
"schema"] =
"o2-sim-geometry-doctor/proposals/1";
1328 out[
"generated_utc"] = stamp;
1329 out[
"geometry"] = geometryFile;
1330 out[
"field_source"] = fieldSource;
1331 out[
"threshold_kG"] = support.models.front().thresholdKG;
1332 out[
"margins_cm"] = {{
"strict", support.marginStrict},
1333 {
"tight", support.marginTight},
1334 {
"edge_uncertainty", support.edgeUncertainty}};
1335 out[
"proposals"] = json::array();
1337 for (
const auto& shared : doctor.sharedVolumes()) {
1338 bool refused =
false;
1339 for (
const auto*
row : shared.out) {
1340 refused = refused ||
row->sensitive || doctor.hasSensitive(
row->node->GetVolume());
1343 entry[
"signature"] =
"shared-volume";
1344 entry[
"action"] =
"split the logical volume, so that its field-free placements can carry a field-free medium";
1345 entry[
"logical_volume"] = shared.lv;
1346 entry[
"n_out"] = shared.out.size();
1347 entry[
"n_in"] = shared.in.size();
1348 entry[
"status"] = refused ?
"refused by default (sensitive path)" :
"proposed";
1349 entry[
"out_placements"] = json::array();
1350 for (
const auto*
row : shared.out) {
1351 entry[
"out_placements"].push_back(placementJson(*
row));
1353 entry[
"in_placements"] = json::array();
1354 for (
const auto*
row : shared.in) {
1355 entry[
"in_placements"].push_back(placementJson(*
row));
1357 out[
"proposals"].push_back(
entry);
1360 for (
const auto*
row : doctor.reverseAudit()) {
1361 const double maxB = std::max(
row->maxB,
row->wholeMaxB);
1363 entry[
"signature"] =
"reverse-audit";
1364 entry[
"action"] = maxB > support.models.front().thresholdKG
1365 ?
"the field-free medium assignment is wrong: straight-line transport inside real field"
1366 :
"the field-free medium reaches field support but no field was found in its own material, review";
1373 entry[
"min_B_kG"] =
row->wholeMinB;
1374 entry[
"max_B_kG"] = maxB;
1376 out[
"proposals"].push_back(
entry);
1379 for (
const auto& container : doctor.containers()) {
1381 entry[
"signature"] =
"missing-container";
1382 entry[
"action"] =
"insert a container with a field-free medium and re-parent the cluster into it";
1383 entry[
"mother"] = container.mother;
1384 entry[
"mother_path"] = container.motherPath;
1385 entry[
"z"] = json::array({container.zlo, container.zhi});
1386 entry[
"rmax"] = container.rmax;
1387 entry[
"n_daughters"] = container.nDaughters;
1388 entry[
"status"] = container.sensitive ?
"refused by default (sensitive path)" :
"proposed";
1389 entry[
"clearance"] = container.clearedByStrictMargin ?
"strict margin" :
"tight margin";
1390 out[
"proposals"].push_back(
entry);
1393 for (
const auto*
row : doctor.straddlingMothers()) {
1395 entry[
"signature"] =
"heterogeneous-mother";
1397 "the mother's own material spans both sides of the predicate, so no per-medium flag can "
1398 "express it; it needs a container";
1402 entry[
"min_B_kG"] =
row->wholeMinB;
1403 entry[
"max_B_kG"] =
row->wholeMaxB;
1404 entry[
"n_daughters"] =
row->nDaughters;
1405 out[
"proposals"].push_back(
entry);
1424bool verifyAnchors(
const std::string&
path, Doctor& doctor, Report&
report)
1426 std::ifstream in(
path);
1428 report(
" cannot open the anchor file " +
path);
1434 }
catch (
const std::exception& e) {
1435 report(std::string(
" cannot parse the anchor file: ") + e.what());
1439 std::set<std::string> flaggedByReverseAudit;
1440 for (
const auto*
row : doctor.reverseAudit()) {
1441 flaggedByReverseAudit.insert(
row->lv);
1444 bool allPassed =
true;
1445 report(form(
" %-22s %-22s %-10s %s",
"volume",
"expected",
"verdict",
"evidence"));
1446 for (
const auto& anchor : anchors.at(
"anchors")) {
1447 const auto volume = anchor.at(
"volume").get<std::string>();
1448 const auto expected = anchor.at(
"expect").get<std::string>();
1452 double worstSeparation = 1e30;
1453 double bestField = -1.;
1454 std::string reported;
1455 for (
const auto&
row : doctor.rows()) {
1456 if (
row.lv != volume) {
1462 ok = isOutFamily(
row.verdict) ||
row.verdict ==
"ASSEMBLY";
1464 ok =
row.verdict ==
"IN";
1465 }
else if (
expected ==
"NOT_OUT") {
1466 ok = !isOutFamily(
row.verdict);
1467 }
else if (
expected ==
"ASSEMBLY") {
1468 ok =
row.verdict ==
"ASSEMBLY";
1469 }
else if (
expected ==
"REVERSE_AUDIT_FLAGGED") {
1475 failures += ok ? 0 : 1;
1476 bestField = std::max(bestField, std::max(
row.maxB,
row.wholeMaxB));
1477 if (
row.separation < worstSeparation) {
1478 worstSeparation =
row.separation;
1479 reported =
row.verdict;
1483 if (
expected ==
"REVERSE_AUDIT_FLAGGED") {
1484 failures = flaggedByReverseAudit.count(volume) > 0 ? 0 : 1;
1485 reported = failures == 0 ?
"flagged" :
"not flagged";
1487 if (placements == 0) {
1489 reported =
"not placed";
1491 if (anchor.contains(
"placements") && placements != anchor.at(
"placements").get<
int>()) {
1494 if (anchor.contains(
"min_max_B_kG") && bestField < anchor.at(
"min_max_B_kG").get<
double>()) {
1498 allPassed = allPassed && failures == 0;
1499 report(form(
" %-22s %-22s %-10s %d placement(s), separation %.3f cm, max|B| %.4f kG %s", volume.c_str(),
1500 expected.c_str(), reported.c_str(), placements, worstSeparation > 1e29 ? -1. : worstSeparation,
1501 bestField, failures == 0 ?
"PASS" :
"FAIL"));
1509 std::string geometryFile;
1510 std::string fieldFile;
1511 int fieldCurrent = 0;
1512 std::string supportFile;
1513 std::string anchorFile;
1514 std::vector<double> thresholdsGauss;
1515 double margin = 5.0;
1516 std::string outputPrefix =
"geometry-doctor";
1525 "Audits a placed geometry against the magnetic field it will be transported "
1526 "in, and reports where the two do not fit together.\n\nOptions");
1528 (
"help,h",
"print this help message")
1529 (
"geometry-file", bpo::value<std::string>(&options.geometryFile)->required(),
1530 "the geometry to audit, e.g. o2sim_geometry.root")
1531 (
"field-file", bpo::value<std::string>(&options.fieldFile),
1532 "a serialized MagneticField carrying reference probe vectors")
1533 (
"field-current", bpo::value<int>(&options.fieldCurrent),
1534 "build the nominal field for this L3 current instead, e.g. -5")
1535 (
"support-file", bpo::value<std::string>(&options.supportFile),
1536 "field-support model cache: read it if it exists, otherwise write it")
1537 (
"threshold", bpo::value<std::vector<double>>(&options.thresholdsGauss)->composing(),
1538 "field threshold in Gauss, repeatable; the lowest one decides the verdicts (default 1 and 10)")
1539 (
"margin", bpo::value<double>(&options.margin)->default_value(5.0),
1540 "clearance in cm a placement must keep from the field support to be called field-free")
1541 (
"output-prefix", bpo::value<std::string>(&options.outputPrefix)->default_value(
"geometry-doctor"),
1542 "prefix for the report, the proposals and the placement table")
1543 (
"verify-anchors", bpo::value<std::string>(&options.anchorFile),
1544 "check the classification against known-good volumes listed in this JSON file");
1554 }
catch (
const bpo::error& e) {
1555 std::cerr <<
"error: " << e.what() <<
"\n\n"
1560 const bool haveFieldFile =
arguments.count(
"field-file") != 0u;
1561 const bool haveFieldCurrent =
arguments.count(
"field-current") != 0u;
1562 if (haveFieldFile == haveFieldCurrent) {
1563 std::cerr <<
"error: give exactly one of --field-file and --field-current\n";
1566 if (options.thresholdsGauss.empty()) {
1567 options.thresholdsGauss = {1., 10.};
1569 std::sort(options.thresholdsGauss.begin(), options.thresholdsGauss.end());
1570 std::vector<double> thresholds;
1571 for (
double gauss : options.thresholdsGauss) {
1572 thresholds.push_back(gauss * 1e-3);
1575 const std::string fieldSource =
1576 haveFieldFile ? options.fieldFile : form(
"createNominalField(%d)", options.fieldCurrent);
1579 if (field ==
nullptr) {
1580 std::cerr <<
"error: no usable magnetic field\n";
1585 report(
"ALICE simulation geometry doctor");
1587 report(
" geometry : " + options.geometryFile);
1592 bool supportFromCache =
false;
1593 if (!options.supportFile.empty()) {
1594 std::ifstream cache(options.supportFile);
1599 }
catch (
const std::exception& e) {
1600 std::cerr <<
"error: cannot parse " << options.supportFile <<
": " << e.what() <<
'\n';
1603 if (!supportFromJson(cached, support)) {
1606 supportFromCache =
true;
1610 if (supportFromCache) {
1611 if (support.models.size() != thresholds.size()) {
1612 std::cerr <<
"error: " << options.supportFile <<
" carries " << support.models.size()
1613 <<
" thresholds but " << thresholds.size() <<
" were requested\n";
1616 for (
size_t t = 0; t < thresholds.size(); ++t) {
1617 if (std::fabs(support.models[t].thresholdKG - thresholds[t]) > 1e-9) {
1618 std::cerr <<
"error: " << options.supportFile <<
" was built for a different threshold ("
1619 << support.models[t].thresholdKG * 1000. <<
" G against " << thresholds[t] * 1000. <<
" G)\n";
1623 if (!support.parameterisation.empty() && support.parameterisation != field->
getParameterName()) {
1624 std::cerr <<
"error: " << options.supportFile <<
" was built for parameterisation "
1625 << support.parameterisation <<
", not " << field->
getParameterName() <<
'\n';
1628 report(
" support model : " + options.supportFile +
" (cached)");
1630 support = buildSupport(field, thresholds, -3000., 3000.);
1631 if (!options.supportFile.empty()) {
1632 std::ofstream out(options.supportFile);
1633 out << supportToJson(support, fieldSource).dump(1,
'\t') <<
'\n';
1634 report(
" support model : built and written to " + options.supportFile);
1636 report(
" support model : built for this run");
1639 support.marginStrict = options.margin;
1641 std::string bandCounts;
1642 for (
const auto& model : support.models) {
1643 bandCounts += form(
"%s%.1f G -> %zu bands", bandCounts.empty() ?
"" :
", ", model.thresholdKG * 1000.,
1644 model.bands.size());
1646 report(
" " + bandCounts);
1647 report(form(
" margins: strict %.2f cm, tight %.2f cm, edge uncertainty %.2f cm",
1648 support.marginStrict, support.marginTight, support.edgeUncertainty));
1653 report(
"outer-bound check");
1654 if (!violationScan(field, support,
report)) {
1656 report(
"The support model is not an outer bound on this field, so no placement can be called");
1657 report(
"field-free from it. Refusing to classify.");
1658 report.write(options.outputPrefix +
"-report.txt");
1664 TGeoManager::Import(options.geometryFile.c_str());
1665 if (gGeoManager ==
nullptr) {
1666 std::cerr <<
"error: no TGeoManager in " << options.geometryFile <<
'\n';
1669 report(form(
" volumes : %d, media %d", gGeoManager->GetListOfVolumes()->GetEntries(),
1670 gGeoManager->GetListOfMedia()->GetEntries()));
1672 Doctor doctor(field, support);
1673 doctor.walk(gGeoManager->GetTopNode());
1674 report(form(
" placements : %zu classified, %zu detector subtrees pruned", doctor.rows().size(),
1678 progress(
"classify: sampling the field inside every placement that reaches the support");
1679 doctor.classifyAll();
1680 doctor.findFindings();
1682 std::map<std::string, int> verdicts;
1683 for (
const auto&
row : doctor.rows()) {
1684 ++verdicts[
row.verdict];
1687 for (
const auto& verdict : verdicts) {
1688 report(form(
" %-16s %7d", verdict.first.c_str(), verdict.second));
1693 const double threshold = support.models.front().thresholdKG;
1694 std::map<std::string, std::pair<int, double>> reverseByVolume;
1695 for (
const auto*
row : doctor.reverseAudit()) {
1696 auto&
entry = reverseByVolume[
row->lv +
" [" +
row->medium +
"]"];
1698 entry.second = std::max(
entry.second, std::max(
row->maxB,
row->wholeMaxB));
1700 int inRealField = 0;
1701 for (
const auto&
entry : reverseByVolume) {
1702 inRealField +=
entry.second.second > threshold ? 1 : 0;
1704 report(form(
"reverse audit: %zu placements carry a field-free medium yet reach into the field support",
1705 doctor.reverseAudit().size()));
1706 report(form(
" %zu logical volumes, %d of them with real field in their own material",
1707 reverseByVolume.size(), inRealField));
1708 report(form(
" %-46s %11s %16s",
"volume [medium]",
"placements",
"max |B| [kG]"));
1709 for (
const auto&
entry : reverseByVolume) {
1711 entry.second.second > threshold ?
" <-- straight-line transport in real field" :
""));
1716 report(form(
"shared volumes: %zu logical volumes are placed both out of and into the field",
1717 doctor.sharedVolumes().size()));
1718 report(form(
" %-28s %8s %8s %s",
"logical volume",
"out",
"in",
"status"));
1719 for (
size_t i = 0;
i < doctor.sharedVolumes().
size() &&
i < 20; ++
i) {
1720 const auto& shared = doctor.sharedVolumes()[
i];
1721 bool refused =
false;
1722 bool approximate =
false;
1723 for (
const auto*
row : shared.out) {
1724 refused = refused ||
row->sensitive || doctor.hasSensitive(
row->node->GetVolume());
1725 approximate = approximate ||
row->approximateExtent;
1727 report(form(
" %-28s %8zu %8zu %s%s", shared.lv.c_str(), shared.out.size(), shared.in.size(),
1728 refused ?
"refused by default (sensitive)" :
"proposed",
1729 approximate ?
" [extent approximate]" :
""));
1731 if (doctor.sharedVolumes().size() > 20) {
1732 report(form(
" ... and %zu more, all of them in the proposals file", doctor.sharedVolumes().size() - 20));
1736 report(form(
"heterogeneous mothers: %zu whose own material straddles the predicate",
1737 doctor.straddlingMothers().size()));
1738 for (
size_t i = 0;
i < doctor.straddlingMothers().
size() &&
i < 10; ++
i) {
1739 const auto*
row = doctor.straddlingMothers()[
i];
1740 report(form(
" %-40s |B| in own material %.4g .. %.4f kG, %d daughters",
row->lv.c_str(),
row->wholeMinB,
1741 row->wholeMaxB,
row->nDaughters));
1745 report(form(
"missing containers: %zu daughter clusters lie wholly on the field-free side",
1746 doctor.containers().size()));
1747 for (
const auto& container : doctor.containers()) {
1748 report(form(
" in %-14s z %9.2f .. %9.2f rmax %7.2f %3d daughters %s%s", container.mother.c_str(),
1749 container.zlo, container.zhi, container.rmax, container.nDaughters,
1750 container.clearedByStrictMargin ?
"clear by the strict margin" :
"clear by the tight margin",
1751 container.sensitive ?
" [refused: sensitive]" :
""));
1756 bool anchorsPassed =
true;
1757 if (!options.anchorFile.empty()) {
1759 anchorsPassed = verifyAnchors(options.anchorFile, doctor,
report);
1760 report(anchorsPassed ?
" all anchors reproduced" :
" ANCHORS FAILED");
1765 const std::string proposalsPath = options.outputPrefix +
"-proposals.json";
1766 const std::string tablePath = options.outputPrefix +
"-placements.csv";
1767 const std::string reportPath = options.outputPrefix +
"-report.txt";
1768 std::ofstream proposals(proposalsPath);
1769 proposals << proposalsToJson(doctor, support, options.geometryFile, fieldSource).dump(1,
'\t') <<
'\n';
1770 writePlacementCsv(doctor.rows(), tablePath);
1771 report(
"wrote " + proposalsPath +
", " + tablePath +
" and " + reportPath);
1772 report.write(reportPath);
1774 return anchorsPassed ? 0 : 2;
header::DataOrigin origin
header::DataDescription description
std::unique_ptr< expressions::Node > node
Definition of the MagF class.
Double_t getFactorDipole() const
Return the sign*scale of the current in the Dipole according to sPolarityConventionthe.
void setDataFileName(const Char_t *nm)
void setFactorDipole(float fc=1.)
Sets the sign*scale of the current in the Dipole according to sPolarityConvention.
Double_t getFactorSolenoid() const
Returns the sign*scale of the current in the Dipole according to sPolarityConventionthe.
void Field(const Double_t *__restrict__ point, Double_t *__restrict__ bField) override
Char_t * getParameterName() const
void setFactorSolenoid(float fc=1.)
Sets the sign/scale of the current in the L3 according to sPolarityConvention.
void CreateField()
real field creation is here
static MagneticField * createNominalField(int fld, bool uniform=false)
create field from rounded value, i.e. +-5 or +-2 kGauss
GLboolean GLboolean GLboolean b
GLint GLint GLsizei GLsizei GLsizei depth
GLsizei const GLchar *const * path
GLboolean GLboolean GLboolean GLboolean a
GLsizei const GLint * box
GLdouble GLdouble GLdouble z
void report(gsl::span< o2::InteractionTimeRecord > irs, int threshold, bool verbose)
bpo::variables_map arguments
std::string to_string(gsl::span< T, Size > span)
std::map< std::string, ID > expected
VectorOfTObjectPtrs other
std::vector< ReadoutWindowData > rows