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