File indexing completed on 2026-07-16 08:08:02
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include "ActsExamples/Jets/TruthJetAlgorithm.hpp"
0010
0011 #include "Acts/Definitions/ParticleData.hpp"
0012 #include "Acts/Definitions/PdgParticle.hpp"
0013 #include "Acts/Definitions/Units.hpp"
0014 #include "Acts/Utilities/Logger.hpp"
0015 #include "Acts/Utilities/ScopedTimer.hpp"
0016 #include "ActsExamples/Framework/AlgorithmContext.hpp"
0017 #include "ActsFatras/EventData/ProcessType.hpp"
0018
0019 #include <algorithm>
0020 #include <ranges>
0021 #include <stdexcept>
0022
0023 #include <boost/container/flat_map.hpp>
0024 #include <fastjet/ClusterSequence.hh>
0025 #include <fastjet/JetDefinition.hh>
0026 #include <fastjet/PseudoJet.hh>
0027
0028 namespace ActsExamples {
0029
0030 TruthJetAlgorithm::TruthJetAlgorithm(const Config& cfg,
0031 Acts::Logging::Level lvl)
0032 : IAlgorithm("TruthJetAlgorithm", lvl), m_cfg(cfg) {
0033 if (m_cfg.inputTruthParticles.empty()) {
0034 throw std::invalid_argument("Input particles collection is not configured");
0035 }
0036 m_inputTruthParticles.initialize(m_cfg.inputTruthParticles);
0037 m_outputJets.initialize(m_cfg.outputJets);
0038 }
0039
0040 namespace {
0041 ActsPlugins::FastJet::JetLabel jetLabelFromHadronType(
0042 Acts::HadronType hadronType) {
0043 using enum Acts::HadronType;
0044 switch (hadronType) {
0045 case BBbarMeson:
0046 case BottomMeson:
0047 case BottomBaryon:
0048 return ActsPlugins::FastJet::JetLabel::BJet;
0049 case CCbarMeson:
0050 case CharmedMeson:
0051 case CharmedBaryon:
0052 return ActsPlugins::FastJet::JetLabel::CJet;
0053 case StrangeMeson:
0054 case StrangeBaryon:
0055 case LightMeson:
0056 case LightBaryon:
0057 return ActsPlugins::FastJet::JetLabel::LightJet;
0058 default:
0059 return ActsPlugins::FastJet::JetLabel::Unknown;
0060 }
0061 }
0062
0063 }
0064
0065 ProcessCode ActsExamples::TruthJetAlgorithm::execute(
0066 const ActsExamples::AlgorithmContext& ctx) const {
0067
0068 std::vector<ActsPlugins::FastJet::TruthJet> outputJetContainer{};
0069
0070 Acts::ScopedTimer globalTimer("TruthJetAlgorithm", logger(),
0071 Acts::Logging::DEBUG);
0072
0073 const SimParticleContainer& truthParticlesRaw = m_inputTruthParticles(ctx);
0074 std::vector<const SimParticle*> truthParticles;
0075 truthParticles.reserve(truthParticlesRaw.size());
0076 std::ranges::transform(truthParticlesRaw, std::back_inserter(truthParticles),
0077 [](const auto& particle) { return &particle; });
0078 ACTS_DEBUG("Number of truth particles: " << truthParticles.size());
0079
0080 const fastjet::JetDefinition defaultJetDefinition = fastjet::JetDefinition(
0081 fastjet::antikt_algorithm, m_cfg.jetClusteringRadius);
0082
0083
0084
0085 std::vector<fastjet::PseudoJet> inputPseudoJets;
0086 {
0087 Acts::ScopedTimer timer("Input particle building", logger(),
0088 Acts::Logging::DEBUG);
0089
0090 for (unsigned int i = 0; i < truthParticles.size(); ++i) {
0091 const auto* particle = truthParticles.at(i);
0092
0093 detail::PrimaryVertexIdGetter primaryVertexIdGetter;
0094
0095 ACTS_VERBOSE("Primary vertex ID: "
0096 << primaryVertexIdGetter(*particle).vertexPrimary()
0097 << ", PDG: " << static_cast<int>(particle->pdg()) << ", pT: "
0098 << particle->transverseMomentum() / Acts::UnitConstants::GeV
0099 << " GeV");
0100
0101 if (m_cfg.clusterHSParticlesOnly &&
0102 primaryVertexIdGetter(*particle).vertexPrimary() != 1) {
0103 continue;
0104 }
0105
0106 fastjet::PseudoJet pseudoJet(
0107 particle->momentum().x(), particle->momentum().y(),
0108 particle->momentum().z(), particle->energy());
0109
0110 pseudoJet.set_user_index(i);
0111 inputPseudoJets.push_back(pseudoJet);
0112 }
0113 }
0114
0115 ACTS_DEBUG("Number of input pseudo jets: " << inputPseudoJets.size());
0116
0117 std::vector<fastjet::PseudoJet> jets;
0118 fastjet::ClusterSequence clusterSeq;
0119 {
0120 Acts::ScopedTimer timer("Jet clustering", logger(), Acts::Logging::DEBUG);
0121
0122 clusterSeq =
0123 fastjet::ClusterSequence(inputPseudoJets, defaultJetDefinition);
0124
0125 jets = sorted_by_pt(clusterSeq.inclusive_jets(m_cfg.jetPtMin));
0126
0127 double minEta = m_cfg.jetEtaRange.first;
0128 double maxEta = m_cfg.jetEtaRange.second;
0129
0130 std::erase_if(jets, [minEta, maxEta](const auto& jet) {
0131 return jet.eta() < minEta || jet.eta() > maxEta;
0132 });
0133 ACTS_DEBUG("Number of clustered jets: " << jets.size());
0134 }
0135
0136
0137 std::vector<std::pair<ActsPlugins::FastJet::JetLabel, const SimParticle*>>
0138 hadrons;
0139
0140 if (m_cfg.doJetLabeling) {
0141 Acts::ScopedTimer timer("hadron finding", logger(), Acts::Logging::DEBUG);
0142 ACTS_DEBUG("Jet labeling is enabled. Finding hadrons for jet labeling.");
0143
0144
0145 auto hadronView =
0146 truthParticles | std::views::filter([this](const auto* particle) {
0147 if (m_cfg.jetLabelingHSHadronsOnly) {
0148 detail::PrimaryVertexIdGetter primaryVertexIdGetter;
0149 if (primaryVertexIdGetter(*particle).vertexPrimary() != 1) {
0150 return false;
0151 }
0152 }
0153
0154 Acts::PdgParticle pdgId{particle->pdg()};
0155 if (!Acts::ParticleIdHelper::isHadron(pdgId)) {
0156 return false;
0157 }
0158
0159
0160 auto label =
0161 jetLabelFromHadronType(Acts::ParticleIdHelper::hadronType(pdgId));
0162 using enum ActsPlugins::FastJet::JetLabel;
0163
0164 if (label == BJet || label == CJet) {
0165 if (particle->transverseMomentum() < m_cfg.jetLabelingHadronPtMin) {
0166 return false;
0167 }
0168 }
0169 return true;
0170 }) |
0171 std::views::transform([](const auto* particle) {
0172 Acts::PdgParticle pdgId{particle->pdg()};
0173 auto type = Acts::ParticleIdHelper::hadronType(pdgId);
0174 auto label = jetLabelFromHadronType(type);
0175 return std::make_pair(label, particle);
0176 }) |
0177 std::views::filter([](const auto& hadron) {
0178 return hadron.first > ActsPlugins::FastJet::JetLabel::Unknown;
0179 });
0180
0181 std::ranges::copy(hadronView, std::back_inserter(hadrons));
0182
0183
0184 std::ranges::sort(hadrons, [](const auto& a, const auto& b) {
0185 return a.second->pdg() < b.second->pdg();
0186 });
0187
0188 auto unique = std::ranges::unique(hadrons);
0189 hadrons.erase(unique.begin(), unique.end());
0190 }
0191
0192
0193
0194 auto classifyJet = [&](const fastjet::PseudoJet& jet) {
0195 auto hadronsInJetView =
0196 hadrons | std::views::filter([&jet, this](const auto& hadron) {
0197 const auto& momentum = hadron.second->momentum();
0198 Acts::Vector3 hadronJetMom{momentum[0], momentum[1], momentum[2]};
0199 Acts::Vector3 jetMom{jet.px(), jet.py(), jet.pz()};
0200 return Acts::VectorHelpers::deltaR(jetMom, hadronJetMom) <
0201 m_cfg.jetLabelingDeltaR;
0202 }) |
0203 std::views::transform([](const auto& hadron) {
0204 return std::pair{
0205 hadron.second,
0206 jetLabelFromHadronType(Acts::ParticleIdHelper::hadronType(
0207 Acts::PdgParticle{hadron.second->pdg()}))};
0208 });
0209
0210 std::vector<std::pair<const SimParticle*, ActsPlugins::FastJet::JetLabel>>
0211 hadronsInJet;
0212 std::ranges::copy(hadronsInJetView, std::back_inserter(hadronsInJet));
0213
0214 ACTS_VERBOSE("-> hadrons in jet: " << hadronsInJet.size());
0215 for (const auto& hadron : hadronsInJet) {
0216 ACTS_VERBOSE(
0217 " - " << hadron.first->pdg() << " "
0218 << Acts::findName(Acts::PdgParticle{hadron.first->pdg()})
0219 .value_or("UNKNOWN")
0220 << " label=" << hadron.second);
0221 }
0222
0223 auto maxHadronIt = std::ranges::max_element(
0224 hadronsInJet, [](const auto& a, const auto& b) { return a < b; },
0225 [](const auto& a) {
0226 const auto& [hadron, label] = a;
0227 return label;
0228 });
0229
0230 if (maxHadronIt == hadronsInJet.end()) {
0231
0232 return ActsPlugins::FastJet::JetLabel::Unknown;
0233 }
0234
0235 const auto& [maxHadron, maxHadronLabel] = *maxHadronIt;
0236
0237 ACTS_VERBOSE("-> max hadron type="
0238 << Acts::findName(Acts::PdgParticle{maxHadron->pdg()})
0239 .value_or("UNKNOWN")
0240 << " label=" << maxHadronLabel);
0241
0242 return maxHadronLabel;
0243 };
0244
0245 boost::container::flat_map<ActsPlugins::FastJet::JetLabel, std::size_t>
0246 jetLabelCounts;
0247
0248 {
0249 Acts::AveragingScopedTimer timer("Jet classification", logger(),
0250 Acts::Logging::DEBUG);
0251
0252 for (unsigned int i = 0; i < jets.size(); ++i) {
0253 const auto& jet = jets.at(i);
0254
0255
0256
0257 ActsPlugins::FastJet::JetLabel jetLabel =
0258 ActsPlugins::FastJet::JetLabel::Unknown;
0259 if (m_cfg.doJetLabeling) {
0260 ACTS_DEBUG("Classifying jet " << i);
0261 auto sample = timer.sample();
0262 jetLabel = classifyJet(jet);
0263 }
0264
0265
0266 Acts::Vector4 jetFourMom{jet.px(), jet.py(), jet.pz(), jet.e()};
0267 ActsPlugins::FastJet::TruthJet truthJet(jetFourMom, jetLabel);
0268
0269 std::vector<fastjet::PseudoJet> jetConstituents = jet.constituents();
0270 std::vector<int> constituentIndices;
0271 constituentIndices.reserve(jetConstituents.size());
0272
0273 for (unsigned int j = 0; j < jetConstituents.size(); ++j) {
0274 constituentIndices.push_back(jetConstituents[j].user_index());
0275 }
0276
0277 truthJet.setConstituentIndices(constituentIndices);
0278 outputJetContainer.push_back(truthJet);
0279
0280 jetLabelCounts[jetLabel] += 1;
0281
0282 ACTS_VERBOSE("-> jet label: " << jetLabel);
0283 ACTS_VERBOSE("-> jet constituents: ");
0284
0285 if (logger().doPrint(Acts::Logging::VERBOSE)) {
0286 for (const auto& constituent : constituentIndices) {
0287 const auto& particle = truthParticles.at(constituent);
0288 ACTS_VERBOSE("- " << particle);
0289 }
0290 }
0291 }
0292 }
0293
0294 ACTS_DEBUG("-> jet label counts: ");
0295 for (const auto& [label, count] : jetLabelCounts) {
0296 ACTS_DEBUG(" - " << label << ": " << count);
0297 }
0298
0299 m_outputJets(ctx, std::move(outputJetContainer));
0300
0301 return ProcessCode::SUCCESS;
0302 }
0303
0304 ProcessCode ActsExamples::TruthJetAlgorithm::finalize() {
0305 ACTS_INFO("Finalizing truth jet clustering");
0306 return ProcessCode::SUCCESS;
0307 }
0308
0309 }