BmnRoot 26.10.0-210-gc52d34b0
Software framework of the BM@N experiment
Loading...
Searching...
No Matches
BmnAlignReport.cxx
Go to the documentation of this file.
1/*
2 BM@N alignment routine
3 BM@N experiment at NICA complex, JINR, 2025
4
5 Department: Math & Soft Group of HEP lab
6 Author: Igor Polev, polev@jinr.int
7
8 BmnAlignReport class implementation
9*/
10
11#include "BmnAlignReport.h"
12
13#include "BmnAlignDefines.h"
14#include "BmnRamIterator.h"
15
16#include <TCanvas.h>
17#include <TDatime.h>
18#include <TGraph.h>
19#include <TH1.h>
20#include <TLatex.h>
21#include <TPad.h>
22#include <TPolyLine3D.h>
23#include <TRandom.h>
24#include <TString.h>
25#include <TSystem.h>
26#include <algorithm>
27#include <cmath>
28#include <fairlogger/Logger.h>
29#include <fstream>
30#include <vector>
31
32namespace
33{
34constexpr Int_t DRAW_TRACKS_CNT{20}; // number of random tracks to draw
35// A ratio or a norm this small is treated as zero rather than divided by.
36// Far above machine epsilon on purpose: it is the scale below which a
37// solved parameter carries no information, not a rounding guard.
38constexpr Double_t SOFT_ZERO{1.0E-6};
39} // anonymous namespace
40
42{
43 auto factor{std::abs(after / before)};
44 if (factor < 1.0)
45 return Form("%+.1f%%", 100.0 * (after / before - 1.0));
46 else
47 return Form("%+.1fom", std::log10(factor));
48}
49
51{
52 LOG(state) << " > BmnAlignReport : final report ===============================================";
53 if (aligner.Results().empty()) {
54 LOG(error) << "<!> No results to report";
55 LOG(state) << " < BmnAlignReport : report canceled ============================================";
56 return;
57 }
58
59 const BmnAlignResult &before{aligner.ResultZero()}, &after{aligner.ResultLast()};
60 Int_t idxLast{after.SVDSig().GetNrows() - 1};
61 LOG(info) << Form(" %.2e -> %.2e (%s) : MSE value change", before.GetValueMSE(), after.GetValueMSE(),
62 Change(before.GetValueMSE(), after.GetValueMSE()));
63 LOG(info) << Form(" %.2e -> %.2e (%s) : mean error in X change (abs)", std::abs(before.ResidualsX().GetMean()),
64 std::abs(after.ResidualsX().GetMean()),
65 Change(before.ResidualsX().GetMean(), after.ResidualsX().GetMean()));
66 LOG(info) << Form(" %.2e -> %.2e (%s) : mean error in Y change (abs)", std::abs(before.ResidualsY().GetMean()),
67 std::abs(after.ResidualsY().GetMean()),
68 Change(before.ResidualsY().GetMean(), after.ResidualsY().GetMean()));
69 LOG(info) << Form(" %.2e -> %.2e (%s) : error stddev in X change", before.ResidualsX().GetStdDev(),
70 after.ResidualsX().GetStdDev(),
71 Change(before.ResidualsX().GetStdDev(), after.ResidualsX().GetStdDev()));
72 LOG(info) << Form(" %.2e -> %.2e (%s) : error stddev in Y change", before.ResidualsY().GetStdDev(),
73 after.ResidualsY().GetStdDev(),
74 Change(before.ResidualsY().GetStdDev(), after.ResidualsY().GetStdDev()));
75 if (after.SVDSig().GetNrows() > 0) {
76 LOG(info) << Form(" %.2e : latest SVD minimum singular value", after.SVDSig()(idxLast));
77 LOG(info) << Form(" %.2e : latest SVD condition number", after.SVDSig()(0) / after.SVDSig()(idxLast));
78 }
79 const TVectorD& params{after.A()};
80 const BmnAlignPriors& priors{aligner.Priors()};
81 const Int_t constCount{priors.ConstCount()};
82 if (constCount) {
83 // A() is the effective correction and already carries what the leaf
84 // coefficients imply, but a NodeName coefficient addresses the
85 // composite's own share - both live in A(), in one layout
86 const TVectorD violation{priors.rhs - priors.constraints * params};
87 Int_t goodCount{0};
88 for (Int_t i = 0; i < constCount; i++) {
89 if (std::abs(violation[i]) <= SOFT_ZERO) {
90 goodCount++;
91 continue;
92 }
93 LOG(warning) << Form(" %.2e : constraint row #%d violation", std::abs(violation[i]), i + 1);
94 }
95 LOG(info) << Form(" %d of %d : constraints satisfied within %.0e", goodCount, constCount, SOFT_ZERO);
96 }
97 if (aligner.Hierarchy().CompositeCount()) {
98 LOG(info) << " Composite node corrections (own share, cm and rad):";
99 TString header{Form(" %3s %-14s", "lvl", "node")};
100 for (Int_t j = 0; j < BMN_GLOBAL_PARAMS_PD; j++)
101 header += Form(" %11s", BmnParamLayout::ParamName(j));
102 LOG(info) << header;
103 for (const NodeRef& node : aligner.Layout().ByNode()) {
104 if (!node.IsComposite())
105 continue;
106 TString line{Form(" %3d %-14s", node.level, node.name)};
107 for (const ParamRef& p : node.params)
108 line += Form(" %+11.4e", params(p.full));
109 LOG(info) << line;
110 }
111 }
112 // TODO: make errors report optional
113 /*
114 LOG(info) << " Relative errors of parameters (sigma / |value|, '--' = undef):";
115 const TVectorD& aVal {after.A()};
116 const TVectorD& aErr {after.AErr()};
117 for (Int_t i = 0; i < Elements(); i++) {
118 const Int_t ID {aligner.DetectorModel()->EncodedID(i)};
119 TString line {Form(" det %2d (id %9d, %s) :", i, ID, aligner.DetectorModel()->DecodeID(ID).c_str())};
120 for (Int_t j = 0; j < BMN_GLOBAL_PARAMS_PD; j++) {
121 const Int_t g {i * BMN_GLOBAL_PARAMS_PD + j};
122 if (aErr[g] <= 0.0 || std::fabs(aVal[g]) <= SOFT_ZERO)
123 line += Form(" %-5s --", BmnParamLayout::ParamName(j));
124 else
125 line += Form(" %-5s %5.1f%%", BmnParamLayout::ParamName(j), 100.0 * aErr[g] / std::fabs(aVal[g]));
126 }
127 LOG(info) << line;
128 }
129 */
130 LOG(info) << " Execution timings:";
131 Int_t cpuTime{static_cast<Int_t>(aligner.TotalCpuTime())}, realTime{static_cast<Int_t>(aligner.TotalRealTime())};
132 LOG(info) << Form(" %dh %02dm %02ds : total CPU time spent", cpuTime / 3600, (cpuTime / 60) % 60,
133 cpuTime % 60);
134 LOG(info) << Form(" %dh %02dm %02ds : total real time spent", realTime / 3600, (realTime / 60) % 60,
135 realTime % 60);
136 LOG(info) << Form(" x%.1f : multithreading boost factor", aligner.TotalCpuTime() / aligner.TotalRealTime());
137 LOG(state) << " < BmnAlignReport : final report end ===========================================";
138 LOG(state) << " ---";
139}
140
141void BmnAlignReport::ModesBelow(const BmnAligner& aligner, Double_t absThreshold, Bool_t dumpNew)
142{
143 LOG(state) << " > BmnAlignReport : weak modes analysis";
144
145 // sanity checks
146 if (aligner.Results().empty()) {
147 LOG(error) << " <!> no iteration results available, aborting";
148 return;
149 }
150 const BmnAlignResult& res{aligner.ResultLast()};
151 const TVectorD& sig{res.SVDSig()};
152 const TMatrixD& V{res.SVDv()};
153 if (sig.GetNrows() == 0 || V.GetNrows() == 0) {
154 LOG(error) << " <!> SVD data not found, aborting";
155 return;
156 }
157 const Int_t nTotal{V.GetNrows()};
158 // V comes from the SVD of the solved system, which is hierarchical
159 const Int_t nParams{aligner.Hierarchy().AllParams()};
160 if (nTotal < nParams) {
161 LOG(error) << " <!> stored V matrix smaller than param count, aborting";
162 return;
163 }
164
165 // Rows of V are full-layout parameters: leaves first, then the composites.
166 // aligner.Layout() names both - indexing a detector table by
167 // row / BMN_GLOBAL_PARAMS_PD would run off its end as soon as the mode
168 // reaches into the composite block.
169
170 // Lazy-opened sink for auto-generated new constraints (one JSON array per call,
171 // only created if at least one unconstrained mode is encountered).
172 std::ofstream newCnstrFile;
173 TString newCnstrPath;
174 Int_t newCnstrCount{0};
175 Bool_t newCnstrFailed{kFALSE};
176 auto openNewCnstrFile = [&]() {
177 if (newCnstrFile.is_open() || newCnstrFailed)
178 return;
179 TDatime dt;
180 const TString stamp{Form("%04d%02d%02d_%02d%02d%02d", dt.GetYear(), dt.GetMonth(), dt.GetDay(), dt.GetHour(),
181 dt.GetMinute(), dt.GetSecond())};
182 TString srcBase;
183 if (aligner.ConstraintsPath()) {
184 srcBase = gSystem->BaseName(aligner.ConstraintsPath());
185 const Int_t dot{srcBase.Last('.')};
186 if (dot >= 0)
187 srcBase.Remove(dot);
188 }
189 newCnstrPath = "cnstr_new";
190 if (!srcBase.IsNull())
191 newCnstrPath += "_from-" + srcBase;
192 newCnstrPath += "_" + stamp + ".json";
193 newCnstrFile.open(newCnstrPath.Data(), std::ios::out);
194 if (!newCnstrFile.is_open()) {
195 LOG(error) << " <!> ReportModesBelow: failed to open " << newCnstrPath
196 << " for writing; further dumps suppressed";
197 newCnstrFailed = kTRUE;
198 return;
199 }
200 newCnstrFile << "[\n";
201 };
202
203 // The constraints have to be compared in the basis V lives in, that is as
204 // the very rows IterateAlignment() appends to the solved system.
205 const TMatrixD cnstrRows{aligner.Hierarchy().PromoteConstraints(aligner.Priors().constraints)};
206 VecDouble_t cnstrNorms;
207 for (Int_t j = 0; j < cnstrRows.GetNrows(); j++)
208 cnstrNorms.push_back(std::sqrt(TVectorD(TMatrixDRow_const(cnstrRows, j)).Norm2Sqr()));
209
210 // walk weak modes from the tail of the descending singular values
211 LOG(info) << Form(" %.2e : eigenvalue threshold to consider", absThreshold);
212 Int_t weakFound{0};
213 for (Int_t k = sig.GetNrows() - 1; k >= 0; k--) {
214 if (sig(k) > absThreshold)
215 break;
216 weakFound++;
217
218 // split the mode into (param, multiplier) subspaces
219 constexpr Double_t PARAM_ENERGY_MIN{0.10};
220 const TVectorD vk{TMatrixDColumn_const(V, k)};
221 const TVectorD vParam{vk.GetSub(0, nParams - 1)};
222 const Double_t paramNorm2{vParam.Norm2Sqr()}, multNorm2{vk.Norm2Sqr() - paramNorm2};
223 const Double_t paramFrac{paramNorm2 / std::max(paramNorm2 + multNorm2, SOFT_ZERO)};
224 LOG(info) << " --------------------------------------------------";
225 LOG(info) << Form(" = weak mode #%d : eigenvalue = %.3e, param-subspace energy = %.1f%%", weakFound, sig(k),
226 100.0 * paramFrac);
227 if (paramFrac < PARAM_ENERGY_MIN) {
228 LOG(info) << " --> multiplier-dominated, likely linearization artifact - skipped";
229 continue;
230 }
231
232 const Double_t pNorm{std::sqrt(paramNorm2)};
233
234 // Cosine similarity vs. user-defined constraints (in param subspace).
235 // Evaluated before contributors so the print can be widened for unconstrained modes.
236 constexpr Double_t OVERLAP_MIN{0.2}; // TODO: make this threshold configurable / meaningful
237 Bool_t anyOverlap{kFALSE};
238 if (aligner.Priors().ConstCount()) {
239 std::vector<std::pair<Int_t, Double_t>> overlaps; // (constraint #, cosine)
240 overlaps.reserve(aligner.Priors().ConstCount());
241 const TVectorD dots{cnstrRows * vParam};
242 for (Int_t j = 0; j < aligner.Priors().ConstCount(); j++) {
243 const Double_t cosine{std::fabs(dots[j]) / std::max(pNorm * cnstrNorms[j], SOFT_ZERO)};
244 overlaps.emplace_back(j, cosine);
245 if (cosine > OVERLAP_MIN)
246 anyOverlap = kTRUE;
247 }
248 LOG(info) << " overlap with existing constraints (|cos angle|):";
249 for (const auto& ov : overlaps)
250 if (ov.second > OVERLAP_MIN)
251 LOG(info) << Form(" constraint row #%d : %.1f%% overlap", ov.first + 1, 100.0 * ov.second);
252 LOG_IF(warning, !anyOverlap) << " ! no overlap with any constraint detected"
253 " -- likely new DoF to be fixed";
254 }
255
256 if (!anyOverlap) {
257 // unconstrained mode: dump the full param eigenvector so the user
258 // can read out the missing constraint directly. With dumpNew = kTRUE emit
259 // a constraints-JSON snippet ready to paste into a constraints file.
260 if (dumpNew) {
261 openNewCnstrFile();
262 if (newCnstrFile.is_open()) {
263 if (newCnstrCount > 0)
264 newCnstrFile << ",\n";
265 newCnstrFile << " {\n";
266 newCnstrFile << Form(" \"Comment\": \"weak mode #%d, eigenvalue = %.3e, auto-dumped\",\n",
267 weakFound, sig(k));
268 newCnstrFile << " \"Coefficients\": [\n";
269 constexpr Double_t VALUE_SKIP_BELOW{0.5e-10}; // empirical threshold for export precision
270 for (Int_t r = 0; r < nParams; r++) {
271 const Double_t v{V(r, k) / pNorm};
272 if (std::fabs(v) < VALUE_SKIP_BELOW)
273 continue; // loader will iterpret as zero
274 const ParamRef& p{aligner.Layout()[r]};
275 // a composite row has no detector to point at, so it is
276 // emitted the way the constraints loader addresses one
277 const TString quotedName{Form("\"%s\"", p.nodeName)};
278 newCnstrFile << (p.IsComposite()
279 ? Form(" { \"NodeName\": %11s, \"ParamName\": \"%s\", "
280 "\"Value\": %.10f },\n",
281 quotedName.Data(), p.prmName, v)
282 : Form(" { \"DetectorID\": %9d, \"ParamName\": \"%s\", "
283 "\"Value\": %.10f },\n",
284 aligner.DetectorModel()->EncodedID(p.leaf), p.prmName, v));
285 }
286 newCnstrFile << " { \"RHS\": 0.0 }\n";
287 newCnstrFile << " ]\n";
288 newCnstrFile << " }";
289 newCnstrCount++;
290 LOG(info) << " new constraint snippet written to " << newCnstrPath;
291 }
292 } else {
293 LOG(info) << " full eigenvector (param-subspace, normalized):";
294 for (const ParamRef& p : aligner.Layout().All())
295 LOG(info) << Form(" %-30s : %+.6f", p.Label().Data(), V(p.full, k) / pNorm);
296 }
297 }
298 if (anyOverlap || dumpNew) {
299 // constrained mode: just the top entries by magnitude.
300 std::vector<std::pair<Double_t, Int_t>> ranked; // |entry|, row index
301 ranked.reserve(nParams);
302 for (Int_t r = 0; r < nParams; r++)
303 ranked.emplace_back(std::fabs(V(r, k) / pNorm), r);
304 std::partial_sort(ranked.begin(), ranked.begin() + std::min<Int_t>(BMN_GLOBAL_PARAMS_PD, ranked.size()),
305 ranked.end(),
306 [](const std::pair<Double_t, Int_t>& a, const std::pair<Double_t, Int_t>& b) {
307 return a.first > b.first;
308 });
309 LOG(info) << " top contributors (param-subspace, normalized):";
310 for (Int_t t = 0; t < BMN_GLOBAL_PARAMS_PD && t < (Int_t)ranked.size(); t++) {
311 const Int_t row{ranked[t].second};
312 const ParamRef& p{aligner.Layout()[row]};
313 LOG(info) << Form(" %-30s : %+.6f", p.Label().Data(), V(row, k) / pNorm);
314 }
315 }
316 }
317
318 if (newCnstrFile.is_open()) {
319 newCnstrFile << "\n]\n";
320 newCnstrFile.close();
321 LOG(info) << " " << newCnstrCount << " new constraint(s) saved into " << newCnstrPath;
322 }
323 LOG(info) << " ========================";
324 LOG(info) << " " << weakFound << " weak mode(s) detected below threshold";
325 LOG(state) << " < BmnAlignReport : weak modes analysis done";
326 LOG(state) << " ---";
327}
328
329void BmnAlignReport::Correlations(const BmnAligner& aligner, Double_t absThreshold, Int_t topK, Bool_t skipIntraDet)
330{
331 LOG(state) << " > BmnAlignReport : parameter correlation analysis";
332
333 if (aligner.Results().empty()) {
334 LOG(error) << " <!> no iteration results available, aborting";
335 return;
336 }
337 const BmnAlignResult& res{aligner.ResultLast()};
338 const TMatrixDSym& C{res.Cov()};
339 const TVectorD& err{res.AErr()};
340 if (C.GetNrows() != aligner.Hierarchy().AllParams()) {
341 LOG(error) << " <!> covariance matrix not available, aborting";
342 return;
343 }
344
345 // sigma source: fAerr (full-length, inherited across iterations); 0 means "no info"
346 auto sigmaOf = [&](Int_t g) -> Double_t {
347 const Double_t s{err(g)};
348 return (s > 0.0 && s != TMath::Infinity()) ? s : 0.0;
349 };
350 // both levels: a leaf-versus-parent-composite correlation is exactly the
351 // pair the priors arbitrate, so it is the most informative one available
352 const BmnParamLayout::Range_t& params{aligner.Layout().All()};
353 Int_t validCnt{0};
354 for (const ParamRef& p : params)
355 if (sigmaOf(p.full) > 0.0)
356 validCnt++;
357
358 // collect upper-triangle pairs with |rho| >= absThreshold
359 struct Pair
360 {
361 Double_t rho;
362 const ParamRef *r, *s;
363 };
364 std::vector<Pair> pairs;
365 Int_t totalPairs{0};
366 for (std::size_t i = 0; i < params.size(); i++) {
367 const ParamRef& r{params[i]};
368 const Double_t sr{sigmaOf(r.full)};
369 if (sr == 0.0)
370 continue;
371 for (std::size_t j = i + 1; j < params.size(); j++) {
372 const ParamRef& s{params[j]};
373 const Double_t ss{sigmaOf(s.full)};
374 if (ss == 0.0)
375 continue;
376 if (skipIntraDet && r.node == s.node)
377 continue;
378 totalPairs++;
379 const Double_t rho{C(r.full, s.full) / (sr * ss)};
380 if (std::fabs(rho) >= absThreshold)
381 pairs.push_back({rho, &r, &s});
382 }
383 }
384
385 std::sort(pairs.begin(), pairs.end(),
386 [](const Pair& a, const Pair& b) { return std::fabs(a.rho) > std::fabs(b.rho); });
387
388 LOG(info) << Form(" %d param(s) with valid sigma", validCnt);
389
390 // per-parameter global correlation
391 const TVectorD& glCorr{res.AglCorr()};
392 struct GlCorr
393 {
394 Double_t corr;
395 const ParamRef* p;
396 };
397 std::vector<GlCorr> glAbove;
398 for (const ParamRef& p : params) {
399 if (sigmaOf(p.full) == 0.0)
400 continue;
401 if (glCorr(p.full) >= absThreshold)
402 glAbove.push_back({glCorr(p.full), &p});
403 }
404 std::sort(glAbove.begin(), glAbove.end(), [](const GlCorr& a, const GlCorr& b) { return a.corr > b.corr; });
405 LOG(info) << " --------------------------------------------------";
406 LOG(info) << Form(" %d param(s) with rho_gl >= %.2f out of %d valid", (Int_t)glAbove.size(), absThreshold,
407 validCnt);
408 const Int_t glShown{std::min(topK, (Int_t)glAbove.size())};
409 if (glShown > 0) {
410 LOG(info) << " top params by global correlation:";
411 LOG(info) << Form(" %-51s %-7s %-10s", "param", "rho_gl", "sigma");
412 for (Int_t k = 0; k < glShown; k++) {
413 const ParamRef& p{*glAbove[k].p};
414 LOG(info) << Form(" %s %-6s %+.4f %.2e", p.nodeName, p.prmName, glAbove[k].corr, sigmaOf(p.full));
415 }
416 }
417
418 LOG(info) << " --------------------------------------------------";
419 LOG(info) << Form(" %d pair(s) with |rho| >= %.2f out of %d pair(s) considered", (Int_t)pairs.size(),
420 absThreshold, totalPairs);
421 const Int_t shown{std::min(topK, (Int_t)pairs.size())};
422 if (shown == 0) {
423 LOG(state) << " < BmnAlignReport : correlation analysis done";
424 LOG(state) << " ---";
425 return;
426 }
427 LOG(info) << " top correlated pairs:";
428 LOG(info) << Form(" %-51s %-51s %-7s %-10s %-10s", "param i", "param j", "rho", "sigma_i", "sigma_j");
429 for (Int_t k = 0; k < shown; k++) {
430 const ParamRef &r{*pairs[k].r}, &s{*pairs[k].s};
431 LOG(info) << Form(" %s %-6s %s %-6s %+.4f %.2e %.2e", r.nodeName, r.prmName, s.nodeName,
432 s.prmName, pairs[k].rho, sigmaOf(r.full), sigmaOf(s.full));
433 }
434
435 LOG(state) << " < BmnAlignReport : correlation analysis done";
436 LOG(state) << " ---";
437}
438
440{
441 TH1* copy{static_cast<TH1*>(source.Clone())};
442 copy->SetDirectory(nullptr);
443 return copy;
444}
445
447{
448 Pads pads;
449 // prepare canvas and pads
450 static const UInt_t ww{1600}, wh{1200};
451 TCanvas* pCnv = new TCanvas( // RAM is not released in macro mode
452 "canvas", "BM@N alignment", ww, wh);
453 pCnv->SetCanvasSize(ww - 20, wh - 70);
454
455 // divide canvas into pads
456 // pay attention: dynamic RAM is not released in ROOT macro mode
457
458 // palce for synthetic histograms by X
459 pads.resSXbefore = new TPad("padG1", "G1", 0.0, 0.0, 0.5, 0.34);
460 pads.resSXafter = new TPad("padG2", "G2", 0.5, 0.0, 1.0, 0.34);
461 // palce for synthetic histograms by Y
462 pads.resSYbefore = new TPad("padG3", "G3", 0.0, 0.34, 0.5, 0.67);
463 pads.resSYafter = new TPad("padG4", "G4", 0.5, 0.34, 1.0, 0.67);
464 // palce for total residuals
465 pads.resX = new TPad("padG5", "G5", 0.0, 0.67, 0.25, 1.0);
466 pads.resY = new TPad("padG6", "G6", 0.25, 0.67, 0.5, 1.0);
467 // palce for SVD singular values
468 pads.svd = new TPad("padG7", "G7", 0.5, 0.67, 0.75, 1.0);
469 // palce for 3D tracks visualization
470 pads.tracks = new TPad("padG8", "G8", 0.75, 0.67, 1.0, 1.0);
471
472 pads.resSXbefore->Draw();
473 pads.resSXafter->Draw();
474 pads.resSYbefore->Draw();
475 pads.resSYafter->Draw();
476 pads.resX->Draw();
477 pads.resY->Draw();
478 pads.svd->Draw();
479 pads.tracks->Draw();
480
481 return pads;
482}
483
484void BmnAlignReport::DrawResiduals(const BmnAligner& aligner, const Pads& pads, std::size_t idx)
485{
486 // BmnAlignResult hands its histograms out through non-const accessors
487 const BmnAligner::Results_t& res{aligner.Results()};
488 TH1* pHist;
489
490 // top row: 1D total residuals by X (G20) and by Y (G21); reference is always [0]
491
492 pads.resX->cd();
493 pHist = ClonePlot(res[0].ResidualsX());
494 pHist->SetStats(0);
495 pHist->SetFillColor(kGray);
496 pHist->SetLineColor(kGray);
497 pHist->GetXaxis()->SetTitle("X residual, cm");
498 pHist->GetYaxis()->SetTitle("measurements");
499 pHist->GetYaxis()->SetRangeUser(0.0, res[idx].ResidualsX().GetMaximum() * 1.1);
500 pHist->GetYaxis()->SetLabelSize(0);
501 pHist->Draw("BAR");
502 pHist = ClonePlot(res[idx].ResidualsX());
503 pHist->SetStats(0);
504 pHist->SetLineColor(kRed);
505 pHist->Draw("CSAME");
506
507 pads.resY->cd();
508 pHist = ClonePlot(res[0].ResidualsY());
509 pHist->SetStats(0);
510 pHist->SetFillColor(kGray);
511 pHist->SetLineColor(kGray);
512 pHist->GetXaxis()->SetTitle("Y residual, cm");
513 pHist->GetYaxis()->SetTitle("measurements");
514 pHist->GetYaxis()->SetRangeUser(0.0, res[idx].ResidualsY().GetMaximum() * 1.1);
515 pHist->GetYaxis()->SetLabelSize(0);
516 pHist->Draw("BAR");
517 pHist = ClonePlot(res[idx].ResidualsY());
518 pHist->SetStats(0);
519 pHist->SetLineColor(kRed);
520 pHist->Draw("CSAME");
521
522 // top-middle: SVD singular values for last iteration (G22), log Y
523
524 pads.svd->cd();
525 pads.svd->SetLogy();
526 const TVectorD& vSig{res[idx].SVDSig()};
527 const Int_t nSig{vSig.GetNrows()};
528 if (nSig > 0) {
529 // Identify the tail of smallest eigenvalues. SVD output is descending,
530 // so we measure log-drops between consecutive pairs, take the two latest
531 // "significant" drops, and start the plot BMN_SVD_TAIL_MARGIN points
532 // before the earlier of them. Falls back to fewer drops (or to the last
533 // BMN_SVD_TAIL_MIN points) if no clear jumps are found; tail length is
534 // capped to no less than BMN_SVD_TAIL_MIN points either way.
535 constexpr Int_t BMN_SVD_TAIL_MIN{75};
536 constexpr Int_t BMN_SVD_TAIL_MARGIN{3};
537 constexpr Int_t BMN_SVD_DROPS_KEEP{2};
538 constexpr Double_t LOG2{0.6931471805599453}; // ln(2) - "factor of 2" threshold
539 Int_t startIdx{0};
540 if (nSig >= 2) {
541 Double_t dropMax{0.0};
542 VecDouble_t drop(nSig - 1, 0.0);
543 for (Int_t i = 0; i < nSig - 1; i++) {
544 Double_t a{std::max(vSig(i), SOFT_ZERO)}, b{std::max(vSig(i + 1), SOFT_ZERO)};
545 drop[i] = std::log(a) - std::log(b);
546 if (drop[i] > dropMax)
547 dropMax = drop[i];
548 }
549 const Double_t thr{std::max(0.5 * dropMax, LOG2)};
550 Int_t earliestKeptJump{-1}, kept{0};
551 for (Int_t i = nSig - 2; i >= 0 && kept < BMN_SVD_DROPS_KEEP; i--)
552 if (drop[i] >= thr) {
553 earliestKeptJump = i;
554 kept++;
555 }
556 startIdx = (earliestKeptJump >= 0) ? std::max(0, earliestKeptJump - BMN_SVD_TAIL_MARGIN + 1)
557 : std::max(0, nSig - BMN_SVD_TAIL_MIN);
558 }
559 if (nSig - startIdx < BMN_SVD_TAIL_MIN)
560 startIdx = std::max(0, nSig - BMN_SVD_TAIL_MIN);
561 const Int_t nTail{nSig - startIdx};
562
563 // RAM not released - acceptable in ROOT macro mode (see PrepareCanvas)
564 TGraph* pGsig = new TGraph(nTail);
565 pGsig->SetName(Form("svdSig_%lu", res[idx].GetHistCounter()));
566 pGsig->SetTitle("MNE SVD overview (tail);eigenvalue index;eigenvalue");
567 for (Int_t i = 0; i < nTail; i++)
568 pGsig->SetPoint(i, static_cast<Double_t>(startIdx + i), vSig(startIdx + i));
569 pGsig->SetFillColor(kGray);
570 pGsig->SetLineColor(kGray);
571 pGsig->Draw("AB");
572
573 // annotate the latest (minimal) eigenvalue above its bar
574 const Double_t sigMin{vSig(nSig - 1)};
575 TLatex* pSigMin = new TLatex();
576 pSigMin->SetTextAlign(21); // h-centered, v-bottom
577 pSigMin->SetTextFont(62); // bold Helvetica
578 pSigMin->SetTextSize(0.05);
579 pSigMin->SetTextColor(kRed);
580 pSigMin->DrawLatex(static_cast<Double_t>(nSig - 1),
581 sigMin * 1.6, // padded above the bar (pad is logy)
582 Form("#lambda_{min} = %.3g", sigMin));
583 }
584
585 // middle row: synthetic Y residuals over Z, before (G10) and after (G11)
586
587 pads.resSYbefore->cd();
588 pHist = ClonePlot(res[0].ResidualsSY());
589 pHist->SetStats(0);
590 pHist->SetTitle(Form("%s - before alignment", pHist->GetTitle()));
591 pHist->Draw("COL");
592
593 pads.resSYafter->cd();
594 pHist = ClonePlot(res[idx].ResidualsSY());
595 pHist->SetStats(0);
596 pHist->SetTitle(Form("%s - after alignment", pHist->GetTitle()));
597 pHist->Draw("COL");
598
599 // bottom row: synthetic X residuals over Z, before (G00) and after (G01)
600
601 pads.resSXbefore->cd();
602 pHist = ClonePlot(res[0].ResidualsSX());
603 pHist->SetStats(0);
604 pHist->SetTitle(Form("%s - before alignment", pHist->GetTitle()));
605 pHist->Draw("COL");
606
607 pads.resSXafter->cd();
608 pHist = ClonePlot(res[idx].ResidualsSX());
609 pHist->SetStats(0);
610 pHist->SetTitle(Form("%s - after alignment", pHist->GetTitle()));
611 pHist->Draw("COL");
612}
613
614void BmnAlignReport::Draw(const BmnAligner& aligner, Int_t number)
615{
616 if (!aligner.Initialized())
617 return;
618 LOG(state) << " > BmnAlignReport : drawing started";
619
620 const BmnAligner::Results_t& results{aligner.Results()};
621 std::size_t resultsCnt{results.size()}, resultIdx{number < 0 ? number + resultsCnt : number - 1};
622 LOG(info) << " " << resultsCnt << " results available to draw";
623 if (resultIdx < 0 || resultIdx >= resultsCnt) {
624 LOG(error) << "<!> result with number " << number << " not found";
625 LOG(state) << " < BmnAlignReport : drawing terminated";
626 return;
627 }
628 LOG(info) << " drawing " << std::abs(number) << " saved results counting from "
629 << (number < 0 ? "the end" : "the beginning");
630
631 const Pads pads{PrepareCanvas()};
632 // TODO: 3D track samples must be optional or excluded
633
634 // draw some tracks in 3D pad
635 BmnRamIterator* pIterator{aligner.RamIterator()};
636 pads.tracks->cd();
637 const Double_t maxZdraw{aligner.MaxZvalue() * 1.1};
638 const Double_t trackProbability{static_cast<Double_t>(DRAW_TRACKS_CNT)
639 / static_cast<Double_t>(aligner.TracksUsable())};
640 TRandom trueRnd(0);
641 // RAM not released - acceptable in ROOT macro mode (see PrepareCanvas).
642 // Lines must remain alive for the pad to keep drawing them on redraw.
643 TPolyLine3D *pTrackLines = new TPolyLine3D[DRAW_TRACKS_CNT], *pApproxLines = new TPolyLine3D[DRAW_TRACKS_CNT];
644
645 aligner.ProgressBar()->Init();
646 Int_t iTrack{0}, iTrackDrawn{0};
647
648 for (pIterator->ResetAll(); iTrackDrawn < DRAW_TRACKS_CNT && !pIterator->EndOfTracks();
649 pIterator->NextTrack(), iTrack++)
650 {
651 // skip tracks according to probability
652 if (!aligner.TrackUsable()->at(iTrack) || trueRnd.Uniform() > trackProbability)
653 continue;
654
655 // draw track itself
656 Int_t iHit{0};
657 for (; !pIterator->EndOfHits(); pIterator->NextHit(), iHit++) {
658 // x and z are swapped for visual convenience
659 pTrackLines[iTrackDrawn].SetPoint(iHit, pIterator->HitZ(), pIterator->HitY(), pIterator->HitX());
660 }
661 pTrackLines[iTrackDrawn].SetLineColor(kBlue);
662 pTrackLines[iTrackDrawn].Draw();
663
664 // draw linear approximation of the track
665 const SVectLC& Alpha{aligner.ResultLast().Alpha(iTrack)};
666 pApproxLines[iTrackDrawn].SetPoint(0, 0.0, Alpha(iY0), Alpha(iX0));
667 pApproxLines[iTrackDrawn].SetPoint(1, maxZdraw, Alpha(iY0) + maxZdraw * Alpha(iY1),
668 Alpha(iX0) + maxZdraw * Alpha(iX1));
669 pApproxLines[iTrackDrawn].SetLineColor(kRed);
670 pApproxLines[iTrackDrawn].SetLineStyle(kDotted);
671 pApproxLines[iTrackDrawn].Draw();
672 iTrackDrawn++;
673 } // loop over tracks
674 aligner.ProgressBar()->Clear();
675
676 DrawResiduals(aligner, pads, resultIdx);
677
678 LOG(state) << " < BmnAlignReport : drawing finished";
679 LOG(state) << " ---";
680}
std::vector< Double_t > VecDouble_t
@ iY1
@ iY0
@ iX1
@ iX0
RM::SVector< Double_t, BMN_LOCAL_PARAMS_PT > SVectLC
#define BMN_GLOBAL_PARAMS_PD
__m128 v
Definition P4_F32vec4.h:1
int i
Definition P4_F32vec4.h:22
bool Bool_t
int Int_t
unsigned int UInt_t
constexpr Bool_t kFALSE
double Double_t
constexpr Bool_t kTRUE
kGray
kRed
kBlue
kDotted
TMatrixTSym< Double_t > TMatrixDSym
TMatrixTColumn_const< Double_t > TMatrixDColumn_const
TMatrixTRow_const< Double_t > TMatrixDRow_const
TMatrixT< Double_t > TMatrixD
char * Form(const char *fmt,...)
R__EXTERN TSystem * gSystem
TVectorT< Double_t > TVectorD
Int_t CompositeCount() const noexcept
Int_t AllParams() const noexcept
TMatrixD PromoteConstraints(const TMatrixD &constraints) const
TMatrixD constraints
Int_t ConstCount() const noexcept
static Pads PrepareCanvas()
static void DrawResiduals(const BmnAligner &aligner, const Pads &pads, std::size_t idx)
static void Correlations(const BmnAligner &aligner, Double_t absThreshold=0.9, Int_t topK=20, Bool_t skipIntraDet=kFALSE)
static void ModesBelow(const BmnAligner &aligner, Double_t absThreshold, Bool_t dumpNew=kTRUE)
static TH1 * ClonePlot(const TH1 &source)
static char * Change(Double_t before, Double_t after)
static void Draw(const BmnAligner &aligner, Int_t number=-1)
static void Results(const BmnAligner &aligner)
TVectorD & SVDSig() noexcept
TMatrixD & SVDv() noexcept
TH1D & ResidualsX() noexcept
TH1D & ResidualsY() noexcept
TVectorD & AErr() noexcept
Double_t GetValueMSE() const noexcept
VecSVectLC_t & Alpha() noexcept
TMatrixDSym & Cov() noexcept
TVectorD & AglCorr() noexcept
BmnSimpleProgressBar * ProgressBar() const noexcept
Definition BmnAligner.h:85
std::deque< BmnAlignResult > Results_t
Definition BmnAligner.h:65
Double_t TotalCpuTime() const noexcept
Definition BmnAligner.h:79
Double_t MaxZvalue() const noexcept
Definition BmnAligner.h:81
const BmnDetectorModel * DetectorModel() const noexcept
Definition BmnAligner.h:73
Bool_t Initialized() const noexcept
Definition BmnAligner.h:66
Int_t TracksUsable() const noexcept
Definition BmnAligner.h:82
const BmnAlignResult & ResultLast() const
Definition BmnAligner.h:272
const VecBool_t * TrackUsable() const noexcept
Definition BmnAligner.h:84
const char * ConstraintsPath() const noexcept
Definition BmnAligner.h:78
BmnRamIterator * RamIterator() const noexcept
Definition BmnAligner.h:76
Results_t & Results() noexcept
Definition BmnAligner.h:69
const BmnParamLayout & Layout() const noexcept
Definition BmnAligner.h:72
const BmnAlignResult & ResultZero() const
Definition BmnAligner.h:284
const BmnAlignPriors & Priors() const noexcept
Definition BmnAligner.h:77
const BmnAlignHierarchy & Hierarchy() const noexcept
Definition BmnAligner.h:71
Double_t TotalRealTime() const noexcept
Definition BmnAligner.h:80
Bool_t EndOfHits() const noexcept
Int_t EncodedID(Int_t idx) const
const Nodes_t & ByNode() const noexcept
static const char * ParamName(Int_t p)
std::vector< ParamRef > Range_t
const Range_t & All() const noexcept
virtual void NextHit() override
virtual void ResetAll() override
virtual Double_t HitY() const override
virtual Double_t HitX() const override
virtual void NextTrack() override
virtual Double_t HitZ() const override
virtual void SetLabelSize(Float_t size=0.04)
virtual void SetFillColor(Color_t fcolor)
virtual void SetLineStyle(Style_t lstyle)
virtual void SetLineColor(Color_t lcolor)
virtual void SetTextAlign(Short_t align=11)
virtual void SetTextColor(Color_t tcolor=1)
virtual void SetTextFont(Font_t tfont=62)
virtual void SetTextSize(Float_t tsize=1)
virtual void SetRangeUser(Double_t ufirst, Double_t ulast)
void SetCanvasSize(UInt_t ww, UInt_t wh) override
Int_t GetMonth() const
Int_t GetDay() const
Int_t GetHour() const
Int_t GetSecond() const
Int_t GetYear() const
Int_t GetMinute() const
virtual void SetPoint(Int_t i, Double_t x, Double_t y)
void SetName(const char *name="") override
void Draw(Option_t *chopt="") override
void SetTitle(const char *title="") override
virtual void SetDirectory(TDirectory *dir)
void SetTitle(const char *title) override
virtual Double_t GetStdDev(Int_t axis=1) const
virtual Double_t GetMean(Int_t axis=1) const
TAxis * GetXaxis()
TAxis * GetYaxis()
void Draw(Option_t *option="") override
TObject * Clone(const char *newname="") const override
virtual void SetStats(Bool_t stats=kTRUE)
TLatex * DrawLatex(Double_t x, Double_t y, const char *text)
virtual void SetTitle(const char *title="")
const char * GetTitle() const override
void SetLogy(Int_t value=1) override
TVirtualPad * cd(Int_t subpadnumber=0) override
void Draw(Option_t *option="") override
virtual void SetPoint(Int_t point, Double_t x, Double_t y, Double_t z)
void Draw(Option_t *option="") override
virtual Double_t Uniform(Double_t x1, Double_t x2)
const char * Data() const
Ssiz_t Last(char c) const
Bool_t IsNull() const
TString & Remove(EStripType s, char c)
Int_t GetNrows() const
Element Norm2Sqr() const
TVectorT< Element > GetSub(Int_t row_lwb, Int_t row_upb, Option_t *option="S") const
Double_t Infinity()
const char * nodeName
const char * prmName
TString Label() const
Bool_t IsComposite() const noexcept