11 #include "HepPDT/ParticleDataTable.hh"
15 #include <type_traits>
20 template <
typename T_EnumClass >
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;
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];
39 template <
typename T,
bool LastRowOnly=false>
40 void accumulateToLastColumnRow(std::size_t n_rows, std::size_t n_cols, std::vector<T> &
stat) {
43 auto stat_begin_iter =
stat.begin();
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());
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 );
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);
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 );
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);
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());
81 for (
const std::array<T,N> &src_elm :
src) {
83 std::array<T,N> &dest_elm =
dest[
idx];
85 for (
const T &
val : src_elm) {
86 assert(
val_i < dest_elm.size());
94 inline double sqr(
double a) {
return a*
a; }
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) ))
105 std::string hfill(
const std::string &
head,
const std::string &
tail, std::size_t
width) {
107 std::stringstream
out;
114 std::stringstream
out;
116 out <<
" " << std::setw(2) <<
static_cast<int>(
val);
130 ISvcLocator *pSvcLocator)
143 template <
bool IsDebug>
144 template <
class T_OutStream>
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;
156 template <
bool IsDebug>
159 std::lock_guard<std::mutex> lock(m_mutex);
160 m_measPerTruthParticleWithoutCounts.add(weighted_measurement_sum);
163 template <
bool 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]);
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(), 0
u);
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;
189 total -= m_detailedStat.m_truthSelectionCuts.m_histogram.at(cut_i++);
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;
195 total -= m_detailedStat.m_truthSelectionCuts.m_histogram.at(cut_i);
197 msg() << std::setw(3) <<
"" <<
" " << std::setw(20) << total <<
" overflow" << std::endl;
200 msg() << m_detailedStat.m_truthSelectionCuts.histogramToString();
205 return StatusCode::SUCCESS;
215 std::array<float,2> best_match_prob {};
218 std::array<float,2> best_match_prob_nonoise{};
226 double total_sum_for_prob_nonoise=total_sum_for_prob;
227 total_sum_for_prob += noise_sum;
229 if (total_sum_for_prob>0.) {
233 unsigned int truth_i=0;
235 for (
const std::pair<const xAOD::TruthParticle *, HitCounterArray > &
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));
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;
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;
265 if (best_match_i_nonoise[0] != best_match_i[0]) {
271 ret.m_matchProbability = best_match_prob[0];
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 );
284 if (best_truth_particle_counts_iter != truth_particle_hit_counts.end()) {
287 hit_efficiency = truth_sum > 0
u ? (common_truth_sum/truth_sum) : 0.;
299 float hit_purity = common_truth_sum / total_sum;
300 ret.m_hitPurity = hit_purity;
301 ret.m_hitEfficiency = hit_efficiency;
305 float best_match_pt = best_match->
pt();
308 event_stat.
fill( eta_category_i, pdg_id_category_i, hit_efficiency, hit_purity, best_match_prob[0], best_match );
323 <<
". This should not happen." );
332 std::size_t n_tracks,
337 for(
const std::pair<const xAOD::TruthParticle * const,ActsTrk::HitCounterArray> &truth_particle : truth_particle_hit_counts) {
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());
354 std::lock_guard<std::mutex> lock(m_statMutex);
363 m_detailedStat += event_stat;
370 std::vector<float>::const_iterator pt_bin_iter = std::upper_bound(
m_statPtBins.begin(),
373 std::vector<float>::const_iterator eta_bin_iter = std::upper_bound(
m_statEtaBins.begin(),
377 +
static_cast<std::size_t
>(eta_bin_iter -
m_statEtaBins.begin());
380 std::vector<float>::const_iterator pt_bin_iter = std::upper_bound(
m_statPtBins.begin(),
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);
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;
396 iter=m_pdgId.begin();
399 return (m_pdgId.capacity()) *
static_cast<std::size_t
>(pt_bin_iter -
m_statPtBins.begin())
400 + (iter - m_pdgId.begin());
404 if (!bin_edges.empty())
406 float last_eta = bin_edges[0];
407 for (
float eta : bin_edges)
411 ATH_MSG_FATAL(bin_label +
" bins for statistics counter not in ascending order.");
434 m_pdgId.reserve(max_pdg_id_slots);
435 m_pdgId.push_back(1000000000);
438 template <
bool DetailEnabled>
441 if constexpr(DetailEnabled) {
451 template <
bool DetailEnabled>
453 const std::vector<float> &statPtBins,
454 const std::vector<float> &statEtaBins,
455 std::vector< int > &pdgId,
457 bool pdgIdCategorisation,
458 bool useAbsEtaForStat) {
459 if constexpr(DetailEnabled) {
460 static constexpr
bool rotate=
true;
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) {
474 for (std::size_t bin_i = 0; bin_i < statPtBins.size() + 2; ++bin_i) {
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) {
485 accumulateToLastColumnRow(statPtBins.size()+2,statEtaBins.size()+2, m_statPerEta);
486 accumulateToLastColumnRow(statPtBins.size()+2,statEtaBins.size()+2, m_counterPerEta);
488 if (statPtBins.empty() || printDetails) {
489 parent.printCategories(pt_labels, eta_labels, counter_labels, m_statPerEta, m_counterPerEta,
495 : std::string(
"eta") ),
496 !statPtBins.empty());
498 if (!statPtBins.empty()) {
499 parent.printData2D(pt_labels, eta_labels,
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;
517 pdg_id_labels.push_back( a_label.str() );
519 unsigned int max_pdg_id_slots=m_statPerPdgId.size()/(statPtBins.size()+2);
520 assert( m_statPerPdgId.size() % (statPtBins.size()+2) == 0 );
522 accumulateToLastRow(statPtBins.size()+2,max_pdg_id_slots, m_statPerPdgId);
523 accumulateToLastRow(statPtBins.size()+2,max_pdg_id_slots, m_counterPerPdgId);
525 if (statPtBins.empty() || printDetails) {
526 parent.printCategories(pt_labels, pdg_id_labels, counter_labels, m_statPerPdgId, m_counterPerPdgId,
532 : std::string(
"eta")),
533 !statPtBins.empty());
535 if (!statPtBins.empty()) {
536 parent.printData2D(pt_labels, pdg_id_labels,
552 msg() << MSG::INFO << std::endl;
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")
562 msg() <<
makeTable( m_counter, counter_labels) << std::endl;
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() );
583 constexpr std::size_t stat_column_width=14*4 + 3*3+4 + 9;
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;
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) {
592 std::vector<std::string> stat_labels { std::string(
"Hit Efficiency") };
594 .columnWidth(stat_column_width)
595 .labelPrefix(row_category_labels.at(row_i)+
" ")
596 .precision(std::vector<unsigned int>{3})
600 std::vector<std::string> stat_labels { std::string(
"Hit Purity") };
602 .columnWidth(stat_column_width)
603 .labelPrefix(row_category_labels.at(row_i)+
" ")
604 .precision(std::vector<unsigned int>{3})
608 std::vector<std::string> stat_labels { std::string(
"Match probability") };
610 .columnWidth(stat_column_width)
611 .labelPrefix(row_category_labels.at(row_i)+
" ")
612 .precision(std::vector<unsigned int>{3})
617 .labelPrefix(row_category_labels.at(row_i)+
" ")
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) {
628 std::vector<std::string> eff_labels { std::string(
"reco efficiency"),
629 std::string(
"stat. uncertainty") };
631 .labelPrefix(row_category_labels.at(row_i)+
" ")
632 .precision(std::vector<unsigned int>{3,3})
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)
668 TablePlusData &precision(std::vector<unsigned int> &&precision) {
m_assocTable.precision(std::move(precision));
return *
this;}
671 template <
typename T>
677 template <
typename T>
683 template <
class T_Container,
class T_Function,
typename T=
double>
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,
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;
695 values.reserve( n_rows * n_cols );
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 )) );
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 )) );
710 return TablePlusData<T>(std::move(
values),
711 rotate ? col_category_labels : row_category_labels,
712 rotate ? row_category_labels : col_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,
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() );
730 std::vector<unsigned int> column_precision;
731 column_precision.resize(
rotate ? row_category_labels.size() : col_category_labels.size(), 3
u);
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) {
740 .precision(std::vector<unsigned int>(column_precision))
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) {
749 .precision(std::vector<unsigned int>(column_precision))
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) {
758 .precision(std::vector<unsigned int>(column_precision))
762 msg() <<
"Reco efficiency : tracks with assoc. truth particle over all selected truth particles with assoc. measurements."
764 << create2DTable( row_category_labels, col_category_labels, top_left_label, counts_per_category,
765 [](
const std::array< std::size_t, kNCategorisedCounter> &
counter) {
771 .precision(std::move(column_precision))
779 ATH_MSG_FATAL(
"There must be exactly one weight per measurement type. But got "
781 return StatusCode::FAILURE;
784 ATH_MSG_FATAL(
"There must be exactly one weight for computing the matching probability per measurement type. But got "
786 return StatusCode::FAILURE;
790 ATH_MSG_FATAL(
"Invalid weights (should be positive) or inconsistency of weights which are zero (match prob. weights, weights):"
792 return StatusCode::FAILURE;
795 return StatusCode::SUCCESS;
799 const std::vector<float> &
weights) {
800 assert(
weights.size() == counts.size());
802 for (
unsigned int count_i=0; count_i < counts.size(); ++count_i) {
809 const std::vector<float> &
weights) {
810 assert(
weights.size() == noise_counts.size());
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];