Project
Loading...
Searching...
No Matches
MatchCosmics.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
15#include "GPUO2InterfaceRefit.h"
29#include "ITStracking/IOUtils.h"
36#include "MathUtils/Utils.h"
39#include "TPCFastTransformPOD.h"
40#include <algorithm>
41#include <numeric>
42#include <unordered_map>
43
44using namespace o2::globaltracking;
45
48
49namespace
50{
51// energy-loss sign of the propagations: the propagator applies the loss along the direction of the track parameters, and the refit runs
52// from the bottom leg up through the top leg (parameters of an upward-moving particle); the cosmic muon flies from the top leg to the
53// bottom leg, so the sign is imposed along its flight
54constexpr int ELossGain = 1;
55constexpr int ELossLoss = -1;
56} // namespace
57
58//________________________________________________________
60{
61 updateTimeDependentParams();
62 mRecords.clear();
63 mWinners.clear();
64 mCosmicTracks.clear();
65 mCosmicTracksLbl.clear();
66
67 createSeeds(data);
68 mNSeedsNearBeam = 0;
69 int ntr = mSeeds.size();
70 const auto prop = o2::base::Propagator::Instance();
71 // propagate to DCA to origin. A VertexBase (origin, zero covariance) selects the TrackParCov overload of propagateToDCABxByBz:
72 // with a Point3D the TrackPar_t overload is used and only the parameters are propagated, the covariance stays the one at the
73 // track's reference X (TPC inner radius), which makes the y/snp cuts and the chi2 of checkPair far too tight.
75 for (int i = 0; i < ntr; i++) {
76 auto& trc = mSeeds[i];
77 if (trc.matchID != Reject) {
78 // a cosmic muon flies downward: along a leg whose outward direction points up (top leg) the inward propagation follows the flight,
79 // so the energy is lost; along the bottom leg it goes back in the flight, so the energy is gained
80 std::array<float, 3> momentum{};
81 trc.getPxPyPzGlo(momentum);
82 const int eLossSign = momentum[1] > 0.f ? ELossLoss : ELossGain;
83 if (!prop->propagateToDCABxByBz(v, trc, mMatchParams->maxStep, mMatchParams->matCorr, nullptr, nullptr, eLossSign)) {
84 trc.matchID = Reject; // reject track
85 continue;
86 }
87 if (std::abs(trc.getY()) < mMatchParams->minSeedDCAxy || trc.getY() * trc.getY() < mMatchParams->minSeedDCAxyNSigma * mMatchParams->minSeedDCAxyNSigma * trc.getSigmaY2()) {
88 // passes close to the beam line, absolutely or within its errors: indistinguishable from collision tracks, unless a TOF flight pair
89 // confirms the cosmic (TPC-only legs above the looser minSeedDCAxyTOF cuts, used in TOF-confirmed pairs only)
90 const bool nearBeam = mMatchParams->tofFlightSelection && mMatchParams->minSeedDCAxyTOF >= 0.f && trc.origID.getSource() == GTrackID::TPC &&
91 std::abs(trc.getY()) >= mMatchParams->minSeedDCAxyTOF &&
92 trc.getY() * trc.getY() >= mMatchParams->minSeedDCAxyNSigmaTOF * mMatchParams->minSeedDCAxyNSigmaTOF * trc.getSigmaY2();
93 if (!nearBeam) {
94 trc.matchID = Reject;
95 continue;
96 }
97 trc.nearBeam = true;
98 mNSeedsNearBeam++;
99 }
100 if (mMatchParams->dcaCutChi2[trc.origID.getSource()] > 0.f && mUsePVInfo && trc.vtIDMin >= 0 && (std::abs(trc.getY()) < mMatchParams->fiducialRIP && std::abs(trc.getZ()) < mMatchParams->fiducialZIP)) {
101 // do the propagation only if we are in the fiducial IP range.
102 for (int iv = trc.vtIDMin; iv <= trc.vtIDMax; iv++) { // vtIDMax is the last compatible vertex (inclusive); vtIDMin < 0: no compatible vertex
103 const auto& pv = data.getPrimaryVertex(iv);
104 o2::track::TrackParCov trcatPV(trc);
106 if (!trcatPV.propagateToDCA(pv, mBz, &dca)) {
107 trc.matchID = Reject;
108 break;
109 }
110 if (trc.origID.getSource() == GTrackID::TPC) { // correct the track Z position for the vertex time
111 const auto& trcTPC = data.getTPCTrack(trc.origID);
112 float deltaZ = trcTPC.hasBothSidesClusters() ? 0.f : (pv.getTimeStamp().getTimeStamp() - trcTPC.getTime0() * 8 * o2::constants::lhc::LHCBunchSpacingMUS) * mTPCVDrift;
113 dca.setZ(dca.getZ() + (trcTPC.hasASideClustersOnly() ? deltaZ : -deltaZ));
114 }
115 dca.addCov({mMatchParams->systSigma2[0], 0.f, mMatchParams->systSigma2[1]});
116 if (dca.calcChi2() < mMatchParams->dcaCutChi2[trc.origID.getSource()]) {
117 trc.matchID = Reject;
118 break;
119 }
120 }
121 }
122 }
123 }
124 // TPC refitter of this TF: same-side TPC-only legs compared at a common time (checkPair) and the refit of the winners
125 std::unique_ptr<o2::gpu::GPUO2InterfaceRefit> tpcRefitter;
126 if (data.inputsTPCclusters) {
127 tpcRefitter = std::make_unique<o2::gpu::GPUO2InterfaceRefit>(&data.inputsTPCclusters->clusterIndex, mTPCCorrMaps, mBz, data.getTPCTracksClusterRefs().data(), 0,
128 data.clusterShMapTPC.data(), data.occupancyMapTPC.data(), data.occupancyMapTPC.size(), nullptr,
130 }
131 mTPCRefitter = tpcRefitter.get();
132 mRecoData = &data;
133 mNRefitsCommonTime = 0;
134 mNTOFConfirmed = 0;
135 mNTOFFallbacks = 0;
136 mNNearBeamConfirmed = 0;
137 if (mMatchParams->tofFlightSelection) {
138 prepareTOFClusters(data);
139 }
140
141 // sort in time bracket lower edge, putting rejected tracks in the end
142 std::vector<int> sortID(ntr);
143 std::iota(sortID.begin(), sortID.end(), 0);
144 std::sort(sortID.begin(), sortID.end(), [this](int a, int b) { return mSeeds[a].matchID == Reject ? false : (mSeeds[b].matchID == Reject ? true : (mSeeds[a].tBracket.getMin() < mSeeds[b].tBracket.getMin())); });
145 int lastValid = ntr - 1;
146 for (; lastValid >= 0; lastValid--) {
147 if (mSeeds[sortID[lastValid]].matchID != Reject) {
148 break;
149 }
150 }
151 ntr = lastValid >= 0 ? lastValid + 1 : 0;
152 LOGP(info, "Collected {} seeds, validated: {}", mSeeds.size(), ntr);
153 sortID.resize(ntr);
154 for (int i = 0; i < ntr; i++) {
155 for (int j = i + 1; j < ntr; j++) {
156 if (checkPair(sortID[i], sortID[j]) == RejTime) {
157 break;
158 }
159 }
160 }
161 if (mMatchParams->tofFlightSelection) {
162 LOGP(info, "{} accepted pairs confirmed by a TOF flight pair", mNTOFConfirmed);
163 }
164 if (mNSeedsPVContributors) {
165 LOGP(info, "{} seeds rejected as (part of) a primary-vertex contributor", mNSeedsPVContributors);
166 }
167 if (mNSeedsNearBeam) {
168 LOGP(info, "{} seeds near the beam line used in TOF-confirmed pairs only, {} such pairs confirmed", mNSeedsNearBeam, mNNearBeamConfirmed);
169 }
170
171 selectWinners();
172 refitWinners(data);
173 if (mNRefitsCommonTime) {
174 LOGP(info, "{} seeds refitted at the common time of same-side pairs", mNRefitsCommonTime);
175 }
176 if (mNTOFFallbacks) {
177 LOGP(info, "{} TOF-confirmed winners failed the refit at the TOF time and were refitted at their time without TOF", mNTOFFallbacks);
178 }
179 mTPCRefitter = nullptr;
180 mRecoData = nullptr;
181
182 mTFCount++;
183}
184
185//________________________________________________________
186void MatchCosmics::refitWinners(const o2::globaltracking::RecoContainer& data)
187{
188 LOG(info) << "Refitting " << mWinners.size() << " winner matches";
189 int count = 0;
190 auto tpcTBinMUSInv = 1. / mTPCTBinMUS;
191 auto* tpcRefitter = mTPCRefitter; // created in process()
192
193 const auto& itsClusters = prepareITSClusters(data);
194 // RS FIXME: this is probably a temporary solution, since ITS tracking over boundaries will likely change the TrackITS format
195 std::vector<int> itsTracksROF;
196
197 const auto& itsTracksROFRec = data.getITSTracksROFRecords();
198 itsTracksROF.resize(data.getITSTracks().size());
199 for (unsigned irf = 0, cnt = 0; irf < itsTracksROFRec.size(); irf++) {
200 int ntr = itsTracksROFRec[irf].getNEntries();
201 for (int itr = 0; itr < ntr; itr++) {
202 itsTracksROF[cnt++] = irf;
203 }
204 }
205
206 auto refitITSTrack = [this, &data, &itsTracksROF, &itsClusters](o2::track::TrackParCov& trFit, GTrackID gidx, float& chi2, bool inward, int eLossSign) {
207 const auto& itsTrOrig = data.getITSTrack(gidx);
208 int nclRefit = 0, ncl = itsTrOrig.getNumberOfClusters(), rof = itsTracksROF[gidx.getIndex()];
209 const auto& itsTrackClusRefs = data.getITSTracksClusterRefs();
210 int clEntry = itsTrOrig.getFirstClusterEntry();
211 const auto propagator = o2::base::Propagator::Instance();
212 const auto geomITS = o2::its::GeometryTGeo::Instance();
213 int from = ncl - 1, to = -1, step = -1;
214 if (inward) {
215 from = 0;
216 to = ncl;
217 step = 1;
218 }
219 for (int icl = from; icl != to; icl += step) { // ITS clusters are referred in layer decreasing order
220 const auto& clus = itsClusters[itsTrackClusRefs[clEntry + icl]];
221 float alpha = geomITS->getSensorRefAlpha(clus.getSensorID()), x = clus.getX();
222 if (!trFit.rotate(alpha) || !propagator->propagateToX(trFit, x, propagator->getNominalBz(), this->mMatchParams->maxSnp, this->mMatchParams->maxStep, this->mMatchParams->matCorr, nullptr, eLossSign)) {
223 break;
224 }
225 chi2 += trFit.getPredictedChi2(clus);
226 if (!trFit.update(clus)) {
227 break;
228 }
229 nclRefit++;
230 }
231 return nclRefit == ncl ? ncl : -1;
232 };
233
234 // refit the legs of winner winRID at the time t0 [mus] (error dt) and add the cosmic; false: a refit, propagation or cut failed
235 auto refitWinner = [&](int winRID, float t0, float dt) {
236 const auto& rec = mRecords[winRID];
237 int poolEntryID[2] = {rec.id0, rec.id1};
238 o2::track::TrackParCov outerLegs[2] = {data.getTrackParamOut(mSeeds[rec.id0].origID), data.getTrackParamOut(mSeeds[rec.id1].origID)};
239 for (auto& leg : outerLegs) {
240 leg.setPID(o2::track::PID::Muon, true); // as the seeds
241 }
242 auto tOverlap = mSeeds[rec.id0].tBracket.getOverlap(mSeeds[rec.id1].tBracket);
243 auto pnt0 = outerLegs[0].getXYZGlo(), pnt1 = outerLegs[1].getXYZGlo();
244 int btm = 0, top = 1;
245 // we fit topward from bottom
246 if (pnt0.Y() > pnt1.Y()) {
247 btm = 1;
248 top = 0;
249 }
250 LOG(debug) << "Winner " << count++ << " Record " << winRID << " Partners:"
251 << " B: " << mSeeds[poolEntryID[btm]].origID << "/" << mSeeds[poolEntryID[btm]].origID.getSourceName()
252 << " U: " << mSeeds[poolEntryID[top]].origID << "/" << mSeeds[poolEntryID[top]].origID.getSourceName()
253 << " | T:" << tOverlap.asString();
254
255 float chi2 = 0;
256 int nclTot = 0;
257
258 // Start from bottom leg inward refit
259 o2::track::TrackParCov trCosm(mSeeds[poolEntryID[btm]]); // copy of the btm track
260 // The bottom leg needs refit only if it is an unconstrained TPC track, otherwise it is already refitted as inner param
261 if (mSeeds[poolEntryID[btm]].origID.getSource() == GTrackID::TPC) {
262 const auto& tpcTrOrig = data.getTPCTrack(mSeeds[poolEntryID[btm]].origID);
263 trCosm = outerLegs[btm];
264 trCosm.resetCovariance();
265 // in case of cosmics, constrain the momentum
266 if (!mFieldON) {
267 trCosm.setQ2Pt(-o2::track::kMostProbablePt);
268 }
269 int retVal = tpcRefitter->RefitTrackAsTrackParCov(trCosm, tpcTrOrig.getClusterRef(), t0 * tpcTBinMUSInv, &chi2, false, false, ELossGain); // inward refit, reset
270 if (retVal < 0) { // refit failed
271 LOG(debug) << "Inward refit of btm TPC track failed.";
272 return false;
273 }
274 nclTot += retVal;
275 LOG(debug) << "chi2 after btm TPC refit with " << retVal << " clusters : " << chi2 << " orig.chi2 was " << tpcTrOrig.getChi2();
276 } else { // just collect NClusters and chi2
277 // since we did not refit bottom track, we just invert its conventional q/pT in case of B=0, so that after the inversion it gets correct sign
278 if (!mFieldON) {
279 trCosm.setQ2Pt(-trCosm.getQ2Pt());
280 }
281 auto gidxListBtm = data.getSingleDetectorRefs(mSeeds[poolEntryID[btm]].origID);
282 if (gidxListBtm[GTrackID::TPC].isIndexSet()) {
283 const auto& tpcTrOrig = data.getTPCTrack(gidxListBtm[GTrackID::TPC]);
284 nclTot += tpcTrOrig.getNClusters();
285 chi2 += tpcTrOrig.getChi2();
286 }
287 if (gidxListBtm[GTrackID::ITS].isIndexSet()) {
288 const auto& itsTrOrig = data.getITSTrack(gidxListBtm[GTrackID::ITS]);
289 nclTot += itsTrOrig.getNClusters();
290 chi2 += itsTrOrig.getChi2();
291 }
292 }
293 trCosm.invert();
294 if (!trCosm.rotate(mSeeds[poolEntryID[top]].getAlpha()) ||
295 !o2::base::Propagator::Instance()->PropagateToXBxByBz(trCosm, mSeeds[poolEntryID[top]].getX(), mMatchParams->maxSnp, mMatchParams->maxStep, mMatchParams->matCorr, nullptr, ELossGain)) {
296 LOG(debug) << "Rotation/propagation of btm-track to top-track frame failed.";
297 return false;
298 }
299 // save bottom parameter at merging point
300 auto trCosmBtm = trCosm;
301 int nclBtm = nclTot;
302
303 // Continue with top leg outward refit
304 auto gidxListTop = data.getSingleDetectorRefs(mSeeds[poolEntryID[top]].origID);
305
306 // is there ITS sub-track?
307 if (gidxListTop[GTrackID::ITS].isIndexSet()) {
308 auto nclfit = refitITSTrack(trCosm, gidxListTop[GTrackID::ITS], chi2, false, ELossGain);
309 if (nclfit < 0) {
310 return false;
311 }
312 LOG(debug) << "chi2 after top ITS refit with " << nclfit << " clusters : " << chi2 << " orig.chi2 was " << data.getITSTrack(gidxListTop[GTrackID::ITS]).getChi2();
313 nclTot += nclfit;
314 } // ITS refit
315 //
316 if (gidxListTop[GTrackID::TPC].isIndexSet()) { // outward refit in TPC
317 // go to TPC boundary, if needed
318 if (trCosm.getX() * trCosm.getX() + trCosm.getY() * trCosm.getY() <= o2::constants::geom::XTPCInnerRef * o2::constants::geom::XTPCInnerRef) {
319 float xtogo = 0;
320 if (!trCosm.getXatLabR(o2::constants::geom::XTPCInnerRef, xtogo, mBz, o2::track::DirOutward) ||
321 !o2::base::Propagator::Instance()->PropagateToXBxByBz(trCosm, xtogo, mMatchParams->maxSnp, mMatchParams->maxStep, mMatchParams->matCorr, nullptr, ELossGain)) {
322 LOG(debug) << "Propagation to inner TPC boundary X=" << xtogo << " failed";
323 return false;
324 }
325 }
326 const auto& tpcTrOrig = data.getTPCTrack(gidxListTop[GTrackID::TPC]);
327 int retVal = tpcRefitter->RefitTrackAsTrackParCov(trCosm, tpcTrOrig.getClusterRef(), t0 * tpcTBinMUSInv, &chi2, true, false, ELossGain); // outward refit, no reset
328 if (retVal < 0) { // refit failed
329 LOG(debug) << "Outward refit of top TPC track failed.";
330 return false;
331 } // outward refit in TPC
332 LOG(debug) << "chi2 after top TPC refit with " << retVal << " clusters : " << chi2 << " orig.chi2 was " << tpcTrOrig.getChi2();
333 nclTot += retVal;
334 }
335
336 // inward refit of top leg for evaluation in DCA
337 float chi2Dummy = 0;
338 auto trCosmTop = outerLegs[top];
339 if (gidxListTop[GTrackID::TPC].isIndexSet()) { // inward refit in TPC
340 const auto& tpcTrOrig = data.getTPCTrack(gidxListTop[GTrackID::TPC]);
341 int retVal = tpcRefitter->RefitTrackAsTrackParCov(trCosmTop, tpcTrOrig.getClusterRef(), t0 * tpcTBinMUSInv, &chi2Dummy, false, true, ELossLoss); // inward refit, reset
342 if (retVal < 0) { // refit failed
343 LOG(debug) << "Inward refit of top TPC track failed.";
344 return false;
345 } // inward refit in TPC
346 }
347 // is there ITS sub-track ?
348 if (gidxListTop[GTrackID::ITS].isIndexSet()) {
349 auto nclfit = refitITSTrack(trCosmTop, gidxListTop[GTrackID::ITS], chi2Dummy, true, ELossLoss);
350 if (nclfit < 0) {
351 return false;
352 }
353 nclTot += nclfit;
354 } // ITS refit
355 // propagate to bottom param
356 if (!trCosmTop.rotate(trCosmBtm.getAlpha()) ||
357 !o2::base::Propagator::Instance()->PropagateToXBxByBz(trCosmTop, trCosmBtm.getX(), mMatchParams->maxSnp, mMatchParams->maxStep, mMatchParams->matCorr, nullptr, ELossLoss)) {
358 LOG(debug) << "Rotation/propagation of top-track to bottom-track frame failed.";
359 return false;
360 }
361 // calculate weighted average of 2 legs and chi2
362 o2::track::TrackParCov::MatrixDSym5 cov5;
363 float chi2Match = trCosmBtm.getPredictedChi2(trCosmTop, cov5);
364 if (mMatchParams->maxChi2Match >= 0.f && chi2Match > mMatchParams->maxChi2Match) {
365 LOG(debug) << "Top/Bottom refitted legs disagree, chi2Match " << chi2Match;
366 return false;
367 }
368 if (!trCosmBtm.update(trCosmTop, cov5)) {
369 LOG(debug) << "Top/Bottom update failed";
370 return false;
371 }
372 // TPC-only legs on opposite sides: the legs' pT is required in checkPair, the refitted cosmic's here
373 if (mSeeds[rec.id0].tpcSide * mSeeds[rec.id1].tpcSide < 0 && std::abs(trCosmBtm.getQ2Pt()) > mQ2PtCutoffOppositeSides) {
374 LOG(debug) << "Cosmic with legs on opposite TPC sides below minPtOppositeSides";
375 return false;
376 }
377 // create final track
378 auto& cosmic = mCosmicTracks.emplace_back(mSeeds[poolEntryID[btm]].origID, mSeeds[poolEntryID[top]].origID, trCosmBtm, trCosmTop, chi2, chi2Match, nclTot, t0, dt);
379 uint8_t flags = 0;
380 if (mSeeds[poolEntryID[btm]].nearBeam) {
381 flags |= o2d::TrackCosmics::NearBeamBottom;
382 }
383 if (mSeeds[poolEntryID[top]].nearBeam) {
384 flags |= o2d::TrackCosmics::NearBeamTop;
385 }
386 cosmic.setFlags(flags);
387 if (mUseMC) {
388 o2::MCCompLabel lbl[2] = {data.getTrackMCLabel(mSeeds[poolEntryID[btm]].origID), data.getTrackMCLabel(mSeeds[poolEntryID[top]].origID)};
389 auto& tlb = mCosmicTracksLbl.emplace_back((nclBtm > nclTot - nclBtm ? lbl[0] : lbl[1]));
390 tlb.setFakeFlag(lbl[0] != lbl[1]);
391 }
392 return true;
393 };
394 for (auto winRID : mWinners) {
395 const auto& rec = mRecords[winRID];
396 // refit at the common time if one is fixed (z continuity of TPC-only legs on opposite sides, TOF flight pair), else at the centre of
397 // the overlap of the legs' time brackets
398 auto refitAt = [&](float tCommon, float tCommonErr) {
399 if (tCommonErr >= 0.f) {
400 return refitWinner(winRID, tCommon, tCommonErr);
401 }
402 auto tOverlap = mSeeds[rec.id0].tBracket.getOverlap(mSeeds[rec.id1].tBracket);
403 return refitWinner(winRID, tOverlap.mean(), tOverlap.delta() * 0.5f);
404 };
405 // a TOF-confirmed winner whose refit at the TOF time fails is refitted at the time it has without its TOF flight pair
406 if (!refitAt(rec.tCommon, rec.tCommonErr) && rec.tofScore >= 0.f && refitAt(rec.tCommonNoTOF, rec.tCommonErrNoTOF)) {
407 mNTOFFallbacks++;
408 }
409 }
410 LOG(info) << "Validated " << mCosmicTracks.size() << " top-bottom tracks in TF# " << mTFCount;
411}
412
413//________________________________________________________
414void MatchCosmics::selectWinners()
415{
416 // select mutually best matches
417 int ntr = mSeeds.size(), iter = 0, nValidated = 0;
418 mWinners.reserve(mRecords.size() / 2); // there are 2 records per match candidate
419 do {
420 nValidated = 0;
421 int nRemaining = 0;
422 for (int i = 0; i < ntr; i++) {
423 if (mSeeds[i].matchID < 0 || mRecords[mSeeds[i].matchID].next == Validated) { // either have no match or already validated
424 continue;
425 }
426 nRemaining++;
427 if (validateMatch(i)) {
428 mWinners.push_back(mSeeds[i].matchID);
429 nValidated++;
430 continue;
431 }
432 }
433 LOGF(info, "iter %d Validated %d of %d remaining matches", iter, nValidated, nRemaining);
434 iter++;
435 } while (nValidated);
436}
437
438//________________________________________________________
439bool MatchCosmics::validateMatch(int partner0)
440{
441 // make sure that the best partner of seed_i has also seed_i as a best partner
442 auto& matchRec = mRecords[mSeeds[partner0].matchID];
443 auto partner1 = matchRec.id1;
444 auto& patnerRec = mRecords[mSeeds[partner1].matchID];
445 if (patnerRec.next == Validated) { // partner1 was already validated with other partner0
446 return false;
447 }
448 if (patnerRec.id1 == partner0) { // mutually best
449 // unlink winner partner0 from all other mathes
450 auto next0 = matchRec.next;
451 while (next0 > MinusOne) {
452 auto& nextRec = mRecords[next0];
453 suppressMatch(partner0, nextRec.id1);
454 next0 = nextRec.next;
455 }
456 matchRec.next = Validated;
457
458 // unlink winner partner1 from all other matches
459 auto next1 = patnerRec.next;
460 while (next1 > MinusOne) {
461 auto& nextRec = mRecords[next1];
462 suppressMatch(partner1, nextRec.id1);
463 next1 = nextRec.next;
464 }
465 patnerRec.next = Validated;
466 return true;
467 }
468 return false;
469}
470
471//________________________________________________________
472void MatchCosmics::suppressMatch(int partner0, int partner1)
473{
474 // suppress reference to partner0 from partner1 match record
475 if (mSeeds[partner1].matchID < 0 || mRecords[mSeeds[partner1].matchID].next == Validated) {
476 LOG(warning) << "Attempt to remove null or validated partner match " << mSeeds[partner1].matchID;
477 return;
478 }
479 int topID = MinusOne, next = mSeeds[partner1].matchID;
480 while (next > MinusOne) {
481 auto& matchRec = mRecords[next];
482 if (matchRec.id1 == partner0) {
483 if (topID < 0) { // best match
484 mSeeds[partner1].matchID = matchRec.next; // exclude best match link
485 } else { // not the 1st link in the chain
486 mRecords[topID].next = matchRec.next;
487 }
488 return;
489 }
490 topID = next;
491 next = matchRec.next;
492 }
493}
494
495//________________________________________________________
496MatchCosmics::RejFlag MatchCosmics::checkPair(int i, int j)
497{
498 // if validated with given chi2, register match
499 RejFlag rej = RejOther;
500 auto& seed0 = mSeeds[i];
501 auto& seed1 = mSeeds[j];
502 if (seed0.matchID == Reject) {
503 return rej;
504 }
505 if (seed1.matchID == Reject) {
506 return rej;
507 }
508
509 LOG(debug) << "Seed " << i << " [" << seed0.tBracket.getMin() << " : " << seed0.tBracket.getMax() << "] | "
510 << "Seed " << j << " [" << seed1.tBracket.getMin() << " : " << seed1.tBracket.getMax() << "] | ";
511 LOG(debug) << seed0.origID << " | " << seed0.o2::track::TrackPar::asString();
512 LOG(debug) << seed1.origID << " | " << seed1.o2::track::TrackPar::asString();
513
514 if (seed1.tBracket > seed0.tBracket) {
515 return (rej = RejTime); // since the brackets are sorted in tmin, all following tbj will also exceed tbi
516 }
517 float chi2 = 1.e9f;
518 float tCommon = 0.f; // time fixed by z continuity of TPC-only legs on opposite sides, used by the refit
519 float tCommonErr = -1.f; // its error (< 0: not fixed)
520 float tofScore = -1.f; // score of the TOF flight pair of an accepted pair (tofFlightSelection; < 0: none)
521 TrackSeed seed0Common; // same-side TPC-only legs refitted at a common time (refitSameSideAtCommonTime)
522 TrackSeed seed1Common;
523 bool commonTime = false;
524
525 // check
526 // 1) crude check on tgl and q/pt (if B!=0). Note: back-to-back tracks will have mutually params (see TrackPar::invertParam)
527 while (1) {
528 // TPC-only legs on opposite sides: their z continuity defines the time, so z does not reject random pairs of collision tracks; require
529 // the pT of a cosmic for both legs already here, so that such a pair cannot win against the true partner of one of its legs
530 if (seed0.tpcSide * seed1.tpcSide < 0 && std::max(std::abs(seed0.getQ2Pt()), std::abs(seed1.getQ2Pt())) > mQ2PtCutoffOppositeSides) {
531 rej = RejQ2Pt;
532 break;
533 }
534 auto dTgl = seed0.getTgl() + seed1.getTgl();
535 if (dTgl * dTgl > (mMatchParams->systSigma2[o2::track::kTgl] + seed0.getSigmaTgl2() + seed1.getSigmaTgl2()) * mMatchParams->crudeNSigma2Cut[o2::track::kTgl]) {
536 rej = RejTgl;
537 break;
538 }
539 if (mFieldON) {
540 auto dQ2Pt = seed0.getQ2Pt() + seed1.getQ2Pt();
541 if (dQ2Pt * dQ2Pt > (mMatchParams->systSigma2[o2::track::kQ2Pt] + seed0.getSigma1Pt2() + seed1.getSigma1Pt2()) * mMatchParams->crudeNSigma2Cut[o2::track::kQ2Pt]) {
542 rej = RejQ2Pt;
543 break;
544 }
545 }
546 if (mMatchParams->vetoSameHalf) {
547 // a cosmic has its two legs on opposite sides of its closest approach to the beam line: project the legs' reference points (before
548 // the propagation to the DCA) on the transverse direction of seed0 at its DCA; pieces of one leg are on the same side. Transverse
549 // only: the z of a TPC-only track refers to its own time0, so z differences between the legs are meaningless. Skipped for
550 // reference points close to the DCA (e.g. ITS-containing tracks), where the sign is undefined.
551 std::array<float, 3> pca{};
552 seed0.getXYZGlo(pca);
553 const float phi = seed0.getAlpha() + std::asin(seed0.getSnp());
554 const float dir[2] = {std::cos(phi), std::sin(phi)};
555 float proj0 = 0.f;
556 float proj1 = 0.f;
557 for (int k = 0; k < 2; k++) {
558 proj0 += (seed0.xyzRef[k] - pca[k]) * dir[k];
559 proj1 += (seed1.xyzRef[k] - pca[k]) * dir[k];
560 }
561 constexpr float MinDist = 20.f; // cm
562 if (std::abs(proj0) > MinDist && std::abs(proj1) > MinDist && proj0 * proj1 > 0.f) {
563 rej = RejSameHalf;
564 break;
565 }
566 }
567 // TPC-only legs on the same side: the tracker transformed each leg's clusters with its own time0, a guess (the two legs of a cosmic
568 // get guesses tens of mus apart), so the legs were distortion-corrected at different z and disagree although they are one track;
569 // compare them refitted at one common time, the centre of their brackets' overlap (the time the refit of the winners uses)
570 if (mMatchParams->refitSameSideAtCommonTime && seed0.tpcSide != 0 && seed0.tpcSide == seed1.tpcSide) { // tpcSide != 0: TPC-only one-side legs
571 const float tPair = seed0.tBracket.getOverlap(seed1.tBracket).mean();
572 commonTime = refitSeedAtTime(seed0, tPair, seed0Common) && refitSeedAtTime(seed1, tPair, seed1Common);
573 }
574 const TrackSeed& leg0 = commonTime ? seed0Common : seed0;
575 const TrackSeed& leg1 = commonTime ? seed1Common : seed1;
576 o2::track::TrackParCov seed1Inv = leg1;
577 seed1Inv.invert();
578 for (int i = 0; i < o2::track::kNParams; i++) { // add systematic error
579 seed1Inv.updateCov(mMatchParams->systSigma2[i], o2::track::DiagMap[i]);
580 }
581
582 if (!seed1Inv.rotate(leg0.getAlpha()) ||
583 !o2::base::Propagator::Instance()->PropagateToXBxByBz(seed1Inv, leg0.getX(), mMatchParams->maxSnp, mMatchParams->maxStep, mMatchParams->matCorr)) {
584 rej = RejProp;
585 break;
586 }
587 auto dSnp = leg0.getSnp() - seed1Inv.getSnp();
588 if (dSnp * dSnp > (leg0.getSigmaSnp2() + seed1Inv.getSigmaSnp2()) * mMatchParams->crudeNSigma2Cut[o2::track::kSnp]) {
589 rej = RejSnp;
590 break;
591 }
592 auto dY = leg0.getY() - seed1Inv.getY();
593 if (dY * dY > (leg0.getSigmaY2() + seed1Inv.getSigmaY2()) * mMatchParams->crudeNSigma2Cut[o2::track::kY]) {
594 rej = RejY;
595 break;
596 }
597 bool ignoreZ = leg0.origID.getSource() == o2d::GlobalTrackID::TPC || leg1.origID.getSource() == o2d::GlobalTrackID::TPC;
598 if (ignoreZ && mMatchParams->constrainTPCOnlyZ) {
599 // a TPC-only track with clusters on one side has z relative to its time0: z(t) = z + side * vD * (t - tRef); a CE-crossing or
600 // non-TPC-only track has an absolute z (side 0). Bring both legs to a common time where possible and test z; for legs on opposite
601 // sides, z continuity fixes the common time, which must lie in both time brackets.
602 const int side0 = leg0.tpcSide;
603 const int side1 = leg1.tpcSide;
604 const float sigZ2 = (leg0.getSigmaZ2() + seed1Inv.getSigmaZ2()) * mMatchParams->crudeNSigma2Cut[o2::track::kZ];
605 if (side0 == 0 || side1 == 0 || side0 == side1) {
606 float dZ = leg0.getZ() - seed1Inv.getZ();
607 float dZTimeTol = 0.f; // the time of a non-TPC absolute leg is only known within its bracket (tRef is the bracket centre)
608 if (side0 != 0 && side1 != 0) { // same side: the z offset is fixed by the difference of the reference times
609 dZ -= side0 * mTPCVDrift * (leg0.tRef - leg1.tRef);
610 } else if (side1 != 0) { // leg0 absolute: move leg1 to the time of leg0
611 dZ -= side1 * mTPCVDrift * (leg0.tRef - leg1.tRef);
612 if (leg0.origID.getSource() != o2d::GlobalTrackID::TPC) {
613 dZTimeTol = 0.5f * mTPCVDrift * leg0.tBracket.delta();
614 }
615 } else if (side0 != 0) { // leg1 absolute: move leg0 to the time of leg1
616 dZ += side0 * mTPCVDrift * (leg1.tRef - leg0.tRef);
617 if (leg1.origID.getSource() != o2d::GlobalTrackID::TPC) {
618 dZTimeTol = 0.5f * mTPCVDrift * leg1.tBracket.delta();
619 }
620 }
621 const float dZTol = std::sqrt(sigZ2) + dZTimeTol;
622 if (dZ * dZ > dZTol * dZTol) {
623 rej = RejZ;
624 break;
625 }
626 } else { // opposite sides
627 const float t = 0.5f * (side0 * (seed1Inv.getZ() - leg0.getZ()) / mTPCVDrift + leg0.tRef + leg1.tRef);
628 const float tTol = std::sqrt(sigZ2) / (2.f * mTPCVDrift);
629 if (t < std::max(leg0.tBracket.getMin(), leg1.tBracket.getMin()) - tTol || t > std::min(leg0.tBracket.getMax(), leg1.tBracket.getMax()) + tTol) {
630 rej = RejZ;
631 break;
632 }
633 tCommon = t;
634 tCommonErr = std::sqrt(leg0.getSigmaZ2() + seed1Inv.getSigmaZ2()) / (2.f * mTPCVDrift);
635 }
636 }
637 // the z of a TPC-only leg refers to its own time0: it is tested above at a common time with constrainTPCOnlyZ, otherwise ignored
638 if (!ignoreZ) { // both legs have an absolute z
639 auto dZ = leg0.getZ() - seed1Inv.getZ();
640 if (dZ * dZ > (leg0.getSigmaZ2() + seed1Inv.getSigmaZ2()) * mMatchParams->crudeNSigma2Cut[o2::track::kZ]) {
641 rej = RejZ;
642 break;
643 }
644 } else { // inflate Z error
645 seed1Inv.setCov(250. * 250., o2::track::DiagMap[o2::track::kZ]);
646 seed1Inv.setCov(0., o2::track::CovarMap[o2::track::kZ][o2::track::kY]); // set all correlation terms for Z error to 0
647 seed1Inv.setCov(0., o2::track::CovarMap[o2::track::kZ][o2::track::kSnp]);
648 seed1Inv.setCov(0., o2::track::CovarMap[o2::track::kZ][o2::track::kTgl]);
649 seed1Inv.setCov(0., o2::track::CovarMap[o2::track::kZ][o2::track::kQ2Pt]);
650 }
651 // calculate chi2 (expensive)
652 chi2 = leg0.getPredictedChi2(seed1Inv);
653 if (chi2 > mMatchParams->crudeChi2Cut) {
654 rej = RejChi2;
655 break;
656 }
657 rej = Accept;
658 const float tCommonNoTOF = tCommon; // the pair's time without a TOF flight pair: the fallback of the refit at the TOF time
659 const float tCommonErrNoTOF = tCommonErr;
660 if (mMatchParams->tofFlightSelection) { // a top / bottom TOF hit pair with the muon's flight time confirms the pair and gives its time
661 const bool timeFixed = tCommonErr >= 0.f;
662 const auto overlap = seed0.tBracket.getOverlap(seed1.tBracket);
663 const float tMin = timeFixed ? tCommon - mMatchParams->nSigmaTError * tCommonErr : overlap.getMin();
664 const float tMax = timeFixed ? tCommon + mMatchParams->nSigmaTError * tCommonErr : overlap.getMax();
665 float tofTimeMUS = 0.f;
666 tofScore = findTOFFlightPair(i, j, tMin, tMax, tofTimeMUS);
667 if (tofScore >= 0.f) {
668 tCommon = tofTimeMUS;
669 tCommonErr = mMatchParams->tofTimeError;
670 mNTOFConfirmed++;
671 }
672 }
673 if (seed0.nearBeam || seed1.nearBeam) { // a leg near the beam line (minSeedDCAxyTOF): TOF-confirmed pairs only
674 if (tofScore < 0.f) {
675 rej = RejNoTOF;
676 break;
677 }
678 mNNearBeamConfirmed++;
679 }
680 registerMatch(i, j, chi2, tCommon, tCommonErr, tofScore, tCommonNoTOF, tCommonErrNoTOF);
681 registerMatch(j, i, chi2, tCommon, tCommonErr, tofScore, tCommonNoTOF, tCommonErrNoTOF); // the reverse reference can be also done in a separate loop
682 LOG(debug) << "Chi2 = " << chi2 << " NMatches " << mRecords.size();
683 break;
684 }
685
686#ifdef _ALLOW_DEBUG_TREES_
687 if (mDBGOut && ((rej == Accept && isDebugFlag(MatchTreeAccOnly)) || isDebugFlag(MatchTreeAll))) {
688 const TrackSeed& dbgLeg0 = commonTime ? seed0Common : seed0; // the legs compared (refitted at the common time if they were)
689 auto seed1I = commonTime ? seed1Common : seed1;
690 seed1I.invert();
691 if (seed1I.rotate(dbgLeg0.getAlpha()) && o2::base::Propagator::Instance()->PropagateToXBxByBz(seed1I, dbgLeg0.getX(), mMatchParams->maxSnp, mMatchParams->maxStep, mMatchParams->matCorr)) {
692 int rejI = int(rej);
693 int commonTimeI = commonTime;
694 (*mDBGOut) << "match"
695 << "tf=" << mTFCount << "seed0=" << dbgLeg0 << "seed1=" << seed1I << "chi2Match=" << chi2 << "rej=" << rejI << "commonTime=" << commonTimeI
696 << "side0=" << int(seed0.tpcSide) << "side1=" << int(seed1.tpcSide) << "tCommon=" << tCommon << "tCommonErr=" << tCommonErr << "tofScore=" << tofScore << "\n";
697 }
698 }
699#endif
700
701 return rej;
702}
703
704//________________________________________________________
705bool MatchCosmics::refitSeedAtTime(const TrackSeed& seed, float timeMUS, TrackSeed& out)
706{
707 // TPC-only seed refitted with its clusters transformed at the time timeMUS, then treated as the seeds in createSeeds and process() (muon,
708 // extra z error, propagation to the DCA to the beam line); energy loss along the muon's flight in the refit and the propagation, as in
709 // refitWinners. Its z then refers to timeMUS
710 if (!mTPCRefitter || !mRecoData) {
711 return false;
712 }
713 const auto& tpcTrack = mRecoData->getTPCTrack(seed.origID);
714 o2::track::TrackParCov trk = tpcTrack.getParamOut();
715 trk.setPID(o2::track::PID::Muon, true);
716 trk.resetCovariance();
717 // the muon flies downward: inward along a leg pointing up (top leg) is along its flight (loss), along the bottom leg against it (gain)
718 std::array<float, 3> momentum{};
719 trk.getPxPyPzGlo(momentum);
720 const int eLossSign = momentum[1] > 0.f ? ELossLoss : ELossGain;
721 if (mTPCRefitter->RefitTrackAsTrackParCov(trk, tpcTrack.getClusterRef(), timeMUS / mTPCTBinMUS, nullptr, false, true, eLossSign) < 0) {
722 return false;
723 }
724 trk.setCov(mMatchParams->tpcExtraZError2 + trk.getSigmaZ2(), o2::track::kSigZ2);
726 if (!o2::base::Propagator::Instance()->propagateToDCABxByBz(v, trk, mMatchParams->maxStep, mMatchParams->matCorr, nullptr, nullptr, eLossSign)) {
727 return false;
728 }
729 out = seed;
730 static_cast<o2::track::TrackParCov&>(out) = trk;
731 out.tRef = timeMUS;
732 mNRefitsCommonTime++;
733 return true;
734}
735
736//________________________________________________________
737void MatchCosmics::prepareTOFClusters(const o2::globaltracking::RecoContainer& data)
738{
739 // TOF clusters of the TF sorted in time; the TOF candidates of a seed are searched on its first use
740 const auto clusters = data.getTOFClusters();
741 mTOFClusterOrder.resize(clusters.size());
742 std::iota(mTOFClusterOrder.begin(), mTOFClusterOrder.end(), 0);
743 std::sort(mTOFClusterOrder.begin(), mTOFClusterOrder.end(), [&clusters](int a, int b) { return clusters[a].getTime() < clusters[b].getTime(); });
744 mTOFClusterTimeMUS.resize(clusters.size());
745 for (size_t k = 0; k < clusters.size(); k++) {
746 mTOFClusterTimeMUS[k] = clusters[mTOFClusterOrder[k]].getTime() * 1e-6; // [ps] since the start of the TF
747 }
748 mSeedTOFCandidates.clear();
749 mSeedTOFCandidates.resize(mSeeds.size());
750 mSeedTOFDone.assign(mSeeds.size(), false);
751}
752
753//________________________________________________________
754const std::vector<MatchCosmics::TOFCandidate>& MatchCosmics::getTOFCandidates(int iseed)
755{
756 // TOF clusters along the outward continuation of a TPC-only seed (from its outer parameters, in the sector it points to and its two
757 // neighbours) within its time bracket: |dy| < tofRoad, and dz within tofRoad of the drift of a one-side leg's z over the bracket
758 auto& candidates = mSeedTOFCandidates[iseed];
759 if (mSeedTOFDone[iseed]) {
760 return candidates;
761 }
762 mSeedTOFDone[iseed] = true;
763 const auto& seed = mSeeds[iseed];
764 if (seed.origID.getSource() != GTrackID::TPC) {
765 return candidates;
766 }
767 constexpr float MaxFlightMUS = 0.1f; // flight time of the muon between the TPC and the TOF, slow tails
768 constexpr float RadiusTOF = 380.f; // [cm], to find the sector the leg points to
769 const auto& tpcTrack = mRecoData->getTPCTrack(seed.origID);
770 const o2::track::TrackPar& parOut = tpcTrack.getParamOut();
771 std::array<float, 3> xyz{};
772 std::array<float, 3> dir{};
773 parOut.getXYZGlo(xyz);
774 parOut.getPxPyPzGlo(dir);
775 // straight line from the outer parameters to the TOF radius (the sector only)
776 const float a = dir[0] * dir[0] + dir[1] * dir[1];
777 const float b = xyz[0] * dir[0] + xyz[1] * dir[1];
778 const float c = xyz[0] * xyz[0] + xyz[1] * xyz[1] - RadiusTOF * RadiusTOF;
779 const float disc = b * b - a * c;
780 if (a <= 0.f || disc < 0.f) {
781 return candidates;
782 }
783 const float step = (-b + std::sqrt(disc)) / a;
784 const int sectorCentre = o2::math_utils::angle2Sector(std::atan2(xyz[1] + step * dir[1], xyz[0] + step * dir[0]));
785 constexpr int NSectors = 18;
786 o2::track::TrackPar parSector[3];
787 bool okSector[3] = {false, false, false};
788 for (int k = 0; k < 3; k++) {
789 parSector[k] = parOut;
790 okSector[k] = parSector[k].rotateParam(o2::math_utils::sector2Angle((sectorCentre + k - 1 + NSectors) % NSectors));
791 }
792 const float road = mMatchParams->tofRoad;
793 // range of z(t) - z(tRef) = side * vD * (t - tRef) over the bracket
794 const float dzDrift0 = seed.tpcSide * mTPCVDrift * (seed.tBracket.getMin() - seed.tRef);
795 const float dzDrift1 = seed.tpcSide * mTPCVDrift * (seed.tBracket.getMax() - seed.tRef);
796 const float dzMin = std::min(dzDrift0, dzDrift1) - road;
797 const float dzMax = std::max(dzDrift0, dzDrift1) + road;
798 const auto clusters = mRecoData->getTOFClusters();
799 auto first = std::lower_bound(mTOFClusterTimeMUS.begin(), mTOFClusterTimeMUS.end(), seed.tBracket.getMin() - MaxFlightMUS);
800 for (auto it = first; it != mTOFClusterTimeMUS.end() && *it <= seed.tBracket.getMax() + MaxFlightMUS; ++it) {
801 const int index = mTOFClusterOrder[it - mTOFClusterTimeMUS.begin()];
802 const auto& cl = clusters[index];
803 const int k = (cl.getSector() - sectorCentre + NSectors + 1) % NSectors; // 0, 1, 2 for the sectors before, at and after the centre
804 if (k > 2 || !okSector[k]) {
805 continue;
806 }
807 float y = 0.f;
808 float z = 0.f;
809 if (!parSector[k].getYZAt(cl.getX(), mBz, y, z)) {
810 continue;
811 }
812 const float dy = cl.getY() - y;
813 const float dz = cl.getZ() - z;
814 if (std::abs(dy) > road || dz < dzMin || dz > dzMax) {
815 continue;
816 }
817 const float alpha = o2::math_utils::sector2Angle(cl.getSector());
818 const float sinAlpha = std::sin(alpha);
819 const float cosAlpha = std::cos(alpha);
820 candidates.push_back(TOFCandidate{index, cl.getTime() * 1e-3, dy, dz, cl.getX() * cosAlpha - cl.getY() * sinAlpha, cl.getX() * sinAlpha + cl.getY() * cosAlpha, cl.getZ()});
821 }
822 return candidates;
823}
824
825//________________________________________________________
826float MatchCosmics::findTOFFlightPair(int i, int j, float tMinMUS, float tMaxMUS, float& tofTimeMUS)
827{
828 // best pair of TOF candidates of seeds i and j whose time difference matches the muon's flight between them along the helix (the higher
829 // hit first), with its mean time within [tMinMUS, tMaxMUS] and the z of both legs in the road at that time. Returns its score, the
830 // squared residuals in units of their cuts (< 0: no pair); tofTimeMUS is the mean time of the two hits
831 constexpr float MaxFlightMUS = 0.1f;
832 constexpr float CmPerNS = 29.9792458f;
833 const auto& candidates0 = getTOFCandidates(i);
834 const auto& candidates1 = getTOFCandidates(j);
835 if (candidates0.empty() || candidates1.empty()) {
836 return -1.f;
837 }
838 const auto& seed0 = mSeeds[i];
839 const auto& seed1 = mSeeds[j];
840 const float curvature = 0.5f * (std::abs(mRecoData->getTPCTrack(seed0.origID).getCurvature(mBz)) + std::abs(mRecoData->getTPCTrack(seed1.origID).getCurvature(mBz)));
841 const float road = mMatchParams->tofRoad;
842 const float tolerance = mMatchParams->tofFlightTolerance;
843 float bestScore = -1.f;
844 for (const auto& c0 : candidates0) {
845 for (const auto& c1 : candidates1) {
846 if (c0.index == c1.index) {
847 continue;
848 }
849 const auto& top = c0.gy > c1.gy ? c0 : c1;
850 const auto& bottom = c0.gy > c1.gy ? c1 : c0;
851 // flight path along the helix: arc in the transverse plane from the chord, then the dip
852 const float chordXY = std::hypot(top.gx - bottom.gx, top.gy - bottom.gy);
853 const float halfAngleSin = 0.5f * curvature * chordXY;
854 const float arcXY = halfAngleSin > 1e-4f && halfAngleSin < 1.f ? 2.f * std::asin(halfAngleSin) / curvature : chordXY;
855 const float length = std::hypot(arcXY, top.gz - bottom.gz);
856 const float flightDev = float(top.timeNS - bottom.timeNS) + length / CmPerNS; // the muon crosses the top TOF first
857 if (std::abs(flightDev) > tolerance) {
858 continue;
859 }
860 const float pairTimeMUS = float(0.5e-3 * (c0.timeNS + c1.timeNS));
861 if (pairTimeMUS < tMinMUS - MaxFlightMUS || pairTimeMUS > tMaxMUS + MaxFlightMUS) {
862 continue;
863 }
864 const float dz0 = c0.dz - seed0.tpcSide * mTPCVDrift * (pairTimeMUS - seed0.tRef);
865 const float dz1 = c1.dz - seed1.tpcSide * mTPCVDrift * (pairTimeMUS - seed1.tRef);
866 if (std::abs(dz0) > road || std::abs(dz1) > road) {
867 continue;
868 }
869 const float score = (c0.dy * c0.dy + c1.dy * c1.dy + dz0 * dz0 + dz1 * dz1) / (road * road) + flightDev * flightDev / (tolerance * tolerance);
870 if (bestScore < 0.f || score < bestScore) {
871 bestScore = score;
872 tofTimeMUS = pairTimeMUS;
873 }
874 }
875 }
876 return bestScore;
877}
878
879//________________________________________________________
880void MatchCosmics::registerMatch(int i, int j, float chi2, float tCommon, float tCommonErr, float tofScore, float tCommonNoTOF, float tCommonErrNoTOF)
881{
884 int newRef = mRecords.size();
885 auto& matchRec = mRecords.emplace_back(MatchRecord{i, j, chi2, MinusOne, tCommon, tCommonErr, tofScore, tCommonNoTOF, tCommonErrNoTOF});
886 const bool confirmed = tofScore >= 0.f;
887 auto* best = &mSeeds[i].matchID;
888 while (*best > MinusOne) {
889 auto& oldMatchRec = mRecords[*best];
890 const bool oldConfirmed = oldMatchRec.tofScore >= 0.f;
891 if ((confirmed && !oldConfirmed) || (confirmed == oldConfirmed && oldMatchRec.chi2 > chi2)) { // insert new match in front of the old one
892 matchRec.next = *best; // new record will refer to the one it is superseding
893 *best = newRef; // the reference on the superseded record should now refer to new one
894 break;
895 }
896 best = &oldMatchRec.next;
897 }
898 if (matchRec.next == MinusOne) { // did not supersed any other record
899 *best = newRef;
900 }
901}
902
903//________________________________________________________
904void MatchCosmics::createSeeds(const o2::globaltracking::RecoContainer& data)
905{
906 // Scan all inputs and create seeding tracks
907
908 mSeeds.clear();
909 std::unordered_map<GTrackID, int> trackEntry;
910
911 auto creator = [this, &trackEntry](auto& _tr, GTrackID _origID, float t0, float terr) {
912 if constexpr (std::is_base_of_v<o2::track::TrackParCov, std::decay_t<decltype(_tr)>>) {
913 if (std::abs(_tr.getQ2Pt()) > this->mQ2PtCutoff) {
914 return true;
915 }
916 if constexpr (isTPCTrack<decltype(_tr)>()) {
917 if (!this->mMatchParams->allowTPCOnly || _tr.getNClusters() < this->mMatchParams->minSeedNClTPC) {
918 return true;
919 }
920 // unconstrained TPC track, with t0 = TrackTPC.getTime0+0.5*(DeltaFwd-DeltaBwd) and terr = 0.5*(DeltaFwd+DeltaBwd) in TimeBins
921 t0 *= this->mTPCTBinMUS;
922 terr *= this->mTPCTBinMUS;
923 } else if (isITSTrack<decltype(_tr)>()) {
924 t0 += 0.5 * this->mITSROFrameLengthMUS; // time 0 is supplied as beginning of ROF in \mus
925 terr *= this->mITSROFrameLengthMUS; // error is supplied a half-ROF duration, convert to \mus
926 } else { // all other tracks are provided with time and its gaussian error in \mus
927 terr *= this->mMatchParams->nSigmaTError;
928 }
929 terr += this->mMatchParams->timeToleranceMUS;
930 trackEntry[_origID] = mSeeds.size();
931 auto& seed = mSeeds.emplace_back(TrackSeed{_tr, {t0 - terr, t0 + terr}, _origID, MinusOne});
932 seed.setPID(o2::track::PID::Muon, true); // muon mass and charge for the material corrections, whatever the leg's dE/dx PID
933 seed.getXYZGlo(seed.xyzRef);
934 seed.tRef = t0;
935 if constexpr (isTPCTrack<decltype(_tr)>()) {
936 seed.setCov(this->mMatchParams->tpcExtraZError2 + _tr.getCov()[o2::track::kSigZ2], o2::track::kSigZ2);
937 seed.tRef = _tr.getTime0() * this->mTPCTBinMUS; // the z of a TPC-only track refers to its time0
938 seed.tpcSide = _tr.hasASideClustersOnly() ? 1 : (_tr.hasCSideClustersOnly() ? -1 : 0);
939 }
940 return true;
941 } else {
942 return false;
943 }
944 };
945
946 data.createTracksVariadic(creator, mSeedSources); // other loaded sources only resolve the primary-vertex contributors below
947
948 mNSeedsPVContributors = 0;
949 if (mUsePVInfo) { // if needed, veto with the primary vertex info
950 auto trackIndex = data.getPrimaryVertexMatchedTracks(); // Global ID's for associated tracks
951 auto vtxRefs = data.getPrimaryVertexMatchedTrackRefs(); // references from vertex to these track IDs
952 int nv = vtxRefs.size() - 1; // The last entry is for unassigned tracks, no need to check them
953 const auto propagator = o2::base::Propagator::Instance();
954 for (int iv = 0; iv < nv; iv++) {
955 const auto& vtref = vtxRefs[iv];
956 int it = vtref.getFirstEntry(), itLim = it + vtref.getEntries();
957 for (; it < itLim; it++) {
958 auto tvid = trackIndex[it];
959 auto entry = trackEntry.find(tvid);
960 if (entry == trackEntry.end()) {
961 // a contributor of a veto source (not itself a leg): the legs it contains (e.g. its TPC track) come from a collision
962 if (mMatchParams->discardPVContributors && tvid.isPVContributor() && mPVVetoSources[tvid.getSource()] && data.isTrackSourceLoaded(tvid.getSource())) {
963 const auto refs = data.getSingleDetectorRefs(tvid);
964 for (int src = 0; src < GTrackID::NSources; src++) {
965 if (!mSeedSources[src] || !refs[src].isIndexSet()) {
966 continue;
967 }
968 auto partEntry = trackEntry.find(refs[src]);
969 if (partEntry != trackEntry.end() && mSeeds[partEntry->second].matchID != Reject) {
970 mSeeds[partEntry->second].matchID = Reject;
971 mNSeedsPVContributors++;
972 }
973 }
974 }
975 continue;
976 }
977 auto& seed = mSeeds[entry->second];
978 if (seed.matchID != Reject && mMatchParams->discardPVContributors && tvid.isPVContributor()) { // the leg itself is a contributor
979 seed.matchID = Reject;
980 mNSeedsPVContributors++;
981 }
982 if (seed.matchID == Reject) {
983 continue;
984 }
985 if (seed.vtIDMin < 0) {
986 seed.vtIDMin = iv;
987 }
988 seed.vtIDMax = std::max(short(iv), seed.vtIDMax);
989 }
990 }
991 }
992}
993
994//________________________________________________________
995void MatchCosmics::updateTimeDependentParams()
996{
999 mTPCTBinMUS = elParam.ZbinWidth; // TPC bin in microseconds
1000 mBz = o2::base::Propagator::Instance()->getNominalBz();
1001 mFieldON = std::abs(mBz) > 0.01;
1002 mQ2PtCutoff = 1.f / std::max(0.05f, mMatchParams->minSeedPt);
1003 mQ2PtCutoffOppositeSides = mMatchParams->minPtOppositeSides > 0.f ? 1.f / mMatchParams->minPtOppositeSides : 1e9;
1004 if (mFieldON) {
1005 mQ2PtCutoff *= 5.00668 / std::abs(mBz);
1006 mQ2PtCutoffOppositeSides *= 5.00668 / std::abs(mBz);
1007 } else {
1008 mQ2PtCutoff = 1e9;
1009 mQ2PtCutoffOppositeSides = 1e9;
1010 }
1011}
1012
1013//________________________________________________________
1015{
1017
1018#ifdef _ALLOW_DEBUG_TREES_COSM
1019 // debug streamer
1020 if (mDBGFlags) {
1021 mDBGOut = std::make_unique<o2::utils::TreeStreamRedirector>(mDebugTreeFileName.data(), "recreate");
1022 }
1023#endif
1024}
1025
1026//________________________________________________________
1028{
1030 int nLr = data.getITSPerLayer() ? o2::globaltracking::MaxITSLayers : 1;
1031 itscl.init(nLr);
1032 for (int lr = 0; lr < nLr; lr++) {
1033 itscl.beginLayer(lr);
1034 const auto& clusITS = data.getITSClusters(lr);
1035 if (clusITS.size()) {
1036 auto pattIt = data.getITSClustersPatterns(lr).begin();
1037 itscl.getClusters().reserve(itscl.size() + clusITS.size());
1038 o2::its::ioutils::convertCompactClusters(clusITS, pattIt, itscl.getClusters(), mITSDict);
1039 }
1040 }
1041 itscl.finalize();
1042 return itscl;
1043}
1044
1045//______________________________________________
1047{
1048#ifdef _ALLOW_DEBUG_TREES_COSM
1049 mDBGOut.reset();
1050#endif
1051}
1052
1053#ifdef _ALLOW_DEBUG_TREES_
1054//______________________________________________
1055void MatchCosmics::setDebugFlag(UInt_t flag, bool on)
1056{
1058 if (on) {
1059 mDBGFlags |= flag;
1060 } else {
1061 mDBGFlags &= ~flag;
1062 }
1063}
1064
1065//______________________________________________
1067{
1068 mTPCVDrift = v.refVDrift * v.corrFact;
1069 mTPCVDriftCorrFact = v.corrFact;
1070 mTPCVDriftRef = v.refVDrift;
1071 mTPCDriftTimeOffset = v.getTimeOffset();
1072}
1073
1074//______________________________________________
1076{
1077 mTPCCorrMaps = maph;
1078}
1079
1080#endif
General auxilliary methods.
Definition of the ITSMFT compact cluster.
Wrapper container for different reconstructed object types.
Definition of the TOF cluster.
std::ostringstream debug
Definition of the FIT RecPoints class.
int32_t i
int32_t retVal
Some ALICE geometry constants of common interest.
Accessor for TrackParCov derived objects from multiple containers.
Definition of the GeometryTGeo class.
Definition of the ITSMFT ROFrame (trigger) record.
float chi2
Class to perform matching/refit of cosmic tracks legs.
Class to store the output of the matching to TOF.
Class to perform TPC ITS matching.
Definition of the parameter class for the detector electronics.
uint32_t j
Definition RawData.h:0
uint32_t c
Definition RawData.h:2
Wrapper container for different reconstructed object types.
constexpr auto isITSTrack()
class to create TPC fast transformation
POD correction map.
Definition of the ITS track.
Result of refitting TPC-ITS matched track.
Result of refitting TPC with TOF match constraint.
calibration data from laser track calibration
Referenc on track indices contributing to the vertex, with possibility chose tracks from specific sou...
Helper class to obtain TPC clusters / digits / labels from DPL.
GPUd() value_type estimateLTFast(o2 static GPUd() float estimateLTIncrement(const o2 PropagatorImpl * Instance(bool uninitialized=false)
Definition Propagator.h:180
@ MatchTreeAll
produce matching candidates tree for all candidates
@ MatchTreeAccOnly
fill the matching candidates tree only once the cut is passed
static constexpr int Validated
bool isDebugFlag(UInt_t flags) const
get debug trees flags
void setDebugFlag(UInt_t flag, bool on=true)
set the name of output debug file
void setTPCVDrift(const o2::tpc::VDriftCorrFact &v)
static constexpr int Reject
void setTPCCorrMaps(const o2::gpu::TPCFastTransformPOD *maph)
void process(const o2::globaltracking::RecoContainer &data)
static constexpr int MinusOne
@ RejNoTOF
a leg kept by minSeedDCAxyTOF only, and no TOF flight pair
int32_t RefitTrackAsTrackParCov(o2::tpc::TrackTPC &trk, bool outward=false, bool resetCov=false)
static GeometryTGeo * Instance()
void beginLayer(int lr)
to be called once all the layers were filled
void init(int nLr)
< prepare for filling nLr layer slots, discarding the previous content
GLfloat GLfloat GLfloat alpha
Definition glcorearb.h:279
GLint GLenum GLint x
Definition glcorearb.h:403
GLenum src
Definition glcorearb.h:1767
GLint GLsizei count
Definition glcorearb.h:399
GLuint entry
Definition glcorearb.h:5735
const GLdouble * v
Definition glcorearb.h:832
GLuint index
Definition glcorearb.h:781
GLdouble GLdouble GLdouble GLdouble top
Definition glcorearb.h:4077
GLint first
Definition glcorearb.h:399
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLint y
Definition glcorearb.h:270
GLboolean * data
Definition glcorearb.h:298
GLuint GLsizei GLsizei * length
Definition glcorearb.h:790
GLint GLint bottom
Definition glcorearb.h:1979
GLbitfield flags
Definition glcorearb.h:1570
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat t0
Definition glcorearb.h:5034
GLboolean GLboolean GLboolean GLboolean a
Definition glcorearb.h:1233
GLdouble GLdouble GLdouble z
Definition glcorearb.h:843
uint8_t int statusCode int
uint8_t itsSharedClusterMap uint8_t
constexpr float XTPCInnerRef
reference radius at which TPC provides the tracks
constexpr int MaxITSLayers
void convertCompactClusters(gsl::span< const itsmft::CompClusterExt > clusters, gsl::span< const unsigned char >::iterator &pattIt, std::vector< o2::BaseCluster< float > > &output, const itsmft::TopologyDictionary *dict)
convert compact clusters to 3D spacepoints
Definition IOUtils.cxx:35
int const float const TrackSeed< NLayers > & seed
const bool const bool const int FollowDirection BestTrial TrackITSInternal< NLayers > & best
int angle2Sector(float phi)
Definition Utils.h:183
float sector2Angle(int sect)
Definition Utils.h:193
return * this
TrackParCovF TrackParCov
Definition Track.h:33
value_T step
Definition TrackUtils.h:42
GPUReconstruction * rec
float crudeNSigma2Cut[o2::track::kNParams]
o2::base::Propagator::MatCorrType matCorr
float dcaCutChi2[o2::dataformats::GlobalTrackID::NSources]
const o2::tpc::TrackTPC & getTPCTrack(GTrackID id) const
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"
std::vector< Cluster > clusters