Project
Loading...
Searching...
No Matches
CosmicsClusterCollectorSpec.cxx
Go to the documentation of this file.
1// Copyright 2019-2026 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
53
54#include <vector>
55#include <array>
56#include <limits>
57#include <unordered_set>
58#include <algorithm>
59#include <numeric>
60#include <iterator>
61#include <cmath>
62#include "TStopwatch.h"
63#include "Framework/Task.h"
94#include "GPUO2InterfaceRefit.h"
100#include "MathUtils/Utils.h"
102#include "TPCFastTransformPOD.h"
104
105using namespace o2::framework;
109
110namespace o2::globaltracking
111{
112
113namespace
114{
115constexpr float TanSector = 0.17632698f; // tan(10 deg): half opening of a sector
116
118bool outsideDriftVolume(bool sideA, float z, float margin)
119{
120 const float zLength = TPCGeo::getTPCzLength();
121 return sideA ? (z < -margin || z > zLength + margin) : (z > margin || z < -zLength - margin);
122}
123
125int tpcSide(const o2::tpc::TrackTPC& trk)
126{
127 return trk.hasASideClustersOnly() ? 1 : (trk.hasCSideClustersOnly() ? -1 : 0);
128}
129} // namespace
130
132{
133 public:
134 CosmicsClusterCollectorSpec(std::shared_ptr<DataRequest> dr, std::shared_ptr<o2::base::GRPGeomRequest> gr, bool useMC, DetID::mask_t roadDets) : mDataRequest(dr), mGGCCDBRequest(gr), mRoadDets(roadDets), mUseMC(useMC) {}
135 ~CosmicsClusterCollectorSpec() override = default;
136 void init(InitContext& ic) final;
137 void run(ProcessingContext& pc) final;
138 void endOfStream(EndOfStreamContext& ec) final;
139 void finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) final;
140
141 private:
143 struct LegBranch {
144 bool isLine = false;
145 float centerX = 0.f;
146 float centerY = 0.f;
147 float pcaX = 0.f;
148 float pcaY = 0.f;
149 float dirX = 0.f;
150 float dirY = 0.f;
151 int sign = 0;
152 int side(float x, float y) const
153 {
154 const float orientation = isLine ? (x - pcaX) * dirX + (y - pcaY) * dirY : (pcaX - centerX) * (y - centerY) - (pcaY - centerY) * (x - centerX);
155 return orientation > 0.f ? 1 : -1;
156 }
157 bool accept(float x, float y) const { return sign == 0 || side(x, y) == sign; }
158 void init(const o2::track::TrackPar& inner, const o2::track::TrackPar& outer, float bz);
159 };
160
162 struct CosmicTime {
163 bool known = false;
164 float tb = 0.f;
165 float errTB = 0.f;
166 };
167
168 void updateTimeDependentParams(ProcessingContext& pc);
169 void buildUsedMap(const RecoContainer& data, std::vector<std::pair<float, float>>& time0Windows);
170 void addTPCAttached(const RecoContainer& data, const o2::tpc::TrackTPC& trk, std::vector<o2::dataformats::CosmicTPCCluster>& out, std::unordered_set<uint32_t>& taken) const;
172 struct RoadFrame {
173 float vertexTime;
174 float dz;
175 float zTolerance;
176 int sectorMin;
177 int sectorMax;
178 uint8_t flag;
179 };
180 void addTPCCorridor(const RecoContainer& data, const o2::tpc::TrackTPC& trk, const CosmicTime& cosmicTime, std::vector<o2::dataformats::CosmicTPCCluster>& out, std::unordered_set<uint32_t>& taken) const;
181 void walkLowAngleRoad(const o2::tpc::ClusterNativeAccess& clusters, const o2::track::TrackPar& start, bool innerPart, float rMid, const LegBranch& branch, const RoadFrame& frame,
182 std::vector<o2::dataformats::CosmicTPCCluster>& out, std::unordered_set<uint32_t>& taken) const;
183 void searchRow(const o2::tpc::ClusterNativeAccess& clusters, int sector, int row, float y, float z, float snp, float tgl, float vertexTime, float zTolerance, uint8_t flag,
184 float dxRow, std::vector<o2::dataformats::CosmicTPCCluster>& out, std::unordered_set<uint32_t>& taken) const;
185 void addDebugRoadPoint(int sector, float x, float y, float z, float snp, float tgl, int row, float vertexTime, int frameCode) const;
186 struct ITSPattRequest {
187 int cosmic;
188 int cluster;
189 int index;
190 };
191 void addITS(const RecoContainer& data, GTrackID gid, uint8_t leg, std::vector<o2::dataformats::CosmicITSCluster>& out, int icosm, std::vector<ITSPattRequest>& requests, std::unordered_set<int>& matched) const;
192 void fillITSPatterns(const RecoContainer& data, std::vector<ITSPattRequest>& requests, std::vector<o2::dataformats::TrackCosmicsExtended>& cosmics) const;
193 void addTOF(const RecoContainer& data, GTrackID gid, uint8_t leg, std::vector<o2::dataformats::CosmicTOFCluster>& out, std::unordered_set<int>& matched) const;
194 void addTRD(const RecoContainer& data, GTrackID gid, uint8_t leg, std::vector<o2::dataformats::CosmicTRDTracklet>& out, std::unordered_set<int>& matched) const;
195 std::pair<float, float> timeWindowMUS(const CosmicTime& cosmicTime) const;
196 bool predictOutward(const o2::tpc::TrackTPC& leg, const CosmicTime& cosmicTime, int sector, float x, float& y, float& z) const;
197 void roadTOF(const RecoContainer& data, const o2::tpc::TrackTPC* const* legs, const CosmicTime& cosmicTime, int icosm, std::vector<o2::dataformats::CosmicTOFCluster>& out, const std::unordered_set<int>& matched, float& timeTOFMUS, float& scoreTOFPair, float& scoreTOFReversed) const;
198 void polish(const o2::tpc::TrackTPC* const* legs, float timeMUS, o2::gpu::GPUO2InterfaceRefit& refitter, o2::dataformats::TrackCosmicsExtended& out) const;
199 void roadTRD(const RecoContainer& data, const o2::tpc::TrackTPC* const* legs, const CosmicTime& cosmicTime, int icosm, std::vector<o2::dataformats::CosmicTRDTracklet>& out, const std::unordered_set<int>& matched) const;
200 void roadITS(const RecoContainer& data, const o2::dataformats::TrackCosmics& cosm, const CosmicTime& cosmicTime, int legsSide, int icosm, std::vector<o2::dataformats::CosmicITSCluster>& out, std::vector<ITSPattRequest>& requests,
201 const std::unordered_set<int>& matched) const;
202 void cacheITSChipCentres();
203 void writeDebug(const o2::dataformats::TrackCosmicsExtended& cosm, int icosm) const;
204 void flagDuplicates(std::vector<o2::dataformats::TrackCosmicsExtended>& cosmics) const;
205 void writeDebugTOF(const o2::tof::Cluster& c, int icosm, int leg, uint8_t flags) const;
206
207 std::shared_ptr<DataRequest> mDataRequest;
208 std::shared_ptr<o2::base::GRPGeomRequest> mGGCCDBRequest;
209 const o2::gpu::TPCFastTransformPOD* mCorrMap = nullptr;
210 const o2::itsmft::TopologyDictionary* mITSDict = nullptr;
211 std::vector<bool> mUsed;
212 o2::InteractionRecord mTFStart{};
213 float mCorridor = 1.f;
214 float mMaxAbsTimeErr = 0.5f;
215 size_t mMaxCosmicsPerTF = 100;
216 DetID::mask_t mRoadDets{};
217 float mRoadTOF = 5.f;
218 float mTOFFlightTol = 2.f;
219 float mTOFTimeErr = 0.1f;
220 float mRoadTRD = 5.f;
221 float mRoadITS = 1.5f;
222 std::vector<o2::math_utils::Point3D<float>> mITSChipCentres;
223 float mTPCTBinMUS = 0.2f;
224 float mBz = 0.f;
225 bool mUseMC = false;
226 mutable bool mStaggeredWarned = false;
227 mutable bool mNoDictWarned = false;
228 mutable bool mTRDMismatchWarned = false;
229 std::unique_ptr<o2::utils::TreeStreamRedirector> mDebugOut;
230 int mDbgTF = 0;
231 int mDbgCosmic = 0;
232 int mDbgLeg = 0;
233 float mDbgTauC = 0.f;
235 struct DebugCosmic {
236 std::vector<int> roadLeg;
237 std::vector<int> roadFrame;
238 std::vector<int> roadSector;
239 std::vector<int> roadRow;
240 std::vector<float> roadX;
241 std::vector<float> roadY;
242 std::vector<float> roadZ;
243 std::vector<float> roadZCos;
244 std::vector<float> roadGx;
245 std::vector<float> roadGy;
246 std::vector<float> roadSnp;
247 std::vector<float> roadTgl;
248 std::vector<int> tofLeg;
249 std::vector<int> tofChannel;
250 std::vector<int> tofFlags;
251 std::vector<double> tofTimeRaw;
252 std::vector<double> tofTime;
253 std::vector<float> tofTot;
254 std::vector<float> tofX;
255 std::vector<float> tofY;
256 std::vector<float> tofZ;
257 std::vector<float> tofGx;
258 std::vector<float> tofGy;
259 std::vector<int> trdLeg;
260 std::vector<int> trdLayer;
261 std::vector<float> trdX;
262 std::vector<float> trdY;
263 std::vector<float> trdZ;
264 std::vector<float> trdDy;
265 std::vector<float> trdDz;
266 std::vector<float> trdTrigMUS;
267 };
268 mutable std::vector<DebugCosmic> mDbgCosmics;
269 size_t mNCosmics = 0;
270 size_t mNClAttached = 0;
271 size_t mNClCorridor = 0;
272 TStopwatch mTimer;
273};
274
276{
277 mTimer.Stop();
278 mTimer.Reset();
280 mCorridor = ic.options().get<float>("corridor-width");
281 mMaxAbsTimeErr = ic.options().get<float>("max-abs-time-err");
282 mMaxCosmicsPerTF = std::max(0, ic.options().get<int>("max-cosmics-per-tf"));
283 mRoadTOF = ic.options().get<float>("tof-road-width");
284 mTOFFlightTol = ic.options().get<float>("tof-flight-tolerance");
285 mTOFTimeErr = ic.options().get<float>("tof-time-error");
286 mRoadTRD = ic.options().get<float>("trd-road-width");
287 mRoadITS = ic.options().get<float>("its-road-width");
288 if (ic.options().get<bool>("debug-tree")) {
289 const auto timesliceId = ic.services().get<const o2::framework::DeviceSpec>().inputTimesliceId;
290 const std::string name = timesliceId == 0 ? "cosmics_collector_debug.root" : fmt::format("cosmics_collector_debug_{}.root", timesliceId);
291 mDebugOut = std::make_unique<o2::utils::TreeStreamRedirector>(name.c_str(), "recreate");
292 }
293}
294
296{
297 mTimer.Start(false);
298 RecoContainer recoData;
299 recoData.collectData(pc, *mDataRequest.get());
300 updateTimeDependentParams(pc);
301
304 mTFStart = {0, tfID.firstTForbit};
306 tfInfo.vDrift = mCorrMap->getVDrift();
307 tfInfo.t0 = mCorrMap->getT0();
308
309 std::vector<o2::dataformats::TrackCosmicsExtended> cosmicsOut;
310 const auto cosmics = recoData.getCosmicTracks();
311 const size_t nCosmics = std::min(cosmics.size(), mMaxCosmicsPerTF);
312 if (nCosmics < cosmics.size()) {
313 LOGP(warning, "{} cosmics in TF {}, only the first {} are written (max-cosmics-per-tf)", cosmics.size(), tfID.tfCounter, nCosmics);
314 }
315 if (nCosmics) {
316 // only TPC tracks whose time0 is within two drift times of a leg's time0 can share clusters with the roads
317 const float maxDistTB = 2.2f * TPCGeo::getTPCzLength() / mCorrMap->getVDrift();
318 std::vector<std::pair<float, float>> time0Windows;
319 for (size_t ic = 0; ic < nCosmics; ic++) {
320 for (const auto leg : {cosmics[ic].getRefBottom(), cosmics[ic].getRefTop()}) {
321 const auto refs = recoData.getSingleDetectorRefs(leg);
322 if (refs[GTrackID::TPC].isIndexSet()) {
323 const float time0 = recoData.getTPCTrack(refs[GTrackID::TPC]).getTime0();
324 time0Windows.emplace_back(time0 - maxDistTB, time0 + maxDistTB);
325 }
326 }
327 }
328 buildUsedMap(recoData, time0Windows);
329 }
330 std::vector<ITSPattRequest> pattRequests; // ITS clusters whose patterns are not in the dictionary
331 std::unique_ptr<o2::gpu::GPUO2InterfaceRefit> tpcRefitter; // polish of the cosmics with a TOF time
332 if (nCosmics && mRoadDets[DetID::TOF] && recoData.inputsTPCclusters) {
333 const auto& clusterShMap = recoData.clusterShMapTPC;
334 const auto& occupancyMap = recoData.occupancyMapTPC;
335 tpcRefitter = std::make_unique<o2::gpu::GPUO2InterfaceRefit>(&recoData.inputsTPCclusters->clusterIndex, mCorrMap, mBz, recoData.getTPCTracksClusterRefs().data(), 0,
336 clusterShMap.data(), occupancyMap.data(), occupancyMap.size(), nullptr, o2::base::Propagator::Instance());
337 }
338 mDbgTF = tfID.tfCounter;
339 if (mDebugOut) {
340 mDbgCosmics.assign(nCosmics, DebugCosmic{});
341 }
342 for (size_t ic = 0; ic < nCosmics; ic++) {
343 const auto& cosm = cosmics[ic];
344 auto& out = cosmicsOut.emplace_back();
345 out.cosmic = cosm;
346 if (mUseMC) {
347 out.label = recoData.getCosmicTrackMCLabel(ic);
348 }
349 const GTrackID legs[2] = {cosm.getRefBottom(), cosm.getRefTop()};
350 const o2::tpc::TrackTPC* tpcLegs[2] = {nullptr, nullptr};
351 std::vector<o2::dataformats::CosmicTPCCluster>* tpcCl[2] = {&out.clTPCBottom, &out.clTPCTop};
352 std::unordered_set<int> matchedITS; // hits of the legs' matched tracks, not searched again on the road
353 std::unordered_set<int> matchedTOF;
354 std::unordered_set<int> matchedTRD;
355 for (uint8_t leg = 0; leg < 2; leg++) {
356 auto refs = recoData.getSingleDetectorRefs(legs[leg]);
357 if (refs[GTrackID::TPC].isIndexSet()) {
358 tpcLegs[leg] = &recoData.getTPCTrack(refs[GTrackID::TPC]);
359 (leg == 0 ? out.tpcBottom : out.tpcTop) = *tpcLegs[leg];
360 }
361 if (refs[GTrackID::ITS].isIndexSet()) {
362 addITS(recoData, refs[GTrackID::ITS], leg, out.clITS, ic, pattRequests, matchedITS);
363 }
364 if (refs[GTrackID::TOF].isIndexSet()) {
365 addTOF(recoData, refs[GTrackID::TOF], leg, out.clTOF, matchedTOF);
366 if (mDebugOut) {
367 writeDebugTOF(recoData.getTOFClusters()[refs[GTrackID::TOF].getIndex()], ic, leg, o2::dataformats::HitMatched);
368 }
369 }
370 if (refs[GTrackID::TRD].isIndexSet()) {
371 addTRD(recoData, refs[GTrackID::TRD], leg, out.trdTracklets, matchedTRD);
372 }
373 }
374 CosmicTime cosmicTime;
375 cosmicTime.tb = cosm.getTimeMUS().getTimeStamp() / mTPCTBinMUS;
376 cosmicTime.errTB = cosm.getTimeMUS().getTimeStampError() / mTPCTBinMUS;
377 cosmicTime.known = cosm.getTimeMUS().getTimeStampError() < mMaxAbsTimeErr;
378 mDbgCosmic = ic;
379 // TOF road first: a top/bottom hit pair matching the muon's flight gives the cosmic's time to ~ns (also for one-side legs on the same
380 // side, whose brackets leave it open by tens of mus); the TPC corridor of the other side and the TRD / ITS roads then use that time
381 if (mRoadDets[DetID::TOF]) {
382 roadTOF(recoData, tpcLegs, cosmicTime, ic, out.clTOF, matchedTOF, out.timeTOFMUS, out.scoreTOFPair, out.scoreTOFReversed);
383 }
384 if (out.hasTOFTime() && tpcRefitter && legs[0].getSource() == GTrackID::TPC && legs[1].getSource() == GTrackID::TPC) {
385 polish(tpcLegs, out.timeTOFMUS, *tpcRefitter, out);
386 }
387 CosmicTime roadTime = cosmicTime;
388 if (out.hasTOFTime()) {
389 roadTime.tb = out.timeTOFMUS / mTPCTBinMUS;
390 roadTime.errTB = mTOFTimeErr / mTPCTBinMUS;
391 roadTime.known = true;
392 }
393 mDbgTauC = roadTime.tb;
394 std::unordered_set<uint32_t> taken;
395 for (int leg = 0; leg < 2; leg++) { // attached clusters of both legs first, so that a road never takes the other leg's clusters
396 if (tpcLegs[leg]) {
397 addTPCAttached(recoData, *tpcLegs[leg], *tpcCl[leg], taken);
398 mNClAttached += tpcCl[leg]->size();
399 }
400 }
401 for (int leg = 0; leg < 2; leg++) {
402 if (tpcLegs[leg]) {
403 const size_t nAttached = tpcCl[leg]->size();
404 mDbgLeg = leg;
405 addTPCCorridor(recoData, *tpcLegs[leg], roadTime, *tpcCl[leg], taken);
406 mNClCorridor += tpcCl[leg]->size() - nAttached;
407 }
408 }
409 if (mRoadDets[DetID::TRD]) {
410 roadTRD(recoData, tpcLegs, roadTime, ic, out.trdTracklets, matchedTRD);
411 }
412 if (mRoadDets[DetID::ITS]) {
413 // the refitted cosmic's z moves with the time only if both legs are TPC-only on one side (an ITS part fixes it absolutely)
414 int legsSide = 0;
415 if (tpcLegs[0] && tpcLegs[1] && legs[0].getSource() == GTrackID::TPC && legs[1].getSource() == GTrackID::TPC && tpcSide(*tpcLegs[0]) == tpcSide(*tpcLegs[1])) {
416 legsSide = tpcSide(*tpcLegs[0]);
417 }
418 roadITS(recoData, cosm, roadTime, legsSide, ic, out.clITS, pattRequests, matchedITS);
419 }
420 }
421 if (!pattRequests.empty()) {
422 fillITSPatterns(recoData, pattRequests, cosmicsOut);
423 }
424 flagDuplicates(cosmicsOut);
425 if (mDebugOut) {
426 for (size_t ic = 0; ic < cosmicsOut.size(); ic++) {
427 writeDebug(cosmicsOut[ic], ic);
428 }
429 }
430 mNCosmics += cosmicsOut.size();
431 LOGP(info, "Collected clusters for {} cosmics in TF {}", cosmicsOut.size(), tfID.tfCounter);
432 pc.outputs().snapshot(Output{"GLO", "COSMFULL", 0}, cosmicsOut);
433 pc.outputs().snapshot(Output{"GLO", "COSMFULLTF", 0}, tfInfo);
434 pc.outputs().snapshot(Output{"GLO", "COSMFULLTFID", 0}, tfID);
435 mTimer.Stop();
436}
437
438void CosmicsClusterCollectorSpec::updateTimeDependentParams(ProcessingContext& pc)
439{
441 mCorrMap = &o2::gpu::TPCFastTransformPOD::get(pc.inputs().get<const char*>("corrMap"));
442 mTPCTBinMUS = o2::tpc::ParameterElectronics::Instance().ZbinWidth;
443 mBz = o2::base::Propagator::Instance()->getNominalBz();
444 if (mRoadDets[DetID::ITS] && mITSChipCentres.empty()) {
445 cacheITSChipCentres();
446 }
447}
448
449void CosmicsClusterCollectorSpec::cacheITSChipCentres()
450{
452 geom->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G));
453 mITSChipCentres.resize(geom->getNumberOfChips());
454 for (int chip = 0; chip < geom->getNumberOfChips(); chip++) {
455 mITSChipCentres[chip] = geom->getMatrixL2G(chip) * o2::math_utils::Point3D<float>(0.f, 0.f, 0.f);
456 }
457}
458
459void CosmicsClusterCollectorSpec::buildUsedMap(const RecoContainer& data, std::vector<std::pair<float, float>>& time0Windows)
460{
461 // merge the windows, then flag the clusters of the TPC tracks whose time0 falls into one of them
462 std::sort(time0Windows.begin(), time0Windows.end());
463 std::vector<std::pair<float, float>> merged;
464 for (const auto& window : time0Windows) {
465 if (!merged.empty() && window.first <= merged.back().second) {
466 merged.back().second = std::max(merged.back().second, window.second);
467 } else {
468 merged.push_back(window);
469 }
470 }
471 const auto& clusters = data.getTPCClusters();
472 const auto tracks = data.getTPCTracks();
473 const auto refs = data.getTPCTracksClusterRefs();
474 mUsed.assign(clusters.nClustersTotal, false);
475 for (const auto& trk : tracks) {
476 const float time0 = trk.getTime0();
477 const auto window = std::upper_bound(merged.begin(), merged.end(), time0, [](float t, const std::pair<float, float>& w) { return t < w.first; });
478 if (window == merged.begin() || time0 > std::prev(window)->second) {
479 continue;
480 }
481 for (int j = 0; j < trk.getNClusterReferences(); j++) {
482 uint8_t sector = 0;
483 uint8_t row = 0;
484 uint32_t clIdx = 0;
485 trk.getClusterReference(refs, j, sector, row, clIdx);
486 mUsed[clusters.clusterOffset[sector][row] + clIdx] = true;
487 }
488 }
489}
490
491void CosmicsClusterCollectorSpec::addTPCAttached(const RecoContainer& data, const o2::tpc::TrackTPC& trk, std::vector<o2::dataformats::CosmicTPCCluster>& out, std::unordered_set<uint32_t>& taken) const
492{
493 const auto& clusters = data.getTPCClusters();
494 const auto refs = data.getTPCTracksClusterRefs();
495 for (int j = 0; j < trk.getNClusterReferences(); j++) {
496 uint8_t sector = 0;
497 uint8_t row = 0;
498 uint32_t clIdx = 0;
499 trk.getClusterReference(refs, j, sector, row, clIdx);
500 if (!taken.insert(clusters.clusterOffset[sector][row] + clIdx).second) {
501 continue;
502 }
503 auto& cl = out.emplace_back();
504 cl.cl = clusters.clusters[sector][row][clIdx];
505 cl.sector = sector;
506 cl.row = row;
508 }
509}
510
511void CosmicsClusterCollectorSpec::LegBranch::init(const o2::track::TrackPar& inner, const o2::track::TrackPar& outer, float bz)
512{
513 if (std::abs(inner.getCurvature(bz)) < 1e-5f) { // radius > 1 km: straight line
514 isLine = true;
515 const auto point = inner.getXYZGlo();
516 const float phi = inner.getPhi();
517 dirX = std::cos(phi);
518 dirY = std::sin(phi);
519 const float proj = point.X() * dirX + point.Y() * dirY;
520 pcaX = point.X() - proj * dirX;
521 pcaY = point.Y() - proj * dirY;
522 } else {
524 float sinAlpha = 0.f;
525 float cosAlpha = 0.f;
526 inner.getCircleParams(bz, circle, sinAlpha, cosAlpha);
527 centerX = circle.xC;
528 centerY = circle.yC;
529 const float centerDist = std::sqrt(centerX * centerX + centerY * centerY);
530 if (centerDist < 1e-3f) { // circle around the beam line: no closest approach
531 sign = 0;
532 return;
533 }
534 pcaX = centerX * (1.f - circle.rC / centerDist);
535 pcaY = centerY * (1.f - circle.rC / centerDist);
536 }
537 // side of the leg from its end farther from the closest approach; a leg with ends on both sides (both > 10 cm away) spans the
538 // closest approach and is accepted on both sides
539 const auto pointIn = inner.getXYZGlo();
540 const auto pointOut = outer.getXYZGlo();
541 const float distIn2 = (pointIn.X() - pcaX) * (pointIn.X() - pcaX) + (pointIn.Y() - pcaY) * (pointIn.Y() - pcaY);
542 const float distOut2 = (pointOut.X() - pcaX) * (pointOut.X() - pcaX) + (pointOut.Y() - pcaY) * (pointOut.Y() - pcaY);
543 const int sideIn = side(pointIn.X(), pointIn.Y());
544 const int sideOut = side(pointOut.X(), pointOut.Y());
545 constexpr float MinDist2 = 10.f * 10.f;
546 if (sideIn == sideOut) {
547 sign = sideIn;
548 } else if (std::min(distIn2, distOut2) < MinDist2) {
549 sign = distIn2 > distOut2 ? sideIn : sideOut;
550 } else {
551 sign = 0;
552 }
553}
554
555void CosmicsClusterCollectorSpec::addTPCCorridor(const RecoContainer& data, const o2::tpc::TrackTPC& trk, const CosmicTime& cosmicTime, std::vector<o2::dataformats::CosmicTPCCluster>& out, std::unordered_set<uint32_t>& taken) const
556{
557 constexpr int NSectorsA = TPCGeo::getNumberOfSectorsA();
558 const float zLength = TPCGeo::getTPCzLength();
559 const float vDrift = mCorrMap->getVDrift();
560 const auto& clusters = data.getTPCClusters();
561 const float time0Leg = trk.getTime0();
562 const int legSide = tpcSide(trk);
563
564 RoadFrame frames[2];
565 int nFrames = 0;
566 if (legSide == 0) { // CE-crossing leg: its time0 is absolute
567 frames[nFrames++] = {time0Leg, 0.f, 0.f, 0, 2 * NSectorsA, 0};
568 } else {
569 frames[nFrames++] = {time0Leg, 0.f, 0.f, legSide > 0 ? 0 : NSectorsA, legSide > 0 ? NSectorsA : 2 * NSectorsA, 0};
570 if (cosmicTime.known) { // z of a one-side leg moves by side * vD * (t - time0) when its clusters are transformed with the time t
571 frames[nFrames++] = {cosmicTime.tb, legSide * (cosmicTime.tb - time0Leg) * vDrift, cosmicTime.errTB * vDrift,
572 legSide > 0 ? NSectorsA : 0, legSide > 0 ? 2 * NSectorsA : NSectorsA, o2::dataformats::CosmicTPCCluster::AbsTime};
573 }
574 }
575
576 const o2::track::TrackPar refPar[2] = {trk, trk.getParamOut()}; // rows below rMid: inner parameter, above: outer
577 LegBranch branch;
578 branch.init(refPar[0], refPar[1], mBz);
579 const auto pointIn = refPar[0].getXYZGlo();
580 const auto pointOut = refPar[1].getXYZGlo();
581 const float rIn = std::hypot(pointIn.X(), pointIn.Y());
582 const float rOut = std::hypot(pointOut.X(), pointOut.Y());
583 const float rMid = 0.5f * (rIn + rOut);
584
585 for (int iFrame = 0; iFrame < nFrames; iFrame++) {
586 const auto& frame = frames[iFrame];
587 for (int sector = frame.sectorMin; sector < frame.sectorMax; sector++) {
588 const float alpha = o2::math_utils::sector2Angle(sector % NSectorsA);
589 const float sinAlpha = std::sin(alpha);
590 const float cosAlpha = std::cos(alpha);
591 const bool sideA = sector < NSectorsA;
592 for (int iPar = 0; iPar < 2; iPar++) {
593 auto par = refPar[iPar];
594 par.setZ(par.getZ() + frame.dz);
595 if (!par.rotateParam(alpha)) { // the leg points away from this sector frame: same helix, opposite direction
596 par.invertParam();
597 if (!par.rotateParam(alpha)) {
598 continue;
599 }
600 }
601 for (int row = 0; row < o2::tpc::constants::MAXGLOBALPADROW; row++) {
602 const float xRow = TPCGeo::getRowInfoX(row);
603 if ((xRow < rMid) != (iPar == 0)) {
604 continue;
605 }
606 auto parRow = par;
607 if (!parRow.propagateParamTo(xRow, mBz)) {
608 continue;
609 }
610 // coarse acceptance at the nominal x of the row before the more expensive move to its real x
611 constexpr float CoarseMargin = 10.f; // [cm] covers the shift of y and z between the nominal and the real x
612 if (std::abs(parRow.getY()) > xRow * TanSector + mCorridor + CoarseMargin || outsideDriftVolume(sideA, parRow.getZ(), mCorridor + frame.zTolerance + CoarseMargin)) {
613 continue;
614 }
615 // the corrected clusters of this row lie at the real x of the row, x + dx(y, z), not at its nominal x
616 float xReal = xRow;
617 const float zInside = sideA ? std::clamp(parRow.getZ(), 0.f, zLength) : std::clamp(parRow.getZ(), -zLength, 0.f);
618 mCorrMap->InverseTransformYZtoX(sector, row, parRow.getY(), zInside, xReal);
619 if (!parRow.propagateParamTo(xReal, mBz)) {
620 continue;
621 }
622 const float y = parRow.getY();
623 const float z = parRow.getZ();
624 if (std::abs(y) > xRow * TanSector + mCorridor) {
625 continue;
626 }
627 if (outsideDriftVolume(sideA, z, mCorridor + frame.zTolerance)) {
628 continue;
629 }
630 if (!branch.accept(xRow * cosAlpha - y * sinAlpha, xRow * sinAlpha + y * cosAlpha)) {
631 continue;
632 }
633 if (mDebugOut) {
634 addDebugRoadPoint(sector, xReal, y, z, parRow.getSnp(), parRow.getTgl(), row, frame.vertexTime, frame.flag != 0);
635 }
636 searchRow(clusters, sector, row, y, z, parRow.getSnp(), parRow.getTgl(), frame.vertexTime, frame.zTolerance, frame.flag, 0.f, out, taken);
637 }
638 }
639 }
640 // parts of the leg nearly parallel to the pad rows, which the propagation row by row cannot reach (e.g. a closest approach to the
641 // beam line inside the TPC: in the sector of the closest approach the track runs along one pad row); a leg whose inner end is not
642 // inside its outer end (all its clusters along one pad row) is walked from the inner end alone
643 const bool splitAtRMid = rOut > rIn;
644 for (int iPar = 0; iPar < (splitAtRMid ? 2 : 1); iPar++) {
645 auto par = refPar[iPar];
646 par.setZ(par.getZ() + frame.dz);
647 walkLowAngleRoad(clusters, par, iPar == 0, splitAtRMid ? rMid : std::numeric_limits<float>::max(), branch, frame, out, taken);
648 }
649 }
650}
651
652void CosmicsClusterCollectorSpec::walkLowAngleRoad(const o2::tpc::ClusterNativeAccess& clusters, const o2::track::TrackPar& start, bool innerPart, float rMid, const LegBranch& branch,
653 const RoadFrame& frame, std::vector<o2::dataformats::CosmicTPCCluster>& out, std::unordered_set<uint32_t>& taken) const
654{
655 constexpr int NSectorsA = TPCGeo::getNumberOfSectorsA();
656 constexpr float CosSector = 0.98480775f; // cos(10 deg)
657 constexpr float Step = 0.5f; // [cm] path length per step, below the road width along the pad row
658 constexpr float MaxPath = 600.f; // [cm] protection; the walk normally ends at the TPC boundary or the closest approach
659 constexpr float MaxEntryPath = 20.f; // [cm] a start outside the leg's part (up to 10 cm past the closest approach, see LegBranch) is stepped into
660 constexpr float MinSnp = 0.8f; // below, the row crossings are well defined and found row by row
661 constexpr float XMargin = 10.f; // [cm] covers the shift between the nominal and the real x of a row (CoarseMargin of the row loop)
662 constexpr int NRows = o2::tpc::constants::MAXGLOBALPADROW;
663 const float zLength = TPCGeo::getTPCzLength();
664 const float rLow = TPCGeo::getRowInfoX(0) - mCorridor;
665 const float rHigh = TPCGeo::getRowInfoX(NRows - 1) / CosSector + mCorridor;
666 std::array<float, NSectorsA> sinSector{};
667 std::array<float, NSectorsA> cosSector{};
668 for (int sector = 0; sector < NSectorsA; sector++) {
669 const float alpha = o2::math_utils::sector2Angle(sector);
670 sinSector[sector] = std::sin(alpha);
671 cosSector[sector] = std::cos(alpha);
672 }
673
674 bool startInside = false; // the start point lies in the leg's part
675 for (int direction = 0; direction < 2; direction++) {
676 auto par = start;
677 if (direction == 1) {
678 par.invertParam();
679 }
680 bool entered = direction == 1 && startInside; // the walk has reached the leg's part
681 // the start point is processed in the first direction only
682 for (float path = direction == 0 ? 0.f : Step; path < MaxPath; path += Step) {
683 // step along the helix in the frame of its direction (snp = 0), where the propagation is always defined
684 if (path > 0.f && (!par.rotateParam(par.getPhi()) || !par.propagateParamTo(par.getX() + Step, mBz))) {
685 break;
686 }
687 const auto pos = par.getXYZGlo();
688 const float r = std::hypot(pos.X(), pos.Y());
689 if (r > rHigh || std::abs(pos.Z()) > zLength + mCorridor + frame.zTolerance) {
690 break;
691 }
692 // outside the leg's part: past its closest approach to the beam line, beyond rMid or inside the inner radius; the start of the walk
693 // can lie there (a leg's end a few cm past the closest approach, an inner end inside the nominal inner radius)
694 if (r < rLow || (r < rMid) != innerPart || !branch.accept(pos.X(), pos.Y())) {
695 if (entered || path > MaxEntryPath) {
696 break;
697 }
698 continue;
699 }
700 entered = true;
701 startInside = startInside || path == 0.f;
702 const float phiDir = par.getPhi();
703 const int sectorPos = o2::math_utils::angle2Sector(std::atan2(pos.Y(), pos.X()));
704 for (int dSector = -1; dSector <= 1; dSector++) { // the road can reach into the neighbouring sectors
705 const int sectorInSide = (sectorPos + dSector + NSectorsA) % NSectorsA;
706 for (int sector = sectorInSide; sector < 2 * NSectorsA; sector += NSectorsA) {
707 if (sector < frame.sectorMin || sector >= frame.sectorMax) {
708 continue;
709 }
710 const bool sideA = sector < NSectorsA;
711 if (outsideDriftVolume(sideA, pos.Z(), mCorridor + frame.zTolerance)) {
712 continue;
713 }
714 const float alpha = o2::math_utils::sector2Angle(sectorInSide);
715 const float sinAlpha = sinSector[sectorInSide];
716 const float cosAlpha = cosSector[sectorInSide];
717 const float x = pos.X() * cosAlpha + pos.Y() * sinAlpha;
718 const float y = -pos.X() * sinAlpha + pos.Y() * cosAlpha;
719 float snp = std::sin(phiDir - alpha);
720 float tgl = par.getTgl();
721 if (std::cos(phiDir - alpha) < 0.f) { // the same line with its direction along +x of the sector frame, as in the row search
722 snp = -snp;
723 tgl = -tgl;
724 }
725 if (std::abs(snp) < MinSnp || std::abs(y) > x * TanSector + mCorridor) {
726 continue;
727 }
728 const float zInside = sideA ? std::clamp(pos.Z(), 0.f, zLength) : std::clamp(pos.Z(), -zLength, 0.f);
729 // rows whose real x lies within the road width of the point: the first by bisection on the nominal x, which grows with the row
730 int rowFirst = 0;
731 int rowEnd = NRows;
732 while (rowFirst < rowEnd) {
733 const int rowMid = (rowFirst + rowEnd) / 2;
734 if (TPCGeo::getRowInfoX(rowMid) < x - mCorridor - XMargin) {
735 rowFirst = rowMid + 1;
736 } else {
737 rowEnd = rowMid;
738 }
739 }
740 for (int row = rowFirst; row < NRows && TPCGeo::getRowInfoX(row) <= x + mCorridor + XMargin; row++) {
741 float xReal = TPCGeo::getRowInfoX(row);
742 mCorrMap->InverseTransformYZtoX(sector, row, y, zInside, xReal);
743 const float dxRow = xReal - x;
744 if (std::abs(dxRow) > mCorridor) {
745 continue;
746 }
747 if (mDebugOut) {
748 addDebugRoadPoint(sector, x, y, pos.Z(), snp, tgl, row, frame.vertexTime, 2 + (frame.flag != 0));
749 }
750 searchRow(clusters, sector, row, y, pos.Z(), snp, tgl, frame.vertexTime, frame.zTolerance, frame.flag, dxRow, out, taken);
751 }
752 }
753 }
754 }
755 }
756}
757
758void CosmicsClusterCollectorSpec::searchRow(const o2::tpc::ClusterNativeAccess& clusters, int sector, int row, float y, float z, float snp, float tgl, float vertexTime, float zTolerance, uint8_t flag,
759 float dxRow, std::vector<o2::dataformats::CosmicTPCCluster>& out, std::unordered_set<uint32_t>& taken) const
760{
761 // (y, z) is the predicted point, dxRow the real x of the row minus the x of that point (0 when the point lies on the row)
762 const float zLength = TPCGeo::getTPCzLength();
763 const float vDrift = mCorrMap->getVDrift();
764 const float t0 = mCorrMap->getT0();
765 // nominal (measured) coordinates of the predicted real point; the correction is evaluated inside the drift volume
766 const float zInside = sector < TPCGeo::getNumberOfSectorsA() ? std::clamp(z, 0.f, zLength) : std::clamp(z, -zLength, 0.f);
767 float yNominal = 0.f;
768 float zNominal = 0.f;
769 mCorrMap->InverseTransformYZtoNominalYZ(sector, row, y, zInside, yNominal, zNominal);
770 zNominal += z - zInside;
771 float padPred = 0.f;
772 float driftLengthPred = 0.f;
773 TPCGeo::convLocalToPadDriftLength(sector, row, yNominal, zNominal, padPred, driftLengthPred);
774 const float timePred = driftLengthPred / vDrift + t0 + vertexTime;
775 // the road is a cylinder around the track; its section with the pad-row plane is an ellipse with half-axes W / cos(phi) in y and
776 // W * sqrt(cos^2(phi) + tgl^2) / cos(phi) in z
777 const float cosPhi = std::sqrt((1.f - snp) * (1.f + snp));
778 const float cosPhiWindow = std::max(cosPhi, 0.1f); // limits the window for tracks nearly parallel to the pad row
779 const float norm = 1.f / std::sqrt(1.f + tgl * tgl);
780 const float dirX = cosPhi * norm; // track direction
781 const float dirY = snp * norm;
782 const float dirZ = tgl * norm;
783 const float corridor2 = mCorridor * mCorridor;
784 const float windowPad = mCorridor / (cosPhiWindow * TPCGeo::getRowInfoPadWidth(row));
785 const float windowTime = (mCorridor * std::sqrt(cosPhiWindow * cosPhiWindow + tgl * tgl) / cosPhiWindow + zTolerance) / vDrift;
786 const auto* rowClusters = clusters.clusters[sector][row];
787 const uint32_t rowOffset = clusters.clusterOffset[sector][row];
788 for (uint32_t k = 0; k < clusters.nClusters[sector][row]; k++) {
789 const auto& c = rowClusters[k];
790 const float time = c.getTime();
791 const float pad = c.getPad();
792 if (std::abs(time - timePred) > windowTime || std::abs(pad - padPred) > windowPad) {
793 continue;
794 }
795 float yCluster = 0.f;
796 float zCluster = 0.f;
797 TPCGeo::convPadDriftLengthToLocal(sector, row, pad, (time - t0 - vertexTime) * vDrift, yCluster, zCluster);
798 const float dy = yCluster - yNominal;
799 float dz = zCluster - zNominal;
800 if (zTolerance > 0.f) { // the vertex time of this frame is known within zTolerance / vD: allow that shift along z
801 dz = std::abs(dz) > zTolerance ? dz - std::copysign(zTolerance, dz) : 0.f;
802 }
803 const float proj = dxRow * dirX + dy * dirY + dz * dirZ;
804 if (dxRow * dxRow + dy * dy + dz * dz - proj * proj > corridor2) { // distance perpendicular to the track
805 continue;
806 }
807 if (!taken.insert(rowOffset + k).second) {
808 continue;
809 }
810 auto& cl = out.emplace_back();
811 cl.cl = c;
812 cl.sector = sector;
813 cl.row = row;
814 cl.flags = o2::dataformats::CosmicTPCCluster::Corridor | flag | (mUsed[rowOffset + k] ? o2::dataformats::CosmicTPCCluster::Used : 0);
815 }
816}
817
818void CosmicsClusterCollectorSpec::addDebugRoadPoint(int sector, float x, float y, float z, float snp, float tgl, int row, float vertexTime, int frameCode) const
819{
820 // predicted point, z in the frame of the road and in the common frame of the cosmic's time
822 const float sinAlpha = std::sin(alpha);
823 const float cosAlpha = std::cos(alpha);
824 const bool sideA = sector < TPCGeo::getNumberOfSectorsA();
825 auto& dbg = mDbgCosmics[mDbgCosmic];
826 dbg.roadLeg.push_back(mDbgLeg);
827 dbg.roadFrame.push_back(frameCode);
828 dbg.roadSector.push_back(sector);
829 dbg.roadRow.push_back(row);
830 dbg.roadX.push_back(x);
831 dbg.roadY.push_back(y);
832 dbg.roadZ.push_back(z);
833 dbg.roadZCos.push_back(z + (sideA ? 1.f : -1.f) * (mDbgTauC - vertexTime) * mCorrMap->getVDrift());
834 dbg.roadGx.push_back(x * cosAlpha - y * sinAlpha);
835 dbg.roadGy.push_back(x * sinAlpha + y * cosAlpha);
836 dbg.roadSnp.push_back(snp);
837 dbg.roadTgl.push_back(tgl);
838}
839
840void CosmicsClusterCollectorSpec::addITS(const RecoContainer& data, GTrackID gid, uint8_t leg, std::vector<o2::dataformats::CosmicITSCluster>& out, int icosm, std::vector<ITSPattRequest>& requests, std::unordered_set<int>& matched) const
841{
842 if (gid.getSource() != GTrackID::ITS) { // ITS-AB tracklets are not stored
843 return;
844 }
845 if (data.getITSPerLayer()) {
846 if (!mStaggeredWarned) {
847 LOGP(warning, "Staggered ITS clusters are not supported, ITS clusters of cosmics are not stored");
848 mStaggeredWarned = true;
849 }
850 return;
851 }
852 const auto& trk = data.getITSTrack(gid);
853 const auto refs = data.getITSTracksClusterRefs();
854 const auto clusters = data.getITSClusters();
855 const auto rofs = data.getITSClustersROFRecords();
856 for (int i = 0; i < trk.getNumberOfClusters(); i++) {
857 const int idx = refs[trk.getFirstClusterEntry() + i];
858 const auto& c = clusters[idx];
859 auto& cl = out.emplace_back();
860 cl.chipID = c.getSensorID();
861 cl.row = c.getRow();
862 cl.col = c.getCol();
863 cl.pattID = c.getPatternID();
864 cl.leg = leg;
866 matched.insert(idx);
867 if (c.getPatternID() == o2::itsmft::CompCluster::InvalidPatternID || (mITSDict && mITSDict->isGroup(c.getPatternID()))) {
868 requests.push_back({icosm, int(out.size()) - 1, idx}); // the pattern is in the TF's pattern stream, copied by fillITSPatterns
869 }
870 auto rof = std::upper_bound(rofs.begin(), rofs.end(), idx, [](int v, const o2::itsmft::ROFRecord& r) { return v < r.getFirstEntry(); });
871 if (rof != rofs.begin()) {
872 cl.rofBC = std::prev(rof)->getBCData().differenceInBC(mTFStart);
873 }
874 }
875}
876
877void CosmicsClusterCollectorSpec::addTOF(const RecoContainer& data, GTrackID gid, uint8_t leg, std::vector<o2::dataformats::CosmicTOFCluster>& out, std::unordered_set<int>& matched) const
878{
879 const auto& c = data.getTOFClusters()[gid.getIndex()];
880 auto& cl = out.emplace_back();
881 cl.timeRaw = c.getTimeRaw();
882 cl.tot = c.getTot();
883 cl.channel = c.getMainContributingChannel();
884 cl.leg = leg;
886 matched.insert(gid.getIndex());
887}
888
889std::pair<float, float> CosmicsClusterCollectorSpec::timeWindowMUS(const CosmicTime& cosmicTime) const
890{
891 // a time fixed by z continuity is given with a 1 sigma error, otherwise the error is the half-width of the legs' time-bracket overlap
892 const float timeMUS = cosmicTime.tb * mTPCTBinMUS;
893 const float errMUS = cosmicTime.errTB * mTPCTBinMUS;
894 const float halfWidth = cosmicTime.known ? 5.f * errMUS + 0.2f : errMUS;
895 return {timeMUS - halfWidth, timeMUS + halfWidth};
896}
897
898bool CosmicsClusterCollectorSpec::predictOutward(const o2::tpc::TrackTPC& leg, const CosmicTime& cosmicTime, int sector, float x, float& y, float& z) const
899{
900 // outward continuation of a leg in the frame of a sector; z in the frame of the cosmic's time (a one-side TPC-only leg has z relative to
901 // its time0)
902 o2::track::TrackPar par = leg.getParamOut();
903 if (!par.rotateParam(o2::math_utils::sector2Angle(sector % TPCGeo::getNumberOfSectorsA())) || !par.propagateParamTo(x, mBz)) {
904 return false;
905 }
906 y = par.getY();
907 z = par.getZ() + tpcSide(leg) * (cosmicTime.tb - leg.getTime0()) * mCorrMap->getVDrift();
908 return true;
909}
910
911void CosmicsClusterCollectorSpec::roadTOF(const RecoContainer& data, const o2::tpc::TrackTPC* const* legs, const CosmicTime& cosmicTime, int icosm, std::vector<o2::dataformats::CosmicTOFCluster>& out, const std::unordered_set<int>& matched, float& timeTOFMUS, float& scoreTOFPair, float& scoreTOFReversed) const
912{
913 // TOF clusters along the legs' outward continuations (the road in PbPb also contains hits of collision tracks). Preferred: the top/bottom
914 // pair whose time difference matches the muon's flight between them; its mean time fixes the cosmic's time, also when the legs' brackets
915 // leave it open by tens of mus (one-side legs on the same side), and the z of one-side legs is shifted to it. Otherwise per leg the
916 // cluster closest to its continuation. The same search in the impossible order (bottom hit first) only finds accidental pairs: its best
917 // score is kept as QA of the flag's background.
918 constexpr float MaxFlightMUS = 0.1f; // flight time of the muon between the TPC and the TOF, slow tails
919 constexpr float CmPerNS = 29.9792458f;
920 const auto window = timeWindowMUS(cosmicTime);
921 const float zTimeTol = 0.5f * (window.second - window.first) / mTPCTBinMUS * mCorrMap->getVDrift(); // z uncertainty of one-side legs
922 const float vDriftPerMUS = mCorrMap->getVDrift() / mTPCTBinMUS;
923 const float cosmicTimeMUS = cosmicTime.tb * mTPCTBinMUS;
924 int legSide[2] = {0, 0};
925 float curvature = 0.f; // |1/R| of the muon [1/cm] for the flight path between the TOF hits
926 int nLegs = 0;
927 for (int leg = 0; leg < 2; leg++) {
928 if (legs[leg]) {
929 legSide[leg] = tpcSide(*legs[leg]);
930 curvature += std::abs(legs[leg]->getCurvature(mBz));
931 nLegs++;
932 }
933 }
934 curvature = nLegs ? curvature / nLegs : 0.f;
935 struct Candidate {
936 int index;
937 bool matched; // hit of the leg's matched global track (already stored)
938 double timeNS; // since the start of the TF
939 float dy;
940 float dzAtCosmicTime;
941 float gx;
942 float gy;
943 float gz;
944 };
945 std::vector<Candidate> candidates[2];
946 const auto clusters = data.getTOFClusters();
947 int best[2] = {-1, -1};
948 float bestScore[2] = {1.f, 1.f};
949 for (int i = 0; i < (int)clusters.size(); i++) {
950 const auto& c = clusters[i];
951 const float timeMUS = c.getTime() * 1e-6f; // [ps] since the start of the TF
952 if (timeMUS < window.first - MaxFlightMUS || timeMUS > window.second + MaxFlightMUS) {
953 continue;
954 }
955 const bool isMatched = matched.count(i) > 0;
956 const float alpha = o2::math_utils::sector2Angle(c.getSector());
957 const float sinAlpha = std::sin(alpha);
958 const float cosAlpha = std::cos(alpha);
959 for (int leg = 0; leg < 2; leg++) {
960 float y = 0.f;
961 float z = 0.f;
962 if (!legs[leg] || !predictOutward(*legs[leg], cosmicTime, c.getSector(), c.getX(), y, z)) {
963 continue;
964 }
965 const float normY = (c.getY() - y) / mRoadTOF;
966 const float normZ = (c.getZ() - z) / (mRoadTOF + zTimeTol);
967 const float score = std::max(normY * normY, normZ * normZ);
968 if (score >= 1.f) {
969 continue;
970 }
971 if (!isMatched && score < bestScore[leg]) {
972 bestScore[leg] = score;
973 best[leg] = i;
974 }
975 candidates[leg].push_back(Candidate{i, isMatched, c.getTime() * 1e-3, c.getY() - y, c.getZ() - z, c.getX() * cosAlpha - c.getY() * sinAlpha,
976 c.getX() * sinAlpha + c.getY() * cosAlpha, c.getZ()});
977 }
978 }
979 float bestPairScore = -1.f;
980 float bestReversedScore = -1.f;
981 int bestPair[2] = {-1, -1};
982 for (const auto& c0 : candidates[0]) {
983 for (const auto& c1 : candidates[1]) {
984 if (c0.index == c1.index) {
985 continue;
986 }
987 // top / bottom by the hits' height: a muon goes down (for near-horizontal cosmics both orders are possible, but rare)
988 const auto& top = c0.gy > c1.gy ? c0 : c1;
989 const auto& bottom = c0.gy > c1.gy ? c1 : c0;
990 // flight path along the helix: arc in the transverse plane from the chord, then the dip
991 const float chordXY = std::hypot(top.gx - bottom.gx, top.gy - bottom.gy);
992 const float halfAngleSin = 0.5f * curvature * chordXY;
993 const float arcXY = halfAngleSin > 1e-4f && halfAngleSin < 1.f ? 2.f * std::asin(halfAngleSin) / curvature : chordXY;
994 const float length = std::hypot(arcXY, top.gz - bottom.gz);
995 const float timeDiff = float(top.timeNS - bottom.timeNS);
996 const float flightDev = timeDiff + length / CmPerNS; // the muon crosses the top TOF first
997 const float reversedDev = timeDiff - length / CmPerNS; // bottom hit first: impossible for a muon, only accidental pairs
998 const bool reversed = std::abs(flightDev) > mTOFFlightTol; // the two cannot both pass (L/c ~ 25 ns >> tolerance)
999 if (reversed && std::abs(reversedDev) > mTOFFlightTol) {
1000 continue;
1001 }
1002 const double pairTimeNS = 0.5 * (c0.timeNS + c1.timeNS);
1003 const float shiftMUS = float(pairTimeNS * 1e-3) - cosmicTimeMUS;
1004 const float dz0 = c0.dzAtCosmicTime - legSide[0] * shiftMUS * vDriftPerMUS;
1005 const float dz1 = c1.dzAtCosmicTime - legSide[1] * shiftMUS * vDriftPerMUS;
1006 if (std::abs(dz0) > mRoadTOF || std::abs(dz1) > mRoadTOF) {
1007 continue;
1008 }
1009 const float timeDev = reversed ? reversedDev : flightDev;
1010 const float score = (c0.dy * c0.dy + c1.dy * c1.dy + dz0 * dz0 + dz1 * dz1) / (mRoadTOF * mRoadTOF) + timeDev * timeDev / (mTOFFlightTol * mTOFFlightTol);
1011 if (reversed) {
1012 if (bestReversedScore < 0.f || score < bestReversedScore) {
1013 bestReversedScore = score;
1014 }
1015 continue;
1016 }
1017 if (bestPairScore < 0.f || score < bestPairScore) {
1018 bestPairScore = score;
1019 bestPair[0] = c0.index;
1020 bestPair[1] = c1.index;
1021 timeTOFMUS = float(pairTimeNS * 1e-3);
1022 }
1023 }
1024 }
1025 scoreTOFPair = bestPairScore;
1026 scoreTOFReversed = bestReversedScore;
1028 if (bestPairScore >= 0.f) {
1029 best[0] = bestPair[0];
1030 best[1] = bestPair[1];
1032 } else if (best[0] >= 0 && best[0] == best[1]) { // one TOF cluster in the roads of both legs: keep it for the closer one
1033 best[bestScore[0] <= bestScore[1] ? 1 : 0] = -1;
1034 }
1035 for (int leg = 0; leg < 2; leg++) {
1036 if (best[leg] < 0) {
1037 continue;
1038 }
1039 const auto& c = clusters[best[leg]];
1040 if (matched.count(best[leg])) { // the leg's matched hit, already stored: flag it as part of the flight pair
1041 for (auto& cl : out) {
1042 if (cl.leg == leg && cl.channel == c.getMainContributingChannel() && cl.timeRaw == c.getTimeRaw()) {
1044 }
1045 }
1046 if (mDebugOut) {
1047 auto& dbg = mDbgCosmics[icosm];
1048 for (size_t j = 0; j < dbg.tofFlags.size(); j++) {
1049 if (dbg.tofLeg[j] == leg && dbg.tofChannel[j] == c.getMainContributingChannel() && dbg.tofTimeRaw[j] == c.getTimeRaw()) {
1050 dbg.tofFlags[j] |= o2::dataformats::HitTOFFlight;
1051 }
1052 }
1053 }
1054 continue;
1055 }
1056 auto& cl = out.emplace_back();
1057 cl.timeRaw = c.getTimeRaw();
1058 cl.tot = c.getTot();
1059 cl.channel = c.getMainContributingChannel();
1060 cl.leg = leg;
1061 cl.flags = flags;
1062 if (mDebugOut) {
1063 writeDebugTOF(c, icosm, leg, flags);
1064 }
1065 }
1066}
1067
1068void CosmicsClusterCollectorSpec::polish(const o2::tpc::TrackTPC* const* legs, float timeMUS, o2::gpu::GPUO2InterfaceRefit& refitter, o2::dataformats::TrackCosmicsExtended& out) const
1069{
1070 // refit of a cosmic with TPC-only legs at its TOF time, as MatchCosmics::refitWinners: the bottom leg inward, then to the closest approach
1071 // to the beam line; the top leg inward, then to the same point; the two halves combined. Muon mass, energy loss along the muon's flight
1072 // (top to bottom): going inward along the bottom leg and up to the closest approach is against the flight (gain), the top leg inward
1073 // and down to the closest approach is with it (loss). With a precise time the one-side legs get their real z, hence also the right
1074 // material, which the matcher's time (the legs' bracket overlap for one-side legs on the same side) cannot give.
1075 constexpr int ELossGain = 1;
1076 constexpr int ELossLoss = -1;
1077 const auto& matchParams = o2::globaltracking::MatchCosmicsParams::Instance(); // propagation settings as in the matcher
1078 const auto prop = o2::base::Propagator::Instance();
1079 const float timeTB = timeMUS / mTPCTBinMUS;
1080 o2::track::TrackParCov bottom = legs[0]->getParamOut();
1081 bottom.setPID(o2::track::PID::Muon, true);
1082 bottom.resetCovariance();
1083 if (std::abs(mBz) <= 0.01f) {
1084 // B = 0: both legs carry the same conventional q/pt; as the matcher, flip the bottom one so that it is right after the inversion
1086 }
1087 if (refitter.RefitTrackAsTrackParCov(bottom, legs[0]->getClusterRef(), timeTB, nullptr, false, false, ELossGain) < 0) {
1088 return;
1089 }
1090 bottom.invert();
1092 if (!prop->propagateToDCABxByBz(origin, bottom, matchParams.maxStep, matchParams.matCorr, nullptr, nullptr, ELossGain)) {
1093 return;
1094 }
1095 o2::track::TrackParCov top = legs[1]->getParamOut();
1096 top.setPID(o2::track::PID::Muon, true);
1097 if (refitter.RefitTrackAsTrackParCov(top, legs[1]->getClusterRef(), timeTB, nullptr, false, true, ELossLoss) < 0) {
1098 return;
1099 }
1100 if (!top.rotate(bottom.getAlpha()) || !prop->PropagateToXBxByBz(top, bottom.getX(), matchParams.maxSnp, matchParams.maxStep, matchParams.matCorr, nullptr, ELossLoss)) {
1101 return;
1102 }
1103 o2::track::TrackParCov::MatrixDSym5 cov5;
1104 const float chi2Match = bottom.getPredictedChi2(top, cov5);
1105 if (!bottom.update(top, cov5)) {
1106 return;
1107 }
1108 out.polished = bottom;
1109 out.chi2MatchPolished = chi2Match;
1110}
1111
1112void CosmicsClusterCollectorSpec::roadTRD(const RecoContainer& data, const o2::tpc::TrackTPC* const* legs, const CosmicTime& cosmicTime, int icosm, std::vector<o2::dataformats::CosmicTRDTracklet>& out, const std::unordered_set<int>& matched) const
1113{
1114 // per leg and layer the TRD tracklet closest to the leg's outward continuation, from triggers whose readout window can contain the cosmic
1115 constexpr float ReadoutWindowMUS = 3.f; // a cosmic leaves tracklets only if it passes within the readout window after a trigger
1116 constexpr float PadLength = 10.f; // [cm] longest TRD pads: the tracklet z is the pad-row centre
1117 constexpr int NLayers = o2::trd::constants::NLAYER;
1118 const auto window = timeWindowMUS(cosmicTime);
1119 const float zTimeTol = 0.5f * (window.second - window.first) / mTPCTBinMUS * mCorrMap->getVDrift();
1120 const auto tracklets = data.getTRDTracklets();
1121 const auto calibrated = data.getTRDCalibratedTracklets();
1122 if (calibrated.size() != tracklets.size()) {
1123 if (!mTRDMismatchWarned) {
1124 LOGP(warning, "{} calibrated vs {} raw TRD tracklets: TRD road of cosmics skipped", calibrated.size(), tracklets.size());
1125 mTRDMismatchWarned = true;
1126 }
1127 return;
1128 }
1129 struct Candidate {
1130 int tracklet = -1;
1131 int trigBC = 0;
1132 float score = 1.f;
1133 };
1134 Candidate best[2][NLayers];
1135 for (const auto& trig : data.getTRDTriggerRecords()) {
1136 const int trigBC = trig.getBCData().differenceInBC(mTFStart);
1137 const float trigMUS = trigBC * o2::constants::lhc::LHCBunchSpacingMUS;
1138 if (trigMUS < window.first - ReadoutWindowMUS || trigMUS > window.second) {
1139 continue;
1140 }
1141 for (int it = trig.getFirstTracklet(); it < trig.getFirstTracklet() + trig.getNumberOfTracklets(); it++) {
1142 if (matched.count(it)) {
1143 continue;
1144 }
1145 const int detector = tracklets[it].getDetector();
1146 const int sector = detector / o2::trd::constants::NCHAMBERPERSEC;
1147 const int layer = detector % NLayers;
1148 const auto& point = calibrated[it];
1149 for (int leg = 0; leg < 2; leg++) {
1150 float y = 0.f;
1151 float z = 0.f;
1152 if (!legs[leg] || !predictOutward(*legs[leg], cosmicTime, sector, point.getX(), y, z)) {
1153 continue;
1154 }
1155 const float normY = (point.getY() - y) / mRoadTRD;
1156 const float normZ = (point.getZ() - z) / (mRoadTRD + PadLength + zTimeTol);
1157 const float score = std::max(normY * normY, normZ * normZ);
1158 if (score < best[leg][layer].score) {
1159 best[leg][layer] = {it, trigBC, score};
1160 }
1161 }
1162 }
1163 }
1164 for (int leg = 0; leg < 2; leg++) {
1165 for (int layer = 0; layer < NLayers; layer++) {
1166 const auto& cand = best[leg][layer];
1167 if (cand.tracklet < 0) {
1168 continue;
1169 }
1170 auto& tr = out.emplace_back();
1171 tr.word = tracklets[cand.tracklet].getTrackletWord();
1172 tr.trigBC = cand.trigBC;
1173 tr.layer = layer;
1174 tr.leg = leg;
1175 tr.flags = o2::dataformats::HitRoad;
1176 if (mDebugOut) {
1177 const auto& point = calibrated[cand.tracklet];
1178 const int sector = tracklets[cand.tracklet].getDetector() / o2::trd::constants::NCHAMBERPERSEC;
1179 float y = 0.f;
1180 float z = 0.f;
1181 predictOutward(*legs[leg], cosmicTime, sector, point.getX(), y, z); // succeeded in the search
1182 auto& dbg = mDbgCosmics[icosm];
1183 dbg.trdLeg.push_back(leg);
1184 dbg.trdLayer.push_back(layer);
1185 dbg.trdX.push_back(point.getX());
1186 dbg.trdY.push_back(point.getY());
1187 dbg.trdZ.push_back(point.getZ());
1188 dbg.trdDy.push_back(point.getY() - y);
1189 dbg.trdDz.push_back(point.getZ() - z);
1190 dbg.trdTrigMUS.push_back(cand.trigBC * float(o2::constants::lhc::LHCBunchSpacingMUS));
1191 }
1192 }
1193 }
1194}
1195
1196void CosmicsClusterCollectorSpec::roadITS(const RecoContainer& data, const o2::dataformats::TrackCosmics& cosm, const CosmicTime& cosmicTime, int legsSide, int icosm, std::vector<o2::dataformats::CosmicITSCluster>& out,
1197 std::vector<ITSPattRequest>& requests, const std::unordered_set<int>& matched) const
1198{
1199 if (data.getITSPerLayer() || mITSChipCentres.empty()) {
1200 return;
1201 }
1202 // trajectory near the beam line from the combined cosmic, sampled every cm along its direction (closest approach at local x = 0)
1203 constexpr float MaxRadius = 45.f; // [cm] outer ITS barrel + margin
1204 constexpr float ChipHalfDiag = 1.7f; // [cm] half diagonal of an ITS chip
1205 o2::track::TrackPar par = cosm;
1206 if (!par.rotateParam(par.getPhi())) {
1207 return;
1208 }
1209 std::vector<o2::math_utils::Point3D<float>> points;
1210 for (float x = -MaxRadius - 5.f; x <= MaxRadius + 5.f; x += 1.f) {
1211 bool ok = false;
1212 const auto point = par.getXYZGloAt(x, mBz, ok);
1213 if (ok) {
1214 points.push_back(point);
1215 }
1216 }
1217 if (points.size() < 2) {
1218 return;
1219 }
1220 // closest segment in the transverse plane: distance, z of the trajectory there
1221 auto closest = [&points](float x, float y, float& dist2, float& zTraj) {
1222 dist2 = 1e10f;
1223 for (size_t i = 0; i + 1 < points.size(); i++) {
1224 const float segX = points[i + 1].X() - points[i].X();
1225 const float segY = points[i + 1].Y() - points[i].Y();
1226 const float len2 = segX * segX + segY * segY;
1227 const float frac = len2 > 0.f ? std::clamp(((x - points[i].X()) * segX + (y - points[i].Y()) * segY) / len2, 0.f, 1.f) : 0.f;
1228 const float dx = x - (points[i].X() + frac * segX);
1229 const float dy = y - (points[i].Y() + frac * segY);
1230 if (dx * dx + dy * dy < dist2) {
1231 dist2 = dx * dx + dy * dy;
1232 zTraj = points[i].Z() + frac * (points[i + 1].Z() - points[i].Z());
1233 }
1234 }
1235 };
1236 float minR2 = 1e10f;
1237 float pcaY = 0.f;
1238 for (const auto& point : points) {
1239 const float r2 = point.X() * point.X() + point.Y() * point.Y();
1240 if (r2 < minR2) {
1241 minR2 = r2;
1242 pcaY = point.Y();
1243 }
1244 }
1245 if (minR2 > MaxRadius * MaxRadius) { // the cosmic does not cross the ITS
1246 return;
1247 }
1248 std::vector<bool> candidateChip(mITSChipCentres.size(), false);
1249 bool anyChip = false;
1250 const float chipCut2 = (mRoadITS + ChipHalfDiag) * (mRoadITS + ChipHalfDiag);
1251 for (size_t chip = 0; chip < mITSChipCentres.size(); chip++) {
1252 float dist2 = 0.f;
1253 float zTraj = 0.f;
1254 closest(mITSChipCentres[chip].X(), mITSChipCentres[chip].Y(), dist2, zTraj);
1255 if (dist2 < chipCut2) {
1256 candidateChip[chip] = true;
1257 anyChip = true;
1258 }
1259 }
1260 if (!anyChip) {
1261 return;
1262 }
1263 const auto window = timeWindowMUS(cosmicTime);
1264 const float zTimeTol = 0.5f * (window.second - window.first) / mTPCTBinMUS * mCorrMap->getVDrift(); // z of the cosmic is in the frame of its time
1265 // the refitted cosmic has the z of its TPC time; with legs on one TPC side and a road time from the TOF its z moves by side * vD * dt
1266 const float zShift = legsSide * (cosmicTime.tb - cosm.getTimeMUS().getTimeStamp() / mTPCTBinMUS) * mCorrMap->getVDrift();
1267 const float rofLengthMUS = o2::itsmft::DPLAlpideParam<DetID::ITS>::Instance().roFrameLengthInBC * o2::constants::lhc::LHCBunchSpacingMUS;
1268 // per half of the cosmic and layer the ITS clusters closest to the trajectory, best first: one in the outer barrel, several in the inner
1269 // barrel, where the road near the beam line is dense with collision clusters and the TPC prediction (~mm) does not single out the hit
1270 constexpr int NLayers = 7;
1271 constexpr int NLayersIB = 3;
1272 constexpr int MaxHitsIB = 5;
1273 struct Candidate {
1274 int index = -1;
1275 int rofBC = 0;
1276 float score = 1.f;
1277 };
1278 Candidate best[2][NLayers][MaxHitsIB];
1279 const auto clusters = data.getITSClusters();
1280 auto geom = o2::its::GeometryTGeo::Instance();
1281 for (const auto& rof : data.getITSClustersROFRecords()) {
1282 const int rofBC = rof.getBCData().differenceInBC(mTFStart);
1283 const float rofMUS = rofBC * o2::constants::lhc::LHCBunchSpacingMUS;
1284 if (rofMUS + rofLengthMUS < window.first || rofMUS > window.second) {
1285 continue;
1286 }
1287 for (int idx = rof.getFirstEntry(); idx < rof.getFirstEntry() + rof.getNEntries(); idx++) {
1288 const auto& c = clusters[idx];
1289 if (!candidateChip[c.getSensorID()] || matched.count(idx)) {
1290 continue;
1291 }
1293 if (mITSDict && c.getPatternID() != o2::itsmft::CompCluster::InvalidPatternID && !mITSDict->isGroup(c.getPatternID())) {
1294 local = mITSDict->getClusterCoordinates<float>(c);
1295 } else { // anchor pixel: good enough for a cm road
1297 }
1298 const auto global = geom->getMatrixL2G(c.getSensorID()) * local;
1299 float dist2 = 0.f;
1300 float zTraj = 0.f;
1301 closest(global.X(), global.Y(), dist2, zTraj);
1302 const float normZ = (global.Z() - zTraj - zShift) / (mRoadITS + zTimeTol);
1303 const float score = std::max(dist2 / (mRoadITS * mRoadITS), normZ * normZ);
1304 const int half = global.Y() < pcaY ? 0 : 1; // bottom / top half of the cosmic
1305 const int layer = geom->getLayer(c.getSensorID());
1306 if (layer < 0 || layer >= NLayers) {
1307 continue;
1308 }
1309 auto* candidates = best[half][layer];
1310 int slot = (layer < NLayersIB ? MaxHitsIB : 1) - 1;
1311 if (!(score < candidates[slot].score)) { // also rejects a NaN score
1312 continue;
1313 }
1314 for (; slot > 0 && score < candidates[slot - 1].score; slot--) {
1315 candidates[slot] = candidates[slot - 1];
1316 }
1317 candidates[slot] = {idx, rofBC, score};
1318 }
1319 }
1320 for (int half = 0; half < 2; half++) {
1321 for (int layer = 0; layer < NLayers; layer++) {
1322 for (const auto& candidate : best[half][layer]) {
1323 if (candidate.index < 0) {
1324 break;
1325 }
1326 const auto& c = clusters[candidate.index];
1327 auto& cl = out.emplace_back();
1328 cl.chipID = c.getSensorID();
1329 cl.row = c.getRow();
1330 cl.col = c.getCol();
1331 cl.pattID = c.getPatternID();
1332 cl.rofBC = candidate.rofBC;
1333 cl.leg = half;
1334 cl.flags = o2::dataformats::HitRoad;
1335 if (c.getPatternID() == o2::itsmft::CompCluster::InvalidPatternID || (mITSDict && mITSDict->isGroup(c.getPatternID()))) {
1336 requests.push_back({icosm, int(out.size()) - 1, candidate.index});
1337 }
1338 }
1339 }
1340 }
1341}
1342
1343void CosmicsClusterCollectorSpec::fillITSPatterns(const RecoContainer& data, std::vector<ITSPattRequest>& requests, std::vector<o2::dataformats::TrackCosmicsExtended>& cosmics) const
1344{
1345 // the TF's pattern stream holds, in cluster order, the patterns of the clusters with an invalid or a group pattern ID
1346 if (!mITSDict) {
1347 if (!mNoDictWarned) {
1348 LOGP(warning, "No ITS cluster dictionary: patterns of ITS clusters of cosmics are not stored");
1349 mNoDictWarned = true;
1350 }
1351 return;
1352 }
1353 std::sort(requests.begin(), requests.end(), [](const ITSPattRequest& a, const ITSPattRequest& b) { return a.index < b.index; });
1354 const auto clusters = data.getITSClusters();
1355 auto pattIt = data.getITSClustersPatterns().begin();
1356 size_t ir = 0;
1357 for (int k = 0; k < (int)clusters.size() && ir < requests.size(); k++) {
1358 const auto pattID = clusters[k].getPatternID();
1359 if (pattID != o2::itsmft::CompCluster::InvalidPatternID && !mITSDict->isGroup(pattID)) {
1360 continue;
1361 }
1362 const auto start = pattIt;
1364 for (; ir < requests.size() && requests[ir].index == k; ir++) {
1365 auto& cosm = cosmics[requests[ir].cosmic];
1366 cosm.clITS[requests[ir].cluster].pattEntry = cosm.itsPatterns.size();
1367 cosm.itsPatterns.insert(cosm.itsPatterns.end(), start, pattIt);
1368 }
1369 }
1370}
1371
1372void CosmicsClusterCollectorSpec::addTRD(const RecoContainer& data, GTrackID gid, uint8_t leg, std::vector<o2::dataformats::CosmicTRDTracklet>& out, std::unordered_set<int>& matched) const
1373{
1374 const auto& trk = data.getTrack<o2::trd::TrackTRD>(gid); // TPC-TRD or ITS-TPC-TRD track
1375 const auto tracklets = data.getTRDTracklets();
1376 const auto trigs = data.getTRDTriggerRecords();
1377 for (int layer = 0; layer < 6; layer++) {
1378 const int it = trk.getTrackletIndex(layer);
1379 if (it < 0) {
1380 continue;
1381 }
1382 auto& tr = out.emplace_back();
1383 tr.word = tracklets[it].getTrackletWord();
1384 tr.layer = layer;
1385 tr.leg = leg;
1386 tr.flags = o2::dataformats::HitMatched;
1387 matched.insert(it);
1388 auto trig = std::upper_bound(trigs.begin(), trigs.end(), it, [](int v, const o2::trd::TriggerRecord& t) { return v < t.getFirstTracklet(); });
1389 if (trig != trigs.begin()) {
1390 tr.trigBC = std::prev(trig)->getBCData().differenceInBC(mTFStart);
1391 }
1392 }
1393}
1394
1395void CosmicsClusterCollectorSpec::flagDuplicates(std::vector<o2::dataformats::TrackCosmicsExtended>& cosmics) const
1396{
1397 // the same muon can be matched twice, e.g. when a leg is split into two TPC tracks: the roads then collect largely the same clusters
1398 // (PbPb 567939: 10 of 47 cosmic pairs in a TF share 56-99 % of the clusters of the smaller one, all others none). The best one (TOF time,
1399 // then more attached clusters) stays, the others point to it.
1400 constexpr float MinSharedFraction = 0.3f;
1401 const size_t n = cosmics.size();
1402 if (n < 2) {
1403 return;
1404 }
1405 std::vector<std::vector<uint64_t>> keys(n); // sorted cluster addresses: sector, row and the packed time and pad of the raw cluster
1406 std::vector<int> nAttached(n, 0);
1407 for (size_t ic = 0; ic < n; ic++) {
1408 for (const auto* legClusters : {&cosmics[ic].clTPCBottom, &cosmics[ic].clTPCTop}) {
1409 for (const auto& c : *legClusters) {
1410 keys[ic].push_back((uint64_t(c.sector) << 56) | (uint64_t(c.row) << 48) | (uint64_t(c.cl.timeFlagsPacked) << 16) | c.cl.padPacked);
1411 nAttached[ic] += c.isAttached();
1412 }
1413 }
1414 std::sort(keys[ic].begin(), keys[ic].end());
1415 }
1416 std::vector<int> order(n);
1417 std::iota(order.begin(), order.end(), 0);
1418 std::stable_sort(order.begin(), order.end(), [&](int a, int b) {
1419 const bool timedA = cosmics[a].hasTOFTime();
1420 const bool timedB = cosmics[b].hasTOFTime();
1421 return timedA != timedB ? timedA : nAttached[a] > nAttached[b];
1422 });
1423 std::vector<int> kept;
1424 for (int ic : order) {
1425 for (int ik : kept) {
1426 std::vector<uint64_t> shared;
1427 std::set_intersection(keys[ic].begin(), keys[ic].end(), keys[ik].begin(), keys[ik].end(), std::back_inserter(shared));
1428 const size_t nMin = std::min(keys[ic].size(), keys[ik].size());
1429 if (nMin > 0 && shared.size() >= MinSharedFraction * nMin) {
1430 cosmics[ic].duplicateOf = ik;
1431 break;
1432 }
1433 }
1434 if (cosmics[ic].duplicateOf < 0) {
1435 kept.push_back(ic);
1436 }
1437 }
1438}
1439
1440void CosmicsClusterCollectorSpec::writeDebug(const o2::dataformats::TrackCosmicsExtended& cosm, int icosm) const
1441{
1442 constexpr int NSectorsA = TPCGeo::getNumberOfSectorsA();
1443 // the time the other-side corridor used (and the common frame zCos): the TOF time if the TOF road found one, else the TPC time
1444 const float timeCosmic = (cosm.hasTOFTime() ? cosm.timeTOFMUS : cosm.cosmic.getTimeMUS().getTimeStamp()) / mTPCTBinMUS;
1445 const bool absTimeKnown = cosm.cosmic.getTimeMUS().getTimeStampError() < mMaxAbsTimeErr;
1446 int nAttached[2] = {0, 0};
1447 int nRoad[2] = {0, 0};
1448 int side[2] = {-2, -2};
1449 std::vector<int> clLeg;
1450 std::vector<int> clFlags;
1451 std::vector<int> clSector;
1452 std::vector<int> clRow;
1453 std::vector<float> clPad;
1454 std::vector<float> clTime;
1455 std::vector<float> clQMax;
1456 std::vector<float> clQTot;
1457 std::vector<float> clX;
1458 std::vector<float> clY;
1459 std::vector<float> clZ;
1460 std::vector<float> clZCos;
1461 std::vector<float> clGx;
1462 std::vector<float> clGy;
1463 const o2::tpc::TrackTPC* legs[2] = {&cosm.tpcBottom, &cosm.tpcTop};
1464 const std::vector<o2::dataformats::CosmicTPCCluster>* legClusters[2] = {&cosm.clTPCBottom, &cosm.clTPCTop};
1465 for (int leg = 0; leg < 2; leg++) {
1466 if (legs[leg]->getNClusters() == 0) { // no TPC part
1467 continue;
1468 }
1469 side[leg] = tpcSide(*legs[leg]);
1470 for (const auto& c : *legClusters[leg]) {
1471 // transform with the vertex time used by the road: the leg's time0, or the cosmic's time for clusters found on the other side
1472 const float vertexTime = c.isAbsTime() ? timeCosmic : legs[leg]->getTime0();
1473 float x = 0.f;
1474 float y = 0.f;
1475 float z = 0.f;
1476 mCorrMap->Transform(c.sector, c.row, c.cl.getPad(), c.cl.getTime(), x, y, z, vertexTime);
1477 float xCos = 0.f;
1478 float yCos = 0.f;
1479 float zCos = 0.f;
1480 mCorrMap->Transform(c.sector, c.row, c.cl.getPad(), c.cl.getTime(), xCos, yCos, zCos, timeCosmic);
1481 const float alpha = o2::math_utils::sector2Angle(c.sector % NSectorsA);
1482 const float sinAlpha = std::sin(alpha);
1483 const float cosAlpha = std::cos(alpha);
1484 clLeg.push_back(leg);
1485 clFlags.push_back(c.flags);
1486 clSector.push_back(c.sector);
1487 clRow.push_back(c.row);
1488 clPad.push_back(c.cl.getPad());
1489 clTime.push_back(c.cl.getTime());
1490 clQMax.push_back(c.cl.getQmax());
1491 clQTot.push_back(c.cl.getQtot());
1492 clX.push_back(x);
1493 clY.push_back(y);
1494 clZ.push_back(z);
1495 clZCos.push_back(zCos);
1496 clGx.push_back(x * cosAlpha - y * sinAlpha);
1497 clGy.push_back(x * sinAlpha + y * cosAlpha);
1498 (c.isAttached() ? nAttached : nRoad)[leg]++;
1499 }
1500 }
1501 std::vector<int> itsLeg;
1502 std::vector<int> itsFlags;
1503 std::vector<int> itsChip;
1504 std::vector<int> itsRow;
1505 std::vector<int> itsCol;
1506 std::vector<int> itsPattID;
1507 std::vector<int> itsHasPatt;
1508 std::vector<int> itsRofBC;
1509 std::vector<float> itsGx;
1510 std::vector<float> itsGy;
1511 std::vector<float> itsGz;
1512 std::vector<float> itsXLoc;
1513 std::vector<float> itsZLoc;
1514 for (const auto& c : cosm.clITS) { // local coordinates on the chip from the dictionary or the stored pattern
1515 const o2::itsmft::CompClusterExt compCluster(c.row, c.col, c.pattID, c.chipID);
1517 if (c.pattEntry >= 0) {
1519 auto pattIt = cosm.itsPatterns.begin() + c.pattEntry;
1520 pattern.acquirePattern(pattIt);
1521 local = o2::itsmft::TopologyDictionary::getClusterCoordinates<float>(compCluster, pattern, c.pattID != o2::itsmft::CompCluster::InvalidPatternID);
1522 } else if (mITSDict && c.pattID != o2::itsmft::CompCluster::InvalidPatternID) {
1523 local = mITSDict->getClusterCoordinates<float>(compCluster);
1524 } else { // pattern not available: pixel centre
1526 }
1527 o2::math_utils::Point3D<float> global(0.f, 0.f, 0.f);
1528 if (!mITSChipCentres.empty()) { // geometry loaded for the ITS road
1529 global = o2::its::GeometryTGeo::Instance()->getMatrixL2G(c.chipID) * local;
1530 }
1531 itsLeg.push_back(c.leg);
1532 itsFlags.push_back(c.flags);
1533 itsChip.push_back(c.chipID);
1534 itsRow.push_back(c.row);
1535 itsCol.push_back(c.col);
1536 itsPattID.push_back(c.pattID);
1537 itsHasPatt.push_back(c.pattEntry >= 0);
1538 itsRofBC.push_back(c.rofBC);
1539 itsGx.push_back(global.X());
1540 itsGy.push_back(global.Y());
1541 itsGz.push_back(global.Z());
1542 itsXLoc.push_back(local.X());
1543 itsZLoc.push_back(local.Z());
1544 }
1545 const auto& dbg = mDbgCosmics[icosm];
1546 const auto& time = cosm.cosmic.getTimeMUS();
1547 (*mDebugOut) << "cosmics"
1548 << "tf=" << mDbgTF << "cosm=" << icosm << "t=" << time.getTimeStamp() << "tErr=" << time.getTimeStampError() << "absTime=" << int(absTimeKnown)
1549 << "chi2Match=" << cosm.cosmic.getChi2Match() << "chi2Refit=" << cosm.cosmic.getChi2Refit() << "q2pt=" << cosm.cosmic.getQ2Pt()
1550 << "tgl=" << cosm.cosmic.getTgl() << "side0=" << side[0] << "side1=" << side[1] << "nAtt0=" << nAttached[0] << "nAtt1=" << nAttached[1]
1551 << "nRoad0=" << nRoad[0] << "nRoad1=" << nRoad[1] << "nITS=" << int(cosm.clITS.size()) << "nTOF=" << int(cosm.clTOF.size())
1552 << "nTRD=" << int(cosm.trdTracklets.size()) << "tTOF=" << cosm.timeTOFMUS << "scoreTOF=" << cosm.scoreTOFPair << "scoreTOFRev=" << cosm.scoreTOFReversed << "dupOf=" << cosm.duplicateOf << "polChi2Match=" << cosm.chi2MatchPolished << "polQ2Pt=" << cosm.polished.getQ2Pt() << "polZ=" << cosm.polished.getZ()
1553 << "mcEvent=" << (cosm.label.isSet() ? cosm.label.getEventID() : -1) << "mcTrack=" << (cosm.label.isSet() ? cosm.label.getTrackID() : -1)
1554 << "clLeg=" << clLeg << "clFlags=" << clFlags << "clSector=" << clSector << "clRow=" << clRow << "clPad=" << clPad << "clTime=" << clTime
1555 << "clQMax=" << clQMax << "clQTot=" << clQTot << "clX=" << clX << "clY=" << clY << "clZ=" << clZ << "clZCos=" << clZCos << "clGx=" << clGx
1556 << "clGy=" << clGy << "roadLeg=" << dbg.roadLeg << "roadFrame=" << dbg.roadFrame << "roadSector=" << dbg.roadSector << "roadRow=" << dbg.roadRow
1557 << "roadX=" << dbg.roadX << "roadY=" << dbg.roadY << "roadZ=" << dbg.roadZ << "roadZCos=" << dbg.roadZCos << "roadGx=" << dbg.roadGx
1558 << "roadGy=" << dbg.roadGy << "roadSnp=" << dbg.roadSnp << "roadTgl=" << dbg.roadTgl << "itsLeg=" << itsLeg << "itsFlags=" << itsFlags
1559 << "itsChip=" << itsChip << "itsRow=" << itsRow << "itsCol=" << itsCol << "itsPattID=" << itsPattID << "itsHasPatt=" << itsHasPatt
1560 << "itsRofBC=" << itsRofBC << "itsGx=" << itsGx << "itsGy=" << itsGy << "itsGz=" << itsGz << "itsXLoc=" << itsXLoc << "itsZLoc=" << itsZLoc
1561 << "tofLeg=" << dbg.tofLeg << "tofChannel=" << dbg.tofChannel << "tofFlags=" << dbg.tofFlags << "tofTimeRaw=" << dbg.tofTimeRaw
1562 << "tofTime=" << dbg.tofTime << "tofTot=" << dbg.tofTot << "tofX=" << dbg.tofX << "tofY=" << dbg.tofY << "tofZ=" << dbg.tofZ << "tofGx=" << dbg.tofGx
1563 << "tofGy=" << dbg.tofGy << "trdLeg=" << dbg.trdLeg << "trdLayer=" << dbg.trdLayer << "trdX=" << dbg.trdX << "trdY=" << dbg.trdY << "trdZ=" << dbg.trdZ
1564 << "trdDy=" << dbg.trdDy << "trdDz=" << dbg.trdDz << "trdTrigMUS=" << dbg.trdTrigMUS << "\n";
1565}
1566
1567void CosmicsClusterCollectorSpec::writeDebugTOF(const o2::tof::Cluster& c, int icosm, int leg, uint8_t flags) const
1568{
1569 // the TOF cluster position is in the frame of its sector
1570 const float alpha = o2::math_utils::sector2Angle(c.getSector());
1571 const float sinAlpha = std::sin(alpha);
1572 const float cosAlpha = std::cos(alpha);
1573 auto& dbg = mDbgCosmics[icosm];
1574 dbg.tofLeg.push_back(leg);
1575 dbg.tofChannel.push_back(c.getMainContributingChannel());
1576 dbg.tofFlags.push_back(flags);
1577 dbg.tofTimeRaw.push_back(c.getTimeRaw());
1578 dbg.tofTime.push_back(c.getTime());
1579 dbg.tofTot.push_back(c.getTot());
1580 dbg.tofX.push_back(c.getX());
1581 dbg.tofY.push_back(c.getY());
1582 dbg.tofZ.push_back(c.getZ());
1583 dbg.tofGx.push_back(c.getX() * cosAlpha - c.getY() * sinAlpha);
1584 dbg.tofGy.push_back(c.getX() * sinAlpha + c.getY() * cosAlpha);
1585}
1586
1588{
1589 if (o2::base::GRPGeomHelper::instance().finaliseCCDB(matcher, obj)) {
1590 return;
1591 }
1592 if (matcher == ConcreteDataMatcher("ITS", "CLUSDICT", 0)) {
1593 mITSDict = (const o2::itsmft::TopologyDictionary*)obj;
1594 return;
1595 }
1596}
1597
1599{
1600 mDebugOut.reset();
1601 LOGP(info, "Cosmics cluster collector: {} cosmics, {} attached and {} road TPC clusters; Cpu: {:.3e} Real: {:.3e} s in {} slots",
1602 mNCosmics, mNClAttached, mNClCorridor, mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1);
1603}
1604
1606{
1607 if (itsStag && roadDets[DetID::ITS]) {
1608 LOGP(warning, "Staggered ITS clusters are not supported: ITS road of cosmics disabled");
1609 roadDets &= ~DetID::getMask(DetID::ITS);
1610 }
1611 auto dataRequest = std::make_shared<DataRequest>();
1612 dataRequest->setITSPerLayer(itsStag);
1613 dataRequest->requestTracks(src, false);
1614 dataRequest->requestTracks(getLegITSSources(src), false); // the ITS clusters of legs with an ITS part are read through their ITS track
1615 dataRequest->requestClusters(src, false);
1616 if (roadDets[DetID::ITS]) {
1617 dataRequest->requestITSClusters(false);
1618 }
1619 if (roadDets[DetID::TOF]) {
1620 dataRequest->requestTOFClusters(false);
1621 }
1622 if (roadDets[DetID::TRD]) {
1623 dataRequest->requestTRDTracklets(false);
1624 }
1625 dataRequest->requestCoscmicTracks(useMC);
1626 auto ggRequest = std::make_shared<o2::base::GRPGeomRequest>(false, // orbitResetTime
1627 false, // GRPECS
1628 false, // GRPLHCIF
1629 true, // GRPMagField
1630 roadDets[DetID::TOF], // askMatLUT: polish refit of TOF-timed cosmics
1631 roadDets[DetID::ITS] ? o2::base::GRPGeomRequest::Aligned : o2::base::GRPGeomRequest::None, // ITS road: chip positions
1632 dataRequest->inputs,
1633 true);
1634 dataRequest->inputs.emplace_back("corrMap", o2::header::gDataOriginTPC, "TPCCORRMAP", 0, Lifetime::Timeframe);
1635
1636 std::vector<OutputSpec> outputs;
1637 outputs.emplace_back("GLO", "COSMFULL", 0, Lifetime::Timeframe);
1638 outputs.emplace_back("GLO", "COSMFULLTF", 0, Lifetime::Timeframe);
1639 outputs.emplace_back("GLO", "COSMFULLTFID", 0, Lifetime::Timeframe);
1640
1641 return DataProcessorSpec{
1642 "cosmics-cluster-collector",
1643 dataRequest->inputs,
1644 outputs,
1645 AlgorithmSpec{adaptFromTask<CosmicsClusterCollectorSpec>(dataRequest, ggRequest, useMC, roadDets)},
1646 Options{
1647 {"corridor-width", VariantType::Float, 1.f, {"half-width of the road around each leg [cm]"}},
1648 {"max-abs-time-err", VariantType::Float, 0.5f, {"max. time error of a cosmic [mus] to search the other TPC side of its one-side legs"}},
1649 {"max-cosmics-per-tf", VariantType::Int, 100, {"write at most this many cosmics per TF (with their clusters), the others are dropped"}},
1650 {"tof-road-width", VariantType::Float, 5.f, {"half-width of the road at the TOF [cm]"}},
1651 {"tof-flight-tolerance", VariantType::Float, 2.f, {"max. deviation of the top/bottom TOF time difference from the muon's flight time [ns]"}},
1652 {"tof-time-error", VariantType::Float, 0.1f, {"error of a cosmic's TOF time for the TPC other-side corridor and the TRD / ITS roads [mus]"}},
1653 {"trd-road-width", VariantType::Float, 5.f, {"half-width of the road at the TRD [cm] (in z plus the pad length)"}},
1654 {"its-road-width", VariantType::Float, 1.5f, {"half-width of the road in the ITS [cm]"}},
1655 {"debug-tree", VariantType::Bool, false, {"write cosmics_collector_debug.root with transformed clusters and road points (test runs)"}}}};
1656}
1657
1658} // namespace o2::globaltracking
header::DataOrigin origin
Class of a TPC cluster in TPC-native coordinates (row, time)
General auxilliary methods.
Definition of the ITSMFT compact cluster.
Collects the raw clusters of matched cosmics (attached + road around the legs) for offline refits.
Wrapper container for different reconstructed object types.
Definition of the ClusterTopology class.
Definition of the TOF cluster.
Global TRD definitions and constants.
int16_t time
Definition RawEventData.h:4
std::vector< std::string > keys
int32_t i
Helper for geometry and GRP related CCDB requests.
Definition of the GeometryTGeo class.
Definition of the ITSMFT ROFrame (trigger) record.
std::vector< o2::its::TrackITS > tracks
Header to collect LHC related constants.
Configurable params for cosmics matching.
Definition of the parameter class for the detector electronics.
Declarations of 2D primitives.
uint16_t pos
Definition RawData.h:3
uint32_t j
Definition RawData.h:0
uint32_t side
Definition RawData.h:0
uint32_t c
Definition RawData.h:2
Definition of the SegmentationAlpide class.
POD correction map.
Matched cosmic track with the raw clusters of its legs and of the road around them,...
Result of top-bottom cosmic tracks leg matching.
Definition of the ITS track.
Helper class to obtain TPC clusters / digits / labels from DPL.
int getTrackID() const
bool isSet() const
Definition MCCompLabel.h:74
int getEventID() const
void checkUpdates(o2::framework::ProcessingContext &pc)
static GRPGeomHelper & instance()
void setRequest(std::shared_ptr< GRPGeomRequest > req)
GPUd() value_type estimateLTFast(o2 static GPUd() float estimateLTIncrement(const o2 PropagatorImpl * Instance(bool uninitialized=false)
Definition Propagator.h:180
const timeEst & getTimeMUS() const
Static class with identifiers, bitmasks and names for ALICE detectors.
Definition DetID.h:60
const Mat3D & getMatrixL2G(int sensID) const
void snapshot(const Output &spec, T const &object)
ServiceRegistryRef services()
Definition InitContext.h:34
ConfigParamRegistry const & options()
Definition InitContext.h:33
decltype(auto) get(R binding, int part=0) const
DataAllocator & outputs()
The data allocator is used to allocate memory for the output data.
InputRecord & inputs()
The inputs associated with this processing context.
void finaliseCCDB(ConcreteDataMatcher &matcher, void *obj) final
void endOfStream(EndOfStreamContext &ec) final
This is invoked whenever we have an EndOfStream event.
CosmicsClusterCollectorSpec(std::shared_ptr< DataRequest > dr, std::shared_ptr< o2::base::GRPGeomRequest > gr, bool useMC, DetID::mask_t roadDets)
int32_t RefitTrackAsTrackParCov(o2::tpc::TrackTPC &trk, bool outward=false, bool resetCov=false)
static GeometryTGeo * Instance()
static void skipPattern(iterator &pattIt)
static constexpr unsigned short InvalidPatternID
Definition CompCluster.h:46
static void detectorToLocalUnchecked(L row, L col, T &xRow, T &zCol)
math_utils::Point3D< T > getClusterCoordinates(const CompCluster &cl) const
bool isGroup(int n) const
Returns true if the element corresponds to a group of rare topologies.
Cluster class for TOF.
Definition Cluster.h:37
Header for data corresponding to the same hardware trigger adapted from DataFormatsITSMFT/ROFRecord.
GLdouble n
Definition glcorearb.h:1982
GLfloat GLfloat GLfloat alpha
Definition glcorearb.h:279
GLint GLenum GLint x
Definition glcorearb.h:403
GLenum src
Definition glcorearb.h:1767
GLsizeiptr size
Definition glcorearb.h:659
GLuint GLuint end
Definition glcorearb.h:469
const GLdouble * v
Definition glcorearb.h:832
GLuint index
Definition glcorearb.h:781
GLdouble GLdouble GLdouble GLdouble top
Definition glcorearb.h:4077
GLuint const GLchar * name
Definition glcorearb.h:781
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
GLsizei const GLchar *const * path
Definition glcorearb.h:3591
GLenum GLuint GLint GLint layer
Definition glcorearb.h:1310
GLenum clamp
Definition glcorearb.h:1245
GLboolean r
Definition glcorearb.h:1233
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat t0
Definition glcorearb.h:5034
GLuint start
Definition glcorearb.h:469
GLboolean GLboolean GLboolean GLboolean a
Definition glcorearb.h:1233
GLubyte GLubyte GLubyte GLubyte w
Definition glcorearb.h:852
GLdouble GLdouble GLdouble z
Definition glcorearb.h:843
constexpr o2::header::DataOrigin gDataOriginTPC
Definition DataHeader.h:579
uint8_t int statusCode int
uint8_t itsSharedClusterMap uint8_t
double norm(const Vec3 &vector)
@ HitTOFFlight
TOF road hit of the top/bottom pair whose time difference matches the muon's flight (fixes the cosmic...
@ HitMatched
part of the leg's matched global track
@ HitRoad
found by the road search around the cosmic
Defining ITS Vertex explicitly as messageable.
Definition Cartesian.h:288
std::vector< ConfigParamSpec > Options
o2::dataformats::GlobalTrackID::mask_t getLegITSSources(o2::dataformats::GlobalTrackID::mask_t src)
framework::DataProcessorSpec getCosmicsClusterCollectorSpec(o2::dataformats::GlobalTrackID::mask_t src, bool useMC, bool itsStag, o2::detectors::DetID::mask_t roadDets)
return *o2::gpu::CAMath::Sqrt(xX0) *(1.f+0.038f *o2 const float const float const float const float rOut
Definition MathUtils.h:133
return *o2::gpu::CAMath::Sqrt(xX0) *(1.f+0.038f *o2 const float const float rIn
Definition MathUtils.h:133
return *o2::gpu::CAMath::Sqrt(xX0) *(1.f+0.038f *o2 const float const float const float rMid
Definition MathUtils.h:133
const bool const bool const int FollowDirection BestTrial TrackITSInternal< NLayers > & best
o2::track::TrackParametrization< float > par
const float const float const float const float std::array< float, 3 > std::array< float, 3 > & direction
int angle2Sector(float phi)
Definition Utils.h:183
float sector2Angle(int sect)
Definition Utils.h:193
Enum< T >::Iterator begin(Enum< T >)
Definition Defs.h:158
TrackParCovF TrackParCov
Definition Track.h:33
a couple of static helper functions to create timestamp values for CCDB queries or override obsolete ...
static void fillTFIDInfo(o2::framework::ProcessingContext &pc, o2::dataformats::TFIDInfo &ti)
@ Corridor
found in the road around the leg
@ Used
attached to some TPC track (for corridor clusters: another track, e.g. a split piece of the leg)
@ Attached
attached to the TPC track of this leg
per-TF quantities of the TPC transformation used in the reconstruction
float t0
time offset of the transformation [time bins]
float vDrift
drift velocity of the transformation [cm/time bin]
matched cosmic with everything needed for an offline refit
float scoreTOFPair
score of the HitTOFFlight pair: road and flight-time residuals squared in units of the cuts (< 0: non...
std::vector< uint8_t > itsPatterns
pattern bytes (row span, column span, bitmap) of the ITS clusters that need them
std::vector< CosmicTRDTracklet > trdTracklets
TRD tracklets of the legs' matched tracks and on the road.
o2::dataformats::TrackCosmics cosmic
matcher output (time in mus; the leg references are only valid within the TF)
float timeTOFMUS
time of the cosmic [mus since the TF start, negative before it] from its HitTOFFlight pair (valid if ...
float chi2MatchPolished
chi2 of the polished bottom and top halves at the closest approach (< 0: not polished)
std::vector< CosmicTPCCluster > clTPCBottom
attached + corridor TPC clusters of the bottom leg
std::vector< CosmicITSCluster > clITS
ITS clusters of the legs' matched tracks and on the road (per half and layer the closest,...
o2::tpc::TrackTPC tpcTop
TPC part of the top leg.
o2::MCCompLabel label
MC label of the cosmic (MC only)
std::vector< CosmicTOFCluster > clTOF
TOF clusters of the legs' matched tracks and on the road.
std::vector< CosmicTPCCluster > clTPCTop
same for the top leg
bool hasTOFTime() const
a HitTOFFlight pair gave the cosmic its time timeTOFMUS
GlobalIDSet getSingleDetectorRefs(GTrackID gidx) const
const o2::tpc::TrackTPC & getTPCTrack(GTrackID id) const
gsl::span< const unsigned char > clusterShMapTPC
externally set TPC clusters sharing map
void collectData(o2::framework::ProcessingContext &pc, const DataRequest &request)
std::unique_ptr< o2::tpc::internal::getWorkflowTPCInput_ret > inputsTPCclusters
gsl::span< const unsigned int > occupancyMapTPC
externally set TPC clusters occupancy map
auto getCosmicTrackMCLabel(int i) const
static constexpr void convPadDriftLengthToLocal(uint32_t sector, uint32_t row, float pad, float driftLength, float &y, float &z)
convert Pad, DriftLength -> Local c.s.
static constexpr float getRowInfoX(uint32_t row)
Gives TPC row info.
static constexpr void convLocalToPadDriftLength(uint32_t sector, uint32_t row, float y, float z, float &pad, float &l)
convert Local c.s. -> Pad, DriftLength
static constexpr int32_t getNumberOfSectorsA()
Gives number of TPC sectors on the A side.
static constexpr float getRowInfoPadWidth(uint32_t row)
static constexpr float getTPCzLength()
Gives Z length of the TPC, one Z side.
constexpr size_t max
o2::InteractionRecord ir(0, 0)
std::vector< Cluster > clusters
std::vector< int > row
std::array< uint16_t, 5 > pattern
std::vector< Tracklet64 > tracklets