ATLAS Offline Software
Loading...
Searching...
No Matches
HyPERTopoReco.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3*/
4#include <AsgMessaging/MessageCheck.h> // To access ANA_MSG
5
6
9
10using indices = std::vector<std::vector<int64_t>>;
11using scores = std::vector<std::vector<float>>;
12
13namespace EventReco {
14bool anyCommonElement(const std::vector<int64_t>& vec1,
15 const std::vector<int64_t>& vec2) {
16 for (const auto& v1 : vec1) {
17 for (const auto& v2 : vec2) {
18 if (v1 == v2)
19 return true;
20 }
21 }
22 return false;
23}
24
26 std::vector<int64_t>& decay_indices,
27 const std::vector<std::vector<int64_t>>& allowed_decay_modes) {
28 // Search for the decay mode in the allowed decay modes
29 // Create a sorted copy of the decay indices and the decay mode
30 std::vector<int64_t> decay_indices_sorted = decay_indices;
31 std::sort(decay_indices_sorted.begin(), decay_indices_sorted.end());
32 for (const auto& decay_mode : allowed_decay_modes) {
33 std::vector<int64_t> decay_mode_sorted = decay_mode;
34 std::sort(decay_mode_sorted.begin(), decay_mode_sorted.end());
35 // Check if the decay mode is compatible with the allowed decay mode
36 if (decay_indices_sorted == decay_mode_sorted)
37 return true;
38 }
39 return false;
40}
41
42bool isHadTop(const std::vector<int64_t>& decay_indices) {
43 // Check if the decay mode is compatible with a hadronic top quark
44 if (decay_indices.size() != 3)
45 throw std::runtime_error(
46 "Number of decay particles is not consistent with a hadronic top "
47 "decay!");
48 for (const auto& decay_index : decay_indices) {
49 if (decay_index != EventReco::HyPERParticleID::jet)
50 return false;
51 }
52 return true;
53}
54
55std::vector<int64_t> findWInTopLep(const std::vector<int64_t>& decay_indices,
56 const std::vector<int64_t>& decay_ids) {
57 std::vector<int64_t> w_candidate = {};
58 if (decay_indices.size() != decay_ids.size())
59 throw std::runtime_error(
60 "Number of decay particles and IDs are not consistent!");
61 if (decay_indices.size() != 3)
62 throw std::runtime_error(
63 "Number of decay particles is not consistent with a leptonic top "
64 "decay!");
65
66 for (std::size_t i = 0; i < decay_indices.size(); i++) {
67 if (decay_ids.at(i) == EventReco::HyPERParticleID::jet)
68 continue;
69 w_candidate.push_back(decay_indices.at(i));
70 }
71
72 if (w_candidate.size() != 2)
73 throw std::runtime_error(
74 "Number of W boson decay particles is not consistent with a leptonic "
75 "top decay!");
76 return w_candidate;
77}
78
79int countParticlesSameType(const HyPERGraph& hyperGraph,
81 int count = 0;
82 for (std::size_t i = 0; i < static_cast<std::size_t>(hyperGraph.nNodes());
83 i++) {
84 // Particle ID should be at the position before the last.
85 std::size_t beforeLastPosition = hyperGraph.getNodeFeats(i).size() - 2;
86 if (hyperGraph.getNodeFeats(i).at(beforeLastPosition) ==
87 static_cast<float>(particleType))
88 count++;
89 }
90
91 return count;
92}
93
94// This overload is for the DiLepton topology where the IDs are scaled by 2 in
95// the input features. See
96int countParticlesSameType(const HyPERGraph& hyperGraph, float particleType) {
97 int count = 0;
98 for (std::size_t i = 0; i < static_cast<std::size_t>(hyperGraph.nNodes());
99 i++) {
100 // Particle ID should be at the position before the last.
101 std::size_t beforeLastPosition = hyperGraph.getNodeFeats(i).size() - 2;
102 if (hyperGraph.getNodeFeats(i).at(beforeLastPosition) ==
103 static_cast<float>(particleType))
104 count++;
105 }
106
107 return count;
108}
109
115 std::vector<int64_t>& top_indices,
116 std::vector<int64_t>& w_indices,
117 const std::vector<int64_t>& ids) {
119 int nElectrons = countParticlesSameType(hyperGraph, EventReco::HyPERParticleID::e);
120 // int nMuons = countParticlesSameType(hyperGraph, EventReco::HyPERParticleID::mu);
121 // // When tau is supported...
123
124 // Sanity check
125 if (nMETs != 1)
126 throw std::runtime_error("Number of METs is not consistent!");
127
128 // Reescale the indices
129 for (std::size_t i = 0; i < top_indices.size(); i++) {
130 if (ids.at(i) ==
131 EventReco::HyPERParticleID::jet) { // For jets do nothing since they are
132 // filled first and then their positions
133 // are the same as in the original
134 // container.
135 continue;
136 } else if (ids.at(i) == EventReco::HyPERParticleID::e) {
137 top_indices.at(i) -= nJets;
138 w_indices.at(0) = top_indices.at(
139 i); // Always put the lepton as the first in the w_indices.
140 } else if (ids.at(i) == EventReco::HyPERParticleID::mu) {
141 top_indices.at(i) -= (nJets + nElectrons);
142 w_indices.at(0) = top_indices.at(
143 i); // Always put the lepton as the first in the w_indices.
144 } else if (ids.at(i) == EventReco::HyPERParticleID::met) {
145 top_indices.at(i) =
146 0; // Just assign 0 here since MET is always the first particle.
147 w_indices.at(1) =
148 0; // always put the MET as the second in the w_indices.
149 }
150 }
151}
152
154 std::vector<int64_t>& top_indices,
155 const std::vector<int64_t>& ids) {
156 // Divide by 2 because of the feature scaling applied to the IDs in
157 // RunHyPERAlg::buildTtbarDiLeptonGraph. See the comments there for more
158 // details.
159 int nJets =
161 int nElectrons =
163 // int nMuons = countParticlesSameType(hyperGraph,
164 // EventReco::HyPERParticleID::mu/2.0f);
165 // // When tau is supported...
166
167 // Sanity check. In di-lepton there should be no METs counted as particles.
168 int nMETs =
170 if (nMETs != 1)
171 throw std::runtime_error("Number of METs is not consistent!");
172
173 // Reescale the indices
174 for (std::size_t i = 0; i < top_indices.size(); i++) {
175 if (ids.at(i) ==
176 EventReco::HyPERParticleID::jet) { // For jets do nothing since they are
177 // filled first and then their positions
178 // are the same as in the original
179 // container.
180 continue;
181 } else if (ids.at(i) == EventReco::HyPERParticleID::e) {
182 top_indices.at(i) -= nJets;
183 } else if (ids.at(i) == EventReco::HyPERParticleID::mu) {
184 top_indices.at(i) -= (nJets + nElectrons);
185 } // When tau is supported...
186 }
187}
188
195 const HyPERGraph& hyperGraph, const scores& edge_scores,
196 const scores&
197 hyperedge_scores, // This is the hyperedge output from the model.
198 std::vector<int64_t>&
199 hyperedge_masks, // This is a vector of 0s and 1s, where 0 means the
200 // hyperedge is already in use.
201 std::vector<int64_t>& top_reco_indices, // This we want to fill with the
202 // indices of the top quark.
203 std::vector<int64_t>& w_reco_indices, // This we want to fill with the
204 // indices of the W boson.
205 float& top_reco_score, // This we want to fill with the score of the top
206 // quark.
207 float&
208 w_reco_score) { // This we want to fill with the score of the W boson.
209
210 if (hyperGraph.nHyperEdges() != static_cast<int>(hyperedge_scores.size()))
211 throw std::runtime_error(
212 "Number of defined hyperedges does not match with the ones from model "
213 "output!");
214 if (hyperGraph.nEdges() != static_cast<int>(edge_scores.size()))
215 throw std::runtime_error(
216 "Number of defined graph edges does not match with the ones from model "
217 "output!");
218
219 for (std::size_t i = 0; i < hyperedge_scores.size(); i++) {
220 if (hyperedge_masks.at(i) == 0)
221 continue; // Skip the hyperedge if it is masked
222
223 if (hyperedge_scores.at(i).at(0) > top_reco_score) {
224 top_reco_score = hyperedge_scores.at(i).at(0);
225 top_reco_indices = {hyperGraph.getHyperEdgeIndices(i).at(0),
226 hyperGraph.getHyperEdgeIndices(i).at(1),
227 hyperGraph.getHyperEdgeIndices(i).at(2)};
228 w_reco_score = 0.0; // Reset the Ws if a new best top is found.
229 w_reco_indices = {-1, -1};
230
231 std::vector<std::vector<int64_t>> w_candidates =
232 buildCombinations<int64_t>(top_reco_indices, 2);
233 for (std::size_t j = 0; j < w_candidates.size(); j++) {
234 // There are two directional edges connecting each pair of nodes
235 float bi_directional_sum = 0;
236 for (std::size_t k = 0; k < edge_scores.size(); k++) {
237 if (w_candidates.at(j).at(0) ==
238 hyperGraph.getEdgeIndicesVector().at(k).first &&
239 w_candidates.at(j).at(1) ==
240 hyperGraph.getEdgeIndicesVector().at(k).second) {
241 bi_directional_sum += edge_scores.at(k).at(0);
242 }
243 if (w_candidates.at(j).at(1) ==
244 hyperGraph.getEdgeIndicesVector().at(k).first &&
245 w_candidates.at(j).at(0) ==
246 hyperGraph.getEdgeIndicesVector().at(k).second) {
247 bi_directional_sum += edge_scores.at(k).at(0);
248 }
249 }
250 if (bi_directional_sum / 2 > w_reco_score) {
251 w_reco_score = bi_directional_sum / 2;
252 w_reco_indices = w_candidates.at(j);
253 }
254 }
255 }
256 }
257}
258
265 const HyPERGraph& hyperGraph, const scores& edge_scores,
266 const scores&
267 hyperedge_scores, // This is the hyperedge output from the model.
268 std::vector<int64_t>&
269 hyperedge_masks, // This is a vector of 0s and 1s, where 0 means the
270 // hyperedge is already in use.
271 std::vector<int64_t>&
272 top_reco_indices, // This we want to fill with the indices of particles
273 // in the top quark.
274 std::vector<int64_t>&
275 w_reco_indices, // This we want to fill with the indices of particles
276 // in the W boson.
277 float& top_reco_score, // This we want to fill with the score of the top
278 // quark.
279 float& w_reco_score,
280 std::vector<int64_t>& top_reco_ids, // This will hold the particle IDs of
281 // the top quark decays.
282 const std::vector<std::vector<int64_t>>&
283 allowed_decay_modes) { // This is the list of decay modes we want to
284 // consider.
285 if (hyperGraph.nHyperEdges() != static_cast<int>(hyperedge_scores.size()))
286 throw std::runtime_error(
287 "Number of defined hyperedges does not match with the ones from model "
288 "output!");
289 if (hyperGraph.nEdges() != static_cast<int>(edge_scores.size()))
290 throw std::runtime_error(
291 "Number of defined graph edges does not match with the ones from model "
292 "output!");
293 if (allowed_decay_modes.empty())
294 throw std::runtime_error("No allowed decay modes available!");
295
296 for (std::size_t i = 0; i < hyperedge_scores.size(); i++) {
297 if (hyperedge_masks.at(i) == 0)
298 continue; // Skip the hyperedge if it is masked
299 if (hyperedge_scores.at(i).at(0) > top_reco_score) {
300
301 std::vector<int64_t> top_candidate_indices = {
302 hyperGraph.getHyperEdgeIndices(i).at(0),
303 hyperGraph.getHyperEdgeIndices(i).at(1),
304 hyperGraph.getHyperEdgeIndices(i).at(2)};
305 // Get particle IDs. The conversions here are float -> HyPERParticleID
306 // (int) -> int64_t
307 // The ID should be the element before the last in the node features. See
308 // RunHyPERAlg::buildTtbarLJetsGraph
309 std::size_t beforeLastPosition = hyperGraph.getNodeFeats(0).size() - 2;
311 hyperGraph.getNodeFeats(top_candidate_indices.at(0))
312 .at(beforeLastPosition));
314 hyperGraph.getNodeFeats(top_candidate_indices.at(1))
315 .at(beforeLastPosition));
317 hyperGraph.getNodeFeats(top_candidate_indices.at(2))
318 .at(beforeLastPosition));
319 std::vector<int64_t> top_candidate_ids = {static_cast<int64_t>(id_1),
320 static_cast<int64_t>(id_2),
321 static_cast<int64_t>(id_3)};
322 if (!isAllowedDecay(top_candidate_ids, allowed_decay_modes))
323 continue;
324
325 top_reco_score = hyperedge_scores.at(i).at(0);
326 top_reco_ids = top_candidate_ids;
327 top_reco_indices = top_candidate_indices;
328 w_reco_score = 0.0; // Reset the Ws if a new best top is found.
329 w_reco_indices = {-1, -1};
330
331 if (isHadTop(top_reco_ids)) { // Do the same as the had top reco
332 std::vector<std::vector<int64_t>> w_candidates =
333 buildCombinations<int64_t>(top_reco_indices, 2);
334 for (std::size_t j = 0; j < w_candidates.size(); j++) {
335 // There are two directional edges connecting each pair of nodes
336 float bi_directional_sum = 0;
337 for (std::size_t k = 0; k < edge_scores.size(); k++) {
338 if (w_candidates.at(j).at(0) ==
339 hyperGraph.getEdgeIndicesVector().at(k).first &&
340 w_candidates.at(j).at(1) ==
341 hyperGraph.getEdgeIndicesVector().at(k).second) {
342 bi_directional_sum += edge_scores.at(k).at(0);
343 }
344 if (w_candidates.at(j).at(1) ==
345 hyperGraph.getEdgeIndicesVector().at(k).first &&
346 w_candidates.at(j).at(0) ==
347 hyperGraph.getEdgeIndicesVector().at(k).second) {
348 bi_directional_sum += edge_scores.at(k).at(0);
349 }
350 }
351 if (bi_directional_sum / 2 > w_reco_score) {
352 w_reco_score = bi_directional_sum / 2;
353 w_reco_indices = w_candidates.at(j);
354 }
355 }
356 } else { // Just look for the hadronic index and the rest is the W boson
357 w_reco_indices = findWInTopLep(top_reco_indices, top_reco_ids);
358 float bi_directional_sum = 0;
359 for (std::size_t k = 0; k < edge_scores.size(); k++) {
360 if (w_reco_indices.at(0) ==
361 hyperGraph.getEdgeIndicesVector().at(k).first &&
362 w_reco_indices.at(1) ==
363 hyperGraph.getEdgeIndicesVector().at(k).second) {
364 bi_directional_sum += edge_scores.at(k).at(0);
365 }
366 if (w_reco_indices.at(0) ==
367 hyperGraph.getEdgeIndicesVector().at(k).second &&
368 w_reco_indices.at(1) ==
369 hyperGraph.getEdgeIndicesVector().at(k).first) {
370 bi_directional_sum += edge_scores.at(k).at(0);
371 }
372 }
373 w_reco_score = bi_directional_sum / 2;
374 }
375 }
376 }
377}
378
382void RecoTtbarAllHadronic(const HyPERGraph& hyperGraph,
383 const scores& edge_scores,
384 const scores& hyperedge_scores, indices& reco_indices,
385 std::vector<float>& reco_scores,
386 std::vector<std::string>& reco_labels) {
387
388 std::vector<int64_t> top1_reco_indices(3, -1);
389 std::vector<int64_t> top2_reco_indices(3, -1);
390 std::vector<int64_t> w1_reco_indices(2, -1);
391 std::vector<int64_t> w2_reco_indices(2, -1);
392
393 float top1_reco_score = 0;
394 float top2_reco_score = 0;
395 float w1_reco_score = 0;
396 float w2_reco_score = 0;
397
398 // Reconstructing top1
399 std::vector<int64_t> top1_masks(hyperedge_scores.size(), 1);
400 RecoHadronicTop(hyperGraph, edge_scores, hyperedge_scores, top1_masks,
401 top1_reco_indices, w1_reco_indices, top1_reco_score,
402 w1_reco_score);
403
404 // Check if reco top1 is successful
405 if (top1_reco_indices == std::vector<int64_t>(3, -1)) {
406 reco_labels.push_back("HyPER_Reco_Top1");
407 reco_indices.push_back(top1_reco_indices);
408 reco_scores.push_back(top1_reco_score);
409 reco_labels.push_back("HyPER_Reco_Top2");
410 reco_indices.push_back(top2_reco_indices);
411 reco_scores.push_back(top2_reco_score);
412 reco_labels.push_back("HyPER_Reco_W1");
413 reco_indices.push_back(w1_reco_indices);
414 reco_scores.push_back(w1_reco_score);
415 reco_labels.push_back("HyPER_Reco_W2");
416 reco_indices.push_back(w2_reco_indices);
417 reco_scores.push_back(w2_reco_score);
418 return; // No point in continuing if top1 is not reconstructed.
419 }
420
421 // Masking out the hyperedges containing nodes in top1
422 std::vector<int64_t> top2_masks;
423 for (std::size_t i = 0;
424 i < static_cast<std::size_t>(hyperGraph.nHyperEdges()); i++) {
425 if (anyCommonElement(top1_reco_indices, hyperGraph.getHyperEdgeIndices(i)))
426 top2_masks.push_back(0);
427 else
428 top2_masks.push_back(1);
429 }
430
431 // Reconstructing top2
432 RecoHadronicTop(hyperGraph, edge_scores, hyperedge_scores, top2_masks,
433 top2_reco_indices, w2_reco_indices, top2_reco_score,
434 w2_reco_score);
435
436 // Save results
437 reco_labels.push_back("HyPER_Reco_Top1");
438 reco_indices.push_back(top1_reco_indices);
439 reco_scores.push_back(top1_reco_score);
440 reco_labels.push_back("HyPER_Reco_Top2");
441 reco_indices.push_back(top2_reco_indices);
442 reco_scores.push_back(top2_reco_score);
443 reco_labels.push_back("HyPER_Reco_W1");
444 reco_indices.push_back(w1_reco_indices);
445 reco_scores.push_back(w1_reco_score);
446 reco_labels.push_back("HyPER_Reco_W2");
447 reco_indices.push_back(w2_reco_indices);
448 reco_scores.push_back(w2_reco_score);
449}
450
454void RecoTtbarLJets(const HyPERGraph& hyperGraph, const scores& edge_scores,
455 const scores& hyperedge_scores,
456 const scores& classification_score, indices& reco_indices,
457 std::vector<float>& reco_scores,
458 std::vector<std::string>& reco_labels,
459 std::vector<std::vector<int64_t>>& reco_ids,
460 float& reco_classification_score) {
461 using namespace asg::msgUserCode;
462
463 std::vector<int64_t> top1_reco_indices(3, -1);
464 std::vector<int64_t> top2_reco_indices(3, -1);
465 std::vector<int64_t> w1_reco_indices(2, -1);
466 std::vector<int64_t> w2_reco_indices(2, -1);
467
468 float top1_reco_score = 0;
469 float top2_reco_score = 0;
470 float w1_reco_score = 0;
471 float w2_reco_score = 0;
472
473 std::vector<int64_t> top1_reco_ids(3, -1);
474 std::vector<int64_t> top2_reco_ids(3, -1);
475
476 if (EventReco::g_hyper_msg_level == MSG::VERBOSE) {
477 setMsgLevel(MSG::INFO);
478 ANA_MSG_INFO("Reconstructing TtbarSingleLepton top quarks...");
479 ANA_MSG_INFO("Working with these HE scores:");
480 for (const auto& he_score : hyperedge_scores) {
481 ANA_MSG_INFO("Element " << he_score.at(0));
482 }
483 ANA_MSG_INFO("Working with these edge scores:");
484 for (const auto& e_score : edge_scores) {
485 ANA_MSG_INFO("Element " << e_score.at(0));
486 }
487 ANA_MSG_INFO("Working with this classification score:");
488 ANA_MSG_INFO(classification_score.at(0).at(0));
489 }
490
491 // Save the classification score
492 reco_classification_score = classification_score.at(0).at(0);
493
494 // Allowed decays
495 std::vector<std::vector<int64_t>> allowed_decays = {
502
503 // Reconstructing top1
504 std::vector<int64_t> top1_masks(hyperedge_scores.size(), 1);
505 RecoTop(hyperGraph, edge_scores, hyperedge_scores, top1_masks,
506 top1_reco_indices, w1_reco_indices, top1_reco_score, w1_reco_score,
507 top1_reco_ids, allowed_decays);
508 if (EventReco::g_hyper_msg_level == MSG::VERBOSE) {
509 ANA_MSG_INFO("Top1 reco indices: " << top1_reco_indices.at(0) << " "
510 << top1_reco_indices.at(1) << " "
511 << top1_reco_indices.at(2));
512 ANA_MSG_INFO("Top1 reco score: " << top1_reco_score);
513 ANA_MSG_INFO("W1 reco indices: " << w1_reco_indices.at(0) << " "
514 << w1_reco_indices.at(1));
515 ANA_MSG_INFO("W1 reco score: " << w1_reco_score);
516 }
517
518 // Check if reco top1 is successful
519 if (top1_reco_indices == std::vector<int64_t>(3, -1)) {
520 // If not, return the default values for both tops.
521 reco_labels.push_back("HyPER_Reco_TopHad");
522 reco_indices.push_back(top1_reco_indices);
523 reco_scores.push_back(top1_reco_score);
524 reco_ids.push_back(top1_reco_ids);
525 reco_labels.push_back("HyPER_Reco_TopLep");
526 reco_indices.push_back(top2_reco_indices);
527 reco_scores.push_back(top2_reco_score);
528 reco_ids.push_back(top2_reco_ids);
529 reco_labels.push_back("HyPER_Reco_WHad");
530 reco_indices.push_back(w1_reco_indices);
531 reco_scores.push_back(w1_reco_score);
532 reco_labels.push_back("HyPER_Reco_WLep");
533 reco_indices.push_back(w2_reco_indices);
534 reco_scores.push_back(w2_reco_score);
535 return;
536 }
537
538 // Masking out the hyperedges containing nodes in top1
539 std::vector<int64_t> top2_masks;
540 for (std::size_t i = 0;
541 i < static_cast<std::size_t>(hyperGraph.nHyperEdges()); i++) {
542 if (anyCommonElement(top1_reco_indices, hyperGraph.getHyperEdgeIndices(i)))
543 top2_masks.push_back(0);
544 else
545 top2_masks.push_back(1);
546 }
547
548 // If the top1 is hadronic, redefine the allowed decays and conversely
549 bool top1_is_hadronic = false;
550 if (isHadTop(top1_reco_ids)) {
551 if (EventReco::g_hyper_msg_level == MSG::VERBOSE)
552 ANA_MSG_INFO("Top1 is hadronic!");
553 top1_is_hadronic = true;
558 } else {
559 if (EventReco::g_hyper_msg_level == MSG::VERBOSE)
560 ANA_MSG_INFO("Top1 is Leptonic!");
563 }
564
565 // Reconstructing top2
566 RecoTop(hyperGraph, edge_scores, hyperedge_scores, top2_masks,
567 top2_reco_indices, w2_reco_indices, top2_reco_score, w2_reco_score,
568 top2_reco_ids, allowed_decays);
569 if (EventReco::g_hyper_msg_level == MSG::VERBOSE) {
570 ANA_MSG_INFO("Top2 reco indices: " << top2_reco_indices.at(0) << " "
571 << top2_reco_indices.at(1) << " "
572 << top2_reco_indices.at(2));
573 ANA_MSG_INFO("Top2 reco score: " << top2_reco_score);
574 ANA_MSG_INFO("W2 reco indices: " << w2_reco_indices.at(0) << " "
575 << w2_reco_indices.at(1));
576 ANA_MSG_INFO("W2 reco score: " << w2_reco_score);
577 }
578
579 // Check if reco top2 is successful
580 bool both_tops_reco = true;
581 if (top2_reco_indices == std::vector<int64_t>(3, -1))
582 both_tops_reco = false;
583
584 // Finally, we need to shift the indices in the leptonic top decay to reflect
585 // the position in the particle-specific containers.
586 if (top1_is_hadronic && both_tops_reco) {
587 reescaleIndicesTtbarLJets(hyperGraph, top2_reco_indices, w2_reco_indices,
588 top2_reco_ids);
589 }
590 if (!top1_is_hadronic) {
591 reescaleIndicesTtbarLJets(hyperGraph, top1_reco_indices, w1_reco_indices,
592 top1_reco_ids);
593 }
594
595 if (EventReco::g_hyper_msg_level == MSG::VERBOSE) {
596 ANA_MSG_INFO("Final reco indices:");
597 ANA_MSG_INFO("Top1 reco indices: " << top1_reco_indices.at(0) << " "
598 << top1_reco_indices.at(1) << " "
599 << top1_reco_indices.at(2));
600 ANA_MSG_INFO("Top2 reco indices: " << top2_reco_indices.at(0) << " "
601 << top2_reco_indices.at(1) << " "
602 << top2_reco_indices.at(2));
603 ANA_MSG_INFO("W1 reco indices: " << w1_reco_indices.at(0) << " "
604 << w1_reco_indices.at(1));
605 ANA_MSG_INFO("W2 reco indices: " << w2_reco_indices.at(0) << " "
606 << w2_reco_indices.at(1));
607 ANA_MSG_INFO("Final reco scores:");
608 ANA_MSG_INFO("Top1 reco score: " << top1_reco_score);
609 ANA_MSG_INFO("Top2 reco score: " << top2_reco_score);
610 ANA_MSG_INFO("W1 reco score: " << w1_reco_score);
611 ANA_MSG_INFO("W2 reco score: " << w2_reco_score);
612 ANA_MSG_INFO("Final reco IDs:");
613 ANA_MSG_INFO("Top1 reco IDs: " << top1_reco_ids.at(0) << " "
614 << top1_reco_ids.at(1) << " "
615 << top1_reco_ids.at(2));
616 ANA_MSG_INFO("Top2 reco IDs: " << top2_reco_ids.at(0) << " "
617 << top2_reco_ids.at(1) << " "
618 << top2_reco_ids.at(2));
619 }
620
621 // Save results
622 if (top1_is_hadronic) {
623 reco_labels.push_back("HyPER_Reco_TopHad");
624 reco_indices.push_back(top1_reco_indices);
625 reco_scores.push_back(top1_reco_score);
626 reco_ids.push_back(top1_reco_ids);
627 reco_labels.push_back("HyPER_Reco_TopLep");
628 reco_indices.push_back(top2_reco_indices);
629 reco_scores.push_back(top2_reco_score);
630 reco_ids.push_back(top2_reco_ids);
631 reco_labels.push_back("HyPER_Reco_WHad");
632 reco_indices.push_back(w1_reco_indices);
633 reco_scores.push_back(w1_reco_score);
634 reco_labels.push_back("HyPER_Reco_WLep");
635 reco_indices.push_back(w2_reco_indices);
636 reco_scores.push_back(w2_reco_score);
637 } else {
638 reco_labels.push_back("HyPER_Reco_TopHad");
639 reco_indices.push_back(top2_reco_indices);
640 reco_scores.push_back(top2_reco_score);
641 reco_ids.push_back(top2_reco_ids);
642 reco_labels.push_back("HyPER_Reco_TopLep");
643 reco_indices.push_back(top1_reco_indices);
644 reco_scores.push_back(top1_reco_score);
645 reco_ids.push_back(top1_reco_ids);
646 reco_labels.push_back("HyPER_Reco_WHad");
647 reco_indices.push_back(w2_reco_indices);
648 reco_scores.push_back(w2_reco_score);
649 reco_labels.push_back("HyPER_Reco_WLep");
650 reco_indices.push_back(w1_reco_indices);
651 reco_scores.push_back(w1_reco_score);
652 }
653}
654
661 const HyPERGraph& hyperGraph, const scores& edge_scores,
662 // const scores&
663 // hyperedge_scores, // This is not needed in this case.
664 std::vector<int64_t>& edge_masks, // This is a vector of 0s and 1s, where 0
665 // means the edge is already used.
666 std::vector<int64_t>& top_reco_indices, // This we want to fill with the
667 // indices of the top quark.
668 float& top_reco_score, // This we want to fill with the score of the top
669 // quark.
670 std::vector<int64_t>& top_reco_ids, // This will hold the particle IDs of
671 // the top quark decays.
672 const std::vector<std::vector<int64_t>>&
673 allowed_decay_modes) { // This is the list of decay modes we want to
674 // consider.
675
676 using namespace asg::msgUserCode;
677 // Sanity checks
678 if (hyperGraph.nEdges() != static_cast<int>(edge_scores.size()))
679 throw std::runtime_error(
680 "Number of defined graph edges does not match with the ones from model "
681 "output!");
682 if (allowed_decay_modes.empty())
683 throw std::runtime_error("No allowed decay modes available!");
684
685 for (std::size_t i = 0; i < edge_scores.size(); i++) {
686 if (edge_masks.at(i) == 0) {
687 if (EventReco::g_hyper_msg_level == MSG::VERBOSE)
688 ANA_MSG_INFO("Skipping due to masking.");
689 continue; // Skip the edge if it is masked
690 }
691
692 // Get particle IDs.
693 std::vector<int64_t> top_candidate_indices = {
694 hyperGraph.getEdgeIndicesVector().at(i).first,
695 hyperGraph.getEdgeIndicesVector().at(i).second};
696 // Get particle IDs. The conversions here are float -> HyPERParticleID
697 // (int) -> int64_t
698 // The ID should be the element before the last in the node features. We
699 // have to MULTIPLY by 2 because of the feature scaling. See
700 // RunHyPERAlg::buildTtbarDiLeptonGraph
701 std::size_t beforeLastPosition = hyperGraph.getNodeFeats(0).size() - 2;
703 2 * hyperGraph.getNodeFeats(top_candidate_indices.at(0))
704 .at(beforeLastPosition));
706 2 * hyperGraph.getNodeFeats(top_candidate_indices.at(1))
707 .at(beforeLastPosition));
708 std::vector<int64_t> top_candidate_ids = {static_cast<int64_t>(id_1),
709 static_cast<int64_t>(id_2)};
710
711 // Only allow allowed decay modes
712 if (!isAllowedDecay(top_candidate_ids, allowed_decay_modes)) {
713 if (EventReco::g_hyper_msg_level == MSG::VERBOSE)
714 ANA_MSG_INFO("Skipping due to not allowed decay.");
715 continue;
716 }
717
718 if (edge_scores.at(i).at(0) > top_reco_score) {
719 top_reco_score = edge_scores.at(i).at(0);
720 top_reco_indices = top_candidate_indices;
721 top_reco_ids = top_candidate_ids;
722 }
723 }
724}
725
729void RecoTtbarDiLepton(const HyPERGraph& hyperGraph, const scores& edge_scores,
730 const scores& hyperedge_scores,
731 const scores& classification_score,
732 indices& reco_indices, std::vector<float>& reco_scores,
733 std::vector<std::string>& reco_labels,
734 std::vector<std::vector<int64_t>>& reco_ids,
735 float& reco_classification_score) {
736 using namespace asg::msgUserCode;
737
738 std::vector<int64_t> top1_reco_indices(2, -1);
739 std::vector<int64_t> top2_reco_indices(2, -1);
740
741 float top1_reco_score = 0;
742 float top2_reco_score = 0;
743
744 std::vector<int64_t> top1_reco_ids(2, -1);
745 std::vector<int64_t> top2_reco_ids(2, -1);
746
747 if (EventReco::g_hyper_msg_level == MSG::VERBOSE) {
748 setMsgLevel(MSG::INFO);
749 ANA_MSG_INFO("Reconstructing TtbarDiLepton top quarks...");
750 ANA_MSG_INFO("Working with these HE scores:");
751 for (const auto& he_score : hyperedge_scores) {
752 ANA_MSG_INFO("Element " << he_score.at(0));
753 }
754 ANA_MSG_INFO("Working with these edge scores:");
755 for (const auto& e_score : edge_scores) {
756 ANA_MSG_INFO("Element " << e_score.at(0));
757 }
758 ANA_MSG_INFO("Working with this classification score:");
759 ANA_MSG_INFO(classification_score.at(0).at(0));
760 }
761
762 // Save the classification score
763 reco_classification_score = classification_score.at(0).at(0);
764
765 // Allowed decays
766 std::vector<std::vector<int64_t>> allowed_decays = {
769
770 // Reconstructing top1
771 std::vector<int64_t> top1_masks(edge_scores.size(), 1);
772 RecoTopForDiLepton(hyperGraph, edge_scores, top1_masks, top1_reco_indices,
773 top1_reco_score, top1_reco_ids, allowed_decays);
774 if (EventReco::g_hyper_msg_level == MSG::VERBOSE) {
775 ANA_MSG_INFO("Top1 reco score: " << top1_reco_score);
776 ANA_MSG_INFO("Top1 reco indices before scaling: "
777 << top1_reco_indices.at(0) << " " << top1_reco_indices.at(1));
778 ANA_MSG_INFO("Top1 reco IDs: " << top1_reco_ids.at(0) << " "
779 << top1_reco_ids.at(1));
780 }
781
782 // If top1 reco failed, return default values for both tops
783 if (top1_reco_indices == std::vector<int64_t>(2, -1)) {
784 ANA_MSG_INFO("Top1 reco failed.");
785 reco_labels.push_back("HyPER_Reco_Top1");
786 reco_indices.push_back(top1_reco_indices);
787 reco_scores.push_back(top1_reco_score);
788 reco_ids.push_back(top1_reco_ids);
789 reco_labels.push_back("HyPER_Reco_Top2");
790 reco_indices.push_back(top2_reco_indices);
791 reco_scores.push_back(top2_reco_score);
792 reco_ids.push_back(top2_reco_ids);
793 reco_labels.push_back("HyPER_Reco_HE");
794 reco_scores.push_back(-1.0);
795 return;
796 }
797
798 // Now, reconstruct top2
799 // Calculate top2 masks
800 std::vector<int64_t> top2_masks(edge_scores.size(), 1);
801 for (std::size_t i = 0; i < static_cast<std::size_t>(edge_scores.size());
802 i++) {
803 if (anyCommonElement(top1_reco_indices,
804 {hyperGraph.getEdgeIndicesVector().at(i).first,
805 hyperGraph.getEdgeIndicesVector().at(i).second}))
806 top2_masks.at(i) = 0;
807 }
808 // Now, reconstruct top2
809 RecoTopForDiLepton(hyperGraph, edge_scores, top2_masks, top2_reco_indices,
810 top2_reco_score, top2_reco_ids, allowed_decays);
811 if (EventReco::g_hyper_msg_level == MSG::VERBOSE) {
812 ANA_MSG_INFO("Top2 reco score: " << top2_reco_score);
813 ANA_MSG_INFO("Top2 reco indices before scaling: "
814 << top2_reco_indices.at(0) << " " << top2_reco_indices.at(1));
815 ANA_MSG_INFO("Top2 reco IDs: " << top2_reco_ids.at(0) << " "
816 << top2_reco_ids.at(1));
817 }
818
819 // Check if reco top2 is successful
820 bool both_tops_reco = true; // At this point, top1 is already reconstructed
821 if (top2_reco_indices == std::vector<int64_t>(2, -1))
822 both_tops_reco = false;
823
824 // Let's find the hyperedge score corresponding to the two tops if both tops
825 // are reconstructed.
826 float HE_reco_score = -1.0;
827 if (both_tops_reco) {
828 for (std::size_t i = 0;
829 i < static_cast<std::size_t>(hyperGraph.nHyperEdges()); i++) {
830 std::vector<int64_t> he_indices = hyperGraph.getHyperEdgeIndices(i);
831 // Check all top indices are in the HE indices.
832 bool top1_in_he =
833 std::find(he_indices.begin(), he_indices.end(),
834 top1_reco_indices.at(0)) != he_indices.end() &&
835 std::find(he_indices.begin(), he_indices.end(),
836 top1_reco_indices.at(1)) != he_indices.end();
837 bool top2_in_he =
838 std::find(he_indices.begin(), he_indices.end(),
839 top2_reco_indices.at(0)) != he_indices.end() &&
840 std::find(he_indices.begin(), he_indices.end(),
841 top2_reco_indices.at(1)) != he_indices.end();
842 if (top1_in_he && top2_in_he) {
843 HE_reco_score = hyperedge_scores.at(i).at(0);
844 break;
845 }
846 }
847 }
848
849 // Finally, we need to shift the indices in the tops to reflect
850 // the position in the particle-specific containers.
851 if (both_tops_reco) {
852 reescaleIndicesTtbarDiLepton(hyperGraph, top1_reco_indices, top1_reco_ids);
853 reescaleIndicesTtbarDiLepton(hyperGraph, top2_reco_indices, top2_reco_ids);
854 } else { // Only shift top1
855 reescaleIndicesTtbarDiLepton(hyperGraph, top1_reco_indices, top1_reco_ids);
856 }
857
858 if (EventReco::g_hyper_msg_level == MSG::VERBOSE) {
859 ANA_MSG_INFO("Top1 reco indices after scaling: "
860 << top1_reco_indices.at(0) << " " << top1_reco_indices.at(1));
861 ANA_MSG_INFO("Top2 reco indices after scaling: "
862 << top2_reco_indices.at(0) << " " << top2_reco_indices.at(1));
863 }
864
865 // Save results
866 reco_labels.push_back("HyPER_Reco_Top1");
867 reco_indices.push_back(top1_reco_indices);
868 reco_scores.push_back(top1_reco_score);
869 reco_ids.push_back(top1_reco_ids);
870 reco_labels.push_back("HyPER_Reco_Top2");
871 reco_indices.push_back(top2_reco_indices);
872 reco_scores.push_back(top2_reco_score);
873 reco_ids.push_back(top2_reco_ids);
874 reco_labels.push_back("HyPER_Reco_HE");
875 reco_scores.push_back(HE_reco_score);
876}
877
878} // namespace EventReco
macros for messaging and checking status codes
#define ANA_MSG_INFO(xmsg,...)
Macro printing info messages.
std::vector< std::vector< float > > scores
std::vector< std::vector< int64_t > > indices
int64_t nNodes() const
Definition GraphBase.h:88
Features getNodeFeats(std::size_t index) const
Definition GraphBase.h:74
int64_t nEdges() const
Definition GraphBase.h:89
const std::vector< EdgeIndex > & getEdgeIndicesVector() const
Definition HyPERGraph.h:35
int64_t nHyperEdges() const
Definition HyPERGraph.h:41
HyperEdgeIndex getHyperEdgeIndices(std::size_t index) const
Definition HyPERGraph.h:38
int count(std::string s, const std::string &regx)
count how many occurances of a regx are in a string
Definition hcg.cxx:148
void RecoTtbarAllHadronic(const HyPERGraph &hyperGraph, const scores &edge_scores, const scores &hyperedge_scores, indices &reco_indices, std::vector< float > &reco_scores, std::vector< std::string > &reco_labels)
Reconstruct the ttbar all hadronic topology.
bool isHadTop(const std::vector< int64_t > &decay_indices)
std::vector< std::vector< T > > buildCombinations(const std::vector< T > &elements, int64_t hyperEdgeOrder)
Definition HyPERUtils.h:47
int countParticlesSameType(const HyPERGraph &hyperGraph, EventReco::HyPERParticleID particleType)
void RecoTop(const HyPERGraph &hyperGraph, const scores &edge_scores, const scores &hyperedge_scores, std::vector< int64_t > &hyperedge_masks, std::vector< int64_t > &top_reco_indices, std::vector< int64_t > &w_reco_indices, float &top_reco_score, float &w_reco_score, std::vector< int64_t > &top_reco_ids, const std::vector< std::vector< int64_t > > &allowed_decay_modes)
Reconstruct a top quark with a decay compatible with a list of decay modes specified by a list of ind...
void RecoHadronicTop(const HyPERGraph &hyperGraph, const scores &edge_scores, const scores &hyperedge_scores, std::vector< int64_t > &hyperedge_masks, std::vector< int64_t > &top_reco_indices, std::vector< int64_t > &w_reco_indices, float &top_reco_score, float &w_reco_score)
Reconstruct the hadronic top quark (with the highest score) and W boson from the hypergraph score out...
MSG::Level g_hyper_msg_level
void reescaleIndicesTtbarLJets(const HyPERGraph &hyperGraph, std::vector< int64_t > &top_indices, std::vector< int64_t > &w_indices, const std::vector< int64_t > &ids)
Re-scale the indices of the reconstructed leptonic top decay to reflect the position in the particle-...
std::vector< int64_t > findWInTopLep(const std::vector< int64_t > &decay_indices, const std::vector< int64_t > &decay_ids)
void RecoTopForDiLepton(const HyPERGraph &hyperGraph, const scores &edge_scores, std::vector< int64_t > &edge_masks, std::vector< int64_t > &top_reco_indices, float &top_reco_score, std::vector< int64_t > &top_reco_ids, const std::vector< std::vector< int64_t > > &allowed_decay_modes)
Reconstruct the leptonic top quark (with the highest score) from DiLepton topology.
bool isAllowedDecay(std::vector< int64_t > &decay_indices, const std::vector< std::vector< int64_t > > &allowed_decay_modes)
void reescaleIndicesTtbarDiLepton(const HyPERGraph &hyperGraph, std::vector< int64_t > &top_indices, const std::vector< int64_t > &ids)
void RecoTtbarLJets(const HyPERGraph &hyperGraph, const scores &edge_scores, const scores &hyperedge_scores, const scores &classification_score, indices &reco_indices, std::vector< float > &reco_scores, std::vector< std::string > &reco_labels, std::vector< std::vector< int64_t > > &reco_ids, float &reco_classification_score)
Reconstruct the ttbar single lepton topology.
void RecoTtbarDiLepton(const HyPERGraph &hyperGraph, const scores &edge_scores, const scores &hyperedge_scores, const scores &classification_score, indices &reco_indices, std::vector< float > &reco_scores, std::vector< std::string > &reco_labels, std::vector< std::vector< int64_t > > &reco_ids, float &reco_classification_score)
Reconstruct the ttbar single lepton topology.
bool anyCommonElement(const std::vector< int64_t > &vec1, const std::vector< int64_t > &vec2)
void sort(typename DataModel_detail::iterator< DVL > beg, typename DataModel_detail::iterator< DVL > end)
Specialization of sort for DataVector/List.