Project
Loading...
Searching...
No Matches
Tracker.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
16#include "MIDTracking/Tracker.h"
17
18#include <cmath>
19#include <functional>
20#include <stdexcept>
21
22#include "Framework/Logger.h"
25
26namespace o2
27{
28namespace mid
29{
30
31//______________________________________________________________________________
32Tracker::Tracker(const GeometryTransformer& geoTrans) : mTransformer(geoTrans)
33{
35}
36
37//______________________________________________________________________________
38bool Tracker::init(bool keepAll)
39{
41
42 if (keepAll) {
43 mFollowTrack = &Tracker::followTrackKeepAll;
44 } else {
45 mFollowTrack = &Tracker::followTrackKeepBest;
46 }
47
48 mImpactParamCut = TrackerParam::Instance().impactParamCut;
49 mSigmaCut = TrackerParam::Instance().sigmaCut;
50 mMaxChi2 = 2. * mSigmaCut * mSigmaCut;
51
52 return true;
53}
54
55//______________________________________________________________________________
56int Tracker::getFirstNeighbourRPC(int rpc) const
57{
59 return (rpc == 0) ? rpc : rpc - 1;
60}
61
62//______________________________________________________________________________
63int Tracker::getLastNeighbourRPC(int rpc) const
64{
66 return (rpc == 8) ? rpc : rpc + 1;
67}
68
69//______________________________________________________________________________
70bool Tracker::loadClusters(gsl::span<const Cluster>& clusters)
71{
73
74 for (auto& cl : clusters) {
75 int deId = cl.deId;
76 // This needs to be done before adding the element to mClusters
77 mClusterIndexes[deId].emplace_back(mClusters.size());
78 const auto& position = mTransformer.localToGlobal(deId, cl.xCoor, cl.yCoor);
79 mClusters.emplace_back(cl);
80 mClusters.back().xCoor = position.x();
81 mClusters.back().yCoor = position.y();
82 mClusters.back().zCoor = position.z();
83 }
84
85 return (clusters.size() > 0);
86}
87
88//______________________________________________________________________________
89void Tracker::process(gsl::span<const Cluster> clusters, gsl::span<const ROFRecord> rofRecords)
90{
93 mClusters.clear();
94 mTracks.clear();
95 mTrackROFRecords.clear();
96 mClusterROFRecords.clear();
97 for (auto& rofRecord : rofRecords) {
98 auto firstTrackEntry = mTracks.size();
99 auto firstClusterEntry = mClusters.size();
100 process(clusters.subspan(rofRecord.firstEntry, rofRecord.nEntries), true);
101 auto nTrackEntries = mTracks.size() - firstTrackEntry;
102 mTrackROFRecords.emplace_back(rofRecord, firstTrackEntry, nTrackEntries);
103 auto nClusterEntries = mClusters.size() - firstClusterEntry;
104 mClusterROFRecords.emplace_back(rofRecord, firstClusterEntry, nClusterEntries);
105 }
106}
107
108//______________________________________________________________________________
109void Tracker::process(gsl::span<const Cluster> clusters, bool accumulate)
110{
113
114 // Reset cluster and tracks information
115 for (auto& clIdx : mClusterIndexes) {
116 clIdx.clear();
117 }
118
119 if (!accumulate) {
120 mClusters.clear();
121 mTracks.clear();
122 }
123
124 // Load the digits to get the fired pads
125 if (loadClusters(clusters)) {
126 mFirstTrackOffset = mTracks.size();
127 try {
128 // Right and left side can be processed in parallel
129 // Right inward
130 mTrackOffset = mTracks.size();
131 mNTracksStep1 = 0;
132 processSide(true, true);
133 mNTracksStep1 = mTracks.size() - mTrackOffset;
134 // Right outward
135 processSide(true, false);
136 // Left inward
137 mTrackOffset = mTracks.size();
138 mNTracksStep1 = 0;
139 processSide(false, true);
140 mNTracksStep1 = mTracks.size() - mTrackOffset;
141 // left outward
142 processSide(false, false);
143 } catch (std::exception const& e) {
144 LOG(error) << e.what() << " --> abort";
145 mTracks.erase(mTracks.begin() + mFirstTrackOffset, mTracks.end());
146 }
147 }
148}
149
150//______________________________________________________________________________
151void Tracker::processSide(bool isRight, bool isInward)
152{
154 int firstCh = (isInward) ? 3 : 0;
155 int secondCh = (isInward) ? 2 : 1;
156 int rpcOffset1 = detparams::getDEId(isRight, firstCh, 0);
157 int rpcOffset2 = detparams::getDEId(isRight, secondCh, 0);
158
159 // loop on RPCs in first plane
160 Track track;
161 for (int irpc = 0; irpc < 9; ++irpc) {
162 int deId1 = rpcOffset1 + irpc;
163 for (auto clIdx1 : mClusterIndexes[deId1]) {
164 // loop on clusters of the RPC in the first plane
165 auto& cl1 = mClusters[clIdx1];
166 int firstRpc = getFirstNeighbourRPC(irpc);
167 int lastRpc = getLastNeighbourRPC(irpc);
168 for (int irpc2 = firstRpc; irpc2 <= lastRpc; ++irpc2) {
169 // loop on (neighbour) RPCs in second plane
170 int deId2 = rpcOffset2 + irpc2;
171 for (auto clIdx2 : mClusterIndexes[deId2]) {
172 // loop on clusters of the RPC in the second plane
173 auto& cl2 = mClusters[clIdx2];
174
175 if (!makeTrackSeed(track, cl1, cl2)) {
176 continue;
177 }
178
179 track.setClusterMatchedUnchecked(firstCh, clIdx1);
180 track.setClusterMatchedUnchecked(secondCh, clIdx2);
181 track.setClusterMatchedUnchecked(3 - firstCh, -1);
182 track.setClusterMatchedUnchecked(3 - secondCh, -1);
183 std::invoke(mFollowTrack, this, track, isRight, isInward);
184 } // loop on clusters in second plane
185 } // loop on RPCs in second plane
186 } // loop on clusters in first plane
187 } // loop on RPCs in first plane
188}
189
190//______________________________________________________________________________
191bool Tracker::makeTrackSeed(Track& track, const Cluster& cl1, const Cluster& cl2) const
192{
194
195 // Check that the 3/4 requirement on both cathodes could be satisfied with this cluster combination
196 if (!(cl1.isFired(0) || cl2.isFired(0)) || !(cl1.isFired(1) || cl2.isFired(1))) {
197 return false;
198 }
199
200 // First check if the delta_x between the two clusters is not too large
201 double dZ = cl2.zCoor - cl1.zCoor;
202 double dZ2 = dZ * dZ;
203 double nonBendingSlope = (cl2.xCoor - cl1.xCoor) / dZ;
204 double nonBendingImpactParam = std::abs(cl2.xCoor - cl2.zCoor * nonBendingSlope);
205 double nonBendingImpactParamErr = std::sqrt(
206 (cl1.zCoor * cl1.zCoor * cl2.getEX2() + cl2.zCoor * cl2.zCoor * cl1.getEX2()) / dZ2);
207 if ((nonBendingImpactParam - mSigmaCut * nonBendingImpactParamErr) > mImpactParamCut) {
208 return false;
209 }
210
211 // Then start making the track (from 2 points)
212 track.setPosition(cl2.xCoor, cl2.yCoor, cl2.zCoor);
213 track.setDirection(nonBendingSlope, (cl2.yCoor - cl1.yCoor) / dZ, 1.);
214 track.setCovarianceParameters(cl2.getEX2(), // x-x
215 cl2.getEY2(), // y-y
216 (cl1.getEX2() + cl2.getEX2()) / dZ2, // slopeX-slopeX
217 (cl1.getEY2() + cl2.getEY2()) / dZ2, // slopeY-slopeY
218 cl2.getEX2() / dZ, // x-slopeX
219 cl2.getEY2() / dZ); // y-slopeY
220 track.setChi2(0.);
221 track.setNDF(0);
222
223 // Flag missing cathode information
224 track.resetIncomplete();
225 for (int cathode = 0; cathode < 2; ++cathode) {
226 if (!cl1.isFired(cathode) || !cl2.isFired(cathode)) {
227 track.setIncomplete(cathode);
228 }
229 }
230
231 return true;
232}
233
234//______________________________________________________________________________
235void Tracker::followTrackKeepAll(Track& track, bool isRight, bool isInward)
236{
246
247 std::unordered_set<int> excludedClusters;
248
249 if (isInward) {
250
251 // look for clusters in both chambers or in first chamber only
252 findAllClusters(track, isRight, 1, 0, 8, 0, excludedClusters, true);
253
254 // if the first chamber can be skipped, look for clusters in the second chamber only
255 if (skipOneChamber(track)) {
256 findAllClusters(track, isRight, 0, 0, 8, -1, excludedClusters, true);
257 }
258
259 } else if (skipOneChamber(track)) {
260
261 // exclude clusters from tracks already found by inward tracking (with 4/4 clusters)
262 excludeUsedClusters(track, 1, 0, excludedClusters);
263
264 // if the second chamber can be skipped, look for clusters in first chamber only
265 findAllClusters(track, isRight, 2, 0, 8, -1, excludedClusters, true);
266
267 // if the first chamber can be skipped, look for clusters in the second chamber only
268 findAllClusters(track, isRight, 3, 0, 8, -1, excludedClusters, true);
269 }
270}
271
272//______________________________________________________________________________
273bool Tracker::findAllClusters(const Track& track, bool isRight, int chamber, int firstRPC, int lastRPC, int nextChamber,
274 std::unordered_set<int>& excludedClusters, bool excludeClusters)
275{
280
281 int rpcOffset = detparams::getDEId(isRight, chamber, 0);
282 bool clusterFound = false;
283 Track newTrack;
284
285 for (int irpc = firstRPC; irpc <= lastRPC; ++irpc) {
286 int deId = rpcOffset + irpc;
287 for (auto clIdx : mClusterIndexes[deId]) {
288
289 // skip excluded clusters
290 if (excludeClusters && excludedClusters.count(clIdx) > 0) {
291 continue;
292 }
293
294 // try to attach this cluster
295 if (!tryOneCluster(track, chamber, clIdx, newTrack)) {
296 continue;
297 }
298 clusterFound = true;
299
300 // add this cluster to the list to be excluded later on
301 if (!excludeClusters) {
302 excludedClusters.emplace(clIdx);
303 }
304
305 // store this track extrapolated to MT11 if it reached the last chamber,
306 // or if no compatible clusters are found in the next chamber and it can be skipped
307 if (nextChamber < 0 || (!findAllClusters(newTrack, isRight, nextChamber, getFirstNeighbourRPC(irpc),
308 getLastNeighbourRPC(irpc), -1, excludedClusters, false) &&
309 skipOneChamber(newTrack))) {
310 if (mTracks.size() - mFirstTrackOffset >= TrackerParam::Instance().maxCandidates) {
311 throw std::length_error(std::string("Too many track candidates (") +
312 (mTracks.size() - mFirstTrackOffset) + ")");
313 }
314 newTrack.propagateToZ(SMT11Z);
315 mTracks.emplace_back(newTrack);
316 }
317 }
318 }
319
320 return clusterFound;
321}
322
323//______________________________________________________________________________
324void Tracker::followTrackKeepBest(Track& track, bool isRight, bool isInward)
325{
328
329 // This algorithm is works generally well,
330 // but tests show that it can fail reconstructing the generated track
331 // in case of two tracks close in space.
332 // The main issue is that we do not have charge distribution information, and the strips are large,
333 // so it can happen that the track obtained by combining the hits of the first track in MT1
334 // and of the second track in MT2 can sometimes have a better chi2 of the real tracks.
335 // Since this algorithm only keeps the best track, it might chose the fake instead of the real track.
336 // The algorithm is however fast, so it should be preferred in case of low tracks multiplicity
337
338 std::array<int, 2> chamberOrder;
339 chamberOrder[0] = isInward ? 1 : 2;
340 chamberOrder[1] = isInward ? 0 : 3;
341
342 Track bestTrack;
343
344 // Look for clusters in both chambers or in first chamber only
345 findNextCluster(track, isRight, isInward, chamberOrder[0], 0, 8, bestTrack);
346
347 // If no track with 4 clusters was found and first chamber can be skipped, look for clusters in second chamber only
348 if (bestTrack.getNDF() != 4 && skipOneChamber(track)) {
349 findNextCluster(track, isRight, isInward, chamberOrder[1], 0, 8, bestTrack);
350 }
351
352 if (bestTrack.getNDF() > 0) {
353 // Extrapolate to MT11
354 bestTrack.propagateToZ(SMT11Z);
355
356 // Add the track if it is not compatible or better than the ones we already have
357 tryAddTrack(bestTrack);
358 }
359}
360
361//______________________________________________________________________________
362void Tracker::findNextCluster(const Track& track, bool isRight, bool isInward, int chamber, int firstRPC, int lastRPC,
363 Track& bestTrack) const
364{
366 int nextChamber = (isInward) ? chamber - 1 : chamber + 1;
367 int rpcOffset = detparams::getDEId(isRight, chamber, 0);
368 Track newTrack;
369 for (int irpc = firstRPC; irpc <= lastRPC; ++irpc) {
370 int deId = rpcOffset + irpc;
371 for (auto clIdx : mClusterIndexes[deId]) {
372 if (!tryOneCluster(track, chamber, clIdx, newTrack)) {
373 continue;
374 }
375 if (nextChamber >= 0 && nextChamber <= 3) {
376 // We found a cluster in the first chamber of the station
377 // We search for a cluster in the last chamber, this time limiting to the RPC above and below this one
378 findNextCluster(newTrack, isRight, isInward, nextChamber,
379 getFirstNeighbourRPC(irpc), getLastNeighbourRPC(irpc), bestTrack);
380
381 // Don't go further with this track if the last chamber cannot be skipped
382 if (!skipOneChamber(newTrack)) {
383 continue;
384 }
385 }
386 if (newTrack.getNDF() > bestTrack.getNDF() ||
387 (newTrack.getNDF() == bestTrack.getNDF() && newTrack.getChi2() < bestTrack.getChi2())) {
388 // Prefer tracks with a larger number of attached clusters, even if the chi2 is worse
389 bestTrack = newTrack;
390 }
391 } // loop on clusters
392 } // loop on RPC
393}
394
395//______________________________________________________________________________
396bool Tracker::tryOneCluster(const Track& track, int chamber, int clIdx, Track& newTrack) const
397{
401
402 auto& cl = mClusters[clIdx];
403 if ((track.isIncomplete(0) && !cl.isFired(0)) || (track.isIncomplete(1) && !cl.isFired(1))) {
404 return false;
405 }
406
407 newTrack = track;
408 newTrack.propagateToZ(cl.zCoor);
409
410 double diff[2] = {cl.xCoor - newTrack.getPositionX(), cl.yCoor - newTrack.getPositionY()};
411 double err2[2] = {newTrack.getCovarianceParameter(Track::CovarianceParamIndex::VarX) + cl.getEX2(),
412 newTrack.getCovarianceParameter(Track::CovarianceParamIndex::VarY) + cl.getEY2()};
413 double chi2 = diff[0] * diff[0] / err2[0] + diff[1] * diff[1] / err2[1];
414 if (chi2 > mMaxChi2) {
415 return false;
416 }
417
418 runKalmanFilter(newTrack, cl);
419 newTrack.setChi2(track.getChi2() + chi2);
420 newTrack.setNDF(track.getNDF() + 2);
421 newTrack.setClusterMatchedUnchecked(chamber, clIdx);
422
423 // Flag missing cathode information
424 for (int cathode = 0; cathode < 2; ++cathode) {
425 if (!cl.isFired(cathode)) {
426 newTrack.setIncomplete(cathode);
427 }
428 }
429
430 return true;
431}
432
433//__________________________________________________________________________
434void Tracker::runKalmanFilter(Track& track, const Cluster& cluster) const
435{
437
438 double pos[2] = {track.getPositionX(), track.getPositionY()};
439 double dir[2] = {track.getDirectionX(), track.getDirectionY()};
440 const std::array<float, 6>& covParams = track.getCovarianceParameters();
441 double clusPos[2] = {cluster.xCoor, cluster.yCoor};
442 double clusterSigma[2] = {cluster.getEX2(), cluster.getEY2()};
443
444 std::array<float, 6> newCovParams;
445 double newPos[2], newDir[2];
446 for (int idx = 0; idx < 2; ++idx) {
447 int slopeIdx = idx + 2;
448 int covIdx = idx + 4;
449
450 double den = clusterSigma[idx] + covParams[idx];
451 assert(den != 0.);
452 // if ( den == 0. ) return 2.*mMaxChi2;
453
454 // Compute the new covariance
455 // cov -> (W+U)^-1
456 // where W = (old_cov)^-1
457 // and U = inverse of uncertainties of cluster
458 // s_x^2 -> s_x^2 * cl_s_x^2 / (s_x^2 + cl_s_x^2)
459 newCovParams[idx] = covParams[idx] * clusterSigma[idx] / den;
460 // cov(x,slopeX) -> cov(x,slopeX) * cl_s_x^2 / (s_x^2 + cl_s_x^2)
461 newCovParams[covIdx] = covParams[covIdx] * clusterSigma[idx] / den;
462 // s_slopeX^2 -> s_slopeX^2 - cov(x,slopeX)^2 / (s_x^2 + cl_s_x^2)
463 newCovParams[slopeIdx] = covParams[slopeIdx] - covParams[covIdx] * covParams[covIdx] / den;
464
465 double diff = clusPos[idx] - pos[idx];
466
467 // New parameters: p' = ((W+U)^-1)U(m-p) + p
468 // x -> x + ( cl_x - x ) * s_x^2 / (s_x^2 + cl_s_x^2)
469 newPos[idx] = pos[idx] + diff * covParams[idx] / den;
470 // slopeX -> slopeX + ( cl_x - x ) * cov(x,slopeX) / (s_x^2 + cl_s_x^2)
471 newDir[idx] = dir[idx] + diff * covParams[covIdx] / den;
472 }
473
474 // Save the new parameters
475 track.setPosition(newPos[0], newPos[1], cluster.zCoor);
476 track.setDirection(newDir[0], newDir[1], 1.);
477
478 // Save the new parameters covariance matrix
479 track.setCovarianceParameters(newCovParams);
480}
481
482//______________________________________________________________________________
483void Tracker::tryAddTrack(const Track& track)
484{
490
491 // We divide the chi2 by two since we want to consider only the uncertainty
492 // on one of the two tracks. We further reduce to 0.4 since we want to account
493 // for the case where one of the two reconstructed tracks has a much better precision
494 // of the other
495 float chi2Cut = 0.4 * mSigmaCut * mSigmaCut;
496 for (auto checkTrack = mTracks.begin() + mTrackOffset; checkTrack != mTracks.end(); ++checkTrack) {
497 int nCommonClusters = 0;
498 for (int ich = 0; ich < 4; ++ich) {
499 if (track.getClusterMatchedUnchecked(ich) == checkTrack->getClusterMatchedUnchecked(ich)) {
500 ++nCommonClusters;
501 }
502 }
503 if (nCommonClusters == 4) {
504 return;
505 }
506 if (nCommonClusters == 3 && track.isCompatible(*checkTrack, chi2Cut)) {
507 // The new track is compatible with an existing one
508 if (track.getNDF() > checkTrack->getNDF() ||
509 (track.getNDF() == checkTrack->getNDF() && track.getChi2() < checkTrack->getChi2())) {
510 // The new track has more cluster or is more precise than the old one: replace it!
511 *checkTrack = track;
512 }
513 return;
514 }
515 }
516
517 // The new track is not compatible with the previous ones: keep it
518 mTracks.emplace_back(track);
519}
520
521//______________________________________________________________________________
522void Tracker::excludeUsedClusters(const Track& track, int ch1, int ch2, std::unordered_set<int>& excludedClusters) const
523{
526
527 int nTracks = mTrackOffset + mNTracksStep1;
528 for (int i = mTrackOffset; i < nTracks; ++i) {
529 const auto& tr = mTracks[i];
530 if (track.getClusterMatchedUnchecked(ch1) == tr.getClusterMatchedUnchecked(ch1) &&
531 track.getClusterMatchedUnchecked(ch2) == tr.getClusterMatchedUnchecked(ch2)) {
532 excludedClusters.emplace(tr.getClusterMatchedUnchecked(3 - ch1));
533 excludedClusters.emplace(tr.getClusterMatchedUnchecked(3 - ch2));
534 }
535 }
536}
537
538//______________________________________________________________________________
539bool Tracker::skipOneChamber(Track& track) const
540{
544
545 if (track.isIncomplete(0) || track.isIncomplete(1)) {
546 return false;
547 }
548
549 track.setIncomplete(0);
550 track.setIncomplete(1);
551
552 return true;
553}
554
555} // namespace mid
556} // namespace o2
Useful detector parameters for MID.
int32_t i
Configurable parameters for MID tracking.
Track reconstruction algorithm for MID.
uint16_t pos
Definition RawData.h:3
HMPID cluster implementation.
Definition Cluster.h:27
math_utils::Point3D< T > localToGlobal(int deId, const math_utils::Point3D< T > &position) const
This class defines the MID track.
Definition Track.h:30
@ VarX
Variance on X position.
@ VarY
Variance on Y position.
void setClusterMatchedUnchecked(int chamber, int id)
Definition Track.h:106
bool init(bool keepAll=false)
Definition Tracker.cxx:38
void process(gsl::span< const Cluster > clusters, bool accumulate=false)
Definition Tracker.cxx:109
Tracker(const GeometryTransformer &geoTrans)
Definition Tracker.cxx:32
o2::track::TrackParCov Track
std::optional< Chamber > chamber(int chamberId)
Definition Chamber.cxx:17
int getDEId(bool isRight, int chamber, int rpc)
std::vector< Cluster > clusters
a couple of static helper functions to create timestamp values for CCDB queries or override obsolete ...
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"