ATLAS Offline Software
Loading...
Searching...
No Matches
STgcClusterBuilderCommon.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
3*/
4
8#include "GaudiKernel/SystemOfUnits.h"
9
11 : AthMessaging("STgcClusterBuilderCommon"),
12 m_stgcIdHelper(idHelper),
13 m_errorCalibData{errorCalibData}
14{
15}
16
17
18//=============================================================================
19std::array<std::vector<Muon::sTgcPrepData>, 8>
20Muon::STgcClusterBuilderCommon::sortSTGCPrdPerLayer(std::vector<Muon::sTgcPrepData>&& stripPrds) const {
21
22
23 std::array<std::vector<Muon::sTgcPrepData>, 8> stgcPrdsPerLayer;
24 for (Muon::sTgcPrepData& prd : stripPrds) {
25 const Identifier id = prd.identify();
26 int layer = 4 * (m_stgcIdHelper.multilayer(id) - 1) + (m_stgcIdHelper.gasGap(id) - 1);
27 ATH_MSG_DEBUG("Sorting PRD into layer, layer: " << layer << " gas_gap: " << m_stgcIdHelper.gasGap(id)
28 << "multilayer: " << m_stgcIdHelper.multilayer(id));
29 stgcPrdsPerLayer.at(layer).push_back(std::move(prd));
30 }
31
32 // sort prds by channel
33 for (unsigned int i_layer = 0; i_layer < stgcPrdsPerLayer.size(); i_layer++) {
34 std::sort(stgcPrdsPerLayer.at(i_layer).begin(), stgcPrdsPerLayer.at(i_layer).end(),
35 [this](const sTgcPrepData& a, const sTgcPrepData& b) {
36 return m_stgcIdHelper.channel(a.identify()) < m_stgcIdHelper.channel(b.identify());
37 });
38 }
39 return stgcPrdsPerLayer;
40}
41
42
43//=============================================================================
44std::vector<std::vector<Muon::sTgcPrepData>> Muon::STgcClusterBuilderCommon::findStripCluster(std::vector<Muon::sTgcPrepData>&& strips,
45 const int maxMissingStrip) const {
46 std::vector<std::vector<Muon::sTgcPrepData>> clusters;
47
48 if (strips.empty()) return clusters;
49
50 // Get the Id of the first strip to verify all the strips are from the same layer
51 const Identifier& firstStripId = strips.front().identify();
52
53 for (Muon::sTgcPrepData& prd : strips) {
54 // If no element has been added to the vector of clusters yet
55 if (clusters.empty()) {
56 clusters.emplace_back();
57 clusters.back().push_back(std::move(prd));
58 continue;
59 }
60
61 const Identifier prd_id = prd.identify();
62 if (m_stgcIdHelper.multilayerID(firstStripId) != m_stgcIdHelper.multilayerID(prd_id) ||
63 m_stgcIdHelper.gasGap(firstStripId) != m_stgcIdHelper.gasGap(prd_id) ||
64 m_stgcIdHelper.channelType(firstStripId) != m_stgcIdHelper.channelType(prd_id)) {
65 ATH_MSG_WARNING("Strips must be separated by layer before attempting to build clusters,"
66 << " but found strips from different chambers");
67 continue;
68 }
69
70 // Add another strip to a vector of clusters
71 if (std::abs(m_stgcIdHelper.channel(prd.identify()) -
72 m_stgcIdHelper.channel(clusters.back().back().identify())) > maxMissingStrip + 1) {
73 clusters.emplace_back();
74 }
75 clusters.back().push_back(std::move(prd));
76 }
77
78 return clusters;
79}
80
81
82//=============================================================================
83std::optional<Muon::STgcClusterPosition> Muon::STgcClusterBuilderCommon::weightedAverage(
84 const std::vector<Muon::sTgcPrepData>& cluster,
85 const double resolution,
86 bool isStrip) const
87{
88
89 if (cluster.empty()) {
90 ATH_MSG_VERBOSE("Skipping empty cluster");
91 return std::nullopt;
92 }
93
94 double weightedPosX{0.0}, maxCharge{-1.0}, sumWeight{0.0}, sigmaSq{0.0};
95 Identifier clusterId;
96 Amg::Vector3D clusDir{Amg::Vector3D::Zero()};
97
98 ATH_MSG_DEBUG("Running weighted average method on a cluster with " << cluster.size() << " strips");
99 for (const Muon::sTgcPrepData& prd : cluster) {
100 // Skip channel if its charge is negative
101 if (prd.charge() < 0) continue;
102
103 double weight = isStrip ? prd.charge() : 1.0;
104 ATH_MSG_DEBUG("isStrip: " << isStrip << " weight: " << weight);
105
106 weightedPosX += prd.localPosition().x()*weight;
107 sumWeight += weight;
108 sigmaSq += weight*weight*resolution*resolution;
109 clusDir += weight * NswClustering::toLocal(prd);
110 ATH_MSG_DEBUG("Channel local position and charge: " << prd.localPosition().x() << " " << prd.charge() );
111
112 // Set the cluster identifier to the max charge strip
113 if (!isStrip) {
114 clusterId = prd.identify();
115 } else if (prd.charge() > maxCharge) {
116 maxCharge = prd.charge();
117 clusterId = prd.identify();
118 }
119 }
120
121 // Charge in PRD is an integer, so the sum of weights should be greater than 1 unless the data is corrupted.
122 // Skip corrupted cluster
123 if (sumWeight < 1) {
124 ATH_MSG_VERBOSE("Got unexpected sum of weights: " << sumWeight);
125 return std::nullopt;
126 }
127
128 // Mean position of the cluster
129 double reconstructedPosX = weightedPosX / sumWeight;
130 sigmaSq /= sumWeight;
131
132 NswErrorCalibData::Input errorCalibIn{};
133 errorCalibIn.stripId = clusterId;
134 errorCalibIn.clusterAuthor = static_cast<unsigned>(sTgcPrepData::Author::SimpleClusterBuilder);
135 errorCalibIn.locPhi = clusDir.phi();
136 errorCalibIn.locTheta = clusDir.theta();
137 errorCalibIn.localPos = Amg::Vector2D{reconstructedPosX, 0};
138 errorCalibIn.clusterSize = cluster.size();
139
140 const double localUncertainty = m_errorCalibData.clusterUncertainty(errorCalibIn);
141
142 ATH_MSG_VERBOSE("Reconstructed a cluster using the weighted average,"
143 << " cluster Id: " << m_stgcIdHelper.print_to_string(clusterId)
144 <<", direction: "<<Amg::toString(clusDir.unit())
145 <<", theta: "<<clusDir.theta() / Gaudi::Units::deg
146 << ", mean position = " << reconstructedPosX
147 << ", uncertainty = " << localUncertainty
148 <<", sigmaSQ: "<<sigmaSq);
149
150 return std::make_optional<Muon::STgcClusterPosition>(clusterId, reconstructedPosX, localUncertainty*localUncertainty);
151}
152
153
154//=============================================================================
155std::optional<Muon::STgcClusterPosition> Muon::STgcClusterBuilderCommon::caruanaGaussianFitting(
156 const std::vector<sTgcPrepData>& cluster,
157 const double positionResolution,
158 const double angularResolution,
159 const MuonGM::MuonDetectorManager* detManager) const
160{
161
162 // Verify that there are 3 or more strips with non-zero charge, and the shape is not similar to a staircase
163 std::vector<int> charges;
164 charges.reserve(cluster.size());
165 std::vector<double> stripPositions;
166 stripPositions.reserve(cluster.size());
167 bool isStairUp{true}, isStairDown{true};
168 int multiplicity = 0;
169 for (unsigned int j = 0; j < cluster.size(); ++j){
170 int stripCharge = cluster.at(j).charge();
171 if (stripCharge > 0) {
172 multiplicity += 1;
173 charges.push_back(stripCharge);
174 stripPositions.push_back(cluster.at(j).localPosition().x());
175
176 if (j > 0) {
177 int prevStripCharge = cluster.at(j-1).charge();
178 if (isStairUp && (stripCharge - prevStripCharge < 0))
179 isStairUp = false;
180 if (isStairDown && (prevStripCharge > 0) && (stripCharge - prevStripCharge > 0))
181 isStairDown = false;
182 }
183 }
184 }
185
186 // Caruana fitting method doesn't support clusters having least than 3 strips or staircase-like clusters
187 if (multiplicity < 3 || isStairUp || isStairDown) {
188 if (msgLvl(MSG::VERBOSE)) {
189 std::stringstream sstr{};
190 for (const Muon::sTgcPrepData& prd : cluster) {
191 sstr << m_stgcIdHelper.print_to_string(prd.identify())
192 << ", local pos: ("<< prd.localPosition().x() << "," << prd.localPosition().y()
193 << "), charge: " << prd.charge() << ", time: " << static_cast<int>(prd.time())
194 << std::endl;
195 }
196 ATH_MSG_VERBOSE("Reject cluster incompatible with the Caruana method..." << std::endl << sstr.str());
197 }
198 return std::nullopt;
199 }
200
201 // Here we implement the Caruana method to reconstruct the position of the cluster
202 AmgSymMatrix(3) elementPosMatrix;
203 for (int i=0; i<3; i++) {
204 for (int j=0; j<=i; j++) elementPosMatrix.fillSymmetric(i, j, 0);
205 }
206 Amg::Vector3D chargeVector(0., 0., 0.);
207
208 // The reconstruction method becomes much simpiler when the strip positions are shifted such that
209 // the center of the cluster is at zero.
210 float clusterCenter = 0;
211 for (size_t i_strip = 0; i_strip < stripPositions.size(); i_strip++) {
212 clusterCenter += stripPositions.at(i_strip);
213 }
214 clusterCenter = clusterCenter / stripPositions.size();
215
216 std::vector<double> stripPositions_shifted = {};
217 for (size_t i_strip = 0; i_strip < stripPositions.size(); i_strip++) {
218 stripPositions_shifted.push_back(stripPositions.at(i_strip)-clusterCenter);
219 }
220
221 // Now build the matrix equation
222 for (size_t i_element = 0; i_element < stripPositions_shifted.size(); i_element++) {
223 elementPosMatrix.fillSymmetric(0, 0, elementPosMatrix(0,0) + 1);
224 elementPosMatrix.fillSymmetric(0, 1, elementPosMatrix(0,1) + stripPositions_shifted.at(i_element));
225 elementPosMatrix.fillSymmetric(0, 2, elementPosMatrix(0,2) + std::pow(stripPositions_shifted.at(i_element), 2));
226 elementPosMatrix.fillSymmetric(1, 2, elementPosMatrix(1,2) + std::pow(stripPositions_shifted.at(i_element), 3));
227 elementPosMatrix.fillSymmetric(2, 2, elementPosMatrix(2,2) + std::pow(stripPositions_shifted.at(i_element), 4));
228 const double log_charge = std::log(charges.at(i_element));
229 chargeVector(0) += log_charge;
230 chargeVector(1) += stripPositions_shifted.at(i_element) * log_charge;
231 chargeVector(2) += std::pow(stripPositions_shifted.at(i_element), 2) * log_charge;
232 }
233 elementPosMatrix(1,1) = elementPosMatrix(0,2);
234
235 // If the matrix is singular then the reconstruction method will fail
236 if (elementPosMatrix.determinant() == 0) {
237 if (msgLvl(MSG::VERBOSE)) {
238 std::stringstream sstr{};
239 for (const Muon::sTgcPrepData& prd : cluster) {
240 sstr << m_stgcIdHelper.print_to_string(prd.identify())
241 << ", local pos: ("<< prd.localPosition().x() << "," << prd.localPosition().y()
242 << "), charge: " << prd.charge() << ", time: " << static_cast<int>(prd.time())
243 << std::endl;
244 }
245 ATH_MSG_VERBOSE("Failed to compute the position of the cluster..." << std::endl << sstr.str());
246 }
247 return std::nullopt;
248 }
249
250 // Solve the matrix equation to find the Caruana parameters
251 Amg::Vector3D caruanaPars = (elementPosMatrix.inverse()) * chargeVector;
252
253 // The 3rd parameter is related to the standard deviation: sigma = sqrt(-1 / (2 * caruanaPars(2))).
254 // Hence use the Caruana method if the std dev is small i.e. < 3*strip_pitch or caruanaPars(2) > -0.005,
255 // or if std dev is not physical i.e. caruanaPars(2) >= 0
256 double reconstructedPosX{0.};
257 if (caruanaPars(2) < -0.005) {
258 reconstructedPosX = clusterCenter - caruanaPars(1) / (2*caruanaPars(2));
259 } else {
260 if (msgLvl(MSG::VERBOSE)) {
261 std::stringstream sstr{};
262 for (const Muon::sTgcPrepData& prd : cluster) {
263 sstr << m_stgcIdHelper.print_to_string(prd.identify())
264 << ", local pos: ("<< prd.localPosition().x() << "," << prd.localPosition().y()
265 << "), charge: " << prd.charge() << ", time: " << static_cast<int>(prd.time())
266 << std::endl;
267 }
268 ATH_MSG_VERBOSE("Reject cluster with large standard deviation..." << std::endl << sstr.str());
269 }
270 return std::nullopt;
271 }
272
273 // Find the channel that the cluster position reconstructs on top of and set the cluster id to its id
274 double minCenterDistance = 9999.99;
275 int channelIndex = 0;
276 for (size_t i_elem = 0; i_elem < cluster.size(); i_elem++) {
277 double strip_localPos = cluster.at(i_elem).localPosition().x();
278 if (minCenterDistance > abs(strip_localPos - reconstructedPosX)) {
279 minCenterDistance = abs(strip_localPos - reconstructedPosX);
280 channelIndex = i_elem;
281 }
282 }
283 const Identifier clusterId = cluster.at(channelIndex).identify();
284
285 // Get the sTGC readoutElement and the strip pitch
286 const MuonGM::sTgcReadoutElement* detEl = detManager->getsTgcReadoutElement(clusterId);
287 const double stripPitch = detEl->channelPitch(clusterId);
288
289 // If the reconstructed cluster position is far, such as one strip pitch, from any strip position in the cluster,
290 // then the Caruana reconstruction method isn't a good method to use.
291 if (minCenterDistance > stripPitch) {
292 if (msgLvl(MSG::VERBOSE)) {
293 std::stringstream sstr{};
294 for (const Muon::sTgcPrepData& prd : cluster) {
295 sstr << m_stgcIdHelper.print_to_string(prd.identify())
296 << ", local pos: ("<< prd.localPosition().x() << "," << prd.localPosition().y()
297 << "), charge: " << prd.charge() << ", time: " << static_cast<int>(prd.time())
298 << std::endl;
299 }
300 ATH_MSG_VERBOSE("Reject cluster since its position is more than " << stripPitch << " mm too far from the nearest strip..."
301 << std::endl << sstr.str());
302 }
303 return std::nullopt;
304 }
305
306 // We denote caruanaPars = (a, b, c) and find the error on the b and c components
307 double gamma0 = 0;
308 double gamma2 = 0;
309 double gamma4 = 0;
310 for (size_t i_strip = 0; i_strip < stripPositions_shifted.size(); i_strip++) {
311 gamma0 += 1;
312 gamma2 += std::pow(stripPositions_shifted.at(i_strip), 2);
313 gamma4 += std::pow(stripPositions_shifted.at(i_strip), 4);
314 }
315
316 // We also need the tan(theta) of the cluster
317 Amg::Vector3D globPos(0., 0., 0.);
318 detEl->stripGlobalPosition(clusterId, globPos);
319 double tan_theta = std::hypot(globPos.x(), globPos.y()) / globPos.z();
320 double spreadFactor = std::hypot(positionResolution, angularResolution * tan_theta);
321
322 double sigma_b = spreadFactor / std::sqrt(gamma2);
323 double sigma_c = spreadFactor * std::sqrt(gamma0 / (gamma0 * gamma4 - gamma2 * gamma2));
324
325 // Now propagate the Uncertainty to find the uncertainty on the mean
326 double sigmaSq = std::pow((1 / (2 * caruanaPars(2))) * sigma_b, 2) +
327 std::pow((caruanaPars(1) / (2 * caruanaPars(2) * caruanaPars(2))) * sigma_c, 2);
328
329 // Caruana method fails, if the uncertainty on the mean position is too large such as greater than 2mm
330 if (sigmaSq > 4) {
331 if (msgLvl(MSG::VERBOSE)) {
332 std::stringstream sstr{};
333 for (const Muon::sTgcPrepData& prd : cluster) {
334 sstr << m_stgcIdHelper.print_to_string(prd.identify())
335 << ", local pos: ("<< prd.localPosition().x() << "," << prd.localPosition().y()
336 << "), charge: " << prd.charge() << ", time: " << static_cast<int>(prd.time())
337 << std::endl;
338 }
339 ATH_MSG_VERBOSE("Reject cluster with large error on the mean position..." << std::endl << sstr.str());
340 }
341 return std::nullopt;
342 }
343
344
345 Amg::Vector3D globalClusterPos {detEl->surface(clusterId).transform()*Amg::Vector3D{reconstructedPosX, 0,0}};
346 Amg::Vector3D clusDir{ NswClustering::toLocal(detEl->surface(clusterId), globalClusterPos) };
347
348 NswErrorCalibData::Input errorCalibIn{};
349 errorCalibIn.stripId = clusterId;
350 errorCalibIn.clusterAuthor = static_cast<unsigned>(sTgcPrepData::Author::Caruana);
351 errorCalibIn.clusterError = std::sqrt(sigmaSq);
352 errorCalibIn.locPhi = clusDir.phi();
353 errorCalibIn.locTheta = clusDir.theta();
354 errorCalibIn.localPos = Amg::Vector2D{reconstructedPosX, 0};
355 errorCalibIn.clusterSize = cluster.size();
356
357 const double localUncertainty = m_errorCalibData.clusterUncertainty(errorCalibIn);
358
359 ATH_MSG_DEBUG("Reconstructed a cluster using the Caruana method,"
360 << " cluster Id: " << m_stgcIdHelper.print_to_string(clusterId)
361 << ", mean position = " << reconstructedPosX
362 << ", uncertainty = " << std::sqrt(sigmaSq));
363
364 return std::make_optional<Muon::STgcClusterPosition>(clusterId, reconstructedPosX, localUncertainty*localUncertainty);
365}
#define ATH_MSG_VERBOSE(x)
#define ATH_MSG_WARNING(x)
#define ATH_MSG_DEBUG(x)
#define AmgSymMatrix(dim)
static Double_t a
bool msgLvl(const MSG::Level lvl) const
Test the output level.
AthMessaging(IMessageSvc *msgSvc, const std::string &name)
Constructor.
virtual const Trk::PlaneSurface & surface() const override
access to chamber surface (phi orientation), uses the first gas gap
The MuonDetectorManager stores the transient representation of the Muon Spectrometer geometry and pro...
const sTgcReadoutElement * getsTgcReadoutElement(const Identifier &id) const
access via extended identifier (requires unpacking)
An sTgcReadoutElement corresponds to a single STGC module; therefore typicaly a barrel muon station c...
double channelPitch(const Identifier &id) const
Channel pitch.
bool stripGlobalPosition(const Identifier &id, Amg::Vector3D &gpos) const
std::optional< STgcClusterPosition > weightedAverage(const std::vector< sTgcPrepData > &cluster, const double resolution, bool isStrip) const
Compute the cluster position using the weighted average method.
std::array< std::vector< sTgcPrepData >, 8 > sortSTGCPrdPerLayer(std::vector< sTgcPrepData > &&stripPrds) const
Separate the sTGC PRDs by layer, from 0 to 7, and sort the PRDs per layer in ascending order of strip...
STgcClusterBuilderCommon(const sTgcIdHelper &idHelper, const NswErrorCalibData &errorCalibData)
Constructor.
std::vector< std::vector< sTgcPrepData > > findStripCluster(std::vector< sTgcPrepData > &&strips, const int maxMissingStrip) const
Find strip clusters, assuming the input vector of PRDs are sorted in ascending order of strip number.
const NswErrorCalibData & m_errorCalibData
std::optional< STgcClusterPosition > caruanaGaussianFitting(const std::vector< sTgcPrepData > &cluster, const double positionResolution, const double angularResolution, const MuonGM::MuonDetectorManager *detManager) const
Method to fit analytically a cluster to a Gaussian function to obtain the position of the cluster The...
Class to represent sTgc measurements.
const Amg::Transform3D & transform() const
Returns HepGeom::Transform3D by reference.
std::string toString(const Translation3D &translation, int precision=4)
GeoPrimitvesToStringConverter.
Eigen::Matrix< double, 2, 1 > Vector2D
Eigen::Matrix< double, 3, 1 > Vector3D
Amg::Vector3D toLocal(const Amg::Transform3D &toLocalTrans, const Amg::Vector3D &dir)
Rotates a direction vector into a local frame: x-axis : Parallell to the radial direction of the dete...
void sort(typename DataModel_detail::iterator< DVL > beg, typename DataModel_detail::iterator< DVL > end)
Specialization of sort for DataVector/List.
Helper struct to be parsed to the object to derive the specific error of the cluster.
unsigned int clusterSize
Cluster size.
double locTheta
Direction of the muon in the local coordinate frame.
double clusterError
cluster error as calculated by the cluster builder tool
Amg::Vector2D localPos
Local position on the surface.
Identifier stripId
Identifier of the strip.
uint8_t clusterAuthor
Author of the cluster.
double locPhi
Azimuthal angle in the local frame.