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