|
| 1 | +// SPDX-License-Identifier: LGPL-3.0-or-later |
| 2 | +// Copyright (C) 2025 Wouter Deconinck |
| 3 | + |
| 4 | +#include "Truthiness.h" |
| 5 | + |
| 6 | +#include <edm4eic/MCRecoParticleAssociationCollection.h> |
| 7 | +#include <edm4eic/ReconstructedParticleCollection.h> |
| 8 | +#include <edm4hep/MCParticleCollection.h> |
| 9 | +#include <edm4hep/utils/vector_utils.h> |
| 10 | +#include <cmath> |
| 11 | +#include <map> |
| 12 | +#include <set> |
| 13 | + |
| 14 | +namespace eicrecon { |
| 15 | + |
| 16 | +void Truthiness::process(const Truthiness::Input& input, |
| 17 | + const Truthiness::Output& /* output */) const { |
| 18 | + |
| 19 | + const auto [mc_particles, rc_particles, associations] = input; |
| 20 | + |
| 21 | + double truthiness = 0.0; |
| 22 | + |
| 23 | + // Track which MC particles and reconstructed particles are covered by associations |
| 24 | + std::set<edm4hep::MCParticle> associated_mc_particles; |
| 25 | + std::set<edm4eic::ReconstructedParticle> associated_rc_particles; |
| 26 | + |
| 27 | + // Process all associations |
| 28 | + for (const auto& assoc : *associations) { |
| 29 | + const auto& mc_part = assoc.getSim(); |
| 30 | + const auto& rc_part = assoc.getRec(); |
| 31 | + |
| 32 | + // Track that these particles have associations |
| 33 | + associated_mc_particles.insert(mc_part); |
| 34 | + associated_rc_particles.insert(rc_part); |
| 35 | + |
| 36 | + // Get particle properties |
| 37 | + const auto mc_momentum = mc_part.getMomentum(); |
| 38 | + const auto rc_momentum = rc_part.getMomentum(); |
| 39 | + |
| 40 | + const double mc_p = edm4hep::utils::magnitude(mc_momentum); |
| 41 | + const double mc_energy = std::sqrt(mc_p * mc_p + mc_part.getMass() * mc_part.getMass()); |
| 42 | + const double rc_energy = rc_part.getEnergy(); |
| 43 | + |
| 44 | + // Calculate energy difference squared |
| 45 | + const double energy_diff = mc_energy - rc_energy; |
| 46 | + const double energy_penalty = energy_diff * energy_diff; |
| 47 | + |
| 48 | + // Calculate momentum component differences squared |
| 49 | + const double px_diff = mc_momentum.x - rc_momentum.x; |
| 50 | + const double py_diff = mc_momentum.y - rc_momentum.y; |
| 51 | + const double pz_diff = mc_momentum.z - rc_momentum.z; |
| 52 | + const double momentum_penalty = px_diff * px_diff + py_diff * py_diff + pz_diff * pz_diff; |
| 53 | + |
| 54 | + // PDG code mismatch penalty |
| 55 | + const double pdg_penalty = (mc_part.getPDG() != rc_part.getPDG()) ? 1.0 : 0.0; |
| 56 | + |
| 57 | + const double assoc_penalty = energy_penalty + momentum_penalty + pdg_penalty; |
| 58 | + |
| 59 | + debug("Association: MC PDG={} (E={:.3f}, p=[{:.3f},{:.3f},{:.3f}]) <-> " |
| 60 | + "RC PDG={} (E={:.3f}, p=[{:.3f},{:.3f},{:.3f}])", |
| 61 | + mc_part.getPDG(), mc_energy, mc_momentum.x, mc_momentum.y, mc_momentum.z, |
| 62 | + rc_part.getPDG(), rc_energy, rc_momentum.x, rc_momentum.y, rc_momentum.z); |
| 63 | + debug(" Energy penalty: {:.6f}, Momentum penalty: {:.6f}, PDG penalty: {:.0f}", energy_penalty, |
| 64 | + momentum_penalty, pdg_penalty); |
| 65 | + |
| 66 | + truthiness += assoc_penalty; |
| 67 | + } |
| 68 | + |
| 69 | + // Penalty for unassociated charged MC particles with generator status 2 |
| 70 | + int unassociated_mc_count = 0; |
| 71 | + for (const auto& mc_part : *mc_particles) { |
| 72 | + if (mc_part.getGeneratorStatus() == 2 && mc_part.getCharge() != 0.0) { |
| 73 | + if (associated_mc_particles.find(mc_part) == associated_mc_particles.end()) { |
| 74 | + unassociated_mc_count++; |
| 75 | + debug("Unassociated MC particle: PDG={}, charge={:.1f}, status={}", mc_part.getPDG(), |
| 76 | + mc_part.getCharge(), mc_part.getGeneratorStatus()); |
| 77 | + } |
| 78 | + } |
| 79 | + } |
| 80 | + const double mc_penalty = static_cast<double>(unassociated_mc_count); |
| 81 | + debug("Unassociated charged MC particles (status 2): {} (penalty: {:.0f})", unassociated_mc_count, |
| 82 | + mc_penalty); |
| 83 | + truthiness += mc_penalty; |
| 84 | + |
| 85 | + // Penalty for unassociated reconstructed particles |
| 86 | + int unassociated_rc_count = 0; |
| 87 | + for (const auto& rc_part : *rc_particles) { |
| 88 | + if (associated_rc_particles.find(rc_part) == associated_rc_particles.end()) { |
| 89 | + unassociated_rc_count++; |
| 90 | + debug("Unassociated reconstructed particle: PDG={}, E={:.3f}, p=[{:.3f},{:.3f},{:.3f}]", |
| 91 | + rc_part.getPDG(), rc_part.getEnergy(), rc_part.getMomentum().x, rc_part.getMomentum().y, |
| 92 | + rc_part.getMomentum().z); |
| 93 | + } |
| 94 | + } |
| 95 | + const double rc_penalty = static_cast<double>(unassociated_rc_count); |
| 96 | + debug("Unassociated reconstructed particles: {} (penalty: {:.0f})", unassociated_rc_count, |
| 97 | + rc_penalty); |
| 98 | + truthiness += rc_penalty; |
| 99 | + |
| 100 | + // Report final truthiness |
| 101 | + info("Event truthiness: {:.6f} (from {} associations, {} unassociated MC, {} unassociated RC)", |
| 102 | + truthiness, associations->size(), unassociated_mc_count, unassociated_rc_count); |
| 103 | +} |
| 104 | + |
| 105 | +} // namespace eicrecon |
0 commit comments