forked from AliceO2Group/AliceO2
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMatchTPCITS.cxx
More file actions
2994 lines (2801 loc) · 123 KB
/
MatchTPCITS.cxx
File metadata and controls
2994 lines (2801 loc) · 123 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
// All rights not expressly granted are reserved.
//
// This software is distributed under the terms of the GNU General Public
// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
//
// In applying this license CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
#include "GPUO2ExternalUser.h" // Needed for propper settings in GPUParam.h
#include "GPUParam.h"
#include "GPUParam.inc"
#ifdef WITH_OPENMP
#include <omp.h>
#endif
#include <TTree.h>
#include <cassert>
#include <algorithm>
#include <fairlogger/Logger.h>
#include "Field/MagneticField.h"
#include "Field/MagFieldFast.h"
#include "ITSBase/GeometryTGeo.h"
#include "CommonUtils/TreeStream.h"
#include "DataFormatsTPC/Defs.h"
#include "TPCBase/ParameterElectronics.h"
#include "TPCBase/ParameterDetector.h"
#include "MathUtils/Cartesian.h"
#include "MathUtils/Utils.h"
#include "CommonConstants/MathConstants.h"
#include "CommonConstants/PhysicsConstants.h"
#include "CommonConstants/GeomConstants.h"
#include "DetectorsBase/GeometryManager.h"
#include "DetectorsBase/GlobalParams.h"
#include <Math/SMatrix.h>
#include <Math/SVector.h>
#include <TFile.h>
#include <TGeoGlobalMagField.h>
#include "SimulationDataFormat/MCTruthContainer.h"
#include "TPCReconstruction/TPCFastTransformHelperO2.h"
#include "CommonUtils/NameConf.h"
#include "ReconstructionDataFormats/Vertex.h"
#include "GlobalTracking/MatchTPCITS.h"
#include "DataFormatsGlobalTracking/RecoContainer.h"
#include "DataFormatsGlobalTracking/RecoContainerCreateTracksVariadic.h"
#include "DataFormatsGlobalTracking/TrackTuneParams.h"
#include "DataFormatsTPC/WorkflowHelper.h"
#include "DetectorsBase/GRPGeomHelper.h"
#include "ITStracking/IOUtils.h"
#ifdef ENABLE_UPGRADES
#include "ITS3Reconstruction/IOUtils.h"
#endif
using namespace o2::globaltracking;
using MatrixDSym4 = ROOT::Math::SMatrix<double, 4, 4, ROOT::Math::MatRepSym<double, 4>>;
using MatrixD4 = ROOT::Math::SMatrix<double, 4, 4, ROOT::Math::MatRepStd<double, 4>>;
using NAMES = o2::base::NameConf;
using GTrackID = o2::dataformats::GlobalTrackID;
using TrackTunePar = o2::globaltracking::TrackTuneParams;
constexpr float MatchTPCITS::Tan70, MatchTPCITS::Cos70I2, MatchTPCITS::MaxSnp, MatchTPCITS::MaxTgp;
LinksPoolMT* TPCABSeed::gLinksPool = nullptr;
const o2::gpu::GPUTPCGeometry MatchTPCITS::TPCGeometry{};
//______________________________________________
MatchTPCITS::MatchTPCITS() = default;
//______________________________________________
MatchTPCITS::~MatchTPCITS() = default;
//______________________________________________
void MatchTPCITS::run(const o2::globaltracking::RecoContainer& inp,
pmr::vector<o2::dataformats::TrackTPCITS>& matchedTracks,
pmr::vector<o2::itsmft::TrkClusRef>& ABTrackletRefs,
pmr::vector<int>& ABTrackletClusterIDs,
pmr::vector<o2::MCCompLabel>& matchLabels,
pmr::vector<o2::MCCompLabel>& ABTrackletLabels,
pmr::vector<o2::dataformats::Triplet<float, float, float>>& calib)
{
///< perform matching for provided input
if (!mInitDone) {
LOG(fatal) << "init() was not done yet";
}
clear();
mRecoCont = &inp;
mStartIR = inp.startIR;
updateTimeDependentParams();
mTimer[SWTot].Start(false);
while (1) {
if (!prepareITSData() || !prepareTPCData() || !prepareFITData()) {
break;
}
if (mVDriftCalibOn) { // in the beginning of the output vector we send the full and reference VDrift used for this TF
calib.emplace_back(mTPCVDrift, mTPCDrift.refVDrift, mTPCDrift.refTP);
calib.emplace_back(mTPCDriftTimeOffset, mTPCDrift.refTimeOffset, -999.);
}
mTimer[SWDoMatching].Start(false);
for (int sec = o2::constants::math::NSectors; sec--;) {
doMatching(sec);
}
mTimer[SWDoMatching].Stop();
if constexpr (false) { // enabling this creates very verbose output
mTimer[SWTot].Stop();
printCandidatesTPC();
printCandidatesITS();
mTimer[SWTot].Start(false);
}
selectBestMatches();
bool fullMatchRefitDone = false;
if (mUseFT0 && mParams->runAfterBurner) {
fullMatchRefitDone = runAfterBurner(matchedTracks, matchLabels, ABTrackletLabels, ABTrackletClusterIDs, ABTrackletRefs, calib);
}
if (!fullMatchRefitDone) {
refitWinners(matchedTracks, matchLabels, calib); // it afterburner is active, full matches refit will be done by it
}
if (mParams->verbosity > 0) {
reportSizes(matchedTracks, ABTrackletRefs, ABTrackletClusterIDs, matchLabels, ABTrackletLabels, calib);
}
#ifdef _ALLOW_DEBUG_TREES_
if (mDBGOut && isDebugFlag(WinnerMatchesTree)) {
dumpWinnerMatches();
}
#endif
break;
}
mTimer[SWTot].Stop();
if (mParams->verbosity > 0) {
reportTiming();
}
mTFCount++;
}
//______________________________________________
void MatchTPCITS::reportTiming()
{
for (int i = 0; i < NStopWatches; i++) {
LOGF(info, "Timing for %15s: Cpu: %.3e Real: %.3e s in %d slots of TF#%d", TimerName[i], mTimer[i].CpuTime(), mTimer[i].RealTime(), mTimer[i].Counter() - 1, mTFCount);
}
}
//______________________________________________
void MatchTPCITS::end()
{
#ifdef _ALLOW_DEBUG_TREES_
mDBGOut.reset();
#endif
}
//______________________________________________
void MatchTPCITS::clear()
{
///< clear results of previous TF reconstruction
mMatchRecordsTPC.clear();
mMatchRecordsITS.clear();
mWinnerChi2Refit.clear();
mITSWork.clear();
mTPCWork.clear();
mInteractions.clear();
mITSROFTimes.clear();
mITSTrackROFContMapping.clear();
mITSClustersArray.clear();
mITSClusterSizes.clear();
mTPCABSeeds.clear();
mTPCABIndexCache.clear();
mABWinnersIDs.clear();
mABClusterLinkIndex.clear();
mNMatchesControl = 0;
for (int sec = o2::constants::math::NSectors; sec--;) {
mITSSectIndexCache[sec].clear();
mITSTimeStart[sec].clear();
mTPCSectIndexCache[sec].clear();
mTPCTimeStart[sec].clear();
}
if (mMCTruthON) {
mTPCLblWork.clear();
mITSLblWork.clear();
}
for (int i = 0; i < mNThreads; i++) {
mABLinksPool.threadPool[i].clear();
}
}
//______________________________________________
void MatchTPCITS::setTPCVDrift(const o2::tpc::VDriftCorrFact& v)
{
mTPCDrift = v;
mTPCVDrift = v.getVDrift();
mTPCDriftTimeOffset = v.getTimeOffset();
}
//______________________________________________
void MatchTPCITS::setTPCCorrMaps(o2::gpu::CorrectionMapsHelper* maph)
{
mTPCCorrMapsHelper = maph;
}
//______________________________________________
void MatchTPCITS::init()
{
///< perform initizalizations, precalculate what is needed
if (mInitDone) {
LOG(error) << "Initialization was already done";
return;
}
for (int i = NStopWatches; i--;) {
mTimer[i].Stop();
mTimer[i].Reset();
}
mParams = &Params::Instance();
YMaxAtXMatchingRef = mParams->XMatchingRef * 0.17632698; ///< max Y in the sector at reference X
mParams->printKeyValues();
mFT0Params = &o2::ft0::InteractionTag::Instance();
setUseMatCorrFlag(mParams->matCorr);
auto* prop = o2::base::Propagator::Instance();
if (!prop->getMatLUT() && mParams->matCorr == o2::base::Propagator::MatCorrType::USEMatCorrLUT) {
LOG(warning) << "Requested material LUT is not loaded, switching to TGeo usage";
setUseMatCorrFlag(o2::base::Propagator::MatCorrType::USEMatCorrTGeo);
}
// make sure T2GRot matrices are loaded into ITS geometry helper
o2::its::GeometryTGeo::Instance()->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2GRot) | o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L));
mSectEdgeMargin = mParams->crudeAbsDiffCut[o2::track::kY] / std::sqrt(Cos70I2);
#ifdef _ALLOW_DEBUG_TREES_
// debug streamer
if (mDBGFlags) {
mDBGOut = std::make_unique<o2::utils::TreeStreamRedirector>(mDebugTreeFileName.data(), "recreate");
}
#endif
if (mParams->runAfterBurner) { // only used in AfterBurner
mRGHelper.init(mParams->lowestLayerAB); // prepare helper for TPC track / ITS clusters matching
}
clear();
mInitDone = true;
if (fair::Logger::Logging(fair::Severity::info)) {
print();
}
}
//______________________________________________
void MatchTPCITS::updateTimeDependentParams()
{
///< update parameters depending on time (once per TF)
auto& elParam = o2::tpc::ParameterElectronics::Instance();
auto& detParam = o2::tpc::ParameterDetector::Instance();
mTPCTBinMUS = elParam.ZbinWidth;
mTPCTBinNS = mTPCTBinMUS * 1e3;
mTPCZMax = detParam.TPClength;
mTPCTBinMUSInv = 1. / mTPCTBinMUS;
assert(mITSROFrameLengthMUS > 0.0f);
mTPCBin2Z = mTPCTBinMUS * mTPCVDrift;
mZ2TPCBin = 1. / mTPCBin2Z;
mTPCVDriftInv = 1. / mTPCVDrift;
mNTPCBinsFullDrift = mTPCZMax * mZ2TPCBin;
mTPCTimeEdgeTSafeMargin = z2TPCBin(mParams->safeMarginTPCTimeEdge);
mTPCExtConstrainedNSigmaInv = 1.f / mParams->tpcExtConstrainedNSigma;
mBz = o2::base::Propagator::Instance()->getNominalBz();
mFieldON = std::abs(mBz) > 0.01;
mMinTPCTrackPtInv = (mFieldON && mParams->minTPCTrackR > 0) ? 1. / std::abs(mParams->minTPCTrackR * mBz * o2::constants::math::B2C) : 999.;
mMinITSTrackPtInv = (mFieldON && mParams->minITSTrackR > 0) ? 1. / std::abs(mParams->minITSTrackR * mBz * o2::constants::math::B2C) : 999.;
o2::math_utils::Point3D<float> p0(90., 1., 1), p1(90., 100., 100.);
auto matbd = o2::base::Propagator::Instance()->getMatBudget(mParams->matCorr, p0, p1);
mTPCmeanX0Inv = matbd.meanX2X0 / matbd.length;
const auto& trackTune = TrackTuneParams::Instance();
float scale = mTPCCorrMapsHelper->getInstLumiCTP();
if (scale < 0.f) {
scale = 0.f;
}
mCovDiagInner = trackTune.getCovInnerTotal(scale);
mCovDiagOuter = trackTune.getCovOuterTotal(scale);
}
//______________________________________________
void MatchTPCITS::selectBestMatches()
{
///< loop over match records and select the ones with best chi2
mTimer[SWSelectBest].Start(false);
int nValidated = 0, iter = 0;
mNMatches = 0;
mNCalibPrelim = 0;
do {
nValidated = 0;
int ntpc = mTPCWork.size(), nremaining = 0;
for (int it = 0; it < ntpc; it++) {
auto& tTPC = mTPCWork[it];
if (isDisabledTPC(tTPC) || isValidatedTPC(tTPC)) {
continue;
}
nremaining++;
if (validateTPCMatch(it)) {
nValidated++;
if (mVDriftCalibOn && (!mFieldON || std::abs(tTPC.getQ2Pt()) < mParams->maxVDriftTrackQ2Pt)) {
mNCalibPrelim++;
}
continue;
}
}
if (mParams->verbosity > 0) {
LOGP(info, "iter {}: Validated {} of {} remaining matches", iter, nValidated, nremaining);
}
iter++;
mNMatches += nValidated;
} while (nValidated);
mTimer[SWSelectBest].Stop();
LOGP(info, "Validated {} matches out of {} for {} TPC and {} ITS tracks in {} iterations", mNMatches, mNMatchesControl, mTPCWork.size(), mITSWork.size(), iter);
}
//______________________________________________
bool MatchTPCITS::validateTPCMatch(int iTPC)
{
auto& tTPC = mTPCWork[iTPC];
auto& rcTPC = mMatchRecordsTPC[tTPC.matchID]; // best TPC->ITS match
// check if it is consistent with corresponding ITS->TPC match
auto& tITS = mITSWork[rcTPC.partnerID]; // partner ITS track
auto& rcITS = mMatchRecordsITS[tITS.matchID]; // best ITS->TPC match record
if (rcITS.nextRecID == Validated) {
return false;
}
if (rcITS.partnerID == iTPC) { // is best matching TPC track for this ITS track actually iTPC?
int cloneID = tITS.getCloneShift(); // check if there is a clone of tITS
while (cloneID) {
cloneID += rcTPC.partnerID;
auto& tITSClone = mITSWork[cloneID];
if (isDisabledITS(tITSClone)) { // ignore clone
break;
}
int nextITSCloneMatchID = tITSClone.matchID;
if (rcITS.isBetter(mMatchRecordsITS[nextITSCloneMatchID])) { // best ITS->TPC match record for the clone is worse than the rcITS
LOGP(debug, "Suppressing clone cloneID={} of winner clone {} of source ITS {}", cloneID, rcTPC.partnerID, tITS.sourceID);
while (nextITSCloneMatchID > MinusOne) {
auto& rcITSClone = mMatchRecordsITS[nextITSCloneMatchID];
removeITSfromTPC(cloneID, rcITSClone.partnerID);
nextITSCloneMatchID = rcITSClone.nextRecID;
}
tITSClone.matchID = MinusTen; // disable
break;
}
return false; // ignore match at this iteration
}
// unlink winner TPC track from all ITS candidates except winning one
int nextTPC = rcTPC.nextRecID;
while (nextTPC > MinusOne) {
auto& rcTPCrem = mMatchRecordsTPC[nextTPC];
removeTPCfromITS(iTPC, rcTPCrem.partnerID); // remove references on mtID from ITS match=rcTPCrem.partnerID
nextTPC = rcTPCrem.nextRecID;
}
rcTPC.nextRecID = Validated;
int itsWinID = rcTPC.partnerID;
// unlink winner ITS match from all TPC matches using it
int nextITS = rcITS.nextRecID;
while (nextITS > MinusOne) {
auto& rcITSrem = mMatchRecordsITS[nextITS];
removeITSfromTPC(itsWinID, rcITSrem.partnerID); // remove references on itsWinID from TPC match=rcITSrem.partnerID
nextITS = rcITSrem.nextRecID;
}
rcITS.nextRecID = Validated;
tTPC.gid.setBit(0); // Flag full match
return true;
}
return false;
}
//______________________________________________
int MatchTPCITS::getNMatchRecordsTPC(const TrackLocTPC& tTPC) const
{
///< get number of matching records for TPC track
int count = 0, recID = tTPC.matchID;
while (recID > MinusOne) {
recID = mMatchRecordsTPC[recID].nextRecID;
count++;
}
return count;
}
//______________________________________________
int MatchTPCITS::getNMatchRecordsITS(const TrackLocITS& tTPC) const
{
///< get number of matching records for ITS track
int count = 0, recID = tTPC.matchID;
while (recID > MinusOne) {
auto& itsRecord = mMatchRecordsITS[recID];
recID = itsRecord.nextRecID;
count++;
}
return count;
}
//______________________________________________
int MatchTPCITS::addTPCSeed(const o2::track::TrackParCov& _tr, float t0, float terr, GTrackID srcGID, int tpcID)
{
// account single TPC seed, can be from standalone TPC track or constrained track from match to TRD and/or TOF
const float SQRT12DInv = 2. / sqrt(12.);
if (_tr.getX() > o2::constants::geom::XTPCInnerRef + 0.1 || std::abs(_tr.getQ2Pt()) > mMinTPCTrackPtInv) {
return -99;
}
const auto& tpcOrig = mTPCTracksArray[tpcID];
// discard tracks w/o certain number of total or innermost pads (last cluster is innermost one)
if (tpcOrig.getNClusterReferences() < mParams->minTPCClusters) {
return -89;
}
uint8_t clSect = 0, clRow = 0;
uint32_t clIdx = 0;
tpcOrig.getClusterReference(mTPCTrackClusIdx, tpcOrig.getNClusterReferences() - 1, clSect, clRow, clIdx);
if (clRow > mParams->askMinTPCRow[clSect]) {
return -9;
}
const auto& clus = mTPCClusterIdxStruct->clusters[clSect][clRow][clIdx];
uint8_t padFromEdge = uint8_t(clus.getPad());
if (padFromEdge > TPCGeometry.NPads(clRow) / 2) {
padFromEdge = TPCGeometry.NPads(clRow) - 1 - padFromEdge;
}
// create working copy of track param
bool extConstrained = srcGID.getSource() != GTrackID::TPC;
if (extConstrained) {
terr *= mParams->tpcExtConstrainedNSigma;
} else {
terr += tpcTimeBin2MUS(tpcOrig.hasBothSidesClusters() ? mParams->safeMarginTPCITSTimeBin : mTPCTimeEdgeTSafeMargin);
}
auto& trc = mTPCWork.emplace_back(
TrackLocTPC{_tr, {t0 - terr, t0 + terr}, extConstrained ? t0 : tpcTimeBin2MUS(tpcOrig.getTime0()),
// for A/C constrained tracks the terr is half-interval, for externally constrained tracks it is sigma*Nsigma
terr * (extConstrained ? mTPCExtConstrainedNSigmaInv : SQRT12DInv),
tpcID,
srcGID,
MinusOne,
clRow,
padFromEdge,
(extConstrained || tpcOrig.hasBothSidesClusters()) ? TrackLocTPC::Constrained : (tpcOrig.hasASideClustersOnly() ? TrackLocTPC::ASide : TrackLocTPC::CSide)});
// propagate to matching Xref
const auto& trackTune = TrackTuneParams::Instance();
// only TPC standalone need to be corrected on the input, provided they were not corrected at the source level,
// other inputs are corrected in respective upstream matching processes
if (srcGID.getSource() == GTrackID::TPC && !trackTune.sourceLevelTPC) {
if (trackTune.useTPCInnerCorr) {
trc.updateParams(trackTune.tpcParInner);
}
if (trackTune.tpcCovInnerType != TrackTuneParams::AddCovType::Disable) {
trc.updateCov(mCovDiagInner, trackTune.tpcCovInnerType == TrackTuneParams::AddCovType::WithCorrelations);
}
}
if (!propagateToRefX(trc)) {
mTPCWork.pop_back(); // discard track whose propagation to XMatchingRef failed
return -1;
}
if (mMCTruthON) {
mTPCLblWork.emplace_back(mTPCTrkLabels[tpcID]);
}
// cache work track index
mTPCSectIndexCache[o2::math_utils::angle2Sector(trc.getAlpha())].push_back(mTPCWork.size() - 1);
return 0;
}
//______________________________________________
bool MatchTPCITS::prepareTPCData()
{
///< load TPC data and prepare for matching
mTimer[SWPrepTPC].Start(false);
const auto& inp = *mRecoCont;
mTPCTracksArray = inp.getTPCTracks();
mTPCTrackClusIdx = inp.getTPCTracksClusterRefs();
mTPCClusterIdxStruct = &inp.inputsTPCclusters->clusterIndex;
mTPCRefitterShMap = inp.clusterShMapTPC;
mTPCRefitterOccMap = inp.occupancyMapTPC;
if (mMCTruthON) {
mTPCTrkLabels = inp.getTPCTracksMCLabels();
}
int ntr = mTPCTracksArray.size(), ntrW = 0.7 * ntr;
mMatchRecordsTPC.reserve(mParams->maxMatchCandidates * ntrW); // number of records might be actually more than N tracks!
mTPCWork.reserve(ntrW);
if (mMCTruthON) {
mTPCLblWork.reserve(ntrW);
}
for (int sec = o2::constants::math::NSectors; sec--;) {
mTPCSectIndexCache[sec].reserve(100 + 1.2 * ntrW / o2::constants::math::NSectors);
}
mTPCRefitter = std::make_unique<o2::gpu::GPUO2InterfaceRefit>(mTPCClusterIdxStruct, mTPCCorrMapsHelper, mBz, mTPCTrackClusIdx.data(), 0, mTPCRefitterShMap.data(), mTPCRefitterOccMap.data(), mTPCRefitterOccMap.size(), nullptr, o2::base::Propagator::Instance());
mTPCRefitter->setTrackReferenceX(900); // disable propagation after refit by setting reference to value > 500
mNTPCOccBinLength = mTPCRefitter->getParam()->rec.tpc.occupancyMapTimeBins;
mTBinClOcc.clear();
if (mNTPCOccBinLength > 1 && mTPCRefitterOccMap.size()) {
mNTPCOccBinLengthInv = 1. / mNTPCOccBinLength;
int nTPCBins = mNHBPerTF * o2::constants::lhc::LHCMaxBunches / 8, ninteg = 0;
int nTPCOccBins = nTPCBins * mNTPCOccBinLengthInv, sumBins = std::max(1, int(o2::constants::lhc::LHCMaxBunches / 8 * mNTPCOccBinLengthInv));
mTBinClOcc.resize(nTPCOccBins);
std::vector<float> mltHistTB(nTPCOccBins);
float sm = 0., tb = 0.5 * mNTPCOccBinLength;
for (int i = 0; i < nTPCOccBins; i++) {
mltHistTB[i] = mTPCRefitter->getParam()->GetUnscaledMult(tb);
tb += mNTPCOccBinLength;
}
for (int i = nTPCOccBins; i--;) {
sm += mltHistTB[i];
if (i + sumBins < nTPCOccBins) {
sm -= mltHistTB[i + sumBins];
}
mTBinClOcc[i] = sm;
}
} else {
mTBinClOcc.resize(1);
}
auto creator = [this](auto& trk, GTrackID gid, float time0, float terr) {
if constexpr (isITSTrack<decltype(trk)>()) {
// do nothing, ITS tracks will be processed in a direct loop over ROFs
}
if constexpr (!std::is_base_of_v<o2::track::TrackParCov, std::decay_t<decltype(trk)>>) {
return true;
} else if (std::abs(trk.getQ2Pt()) > mMinTPCTrackPtInv) {
return true;
}
int resAdd = -100;
int tpcIndex = -1;
if constexpr (isTPCTrack<decltype(trk)>()) {
// unconstrained TPC track, with t0 = TrackTPC.getTime0+0.5*(DeltaFwd-DeltaBwd) and terr = 0.5*(DeltaFwd+DeltaBwd) in TimeBins
if (!this->mSkipTPCOnly && trk.getNClusters() > 0) {
resAdd = this->addTPCSeed(trk, this->tpcTimeBin2MUS(time0), this->tpcTimeBin2MUS(terr), gid, (tpcIndex = gid.getIndex()));
}
}
if constexpr (isTPCTOFTrack<decltype(trk)>()) {
// TPC track constrained by TOF time, time and its error in \mus
resAdd = this->addTPCSeed(trk, time0, terr, gid, (tpcIndex = this->mRecoCont->getTPCContributorGID(gid)));
}
if constexpr (isTRDTrack<decltype(trk)>()) {
// TPC track constrained by TRD trigger time, time and its error in \mus
resAdd = this->addTPCSeed(trk, time0, terr, gid, (tpcIndex = this->mRecoCont->getTPCContributorGID(gid)));
}
#ifdef _ALLOW_DEBUG_TREES_
if (resAdd > -10 && mDBGOut && isDebugFlag(TPCOrigTree)) {
dumpTPCOrig(resAdd == 0, tpcIndex);
}
#endif
// note: TPCTRDTPF tracks are actually TRD track with extra TOF cluster
return true;
};
mRecoCont->createTracksVariadic(creator);
float maxTime = 0;
int nITSROFs = mITSROFTimes.size();
// sort tracks in each sector according to their timeMax
for (int sec = o2::constants::math::NSectors; sec--;) {
auto& indexCache = mTPCSectIndexCache[sec];
if (mParams->verbosity > 0) {
LOG(info) << "Sorting sector" << sec << " | " << indexCache.size() << " TPC tracks";
}
if (!indexCache.size()) {
continue;
}
std::sort(indexCache.begin(), indexCache.end(), [this](int a, int b) {
auto& trcA = mTPCWork[a];
auto& trcB = mTPCWork[b];
return (trcA.tBracket.getMax() - trcB.tBracket.getMax()) < 0.;
});
// build array of 1st entries with tmax corresponding to each ITS ROF (or trigger),
// TPC tracks below this entry cannot match to ITS tracks of this and higher ROFs
float tmax = mTPCWork[indexCache.back()].tBracket.getMax();
if (maxTime < tmax) {
maxTime = tmax;
}
int nbins = 1 + (mITSTriggered ? time2ITSROFrameTrig(tmax, 0) : time2ITSROFrameCont(tmax));
auto& timeStart = mTPCTimeStart[sec];
timeStart.resize(nbins, -1);
int itsROF = 0;
timeStart[0] = 0;
for (int itr = 0; itr < (int)indexCache.size(); itr++) {
auto& trc = mTPCWork[indexCache[itr]];
while (itsROF < nITSROFs && !(trc.tBracket < mITSROFTimes[itsROF])) { // 1st ITS frame after max allowed time for this TPC track
itsROF++;
}
int itsROFMatch = itsROF;
if (itsROFMatch && timeStart[--itsROFMatch] == -1) { // register ITSrof preceding the one which exceeds the TPC track tmax
timeStart[itsROFMatch] = itr;
}
}
for (int i = 1; i < nbins; i++) {
if (timeStart[i] == -1) { // fill gaps with preceding indices
timeStart[i] = timeStart[i - 1];
}
}
} // loop over tracks of single sector
// FIXME We probably don't need this
/*
// create mapping from TPC time to ITS ROFs
if (mITSROFTimes.back() < maxTime) {
maxTime = mITSROFTimes.back().getMax();
}
int nb = int(maxTime) + 1;
mITSROFofTPCBin.resize(nb, -1);
int itsROF = 0;
for (int ib = 0; ib < nb; ib++) {
while (itsROF < nITSROFs && ib < mITSROFTimes[itsROF].getMin()) {
itsROF++;
}
mITSROFofTPCBin[ib] = itsROF;
}
*/
mInteractionMUSLUT.clear();
mInteractionMUSLUT.resize(maxTime + 3 * o2::constants::lhc::LHCOrbitMUS, -1);
mTimer[SWPrepTPC].Stop();
return mTPCWork.size() > 0;
}
//_____________________________________________________
bool MatchTPCITS::prepareITSData()
{
static size_t errCount = 0;
constexpr size_t MaxErrors2Report = 10;
// Do preparatory work for matching
mTimer[SWPrepITS].Start(false);
const auto& inp = *mRecoCont;
// ITS clusters
mITSClusterROFRec = inp.getITSClustersROFRecords();
const auto clusITS = inp.getITSClusters();
if (mITSClusterROFRec.empty() || clusITS.empty()) {
LOG(info) << "No ITS clusters";
return false;
}
const auto patterns = inp.getITSClustersPatterns();
auto pattIt = patterns.begin();
mITSClustersArray.reserve(clusITS.size());
#ifdef ENABLE_UPGRADES
bool withITS3 = o2::GlobalParams::Instance().withITS3;
if (withITS3) {
o2::its3::ioutils::convertCompactClusters(clusITS, pattIt, mITSClustersArray, mIT3Dict);
} else {
o2::its::ioutils::convertCompactClusters(clusITS, pattIt, mITSClustersArray, mITSDict);
}
#else
o2::its::ioutils::convertCompactClusters(clusITS, pattIt, mITSClustersArray, mITSDict);
#endif
// ITS clusters sizes
mITSClusterSizes.reserve(clusITS.size());
auto pattIt2 = patterns.begin();
for (auto& clus : clusITS) {
auto pattID = clus.getPatternID();
unsigned int npix;
#ifdef ENABLE_UPGRADES
auto ib = o2::its3::constants::detID::isDetITS3(clus.getChipID());
if ((pattID == o2::itsmft::CompCluster::InvalidPatternID) || ((withITS3) ? mIT3Dict->isGroup(pattID, ib) : mITSDict->isGroup(pattID))) { // braces guarantee evaluation order
#else
if (pattID == o2::itsmft::CompCluster::InvalidPatternID || mITSDict->isGroup(pattID)) {
#endif
o2::itsmft::ClusterPattern patt;
patt.acquirePattern(pattIt2);
npix = patt.getNPixels();
} else {
#ifdef ENABLE_UPGRADES
if (withITS3) {
npix = mIT3Dict->getNpixels(pattID, ib);
} else {
npix = mITSDict->getNpixels(pattID);
}
#else
npix = mITSDict->getNpixels(pattID);
#endif
}
mITSClusterSizes.push_back(std::clamp(npix, 0u, 255u));
}
if (mMCTruthON) {
mITSClsLabels = inp.mcITSClusters.get();
}
// ITS tracks
mITSTracksArray = inp.getITSTracks();
mITSTrackClusIdx = inp.getITSTracksClusterRefs();
mITSTrackROFRec = inp.getITSTracksROFRecords();
if (mMCTruthON) {
mITSTrkLabels = inp.getITSTracksMCLabels();
}
int nROFs = mITSTrackROFRec.size();
mITSWork.reserve(mITSTracksArray.size());
// total N ITS clusters in TF
const auto& lastClROF = mITSClusterROFRec[nROFs - 1];
int nITSClus = lastClROF.getFirstEntry() + lastClROF.getNEntries();
mABClusterLinkIndex.resize(nITSClus, MinusOne);
for (int sec = o2::constants::math::NSectors; sec--;) {
mITSTimeStart[sec].resize(nROFs, -1); // start of ITS work tracks in every sector
}
long nHBF = o2::base::GRPGeomHelper::getNHBFPerTF();
long maxBCs = nHBF * long(o2::constants::lhc::LHCMaxBunches);
o2::track::TrackLTIntegral trackLTInt;
trackLTInt.setTimeNotNeeded();
for (int irof = 0; irof < nROFs; irof++) {
const auto& rofRec = mITSTrackROFRec[irof];
long nBC = rofRec.getBCData().differenceInBC(mStartIR);
if (nBC > maxBCs || nBC < 0) {
if (++errCount < MaxErrors2Report) {
LOGP(alarm, "ITS ROF#{} start {} is not compatible with TF 1st orbit {} or TF length of {} HBFs",
irof, rofRec.getBCData().asString(), mStartIR.asString(), nHBF);
}
break;
}
float tMin = nBC * o2::constants::lhc::LHCBunchSpacingMUS + mITSTimeBiasMUS;
float tMax = (nBC + mITSROFrameLengthInBC) * o2::constants::lhc::LHCBunchSpacingMUS + mITSTimeBiasMUS;
if (!mITSTriggered) {
size_t irofCont = nBC / mITSROFrameLengthInBC;
if (mITSTrackROFContMapping.size() <= irofCont) { // there might be gaps in the non-empty rofs, this will map continuous ROFs index to non empty ones
mITSTrackROFContMapping.resize((1 + irofCont / 128) * 128, 0);
}
mITSTrackROFContMapping[irofCont] = irof;
}
mITSROFTimes.emplace_back(tMin, tMax); // ITS ROF min/max time
for (int sec = o2::constants::math::NSectors; sec--;) { // start of sector's tracks for this ROF
mITSTimeStart[sec][irof] = mITSSectIndexCache[sec].size(); // The sorting does not affect this
}
int trlim = rofRec.getFirstEntry() + rofRec.getNEntries();
for (int it = rofRec.getFirstEntry(); it < trlim; it++) {
const auto& trcOrig = mITSTracksArray[it];
if (mParams->runAfterBurner) {
flagUsedITSClusters(trcOrig);
}
if (trcOrig.getParamOut().getX() < 1.) {
continue; // backward refit failed
}
if (std::abs(trcOrig.getQ2Pt()) > mMinITSTrackPtInv) {
continue;
}
int nWorkTracks = mITSWork.size();
// working copy of outer track param
auto& trc = mITSWork.emplace_back(TrackLocITS{trcOrig.getParamOut(), {tMin, tMax}, it, irof, MinusOne});
if (!trc.rotate(o2::math_utils::angle2Alpha(trc.getPhiPos()))) {
mITSWork.pop_back(); // discard failed track
continue;
}
// make sure the track is at the ref. radius
trackLTInt.clearFast();
if (!propagateToRefX(trc, &trackLTInt)) {
mITSWork.pop_back(); // discard failed track
continue; // add to cache only those ITS tracks which reached ref.X and have reasonable snp
}
trc.xrho = trackLTInt.getXRho(); // we collect seen x*rho and distance to the reference X for further PID correcrions
trc.dL = trackLTInt.getL();
if (mMCTruthON) {
mITSLblWork.emplace_back(mITSTrkLabels[it]);
}
trc.setUserField(0);
// cache work track index
int sector = o2::math_utils::angle2Sector(trc.getAlpha());
mITSSectIndexCache[sector].push_back(nWorkTracks);
// If the ITS track is very close to the sector edge, it may match also to a TPC track in the neighb. sector.
// For a track with Yr and Phir at Xr the distance^2 between the poisition of this track in the neighb. sector
// when propagated to Xr (in this neighbouring sector) and the edge will be (neglecting the curvature)
// [(Xr*tg(10)-Yr)/(tgPhir+tg70)]^2 / cos(70)^2 // for the next sector
// [(Xr*tg(10)+Yr)/(tgPhir-tg70)]^2 / cos(70)^2 // for the prev sector
// Distances to the sector edges in neighbourings sectors (at Xref in their proper frames)
float trcY = trc.getY(), tgp = trc.getSnp();
tgp /= std::sqrt((1.f - tgp) * (1.f + tgp)); // tan of track direction XY
float dyUpDn[2] = {std::abs((YMaxAtXMatchingRef - trcY) / (tgp + Tan70)), std::abs((YMaxAtXMatchingRef + trcY) / (tgp - Tan70))}; // sector up, down edge distances
// we do the cloning for closest edge only
int sel = dyUpDn[0] < dyUpDn[1] ? 0 : 1;
if (dyUpDn[sel] < mSectEdgeMargin) { // need to check this track for matching in sector up or down
int sectNeib = sel == 0 ? (sector < (o2::constants::math::NSectors - 1) ? sector + 1 : 0) : (sector > 1 ? sector - 1 : o2::constants::math::NSectors - 1);
addLastTrackCloneForNeighbourSector(sectNeib, &trackLTInt);
}
}
}
if (!mITSTriggered) { // fill the gaps;
int nr = mITSTrackROFContMapping.size();
for (int i = 1; i < nr; i++) {
if (mITSTrackROFContMapping[i] < mITSTrackROFContMapping[i - 1]) {
mITSTrackROFContMapping[i] = mITSTrackROFContMapping[i - 1];
}
}
}
// sort tracks in each sector according to their min time, then tgl
// RSTODO: sorting in tgl will be dangerous once the tracks with different time uncertaincies will be added
for (int sec = o2::constants::math::NSectors; sec--;) {
auto& indexCache = mITSSectIndexCache[sec];
if (mParams->verbosity > 0) {
LOG(info) << "Sorting sector" << sec << " | " << indexCache.size() << " ITS tracks";
}
if (!indexCache.size()) {
continue;
}
std::sort(indexCache.begin(), indexCache.end(), [this](int a, int b) {
auto& trackA = mITSWork[a];
auto& trackB = mITSWork[b];
if (trackA.tBracket.getMin() < trackB.tBracket.getMin()) {
return true;
} else if (trackA.tBracket.getMin() > trackB.tBracket.getMin()) {
return false;
}
return trackA.getTgl() < trackB.getTgl();
});
} // loop over tracks of single sector
mMatchRecordsITS.reserve(mITSWork.size() * mParams->maxMatchCandidates);
mTimer[SWPrepITS].Stop();
return nITSClus > 0;
}
//_____________________________________________________
bool MatchTPCITS::prepareFITData()
{
// If available, read FIT Info
if (mUseFT0) {
mFITInfo = mRecoCont->getFT0RecPoints();
prepareInteractionTimes();
}
return true;
}
//_____________________________________________________
void MatchTPCITS::doMatching(int sec)
{
///< run matching for currently cached ITS data for given TPC sector
auto& cacheITS = mITSSectIndexCache[sec]; // array of cached ITS track indices for this sector
auto& cacheTPC = mTPCSectIndexCache[sec]; // array of cached ITS track indices for this sector
auto& timeStartTPC = mTPCTimeStart[sec]; // array of 1st TPC track with timeMax in ITS ROFrame
auto& timeStartITS = mITSTimeStart[sec];
int nTracksTPC = cacheTPC.size(), nTracksITS = cacheITS.size();
if (!nTracksTPC || !nTracksITS) {
if (mParams->verbosity > 0) {
LOG(info) << "Matchng sector " << sec << " : N tracks TPC:" << nTracksTPC << " ITS:" << nTracksITS << " in sector " << sec;
}
return;
}
/// full drift time + safety margin
float maxTDriftSafe = tpcTimeBin2MUS(mNTPCBinsFullDrift + mParams->safeMarginTPCITSTimeBin + mTPCTimeEdgeTSafeMargin);
float vdErrT = tpcTimeBin2MUS(mZ2TPCBin * mParams->maxVDriftUncertainty);
// get min ROFrame of ITS tracks currently in cache
auto minROFITS = mITSWork[cacheITS.front()].roFrame;
if (minROFITS >= int(timeStartTPC.size())) {
LOG(info) << "ITS min ROFrame " << minROFITS << " exceeds all cached TPC track ROF eqiuvalent " << cacheTPC.size() - 1;
return;
}
int nCheckTPCControl = 0, nCheckITSControl = 0, nMatchesControl = 0; // temporary
int idxMinTPC = timeStartTPC[minROFITS]; // index of 1st cached TPC track within cached ITS ROFrames
auto t2nbs = tpcTimeBin2MUS(mZ2TPCBin * mParams->tpcTimeICMatchingNSigma);
bool checkInteractionCandidates = mUseFT0 && mParams->validateMatchByFIT != MatchTPCITSParams::Disable;
int itsROBin = 0;
for (int itpc = idxMinTPC; itpc < nTracksTPC; itpc++) {
auto& trefTPC = mTPCWork[cacheTPC[itpc]];
// estimate ITS 1st ROframe bin this track may match to: TPC track are sorted according to their
// timeMax, hence the timeMax - MaxmNTPCBinsFullDrift are non-decreasing
auto tmn = trefTPC.tBracket.getMax() - maxTDriftSafe;
itsROBin = mITSTriggered ? time2ITSROFrameTrig(tmn, itsROBin) : time2ITSROFrameCont(tmn);
if (itsROBin >= int(timeStartITS.size())) { // time of TPC track exceeds the max time of ITS in the cache
break;
}
int iits0 = timeStartITS[itsROBin];
nCheckTPCControl++;
for (auto iits = iits0; iits < nTracksITS; iits++) {
auto& trefITS = mITSWork[cacheITS[iits]];
// compare if the ITS and TPC tracks may overlap in time
LOG(debug) << "TPC bracket: " << trefTPC.tBracket.asString() << " ITS bracket: " << trefITS.tBracket.asString() << " TPCtgl: " << trefTPC.getTgl() << " ITStgl: " << trefITS.getTgl();
if (trefTPC.tBracket < trefITS.tBracket) { // since TPC tracks are sorted in timeMax and ITS tracks are sorted in timeMin all following ITS tracks also will not match
break;
}
if (trefTPC.tBracket > trefITS.tBracket) { // its bracket precedes TPC bracket
continue;
}
// is corrected TPC track time compatible with ITS ROF expressed
auto deltaT = (trefITS.getZ() - trefTPC.getZ()) * mTPCVDriftInv; // drift time difference corresponding to Z differences
auto timeCorr = trefTPC.getCorrectedTime(deltaT); // TPC time required to match to Z of ITS track
auto timeCorrErr = std::sqrt(trefITS.getSigmaZ2() + trefTPC.getSigmaZ2()) * t2nbs + mParams->safeMarginTimeCorrErr; // nsigma*error
if (mVDriftCalibOn) {
timeCorrErr += vdErrT * (250. - abs(trefITS.getZ())); // account for the extra error from TPC VDrift uncertainty
}
o2::math_utils::Bracketf_t trange(timeCorr - timeCorrErr, timeCorr + timeCorrErr);
LOG(debug) << "TPC range: " << trange.asString() << " ITS bracket: " << trefITS.tBracket.asString() << " DZ: " << (trefITS.getZ() - trefTPC.getZ()) << " DT: " << timeCorr;
// check if the assigned time is strictly within the ITS bracket
auto cmpITSBracket = trefITS.tBracket.isOutside(timeCorr);
if (cmpITSBracket) { // no, check if brackets are overlapping at all
if (trefITS.tBracket.isOutside(trange)) {
continue;
}
if (mParams->ITSTimeOutliersPolicy == MatchTPCITSParams::TimeOutliersPolicy::Adjust) {
if (cmpITSBracket == o2::math_utils::Bracketf_t::Below) {
timeCorr = trefITS.tBracket.getMin();
trange.setMin(timeCorr);
} else {
timeCorr = trefITS.tBracket.getMax();
trange.setMax(timeCorr);
}
} else if (mParams->ITSTimeOutliersPolicy == MatchTPCITSParams::TimeOutliersPolicy::Reject) {
continue;
}
}
nCheckITSControl++;
float chi2 = -1;
int rejFlag = compareTPCITSTracks(trefITS, trefTPC, chi2);
#ifdef _ALLOW_DEBUG_TREES_
if (mDBGOut && ((rejFlag == Accept && isDebugFlag(MatchTreeAccOnly)) || isDebugFlag(MatchTreeAll))) {
fillTPCITSmatchTree(cacheITS[iits], cacheTPC[itpc], rejFlag, chi2, timeCorr);
}
#endif
/*
// RS: this might be dangerous for ITS tracks with different time coverages.
if (rejFlag == RejectOnTgl) {
// ITS tracks in each ROFrame are ordered in Tgl, hence if this check failed on Tgl check
// (i.e. tgl_its>tgl_tpc+tolerance), then all other ITS tracks in this ROFrame will also have tgl too large.
// Jump on the 1st ITS track of the next ROFrame
int rof = trefITS.roFrame;
bool stop = false;
do {
if (++rof >= int(timeStartITS.size())) {
stop = true;
break; // no more ITS ROFrames in cache
}
iits = timeStartITS[rof] - 1; // next track to be checked -1
} while (iits <= timeStartITS[trefITS.roFrame]); // skip empty bins
if (stop) {
break;
}
continue;
}
*/
if (rejFlag != Accept) {
continue;
}
int matchedIC = MinusOne;
if (!isCosmics()) {
// validate by bunch filling scheme
if (mUseBCFilling) {
auto irBracket = tBracket2IRBracket(trange);
if (irBracket.isInvalid()) {
continue;
}
}
if (checkInteractionCandidates && mInteractions.size()) {
// check if corrected TPC track time is compatible with any of interaction times
int tmus = trange.getMin();
if (tmus < 0) {
tmus = 0;
}
auto entStart = tmus < int(mInteractionMUSLUT.size()) ? mInteractionMUSLUT[tmus] : (mInteractionMUSLUT.size() ? mInteractionMUSLUT.back() : 0);
for (int ent = entStart; ent < (int)mInteractions.size(); ent++) {
auto cmp = mInteractions[ent].tBracket.isOutside(trange);
if (cmp == o2::math_utils::Bracketf_t::Above) { // trange is above this interaction candidate, the following ones may match
continue;
}
if (cmp == o2::math_utils::Bracketf_t::Inside) {
matchedIC = ent;
}
break; // we loop till 1st matching IC or the one above the trange (since IC are ordered, all others will be above too)
}
}
if (mParams->validateMatchByFIT == MatchTPCITSParams::Require && matchedIC == MinusOne) {
continue;
}
}
registerMatchRecordTPC(cacheITS[iits], cacheTPC[itpc], chi2, matchedIC); // register matching candidate
nMatchesControl++;