ATLAS Offline Software
Loading...
Searching...
No Matches
AnalogueClusteringToolImpl.icc
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3*/
4#ifndef ANALOGUECLUSTERINGTOOLIMPL_ICC
5#define ANALOGUECLUSTERINGTOOLIMPL_ICC
6
7#include <limits>
8#include "ActsGeometry/ActsDetectorElement.h"
9#include <PixelReadoutGeometry/PixelModuleDesign.h>
10#include "AthenaBaseComps/AthMsgStreamMacros.h"
11#include "InDetReadoutGeometry/SiDetectorElement.h"
12#include "InDetMeasurementUtilities/Helpers.h"
13
14#include <stdexcept>
15
16namespace ActsTrk::detail {
17
18template <typename calib_data_t, typename traj_t>
19StatusCode AnalogueClusteringToolImpl<calib_data_t, traj_t>::initialize()
20{
21 ATH_MSG_DEBUG("Initializing " << AthAlgTool::name() << " ...");
22
23 ATH_CHECK( BASE::initialize() );
24 ATH_CHECK(m_clusterErrorKey.initialize());
25 ATH_MSG_DEBUG(AthAlgTool::name() << " successfully initialized");
26
27 ATH_MSG_DEBUG( this->calibrateAfterMeasurementSelection() );
28 ATH_MSG_DEBUG( m_correctCovariance );
29 ATH_MSG_DEBUG( m_calibratedCovarianceLowerBound );
30 ATH_MSG_DEBUG( m_errorStrategy );
31
32 return StatusCode::SUCCESS;
33}
34
35template <typename calib_data_t, typename traj_t>
36std::pair<float, float>
37AnalogueClusteringCalibrator<calib_data_t, traj_t>::getCentroid(
38 const EventContext& ctx,
39 const xAOD::PixelCluster& cluster,
40 const InDetDD::SiDetectorElement& element) const
41{
42 // Reproduce the base position of the legacy ITk::PixelClusterOnTrackTool,
43 // i.e. the geometric 4-corner bounding-box centroid plus the Lorentz shift. This
44 // is the reference the getDelta charge-interpolation constants are calibrated
45 // against, so the analogue correction must be applied on top of it.
46 SG::ConstAccessor<SG::JaggedVecElt<Identifier::value_type> >::element_type
47 rdos = cluster.rdoList();
48
49 int rowmin = std::numeric_limits<int>::max();
50 int rowmax = std::numeric_limits<int>::min();
51 int colmin = std::numeric_limits<int>::max();
52 int colmax = std::numeric_limits<int>::min();
53 // convert Identifier::value_type into Identifier
54 for (Identifier::value_type rid_value : rdos) {
55 Identifier rid(rid_value);
56 int row = this->pixelID().phi_index(rid);
57 rowmin = std::min(row, rowmin);
58 rowmax = std::max(row, rowmax);
59
60 int col = this->pixelID().eta_index(rid);
61 colmin = std::min(col, colmin);
62 colmax = std::max(col, colmax);
63 }
64
65 const InDetDD::PixelModuleDesign& design =
66 dynamic_cast<const InDetDD::PixelModuleDesign&>(element.design());
67
68 InDetDD::SiLocalPosition pos1 =
69 design.positionFromColumnRow(colmin, rowmin);
70
71 InDetDD::SiLocalPosition pos2 =
72 design.positionFromColumnRow(colmax, rowmin);
73
74 InDetDD::SiLocalPosition pos3 =
75 design.positionFromColumnRow(colmin, rowmax);
76
77 InDetDD::SiLocalPosition pos4 =
78 design.positionFromColumnRow(colmax, rowmax);
79
80 InDetDD::SiLocalPosition centroid = 0.25 * (pos1 + pos2 + pos3 + pos4);
81
82 double shift = this->getLorentzShift(element.identifyHash(), ctx);
83
84 return std::make_pair(centroid.xPhi() + shift, centroid.xEta());
85}
86
87template <typename calib_data_t, typename traj_t>
88const typename AnalogueClusteringCalibrator<calib_data_t,traj_t>::error_data_t*
89AnalogueClusteringToolImpl<calib_data_t, traj_t>::getErrorData(const EventContext &ctx) const
90{
91 SG::ReadCondHandle<calib_data_t> handle(m_clusterErrorKey,ctx);
92
93 if (!handle.isValid()) {
94 ATH_MSG_ERROR(m_clusterErrorKey << " is not available.");
95 return nullptr;
96 }
97
98 const typename AnalogueClusteringCalibrator<calib_data_t,traj_t>::error_data_t* data = handle->getClusterErrorData();
99 if (data == nullptr) {
100 ATH_MSG_ERROR("No cluster error data corresponding to " << m_clusterErrorKey);
101 return nullptr;
102 }
103
104 return data;
105}
106
107namespace {
108 // negative limit of eta when computing eta as eta(T x) {return -log(tan(x*0.5)); } with x=M_PI-epsilon
109 // was eta<T>(static_cast<T>(M_PI)-std::numeric_limits<T>::epsilon());
110 // with T eta(T theta) { return static_cast<float>(-1*log(tan(theta*0.5))); }, and T=float
111 constexpr float etaMin() { return -16.3991603f; }
112 // positive limit of eta when computing eta as eta(T x) {return -log(tan(x*0.5)); } with x=0+epsilon
113 // was eta<T>(static_cast<T>(0)-std::numeric_limits<T>::epsilon()), with T=float
114 constexpr float etaMax() { return 16.6355323f; }
115
116 // compute eta from tan_theta
117 double computeEta(double tan_theta) {
118 // eta = - ln ( tan theta/2)
119 // tan theta /2 = tan theta / (1 + sec theta)
120 // 1+tan^2 theta = sec^2 theta
121 // sec theta = sqrt ( 1+ tan^2 theta)
122 double one_plus_sec_theta = 1.+std::copysign(std::sqrt ( 1. + std::pow(tan_theta,2) ), tan_theta );
123
124 // reproduce roughly the original range;
125 // @TODO what would be the allowed range for getDelta ?
126 static constexpr double min_eta = etaMin();
127 static constexpr double max_eta = etaMax();
128
129 return std::max(min_eta,
130 std::min( max_eta,
131 (std::abs(one_plus_sec_theta)>tan_theta*std::numeric_limits<double>::epsilon()
132 ? -std::log( tan_theta / ( one_plus_sec_theta ))
133 :min_eta ) ));
134 }
135}
136
137
138template <typename calib_data_t, typename traj_t>
139std::pair<std::optional<float>, std::optional<float>>
140AnalogueClusteringCalibrator<calib_data_t, traj_t>::getCorrectedPosition(
141 const EventContext& ctx,
142 const xAOD::PixelCluster& cluster,
143 const error_data_t& errorData,
144 const InDetDD::SiDetectorElement& element,
145 const std::pair<float, float>& tan_angles) const
146{
147 auto& [tan_anglePhi, tan_angleEta] = tan_angles; // Phi and Eta denote the direction of the axis
148
149 std::optional<float> posX {std::nullopt};
150 std::optional<float> posY {std::nullopt};
151
152 int nrows = cluster.channelsInPhi();
153 int ncols = cluster.channelsInEta();
154
155 // If size of cluster (in any direction) is higher than 1, we may need to apply a correction
156 if (nrows > 1 or ncols > 1) {
157 const auto& [omX, omY] = TrackingUtilities::computeOmegas(cluster,
158 this->pixelID());
159 // if the omegas are valid, we can apply the correction
160 if (omX > -0.5 and omY > -0.5) {
161 std::pair<float, float> centroid = getCentroid(ctx, cluster, element);
162 IdentifierHash idHash = element.identifyHash();
163
164 // getDelta expects the pseudo-rapidity built from the polar angle
165 // measured with respect to the module *plane* (theta = pi/2 - angleFromNormal),
166 // exactly as the legacy ITk::PixelClusterOnTrackTool does. tan_angleEta is
167 // the tangent of the incidence angle with respect to the module *normal*, so
168 // the polar-angle tangent is its cotangent: tan(pi/2 - b) = 1/tan(b).
169 // (For all quadrants of boweta the legacy thetaloc satisfies tan(thetaloc) = cot(boweta).)
170 const double local_eta = tan_angleEta != 0.f ? computeEta(1.0 / tan_angleEta) : 0.0;
171 std::pair<double, double> delta =
172 errorData.getDelta(idHash,
173 nrows,
174 atan(tan_anglePhi),
175 ncols,
176 local_eta);
177
178 if (nrows > 1)
179 posX = centroid.first + delta.first * (omX - 0.5);
180
181 if (ncols > 1)
182 posY = centroid.second + delta.second * (omY - 0.5);
183 } // check on omega values
184 } // check on cluster sizes
185
186 return std::make_pair(posX, posY);
187}
188
189 template <typename calib_data_t, typename traj_t>
190 std::pair<std::optional<float>, std::optional<float>>
191 AnalogueClusteringCalibrator<calib_data_t, traj_t>::getCorrectedError(
192 const typename AnalogueClusteringCalibrator<calib_data_t, traj_t>::error_data_t& errorData,
193 [[maybe_unused]] const InDetDD::SiDetectorElement& element,
194 const std::pair<float, float>& tan_angles,
195 const xAOD::PixelCluster& cluster) const
196 {
197 std::optional<float> errX {std::nullopt};
198 std::optional<float> errY {std::nullopt};
199 if (not m_options.m_correctCovariance) {
200 return std::make_pair(errX, errY);
201 }
202
203 typename BASE::Cov cov = cluster.template localCovariance<2>();
204 int nrows = cluster.channelsInPhi();
205 int ncols = cluster.channelsInEta();
206
207 auto& [tan_anglePhi, tan_angleEta] = tan_angles;
208
209 if (m_options.m_errorStrategy == 0) {
210
211 // Special case for very shallow tracks
212 // Error estimated from geometrical projection of
213 // the track path in silicon onto the module surface
214 static constexpr float tan_1 = 1.55740772f; // std::tan(1.f);
215 if (std::abs(tan_anglePhi) > tan_1) {
216 static const double one_over_sqrt12 = 1./std::sqrt(12);
217 double thickness = element.design().thickness();
218 errX = thickness * std::abs(tan_anglePhi) * one_over_sqrt12;
219 errY = thickness * std::abs(tan_angleEta);
220 if (cluster.widthInEta() > errY) {
221 errY = cluster.widthInEta() * one_over_sqrt12;
222 } else {
223 errY = *errY * one_over_sqrt12;
224 }
225 } else if (nrows > 1 or ncols > 1) {
226 IdentifierHash idHash = cluster.identifierHash();
227 auto [vX, vY] = errorData.getDeltaError(idHash);
228 if (nrows > 1 and vX > 0)
229 errX = vX;
230 if (ncols > 1 and vY > 0)
231 errY = vY;
232 }
233 } else if (m_options.m_errorStrategy == 1) { // this should check if we are using broad errors in the clustering tool
234
235 //This assumes that we ran broad errors in the clustering tool
236 errX = std::sqrt(cov(0,0)) / nrows;
237 errY = std::sqrt(cov(1,1)) / ncols;
238
239 } else { // throw exception for the moment
240 throw std::runtime_error("Only errorStrategy 0 or 1 is supported for the moment");
241 }
242
243 return std::make_pair(errX, errY);
244}
245
246template <typename calib_data_t, typename traj_t>
247std::tuple<typename AnalogueClusteringCalibrator<calib_data_t, traj_t>::BASE::Pos,
248 typename AnalogueClusteringCalibrator<calib_data_t, traj_t>::BASE::Cov,
249 unsigned int>
250AnalogueClusteringCalibrator<calib_data_t, traj_t>::calibrate(
251 const EventContext& ctx,
252 const Acts::GeometryContext& /*gctx*/,
253 const Acts::CalibrationContext& /*cctx*/,
254 const xAOD::PixelCluster& cluster,
255 const InDetDD::SiDetectorElement& detElement,
256 const std::pair<float, float>& tan_incidence_angles) const
257{
258 typename BASE::Pos pos = cluster.template localPosition<2>();
259 typename BASE::Cov cov = cluster.template localCovariance<2>();
260 assert(!cluster.rdoList().empty());
261
262 const error_data_t *errorData = getErrorData();
263 if (errorData == nullptr) {
264 throw std::runtime_error("PixelClusterErrorData is NULL");
265 }
266
267 auto [posX, posY] = getCorrectedPosition(ctx, cluster, *errorData, detElement, tan_incidence_angles);
268 if (posX.has_value())
269 pos[Acts::eBoundLoc0] = *posX;
270 if (posY.has_value())
271 pos[Acts::eBoundLoc1] = *posY;
272
273 auto [errX, errY] = getCorrectedError(*errorData, detElement, tan_incidence_angles, cluster);
274 if (errX.has_value()) {
275 double newErr = *errX;
276 cov(0, 0) = std::max(newErr * newErr, cov(0, 0) * m_options.m_calibratedCovarianceLowerBound);
277 }
278 if (errY.has_value()) {
279 double newErr = *errY;
280 cov(1, 1) = std::max(newErr * newErr, cov(1, 1) * m_options.m_calibratedCovarianceLowerBound);
281 }
282
283 return std::make_tuple(pos, cov, 0u);
284}
285
286
287} // namespace ActsTrk
288
289#endif