ATLAS Offline Software
Loading...
Searching...
No Matches
TrackParamsEstimationTool.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3*/
4
8#include "Acts/Seeding/EstimateTrackParamsFromSeed.hpp"
9#include "Acts/SpacePointFormation/StripSpacePointCalibration.hpp"
10#include "Acts/EventData/StripSpacePointCalibrationDetails.hpp"
11#include "Acts/Utilities/MathHelpers.hpp"
12#include "Acts/Utilities/Result.hpp"
13#include "Acts/Utilities/VectorHelpers.hpp"
14
15#include <algorithm>
16#include <cmath>
17#include <ranges>
18#include <span>
19#include <vector>
20
21namespace ActsTrk {
22
23namespace {
24
25bool isStripSpacePoint(const xAOD::SpacePoint* sp) {
26 return sp->elementIdList().size() > 1;
27}
28
29// Move the strip space points along their strips, so that they match the track
30// tangents at their positions. The pixel space points do not change.
31void calibrateStripSpacePoints(
32 std::span<const xAOD::SpacePoint* const> spacePoints,
33 std::span<Acts::Vector3> spPositions,
34 std::span<const Acts::Vector3> spTangents) {
35 for (std::size_t j = 0; j < spacePoints.size(); ++j) {
37 if (!isStripSpacePoint(sp)) {
38 continue;
39 }
40
41 Acts::OuterStripSpacePointCalibrationDetails calibrationDetails;
42 Eigen::Map<Eigen::Vector3f>(calibrationDetails.outerCenter.data()) = sp->topStripCenter();
43 Eigen::Map<Eigen::Vector3f>(calibrationDetails.innerToOuterSeparation.data()) = sp->stripCenterDistance();
44 Eigen::Map<Eigen::Vector3f>(calibrationDetails.outerHalfVector.data()) = sp->topHalfStripLength() * sp->topStripDirection();
45 Eigen::Map<Eigen::Vector3f>(calibrationDetails.innerHalfVector.data()) = sp->bottomHalfStripLength() * sp->bottomStripDirection();
46 const Acts::OuterStripSpacePointCalibrationDetailsDerived derivedCalibrationDetails =
47 Acts::deriveOuterStripSpacePointCalibrationDetails(calibrationDetails);
48
49 const std::optional<Eigen::Vector3f> calibratedPosition =
50 Acts::calibrateOuterStripSpacePoint(spTangents[j].cast<float>(), derivedCalibrationDetails);
51 if (!calibratedPosition.has_value()) {
52 continue;
53 }
54 spPositions[j] = calibratedPosition->cast<double>();
55 }
56}
57
58template <typename sp_range_t>
59Acts::FreeVector estimateTrackParamsFromSeed(
60 const sp_range_t& spRange,
61 const Acts::Vector3& bField,
62 const std::size_t stripCalibrationIterations) {
63 std::array<const xAOD::SpacePoint*, 3> spArray{};
64 std::array<Acts::Vector3, 3> spPositions{};
65
66 std::size_t i = 0;
67 for (const auto* sp : spRange) {
68 if (sp == nullptr) {
69 throw std::invalid_argument("Empty space point found.");
70 }
71 if (i >= spArray.size()) {
72 throw std::invalid_argument("More than 3 space points provided.");
73 }
74 spArray[i] = sp;
75 spPositions[i] = Acts::Vector3(sp->x(), sp->y(), sp->z());
76 ++i;
77 }
78 if (i < spArray.size()) {
79 throw std::invalid_argument("Less than 3 space points provided.");
80 }
81
82 if (std::ranges::any_of(spArray, isStripSpacePoint)) {
83 std::array<Acts::Vector3, 3> spTangents{};
84
85 for (std::size_t i = 0; i < stripCalibrationIterations; ++i) {
86 Acts::estimateTrackParamsFromSeed(
87 spPositions[0], 0, spPositions[1], spPositions[2], bField,
88 &spTangents[0], &spTangents[1], &spTangents[2]);
89
90 calibrateStripSpacePoints(spArray, spPositions, spTangents);
91 }
92 }
93
94 return Acts::estimateTrackParamsFromSeed(
95 spPositions[0], 0, spPositions[1], spPositions[2], bField);
96}
97
98// Fit a helix through all space points of the range. The fit of three space
99// points is exact, so the triplet estimate is used for them.
100template <typename sp_range_t>
101Acts::Result<Acts::FreeVector> estimateTrackParamsFromAllSpacePoints(
102 const sp_range_t& spRange,
103 const Acts::Vector3& bField,
104 const std::size_t stripCalibrationIterations,
105 const std::size_t geometricRefineIterations,
106 const double weightExponent) {
107 std::vector<const xAOD::SpacePoint*> spacePoints;
108 std::vector<Acts::Vector3> spPositions;
109 for (const auto* sp : spRange) {
110 if (sp == nullptr) {
111 throw std::invalid_argument("Empty space point found.");
112 }
113 spacePoints.push_back(sp);
114 spPositions.emplace_back(sp->x(), sp->y(), sp->z());
115 }
116
117 if (spacePoints.size() == 3) {
118 return Acts::Result<Acts::FreeVector>::success(
119 estimateTrackParamsFromSeed(spacePoints, bField, stripCalibrationIterations));
120 }
121
122 // An empty span gives uniform weights
123 std::vector<double> weights;
124 if (weightExponent != 0) {
125 weights.reserve(spPositions.size());
126 for (const Acts::Vector3& position : spPositions) {
127 const double r = Acts::VectorHelpers::perp(position);
128 weights.push_back(r > 0 ? std::pow(r, -weightExponent) : 1.);
129 }
130 }
131
132 const auto fit = [&](std::size_t referenceIndex) {
133 return Acts::estimateTrackParamsFromSpacePoints(
134 spPositions, bField, 0, geometricRefineIterations, weights, referenceIndex);
135 };
136
137 if (std::ranges::any_of(spacePoints, isStripSpacePoint)) {
138 std::vector<Acts::Vector3> spTangents(spacePoints.size(), Acts::Vector3::Zero());
139
140 for (std::size_t i = 0; i < stripCalibrationIterations; ++i) {
141 // The fitted helix does not depend on the reference index, so the
142 // direction at the reference is the tangent at that space point
143 for (std::size_t j = 0; j < spacePoints.size(); ++j) {
144 if (!isStripSpacePoint(spacePoints[j])) {
145 continue;
146 }
147 const Acts::Result<Acts::FreeVector> tangentParams = fit(j);
148 if (!tangentParams.ok()) {
149 return Acts::Result<Acts::FreeVector>::failure(tangentParams.error());
150 }
151 spTangents[j] = tangentParams->segment<3>(Acts::eFreeDir0);
152 }
153
154 calibrateStripSpacePoints(spacePoints, spPositions, spTangents);
155 }
156 }
157
158 return fit(0);
159}
160
161}
162
164 const std::string& name,
165 const IInterface* parent)
166 : base_class(type, name, parent)
167 {}
168
170 {
171 ATH_MSG_INFO( "Initializing " << name() << "..." );
172
173 ATH_MSG_DEBUG( "Properties Summary:" );
174 ATH_MSG_DEBUG( " " << m_sigmaLoc0 );
175 ATH_MSG_DEBUG( " " << m_sigmaLoc1 );
176 ATH_MSG_DEBUG( " " << m_sigmaPhi );
177 ATH_MSG_DEBUG( " " << m_sigmaTheta );
179 ATH_MSG_DEBUG( " " << m_sigmaT0 );
181 ATH_MSG_DEBUG( " " << m_bFieldMode );
182 ATH_MSG_DEBUG( " " << m_firstSp );
183 ATH_MSG_DEBUG( " " << m_minDeltaR );
187 ATH_MSG_DEBUG( " " << m_refitSeeds );
188
189 if (m_bFieldMode != 0 && m_bFieldMode != 1) {
190 ATH_MSG_FATAL("bFieldMode must be 0 or 1, but is " << m_bFieldMode.value());
191 return StatusCode::FAILURE;
192 }
193
194 ATH_CHECK(m_fitterTool.retrieve(EnableTool{m_refitSeeds}));
195
196 m_logger = makeActsAthenaLogger(this, "Acts");
197
198 m_extrapolator = Extrapolator(Stepper(std::make_shared<ATLASMagneticFieldWrapper>()), Navigator(), logger().cloneWithSuffix("Prop"));
199
201
202 if (m_refitSeeds) {
205 }
206
207 return StatusCode::SUCCESS;
208 }
209
210 std::pair<std::optional<Acts::BoundTrackParameters>, TrackParamsEstimationTool::EstimationStatus>
212 const ActsTrk::Seed& seed,
213 const Acts::GeometryContext& geoContext,
214 const Acts::MagneticFieldContext& magFieldContext,
215 const Acts::CalibrationContext& calContext,
216 std::function<const Acts::Surface&(const ActsTrk::Seed& seed)> retrieveSurface) const
217 {
218 const auto& sp_collection = seed.sp();
219 if ( sp_collection.size() < 3 ) return {std::nullopt, kNoSeedRefit};
220 const xAOD::SpacePoint* bottom_sp = sp_collection.front();
221
222 // Magnetic Field
223 ATLASMagneticFieldWrapper magneticField;
224 Acts::MagneticFieldProvider::Cache magFieldCache = magneticField.makeCache( magFieldContext );
225 Acts::Vector3 bField = *magneticField.getField( Acts::Vector3(bottom_sp->x(), bottom_sp->y(), bottom_sp->z()),
226 magFieldCache );
227 if (m_bFieldMode == 1) {
228 bField[0] = 0.0;
229 bField[1] = 0.0;
230 }
231
232 // Get the surface
233 const Acts::Surface& surface = retrieveSurface(seed);
234
236 seed,
237 geoContext,
238 magFieldContext,
239 calContext,
240 surface,
241 bField);
242 }
243
244 std::pair<std::optional<Acts::BoundTrackParameters>, TrackParamsEstimationTool::EstimationStatus>
246 const ActsTrk::Seed& seed,
247 const Acts::GeometryContext& geoContext,
248 const Acts::MagneticFieldContext& magFieldContext,
249 const Acts::CalibrationContext& calContext,
250 const Acts::Surface& surface,
251 const Acts::Vector3& bField) const
252 {
253 // Get SPs
254 const auto& sp_collection = seed.sp();
255 const std::size_t nSp = sp_collection.size();
256 if (nSp < 3) return {std::nullopt, kNoSeedRefit};
257
258 // Function to extract the values from sp_collection
259 const auto sp_collection_extract = std::views::transform([&sp_collection](std::size_t i) {
260 return sp_collection.at(i);
261 });
262
263 // Compute free parameters
264 Acts::FreeVector freeParams;
265 if (m_parameterEstimationMode == 4) {
266 const Acts::Result<Acts::FreeVector> freeParamsResult = estimateTrackParamsFromAllSpacePoints(
267 std::views::iota(std::size_t{0}, nSp) | sp_collection_extract, bField, m_stripCalibrationIterations,
269 if (!freeParamsResult.ok()) {
270 ATH_MSG_DEBUG("Fit of " << nSp << "-SP seed failed - " << freeParamsResult.error().message());
271 return {std::nullopt, kNoSeedRefit};
272 }
273 freeParams = *freeParamsResult;
274 } else {
275 freeParams = estimateTrackParamsFromSeed(m_spacePointIndicesFun(sp_collection) | sp_collection_extract, bField, m_stripCalibrationIterations);
276 }
277 // The fit cannot resolve the curvature of a straight seed, and a zero
278 // q/p gives NaN in the covariance transport
279 if (freeParams[Acts::eFreeQOverP] == 0) {
280 ATH_MSG_DEBUG("Fit of " << nSp << "-SP seed did not resolve the curvature - skip seed");
281 return {std::nullopt, kNoSeedRefit};
282 }
283
284 if (m_parameterEstimationMode == 1 && nSp > 3ul) {
285 const auto spacePointIndicesFun2 = [](std::size_t nSp) -> std::array<std::size_t, 3> {
286 return {0, nSp / 2ul, nSp - 1};
287 };
288 const Acts::FreeVector freeParams2 = estimateTrackParamsFromSeed(spacePointIndicesFun2(nSp) | sp_collection_extract, bField, m_stripCalibrationIterations);
289 ATH_MSG_DEBUG("update seed p = " << 1.0 / freeParams[Acts::eFreeQOverP] << " to " << 1.0 / freeParams2[Acts::eFreeQOverP]);
290 freeParams[Acts::eFreeQOverP] = freeParams2[Acts::eFreeQOverP];
291 }
292
293 // Convert free params to curvilinear params for extrapolation
294 Acts::BoundTrackParameters curvilinearParams = Acts::BoundTrackParameters::createCurvilinear(
295 freeParams.segment<4>(Acts::eFreePos0),
296 freeParams.segment<3>(Acts::eFreeDir0),
297 freeParams[Acts::eFreeQOverP],
298 std::nullopt,
299 Acts::ParticleHypothesis::pion());
300
301 // Extrapolate to surface
302 Acts::PropagatorPlainOptions propOptions(geoContext, magFieldContext);
303 propOptions.direction = Acts::Direction::fromScalarZeroAsPositive(
304 surface.intersect(
305 geoContext,
306 freeParams.segment<3>(Acts::eFreePos0),
307 freeParams.segment<3>(Acts::eFreeDir0)
308 ).closest().pathLength());
309
310 std::optional<Acts::BoundTrackParameters> boundParams;
311 auto boundParamsResult =
312 m_extrapolator->propagateToSurface(curvilinearParams, surface, propOptions);
313
314 if (!boundParamsResult.ok()) {
315 ATH_MSG_DEBUG("Extrapolation from " << seed.sp().size() << "-SP seed failed - "
316 << (m_allowPropagatorFailure ? "use curvilinear parameters" : "skip seed"));
318 // Fallback: use curvilinear parameters instead of failing
319 boundParams = curvilinearParams;
320 } else {
321 return {std::nullopt, kNoSeedRefit};
322 }
323 } else {
324 boundParams = *boundParamsResult;
325 }
326
327 // Estimate covariance
328 Acts::EstimateTrackParamCovarianceConfig covarianceEstimationConfig = {
330 .initialSigmaPtRel = m_initialSigmaPtRel,
331 .initialVarInflation = Eigen::Map<const Acts::BoundVector>(m_initialVarInflation.value().data()),
332 .noTimeVarInflation = 1.0,
333 };
334 boundParams->covariance() = Acts::estimateTrackParamCovariance(
335 covarianceEstimationConfig,
336 boundParams->parameters(),
337 false);
338
339 if (!m_refitSeeds) {
340 ATH_MSG_DEBUG("estimateTrackParams from " << seed.sp().size() << "-SP seed succeeded");
341 return {boundParams, kNoSeedRefit};
342 }
343
344 auto refitResult = doRefit(seed, *boundParams, geoContext, magFieldContext, calContext);
345 ATH_MSG_DEBUG("Refit " << seed.sp().size() << "-SP seed " << (refitResult ? "succeeded" : "failed"));
346 if (refitResult) {
347 // The covariance of the refit is too small as an input to the CKF,
348 // so use the same crude estimate as for the unfitted seed
349 refitResult->covariance() = Acts::estimateTrackParamCovariance(
350 covarianceEstimationConfig,
351 refitResult->parameters(),
352 false);
353 return {refitResult, kSeedRefitSuccess};
354 } else {
355 return {boundParams, kSeedRefitFailed};
356 }
357
358 }
359
360 // Function to return which 3 SPs of a seed to use
362 // MinDeltaR
363 if (m_parameterEstimationMode == 3) {
364 const double minDeltaR = m_minDeltaR;
365 return [minDeltaR](const ActsTrk::SpacePointRange& spacePoints) -> std::array<std::size_t, 3> {
366 const std::size_t nSp = spacePoints.size();
367 std::array<std::size_t, 3> indices{};
368 std::size_t nSelected = 0;
369 double lastDistance = 0.;
370 for (std::size_t i = 0; i < nSp && nSelected < indices.size(); ++i) {
371 const xAOD::SpacePoint* sp = spacePoints[i];
372 const double distance = Acts::fastHypot(sp->x(), sp->y(), sp->z());
373 if (nSelected > 0 && std::abs(distance - lastDistance) <= minDeltaR) {
374 continue;
375 }
376 indices[nSelected++] = i;
377 lastDistance = distance;
378 }
379 if (nSelected < indices.size()) {
380 if (nSp > 3ul)
381 return {0, nSp / 2ul, nSp - 1};
382 else
383 return {0, 1, 2};
384 }
385 return indices;
386 };
387 }
388 // FirstMiddleLast. The fit of all SPs uses the same SPs to detect duplicate seeds.
390 return [](const ActsTrk::SpacePointRange& spacePoints) -> std::array<std::size_t, 3> {
391 const std::size_t nSp = spacePoints.size();
392 if (nSp > 3ul)
393 return {0, nSp / 2ul, nSp - 1};
394 else
395 return {0, 1, 2};
396 };
397 } else if (m_firstSp > 0ul) {
398 std::size_t firstSp = m_firstSp;
399 return [firstSp](const ActsTrk::SpacePointRange& spacePoints) -> std::array<std::size_t, 3> {
400 const std::size_t nSp = spacePoints.size();
401 if (nSp > 3ul) {
402 std::size_t first = std::min(firstSp, nSp - 3ul);
403 return {first, first + 1, first + 2};
404 } else
405 return {0, 1, 2};
406 };
407 } else {
408 return [](const ActsTrk::SpacePointRange&) -> std::array<std::size_t, 3> {
409 return {0, 1, 2};
410 };
411 }
412 };
413
414
415 // Refit track. Used if refitSeeds=True.
416 std::optional<Acts::BoundTrackParameters> TrackParamsEstimationTool::doRefit(
417 const ActsTrk::Seed &measurement,
418 const Acts::BoundTrackParameters &initialParameters,
419 const Acts::GeometryContext& geometry,
420 const Acts::MagneticFieldContext& magField,
421 const Acts::CalibrationContext& calib) const {
422 // Perform KF before CKF
423 // get the inner-most surface
424 const Acts::Surface* targetSurface = m_uncalibMeasSurfAcc.get(measurement.sp().front()->measurements().front());
425 if (not targetSurface) {
426 ATH_MSG_WARNING("Could not identify the target surface for fitting the provided seed");
427 return std::nullopt;
428 }
429 const auto fittedSeedCollection = m_fitterTool->fit(measurement, initialParameters,
430 geometry, magField, calib,
431 *targetSurface);
432 if (not fittedSeedCollection) {
433 ATH_MSG_VERBOSE("KF fit failure");
434 return std::nullopt;
435 }
436 if (fittedSeedCollection->size() != 1) {
437 ATH_MSG_WARNING("KF produced " << fittedSeedCollection->size() << " tracks but should produce 1!");
438 return std::nullopt;
439 }
440 const auto fittedSeed = fittedSeedCollection->getTrack(0);
441
442 // get the track state at the beginning of the track, where we started
443 std::optional<typename decltype(fittedSeed)::ConstTrackStateProxy> trackState = fittedSeed.innermostTrackState();
444 if (!trackState) {
445 // if the track is not forward linked (fixed by #5666), then we need to search back to the innermost track state
446 for (auto st : fittedSeed.trackStatesReversed()) {
447 trackState = st;
448 }
449 }
450
451 if (!trackState) {
452 ATH_MSG_VERBOSE("Missing innermost track state");
453 return std::nullopt;
454 }
455
456 // Return updated parameters
457 return fittedSeed.createParametersFromState(trackState.value());
458 };
459
460}
461// namespace ActsTrk
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_DEBUG(x,...)
#define ATH_MSG_WARNING(x,...)
#define ATH_MSG_VERBOSE(x,...)
#define ATH_MSG_INFO(x,...)
#define ATH_MSG_FATAL(x,...)
std::vector< std::vector< int64_t > > indices
static Double_t sp
std::unique_ptr< const Acts::Logger > makeActsAthenaLogger(IMessageSvc *svc, const std::string &name, int level, std::optional< std::string > parent_name)
Definition Logger.cxx:64
Acts::Result< Acts::Vector3 > getField(const Acts::Vector3 &position, Acts::MagneticFieldProvider::Cache &gcache) const override
MagneticFieldProvider::Cache makeCache(const Acts::MagneticFieldContext &mctx) const override
std::function< std::array< std::size_t, 3 >(const ActsTrk::SpacePointRange &)> SpacePointIndicesFun_t
std::optional< Acts::BoundTrackParameters > doRefit(const ActsTrk::Seed &measurement, const Acts::BoundTrackParameters &initialParameters, const Acts::GeometryContext &geometry, const Acts::MagneticFieldContext &magField, const Acts::CalibrationContext &calib) const
Gaudi::Property< std::size_t > m_geometricRefineIterations
const Acts::Logger & logger() const
Private access to the logger.
std::unique_ptr< const Acts::Logger > m_logger
logging instance
ToolHandle< ActsTrk::IFitterTool > m_fitterTool
ServiceHandle< ActsTrk::ITrackingGeometrySvc > m_trackingGeometrySvc
SpacePointIndicesFun_t spacePointIndicesFun() const override
TrackParamsEstimationTool(const std::string &type, const std::string &name, const IInterface *parent)
virtual std::pair< std::optional< Acts::BoundTrackParameters >, EstimationStatus > estimateTrackParameters(const ActsTrk::Seed &seed, const Acts::GeometryContext &geoContext, const Acts::MagneticFieldContext &magFieldContext, const Acts::CalibrationContext &calContext, std::function< const Acts::Surface &(const ActsTrk::Seed &seed)> retrieveSurface) const override
Gaudi::Property< std::vector< double > > m_initialVarInflation
Gaudi::Property< std::size_t > m_firstSp
std::optional< Extrapolator > m_extrapolator
detail::xAODUncalibMeasSurfAcc m_uncalibMeasSurfAcc
Gaudi::Property< std::size_t > m_stripCalibrationIterations
Gaudi::Property< double > m_spacePointWeightExponent
Helper class to access the Acts::surface associated with an Uncalibrated xAOD measurement.
float z() const
float y() const
float x() const
int r
Definition globals.cxx:22
The AlignStoreProviderAlg loads the rigid alignment corrections and pipes them through the readout ge...
float j(const xAOD::IParticle &, const xAOD::TrackMeasurementValidation &hit, const Eigen::Matrix3d &jab_inv)
SpacePointRange sp() const noexcept