ATLAS Offline Software
Loading...
Searching...
No Matches
ActsEgammaSelectedTrackCopy.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3 */
4
6//
10#include "GaudiKernel/EventContext.h"
19#include "Acts/Geometry/TrackingGeometry.hpp"
20#include "Acts/Surfaces/CurvilinearSurface.hpp"
21#include "Acts/Surfaces/PerigeeSurface.hpp"
22#include "Acts/Surfaces/PlaneSurface.hpp"
23#include "Acts/Utilities/Zip.hpp"
24// std includes
25#include <algorithm>
26#include <cmath>
27#include <memory>
28#include <vector>
29
31
33 ISvcLocator* pSvcLocator)
34 : AthReentrantAlgorithm(name, pSvcLocator)
35{}
36
37StatusCode
39{
41 ATH_CHECK(m_clusterContainerKey.initialize());
44 ATH_CHECK(m_caloDetDescrMgrKey.initialize());
45 ATH_CHECK(m_extrapolationTool->initialize());
47
48 // Here we extract the geometry identifiers of the 4 calo volumes in the ACTS geometry
49 // It seems more robust to do the matching with the volume name, since the geometry ID
50 // can change if details of the geometry change.
51 const static std::map<std::string, std::size_t> volIndex = {
52 {"PreSamplerB_Layer", 0},
53 {"EMB1_Layer", 1},
54 {"EMB2_Layer", 2},
55 {"EMB3_Layer", 3}
56 };
57
58 // Debug info in case volume matching fails
59 std::vector<std::string> unmatchedVolumes;
60
61 m_trackingGeometryTool->trackingGeometry()->visitVolumes([&](const Acts::TrackingVolume *vol) {
62 auto name = vol->volumeName();
63 if( volIndex.contains(name) ) {
64 ATH_MSG_DEBUG(vol->volumeName() << " - " << vol->geometryId() << " - surfaces: " << vol->surfaces().size());
65 m_barrelCaloGeoIds.at(volIndex.at(name)) = vol->geometryId();
66 } else {
67 unmatchedVolumes.push_back(name);
68 }
69 });
70
71 // Check that all geoIds were found
72 for(const auto &[geoId, layerInfo] : Acts::zip(m_barrelCaloGeoIds, volIndex)) {
73 if( geoId == Acts::GeometryIdentifier{} ) {
74 ATH_MSG_ERROR("Could not find geometry ID for barrel calo layer '" << layerInfo.first << "'");
75
76 ATH_MSG_ERROR("List of unmatched volumes:");
77 std::stringstream ss;
78 for( const auto &name : unmatchedVolumes ) {
79 ss << name << ", ";
80 }
81 ATH_MSG_ERROR(ss.str());
82
83 return StatusCode::FAILURE;
84 }
85 }
86
87 return StatusCode::SUCCESS;
88}
89
90StatusCode
92{
93 ATH_MSG_INFO("===> ACTS egamma Selected Tracks Statistics ======");
94 ATH_MSG_INFO("--- All Central Clusters: " << m_AllClusters);
95 ATH_MSG_INFO("--- Selected Central Clusters: " << m_SelectedClusters);
96 ATH_MSG_INFO("--- All Tracks: " << m_AllTracks);
97 ATH_MSG_INFO("--- Selected Central Tracks: " << m_SelectedTracks);
98 ATH_MSG_INFO("<=================================================");
99
100 return StatusCode::SUCCESS;
101}
102
103StatusCode
104ActsEgammaSelectedTrackCopy::execute(const EventContext& ctx) const
105{
107 ATH_CHECK(clusterTES.isValid());
108 m_AllClusters += clusterTES->size();
109
111 ATH_CHECK(trackTES.isValid());
112 m_AllTracks += trackTES->size();
113
114 // Here it just needs to be a view copy , i.e the collection of selected
115 // trackParticles we create does not really own its elements.
116 using output_collection_t = ConstDataVector<xAOD::TrackParticleContainer>;
118 ATH_CHECK( outputTrkPartContainer.record( std::make_unique<output_collection_t>(SG::VIEW_ELEMENTS) ) );
119 output_collection_t *viewCopy = outputTrkPartContainer.ptr();
120
122 ATH_CHECK(caloDetDescrMgrHandle.isValid());
123
124 const CaloDetDescrManager* calodetdescrmgr = caloDetDescrMgrHandle.cptr();
125
126 // Perigee surface at the origin. TODO: use beamspot conditions data.
127 auto perigeeSurface = Acts::Surface::makeShared<Acts::PerigeeSurface>(Acts::Vector3::Zero());
128
129 // Check which clusters to seed on.
130 std::vector<const xAOD::CaloCluster*> passingClusters;
131 passingClusters.reserve(clusterTES->size());
132 for (const xAOD::CaloCluster* cluster : *clusterTES) {
133 if (m_egammaCaloClusterSelector->passSelection(cluster, *calodetdescrmgr)) {
134 passingClusters.push_back(cluster);
135 }
136 }
137
138 if( passingClusters.empty() ) {
139 ATH_MSG_DEBUG("No cluster left after selection");
140 return StatusCode::SUCCESS;
141 }
142
143 m_SelectedClusters += passingClusters.size();
144
145 for (const xAOD::TrackParticle* track : *trackTES) {
146 // For now only barrel
147 if( std::abs(track->eta()) > s_calorimeterEtaCoverage ) {
148 ATH_MSG_DEBUG("Track eta " << track->eta() << " outside coverage " <<
149 "|" << s_calorimeterEtaCoverage << "|");
150 continue;
151 }
152
153
154 for (const xAOD::CaloCluster* cluster : passingClusters) {
155 // First we will see if it fails the quick match.
156 // Then if it passed it will get 2 chances to be selected.
157 // One if it matches from last measurement.
158 // The second if it matched from Perigee rescales.
159 if( !checkBroadCriteria(*cluster, *track) ) {
160 ATH_MSG_DEBUG("Track fails broad criteria");
161 continue;
162 }
163
164 if (matchWithExtrapolation(ctx, *cluster, *track, perigeeSurface)) {
165 viewCopy->push_back(track);
166 break;
167 }
168 }
169 }
170
171 m_SelectedTracks += viewCopy->size();
172
173 return StatusCode::SUCCESS;
174}
175
176bool
178 const xAOD::CaloCluster& cluster,
179 const xAOD::TrackParticle& track,
180 const std::shared_ptr<const Acts::Surface>& perigeeSurface) const
181{
182 using namespace Acts::UnitLiterals;
183
184 ATH_MSG_VERBOSE("Extrapolating track to calo cluster at position: "
185 << cluster.eta() << ", " << cluster.phi());
186
187 // Extrapolate from last measurement to the four EM layers.
188 unsigned int lastMeasIdx = 0;
189 if (!track.indexOfParameterAtPosition(lastMeasIdx, xAOD::LastMeasurement)) {
190 ATH_MSG_WARNING("TrackParticle has no last measurement parameters");
191 return false;
192 }
193
194 Acts::Vector3 lastPos{track.parameterX(lastMeasIdx),
195 track.parameterY(lastMeasIdx),
196 track.parameterZ(lastMeasIdx)};
197 Acts::Vector3 lastMom{track.parameterPX(lastMeasIdx),
198 track.parameterPY(lastMeasIdx),
199 track.parameterPZ(lastMeasIdx)};
200 lastMom *= Acts::UnitConstants::MeV;
201
202 ATH_MSG_DEBUG("Last measurement global position: " << lastPos.transpose());
203
204 Acts::BoundVector lastBoundParams = Acts::BoundVector::Zero();
205 lastBoundParams[Acts::eBoundPhi] = Acts::VectorHelpers::phi(lastMom);
206 lastBoundParams[Acts::eBoundTheta] = Acts::VectorHelpers::theta(lastMom);
207 lastBoundParams[Acts::eBoundQOverP] = track.charge() / lastMom.norm();
208
209 std::shared_ptr<const Acts::Surface> lastSurface =
210 Acts::CurvilinearSurface(lastPos, lastMom.normalized()).planeSurface()->getSharedPtr();
211 Acts::BoundTrackParameters boundPars{
212 lastSurface,
213 lastBoundParams,
214 std::nullopt,
215 Acts::ParticleHypothesis::electron()
216 };
217
218 // First try the EMB2, if this does not match, print a message and try any other calo layer
219 auto extractCaloMatch = [&](const auto &caloMatches) {
220 if( caloMatches.at(2).has_value() ) {
221 return caloMatches.at(2);;
222 }
223 ATH_MSG_DEBUG("No calo match at EM2, trying any other layer");
224 for( const auto &matchOpt : caloMatches ) {
225 if( matchOpt.has_value() ) {
226 ATH_MSG_DEBUG("Using calo match at layer " << matchOpt->layer);
227 return matchOpt;
228 }
229 }
230 return std::optional<CaloMatch>{};
231 };
232
233 auto caloMatches = extrapolateToCalo(boundPars, cluster, ctx);
234 auto caloMatch = extractCaloMatch(caloMatches);
235 if( !caloMatch.has_value() ) {
236 ATH_MSG_DEBUG("Could not extrapolate to calo from last measurement");
237 return false;
238 }
239
240 // Get the calo match at EM2.
241 ATH_MSG_DEBUG("Calo match from last measurement at " << caloMatch->layer
242 << ", dEta: " << caloMatch->deltaEta
243 << ", dPhi: " << caloMatch->deltaPhi);
244
245 // First check if delta eta is OK, fail if not (rescaling should not affect eta).
246 if (std::abs(caloMatch->deltaEta) > m_narrowDeltaEta) {
247 ATH_MSG_DEBUG("Fails narrow window eta match, deta=" << caloMatch->deltaEta);
248 return false;
249 }
250
251 // Selection in narrow phi window from last measurement.
252 if ( caloMatch->deltaPhi >= -m_narrowDeltaPhiBrem &&
253 caloMatch->deltaPhi <= m_narrowDeltaPhi ) {
254 ATH_MSG_DEBUG("Match from Last measurement is successful, deta=" << caloMatch->deltaEta
255 << " , dphi=" << caloMatch->deltaPhi);
256 return true;
257 }
258
259 // Now try the rescale from perigee
260 Acts::BoundVector paramsAtPerigee = Acts::BoundVector::Zero();
261 paramsAtPerigee[Acts::eBoundLoc0] = track.d0();
262 paramsAtPerigee[Acts::eBoundLoc1] = track.z0();
263 paramsAtPerigee[Acts::eBoundPhi] = track.phi0();
264 paramsAtPerigee[Acts::eBoundTheta] = track.theta();
265 paramsAtPerigee[Acts::eBoundQOverP] = track.charge() / (cluster.e() * Acts::UnitConstants::MeV);
266
267 Acts::BoundTrackParameters rescaledParsPerigee{
268 perigeeSurface,
269 paramsAtPerigee,
270 std::nullopt,
271 Acts::ParticleHypothesis::electron()
272 };
273
274 const auto caloMatchesRescaled = extrapolateToCalo(rescaledParsPerigee, cluster, ctx);
275 auto caloMatchRescaled = extractCaloMatch(caloMatchesRescaled);
276
277 if( !caloMatchRescaled.has_value() ) {
278 ATH_MSG_DEBUG("Could not extrapolate to calo from rescaled perigee");
279 return false;
280 }
281
282 // Get the calo match at EM2.
283 ATH_MSG_DEBUG("Calo match from perigee rescaled at " << caloMatchRescaled->layer
284 << ", dEta: " << caloMatchRescaled->deltaEta
285 << ", dPhi: " << caloMatchRescaled->deltaPhi);
286
287 // Selection in narrow phi window
288 if ( caloMatchRescaled->deltaPhi >= -m_narrowRescaleBrem &&
289 caloMatchRescaled->deltaPhi <= m_narrowRescale ) {
290 ATH_MSG_DEBUG("Match from Perigee Rescaled is successful, dphi=" << caloMatchRescaled->deltaPhi);
291 return true;
292 }
293
294 ATH_MSG_DEBUG("Match not successful");
295 return false;
296}
297
298bool
300 const xAOD::TrackParticle& track) const
301{
302 const Trk::Perigee& perigee = track.perigeeParameters();
303
304 // Get Perigee Parameters.
305 const double trkPhi = perigee.parameters()[Trk::phi];
306 const double z_perigee = perigee.position().z();
307 const Amg::Vector3D PerigeeXYZPosition(perigee.position().x(),
308 perigee.position().y(),
309 z_perigee);
310
311 // Get Cluster parameters.
312 const double clusterEta = xAOD::EgammaHelpers::isFCAL(&cluster) ? cluster.eta() : cluster.etaBE(2);
313 const bool isEndCap = !xAOD::EgammaHelpers::isBarrel(&cluster);
314
315 // Use perigee.eta() only if sufficient hits in the Si.
316 const double Et = cluster.e() / std::cosh(perigee.eta());
317
318 ATH_MSG_VERBOSE("clusterEta: " << clusterEta);
319 ATH_MSG_VERBOSE("perigee.eta(): " << perigee.eta());
320 ATH_MSG_VERBOSE("Et: " << Et);
321 ATH_MSG_VERBOSE("isEndCap: " << std::boolalpha << isEndCap);
322
323 // Calculate the eta/phi of the cluster as would be seen from the perigee
324 // position of the Track.
325 const Amg::Vector3D globalClusterPosWrtPerigee = CandidateMatchHelpers::approxXYZwrtPoint(
326 cluster, perigee.position(), isEndCap);
327
328 // Calculate the possible rotation of the track.
329 // Once assuming the cluster Et being the better estimate (e.g big brem).
330 const double phiRotRescaled = CandidateMatchHelpers::PhiROT(
331 Et, perigee.eta(), track.charge(), perigee.position().perp(), isEndCap);
332
333 // And also assuming the track Pt being correct.
334 const double phiRotTrack = CandidateMatchHelpers::PhiROT(
335 track.pt(), perigee.eta(), track.charge(), perigee.position().perp(), isEndCap);
336
337 const double clusterPhiCorrected = globalClusterPosWrtPerigee.phi();
338
339 // DeltaPhi between the track and the cluster.
340 const double deltaPhiStd = P4Helpers::deltaPhi(clusterPhiCorrected, trkPhi);
341 const bool failPhiStd = std::abs(deltaPhiStd) > m_broadDeltaPhi;
342
343 // DeltaPhi between the track and the cluster accounting for rotation assuming
344 // cluster Et is a better estimator.
345 const double trkPhiRescaled = P4Helpers::deltaPhi(trkPhi, phiRotRescaled);
346 const double deltaPhiRescaled = P4Helpers::deltaPhi(clusterPhiCorrected, trkPhiRescaled);
347 const bool failPhiRescalded = std::abs(deltaPhiRescaled) > m_broadDeltaPhi;
348
349 // DeltaPhi between the track and the cluster accounting for rotation.
350 const double trkPhiCorrTrack = P4Helpers::deltaPhi(trkPhi, phiRotTrack);
351 const double deltaPhiTrack = P4Helpers::deltaPhi(clusterPhiCorrected, trkPhiCorrTrack);
352 const bool failPhiTrack = std::abs(deltaPhiTrack) > m_broadDeltaPhi;
353
354 // Broad phi check.
355 if (failPhiRescalded && failPhiTrack && failPhiStd) {
357 "FAILS broad window phi match (track phi, phirotCluster , phiRotTrack , " <<
358 "cluster phi corrected, cluster phi): ( " <<
359 trkPhi << ", " <<
360 phiRotRescaled << ", " <<
361 phiRotTrack << ", " <<
362 clusterPhiCorrected << ", " <<
363 cluster.phi() << ")"
364 );
365 return false;
366 }
367
368 // Broad eta check.
369 const double clusterEtaCorrected = globalClusterPosWrtPerigee.eta();
370 const bool failEta = std::abs(cluster.eta() - perigee.eta()) > m_broadDeltaEta;
371 const bool failEtaCorrected = std::abs(globalClusterPosWrtPerigee.eta() - perigee.eta()) > m_broadDeltaEta;
372
373 if (failEta && failEtaCorrected) {
375 "FAILS broad window eta match (track eta, cluster eta, cluster eta corrected): ( " <<
376 perigee.eta() << ", " <<
377 cluster.eta() << ", " <<
378 clusterEtaCorrected << " )"
379 );
380 return false;
381 }
382
383 return true;
384}
385
386std::array<std::optional<ActsEgammaSelectedTrackCopy::CaloMatch>, 4> ActsEgammaSelectedTrackCopy::extrapolateToCalo(
387 const Acts::BoundTrackParameters &parameters, const xAOD::CaloCluster& cluster,
388 const EventContext& ctx) const
389{
390 auto res = m_extrapolationTool.get()->propagationSteps(ctx, parameters);
391 if( !res.ok() ) {
392 ATH_MSG_DEBUG("Error during extrapolation: " << res.error().message());
393 return {};
394 }
395
396 const auto &[steps, _] = res.value();
397
398 std::array<std::optional<CaloMatch>, 4> matches{};
399 for(const auto &step : steps) {
400 if( step.surface == nullptr || step.surface->geometryId().sensitive() == 0 ) {
401 continue;
402 }
403 const auto &p = step.position;
404 if ( std::hypot(p.x(), p.y()) > s_maxExtrapolationRadius ) {
405 ATH_MSG_DEBUG("Extrapolated beyond calo radius, stopping.");
406 break;
407 }
408
409 ATH_MSG_VERBOSE("Extrapolated step at pos [r,z]: " << std::hypot(p.x(), p.y())
410 << ", " << p.z() << " - : " << step.geoID);
411
412 auto found = std::ranges::find_if(m_barrelCaloGeoIds, [&](const Acts::GeometryIdentifier &id){
413 return id.volume() == step.geoID.volume();
414 });
415
416 if( found == m_barrelCaloGeoIds.end() ) {
417 ATH_MSG_VERBOSE("Step not in barrel calo layers");
418 continue;
419 }
420 int layerIdx = std::distance(m_barrelCaloGeoIds.begin(), found);
421 assert(layerIdx >= 0 && layerIdx < 4);
422
423 if( matches.at(layerIdx).has_value() ) {
424 ATH_MSG_VERBOSE("Step in calo layer already visited");
425 continue;
426 }
427
428 auto eta = Acts::VectorHelpers::eta(p);
429 auto phi = Acts::VectorHelpers::phi(p);
430
431 double deltaEta = eta - cluster.etaBE(layerIdx);
432 double deltaPhi = P4Helpers::deltaPhi(phi, cluster.phiBE(layerIdx));
433
434 ATH_MSG_DEBUG("Step at pos [r,z]: " << std::hypot(p.x(), p.y()) <<
435 ", layerIdx: " << layerIdx <<
436 ", " << p.z() << " - : " << step.geoID <<
437 ", dEta: " << deltaEta << ", dPhi: " << deltaPhi);
438
439 matches.at(layerIdx) = CaloMatch{
440 layerIdx,
441 eta,
442 phi,
443 deltaEta,
445 };
446 }
447
448 return matches;
449}
450
Scalar eta() const
pseudorapidity method
Scalar deltaPhi(const MatrixBase< Derived > &vec) const
Scalar phi() const
phi method
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_ERROR(x)
#define ATH_MSG_INFO(x)
#define ATH_MSG_VERBOSE(x)
#define ATH_MSG_WARNING(x)
#define ATH_MSG_DEBUG(x)
Definition of CaloDetDescrManager.
Helpers for checking error return status codes and reporting errors.
std::pair< std::vector< unsigned int >, bool > res
static Double_t ss
Handle class for reading from StoreGate.
Handle class for recording to StoreGate.
Gaudi::Accumulators::Counter m_AllClusters
virtual StatusCode finalize() override final
virtual StatusCode initialize() override final
bool checkBroadCriteria(const xAOD::CaloCluster &cluster, const xAOD::TrackParticle &track) const
ToolHandle< ActsTrk::IExtrapolationTool > m_extrapolationTool
SG::ReadHandleKey< xAOD::TrackParticleContainer > m_trackParticleContainerKey
virtual StatusCode execute(const EventContext &ctx) const override final
static constexpr double s_calorimeterEtaCoverage
static constexpr double s_maxExtrapolationRadius
PublicToolHandle< ActsTrk::ITrackingGeometryTool > m_trackingGeometryTool
Gaudi::Property< double > m_narrowDeltaEta
Narrow windows.
bool matchWithExtrapolation(const EventContext &ctx, const xAOD::CaloCluster &cluster, const xAOD::TrackParticle &track, const std::shared_ptr< const Acts::Surface > &perigeeSurface) const
Track selection method.
Gaudi::Property< double > m_broadDeltaPhi
Gaudi::Accumulators::Counter m_SelectedClusters
Gaudi::Property< double > m_narrowDeltaPhi
SG::WriteHandleKey< ConstDataVector< xAOD::TrackParticleContainer > > m_OutputTrkPartContainerKey
std::array< Acts::GeometryIdentifier, 4 > m_barrelCaloGeoIds
SG::ReadHandleKey< xAOD::CaloClusterContainer > m_clusterContainerKey
Names of input output collections.
Gaudi::Property< double > m_narrowDeltaPhiBrem
Gaudi::Accumulators::Counter m_SelectedTracks
Gaudi::Property< double > m_narrowRescale
std::array< std::optional< CaloMatch >, 4 > extrapolateToCalo(const Acts::BoundTrackParameters &parameters, const xAOD::CaloCluster &cluster, const EventContext &ctx) const
SG::ReadCondHandleKey< CaloDetDescrManager > m_caloDetDescrMgrKey
Gaudi::Property< double > m_broadDeltaEta
Broad windows.
Gaudi::Accumulators::Counter m_AllTracks
Gaudi::Property< double > m_narrowRescaleBrem
ActsEgammaSelectedTrackCopy(const std::string &name, ISvcLocator *pSvcLocator)
Default constructor.
ToolHandle< IegammaCaloClusterSelector > m_egammaCaloClusterSelector
Tool to filter the calo clusters.
An algorithm that can be simultaneously executed in multiple threads.
This class provides the client interface for accessing the detector description information common to...
DataVector adapter that acts like it holds const pointers.
const_pointer_type cptr()
virtual bool isValid() override final
Can the handle be successfully dereferenced?
StatusCode record(std::unique_ptr< T > data)
Record a const object to the store.
pointer_type ptr()
Dereference the pointer.
double eta() const
Access method for pseudorapidity - from momentum.
const Amg::Vector3D & position() const
Access method for the position.
float phiBE(const unsigned layer) const
Get the phi in one layer of the EM Calo.
virtual double eta() const
The pseudorapidity ( ) of the particle.
virtual double e() const
The total energy of the particle.
virtual double phi() const
The azimuthal angle ( ) of the particle.
float etaBE(const unsigned layer) const
Get the eta in one layer of the EM Calo.
Eigen::Matrix< double, 3, 1 > Vector3D
double PhiROT(const double pt, const double eta, const int charge, const double r_start, const bool isEndCap)
Function to calculate the approximate rotation in phi/bending of a track until it reaches the calo.
Amg::Vector3D approxXYZwrtPoint(const xAOD::CaloCluster &cluster, const Amg::Vector3D &point, const bool isEndCap)
Function to get the (x,y,z) of the cluster wrt to a point (x0,y0,z0).
double deltaPhi(double phiA, double phiB)
delta Phi in range [-pi,pi[
Definition P4Helpers.h:34
@ VIEW_ELEMENTS
this data object is a view, it does not own its elmts
SG::ReadCondHandle< T > makeHandle(const SG::ReadCondHandleKey< T > &key, const EventContext &ctx=Gaudi::Hive::currentContext())
ParametersT< TrackParametersDim, Charged, PerigeeSurface > Perigee
@ phi
Definition ParamDefs.h:75
bool isBarrel(const xAOD::Egamma *eg)
return true if the cluster is in the barrel
bool isFCAL(const xAOD::CaloCluster *cluster)
return true if the cluster (or the majority of its energy) is in the FCAL0
int summaryValueInt(const xAOD::TrackParticle &tp, const xAOD::SummaryType &info, int deflt=-999)
return the summary value for a TrackParticle or default value (-999) (to be used mostly in python whe...
CaloCluster_v1 CaloCluster
Define the latest version of the calorimeter cluster class.
TrackParticle_v1 TrackParticle
Reference the current persistent version:
@ LastMeasurement
Parameter defined at the position of the last measurement.