ATLAS Offline Software
Loading...
Searching...
No Matches
TrackTruthMatchingBaseAlg.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
3*/
5
9
10// for pdg_id -> name
11#include "HepPDT/ParticleDataTable.hh"
12
13#include <iomanip>
14#include <cmath>
15#include <type_traits>
16#include <typeinfo>
17#include <numeric>
18
19namespace {
20 template <typename T_EnumClass >
21 constexpr typename std::underlying_type<T_EnumClass>::type to_underlying(T_EnumClass an_enum) {
22 return static_cast<typename std::underlying_type<T_EnumClass>::type>(an_enum);
23 }
24
25 template <typename T, std::size_t N>
26 void accumulateTo(typename std::vector<std::array<T,N> >::const_iterator src_begin,
27 typename std::vector<std::array<T,N> >::const_iterator src_end,
28 std::array<T,N> &dest) {
29 for (typename std::vector<std::array<T,N> >::const_iterator src_iter = src_begin;
30 src_iter != src_end;
31 ++src_iter) {
32 for (unsigned int elm_i=0; elm_i<dest.size(); ++elm_i) {
33 assert( elm_i < src_iter->size() );
34 dest[elm_i] += (*src_iter)[elm_i];
35 }
36 }
37 }
38
39 template <typename T, bool LastRowOnly=false>
40 void accumulateToLastColumnRow(std::size_t n_rows, std::size_t n_cols, std::vector<T> &stat) {
41 assert(n_cols > 0);
42 assert(n_rows > 0);
43 auto stat_begin_iter = stat.begin();
44 if (n_rows>1) {
45 auto stat_total_row_begin_iter = stat.begin() + (n_rows-1) * n_cols;
46 assert( static_cast<std::size_t>(stat_total_row_begin_iter - stat_begin_iter) < stat.size());
47
48 for (std::size_t row_i = 0; row_i < n_rows-1; ++row_i) {
49 auto stat_end_iter = stat_begin_iter + n_cols - 1 ;
50 assert(static_cast<std::size_t>(stat_end_iter - stat.begin()) < stat.size());
51 auto stat_total_row_iter = stat_total_row_begin_iter;
52 if constexpr(!LastRowOnly) {
53 accumulateTo(stat_begin_iter, stat_end_iter, *stat_end_iter );
54 }
55
56 ++stat_end_iter; // now also consider the total eta bin.
57 for (; stat_begin_iter != stat_end_iter; ++stat_begin_iter, ++stat_total_row_iter) {
58 assert(static_cast<std::size_t>(stat_begin_iter - stat.begin()) < stat.size());
59 assert(static_cast<std::size_t>(stat_total_row_iter - stat.begin()) < stat.size());
60 for (unsigned int idx=0; idx < stat_total_row_iter->size(); ++idx) {
61 stat_total_row_iter->at(idx) += stat_begin_iter->at(idx);
62 }
63 }
64 }
65 }
66 else if constexpr(!LastRowOnly) {
67 auto stat_end_iter = stat_begin_iter + n_cols - 1 ;
68 accumulateTo(stat_begin_iter, stat_end_iter, *stat_end_iter );
69 }
70 }
71
72 template <typename T>
73 void accumulateToLastRow(std::size_t n_rows, std::size_t n_cols, std::vector<T> &stat) {
74 accumulateToLastColumnRow<T,true>(n_rows, n_cols, stat);
75 }
76
77 template <typename T, std::size_t N>
78 void addStat(const std::vector<std::array<T,N> > &src, std::vector<std::array<T,N> > &dest) {
79 assert( src.size() == dest.size());
80 unsigned int idx=0;
81 for (const std::array<T,N> &src_elm : src) {
82 assert( idx < dest.size());
83 std::array<T,N> &dest_elm = dest[idx];
84 unsigned val_i=0;
85 for (const T &val : src_elm) {
86 assert( val_i < dest_elm.size());
87 dest_elm[val_i] += val;
88 ++val_i;
89 }
90 ++idx;
91 }
92 }
93
94 inline double sqr(double a) { return a*a; }
95 // computate ratio and its statistical uncertainty
96 inline std::array<float, 2> computeRatio(unsigned int numerator_counts, unsigned int denominator_counts) {
97 double inv_denominator_counts = denominator_counts > 0 ? 1./denominator_counts : 0.;
98 return std::array<float, 2> {
99 static_cast<float>(numerator_counts * inv_denominator_counts),
100 static_cast<float>(sqrt( numerator_counts * (denominator_counts-numerator_counts)
101 * inv_denominator_counts * sqr(inv_denominator_counts) ))
102 };
103 }
104
105 std::string hfill(const std::string &head, const std::string &tail, std::size_t width) {
106 width=std::max(width, head.size() + tail.size()) - head.size() - tail.size();
107 std::stringstream out;
108 out << head << std::setw(width) << " " << tail;
109 return out.str();
110
111 }
112
113 std::string dumpCounts(const ActsTrk::HitCounterArray &counts) {
114 std::stringstream out;
115 for (uint8_t val : counts) {
116 out << " " << std::setw(2) << static_cast<int>(val);
117 }
118 return out.str();
119 }
120}
121namespace ActsTrk
122{
123 // to dump
124 inline MsgStream &operator<<(MsgStream &out, const ActsUtils::Stat &stat) {
125 ActsUtils::dumpStat(out, stat);
126 return out;
127 }
128
130 ISvcLocator *pSvcLocator)
131 : AthReentrantAlgorithm(name, pSvcLocator)
132 {
133 }
134
136 {
137 ATH_CHECK( m_truthSelectionTool.retrieve());
138 ATH_CHECK( m_truthHitCounts.initialize() );
140 return checkMatchWeights();
141 }
142
143 template <bool IsDebug>
144 template <class T_OutStream>
146 if constexpr(IsDebug) {
147 out << "Weighted measurement sum per truth particle without associated counts :" << m_measPerTruthParticleWithoutCounts << std::endl
148 << m_measPerTruthParticleWithoutCounts.histogramToString() << std::endl
149 << "Match probability of best match :" << m_bestMatchProb << std::endl
150 << m_bestMatchProb.histogramToString() << std::endl
151 << "Match probability of next-to-best match :" << m_nextToBestMatchProb << std::endl
152 << m_nextToBestMatchProb.histogramToString() << std::endl;
153 }
154 }
155
156 template <bool IsDebug>
158 if constexpr(IsDebug) {
159 std::lock_guard<std::mutex> lock(m_mutex);
160 m_measPerTruthParticleWithoutCounts.add(weighted_measurement_sum);
161 }
162 }
163 template <bool IsDebug>
164 inline void TrackTruthMatchingBaseAlg::DebugCounter<IsDebug>::fillTruthMatchProb(const std::array<float,2> &best_match_prob) const {
165 if constexpr(IsDebug) {
166 std::lock_guard<std::mutex> lock(m_mutex);
167 m_bestMatchProb.add(best_match_prob[0]);
168 m_nextToBestMatchProb.add(best_match_prob[1]);
169 }
170 }
171
172
174 {
175 if (msgLvl(MSG::INFO)) {
177 m_debugCounter.dumpStatistics(msg());
178 }
179 msg(MSG::INFO) << "Truth selection cuts: " << std::endl;
180 unsigned int cut_i=0;
181 std::size_t total = std::accumulate ( m_detailedStat.m_truthSelectionCuts.m_histogram.begin(),
182 m_detailedStat.m_truthSelectionCuts.m_histogram.end(), 0u);
183 msg() << std::setw(3) << "" << " " << std::setw(20) << total << " total" << std::endl;
184 if (m_detailedStat.m_truthSelectionCuts.m_histogram.at(cut_i) > 0) {
185 msg() << std::setw(3) << "" << " "
186 << std::setw(20) << (total - m_detailedStat.m_truthSelectionCuts.m_histogram.at(cut_i))
187 << " underflow" << std::endl;
188 }
189 total -= m_detailedStat.m_truthSelectionCuts.m_histogram.at(cut_i++);
190 for (const std::string &name : m_truthSelectionTool->names()) {
191 total -= m_detailedStat.m_truthSelectionCuts.m_histogram.at(cut_i);
192 msg() << std::setw(3) << cut_i << " " << std::setw(20) << total << " " << name << std::endl;
193 ++cut_i;
194 }
195 total -= m_detailedStat.m_truthSelectionCuts.m_histogram.at(cut_i);
196 if (total>0) {
197 msg() << std::setw(3) << "" << " " << std::setw(20) << total << " overflow" << std::endl;
198 }
199 if (msgLvl(MSG::DEBUG)) {
200 msg() << m_detailedStat.m_truthSelectionCuts.histogramToString();
201 }
202 msg() << endmsg;
203 }
205 return StatusCode::SUCCESS;
206 }
207
210 const HitCountsPerTrack &track_hit_counts,
211 TrackTruthMatchingBaseAlg::EventStat &event_stat) const
212 {
213 TruthMatchResult ret{};
214 std::array<unsigned int,2> best_match_i{std::numeric_limits<unsigned int>::max(),std::numeric_limits<unsigned int>::max()};
215 std::array<float,2> best_match_prob {};
216
217 std::array<unsigned int,2> best_match_i_nonoise{std::numeric_limits<unsigned int>::max(),std::numeric_limits<unsigned int>::max()};
218 std::array<float,2> best_match_prob_nonoise{};
219
220 const HitCounterArray &total_counts = track_hit_counts.totalCounts();
221 const HitCounterArray &noise_counts = track_hit_counts.noiseCounts();
222
223 double total_sum=weightedCountSum(total_counts, m_weights.value() );
224 double total_sum_for_prob=weightedCountSum(total_counts, m_weightsForProb.value() );
225 double noise_sum=noiseCorrection(noise_counts, m_weightsForProb.value() );
226 double total_sum_for_prob_nonoise=total_sum_for_prob;
227 total_sum_for_prob += noise_sum;
228
229 if (total_sum_for_prob>0.) {
230 // compute total hit count per truth particle and remember the highest and second highest
231 // count per truth particle.
232 // The match probability is then max_counter / sum_{i in associated truth particles} counts_i
233 unsigned int truth_i=0;
234 --truth_i;
235 for (const std::pair<const xAOD::TruthParticle *, HitCounterArray > &
236 hit_counts_for_associated_truth_particle : track_hit_counts.countsPerTruthParticle() ) {
237 ++truth_i;
238 double truth_sum_for_prob=weightedCountSum(hit_counts_for_associated_truth_particle.second, m_weightsForProb.value() );
239 float match_prob_nonoise = truth_sum_for_prob /total_sum_for_prob_nonoise;
240 float match_prob = truth_sum_for_prob /total_sum_for_prob;
241 if (match_prob>1 || match_prob<0.) {
242 ATH_MSG_ERROR("Negative or too large truth match \"probability\". This should not happen."
243 << " Track hits: " << dumpCounts(total_counts)
244 << " noise hits of those: " << dumpCounts(noise_counts)
245 << " truth hits: " << dumpCounts(hit_counts_for_associated_truth_particle.second));
246 } // remember the highest and next-to-highest hit count per truth particle
247 if (match_prob>best_match_prob[1]) {
248 int dest_i=match_prob<best_match_prob[0];
249 best_match_i[1]=best_match_i[0];
250 best_match_prob[1]=best_match_prob[0];
251 best_match_prob[dest_i]=match_prob;
252 best_match_i[dest_i]=truth_i;
253 }
254 if (match_prob_nonoise>best_match_prob_nonoise[1]) {
255 int dest_i=match_prob_nonoise<best_match_prob_nonoise[0];
256 best_match_i_nonoise[1]=best_match_i_nonoise[0];
257 best_match_prob_nonoise[1]=best_match_prob_nonoise[0];
258 best_match_prob_nonoise[dest_i]=match_prob_nonoise;
259 best_match_i_nonoise[dest_i]=truth_i;
260 }
261
262 }
263 }
264
265 if (best_match_i_nonoise[0] != best_match_i[0]) {
267 }
268 if ( best_match_i[0] < track_hit_counts.countsPerTruthParticle().size()
269 && track_hit_counts.countsPerTruthParticle()[ best_match_i[0] ].first) {
270 ret.m_truthParticle = track_hit_counts.countsPerTruthParticle()[ best_match_i[0] ].first;
271 ret.m_matchProbability = best_match_prob[0];
272
273 const xAOD::TruthParticle *best_match = track_hit_counts.countsPerTruthParticle()[ best_match_i[0] ].first;
274 const IAthSelectionTool::CutResult accept = m_truthSelectionTool->accept(best_match);
275 event_stat.m_truthSelectionCuts.add( event_stat.m_nTruthCuts - accept.missingCuts() );
276 if (accept) {
277
278 double common_truth_sum=weightedCountSum(track_hit_counts.countsPerTruthParticle()[ best_match_i[0] ].second, m_weights.value() );
279
280 float hit_efficiency = 0.;
281 std::unordered_map<const xAOD::TruthParticle *,HitCounterArray>::const_iterator
282 best_truth_particle_counts_iter = truth_particle_hit_counts.find( best_match );
283
284 if (best_truth_particle_counts_iter != truth_particle_hit_counts.end()) {
285 double truth_sum=weightedCountSum(best_truth_particle_counts_iter->second, m_weights.value() );
286 // in principle truth_measuremnts should always be larger than 0
287 hit_efficiency = truth_sum > 0u ? (common_truth_sum/truth_sum) : 0.;
288 }
289 else {
290 // this can happen if the total number of hits are below threshold for
291 // accepting a truth particle
293 m_debugCounter.fillMeasForTruthParticleWithoutCount(total_sum);
294 }
295
296 // in principle a track matched to a truth particle should always have n_total > 0
297 // but the hits could be filtered out.
298 if (total_sum>0.) {
299 float hit_purity = common_truth_sum / total_sum;
300 ret.m_hitPurity = hit_purity;
301 ret.m_hitEfficiency = hit_efficiency;
302 m_debugCounter.fillTruthMatchProb(best_match_prob);
303
305 float best_match_pt = best_match->pt();
306 std::size_t eta_category_i = getPtEtaStatCategory(best_match_pt, best_match->eta());
307 std::size_t pdg_id_category_i = getPtPdgIdStatCategory(best_match_pt, best_match->pdg_id());
308 event_stat.fill( eta_category_i, pdg_id_category_i, hit_efficiency, hit_purity, best_match_prob[0], best_match );
309 }
310 }
311 }
312 else {
314 }
315 }
316 else {
317 // no eta, pdg_id for tracks without associated truth particle
318 // could use eta of track but not available to the algorithm
319
321 if (!track_hit_counts.countsPerTruthParticle().empty()) {
322 ATH_MSG_ERROR("Failed to select best matching truth particle out of " << track_hit_counts.countsPerTruthParticle().size()
323 << ". This should not happen." );
325 }
326 return ret;
327 }
328
329
330
332 std::size_t n_tracks,
333 TrackTruthMatchingBaseAlg::EventStat &event_stat) const
334 {
335 if constexpr(EventStat::doDetail) {
336 if (m_computeTrackRecoEfficiency.value()) {
337 for(const std::pair<const xAOD::TruthParticle * const,ActsTrk::HitCounterArray> &truth_particle : truth_particle_hit_counts) {
338 const IAthSelectionTool::CutResult accept = m_truthSelectionTool->accept(truth_particle.first);
339 if (accept) {
340 double truth_sum=weightedCountSum(truth_particle.second, m_weights.value() );
341 if (truth_sum>0.) {
342 float truth_particle_pt = truth_particle.first->pt();
343 std::size_t eta_category_i = getPtEtaStatCategory(truth_particle_pt, truth_particle.first->eta());
344 std::size_t pdg_id_category_i = getPtPdgIdStatCategory(truth_particle_pt, truth_particle.first->pdg_id());
345 event_stat.incrementTotal(eta_category_i, pdg_id_category_i);
346 }
347 }
348 }
349 }
350 }
351
352 // update total statistic counter
353 {
354 std::lock_guard<std::mutex> lock(m_statMutex);
359 m_counter[NTracksTotal]+=n_tracks;
360 m_counter[NTruthWithCountsTotal]+=truth_particle_hit_counts.size();
361
362 if constexpr(EventStat::doDetail) {
363 m_detailedStat += event_stat;
364 }
365 }
366 }
367
368 inline std::size_t TrackTruthMatchingBaseAlg::getPtEtaStatCategory(float pt, float eta) const
369 {
370 std::vector<float>::const_iterator pt_bin_iter = std::upper_bound(m_statPtBins.begin(),
371 m_statPtBins.end(),
372 pt);
373 std::vector<float>::const_iterator eta_bin_iter = std::upper_bound(m_statEtaBins.begin(),
374 m_statEtaBins.end(),
375 m_useAbsEtaForStat ? std::abs(eta) : eta);
376 return (m_statEtaBins.size()+2u) * static_cast<std::size_t>(pt_bin_iter - m_statPtBins.begin())
377 + static_cast<std::size_t>(eta_bin_iter - m_statEtaBins.begin());
378 }
379 std::size_t TrackTruthMatchingBaseAlg::getPtPdgIdStatCategory(float pt, int pdg_id) const {
380 std::vector<float>::const_iterator pt_bin_iter = std::upper_bound(m_statPtBins.begin(),
381 m_statPtBins.end(),
382 pt);
383 int abs_pdg_id = std::min(std::abs(pdg_id), s_pdgIdMax);
384 std::vector< int >::const_iterator iter = std::find(m_pdgId.begin(), m_pdgId.end(), abs_pdg_id);
385 if (iter == m_pdgId.end()){
386 if (m_pdgId.size() < m_pdgId.capacity()) {
387 std::lock_guard<std::mutex> lock(m_statMutex); // @TODO pdg id list specific mutex ?
388 // make sure that the pdg id still does not exist.
389 iter = std::find(m_pdgId.begin(), m_pdgId.end(), abs_pdg_id);
390 if (iter == m_pdgId.end()){
391 m_pdgId.push_back(abs_pdg_id);
392 iter = m_pdgId.end()-1;
393 }
394 }
395 else {
396 iter=m_pdgId.begin();
397 }
398 }
399 return (m_pdgId.capacity()) * static_cast<std::size_t>(pt_bin_iter - m_statPtBins.begin())
400 + (iter - m_pdgId.begin());
401 }
402
403 void TrackTruthMatchingBaseAlg::checkBinOrder( const std::vector<float> &bin_edges, const std::string &bin_label) const {
404 if (!bin_edges.empty())
405 {
406 float last_eta = bin_edges[0];
407 for (float eta : bin_edges)
408 {
409 if (eta < last_eta)
410 {
411 ATH_MSG_FATAL(bin_label + " bins for statistics counter not in ascending order.");
412 }
413 last_eta = eta;
414 }
415 }
416 }
418 {
419 if (!m_statEtaBins.empty())
420 {
422 checkBinOrder(m_statEtaBins.value(),"Eta");
423 }
424 checkBinOrder(m_statPtBins.value(),"Pt");
425
426
427 unsigned int max_pdg_id_slots=( m_pdgIdCategorisation.value() ? 20 : 1 );
428
429 // last element in statPerEta and counterPerEta will be used accumulate statistics of all eta bins
430 m_detailedStat.reset( *m_truthSelectionTool,
431 (m_statPtBins.size() + 2) * (m_statEtaBins.size() + 2),
432 (m_statPtBins.size() + 2) * max_pdg_id_slots);
433
434 m_pdgId.reserve(max_pdg_id_slots);
435 m_pdgId.push_back(1000000000);
436 }
437
438 template <bool DetailEnabled>
441 if constexpr(DetailEnabled) {
442 addStat(event_stat.m_counterPerEta,m_counterPerEta);
443 addStat(event_stat.m_statPerEta, m_statPerEta);
444 addStat(event_stat.m_counterPerPdgId,m_counterPerPdgId);
445 addStat(event_stat.m_statPerPdgId, m_statPerPdgId);
446 }
448 return *this;
449 }
450
451 template <bool DetailEnabled>
453 const std::vector<float> &statPtBins,
454 const std::vector<float> &statEtaBins,
455 std::vector< int > &pdgId,
456 bool printDetails,
457 bool pdgIdCategorisation,
458 bool useAbsEtaForStat) {
459 if constexpr(DetailEnabled) {
460 static constexpr bool rotate=true;// row : eta/PDG ID; column: pt
461 std::vector<std::string> counter_labels { std::string("Truth particles"),
462 std::string("with asso. track"),
463 std::string("with >1 asso. tracks"),
464 std::string("total tracks")};
465 std::vector<std::string> pt_labels;
466 pt_labels.reserve(statPtBins.size() + 2);
467 unsigned int pt_precision=0;
468 for (float pt : statPtBins) {
469 if (pt<1.) {
470 pt_precision=1;
471 break;
472 }
473 }
474 for (std::size_t bin_i = 0; bin_i < statPtBins.size() + 2; ++bin_i) {
475 pt_labels.push_back(TableUtils::makeBinLabel("pt",statPtBins, bin_i, true, pt_precision));
476 }
477 // statistics eta-bins
478 {
479 std::vector<std::string> eta_labels;
480 eta_labels.reserve(statEtaBins.size() + 2);
481 for (std::size_t eta_bin_i = 0; eta_bin_i < statEtaBins.size() + 2; ++eta_bin_i) {
482 eta_labels.push_back(TableUtils::makeEtaBinLabel(statEtaBins, eta_bin_i, useAbsEtaForStat));
483 }
484
485 accumulateToLastColumnRow(statPtBins.size()+2,statEtaBins.size()+2, m_statPerEta);
486 accumulateToLastColumnRow(statPtBins.size()+2,statEtaBins.size()+2, m_counterPerEta);
487
488 if (statPtBins.empty() || printDetails) {
489 parent.printCategories(pt_labels, eta_labels, counter_labels, m_statPerEta, m_counterPerEta,
490 (!statPtBins.empty()
491 ? hfill("pt ",
492 "eta",
494 +TableUtils::maxLabelWidth(eta_labels))
495 : std::string("eta") ),
496 !statPtBins.empty());
497 }
498 if (!statPtBins.empty()) {
499 parent.printData2D(pt_labels, eta_labels,
500 rotate
501 ? hfill("eta","\\ pt", TableUtils::maxLabelWidth(eta_labels))
502 : hfill("pt","\\ eta", TableUtils::maxLabelWidth(pt_labels)),
505 rotate);
506 }
507 }
508
509 // statistics in PDG ID bins.
510 if (pdgIdCategorisation) {
511 std::vector<std::string> pdg_id_labels;
512 pdg_id_labels.reserve( pdgId.size());
513 pdg_id_labels.push_back("Other");
514 for (unsigned int pdg_i=1; pdg_i < pdgId.size(); ++pdg_i) {
515 std::stringstream a_label;
516 a_label << HepPID::particleName(pdgId[pdg_i]) << " [" << pdgId[pdg_i] << "]";
517 pdg_id_labels.push_back( a_label.str() );
518 }
519 unsigned int max_pdg_id_slots=m_statPerPdgId.size()/(statPtBins.size()+2);
520 assert( m_statPerPdgId.size() % (statPtBins.size()+2) == 0 );
521 // also the unused columns are projected, but that does not harm :
522 accumulateToLastRow(statPtBins.size()+2,max_pdg_id_slots, m_statPerPdgId);
523 accumulateToLastRow(statPtBins.size()+2,max_pdg_id_slots, m_counterPerPdgId);
524
525 if (statPtBins.empty() || printDetails) {
526 parent.printCategories(pt_labels, pdg_id_labels, counter_labels, m_statPerPdgId, m_counterPerPdgId,
527 (!statPtBins.empty()
528 ? hfill("pt ",
529 "PDG-id",
531 +TableUtils::maxLabelWidth(pdg_id_labels))
532 : std::string("eta")),
533 !statPtBins.empty());
534 }
535 if (!statPtBins.empty()) {
536 parent.printData2D(pt_labels, pdg_id_labels,
537 rotate
538 ? hfill("PDG ID","\\ pt", TableUtils::maxLabelWidth(pdg_id_labels))
539 : hfill("pt","\\ PDG ID", TableUtils::maxLabelWidth(pt_labels)),
542 rotate);
543 }
544 }
545 }
546 }
547
549 {
550 if (msgLvl(MSG::INFO))
551 {
552 msg() << MSG::INFO << std::endl;
553 m_detailedStat.printStatTables( *this, m_statPtBins, m_statEtaBins, m_pdgId, m_printDetails.value(), m_pdgIdCategorisation.value(), m_useAbsEtaForStat );
554 {
555 std::array<std::string, kNCounter> counter_labels { std::string("Number of tracks"),
556 std::string("Number of truth particles with hit counts"),
557 std::string("Associated truth particles without hit counts"),
558 std::string("Tracks without associated truth particle"),
559 std::string("Tracks without selected, associated truth particle"),
560 std::string("Best truth particle without noise correction mismatch")
561 };
562 msg() << makeTable( m_counter, counter_labels) << std::endl;
563 }
564 msg() << endmsg;
565 }
566 }
567
568
569
570 void TrackTruthMatchingBaseAlg::printCategories(const std::vector<std::string> &row_category_labels,
571 const std::vector<std::string> &col_category_labels,
572 std::vector<std::string> &counter_labels,
573 std::vector< std::array< ActsUtils::Stat, kNCategorisedStat> > &stat_per_category,
574 std::vector< std::array< std::size_t, kNCategorisedCounter> > &counts_per_category,
575 const std::string &top_left,
576 bool print_sub_categories) const {
577 if (!row_category_labels.empty() && !col_category_labels.empty()) {
578 if (row_category_labels.size() * col_category_labels.size() > counts_per_category.size() ) {
579 ATH_MSG_ERROR( "Mismatch between category labels and number of counters (logic error -> fix needed):"
580 << row_category_labels.size() << " * " << col_category_labels.size()
581 << " > " << counts_per_category.size() );
582 }
583 constexpr std::size_t stat_column_width=14*4 + 3*3+4 + 9; // floats + seperators + integer of Stat output
584 assert( stat_per_category.size() == counts_per_category.size());
585 const unsigned int n_rows = row_category_labels.size();
586 const unsigned int n_cols = stat_per_category.size() / n_rows; // some columns at the end of a row might not have labels
587 // and are to be ignored
588
589 assert( stat_per_category.size() % n_rows == 0 );
590 for(unsigned int row_i=(print_sub_categories ? 0 : n_rows-1); row_i<n_rows; ++row_i) {
591 {
592 std::vector<std::string> stat_labels { std::string("Hit Efficiency") };
593 msg() << makeTable( stat_per_category, row_i*n_cols, kNCategorisedStat, kHitEfficiency, 1u, col_category_labels, stat_labels, top_left)
594 .columnWidth(stat_column_width)
595 .labelPrefix(row_category_labels.at(row_i)+" ")
596 .precision(std::vector<unsigned int>{3})
597 << std::endl;
598 }
599 {
600 std::vector<std::string> stat_labels { std::string("Hit Purity") };
601 msg() << makeTable( stat_per_category, row_i*n_cols, kNCategorisedStat, kHitPurity, 1u, col_category_labels, stat_labels, top_left)
602 .columnWidth(stat_column_width)
603 .labelPrefix(row_category_labels.at(row_i)+" ")
604 .precision(std::vector<unsigned int>{3})
605 << std::endl;
606 }
607 {
608 std::vector<std::string> stat_labels { std::string("Match probability") };
609 msg() << makeTable( stat_per_category, row_i*n_cols, kNCategorisedStat, kMatchProbability, 1u, col_category_labels, stat_labels, top_left)
610 .columnWidth(stat_column_width)
611 .labelPrefix(row_category_labels.at(row_i)+" ")
612 .precision(std::vector<unsigned int>{3})
613 << std::endl;
614 }
615 if (m_showRawCounts.value()) {
616 msg() << makeTable( counts_per_category, row_i*n_cols, kNCategorisedCounter, 0u, 1u, col_category_labels, counter_labels, top_left)
617 .labelPrefix(row_category_labels.at(row_i)+" ")
618 << std::endl;
619 }
620
621 if (m_computeTrackRecoEfficiency.value()) {
622 std::vector< std::array< float, 2> > eff;
623 eff.reserve(m_pdgId.size());
624 for (unsigned int category_i=0; category_i< col_category_labels.size(); ++category_i) {
625 eff.push_back( computeRatio( counts_per_category[category_i+row_i*n_cols][kNParticleWithAssociatedTrack],
626 counts_per_category[category_i+row_i*n_cols][kNTotalParticles] ) );
627 }
628 std::vector<std::string> eff_labels { std::string("reco efficiency"),
629 std::string("stat. uncertainty") };
630 msg() << makeTable( eff, 0u, eff.begin()->size(),0u,1u, col_category_labels, eff_labels, top_left)
631 .labelPrefix(row_category_labels.at(row_i)+" ")
632 .precision(std::vector<unsigned int>{3,3})
633 << std::endl;
634 }
635 }
636 }
637 }
638
639 namespace {
640 // helper to prevent temporary table data from beeing destructed too early
641 template <typename T>
642 struct TablePlusData {
643 TablePlusData(std::vector<T> &&values,
644 const std::vector<std::string> &row_labels,
645 const std::vector<std::string> &col_labels,
646 const std::string &top_left_label)
647 : m_data(std::move(values)),
648 m_assocTable({
649 TableUtils::Range2D<T>{m_data.data(),
650 row_labels.size(), // n-rows
651 col_labels.size(), // n-columns
652 col_labels.size(), // offset between rows
653 0u, // first column index
654 1u}, // offset between columns
655 TableUtils::Range<std::string> {row_labels.data(), row_labels.size()},
656 TableUtils::Range<std::string> {col_labels.data(), col_labels.size()},
657 top_left_label
658 })
659 {}
660 std::vector<T> m_data;
661 TableUtils::MultiColumnTable<T> m_assocTable;
662 TablePlusData &columnWidth(std::size_t value) { m_assocTable.columnWidth(value); return *this;}
663 TablePlusData &minLabelWidth(std::size_t value) { m_assocTable.minLabelWidth(value); return *this;}
664 TablePlusData &dumpHeader(bool value=true) { m_assocTable.dumpHeader(value); return *this;}
665 TablePlusData &dumpFooter(bool value=true) { m_assocTable.dumpFooter(value); return *this;}
666 TablePlusData &separateLastRow(bool value=true) { m_assocTable.separateLastRow(value); return *this;}
667 TablePlusData &labelPrefix(const std::string& value) { m_assocTable.labelPrefix(value); return *this;}
668 TablePlusData &precision(std::vector<unsigned int> &&precision) { m_assocTable.precision(std::move(precision)); return *this;}
669 };
670
671 template <typename T>
672 inline MsgStream &operator<<(MsgStream &out, const TablePlusData<T> &table) {
673 out << table.m_assocTable;
674 return out;
675 }
676
677 template <typename T>
678 inline std::ostream &operator<<(std::ostream &out, const TablePlusData<T> &table) {
679 out << table.m_assocTable;
680 return out;
681 }
682
683 template <class T_Container, class T_Function, typename T=double>
684 TablePlusData<T>
685 create2DTable(const std::vector<std::string> &row_category_labels,
686 const std::vector<std::string> &col_category_labels,
687 const std::string &top_left_label,
688 T_Container container,
689 T_Function function,
690 bool rotate) {
691 const unsigned int n_rows = row_category_labels.size();
692 const unsigned int n_cols = col_category_labels.size();
693 const unsigned int n_cols_total = container.size() / n_rows; // some columns at the end of a row might not have labels
694 std::vector< T > values;
695 values.reserve( n_rows * n_cols );
696 if (rotate) {
697 for (unsigned int col_i=0; col_i< n_cols; ++col_i) {
698 for (unsigned int row_i=0; row_i< n_rows; ++row_i) {
699 values.push_back( function(container.at( row_i * n_cols_total + col_i )) );
700 }
701 }
702 }
703 else {
704 for (unsigned int row_i=0; row_i< n_rows; ++row_i) {
705 for (unsigned int col_i=0; col_i< n_cols; ++col_i) {
706 values.push_back( function(container.at( row_i * n_cols_total + col_i )) );
707 }
708 }
709 }
710 return TablePlusData<T>(std::move(values),
711 rotate ? col_category_labels : row_category_labels, // rows
712 rotate ? row_category_labels : col_category_labels, // columns
713 top_left_label);
714 }
715 }
716
717 void TrackTruthMatchingBaseAlg::printData2D(const std::vector<std::string> &row_category_labels,
718 const std::vector<std::string> &col_category_labels,
719 const std::string &top_left_label,
720 std::vector< std::array< ActsUtils::Stat, kNCategorisedStat> > &stat_per_category,
721 std::vector< std::array< std::size_t, kNCategorisedCounter> > &counts_per_category,
722 bool rotate) const
723 {
724 if (!row_category_labels.empty() && !col_category_labels.empty()) {
725 if (row_category_labels.size() * col_category_labels.size() > counts_per_category.size() ) {
726 ATH_MSG_ERROR( "Mismatch between category labels and number of counters (logic error -> fix needed):"
727 << row_category_labels.size() << " * " << col_category_labels.size()
728 << " > " << counts_per_category.size() );
729 }
730 std::vector<unsigned int> column_precision;
731 column_precision.resize( rotate ? row_category_labels.size() : col_category_labels.size(), 3u);
732 assert( stat_per_category.size() == counts_per_category.size());
733 msg() << "Hit efficiency : contributing hits over all hits of best matching truth particle" << std::endl
734 << create2DTable( row_category_labels, col_category_labels, top_left_label, stat_per_category,
735 [](const std::array< ActsUtils::Stat, kNCategorisedStat> &stat) {
736 return stat.at(kHitEfficiency).mean();
737 },
738 rotate)
739 .columnWidth(10)
740 .precision(std::vector<unsigned int>(column_precision))
741 << std::endl;
742 msg() << "Hit purity : contributing hits of best matching truth particle over all hits on track" << std::endl
743 << create2DTable( row_category_labels, col_category_labels, top_left_label, stat_per_category,
744 [](const std::array< ActsUtils::Stat, kNCategorisedStat> &stat) {
745 return stat.at(kHitPurity).mean();
746 },
747 rotate)
748 .columnWidth(10)
749 .precision(std::vector<unsigned int>(column_precision))
750 << std::endl;
751 msg() << "Match probability : weighted common hit sum of best matching truth particle over total track weighted hit sum" << std::endl
752 << create2DTable( row_category_labels, col_category_labels, top_left_label, stat_per_category,
753 [](const std::array< ActsUtils::Stat, kNCategorisedStat> &stat) {
754 return stat.at(kMatchProbability).mean();
755 },
756 rotate)
757 .columnWidth(10)
758 .precision(std::vector<unsigned int>(column_precision))
759 << std::endl;
760
761 if (m_computeTrackRecoEfficiency.value()) {
762 msg() << "Reco efficiency : tracks with assoc. truth particle over all selected truth particles with assoc. measurements."
763 << std::endl
764 << create2DTable( row_category_labels, col_category_labels, top_left_label, counts_per_category,
765 [](const std::array< std::size_t, kNCategorisedCounter> &counter) {
766 return computeRatio( counter[kNParticleWithAssociatedTrack],
767 counter[kNTotalParticles] )[0];
768 },
769 rotate)
770 .columnWidth(10)
771 .precision(std::move(column_precision))
772 << std::endl;
773 }
774 }
775 }
776
778 if (m_weights.size() != s_NMeasurementTypes) {
779 ATH_MSG_FATAL( "There must be exactly one weight per measurement type. But got "
780 << m_weights.size() << " != " << s_NMeasurementTypes);
781 return StatusCode::FAILURE;
782 }
784 ATH_MSG_FATAL( "There must be exactly one weight for computing the matching probability per measurement type. But got "
785 << m_weightsForProb.size() << " != " << s_NMeasurementTypes);
786 return StatusCode::FAILURE;
787 }
788 for (unsigned int type_i=0; type_i<s_NMeasurementTypes; ++type_i) {
789 if (m_weightsForProb[type_i]<0. || m_weights[type_i]<0. || (m_weights[type_i]>0) != (m_weightsForProb[type_i]>0.)) {
790 ATH_MSG_FATAL( "Invalid weights (should be positive) or inconsistency of weights which are zero (match prob. weights, weights):"
791 << m_weightsForProb[type_i] << " vs " << m_weights[type_i]);
792 return StatusCode::FAILURE;
793 }
794 }
795 return StatusCode::SUCCESS;
796 }
797
799 const std::vector<float> &weights) {
800 assert( weights.size() == counts.size());
801 double sum=0.;
802 for (unsigned int count_i=0; count_i < counts.size(); ++count_i) {
803 sum += counts[count_i] * weights[count_i];
804 }
805 return sum;
806 }
807
809 const std::vector<float> &weights) {
810 assert( weights.size() == noise_counts.size());
811 double sum=0.;
812 for (unsigned int count_i=0; count_i < noise_counts.size(); ++count_i) {
813 sum -= weights[count_i] * noise_counts[count_i] - noise_counts[count_i];
814 }
815 return sum;
816 }
817}
Scalar eta() const
pseudorapidity method
#define endmsg
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_ERROR(x)
#define ATH_MSG_FATAL(x)
static Double_t a
#define sqr(t)
void rotate(double angler, GeoTrf::Vector2D &vector)
const double width
TableUtils::StatTable< T > makeTable(const std::array< T, N > &counter, const std::array< std::string, N > &label)
Definition TableUtils.h:523
Container for hit counts per track Contains hit counts per associated truth particle and the total hi...
const container & countsPerTruthParticle() const
vector with counts per associated truth particle (read only)
HitCounterArray & noiseCounts()
Noise hit counts per track.
HitCounterArray & totalCounts()
Total hit counts per track.
void checkBinOrder(const std::vector< float > &bin_edges, const std::string &bin_label) const
check that bins are in increasing order.
static constexpr unsigned int s_NMeasurementTypes
void printData2D(const std::vector< std::string > &row_category_labels, const std::vector< std::string > &col_category_labels, const std::string &top_left_label, std::vector< std::array< ActsUtils::Stat, kNCategorisedStat > > &stat_per_category, std::vector< std::array< std::size_t, kNCategorisedCounter > > &counts_per_category, bool rotate) const
SG::ReadHandleKey< TruthParticleHitCounts > m_truthHitCounts
TruthMatchResult analyseTrackTruth(const TruthParticleHitCounts &truth_particle_hit_counts, const HitCountsPerTrack &track_hit_counts, EventStat &event_stat) const
DebugCounter< TrackFindingValidationDebugHists > m_debugCounter
void printCategories(const std::vector< std::string > &pt_category_labels, const std::vector< std::string > &eta_category_labels, std::vector< std::string > &counter_labels, std::vector< std::array< ActsUtils::Stat, kNCategorisedStat > > &stat_per_category, std::vector< std::array< std::size_t, kNCategorisedCounter > > &counts_per_category, const std::string &top_left_label, bool print_sub_categories) const
EventStatBase< TrackFindingValidationDetailedStat > EventStat
static double noiseCorrection(const ActsTrk::HitCounterArray &noise_counts, const std::vector< float > &weights)
std::size_t getPtPdgIdStatCategory(float pt, int pdg_id) const
Return the category based on the PDG ID.
static double weightedCountSum(const ActsTrk::HitCounterArray &counts, const std::vector< float > &weights)
Property< std::vector< float > > m_statPtBins
void postProcessEventStat(const TruthParticleHitCounts &truth_particle_hit_counts, std::size_t n_tracks, EventStat &event_stat) const
Gaudi::Property< std::vector< float > > m_weightsForProb
std::size_t getPtEtaStatCategory(float pt, float eta) const
Return the category based on the provided eta value.
TrackTruthMatchingBaseAlg(const std::string &name, ISvcLocator *pSvcLocator)
Property< std::vector< float > > m_statEtaBins
Gaudi::Property< std::vector< float > > m_weights
ToolHandle< IAthSelectionTool > m_truthSelectionTool
void add(double val)
Gather statistics and fill the histogram if not disabled.
Definition StatUtils.h:117
bool msgLvl(const MSG::Level lvl) const
An algorithm that can be simultaneously executed in multiple threads.
int pdg_id() const
PDG ID code.
virtual double pt() const override final
The transverse momentum ( ) of the particle.
virtual double eta() const override final
The pseudorapidity ( ) of the particle.
std::string tail(std::string s, const std::string &pattern)
tail of a string
std::string head(std::string s, const std::string &pattern)
head of a string
The AlignStoreProviderAlg loads the rigid alignment corrections and pipes them through the readout ge...
std::unordered_map< const xAOD::TruthParticle *, HitCounterArray > TruthParticleHitCounts
constexpr bool TrackFindingValidationDetailedStat
std::underlying_type_t< T > to_underlying(T val)
MsgStream & operator<<(MsgStream &out, const ActsUtils::Stat &stat)
constexpr bool TrackFindingValidationDebugHists
void dumpStat(T_Stream &out, const Stat &stat)
Dump the given statistics object to the given output stream.
Definition StatUtils.h:63
StatusCode accept(const xAOD::Muon *mu)
float computeRatio(std::size_t numerator, std::size_t denominator)
Definition TableUtils.h:414
std::size_t maxLabelWidth(const T_Collection &col)
Definition TableUtils.h:290
std::string makeEtaBinLabel(const std::vector< float > &eta_bins, std::size_t eta_bin_i, bool abs_eta=false)
Definition TableUtils.h:514
std::string makeBinLabel(const std::string &variable_name, const std::vector< float > &bins, std::size_t bin_i, bool abs_value=false, int precision=1)
Definition TableUtils.h:485
@ u
Enums for curvilinear frames.
Definition ParamDefs.h:77
STL namespace.
TruthParticle_v1 TruthParticle
Typedef to implementation.
void incrementTotal(unsigned int eta_category_i, unsigned int pdg_id_category_i)
BaseStat< DetailEnabled > & operator+=(const BaseStat< DetailEnabled > &event_stat)
void printStatTables(const TrackTruthMatchingBaseAlg &parent, const std::vector< float > &statPtBins, const std::vector< float > &statEtaBins, std::vector< int > &pdgId, bool printDetails, bool pdgIdCategorisation, bool useAbsEtaForStat)
void fillMeasForTruthParticleWithoutCount(double weighted_measurement_sum) const
void fillTruthMatchProb(const std::array< float, 2 > &best_match_prob) const
void fill(unsigned int eta_category_i, unsigned int pdg_id_category_i, float hit_efficiency, float hit_purity, float match_prob, const xAOD::TruthParticle *best_match)
float m_hitPurity
fraction of hits originting from best match over total reco hits
float m_matchProbability
the matching probability based on weighted hit sums
float m_hitEfficiency
fraction of hits originting from best match over total best match hits
const xAOD::TruthParticle * m_truthParticle
best matching truth particle or nullptr