ATLAS Offline Software
Loading...
Searching...
No Matches
TruthVertexDecoratorAlg.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#include <cstdlib>
9#include <unordered_set>
10
11namespace ParticleJetTools{
12
14 const xAOD::TruthParticle* part
15) {
16 const xAOD::TruthParticle* parent = part;
17 for ( int i = 0; i < 1000; i++ ) {
18 const xAOD::TruthParticle* next_parent = nullptr;
19
20 if(parent->nParents() > 1){
21 float cur_pt = -1.0;
22 for (size_t j = 0; j < parent->nParents(); j++) {
23 const xAOD::TruthParticle* p = parent->parent(j);
24 if ( !p ) continue;
25 if ( p->pt() > cur_pt ) {
26 cur_pt = p->pt();
27 next_parent = p;
28 }
29 }
30 }else{
31 next_parent = parent->parent(0);
32 }
33 if ( !next_parent ) break;
34 if ( !next_parent->hasDecayVtx() || !next_parent->decayVtx() ) break;
35 parent = next_parent;
36 }
37 return parent;
38}
39
40std::vector<const xAOD::TruthParticle*> get_all_initial_particles(
41 const std::vector<const xAOD::TruthParticle*>& truth_particles
42) {
43 std::unordered_set<const xAOD::TruthParticle*> pv_parts;
44
45 for ( const auto& part : truth_particles ) {
46 auto initial_part = get_initial_particle(part);
47 if ( initial_part ) {
48 pv_parts.insert(initial_part);
49 }
50 }
51
52 std::vector<const xAOD::TruthParticle*> initial_particles;
53 initial_particles.reserve(pv_parts.size());
54 for ( const auto& part : pv_parts ) {
55 initial_particles.push_back(part);
56 }
57 return initial_particles;
58
59}
60
66std::vector<const xAOD::TruthVertex*> get_matched_primary_vertices(
67 const xAOD::TruthVertex* pv, const std::vector<const xAOD::TruthVertex*>& truth_vertices,
68 float selection_distance = 0.001,
70) {
71 std::vector<const xAOD::TruthVertex*> matched_vertices;
72
73 float closest_distance = 1e9;
74 const xAOD::TruthVertex* closest_vertex = nullptr;
75 for( const auto& vert : truth_vertices ) {
76 if ( !vert ) continue;
77 float r = FatVertex::vertex_distance(pv, vert);
78 if(r < selection_distance){
79 matched_vertices.push_back(vert);
80 if (r < closest_distance) {
81 closest_distance = r;
82 closest_vertex = vert;
83 }else if (r == closest_distance) {
84 // If we have two vertices at the same distance, we'll take the one with the lowest
85 // barcode as it *should* be the case that this is the actual PV.
86 if ( std::abs(acc_uid(*vert)) < std::abs(acc_uid(*closest_vertex)) ) {
87 closest_vertex = vert;
88 }
89 }
90 }
91 }
92 // Set it such that the first vertex is the closest vertex
93 if ( !matched_vertices.empty() && closest_vertex ) {
94 // remove the closest vertex from the matched vertices
95 matched_vertices.erase(
96 std::remove(matched_vertices.begin(), matched_vertices.end(), closest_vertex),
97 matched_vertices.end()
98 );
99 // Insert into the front of the vector
100 matched_vertices.insert(matched_vertices.begin(), closest_vertex);
101
102 }
103 return matched_vertices;
104
105}
106
107TruthVertexDecoratorAlg::TruthVertexDecoratorAlg(const std::string& name, ISvcLocator* loc)
108 : AthReentrantAlgorithm(name, loc) {}
109
111 ATH_MSG_INFO("Initializing " << name() << "... ");
112
113 // Initialize Container keys
114 ATH_MSG_DEBUG("Initializing containers:");
116 ATH_MSG_DEBUG(" ** " << m_TruthPVsKey);
118
119 ATH_CHECK(m_TruthContainerKey.initialize());
120 ATH_CHECK(m_TruthPVsKey.initialize());
122 ATH_CHECK(m_TrackContainerKey.initialize());
123
124 m_vertexValid = m_TruthContainerKey.key() + "." + m_vertexValid.key();
136
137 ATH_CHECK(m_vertexValid.initialize());
138 ATH_CHECK(m_vertexDecayType.initialize());
140 ATH_CHECK(m_vertexDecayID.initialize());
141 ATH_CHECK(m_vertexDecayParentID.initialize());
142 ATH_CHECK(m_vertexDecayNCharged.initialize());
143 ATH_CHECK(m_vertexDecayNNeutral.initialize());
147 ATH_CHECK(m_vertexDecayDistance.initialize());
148 ATH_CHECK(m_vertexIsVertex.initialize());
149
153 m_trackPDGID = m_TrackContainerKey.key() + '.' + m_trackPDGID.key();
155 ATH_CHECK(m_trackTPDecayVertexID.initialize());
158 ATH_CHECK(m_trackPDGID.initialize());
159 ATH_CHECK(m_trackParentPDGID.initialize());
160
162
165
167 m_truthMatchProbabilityAcc = SG::AuxElement::ConstAccessor<float>(m_truthMatchProbabilityAuxName);
168 return StatusCode::SUCCESS;
169}
170
171StatusCode TruthVertexDecoratorAlg::execute(const EventContext& ctx) const {
172 ATH_MSG_DEBUG("Executing " << name() << "... ");
173
174 // read collections
176 ATH_CHECK(truth_particles.isValid());
177 ATH_MSG_DEBUG("Retrieved " << truth_particles->size() << " truth_particles...");
178
179 // Get the truth PV
181 ATH_CHECK(truth_PVs.isValid());
182 if ( truth_PVs->size() != 1 ) {
183 ATH_MSG_ERROR("Truth PVs != 1");
184 return StatusCode::FAILURE;
185 }
186 const xAOD::TruthVertex* truth_PV = truth_PVs->at(0);
187
188 // Get all truth vertices, and then get the 'matched' primary vertex
190 ATH_CHECK(truth_vertices.isValid());
191 std::vector<const xAOD::TruthVertex*> all_truth_vertices;
192 for ( auto vert : *truth_vertices ) { all_truth_vertices.push_back(vert); }
193 auto pvs = get_matched_primary_vertices(truth_PV, all_truth_vertices, 0.001, m_acc_uid);
194 if (pvs.empty()) {
195 ATH_MSG_ERROR("No matched primary vertex found!");
196 return StatusCode::FAILURE;
197 }
198 const xAOD::TruthVertex* pv = pvs[0];
199 // Get all truth particles, and generate a map for speedier lookup
200 std::vector<const xAOD::TruthParticle*> sorted_truth_particles;
201 for ( const auto tp : *truth_particles ) { sorted_truth_particles.push_back(tp); }
202 std::sort(
203 sorted_truth_particles.begin(), sorted_truth_particles.end(),
204 [](const xAOD::IParticle* p1, const xAOD::IParticle* p2) {
205 return p1->pt() < p2->pt();
206 }
207 );
208
209 // We search all particles to get a vector of all initial particles. We need to do this
210 // as just looking at the PV isn't sufficient, due to ISR etc
211 auto initial_particles = get_all_initial_particles(sorted_truth_particles);
212
213
214 // Generate a vector of all fat veritces, starting at the PV. We include all initial particles
215 // as part of this fat-pv
216 std::vector<FatVertex::FatVertex> fat_vertices = FatVertex::generateFatVertices(
218 initial_particles
219 );
220
221 // Setup parent/child links for all vertices
222 for(auto& fat_vertex : fat_vertices){
223 fat_vertex.doParentChildLinks(fat_vertices);
224 }
225
226 // Select based on a pT threshold
227 std::vector<FatVertex::FatVertex> hpt_fat_vertices;
228 for ( auto& fat_vertex : fat_vertices ) {
229 if ( fat_vertex.getPT() > 0 || fat_vertex.is_pv ) hpt_fat_vertices.push_back(fat_vertex);
230 }
231
232
233
234 // Define the write handles for decorating the truth particles with their vertex information
247
248 // Anything which doesn't have a decay vertex is a stable particle
249 std::vector<const xAOD::TruthParticle*> stable_neutrals;
250 std::vector<const xAOD::TruthParticle*> stable_charged;
251 std::unordered_map<int, int> barcode_to_other_id;
252
253 // Default: unclassified (-1). PV particles get type 0 through the
254 // actual PV vertex traversal below. Only fakes (-3) and pileup (-2)
255 // use other negative values.
256 for ( const auto& truth_particle : sorted_truth_particles ) {
257 vertexValid(*truth_particle) = 0;
258 vertexType(*truth_particle) = -1;
259 vertexSimpleType(*truth_particle) = -1;
260 vertexDecayID(*truth_particle) = -1;
261 vertexDecayParentID(*truth_particle) = -1;
262 vertexDecayPVDistance(*truth_particle) = NAN;
263 vertexDecayDistance(*truth_particle) = NAN;
264 vertexDecayNCharged(*truth_particle) = -1;
265 vertexDecayNNeutral(*truth_particle) = -1;
266 vertexDecayAllNCharged(*truth_particle) = -1;
267 vertexDecayAllNNeutral(*truth_particle) = -1;
268 vertexIsVertex(*truth_particle) = 0;
269 }
270 // Iterate all the vertices, defining each based on the counter
271 std::unordered_map<int, int> barcode_to_vertex_id;
272 int counter = 0;
273 for ( auto fat_vertex : hpt_fat_vertices ) {
274 for ( auto part : fat_vertex.internal ) {
275 barcode_to_other_id[m_acc_uid(*part)] = counter;
276 vertexDecayID(*part) = counter;
277 }
278 for ( auto part : fat_vertex.inparts ) {
279
280 FatVertex::VertexType vt = fat_vertex.getType(m_acc_uid);
281 // This is the PV, as we are writing the 'input' particles of the PV, this doesn't really
282 // make sense, e.g. this could be argued as one/both protons, but more likely its a gluon
283 // and so not a particle we want to write.
285 continue;
286 }
287
288 vertexValid(*part) = 1;
289 vertexType(*part) = int(vt.detailedType);
290 vertexSimpleType(*part) = int(vt.getSimpleType());
291 vertexDecayID(*part) = counter;
292 // The number of neutral and charged decay products at truth level -> excludes neutrinos
293 vertexDecayNCharged(*part) = fat_vertex.getNumChargedDecays(m_truthParticleMinimumPt);
294 vertexDecayNNeutral(*part) = fat_vertex.getNumNeutralDecays(m_truthParticleMinimumPt);
295 vertexDecayAllNCharged(*part) = fat_vertex.getNumChargedDecays();
296 vertexDecayAllNNeutral(*part) = fat_vertex.getNumNeutralDecays();
297 // Any inpart of a created fat vertex is a vertex
298 vertexIsVertex(*part) = 1;
299 barcode_to_other_id[m_acc_uid(*part)] = counter;
300 }
301
302 for ( auto part : fat_vertex.outparts ) {
303 if (!part){
304 ATH_MSG_ERROR("Found a null particle in the out parts of a fat vertex, skipping it - this should never happen");
305 continue;
306 }
307
308 vertexDecayParentID(*part) = counter;
309 barcode_to_vertex_id[m_acc_uid(*part)] = counter;
310
311 // Outparts don't inherit their parent's vertex type — they're
312 // truth particles, not vertices. Their parent is recorded in
313 // vertexDecayParentID. If an outpart decays, it becomes an inpart
314 // of its own vertex and gets its type there.
315
316 // If this particle has a decay vertex, then we label its PV-Lxy
317 if ( part->decayVtx() && FatVertex::has_valid_child(part)) {
318 float pvDist = FatVertex::vertex_distance(part->decayVtx(), pv);
319 vertexDecayPVDistance(*part) = pvDist;
320 // Production→decay flight distance
321 if ( part->hasProdVtx() && part->prodVtx() ) {
322 vertexDecayDistance(*part) = FatVertex::vertex_distance(part->prodVtx(), part->decayVtx());
323 }
324
325 // If it doesn't, we add it to a a vector based on its charge for later use
326 }else{
327 // If we always keep charged/neutrals, then we do so here
328 if (part->isCharged()){
329 stable_charged.push_back(part);
331 vertexValid(*part) = 1;
332 }
333 }
334 else{
335 stable_neutrals.push_back(part);
337 vertexValid(*part) = 1;
338 }
339 }
340 }
341 }
342
343 counter++;
344 }
345
346 // Get all tracks so we can decorate them
348 ATH_CHECK(tracks.isValid());
349 std::vector<const xAOD::TrackParticle*> tracks_vector(tracks->begin(), tracks->end());
350
352
354 TrackIntWriter trackTPDecayVertexID(m_trackTPDecayVertexID, ctx);
355 TrackIntWriter trackTPDecayVertexType(m_trackTPDecayVertexType, ctx);
356 TrackIntWriter trackTPDecaySimpleVertexType(m_trackTPDecaySimpleVertexType, ctx);
357 TrackIntWriter trackPDGID(m_trackPDGID, ctx);
358 TrackIntWriter trackParentPDGID(m_trackParentPDGID, ctx);
359
360 for ( auto track : tracks_vector ) {
361 // Default: unclassified. PV tracks get type 0 via barcode lookup.
362 // Fakes = -3, pileup = -2, unclassified = -1.
363 trackTPDecayVertexID(*track) = -1;
364 trackTPDecayVertexType(*track) = -1;
365 trackTPDecaySimpleVertexType(*track) = -1;
366 trackPDGID(*track) = 0;
367 trackParentPDGID(*track) = 0;
368 const auto truth = m_trackTruthOriginTool->getTruth(track);
369 // No truth match → pileup
370 if ( !truth ){
371 trackTPDecayVertexType(*track) = -2;
372 trackTPDecaySimpleVertexType(*track) = -2;
373 continue;
374 }
375 float truthProb = m_truthMatchProbabilityAcc(*track);
376
377 if(truthProb < m_truthMatchProbabilityCut){
378 // Fake track
379 trackTPDecayVertexType(*track) = -3;
380 trackTPDecaySimpleVertexType(*track) = -3;
381 continue;
382 }
383 // If we aren't *always* keeping stable charged TP, but we're keeping them whenever
384 // there's no associated track, then here we remove anything matched from out
385 // stable charged particle vector, so we know to only write those that remain
387 stable_charged.erase(
388 std::remove(stable_charged.begin(), stable_charged.end(), truth),
389 stable_charged.end()
390 );
391 }
392 trackPDGID(*track) = truth->pdgId();
393
394 if ( truth->hasProdVtx() && truth->prodVtx() ) {
395 auto parent = truth->prodVtx()->incomingParticle(0);
396 if ( parent ) {
397 trackParentPDGID(*track) = parent->pdgId();
398 }
399 }
400
401 // Get vertex ID from barcode
402 if ( barcode_to_vertex_id.find(m_acc_uid(*truth)) != barcode_to_vertex_id.end() ) {
403 int vertex_id = barcode_to_vertex_id[m_acc_uid(*truth)];
404 trackTPDecayVertexID(*track) = vertex_id;
405 FatVertex::VertexType vt = hpt_fat_vertices[vertex_id].getType(m_acc_uid);
406 trackTPDecayVertexType(*track) = int(vt.detailedType);
407 trackTPDecaySimpleVertexType(*track) = int(vt.getSimpleType());
408 } else {
409 // Lets go for a different default value for debug purposes
410 if(barcode_to_other_id.find(m_acc_uid(*truth)) != barcode_to_other_id.end()){
411 ATH_MSG_DEBUG("Found a track which is truth matched to an internal particle, not an out part ");
412 int vertex_id = barcode_to_other_id[m_acc_uid(*truth)];
413 trackTPDecayVertexID(*track) = vertex_id;
414 FatVertex::VertexType vt = hpt_fat_vertices[vertex_id].getType(m_acc_uid);
415 trackTPDecayVertexType(*track) = int(vt.detailedType);
416 trackTPDecaySimpleVertexType(*track) = int(vt.getSimpleType());
417 }else{
418 // Truth-matched track not found in any fat vertex — default is PV (already set above).
419 // This can happen for tracks from ISR/fragmentation or from truth particles whose
420 // decay chain wasn't fully traversed.
421 }
422
423 }
424 }
425 // Now we set all charged stable TPs without an associated track valid so we can save them
427 for(auto stable_charged_tp : stable_charged){
428 vertexValid(*stable_charged_tp) = 1;
429 }
430
431 }
432
433 // TODO match ID hits to TP if they exist
434 // TODO match flows to TP if possible (this is a bit more difficult)
435 return StatusCode::SUCCESS;
436}
437
438} // End of ParticleJetTools namespace
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_ERROR(x)
#define ATH_MSG_INFO(x)
#define ATH_MSG_DEBUG(x)
Handle class for reading a decoration on an object.
Handle class for adding a decoration to an object.
An algorithm that can be simultaneously executed in multiple threads.
SG::WriteDecorHandleKey< xAOD::TruthParticleContainer > m_vertexDecayType
SG::WriteDecorHandleKey< xAOD::TrackParticleContainer > m_trackTPDecayVertexType
SG::WriteDecorHandleKey< xAOD::TruthParticleContainer > m_vertexDecayDistance
TruthVertexDecoratorAlg(const std::string &name, ISvcLocator *pSvcLocator)
SG::WriteDecorHandleKey< xAOD::TruthParticleContainer > m_vertexDecayNCharged
SG::WriteDecorHandleKey< xAOD::TruthParticleContainer > m_vertexDecayPVDistance
SG::ReadDecorHandleKey< xAOD::TruthParticleContainer > m_acc_truth_particle_vertex_id
virtual StatusCode execute(const EventContext &) const override
SG::WriteDecorHandleKey< xAOD::TruthParticleContainer > m_vertexValid
SG::WriteDecorHandleKey< xAOD::TruthParticleContainer > m_vertexDecayNNeutral
SG::WriteDecorHandleKey< xAOD::TrackParticleContainer > m_trackTPDecayVertexID
SG::ReadHandleKey< xAOD::TruthParticleContainer > m_TruthContainerKey
SG::ReadHandleKey< xAOD::TruthVertexContainer > m_TruthVertexContainerKey
Gaudi::Property< std::string > m_truthMatchProbabilityAuxName
SG::ReadHandleKey< xAOD::TrackParticleContainer > m_TrackContainerKey
ToolHandle< InDet::InDetTrackTruthOriginTool > m_trackTruthOriginTool
SG::WriteDecorHandleKey< xAOD::TruthParticleContainer > m_vertexDecayID
SG::AuxElement::ConstAccessor< float > m_truthMatchProbabilityAcc
SG::WriteDecorHandleKey< xAOD::TruthParticleContainer > m_vertexDecayParentID
SG::ReadHandleKey< xAOD::TruthVertexContainer > m_TruthPVsKey
SG::WriteDecorHandleKey< xAOD::TruthParticleContainer > m_vertexDecayAllNCharged
SG::WriteDecorHandleKey< xAOD::TrackParticleContainer > m_trackPDGID
SG::WriteDecorHandleKey< xAOD::TruthParticleContainer > m_vertexDecayAllNNeutral
SG::WriteDecorHandleKey< xAOD::TruthParticleContainer > m_vertexSimpleDecayType
SG::WriteDecorHandleKey< xAOD::TruthParticleContainer > m_vertexIsVertex
SG::WriteDecorHandleKey< xAOD::TrackParticleContainer > m_trackTPDecaySimpleVertexType
SG::WriteDecorHandleKey< xAOD::TrackParticleContainer > m_trackParentPDGID
Helper class to provide constant type-safe access to aux data.
Handle class for reading a decoration on an object.
virtual bool isValid() override final
Can the handle be successfully dereferenced?
Handle class for adding a decoration to an object.
Class providing the definition of the 4-vector interface.
const TruthVertex_v1 * decayVtx() const
The decay vertex of this particle.
bool hasDecayVtx() const
Check for a decay vertex on this particle.
int r
Definition globals.cxx:22
bool has_valid_child(const xAOD::TruthParticle *part)
Definition FatVertex.cxx:33
float vertex_distance(const xAOD::TruthVertex *v1, const xAOD::TruthVertex *v2)
Definition FatVertex.cxx:53
std::vector< FatVertex > generateFatVertices(const xAOD::TruthVertex *pv, const float truthVertexMergeDistance, const std::vector< const xAOD::TruthParticle * > &truth_particles)
std::vector< const xAOD::TruthVertex * > get_matched_primary_vertices(const xAOD::TruthVertex *pv, const std::vector< const xAOD::TruthVertex * > &truth_vertices, float selection_distance=0.001, SG::ConstAccessor< int > acc_uid=SG::ConstAccessor< int >("barcode"))
Get the matched primary vertex for a given truth vertex.
const xAOD::TruthParticle * get_initial_particle(const xAOD::TruthParticle *part)
std::vector< const xAOD::TruthParticle * > get_all_initial_particles(const std::vector< const xAOD::TruthParticle * > &truth_particles)
DataModel_detail::iterator< DVL > remove(typename DataModel_detail::iterator< DVL > beg, typename DataModel_detail::iterator< DVL > end, const T &value)
Specialization of remove for DataVector/List.
void sort(typename DataModel_detail::iterator< DVL > beg, typename DataModel_detail::iterator< DVL > end)
Specialization of sort for DataVector/List.
TruthVertex_v1 TruthVertex
Typedef to implementation.
Definition TruthVertex.h:15
TruthParticle_v1 TruthParticle
Typedef to implementation.