File indexing completed on 2025-11-29 09:18:28
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include "ActsExamples/Io/Root/RootVertexNTupleWriter.hpp"
0010
0011 #include "Acts/Definitions/Algebra.hpp"
0012 #include "Acts/Definitions/TrackParametrization.hpp"
0013 #include "Acts/EventData/GenericBoundTrackParameters.hpp"
0014 #include "Acts/EventData/TrackParameters.hpp"
0015 #include "Acts/Propagator/Propagator.hpp"
0016 #include "Acts/Propagator/SympyStepper.hpp"
0017 #include "Acts/Surfaces/PerigeeSurface.hpp"
0018 #include "Acts/Surfaces/Surface.hpp"
0019 #include "Acts/Utilities/Intersection.hpp"
0020 #include "Acts/Utilities/Logger.hpp"
0021 #include "Acts/Utilities/UnitVectors.hpp"
0022 #include "Acts/Vertexing/TrackAtVertex.hpp"
0023 #include "ActsExamples/EventData/SimParticle.hpp"
0024 #include "ActsExamples/EventData/SimVertex.hpp"
0025 #include "ActsExamples/EventData/Track.hpp"
0026 #include "ActsExamples/EventData/TruthMatching.hpp"
0027 #include "ActsFatras/EventData/Barcode.hpp"
0028
0029 #include <algorithm>
0030 #include <cmath>
0031 #include <cstddef>
0032 #include <cstdint>
0033 #include <ios>
0034 #include <limits>
0035 #include <map>
0036 #include <memory>
0037 #include <numbers>
0038 #include <optional>
0039 #include <ostream>
0040 #include <set>
0041 #include <stdexcept>
0042 #include <utility>
0043
0044 #include <TFile.h>
0045 #include <TTree.h>
0046
0047 using Acts::VectorHelpers::eta;
0048 using Acts::VectorHelpers::perp;
0049 using Acts::VectorHelpers::phi;
0050 using Acts::VectorHelpers::theta;
0051
0052 namespace ActsExamples {
0053
0054 namespace {
0055
0056 constexpr double nan = std::numeric_limits<double>::quiet_NaN();
0057
0058 std::uint32_t getNumberOfReconstructableVertices(
0059 const SimParticleContainer& collection) {
0060
0061 std::map<std::uint32_t, std::uint32_t> fmap;
0062
0063 std::vector<std::uint32_t> reconstructableTruthVertices;
0064
0065
0066 for (const SimParticle& p : collection) {
0067 std::uint32_t generation = p.particleId().generation();
0068 if (generation > 0) {
0069
0070 continue;
0071 }
0072 std::uint32_t priVtxId = p.particleId().vertexPrimary();
0073 fmap[priVtxId]++;
0074 }
0075
0076
0077 for (const auto& [priVtxId, occurrence] : fmap) {
0078
0079 if (occurrence > 1) {
0080 reconstructableTruthVertices.push_back(priVtxId);
0081 }
0082 }
0083
0084 return reconstructableTruthVertices.size();
0085 }
0086
0087 std::uint32_t getNumberOfTruePriVertices(
0088 const SimParticleContainer& collection) {
0089
0090 std::set<std::uint32_t> allPriVtxIds;
0091 for (const SimParticle& p : collection) {
0092 std::uint32_t priVtxId = p.particleId().vertexPrimary();
0093 std::uint32_t generation = p.particleId().generation();
0094 if (generation > 0) {
0095
0096 continue;
0097 }
0098
0099 allPriVtxIds.insert(priVtxId);
0100 }
0101
0102 return allPriVtxIds.size();
0103 }
0104
0105 double calcSumPt2(const RootVertexNTupleWriter::Config& config,
0106 const Acts::Vertex& vtx) {
0107 double sumPt2 = 0;
0108 for (const auto& trk : vtx.tracks()) {
0109 if (trk.trackWeight > config.minTrkWeight) {
0110 double pt = trk.originalParams.as<Acts::BoundTrackParameters>()
0111 ->transverseMomentum();
0112 sumPt2 += pt * pt;
0113 }
0114 }
0115 return sumPt2;
0116 }
0117
0118
0119 double pull(double diff, double variance, const std::string& variableStr,
0120 bool afterFit, const Acts::Logger& logger) {
0121 if (variance <= 0) {
0122 std::string tempStr;
0123 if (afterFit) {
0124 tempStr = "after";
0125 } else {
0126 tempStr = "before";
0127 }
0128 ACTS_WARNING("Nonpositive variance " << tempStr << " vertex fit: Var("
0129 << variableStr << ") = " << variance
0130 << " <= 0.");
0131 return nan;
0132 }
0133 double std = std::sqrt(variance);
0134 return diff / std;
0135 }
0136
0137 double calculateTruthPrimaryVertexDensity(
0138 const RootVertexNTupleWriter::Config& config,
0139 const SimVertexContainer& truthVertices, const Acts::Vertex& vtx) {
0140 double z = vtx.fullPosition()[Acts::CoordinateIndices::eZ];
0141 int count = 0;
0142 for (const SimVertex& truthVertex : truthVertices) {
0143 if (truthVertex.vertexId().vertexSecondary() != 0) {
0144 continue;
0145 }
0146 double zTruth = truthVertex.position4[Acts::CoordinateIndices::eZ];
0147 if (std::abs(z - zTruth) <= config.vertexDensityWindow) {
0148 ++count;
0149 }
0150 }
0151 return count / (2 * config.vertexDensityWindow);
0152 }
0153
0154 const SimParticle* findParticle(
0155 const SimParticleContainer& particles,
0156 const TrackParticleMatching& trackParticleMatching, ConstTrackProxy track,
0157 const Acts::Logger& logger) {
0158
0159 auto imatched = trackParticleMatching.find(track.index());
0160 if (imatched == trackParticleMatching.end() ||
0161 !imatched->second.particle.has_value()) {
0162 ACTS_DEBUG("No truth particle associated with this track, index = "
0163 << track.index() << " tip index = " << track.tipIndex());
0164 return nullptr;
0165 }
0166
0167 const TrackMatchEntry& particleMatch = imatched->second;
0168
0169 auto iparticle = particles.find(SimBarcode{particleMatch.particle.value()});
0170 if (iparticle == particles.end()) {
0171 ACTS_DEBUG(
0172 "Truth particle found but not monitored with this track, index = "
0173 << track.index() << " tip index = " << track.tipIndex()
0174 << " and this barcode = " << particleMatch.particle.value());
0175 return {};
0176 }
0177
0178 return &(*iparticle);
0179 }
0180
0181 std::optional<ConstTrackProxy> findTrack(const ConstTrackContainer& tracks,
0182 const Acts::TrackAtVertex& trkAtVtx) {
0183
0184 const Acts::BoundTrackParameters& origTrack =
0185 *trkAtVtx.originalParams.as<Acts::BoundTrackParameters>();
0186
0187
0188
0189
0190
0191 for (ConstTrackProxy inputTrk : tracks) {
0192 const auto& params = inputTrk.parameters();
0193
0194 if (origTrack.parameters() == params) {
0195 return inputTrk;
0196 }
0197 }
0198
0199 return std::nullopt;
0200 }
0201
0202 }
0203
0204 RootVertexNTupleWriter::RootVertexNTupleWriter(
0205 const RootVertexNTupleWriter::Config& config, Acts::Logging::Level level)
0206 : WriterT(config.inputVertices, "RootVertexNTupleWriter", level),
0207 m_cfg(config) {
0208 if (m_cfg.filePath.empty()) {
0209 throw std::invalid_argument("Missing output filename");
0210 }
0211 if (m_cfg.treeName.empty()) {
0212 throw std::invalid_argument("Missing tree name");
0213 }
0214 if (m_cfg.inputTruthVertices.empty()) {
0215 throw std::invalid_argument("Collection with truth vertices missing");
0216 }
0217 if (m_cfg.inputParticles.empty()) {
0218 throw std::invalid_argument("Collection with particles missing");
0219 }
0220 if (m_cfg.inputSelectedParticles.empty()) {
0221 throw std::invalid_argument("Collection with selected particles missing");
0222 }
0223
0224 m_inputTracks.maybeInitialize(m_cfg.inputTracks);
0225 m_inputTruthVertices.initialize(m_cfg.inputTruthVertices);
0226 m_inputParticles.initialize(m_cfg.inputParticles);
0227 m_inputSelectedParticles.initialize(m_cfg.inputSelectedParticles);
0228 m_inputTrackParticleMatching.maybeInitialize(
0229 m_cfg.inputTrackParticleMatching);
0230
0231 if (m_cfg.writeTrackInfo && !m_inputTracks.isInitialized()) {
0232 throw std::invalid_argument("Missing input tracks collection");
0233 }
0234 if (m_cfg.writeTrackInfo && !m_inputTrackParticleMatching.isInitialized()) {
0235 throw std::invalid_argument("Missing input track particles matching");
0236 }
0237
0238
0239 m_outputFile = TFile::Open(m_cfg.filePath.c_str(), m_cfg.fileMode.c_str());
0240 if (m_outputFile == nullptr) {
0241 throw std::ios_base::failure("Could not open '" + m_cfg.filePath + "'");
0242 }
0243 m_outputFile->cd();
0244 m_outputTree = new TTree(m_cfg.treeName.c_str(), m_cfg.treeName.c_str());
0245 if (m_outputTree == nullptr) {
0246 throw std::bad_alloc();
0247 }
0248
0249 m_outputTree->Branch("event_nr", &m_eventNr);
0250
0251 m_outputTree->Branch("nRecoVtx", &m_nRecoVtx);
0252 m_outputTree->Branch("nTrueVtx", &m_nTrueVtx);
0253 m_outputTree->Branch("nCleanVtx", &m_nCleanVtx);
0254 m_outputTree->Branch("nMergedVtx", &m_nMergedVtx);
0255 m_outputTree->Branch("nSplitVtx", &m_nSplitVtx);
0256 m_outputTree->Branch("nVtxDetectorAcceptance", &m_nVtxDetAcceptance);
0257 m_outputTree->Branch("nVtxReconstructable", &m_nVtxReconstructable);
0258
0259 m_outputTree->Branch("nTracksRecoVtx", &m_nTracksOnRecoVertex);
0260
0261 m_outputTree->Branch("recoVertexTrackWeights", &m_recoVertexTrackWeights);
0262
0263 m_outputTree->Branch("sumPt2", &m_sumPt2);
0264
0265 m_outputTree->Branch("recoX", &m_recoX);
0266 m_outputTree->Branch("recoY", &m_recoY);
0267 m_outputTree->Branch("recoZ", &m_recoZ);
0268 m_outputTree->Branch("recoT", &m_recoT);
0269
0270 m_outputTree->Branch("covXX", &m_covXX);
0271 m_outputTree->Branch("covYY", &m_covYY);
0272 m_outputTree->Branch("covZZ", &m_covZZ);
0273 m_outputTree->Branch("covTT", &m_covTT);
0274 m_outputTree->Branch("covXY", &m_covXY);
0275 m_outputTree->Branch("covXZ", &m_covXZ);
0276 m_outputTree->Branch("covXT", &m_covXT);
0277 m_outputTree->Branch("covYZ", &m_covYZ);
0278 m_outputTree->Branch("covYT", &m_covYT);
0279 m_outputTree->Branch("covZT", &m_covZT);
0280
0281 m_outputTree->Branch("seedX", &m_seedX);
0282 m_outputTree->Branch("seedY", &m_seedY);
0283 m_outputTree->Branch("seedZ", &m_seedZ);
0284 m_outputTree->Branch("seedT", &m_seedT);
0285
0286 m_outputTree->Branch("vertex_primary", &m_vertexPrimary);
0287 m_outputTree->Branch("vertex_secondary", &m_vertexSecondary);
0288
0289 m_outputTree->Branch("nTracksTruthVtx", &m_nTracksOnTruthVertex);
0290
0291 m_outputTree->Branch("truthPrimaryVertexDensity",
0292 &m_truthPrimaryVertexDensity);
0293
0294 m_outputTree->Branch("truthVertexTrackWeights", &m_truthVertexTrackWeights);
0295 m_outputTree->Branch("truthVertexMatchRatio", &m_truthVertexMatchRatio);
0296 m_outputTree->Branch("recoVertexContamination", &m_recoVertexContamination);
0297
0298 m_outputTree->Branch("recoVertexClassification", &m_recoVertexClassification);
0299
0300 m_outputTree->Branch("truthX", &m_truthX);
0301 m_outputTree->Branch("truthY", &m_truthY);
0302 m_outputTree->Branch("truthZ", &m_truthZ);
0303 m_outputTree->Branch("truthT", &m_truthT);
0304
0305 m_outputTree->Branch("resX", &m_resX);
0306 m_outputTree->Branch("resY", &m_resY);
0307 m_outputTree->Branch("resZ", &m_resZ);
0308 m_outputTree->Branch("resT", &m_resT);
0309
0310 m_outputTree->Branch("resSeedZ", &m_resSeedZ);
0311 m_outputTree->Branch("resSeedT", &m_resSeedT);
0312
0313 m_outputTree->Branch("pullX", &m_pullX);
0314 m_outputTree->Branch("pullY", &m_pullY);
0315 m_outputTree->Branch("pullZ", &m_pullZ);
0316 m_outputTree->Branch("pullT", &m_pullT);
0317
0318 if (m_cfg.writeTrackInfo) {
0319 m_outputTree->Branch("trk_weight", &m_trkWeight);
0320
0321 m_outputTree->Branch("trk_recoPhi", &m_recoPhi);
0322 m_outputTree->Branch("trk_recoTheta", &m_recoTheta);
0323 m_outputTree->Branch("trk_recoQOverP", &m_recoQOverP);
0324
0325 m_outputTree->Branch("trk_recoPhiFitted", &m_recoPhiFitted);
0326 m_outputTree->Branch("trk_recoThetaFitted", &m_recoThetaFitted);
0327 m_outputTree->Branch("trk_recoQOverPFitted", &m_recoQOverPFitted);
0328
0329 m_outputTree->Branch("trk_truthPhi", &m_truthPhi);
0330 m_outputTree->Branch("trk_truthTheta", &m_truthTheta);
0331 m_outputTree->Branch("trk_truthQOverP", &m_truthQOverP);
0332
0333 m_outputTree->Branch("trk_resPhi", &m_resPhi);
0334 m_outputTree->Branch("trk_resTheta", &m_resTheta);
0335 m_outputTree->Branch("trk_resQOverP", &m_resQOverP);
0336 m_outputTree->Branch("trk_momOverlap", &m_momOverlap);
0337
0338 m_outputTree->Branch("trk_resPhiFitted", &m_resPhiFitted);
0339 m_outputTree->Branch("trk_resThetaFitted", &m_resThetaFitted);
0340 m_outputTree->Branch("trk_resQOverPFitted", &m_resQOverPFitted);
0341 m_outputTree->Branch("trk_momOverlapFitted", &m_momOverlapFitted);
0342
0343 m_outputTree->Branch("trk_pullPhi", &m_pullPhi);
0344 m_outputTree->Branch("trk_pullTheta", &m_pullTheta);
0345 m_outputTree->Branch("trk_pullQOverP", &m_pullQOverP);
0346
0347 m_outputTree->Branch("trk_pullPhiFitted", &m_pullPhiFitted);
0348 m_outputTree->Branch("trk_pullThetaFitted", &m_pullThetaFitted);
0349 m_outputTree->Branch("trk_pullQOverPFitted", &m_pullQOverPFitted);
0350 }
0351 }
0352
0353 RootVertexNTupleWriter::~RootVertexNTupleWriter() {
0354 if (m_outputFile != nullptr) {
0355 m_outputFile->Close();
0356 }
0357 }
0358
0359 ProcessCode RootVertexNTupleWriter::finalize() {
0360 m_outputFile->cd();
0361 m_outputTree->Write();
0362 m_outputFile->Close();
0363
0364 return ProcessCode::SUCCESS;
0365 }
0366
0367 ProcessCode RootVertexNTupleWriter::writeT(
0368 const AlgorithmContext& ctx, const std::vector<Acts::Vertex>& vertices) {
0369
0370
0371 const static TrackParticleMatching emptyTrackParticleMatching;
0372 const static Acts::ConstVectorTrackContainer emptyConstTrackContainer;
0373 const static Acts::ConstVectorMultiTrajectory emptyConstTrackStateContainer;
0374 const static ConstTrackContainer emptyTracks(
0375 std::make_shared<Acts::ConstVectorTrackContainer>(
0376 emptyConstTrackContainer),
0377 std::make_shared<Acts::ConstVectorMultiTrajectory>(
0378 emptyConstTrackStateContainer));
0379
0380
0381 const SimVertexContainer& truthVertices = m_inputTruthVertices(ctx);
0382
0383 const SimParticleContainer& particles = m_inputParticles(ctx);
0384 const SimParticleContainer& selectedParticles = m_inputSelectedParticles(ctx);
0385 const TrackParticleMatching& trackParticleMatching =
0386 (m_inputTrackParticleMatching.isInitialized()
0387 ? m_inputTrackParticleMatching(ctx)
0388 : emptyTrackParticleMatching);
0389 const ConstTrackContainer& tracks =
0390 (m_inputTracks.isInitialized() ? m_inputTracks(ctx) : emptyTracks);
0391 SimParticleContainer recoParticles;
0392
0393 for (ConstTrackProxy track : tracks) {
0394 if (!track.hasReferenceSurface()) {
0395 ACTS_DEBUG("No reference surface on this track, index = "
0396 << track.index() << " tip index = " << track.tipIndex());
0397 continue;
0398 }
0399
0400 if (const SimParticle* particle =
0401 findParticle(particles, trackParticleMatching, track, logger());
0402 particle != nullptr) {
0403 recoParticles.insert(*particle);
0404 }
0405 }
0406 if (tracks.size() == 0) {
0407
0408
0409 recoParticles = particles;
0410 }
0411
0412
0413 std::lock_guard<std::mutex> lock(m_writeMutex);
0414
0415 m_nRecoVtx = vertices.size();
0416 m_nCleanVtx = 0;
0417 m_nMergedVtx = 0;
0418 m_nSplitVtx = 0;
0419
0420 ACTS_DEBUG("Number of reco vertices in event: " << m_nRecoVtx);
0421
0422
0423 m_nTrueVtx = getNumberOfTruePriVertices(particles);
0424
0425 m_nVtxDetAcceptance = getNumberOfTruePriVertices(selectedParticles);
0426
0427 ACTS_DEBUG("Number of truth particles in event : " << particles.size());
0428 ACTS_DEBUG("Number of truth primary vertices : " << m_nTrueVtx);
0429 ACTS_DEBUG("Number of detector-accepted truth primary vertices : "
0430 << m_nVtxDetAcceptance);
0431
0432
0433 m_eventNr = ctx.eventNumber;
0434
0435
0436 m_nVtxReconstructable = getNumberOfReconstructableVertices(recoParticles);
0437
0438 ACTS_DEBUG("Number of reconstructed tracks : " << tracks.size());
0439 ACTS_DEBUG("Number of reco track-associated truth particles in event : "
0440 << recoParticles.size());
0441 ACTS_DEBUG("Maximum number of reconstructible primary vertices : "
0442 << m_nVtxReconstructable);
0443
0444 struct ToTruthMatching {
0445 std::optional<SimVertexBarcode> vertexId;
0446 double totalTrackWeight{};
0447 double truthMajorityTrackWeights{};
0448 double matchFraction{};
0449
0450 RecoVertexClassification classification{RecoVertexClassification::Unknown};
0451 };
0452 struct ToRecoMatching {
0453 std::size_t recoIndex{};
0454
0455 double recoSumPt2{};
0456 };
0457
0458 std::vector<ToTruthMatching> recoToTruthMatching;
0459 std::map<SimVertexBarcode, ToRecoMatching> truthToRecoMatching;
0460
0461
0462 for (const auto& [vtxIndex, vtx] : Acts::enumerate(vertices)) {
0463
0464 const std::vector<Acts::TrackAtVertex>& tracksAtVtx = vtx.tracks();
0465
0466
0467
0468 std::vector<std::pair<SimVertexBarcode, double>> contributingTruthVertices;
0469
0470 double totalTrackWeight = 0;
0471 if (!tracksAtVtx.empty()) {
0472 for (const Acts::TrackAtVertex& trk : tracksAtVtx) {
0473 if (trk.trackWeight < m_cfg.minTrkWeight) {
0474 continue;
0475 }
0476
0477 totalTrackWeight += trk.trackWeight;
0478
0479 std::optional<ConstTrackProxy> trackOpt = findTrack(tracks, trk);
0480 if (!trackOpt.has_value()) {
0481 ACTS_DEBUG("Track has no matching input track.");
0482 continue;
0483 }
0484 const ConstTrackProxy& inputTrk = *trackOpt;
0485 const SimParticle* particle =
0486 findParticle(particles, trackParticleMatching, inputTrk, logger());
0487 if (particle == nullptr) {
0488 ACTS_VERBOSE("Track has no matching truth particle.");
0489 } else {
0490 contributingTruthVertices.emplace_back(
0491 SimBarcode{particle->particleId()}.vertexId(), trk.trackWeight);
0492 }
0493 }
0494 } else {
0495
0496 for (auto& particle : recoParticles) {
0497 contributingTruthVertices.emplace_back(
0498 SimBarcode{particle.particleId()}.vertexId(), 1.);
0499 }
0500 }
0501
0502
0503 std::map<SimVertexBarcode, std::pair<int, double>> fmap;
0504 for (const auto& [vtxId, weight] : contributingTruthVertices) {
0505 ++fmap[vtxId].first;
0506 fmap[vtxId].second += weight;
0507 }
0508 double truthMajorityVertexTrackWeights = 0;
0509 std::optional<SimVertexBarcode> truthMajorityVertexId = std::nullopt;
0510 for (const auto& [vtxId, counter] : fmap) {
0511 if (counter.second > truthMajorityVertexTrackWeights) {
0512 truthMajorityVertexId = vtxId;
0513 truthMajorityVertexTrackWeights = counter.second;
0514 }
0515 }
0516
0517 double sumPt2 = calcSumPt2(m_cfg, vtx);
0518
0519 double vertexMatchFraction =
0520 (totalTrackWeight > 0
0521 ? truthMajorityVertexTrackWeights / totalTrackWeight
0522 : 0.);
0523 RecoVertexClassification recoVertexClassification =
0524 RecoVertexClassification::Unknown;
0525
0526 if (vertexMatchFraction >= m_cfg.vertexMatchThreshold) {
0527 recoVertexClassification = RecoVertexClassification::Clean;
0528 } else {
0529 recoVertexClassification = RecoVertexClassification::Merged;
0530 }
0531
0532 recoToTruthMatching.push_back({truthMajorityVertexId, totalTrackWeight,
0533 truthMajorityVertexTrackWeights,
0534 vertexMatchFraction,
0535 recoVertexClassification});
0536
0537 auto& recoToTruth = recoToTruthMatching.back();
0538
0539
0540 if (!truthMajorityVertexId.has_value()) {
0541
0542 ACTS_DEBUG("No truth vertex matched to this reconstructed vertex.");
0543 continue;
0544 }
0545
0546 if (auto it = truthToRecoMatching.find(truthMajorityVertexId.value());
0547 it != truthToRecoMatching.end()) {
0548
0549
0550
0551
0552
0553 if (sumPt2 <= it->second.recoSumPt2) {
0554
0555
0556 recoToTruth.classification = RecoVertexClassification::Split;
0557
0558
0559 } else {
0560
0561
0562 auto& otherRecoToTruth = recoToTruthMatching.at(it->second.recoIndex);
0563
0564 recoToTruth.classification = otherRecoToTruth.classification;
0565 otherRecoToTruth.classification = RecoVertexClassification::Split;
0566
0567
0568 it->second = {vtxIndex, sumPt2};
0569 }
0570 } else {
0571 truthToRecoMatching[truthMajorityVertexId.value()] = {vtxIndex, sumPt2};
0572 }
0573 }
0574
0575
0576
0577 for (const auto& [vtxIndex, vtx] : Acts::enumerate(vertices)) {
0578
0579 const auto& tracksAtVtx = vtx.tracks();
0580
0581 std::vector<std::uint32_t> trackIndices;
0582
0583 const auto& toTruthMatching = recoToTruthMatching[vtxIndex];
0584
0585 m_recoX.push_back(vtx.fullPosition()[Acts::CoordinateIndices::eX]);
0586 m_recoY.push_back(vtx.fullPosition()[Acts::CoordinateIndices::eY]);
0587 m_recoZ.push_back(vtx.fullPosition()[Acts::CoordinateIndices::eZ]);
0588 m_recoT.push_back(vtx.fullPosition()[Acts::CoordinateIndices::eTime]);
0589
0590 double varX = vtx.fullCovariance()(Acts::CoordinateIndices::eX,
0591 Acts::CoordinateIndices::eX);
0592 double varY = vtx.fullCovariance()(Acts::CoordinateIndices::eY,
0593 Acts::CoordinateIndices::eY);
0594 double varZ = vtx.fullCovariance()(Acts::CoordinateIndices::eZ,
0595 Acts::CoordinateIndices::eZ);
0596 double varTime = vtx.fullCovariance()(Acts::CoordinateIndices::eTime,
0597 Acts::CoordinateIndices::eTime);
0598
0599 m_covXX.push_back(varX);
0600 m_covYY.push_back(varY);
0601 m_covZZ.push_back(varZ);
0602 m_covTT.push_back(varTime);
0603 m_covXY.push_back(vtx.fullCovariance()(Acts::CoordinateIndices::eX,
0604 Acts::CoordinateIndices::eY));
0605 m_covXZ.push_back(vtx.fullCovariance()(Acts::CoordinateIndices::eX,
0606 Acts::CoordinateIndices::eZ));
0607 m_covXT.push_back(vtx.fullCovariance()(Acts::CoordinateIndices::eX,
0608 Acts::CoordinateIndices::eTime));
0609 m_covYZ.push_back(vtx.fullCovariance()(Acts::CoordinateIndices::eY,
0610 Acts::CoordinateIndices::eZ));
0611 m_covYT.push_back(vtx.fullCovariance()(Acts::CoordinateIndices::eY,
0612 Acts::CoordinateIndices::eTime));
0613 m_covZT.push_back(vtx.fullCovariance()(Acts::CoordinateIndices::eZ,
0614 Acts::CoordinateIndices::eTime));
0615
0616 double sumPt2 = calcSumPt2(m_cfg, vtx);
0617 m_sumPt2.push_back(sumPt2);
0618
0619 double recoVertexTrackWeights = 0;
0620 for (const Acts::TrackAtVertex& trk : tracksAtVtx) {
0621 if (trk.trackWeight < m_cfg.minTrkWeight) {
0622 continue;
0623 }
0624 recoVertexTrackWeights += trk.trackWeight;
0625 }
0626 m_recoVertexTrackWeights.push_back(recoVertexTrackWeights);
0627
0628 unsigned int nTracksOnRecoVertex = std::count_if(
0629 tracksAtVtx.begin(), tracksAtVtx.end(), [this](const auto& trkAtVtx) {
0630 return trkAtVtx.trackWeight > m_cfg.minTrkWeight;
0631 });
0632 m_nTracksOnRecoVertex.push_back(nTracksOnRecoVertex);
0633
0634
0635 bool truthInfoWritten = false;
0636 std::optional<Acts::Vector4> truthPos;
0637 if (toTruthMatching.vertexId.has_value()) {
0638 auto iTruthVertex = truthVertices.find(toTruthMatching.vertexId.value());
0639 if (iTruthVertex == truthVertices.end()) {
0640 ACTS_ERROR("Truth vertex not found for id: "
0641 << toTruthMatching.vertexId.value());
0642 continue;
0643 }
0644 const SimVertex& truthVertex = *iTruthVertex;
0645
0646 m_vertexPrimary.push_back(truthVertex.vertexId().vertexPrimary());
0647 m_vertexSecondary.push_back(truthVertex.vertexId().vertexSecondary());
0648
0649
0650 int nTracksOnTruthVertex = 0;
0651 for (const auto& particle : selectedParticles) {
0652 if (static_cast<SimVertexBarcode>(particle.particleId().vertexId()) ==
0653 truthVertex.vertexId()) {
0654 ++nTracksOnTruthVertex;
0655 }
0656 }
0657 m_nTracksOnTruthVertex.push_back(nTracksOnTruthVertex);
0658
0659 double truthPrimaryVertexDensity =
0660 calculateTruthPrimaryVertexDensity(m_cfg, truthVertices, vtx);
0661 m_truthPrimaryVertexDensity.push_back(truthPrimaryVertexDensity);
0662
0663 double truthVertexTrackWeights =
0664 toTruthMatching.truthMajorityTrackWeights;
0665 m_truthVertexTrackWeights.push_back(truthVertexTrackWeights);
0666
0667 double truthVertexMatchRatio = toTruthMatching.matchFraction;
0668 m_truthVertexMatchRatio.push_back(truthVertexMatchRatio);
0669
0670 double recoVertexContamination = 1 - truthVertexMatchRatio;
0671 m_recoVertexContamination.push_back(recoVertexContamination);
0672
0673 RecoVertexClassification recoVertexClassification =
0674 toTruthMatching.classification;
0675 m_recoVertexClassification.push_back(
0676 static_cast<int>(recoVertexClassification));
0677
0678 if (recoVertexClassification == RecoVertexClassification::Clean) {
0679 ++m_nCleanVtx;
0680 } else if (recoVertexClassification == RecoVertexClassification::Merged) {
0681 ++m_nMergedVtx;
0682 } else if (recoVertexClassification == RecoVertexClassification::Split) {
0683 ++m_nSplitVtx;
0684 }
0685
0686 const Acts::Vector4& truePos = truthVertex.position4;
0687 truthPos = truePos;
0688 m_truthX.push_back(truePos[Acts::CoordinateIndices::eX]);
0689 m_truthY.push_back(truePos[Acts::CoordinateIndices::eY]);
0690 m_truthZ.push_back(truePos[Acts::CoordinateIndices::eZ]);
0691 m_truthT.push_back(truePos[Acts::CoordinateIndices::eTime]);
0692
0693 const Acts::Vector4 diffPos = vtx.fullPosition() - truePos;
0694 m_resX.push_back(diffPos[Acts::CoordinateIndices::eX]);
0695 m_resY.push_back(diffPos[Acts::CoordinateIndices::eY]);
0696 m_resZ.push_back(diffPos[Acts::CoordinateIndices::eZ]);
0697 m_resT.push_back(diffPos[Acts::CoordinateIndices::eTime]);
0698
0699 m_pullX.push_back(pull(diffPos[Acts::CoordinateIndices::eX], varX, "X",
0700 true, logger()));
0701 m_pullY.push_back(pull(diffPos[Acts::CoordinateIndices::eY], varY, "Y",
0702 true, logger()));
0703 m_pullZ.push_back(pull(diffPos[Acts::CoordinateIndices::eZ], varZ, "Z",
0704 true, logger()));
0705 m_pullT.push_back(pull(diffPos[Acts::CoordinateIndices::eTime], varTime,
0706 "T", true, logger()));
0707
0708 truthInfoWritten = true;
0709 }
0710 if (!truthInfoWritten) {
0711 m_vertexPrimary.push_back(-1);
0712 m_vertexSecondary.push_back(-1);
0713
0714 m_nTracksOnTruthVertex.push_back(-1);
0715
0716 m_truthPrimaryVertexDensity.push_back(nan);
0717
0718 m_truthVertexTrackWeights.push_back(nan);
0719 m_truthVertexMatchRatio.push_back(nan);
0720 m_recoVertexContamination.push_back(nan);
0721
0722 m_recoVertexClassification.push_back(
0723 static_cast<int>(RecoVertexClassification::Unknown));
0724
0725 m_truthX.push_back(nan);
0726 m_truthY.push_back(nan);
0727 m_truthZ.push_back(nan);
0728 m_truthT.push_back(nan);
0729
0730 m_resX.push_back(nan);
0731 m_resY.push_back(nan);
0732 m_resZ.push_back(nan);
0733 m_resT.push_back(nan);
0734
0735 m_pullX.push_back(nan);
0736 m_pullY.push_back(nan);
0737 m_pullZ.push_back(nan);
0738 m_pullT.push_back(nan);
0739 }
0740
0741 if (m_cfg.writeTrackInfo) {
0742 writeTrackInfo(ctx, particles, tracks, trackParticleMatching, truthPos,
0743 tracksAtVtx);
0744 }
0745 }
0746
0747
0748 m_outputTree->Fill();
0749
0750 m_nTracksOnRecoVertex.clear();
0751 m_recoVertexTrackWeights.clear();
0752 m_recoX.clear();
0753 m_recoY.clear();
0754 m_recoZ.clear();
0755 m_recoT.clear();
0756 m_covXX.clear();
0757 m_covYY.clear();
0758 m_covZZ.clear();
0759 m_covTT.clear();
0760 m_covXY.clear();
0761 m_covXZ.clear();
0762 m_covXT.clear();
0763 m_covYZ.clear();
0764 m_covYT.clear();
0765 m_covZT.clear();
0766 m_seedX.clear();
0767 m_seedY.clear();
0768 m_seedZ.clear();
0769 m_seedT.clear();
0770 m_vertexPrimary.clear();
0771 m_vertexSecondary.clear();
0772 m_nTracksOnTruthVertex.clear();
0773 m_truthPrimaryVertexDensity.clear();
0774 m_truthVertexTrackWeights.clear();
0775 m_truthVertexMatchRatio.clear();
0776 m_recoVertexContamination.clear();
0777 m_recoVertexClassification.clear();
0778 m_truthX.clear();
0779 m_truthY.clear();
0780 m_truthZ.clear();
0781 m_truthT.clear();
0782 m_resX.clear();
0783 m_resY.clear();
0784 m_resZ.clear();
0785 m_resT.clear();
0786 m_resSeedZ.clear();
0787 m_resSeedT.clear();
0788 m_pullX.clear();
0789 m_pullY.clear();
0790 m_pullZ.clear();
0791 m_pullT.clear();
0792 m_sumPt2.clear();
0793 m_trkWeight.clear();
0794 m_recoPhi.clear();
0795 m_recoTheta.clear();
0796 m_recoQOverP.clear();
0797 m_recoPhiFitted.clear();
0798 m_recoThetaFitted.clear();
0799 m_recoQOverPFitted.clear();
0800 m_trkParticleIdVertexPrimary.clear();
0801 m_trkParticleIdVertexSecondary.clear();
0802 m_trkParticleIdParticle.clear();
0803 m_trkParticleIdGeneration.clear();
0804 m_trkParticleIdSubParticle.clear();
0805 m_truthPhi.clear();
0806 m_truthTheta.clear();
0807 m_truthQOverP.clear();
0808 m_resPhi.clear();
0809 m_resTheta.clear();
0810 m_resQOverP.clear();
0811 m_momOverlap.clear();
0812 m_resPhiFitted.clear();
0813 m_resThetaFitted.clear();
0814 m_resQOverPFitted.clear();
0815 m_momOverlapFitted.clear();
0816 m_pullPhi.clear();
0817 m_pullTheta.clear();
0818 m_pullQOverP.clear();
0819 m_pullPhiFitted.clear();
0820 m_pullThetaFitted.clear();
0821 m_pullQOverPFitted.clear();
0822
0823 return ProcessCode::SUCCESS;
0824 }
0825
0826 void RootVertexNTupleWriter::writeTrackInfo(
0827 const AlgorithmContext& ctx, const SimParticleContainer& particles,
0828 const ConstTrackContainer& tracks,
0829 const TrackParticleMatching& trackParticleMatching,
0830 const std::optional<Acts::Vector4>& truthPos,
0831 const std::vector<Acts::TrackAtVertex>& tracksAtVtx) {
0832
0833
0834
0835 Acts::SympyStepper stepper(m_cfg.bField);
0836 using Propagator = Acts::Propagator<Acts::SympyStepper>;
0837 auto propagator = std::make_shared<Propagator>(stepper);
0838
0839
0840
0841
0842
0843
0844
0845 auto& innerTrkWeight = m_trkWeight.emplace_back();
0846
0847 auto& innerRecoPhi = m_recoPhi.emplace_back();
0848 auto& innerRecoTheta = m_recoTheta.emplace_back();
0849 auto& innerRecoQOverP = m_recoQOverP.emplace_back();
0850
0851 auto& innerRecoPhiFitted = m_recoPhiFitted.emplace_back();
0852 auto& innerRecoThetaFitted = m_recoThetaFitted.emplace_back();
0853 auto& innerRecoQOverPFitted = m_recoQOverPFitted.emplace_back();
0854
0855 auto& innerTrkParticleIdVertexPrimary =
0856 m_trkParticleIdVertexPrimary.emplace_back();
0857 auto& innerTrkParticleIdVertexSecondary =
0858 m_trkParticleIdVertexSecondary.emplace_back();
0859 auto& innerTrkParticleIdParticle = m_trkParticleIdParticle.emplace_back();
0860 auto& innerTrkParticleIdGeneration = m_trkParticleIdGeneration.emplace_back();
0861 auto& innerTrkParticleIdSubParticle =
0862 m_trkParticleIdSubParticle.emplace_back();
0863
0864 auto& innerTruthPhi = m_truthPhi.emplace_back();
0865 auto& innerTruthTheta = m_truthTheta.emplace_back();
0866 auto& innerTruthQOverP = m_truthQOverP.emplace_back();
0867
0868 auto& innerResPhi = m_resPhi.emplace_back();
0869 auto& innerResTheta = m_resTheta.emplace_back();
0870 auto& innerResQOverP = m_resQOverP.emplace_back();
0871
0872 auto& innerResPhiFitted = m_resPhiFitted.emplace_back();
0873 auto& innerResThetaFitted = m_resThetaFitted.emplace_back();
0874 auto& innerResQOverPFitted = m_resQOverPFitted.emplace_back();
0875
0876 auto& innerMomOverlap = m_momOverlap.emplace_back();
0877 auto& innerMomOverlapFitted = m_momOverlapFitted.emplace_back();
0878
0879 auto& innerPullPhi = m_pullPhi.emplace_back();
0880 auto& innerPullTheta = m_pullTheta.emplace_back();
0881 auto& innerPullQOverP = m_pullQOverP.emplace_back();
0882
0883 auto& innerPullPhiFitted = m_pullPhiFitted.emplace_back();
0884 auto& innerPullThetaFitted = m_pullThetaFitted.emplace_back();
0885 auto& innerPullQOverPFitted = m_pullQOverPFitted.emplace_back();
0886
0887
0888 std::shared_ptr<Acts::PerigeeSurface> perigeeSurface;
0889 if (truthPos.has_value()) {
0890 perigeeSurface =
0891 Acts::Surface::makeShared<Acts::PerigeeSurface>(truthPos->head<3>());
0892 }
0893
0894 auto propagateToVtx =
0895 [&](const auto& params) -> std::optional<Acts::BoundTrackParameters> {
0896 if (!perigeeSurface) {
0897 return std::nullopt;
0898 }
0899
0900 Acts::Intersection3D intersection =
0901 perigeeSurface
0902 ->intersect(ctx.geoContext, params.position(ctx.geoContext),
0903 params.direction(), Acts::BoundaryTolerance::Infinite())
0904 .closest();
0905
0906
0907 using PropagatorOptions = Propagator::Options<>;
0908 PropagatorOptions pOptions(ctx.geoContext, ctx.magFieldContext);
0909 pOptions.direction =
0910 Acts::Direction::fromScalarZeroAsPositive(intersection.pathLength());
0911
0912 auto result = propagator->propagate(params, *perigeeSurface, pOptions);
0913 if (!result.ok()) {
0914 ACTS_ERROR("Propagation to true vertex position failed.");
0915 return std::nullopt;
0916 }
0917 auto& paramsAtVtx = *result->endParameters;
0918 return std::make_optional(paramsAtVtx);
0919 };
0920
0921 for (const Acts::TrackAtVertex& trk : tracksAtVtx) {
0922 if (trk.trackWeight < m_cfg.minTrkWeight) {
0923 continue;
0924 }
0925
0926 innerTrkWeight.push_back(trk.trackWeight);
0927
0928 Acts::Vector3 trueUnitDir = Acts::Vector3::Zero();
0929 Acts::Vector3 trueMom = Acts::Vector3::Zero();
0930
0931 const SimParticle* particle = nullptr;
0932 std::optional<ConstTrackProxy> trackOpt = findTrack(tracks, trk);
0933 if (trackOpt.has_value()) {
0934 particle =
0935 findParticle(particles, trackParticleMatching, *trackOpt, logger());
0936 }
0937
0938 if (particle != nullptr) {
0939 const auto barcode = particle->particleId();
0940 innerTrkParticleIdVertexPrimary.push_back(barcode.vertexPrimary());
0941 innerTrkParticleIdVertexSecondary.push_back(barcode.vertexSecondary());
0942 innerTrkParticleIdParticle.push_back(barcode.particle());
0943 innerTrkParticleIdGeneration.push_back(barcode.generation());
0944 innerTrkParticleIdSubParticle.push_back(barcode.subParticle());
0945
0946 trueUnitDir = particle->direction();
0947 trueMom.head<2>() = Acts::makePhiThetaFromDirection(trueUnitDir);
0948 trueMom[2] = particle->qOverP();
0949
0950 innerTruthPhi.push_back(trueMom[0]);
0951 innerTruthTheta.push_back(trueMom[1]);
0952 innerTruthQOverP.push_back(trueMom[2]);
0953 } else {
0954 ACTS_VERBOSE("Track has no matching truth particle.");
0955
0956 innerTrkParticleIdVertexPrimary.push_back(0);
0957 innerTrkParticleIdVertexSecondary.push_back(0);
0958 innerTrkParticleIdParticle.push_back(0);
0959 innerTrkParticleIdGeneration.push_back(0);
0960 innerTrkParticleIdSubParticle.push_back(0);
0961
0962 innerTruthPhi.push_back(nan);
0963 innerTruthTheta.push_back(nan);
0964 innerTruthQOverP.push_back(nan);
0965 }
0966
0967
0968 const auto paramsAtVtx =
0969 propagateToVtx(*(trk.originalParams.as<Acts::BoundTrackParameters>()));
0970 if (paramsAtVtx.has_value()) {
0971 Acts::Vector3 recoMom =
0972 paramsAtVtx->parameters().segment(Acts::eBoundPhi, 3);
0973 const Acts::SquareMatrix3& momCov =
0974 paramsAtVtx->covariance()->template block<3, 3>(Acts::eBoundPhi,
0975 Acts::eBoundPhi);
0976 innerRecoPhi.push_back(recoMom[0]);
0977 innerRecoTheta.push_back(recoMom[1]);
0978 innerRecoQOverP.push_back(recoMom[2]);
0979
0980 if (particle != nullptr) {
0981 Acts::Vector3 diffMom = recoMom - trueMom;
0982
0983
0984 diffMom[0] = Acts::detail::difference_periodic(recoMom(0), trueMom(0),
0985 2 * std::numbers::pi);
0986 innerResPhi.push_back(diffMom[0]);
0987 innerResTheta.push_back(diffMom[1]);
0988 innerResQOverP.push_back(diffMom[2]);
0989
0990 innerPullPhi.push_back(
0991 pull(diffMom[0], momCov(0, 0), "phi", false, logger()));
0992 innerPullTheta.push_back(
0993 pull(diffMom[1], momCov(1, 1), "theta", false, logger()));
0994 innerPullQOverP.push_back(
0995 pull(diffMom[2], momCov(2, 2), "q/p", false, logger()));
0996
0997 const auto& recoUnitDir = paramsAtVtx->direction();
0998 double overlap = trueUnitDir.dot(recoUnitDir);
0999 innerMomOverlap.push_back(overlap);
1000 } else {
1001 innerResPhi.push_back(nan);
1002 innerResTheta.push_back(nan);
1003 innerResQOverP.push_back(nan);
1004 innerPullPhi.push_back(nan);
1005 innerPullTheta.push_back(nan);
1006 innerPullQOverP.push_back(nan);
1007 innerMomOverlap.push_back(nan);
1008 }
1009 } else {
1010 innerRecoPhi.push_back(nan);
1011 innerRecoTheta.push_back(nan);
1012 innerRecoQOverP.push_back(nan);
1013 innerResPhi.push_back(nan);
1014 innerResTheta.push_back(nan);
1015 innerResQOverP.push_back(nan);
1016 innerPullPhi.push_back(nan);
1017 innerPullTheta.push_back(nan);
1018 innerPullQOverP.push_back(nan);
1019 innerMomOverlap.push_back(nan);
1020 }
1021
1022
1023 const auto paramsAtVtxFitted = propagateToVtx(trk.fittedParams);
1024 if (paramsAtVtxFitted.has_value()) {
1025 Acts::Vector3 recoMomFitted =
1026 paramsAtVtxFitted->parameters().segment(Acts::eBoundPhi, 3);
1027 const Acts::SquareMatrix3& momCovFitted =
1028 paramsAtVtxFitted->covariance()->block<3, 3>(Acts::eBoundPhi,
1029 Acts::eBoundPhi);
1030 innerRecoPhiFitted.push_back(recoMomFitted[0]);
1031 innerRecoThetaFitted.push_back(recoMomFitted[1]);
1032 innerRecoQOverPFitted.push_back(recoMomFitted[2]);
1033
1034 if (particle != nullptr) {
1035 Acts::Vector3 diffMomFitted = recoMomFitted - trueMom;
1036
1037
1038 diffMomFitted[0] = Acts::detail::difference_periodic(
1039 recoMomFitted(0), trueMom(0), 2 * std::numbers::pi);
1040 innerResPhiFitted.push_back(diffMomFitted[0]);
1041 innerResThetaFitted.push_back(diffMomFitted[1]);
1042 innerResQOverPFitted.push_back(diffMomFitted[2]);
1043
1044 innerPullPhiFitted.push_back(
1045 pull(diffMomFitted[0], momCovFitted(0, 0), "phi", true, logger()));
1046 innerPullThetaFitted.push_back(pull(
1047 diffMomFitted[1], momCovFitted(1, 1), "theta", true, logger()));
1048 innerPullQOverPFitted.push_back(
1049 pull(diffMomFitted[2], momCovFitted(2, 2), "q/p", true, logger()));
1050
1051 const auto& recoUnitDirFitted = paramsAtVtxFitted->direction();
1052 double overlapFitted = trueUnitDir.dot(recoUnitDirFitted);
1053 innerMomOverlapFitted.push_back(overlapFitted);
1054 } else {
1055 innerResPhiFitted.push_back(nan);
1056 innerResThetaFitted.push_back(nan);
1057 innerResQOverPFitted.push_back(nan);
1058 innerPullPhiFitted.push_back(nan);
1059 innerPullThetaFitted.push_back(nan);
1060 innerPullQOverPFitted.push_back(nan);
1061 innerMomOverlapFitted.push_back(nan);
1062 }
1063 } else {
1064 innerRecoPhiFitted.push_back(nan);
1065 innerRecoThetaFitted.push_back(nan);
1066 innerRecoQOverPFitted.push_back(nan);
1067 innerResPhiFitted.push_back(nan);
1068 innerResThetaFitted.push_back(nan);
1069 innerResQOverPFitted.push_back(nan);
1070 innerPullPhiFitted.push_back(nan);
1071 innerPullThetaFitted.push_back(nan);
1072 innerPullQOverPFitted.push_back(nan);
1073 innerMomOverlapFitted.push_back(nan);
1074 }
1075 }
1076 }
1077
1078 }