Skip to content

Commit 59961cd

Browse files
committed
feat: add Truthiness algorithm for event quality assessment
1 parent 8583004 commit 59961cd

File tree

5 files changed

+206
-0
lines changed

5 files changed

+206
-0
lines changed

src/algorithms/reco/Truthiness.cc

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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

src/algorithms/reco/Truthiness.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
// Copyright (C) 2025 Wouter Deconinck
3+
4+
#pragma once
5+
6+
#include <algorithms/algorithm.h>
7+
#include <edm4eic/MCRecoParticleAssociation.h>
8+
#include <edm4eic/ReconstructedParticle.h>
9+
#include <edm4hep/MCParticle.h>
10+
#include <edm4hep/utils/vector_utils.h>
11+
#include <string>
12+
#include <string_view>
13+
14+
#include "TruthinessConfig.h"
15+
#include "algorithms/interfaces/WithPodConfig.h"
16+
17+
namespace eicrecon {
18+
19+
using TruthinessAlgorithm = algorithms::Algorithm<
20+
algorithms::Input<edm4hep::MCParticleCollection, edm4eic::ReconstructedParticleCollection,
21+
edm4eic::MCRecoParticleAssociationCollection>,
22+
algorithms::Output<>>;
23+
24+
class Truthiness : public TruthinessAlgorithm, public WithPodConfig<TruthinessConfig> {
25+
26+
public:
27+
Truthiness(std::string_view name)
28+
: TruthinessAlgorithm{
29+
name,
30+
{"inputMCParticles", "inputReconstructedParticles", "inputAssociations"},
31+
{},
32+
"Calculate truthiness metric comparing reconstructed particles to MC "
33+
"truth."} {}
34+
35+
void init() final {};
36+
void process(const Input&, const Output&) const final;
37+
};
38+
39+
} // namespace eicrecon
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
// Copyright (C) 2025 Wouter Deconinck
3+
4+
#pragma once
5+
6+
namespace eicrecon {
7+
8+
struct TruthinessConfig {
9+
// No configuration parameters needed at this time
10+
};
11+
12+
} // namespace eicrecon
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
// Copyright (C) 2025 Wouter Deconinck
3+
4+
#pragma once
5+
6+
#include <JANA/JEvent.h>
7+
#include <memory>
8+
#include <string>
9+
#include <utility>
10+
#include <vector>
11+
12+
#include "algorithms/reco/Truthiness.h"
13+
#include "extensions/jana/JOmniFactory.h"
14+
#include "services/algorithms_init/AlgorithmsInit_service.h"
15+
16+
namespace eicrecon {
17+
18+
class Truthiness_factory : public JOmniFactory<Truthiness_factory> {
19+
20+
public:
21+
using FactoryT = JOmniFactory<Truthiness_factory>;
22+
23+
private:
24+
std::unique_ptr<Truthiness> m_algo;
25+
26+
FactoryT::PodioInput<edm4hep::MCParticle> m_mc_particles_input{this};
27+
FactoryT::PodioInput<edm4eic::ReconstructedParticle> m_rc_particles_input{this};
28+
FactoryT::PodioInput<edm4eic::MCRecoParticleAssociation> m_associations_input{this};
29+
30+
FactoryT::Service<AlgorithmsInit_service> m_algorithmsInit{this};
31+
32+
public:
33+
void Configure() {
34+
m_algo = std::make_unique<Truthiness>(this->GetPrefix());
35+
m_algo->level(static_cast<algorithms::LogLevel>(this->logger()->level()));
36+
m_algo->init();
37+
}
38+
39+
void Process(int32_t /* run_number */, uint64_t /* event_number */) {
40+
m_algo->process({m_mc_particles_input(), m_rc_particles_input(), m_associations_input()}, {});
41+
}
42+
};
43+
44+
} // namespace eicrecon

src/global/reco/reco.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#include "factories/reco/ScatteredElectronsTruth_factory.h"
4343
#include "factories/reco/TrackClusterMatch_factory.h"
4444
#include "factories/reco/TransformBreitFrame_factory.h"
45+
#include "factories/reco/Truthiness_factory.h"
4546
#include "factories/reco/UndoAfterBurnerMCParticles_factory.h"
4647

4748
extern "C" {
@@ -267,5 +268,10 @@ void InitPlugin(JApplication* app) {
267268

268269
app->Add(new JOmniFactoryGeneratorT<PrimaryVertices_factory>(
269270
"PrimaryVertices", {"CentralTrackVertices"}, {"PrimaryVertices"}, {}, app));
271+
272+
// Truthiness metric for event quality assessment
273+
app->Add(new JOmniFactoryGeneratorT<Truthiness_factory>(
274+
"Truthiness", {"MCParticles", "ReconstructedParticles", "ReconstructedParticleAssociations"},
275+
{}, app));
270276
}
271277
} // extern "C"

0 commit comments

Comments
 (0)