2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
4#ifndef ANALOGUECLUSTERINGTOOLIMPL_ICC
5#define ANALOGUECLUSTERINGTOOLIMPL_ICC
8#include "ActsGeometry/ActsDetectorElement.h"
9#include <PixelReadoutGeometry/PixelModuleDesign.h>
10#include "InDetReadoutGeometry/SiDetectorElement.h"
11#include "InDetMeasurementUtilities/Helpers.h"
15namespace ActsTrk::detail {
17template <typename calib_data_t, typename traj_t>
18StatusCode AnalogueClusteringToolImpl<calib_data_t, traj_t>::initialize()
20 ATH_MSG_DEBUG("Initializing " << AthAlgTool::name() << " ...");
22 ATH_CHECK( BASE::initialize() );
23 ATH_CHECK(m_clusterErrorKey.initialize());
24 ATH_MSG_DEBUG(AthAlgTool::name() << " successfully initialized");
26 ATH_MSG_DEBUG( this->calibrateAfterMeasurementSelection() );
27 ATH_MSG_DEBUG( m_useWeightedPos );
28 ATH_MSG_DEBUG( m_correctCovariance );
29 ATH_MSG_DEBUG( m_calibratedCovarianceLowerBound );
30 ATH_MSG_DEBUG( m_errorStrategy );
32 return StatusCode::SUCCESS;
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
42 // Different behaviours if pixel uncalibrated cluster use digital or
43 // charge-weighted local position
44 if (not m_options.m_useWeightedPos) {
45 // We return the cluster local position here
46 // computed from the clustering tool
47 // Eventually we will have a NN for the on-track calibration instead this instead
48 const auto& localPos = cluster.template localPosition<2>();
49 return std::make_pair(localPos(0, 0),
53 // if weighted position, we need to compute the centroid
54 SG::ConstAccessor<SG::JaggedVecElt<Identifier::value_type> >::element_type
55 rdos = cluster.rdoList();
57 int rowmin = std::numeric_limits<int>::max();
58 int rowmax = std::numeric_limits<int>::min();
59 int colmin = std::numeric_limits<int>::max();
60 int colmax = std::numeric_limits<int>::min();
61 // convert Identifier::value_type into Identifier
62 for (Identifier::value_type rid_value : rdos) {
63 Identifier rid(rid_value);
64 int row = this->pixelID().phi_index(rid);
65 rowmin = std::min(row, rowmin);
66 rowmax = std::max(row, rowmax);
68 int col = this->pixelID().eta_index(rid);
69 colmin = std::min(col, colmin);
70 colmax = std::max(col, colmax);
73 const InDetDD::PixelModuleDesign& design =
74 dynamic_cast<const InDetDD::PixelModuleDesign&>(element.design());
76 InDetDD::SiLocalPosition pos1 =
77 design.positionFromColumnRow(colmin, rowmin);
79 InDetDD::SiLocalPosition pos2 =
80 design.positionFromColumnRow(colmax, rowmin);
82 InDetDD::SiLocalPosition pos3 =
83 design.positionFromColumnRow(colmin, rowmax);
85 InDetDD::SiLocalPosition pos4 =
86 design.positionFromColumnRow(colmax, rowmax);
88 InDetDD::SiLocalPosition centroid = 0.25 * (pos1 + pos2 + pos3 + pos4);
90 double shift = this->getLorentzShift(element.identifyHash(), ctx);
92 return std::make_pair(centroid.xPhi() + shift, centroid.xEta());
95template <typename calib_data_t, typename traj_t>
96const typename AnalogueClusteringCalibrator<calib_data_t,traj_t>::error_data_t*
97AnalogueClusteringToolImpl<calib_data_t, traj_t>::getErrorData(const EventContext &ctx) const
99 SG::ReadCondHandle<calib_data_t> handle(m_clusterErrorKey,ctx);
101 if (!handle.isValid()) {
102 ATH_MSG_ERROR(m_clusterErrorKey << " is not available.");
106 const typename AnalogueClusteringCalibrator<calib_data_t,traj_t>::error_data_t* data = handle->getClusterErrorData();
107 if (data == nullptr) {
108 ATH_MSG_ERROR("No cluster error data corresponding to " << m_clusterErrorKey);
116 // negative limit of eta when computing eta as eta(T x) {return -log(tan(x*0.5)); } with x=M_PI-epsilon
117 // was eta<T>(static_cast<T>(M_PI)-std::numeric_limits<T>::epsilon());
118 // with T eta(T theta) { return static_cast<float>(-1*log(tan(theta*0.5))); }, and T=float
119 constexpr float etaMin() { return -16.3991603f; }
120 // positive limit of eta when computing eta as eta(T x) {return -log(tan(x*0.5)); } with x=0+epsilon
121 // was eta<T>(static_cast<T>(0)-std::numeric_limits<T>::epsilon()), with T=float
122 constexpr float etaMax() { return 16.6355323f; }
124 // compute eta from tan_theta
125 double computeEta(double tan_theta) {
126 // eta = - ln ( tan theta/2)
127 // tan theta /2 = tan theta / (1 + sec theta)
128 // 1+tan^2 theta = sec^2 theta
129 // sec theta = sqrt ( 1+ tan^2 theta)
130 double one_plus_sec_theta = 1.+std::copysign(std::sqrt ( 1. + std::pow(tan_theta,2) ), tan_theta );
132 // reproduce roughly the original range;
133 // @TODO what would be the allowed range for getDelta ?
134 static constexpr double min_eta = etaMin();
135 static constexpr double max_eta = etaMax();
137 return std::max(min_eta,
139 (std::abs(one_plus_sec_theta)>tan_theta*std::numeric_limits<double>::epsilon()
140 ? -std::log( tan_theta / ( one_plus_sec_theta ))
146template <typename calib_data_t, typename traj_t>
147std::pair<std::optional<float>, std::optional<float>>
148AnalogueClusteringCalibrator<calib_data_t, traj_t>::getCorrectedPosition(
149 const EventContext& ctx,
150 const xAOD::PixelCluster& cluster,
151 const error_data_t& errorData,
152 const InDetDD::SiDetectorElement& element,
153 const std::pair<float, float>& tan_angles) const
155 auto& [tan_anglePhi, tan_angleEta] = tan_angles; // Phi and Eta denote the direction of the axis
157 std::optional<float> posX {std::nullopt};
158 std::optional<float> posY {std::nullopt};
160 int nrows = cluster.channelsInPhi();
161 int ncols = cluster.channelsInEta();
163 // If size of cluster (in any direction) is higher than 1, we may need to apply a correction
164 if (nrows > 1 or ncols > 1) {
165 const auto& [omX, omY] = TrackingUtilities::computeOmegas(cluster,
167 // if the omegas are valid, we can apply the correction
168 if (omX > -0.5 and omY > -0.5) {
169 std::pair<float, float> centroid = getCentroid(ctx, cluster, element);
170 IdentifierHash idHash = element.identifyHash();
172 double local_eta = computeEta(tan_angleEta);
173 std::pair<double, double> delta =
174 errorData.getDelta(idHash,
178 local_eta); // @TODO why local eta and not the slope i.e. tan_angleEta ?
181 posX = centroid.first + delta.first * (omX - 0.5);
184 posY = centroid.second + delta.second * (omY - 0.5);
185 } // check on omega values
186 } // check on cluster sizes
188 return std::make_pair(posX, posY);
191 template <typename calib_data_t, typename traj_t>
192 std::pair<std::optional<float>, std::optional<float>>
193 AnalogueClusteringCalibrator<calib_data_t, traj_t>::getCorrectedError(
194 const typename AnalogueClusteringCalibrator<calib_data_t, traj_t>::error_data_t& errorData,
195 [[maybe_unused]] const InDetDD::SiDetectorElement& element,
196 const std::pair<float, float>& tan_angles,
197 const xAOD::PixelCluster& cluster) const
199 std::optional<float> errX {std::nullopt};
200 std::optional<float> errY {std::nullopt};
201 if (not m_options.m_correctCovariance) {
202 return std::make_pair(errX, errY);
205 typename BASE::Cov cov = cluster.template localCovariance<2>();
206 int nrows = cluster.channelsInPhi();
207 int ncols = cluster.channelsInEta();
209 auto& [tan_anglePhi, tan_angleEta] = tan_angles;
211 if (m_options.m_errorStrategy == 0) {
213 // Special case for very shallow tracks
214 // Error estimated from geometrical projection of
215 // the track path in silicon onto the module surface
216 static constexpr float tan_1 = 1.55740772f; // std::tan(1.f);
217 if (std::abs(tan_anglePhi) > tan_1) {
218 static const double one_over_sqrt12 = 1./std::sqrt(12);
219 double thickness = element.design().thickness();
220 errX = thickness * std::abs(tan_anglePhi) * one_over_sqrt12;
221 errY = thickness * std::abs(tan_angleEta);
222 if (cluster.widthInEta() > errY) {
223 errY = cluster.widthInEta() * one_over_sqrt12;
225 errY = *errY * one_over_sqrt12;
227 } else if (nrows > 1 or ncols > 1) {
228 IdentifierHash idHash = cluster.identifierHash();
229 auto [vX, vY] = errorData.getDeltaError(idHash);
230 if (nrows > 1 and vX > 0)
232 if (ncols > 1 and vY > 0)
235 } else if (m_options.m_errorStrategy == 1) { // this should check if we are using broad errors in the clustering tool
237 //This assumes that we ran broad errors in the clustering tool
238 errX = std::sqrt(cov(0,0)) / nrows;
239 errY = std::sqrt(cov(1,1)) / ncols;
241 } else { // throw exception for the moment
242 throw std::runtime_error("Only errorStrategy 0 or 1 is supported for the moment");
245 return std::make_pair(errX, errY);
248template <typename calib_data_t, typename traj_t>
249std::pair<typename AnalogueClusteringCalibrator<calib_data_t, traj_t>::BASE::Pos,
250 typename AnalogueClusteringCalibrator<calib_data_t, traj_t>::BASE::Cov>
251AnalogueClusteringCalibrator<calib_data_t, traj_t>::calibrate(
252 const EventContext& ctx,
253 const Acts::GeometryContext& /*gctx*/,
254 const Acts::CalibrationContext& /*cctx*/,
255 const xAOD::PixelCluster& cluster,
256 const InDetDD::SiDetectorElement& detElement,
257 const std::pair<float, float>& tan_incidence_angles) const
259 typename BASE::Pos pos = cluster.template localPosition<2>();
260 typename BASE::Cov cov = cluster.template localCovariance<2>();
262 assert(!cluster.rdoList().empty());
264 const error_data_t *errorData = getErrorData();
265 if (errorData == nullptr) {
266 throw std::runtime_error("PixelClusterErrorData is NULL");
269 auto [posX, posY] = getCorrectedPosition(ctx, cluster, *errorData, detElement, tan_incidence_angles);
270 if (posX.has_value())
271 pos[Acts::eBoundLoc0] = *posX;
272 if (posY.has_value())
273 pos[Acts::eBoundLoc1] = *posY;
275 auto [errX, errY] = getCorrectedError(*errorData, detElement, tan_incidence_angles, cluster);
276 if (errX.has_value()) {
277 double newErr = *errX;
278 cov(0, 0) = std::max(newErr * newErr, cov(0, 0) * m_options.m_calibratedCovarianceLowerBound);
280 if (errY.has_value()) {
281 double newErr = *errY;
282 cov(1, 1) = std::max(newErr * newErr, cov(1, 1) * m_options.m_calibratedCovarianceLowerBound);
285 return std::make_pair(pos, cov);
289} // namespace ActsTrk