File indexing completed on 2025-12-16 09:23:30
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<TrackContainer>>
0069 outputJetContainer{};
0070
0071 Acts::ScopedTimer globalTimer("TruthJetAlgorithm", logger(),
0072 Acts::Logging::DEBUG);
0073
0074 const SimParticleContainer& truthParticlesRaw = m_inputTruthParticles(ctx);
0075 std::vector<const SimParticle*> truthParticles;
0076 truthParticles.reserve(truthParticlesRaw.size());
0077 std::ranges::transform(truthParticlesRaw, std::back_inserter(truthParticles),
0078 [](const auto& particle) { return &particle; });
0079 ACTS_DEBUG("Number of truth particles: " << truthParticles.size());
0080
0081 const fastjet::JetDefinition defaultJetDefinition = fastjet::JetDefinition(
0082 fastjet::antikt_algorithm, m_cfg.jetClusteringRadius);
0083
0084
0085
0086 std::vector<fastjet::PseudoJet> inputPseudoJets;
0087 {
0088 Acts::ScopedTimer timer("Input particle building", logger(),
0089 Acts::Logging::DEBUG);
0090
0091 for (unsigned int i = 0; i < truthParticles.size(); ++i) {
0092 const auto* particle = truthParticles.at(i);
0093
0094 detail::PrimaryVertexIdGetter primaryVertexIdGetter;
0095
0096 ACTS_VERBOSE("Primary vertex ID: "
0097 << primaryVertexIdGetter(*particle).vertexPrimary()
0098 << ", PDG: " << static_cast<int>(particle->pdg()) << ", pT: "
0099 << particle->transverseMomentum() / Acts::UnitConstants::GeV
0100 << " GeV");
0101
0102 if (m_cfg.clusterHSParticlesOnly &&
0103 primaryVertexIdGetter(*particle).vertexPrimary() != 1) {
0104 continue;
0105 }
0106
0107 fastjet::PseudoJet pseudoJet(
0108 particle->momentum().x(), particle->momentum().y(),
0109 particle->momentum().z(), particle->energy());
0110
0111 pseudoJet.set_user_index(i);
0112 inputPseudoJets.push_back(pseudoJet);
0113 }
0114 }
0115
0116 ACTS_DEBUG("Number of input pseudo jets: " << inputPseudoJets.size());
0117
0118 std::vector<fastjet::PseudoJet> jets;
0119 fastjet::ClusterSequence clusterSeq;
0120 {
0121 Acts::ScopedTimer timer("Jet clustering", logger(), Acts::Logging::DEBUG);
0122
0123 clusterSeq =
0124 fastjet::ClusterSequence(inputPseudoJets, defaultJetDefinition);
0125
0126 jets = sorted_by_pt(clusterSeq.inclusive_jets(m_cfg.jetPtMin));
0127
0128 double minEta = m_cfg.jetEtaRange.first;
0129 double maxEta = m_cfg.jetEtaRange.second;
0130
0131 std::erase_if(jets, [minEta, maxEta](const auto& jet) {
0132 return jet.eta() < minEta || jet.eta() > maxEta;
0133 });
0134 ACTS_DEBUG("Number of clustered jets: " << jets.size());
0135 }
0136
0137
0138 std::vector<std::pair<ActsPlugins::FastJet::JetLabel, const SimParticle*>>
0139 hadrons;
0140
0141 if (m_cfg.doJetLabeling) {
0142 Acts::ScopedTimer timer("hadron finding", logger(), Acts::Logging::DEBUG);
0143 ACTS_DEBUG("Jet labeling is enabled. Finding hadrons for jet labeling.");
0144
0145
0146 auto hadronView =
0147 truthParticles | std::views::filter([this](const auto* particle) {
0148 if (m_cfg.jetLabelingHSHadronsOnly) {
0149 detail::PrimaryVertexIdGetter primaryVertexIdGetter;
0150 if (primaryVertexIdGetter(*particle).vertexPrimary() != 1) {
0151 return false;
0152 }
0153 }
0154
0155 Acts::PdgParticle pdgId{particle->pdg()};
0156 if (!Acts::ParticleIdHelper::isHadron(pdgId)) {
0157 return false;
0158 }
0159
0160
0161 auto label =
0162 jetLabelFromHadronType(Acts::ParticleIdHelper::hadronType(pdgId));
0163 using enum ActsPlugins::FastJet::JetLabel;
0164
0165 if (label == BJet || label == CJet) {
0166 if (particle->transverseMomentum() < m_cfg.jetLabelingHadronPtMin) {
0167 return false;
0168 }
0169 }
0170 return true;
0171 }) |
0172 std::views::transform([](const auto* particle) {
0173 Acts::PdgParticle pdgId{particle->pdg()};
0174 auto type = Acts::ParticleIdHelper::hadronType(pdgId);
0175 auto label = jetLabelFromHadronType(type);
0176 return std::make_pair(label, particle);
0177 }) |
0178 std::views::filter([](const auto& hadron) {
0179 return hadron.first > ActsPlugins::FastJet::JetLabel::Unknown;
0180 });
0181
0182 std::ranges::copy(hadronView, std::back_inserter(hadrons));
0183
0184
0185 std::ranges::sort(hadrons, [](const auto& a, const auto& b) {
0186 return a.second->pdg() < b.second->pdg();
0187 });
0188
0189 auto unique = std::ranges::unique(hadrons);
0190 hadrons.erase(unique.begin(), unique.end());
0191 }
0192
0193
0194
0195 auto classifyJet = [&](const fastjet::PseudoJet& jet) {
0196 auto hadronsInJetView =
0197 hadrons | std::views::filter([&jet, this](const auto& hadron) {
0198 const auto& momentum = hadron.second->momentum();
0199 Acts::Vector3 hadronJetMom{momentum[0], momentum[1], momentum[2]};
0200 Acts::Vector3 jetMom{jet.px(), jet.py(), jet.pz()};
0201 return Acts::VectorHelpers::deltaR(jetMom, hadronJetMom) <
0202 m_cfg.jetLabelingDeltaR;
0203 }) |
0204 std::views::transform([](const auto& hadron) {
0205 return std::pair{
0206 hadron.second,
0207 jetLabelFromHadronType(Acts::ParticleIdHelper::hadronType(
0208 Acts::PdgParticle{hadron.second->pdg()}))};
0209 });
0210
0211 std::vector<std::pair<const SimParticle*, ActsPlugins::FastJet::JetLabel>>
0212 hadronsInJet;
0213 std::ranges::copy(hadronsInJetView, std::back_inserter(hadronsInJet));
0214
0215 ACTS_VERBOSE("-> hadrons in jet: " << hadronsInJet.size());
0216 for (const auto& hadron : hadronsInJet) {
0217 ACTS_VERBOSE(
0218 " - " << hadron.first->pdg() << " "
0219 << Acts::findName(Acts::PdgParticle{hadron.first->pdg()})
0220 .value_or("UNKNOWN")
0221 << " label=" << hadron.second);
0222 }
0223
0224 auto maxHadronIt = std::ranges::max_element(
0225 hadronsInJet, [](const auto& a, const auto& b) { return a < b; },
0226 [](const auto& a) {
0227 const auto& [hadron, label] = a;
0228 return label;
0229 });
0230
0231 if (maxHadronIt == hadronsInJet.end()) {
0232
0233 return ActsPlugins::FastJet::JetLabel::Unknown;
0234 }
0235
0236 const auto& [maxHadron, maxHadronLabel] = *maxHadronIt;
0237
0238 ACTS_VERBOSE("-> max hadron type="
0239 << Acts::findName(Acts::PdgParticle{maxHadron->pdg()})
0240 .value_or("UNKNOWN")
0241 << " label=" << maxHadronLabel);
0242
0243 return maxHadronLabel;
0244 };
0245
0246 boost::container::flat_map<ActsPlugins::FastJet::JetLabel, std::size_t>
0247 jetLabelCounts;
0248
0249 {
0250 Acts::AveragingScopedTimer timer("Jet classification", logger(),
0251 Acts::Logging::DEBUG);
0252
0253 for (unsigned int i = 0; i < jets.size(); ++i) {
0254 const auto& jet = jets.at(i);
0255
0256
0257
0258 ActsPlugins::FastJet::JetLabel jetLabel =
0259 ActsPlugins::FastJet::JetLabel::Unknown;
0260 if (m_cfg.doJetLabeling) {
0261 ACTS_DEBUG("Classifying jet " << i);
0262 auto sample = timer.sample();
0263 jetLabel = classifyJet(jet);
0264 }
0265
0266
0267 Acts::Vector4 jetFourMom{jet.px(), jet.py(), jet.pz(), jet.e()};
0268 ActsPlugins::FastJet::TruthJet<TrackContainer> truthJet(jetFourMom,
0269 jetLabel);
0270
0271 std::vector<fastjet::PseudoJet> jetConstituents = jet.constituents();
0272 std::vector<int> constituentIndices;
0273 constituentIndices.reserve(jetConstituents.size());
0274
0275 for (unsigned int j = 0; j < jetConstituents.size(); ++j) {
0276 constituentIndices.push_back(jetConstituents[j].user_index());
0277 }
0278
0279 truthJet.setConstituentIndices(constituentIndices);
0280 outputJetContainer.push_back(truthJet);
0281
0282 jetLabelCounts[jetLabel] += 1;
0283
0284 ACTS_VERBOSE("-> jet label: " << jetLabel);
0285 ACTS_VERBOSE("-> jet constituents: ");
0286
0287 if (logger().doPrint(Acts::Logging::VERBOSE)) {
0288 for (const auto& constituent : constituentIndices) {
0289 const auto& particle = truthParticles.at(constituent);
0290 ACTS_VERBOSE("- " << particle);
0291 }
0292 }
0293 }
0294 }
0295
0296 ACTS_DEBUG("-> jet label counts: ");
0297 for (const auto& [label, count] : jetLabelCounts) {
0298 ACTS_DEBUG(" - " << label << ": " << count);
0299 }
0300
0301 m_outputJets(ctx, std::move(outputJetContainer));
0302
0303 return ProcessCode::SUCCESS;
0304 }
0305
0306 ProcessCode ActsExamples::TruthJetAlgorithm::finalize() {
0307 ACTS_INFO("Finalizing truth jet clustering");
0308 return ProcessCode::SUCCESS;
0309 }
0310
0311 }