Project
Loading...
Searching...
No Matches
AlignmentSpec.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
12#include <cmath>
13#include <chrono>
14#include <fstream>
15#include <memory>
16
17#ifdef WITH_OPENMP
18#include <omp.h>
19#endif
20
21#include <Eigen/Dense>
22#include <GblTrajectory.h>
23#include <GblData.h>
24#include <GblPoint.h>
25#include <GblMeasurement.h>
26#include <MilleBinary.h>
27#include <nlohmann/json.hpp>
28
31#include "Framework/Task.h"
44#include "ITStracking/IOUtils.h"
46#include "ITS3Align/TrackFit.h"
54
55namespace o2::its3::align
56{
57using namespace o2::framework;
65
66namespace
67{
68DerivativeContext makeDerivativeContext(const FrameInfoExt& frame, const TrackD& trk)
69{
70 const auto slopes = computeTrackSlopes(trk.getSnp(), trk.getTgl());
71 const bool isITS3 = constants::detID::isDetITS3(frame.sens);
72 return {.sensorID = isITS3 ? constants::detID::getSensorID(frame.sens) : -1,
73 .layerID = isITS3 ? constants::detID::getDetID2Layer(frame.sens) : -1,
74 .measX = frame.x,
75 .measAlpha = frame.alpha,
76 .measZ = frame.positionTrackingFrame[1],
77 .trkY = trk.getY(),
78 .trkZ = trk.getZ(),
79 .snp = trk.getSnp(),
80 .tgl = trk.getTgl(),
81 .dydx = slopes.dydx,
82 .dzdx = slopes.dzdx};
83}
84
85Matrix36 getRigidBodyBaseDerivatives(const DerivativeContext& ctx)
86{
87 static const RigidBodyDOFSet sRigidBodyBasis;
88 Eigen::MatrixXd dyn(3, sRigidBodyBasis.nDOFs());
89 sRigidBodyBasis.fillDerivatives(ctx, dyn);
90 return dyn;
91}
92} // namespace
93
94class AlignmentSpec final : public Task
95{
96 public:
97 ~AlignmentSpec() final = default;
98 AlignmentSpec(const AlignmentSpec&) = delete;
100 AlignmentSpec& operator=(const AlignmentSpec&) = delete;
101 AlignmentSpec& operator=(AlignmentSpec&&) = delete;
102 AlignmentSpec(std::shared_ptr<DataRequest> dr, std::shared_ptr<o2::base::GRPGeomRequest> gr, GTrackID::mask_t src, bool useMC, bool withPV, bool withITS, OutputEnum out)
103 : mDataRequest(dr), mGGCCDBRequest(gr), mTracksSrc(src), mUseMC(useMC), mWithPV(withPV), mIsITS3(!withITS), mOutOpt(out)
104 {
105 }
106
107 void init(InitContext& ic) final;
108 void run(ProcessingContext& pc) final;
109 void endOfStream(EndOfStreamContext& ec) final;
110 void finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) final;
111 void process();
112
113 private:
114 void updateTimeDependentParams(ProcessingContext& pc);
115 void buildHierarchy();
116
117 // calculate the transport jacobian for points FROM and TO numerically via ridder's method
118 // this assumes the track is already at point FROM and will be extrapolated to TO's x (xTo)
119 // method does not modify the original track
120 bool getTransportJacobian(const TrackD& track, double xTo, double alphaTo, gbl::Matrix5d& jac, gbl::Matrix5d& err);
121
122 // refit ITS track with inward/outward fit (opt. impose pv as additional constraint)
123 // after this we have the refitted track at the innermost update point
124 bool prepareITSTrack(int iTrk, const o2::its::TrackITS& itsTrack, Track& resTrack);
125
126 // prepare ITS measuremnt points
127 void prepareMeasurments(std::span<const itsmft::CompClusterExt> clusters, std::span<const unsigned char> pattIt);
128
129 // build track to vertex association
130 void buildT2V();
131
132 // apply some misalignment on inner ITS3 layers
133 // it can happen that a measurement is pushed outside of
134 // ITS3 acceptance so false is to discard track
135 bool applyMisalignment(Eigen::Vector2d& res, const FrameInfoExt& frame, const TrackD& wTrk, size_t iTrk);
136
137 OutputEnum mOutOpt;
138 std::unique_ptr<o2::utils::TreeStreamRedirector> mDBGOut;
139 std::vector<dataformats::VertexBase> mPVs;
140 std::vector<int> mT2PV;
141 bool mIsITS3{true};
142 const o2::itsmft::TopologyDictionary* mITSDict{nullptr};
143 const o2::its3::TopologyDictionary* mIT3Dict{nullptr};
144 o2::globaltracking::RecoContainer* mRecoData = nullptr;
145 std::unique_ptr<steer::MCKinematicsReader> mcReader;
146 std::vector<FrameInfoExt> mITSTrackingInfo;
147 std::shared_ptr<DataRequest> mDataRequest;
148 std::shared_ptr<o2::base::GRPGeomRequest> mGGCCDBRequest;
149 std::unique_ptr<AlignableVolume> mHierarchy; // tree-hiearchy
150 AlignableVolume::SensorMapping mChip2Hiearchy; // global label mapping to leaves in the tree
151 bool mUseMC{false};
152 bool mWithPV{false};
153 GTrackID::mask_t mTracksSrc;
154 int mNThreads{1};
155 const AlignmentParams* mParams{nullptr};
156 MisalignmentModel mMisalignment;
157 std::array<Eigen::Matrix<double, 6, 1>, 6> mRigidBodyParams; // (dx,dy,dz,rx,ry,rz) in LOC per sensorID
158};
159
161{
163 mNThreads = ic.options().get<int>("nthreads");
164 if (mOutOpt) {
165 LOG(info) << mOutOpt.pstring();
166 mDBGOut = std::make_unique<o2::utils::TreeStreamRedirector>("its3_debug_alg.root", "recreate");
167 }
168 if (mUseMC) {
169 mcReader = std::make_unique<steer::MCKinematicsReader>("collisioncontext.root");
170 }
171}
172
174{
175 if (mOutOpt[OutputOpt::MilleRes]) {
176 updateTimeDependentParams(pc);
177 writeMillepedeResults(mHierarchy.get(), mParams->milleResFile, mParams->milleResOutJson, mParams->misAlgJson);
178 } else {
180 mRecoData = &recoData;
181 mRecoData->collectData(pc, *mDataRequest);
182 updateTimeDependentParams(pc);
183 process();
184 }
185 mRecoData = nullptr;
186}
187
189{
190 if (!mITSDict && !mIT3Dict) {
191 LOGP(fatal, "ITS data is not loaded");
192 }
194 const auto bz = prop->getNominalBz();
195 const auto itsTracks = mRecoData->getITSTracks();
196 const auto itsClRefs = mRecoData->getITSTracksClusterRefs();
197 const auto clusITS = mRecoData->getITSClusters();
198 const auto patterns = mRecoData->getITSClustersPatterns();
199 std::span<const o2::MCCompLabel> mcLbls;
200 if (mUseMC) {
201 mcLbls = mRecoData->getITSTracksMCLabels();
202 }
203 prepareMeasurments(clusITS, patterns);
204
205 if (mWithPV) {
206 buildT2V();
207 }
208
209 if (mNThreads > 1 && !(mParams->misAlgJson.empty())) {
210 LOGP(warn, "Applying misalignment works only single-threaded, forcing to 1");
211 mNThreads = 1;
212 }
213 LOGP(info, "Starting fits with {} threads", mNThreads);
214
215 // Data
216 std::vector<std::vector<gbl::GblTrajectory>> gblTrajSlots(mNThreads);
217 std::vector<std::vector<Track>> resTrackSlots(mNThreads);
218
219 auto timeStart = std::chrono::high_resolution_clock::now();
220 int cFailedRefit{0}, cFailedProp{0}, cSelected{0}, cGBLFit{0}, cGBLFitFail{0}, cGBLChi2Rej{0}, cGBLConstruct{0};
221 double chi2Sum{0}, lostWeightSum{0};
222 int ndfSum{0};
223#ifdef WITH_OPENMP
224#pragma omp parallel num_threads(mNThreads) \
225 reduction(+ : cFailedRefit) \
226 reduction(+ : cFailedProp) \
227 reduction(+ : cSelected) \
228 reduction(+ : cGBLFit) \
229 reduction(+ : cGBLFitFail) \
230 reduction(+ : cGBLChi2Rej) \
231 reduction(+ : cGBLConstruct) \
232 reduction(+ : chi2Sum) \
233 reduction(+ : lostWeightSum) \
234 reduction(+ : ndfSum)
235#endif
236 {
237#ifdef WITH_OPENMP
238 const int tid = omp_get_thread_num();
239#else
240 const int tid = 0;
241#endif
242 auto& gblTrajSlot = gblTrajSlots[tid];
243 auto& resTrackSlot = resTrackSlots[tid];
244
245#ifdef WITH_OPENMP
246#pragma omp for schedule(dynamic)
247#endif
248 for (size_t iTrk = 0; iTrk < (int)itsTracks.size(); ++iTrk) {
249 const auto& trk = itsTracks[iTrk];
250 if (trk.getNClusters() < mParams->minITSCls ||
251 (trk.getChi2() / ((float)trk.getNClusters() * 2 - 5)) >= mParams->maxITSChi2Ndf ||
252 trk.getPt() < mParams->minPt ||
253 (mUseMC && (!mcLbls[iTrk].isValid() || !mcLbls[iTrk].isCorrect()))) {
254 continue;
255 }
256 ++cSelected;
257 Track& resTrack = resTrackSlot.emplace_back();
258 if (!prepareITSTrack((int)iTrk, trk, resTrack)) {
259 ++cFailedRefit;
260 resTrackSlot.pop_back();
261 continue;
262 }
263
264 o2::track::TrackParD* refLin = nullptr;
265 if (mParams->useStableRef) {
266 refLin = &resTrack.track;
267 }
268
269 // outward stepping from track IU
270 auto wTrk = resTrack.track;
271 const bool hasPV = resTrack.info[0].lr == -1;
272 std::vector<gbl::GblPoint> points;
273 bool failed = false;
274 const int np = (int)resTrack.points.size();
276 lt.setTimeNotNeeded();
277 constexpr int perm[5] = {4, 2, 3, 0, 1}; // ALICE->GBL: Q/Pt,Snp,Tgl,Y,Z
278 for (int ip{0}; ip < np; ++ip) {
279 const auto& frame = resTrack.info[ip];
280 gbl::Matrix5d err = gbl::Matrix5d::Identity(), jacALICE = gbl::Matrix5d::Identity(), jacGBL;
281 float msErr = 0.f;
282 if (ip) {
283 // numerically calculates the transport jacobian from prev. point to this point
284 // then we actually do the step to the point and accumulate the material
285 if (!getTransportJacobian(wTrk, frame.x, frame.alpha, jacALICE, err) ||
286 !prop->propagateToAlphaX(wTrk, refLin, frame.alpha, frame.x, false, mParams->maxSnp, mParams->maxStep, 1, mParams->corrType, &lt)) {
287 ++cFailedProp;
288 failed = true;
289 break;
290 }
291 msErr = its::math_utils::MSangle(trk.getPID().getMass(), trk.getP(), lt.getX2X0());
292 // after computing jac, reorder to GBL convention
293 for (int i = 0; i < 5; i++) {
294 for (int j = 0; j < 5; j++) {
295 jacGBL(i, j) = jacALICE(perm[i], perm[j]);
296 }
297 }
298 }
299
300 // wTrk is now in the measurment frame
301 gbl::GblPoint point(jacGBL);
302 // measurement
303 Eigen::Vector2d res, prec;
304 res << frame.positionTrackingFrame[0] - wTrk.getY(), frame.positionTrackingFrame[1] - wTrk.getZ();
305
306 // here we can apply some misalignment on the measurment
307 if (!applyMisalignment(res, frame, wTrk, iTrk)) {
308 failed = true;
309 break;
310 }
311
312 prec << 1. / resTrack.points[ip].sig2y, 1. / resTrack.points[ip].sig2z;
313 // the projection matrix is in the tracking frame the idendity so no need to diagonalize it
314 point.addMeasurement(res, prec);
315 if (msErr > mParams->minMS && ip < np - 1) {
316 Eigen::Vector2d scat(0., 0.), scatPrec = Eigen::Vector2d::Constant(1. / (msErr * msErr));
317 point.addScatterer(scat, scatPrec);
318 lt.clearFast(); // clear if accounted
319 }
320
321 if (frame.lr >= 0) {
322 GlobalLabel lbl(0, frame.sens, true);
323 if (mChip2Hiearchy.find(lbl) == mChip2Hiearchy.end()) {
324 LOGP(fatal, "Cannot find global label: {}", lbl.asString());
325 }
326
327 // derivatives for all sensitive volumes and their parents
328 // this is the derivative in TRK but we want to align in LOC
329 // so dr/da_(LOC) = dr/da_(TRK) * da_(TRK)/da_(LOC)
330 const auto* tileVol = mChip2Hiearchy.at(lbl);
331 const auto derCtx = makeDerivativeContext(frame, wTrk);
332 Matrix36 der = getRigidBodyBaseDerivatives(derCtx);
333
334 // count rigid body columns: only volumes with real DOFs (not DOFPseudo)
335 int nColRB{0};
336 for (const auto* v = tileVol; v && !v->isRoot(); v = v->getParent()) {
337 if (v->getRigidBody()) {
338 nColRB += v->getRigidBody()->nDOFs();
339 }
340 }
341
342 // count calibration columns
343 const auto* sensorVol = tileVol->getParent();
344 const auto* calibSet = sensorVol ? sensorVol->getCalib() : nullptr;
345 const int nCalib = calibSet ? calibSet->nDOFs() : 0;
346 const int nCol = nColRB + nCalib;
347
348 std::vector<int> gLabels;
349 gLabels.reserve(nCol);
350 Eigen::MatrixXd gDer(3, nCol);
351 gDer.setZero();
352 Eigen::Index curCol{0};
353
354 // 1) tile: TRK -> LOC via precomputed T2L and J_L2T
355 const double posTrk[3] = {frame.x, 0., 0.};
356 double posLoc[3];
357 tileVol->getT2L().LocalToMaster(posTrk, posLoc);
358 Matrix66 jacL2T;
359 tileVol->computeJacobianL2T(posLoc, jacL2T);
360 der *= jacL2T;
361 if (tileVol->getRigidBody()) {
362 const int nd = tileVol->getRigidBody()->nDOFs();
363 for (int iDOF = 0; iDOF < nd; ++iDOF) {
364 gLabels.push_back(tileVol->getLabel().rawGBL(iDOF));
365 }
366 gDer.middleCols(curCol, nd) = der;
367 curCol += nd;
368 }
369
370 // 2) chain through parents: child's J_L2P
371 for (const auto* child = tileVol; child->getParent() && !child->getParent()->isRoot(); child = child->getParent()) {
372 der *= child->getJL2P();
373 const auto* parent = child->getParent();
374 if (parent->getRigidBody()) {
375 const int nd = parent->getRigidBody()->nDOFs();
376 for (int iDOF = 0; iDOF < nd; ++iDOF) {
377 gLabels.push_back(parent->getLabel().rawGBL(iDOF));
378 }
379 gDer.middleCols(curCol, nd) = der;
380 curCol += nd;
381 }
382 }
383
384 // 3) calibration derivatives (apply directly on the whole sensor, not on individual tiles)
385 if (calibSet) {
386 const int nd = calibSet->nDOFs();
387 Eigen::MatrixXd calDer(3, nd);
388 calibSet->fillDerivatives(derCtx, calDer);
389 for (int iDOF = 0; iDOF < nd; ++iDOF) {
390 gLabels.push_back(sensorVol->getLabel().asCalib().rawGBL(iDOF));
391 }
392 gDer.middleCols(curCol, nd) = calDer;
393 curCol += nd;
394 }
395 point.addGlobals(gLabels, gDer);
396 }
397
398 if (mOutOpt[OutputOpt::VerboseGBL]) {
399 static Eigen::IOFormat fmt(4, 0, ", ", "\n", "[", "]");
400 LOGP(info, "WORKING-POINT {}", ip);
401 LOGP(info, "Track: {}", wTrk.asString());
402 LOGP(info, "FrameInfo: {}", frame.asString());
403 std::cout << "jacALICE:\n"
404 << jacALICE.format(fmt) << '\n';
405 std::cout << "jacGBL:\n"
406 << jacGBL.format(fmt) << '\n';
407 LOGP(info, "Point {}: GBL res=({}, {}), KF stored res=({}, {})",
408 ip, res[0], res[1], resTrack.points[ip].dy, resTrack.points[ip].dz);
409 LOGP(info, "residual: dy={} dz={}", res[0], res[1]);
410 LOGP(info, "precision: precY={} precZ={}", prec[0], prec[1]);
411 point.printPoint(5);
412 }
413 points.push_back(point);
414 }
415 if (!failed) {
416 gbl::GblTrajectory traj(points, std::abs(bz) > 0.01);
417 if (traj.isValid()) {
418 double chi2 = NAN, lostWeight = NAN;
419 int ndf = 0;
420 if (auto ierr = traj.fit(chi2, ndf, lostWeight); !ierr) {
421 if (mOutOpt[OutputOpt::VerboseGBL]) {
422 LOGP(info, "GBL FIT chi2 {} ndf {}", chi2, ndf);
423 traj.printTrajectory(5);
424 }
425 if (chi2 / ndf > mParams->maxChi2Ndf && cGBLChi2Rej++ < 10) {
426 LOGP(error, "GBL fit exceeded red chi2 {}", chi2 / ndf);
427 if (std::abs(resTrack.kfFit.chi2Ndf - 1) < 0.02) {
428 LOGP(error, "\tGBL is far away from good KF fit!!!!");
429 continue;
430 }
431 } else {
432 ++cGBLFit;
433 chi2Sum += chi2;
434 lostWeightSum += lostWeight;
435 ndfSum += ndf;
436 if (mOutOpt[OutputOpt::MilleData]) {
437 gblTrajSlot.push_back(traj);
438 }
439 FitInfo fit;
440 fit.ndf = ndf;
441 fit.chi2 = (float)chi2;
442 fit.chi2Ndf = (float)chi2 / (float)ndf;
443 resTrack.gblFit = fit;
444 }
445 } else {
446 ++cGBLFitFail;
447 }
448 } else {
449 ++cGBLConstruct;
450 }
451 }
452 }
453 }
454 auto timeEnd = std::chrono::high_resolution_clock::now();
455 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(timeEnd - timeStart);
456 LOGP(info, "Fitted {} tracks out of {} (selected {}) in {} sec", cGBLFit, itsTracks.size(), cSelected, duration.count() / 1e3);
457 LOGP(info, "\tRefit failed for {} tracks; Failed prop for {} tracks", cFailedRefit, cFailedProp);
458 LOGP(info, "\tGBL SUMMARY:");
459 LOGP(info, "\t\tGBL construction failed {}", cGBLConstruct);
460 LOGP(info, "\t\tGBL fit failed {}", cGBLFitFail);
461 LOGP(info, "\t\tGBL chi2Ndf rejected {}", cGBLChi2Rej);
462 if (!ndfSum) {
463 LOGP(info, "\t\tGBL Chi2/Ndf = NDF IS 0");
464 } else {
465 LOGP(info, "\t\tGBL Chi2/Ndf = {}", chi2Sum / ndfSum);
466 }
467 LOGP(info, "\t\tGBL LostWeight = {}", lostWeightSum);
468 LOGP(info, "Streaming results to output");
469 if (mOutOpt[OutputOpt::MilleData]) {
470 gbl::MilleBinary mille(mParams->milleBinFile, true);
471 for (auto& slot : gblTrajSlots) {
472 for (auto& traj : slot) {
473 traj.milleOut(mille);
474 }
475 }
476 }
477 if (mOutOpt[OutputOpt::Debug]) {
478 for (auto& slot : resTrackSlots) {
479 for (auto& res : slot) {
480 (*mDBGOut) << "res"
481 << "trk=" << res
482 << "\n";
483 }
484 }
485 }
486}
487
488void AlignmentSpec::updateTimeDependentParams(ProcessingContext& pc)
489{
491 if (static bool initOnce{false}; !initOnce) {
492 initOnce = true;
495 mParams = &AlignmentParams::Instance();
496 mParams->printKeyValues(true, true);
497
498 buildHierarchy();
499
500 if (mParams->doMisalignmentLeg || mParams->doMisalignmentRB || mParams->doMisalignmentInex) {
501 mMisalignment = {};
502 for (auto& rb : mRigidBodyParams) {
503 rb.setZero();
504 }
505 if (!mParams->misAlgJson.empty()) {
506 mMisalignment = loadMisalignmentModel(mParams->misAlgJson);
507 if (mParams->doMisalignmentRB) {
508 using json = nlohmann::json;
509 std::ifstream f(mParams->misAlgJson);
510 auto data = json::parse(f);
511 for (const auto& item : data) {
512 int id = item["id"].get<int>();
513 if (!item.contains("rigidBody")) {
514 continue;
515 }
516 auto rb = item["rigidBody"].get<std::vector<double>>();
517 for (int k = 0; k < 6 && k < static_cast<int>(rb.size()); ++k) {
518 mRigidBodyParams[id](k) = rb[k];
519 }
520 }
521 }
522 }
523 }
524 }
525}
526
527void AlignmentSpec::buildHierarchy()
528{
529 if (mIsITS3) {
530 mHierarchy = buildHierarchyIT3(mChip2Hiearchy);
531 } else {
532 mHierarchy = buildHierarchyITS(mChip2Hiearchy);
533 }
534
535 if (!mParams->dofConfigJson.empty()) {
536 applyDOFConfig(mHierarchy.get(), mParams->dofConfigJson);
537 }
538
539 mHierarchy->finalise();
540 if (mOutOpt[OutputOpt::MilleSteer]) {
541 std::ofstream tree(mParams->milleTreeFile);
542 mHierarchy->writeTree(tree);
543 std::ofstream cons(mParams->milleConFile);
544 mHierarchy->writeRigidBodyConstraints(cons);
545 std::ofstream par(mParams->milleParamFile);
546 mHierarchy->writeParameters(par);
547 }
548}
549
550bool AlignmentSpec::getTransportJacobian(const TrackD& track, double xTo, double alphaTo, gbl::Matrix5d& jac, gbl::Matrix5d& err)
551{
553 const auto bz = prop->getNominalBz();
554 const auto minStep = std::sqrt(std::numeric_limits<double>::epsilon());
555 const gbl::Vector5d x0(track.getParams());
556 auto trackC = track;
557 o2::track::TrackParD* refLin{nullptr};
558 if (mParams->useStableRef) {
559 refLin = &trackC;
560 }
561
562 auto propagate = [&](gbl::Vector5d& p) -> bool {
563 TrackD tmp(track);
564 for (int i{0}; i < track::kNParams; ++i) {
565 tmp.setParam(p[i], i);
566 }
567 if (!prop->propagateToAlphaX(tmp, refLin, alphaTo, xTo, false, mParams->maxSnp, mParams->maxStep, 1, mParams->corrType)) {
568 return false;
569 }
570 p = gbl::Vector5d(tmp.getParams());
571 return true;
572 };
573
574 for (int iPar{0}; iPar < track::kNParams; ++iPar) {
575 // step size
576 double h = std::min(mParams->ridderMaxIniStep[iPar], std::max(minStep, std::abs(track.getParam(iPar)) * mParams->ridderRelIniStep[iPar]) * std::pow(mParams->ridderShrinkFac, mParams->ridderMaxExtrap / 2));
577 ;
578 // romberg tableu
579 Eigen::MatrixXd cur(track::kNParams, mParams->ridderMaxExtrap);
580 Eigen::MatrixXd pre(track::kNParams, mParams->ridderMaxExtrap);
581 double normErr = std::numeric_limits<double>::max();
582 gbl::Vector5d bestDeriv = gbl::Vector5d::Constant(std::numeric_limits<double>::max());
583 for (int iExt{0}; iExt < mParams->ridderMaxExtrap; ++iExt) {
584 gbl::Vector5d xPlus = x0, xMinus = x0;
585 xPlus(iPar) += h;
586 xMinus(iPar) -= h;
587 if (!propagate(xPlus) || !propagate(xMinus)) {
588 return false;
589 }
590 cur.col(0) = (xPlus - xMinus) / (2.0 * h);
591 if (!iExt) {
592 bestDeriv = cur.col(0);
593 }
594 // shrink step in next iteration
595 h /= mParams->ridderShrinkFac;
596 // richardson extrapolation
597 double fac = mParams->ridderShrinkFac * mParams->ridderShrinkFac;
598 for (int k{1}; k <= iExt; ++k) {
599 cur.col(k) = (fac * cur.col(k - 1) - pre.col(k - 1)) / (fac - 1.0);
600 fac *= mParams->ridderShrinkFac * mParams->ridderShrinkFac;
601 double e = std::max((cur.col(k) - cur.col(k - 1)).norm(), (cur.col(k) - pre.col(k - 1)).norm());
602 if (e <= normErr) {
603 normErr = e;
604 bestDeriv = cur.col(k);
605 if (normErr < mParams->ridderEps) {
606 break;
607 }
608 }
609 }
610 if (normErr < mParams->ridderEps) {
611 break;
612 }
613 // check stability
614 if (iExt > 0) {
615 double tableauErr = (cur.col(iExt) - pre.col(iExt - 1)).norm();
616 if (tableauErr >= 2.0 * normErr) {
617 break;
618 }
619 }
620 std::swap(cur, pre);
621 }
622 if (bestDeriv.isApproxToConstant(std::numeric_limits<double>::max())) {
623 return false;
624 }
625 jac.col(iPar) = bestDeriv;
626 err.col(iPar) = gbl::Vector5d::Constant(normErr);
627 }
628
629 if (jac.isIdentity(1e-8)) {
630 LOGP(error, "Near jacobian idendity for taking track from {} to {}", track.getX(), xTo);
631 return false;
632 }
633
634 return true;
635}
636
637bool AlignmentSpec::prepareITSTrack(int iTrk, const o2::its::TrackITS& itsTrack, align::Track& resTrack)
638{
639 const auto itsClRefs = mRecoData->getITSTracksClusterRefs();
640 auto trFit = convertTrack<double>(itsTrack.getParamOut()); // take outer track fit as start of refit
643 const auto bz = prop->getNominalBz();
644 std::array<const FrameInfoExt*, 8> frameArr{};
645 o2::track::TrackParD trkOut, *refLin = nullptr;
646 if (mParams->useStableRef) {
647 refLin = &(trkOut = trFit);
648 }
649
650 auto accountCluster = [&](int i, TrackD& tr, float& chi2, Measurement& meas, o2::track::TrackParD* refLin) {
651 if (frameArr[i]) { // update with cluster
652 if (!prop->propagateToAlphaX(tr, refLin, frameArr[i]->alpha, frameArr[i]->x, false, mParams->maxSnp, mParams->maxStep, 1, mParams->corrType)) {
653 return 2;
654 }
655 meas.dy = frameArr[i]->positionTrackingFrame[0] - tr.getY();
656 meas.dz = frameArr[i]->positionTrackingFrame[1] - tr.getZ();
657 meas.sig2y = frameArr[i]->covarianceTrackingFrame[0];
658 meas.sig2z = frameArr[i]->covarianceTrackingFrame[2];
659 meas.z = tr.getZ();
660 meas.phi = tr.getPhi();
661 o2::math_utils::bringTo02Pid(meas.phi);
662 chi2 += (float)tr.getPredictedChi2Quiet(frameArr[i]->positionTrackingFrame, frameArr[i]->covarianceTrackingFrame);
663 if (!tr.update(frameArr[i]->positionTrackingFrame, frameArr[i]->covarianceTrackingFrame)) {
664 return 2;
665 }
666 if (refLin) { // displace the reference to the last updated cluster
667 refLin->setY(frameArr[i]->positionTrackingFrame[0]);
668 refLin->setZ(frameArr[i]->positionTrackingFrame[1]);
669 }
670 return 0;
671 }
672 return 1;
673 };
674
675 FrameInfoExt pvInfo;
676 if (mWithPV) { // add PV as constraint
677 const int iPV = mT2PV[iTrk];
678 if (iPV < 0) {
679 return false;
680 }
681 const auto& pv = mPVs[iPV];
682 auto tmp = convertTrack<double>(itsTrack.getParamIn());
683 if (!prop->propagateToDCA(pv, tmp, bz)) {
684 return false;
685 }
686 pvInfo.alpha = (float)tmp.getAlpha();
687 double ca{0}, sa{0};
688 o2::math_utils::bringToPMPid(pvInfo.alpha);
689 o2::math_utils::sincosd(pvInfo.alpha, sa, ca);
690 pvInfo.x = tmp.getX();
691 pvInfo.positionTrackingFrame[0] = -pv.getX() * sa + pv.getY() * ca;
692 pvInfo.positionTrackingFrame[1] = pv.getZ();
693 pvInfo.covarianceTrackingFrame[0] = 0.5 * (pv.getSigmaX2() + pv.getSigmaY2());
694 pvInfo.covarianceTrackingFrame[2] = pv.getSigmaY2();
695 pvInfo.sens = -1;
696 pvInfo.lr = -1;
697 frameArr[0] = &pvInfo;
698 }
699
700 // collect all track clusters to array, placing them to layer+1 slot
701 int nCl = itsTrack.getNClusters();
702 for (int i = 0; i < nCl; i++) { // clusters are ordered from the outermost to the innermost
703 const auto& curInfo = mITSTrackingInfo[itsClRefs[itsTrack.getClusterEntry(i)]];
704 frameArr[1 + curInfo.lr] = &curInfo;
705 }
706
707 // start refit
708 resTrack.points.clear();
709 resTrack.info.clear();
710 trFit.resetCovariance();
711 trFit.setCov(trFit.getQ2Pt() * trFit.getQ2Pt() * trFit.getCov()[14], 14);
712 float chi2{0};
713 for (int i{7}; i >= 0; --i) {
714 Measurement point;
715 int res = accountCluster(i, trFit, chi2, point, refLin);
716 if (res == 2) {
717 return false;
718 } else if (res == 0) {
719 resTrack.points.push_back(point);
720 resTrack.info.push_back(*frameArr[i]);
721 resTrack.track = trFit; // put track to whatever the IU is
722 }
723 }
724 // reverse inserted points so they are in the same order as the track
725 std::reverse(resTrack.info.begin(), resTrack.info.end());
726 std::reverse(resTrack.points.begin(), resTrack.points.end());
727 resTrack.kfFit.chi2 = chi2;
728 resTrack.kfFit.ndf = (int)resTrack.info.size() * 2 - 5;
729 resTrack.kfFit.chi2Ndf = chi2 / (float)resTrack.kfFit.ndf;
730
731 return true;
732}
733
734void AlignmentSpec::prepareMeasurments(std::span<const itsmft::CompClusterExt> clusters, std::span<const unsigned char> patterns)
735{
736 LOGP(info, "Preparing {} measurments", clusters.size());
737 auto geom = its::GeometryTGeo::Instance();
738 geom->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L, o2::math_utils::TransformType::L2G));
739 mITSTrackingInfo.clear();
740 mITSTrackingInfo.reserve(clusters.size());
741 auto pattIt = patterns.begin();
742 for (const auto& cls : clusters) {
743 const auto sens = cls.getSensorID();
744 const auto lay = geom->getLayer(sens);
745 double sigmaY2{0}, sigmaZ2{0};
747 if (mIsITS3) {
748 locXYZ = o2::its3::ioutils::extractClusterData(cls, pattIt, mIT3Dict, sigmaY2, sigmaZ2);
749 } else {
750 locXYZ = o2::its::ioutils::extractClusterData(cls, pattIt, mITSDict, sigmaY2, sigmaZ2);
751 }
752 sigmaY2 += mParams->extraClsErrY[lay] * mParams->extraClsErrY[lay];
753 sigmaZ2 += mParams->extraClsErrZ[lay] * mParams->extraClsErrZ[lay];
754 // Transformation to the local --> global
755 const auto gloXYZ = geom->getMatrixL2G(sens) * locXYZ;
756 // Inverse transformation to the local --> tracking
757 auto trkXYZf = geom->getMatrixT2L(sens) ^ locXYZ;
759 trkXYZ.SetCoordinates(trkXYZf.X(), trkXYZf.Y(), trkXYZf.Z());
760 // Tracking alpha angle
761 // We want that each cluster rotates its tracking frame to the clusters phi
762 // that way the track linearization around the measurement is less biases to the arc
763 // this means automatically that the measurement on the arc is at 0 for the curved layers
764 double alpha = geom->getSensorRefAlpha(sens);
765 double x = trkXYZ.x();
766 if (mIsITS3 && constants::detID::isDetITS3(sens)) {
767 trkXYZ.SetY(0.f);
768 // alpha&x always have to be defined wrt to the global Z axis!
769 x = std::hypot(gloXYZ.x(), gloXYZ.y());
770 trkXYZ.SetX(x);
771 alpha = std::atan2(gloXYZ.y(), gloXYZ.x());
772 auto chip = constants::detID::getSensorID(sens);
773 sigmaY2 += mParams->extraClsErrY[chip] * mParams->extraClsErrY[chip];
774 sigmaZ2 += mParams->extraClsErrZ[chip] * mParams->extraClsErrZ[chip];
775 }
777 mITSTrackingInfo.emplace_back(sens, lay, x, alpha,
778 std::array<double, 2>{trkXYZ.y(), trkXYZ.z()},
779 std::array<double, 3>{sigmaY2, 0., sigmaZ2});
780 }
781}
782
783void AlignmentSpec::buildT2V()
784{
785 const auto& itsTracks = mRecoData->getITSTracks();
786 mT2PV.clear();
787 mT2PV.resize(itsTracks.size(), -1);
788 if (mUseMC) {
789 mPVs.reserve(mcReader->getNEvents(0));
790 for (int iEve{0}; iEve < mcReader->getNEvents(0); ++iEve) {
791 const auto& eve = mcReader->getMCEventHeader(0, iEve);
793 constexpr float err{22e-4f};
794 vtx.setX((float)eve.GetX());
795 vtx.setY((float)eve.GetY());
796 vtx.setZ((float)eve.GetZ());
797 vtx.setSigmaX(err);
798 vtx.setSigmaY(err);
799 vtx.setSigmaZ(err);
800 mPVs.push_back(vtx);
801 }
802 const auto& mcLbls = mRecoData->getITSTracksMCLabels();
803 for (size_t iTrk{0}; iTrk < mcLbls.size(); ++iTrk) {
804 const auto& lbl = mcLbls[iTrk];
805 if (!lbl.isValid() || !lbl.isCorrect()) {
806 continue;
807 }
808 const auto& mcTrk = mcReader->getTrack(lbl);
809 if (mcTrk->isPrimary()) {
810 mT2PV[iTrk] = lbl.getEventID();
811 }
812 }
813 } else {
814 LOGP(fatal, "Data PV to track TODO");
815 }
816}
817
818bool AlignmentSpec::applyMisalignment(Eigen::Vector2d& res, const FrameInfoExt& frame, const TrackD& wTrk, size_t iTrk)
819{
820 if (!constants::detID::isDetITS3(frame.sens)) {
821 return true;
822 }
823
824 const int sensorID = constants::detID::getSensorID(frame.sens);
825 const int layerID = constants::detID::getDetID2Layer(frame.sens);
826 const MisalignmentFrame misFrame{
827 .sensorID = sensorID,
828 .layerID = layerID,
829 .x = frame.x,
830 .alpha = frame.alpha,
831 .z = frame.positionTrackingFrame[1]};
832
833 // --- Legendre deformation (non-rigid-body) ---
834 if (mParams->doMisalignmentLeg && mIsITS3 && mUseMC) {
835 const auto prop = o2::base::PropagatorD::Instance();
836
837 const auto lbl = mRecoData->getITSTracksMCLabels()[iTrk];
838 if (lbl.isFake()) {
839 return false;
840 }
841 const auto mcTrk = mcReader->getTrack(lbl);
842 if (!mcTrk) {
843 return false;
844 }
845 std::array<double, 3> xyz{mcTrk->GetStartVertexCoordinatesX(), mcTrk->GetStartVertexCoordinatesY(), mcTrk->GetStartVertexCoordinatesZ()};
846 std::array<double, 3> pxyz{mcTrk->GetStartVertexMomentumX(), mcTrk->GetStartVertexMomentumY(), mcTrk->GetStartVertexMomentumZ()};
847 TParticlePDG* pPDG = TDatabasePDG::Instance()->GetParticle(mcTrk->GetPdgCode());
848 if (!pPDG) {
849 return false;
850 }
851 o2::track::TrackParD mcPar(xyz, pxyz, TMath::Nint(pPDG->Charge() / 3), false);
852
853 auto mcAtCl = mcPar;
854 if (!mcAtCl.rotate(frame.alpha) || !prop->PropagateToXBxByBz(mcAtCl, frame.x)) {
855 return false;
856 }
857
858 const auto shift = evaluateLegendreShift(mMisalignment[sensorID], misFrame, computeTrackSlopes(mcAtCl.getSnp(), mcAtCl.getTgl()));
859 if (!shift.accepted) {
860 return false;
861 }
862
863 res[0] += shift.dy;
864 res[1] += shift.dz;
865 }
866
867 // --- Rigid body misalignment ---
868 // Must use the same derivative chain as GBL:
869 // dres/da_parent = dres/da_TRK * J_L2T_tile * J_L2P_tile
870 // The tile is a pseudo-volume; Millepede fits at the halfBarrel (parent) level.
871 if (mParams->doMisalignmentRB) {
872 GlobalLabel lbl(0, frame.sens, true);
873 if (mChip2Hiearchy.find(lbl) == mChip2Hiearchy.end()) {
874 return true; // sensor not in hierarchy, skip
875 }
876 const auto* tileVol = mChip2Hiearchy.at(lbl);
877
878 // derivative in TRK frame (3x6: rows = dy, dz, dsnp)
879 Matrix36 der = getRigidBodyBaseDerivatives(makeDerivativeContext(frame, wTrk));
880
881 // TRK -> tile LOC
882 const double posTrk[3] = {frame.x, 0., 0.};
883 double posLoc[3];
884 tileVol->getT2L().LocalToMaster(posTrk, posLoc);
885 Matrix66 jacL2T;
886 tileVol->computeJacobianL2T(posLoc, jacL2T);
887 der *= jacL2T;
888
889 // tile LOC -> halfBarrel LOC (same chain as GBL hierarchy walk)
890 der *= tileVol->getJL2P();
891
892 // apply: delta_res = der * delta_a_halfBarrel
893 Eigen::Vector3d shift = der * mRigidBodyParams[sensorID];
894 res[0] += shift[0]; // dy
895 res[1] += shift[1]; // dz
896 }
897
898 // --- In-extensional deformation ---
899 // displacement field u(phi,z) = (u_phi, u_z, u_r)
900 // dy = -u_phi + y' * u_r, dz = -u_z + z' * u_r
901 if (mParams->doMisalignmentInex) {
902 const auto shift = evaluateInextensionalShift(mMisalignment[sensorID], misFrame, computeTrackSlopes(wTrk.getSnp(), wTrk.getTgl()));
903 res[0] += shift.dy;
904 res[1] += shift.dz;
905 }
906
907 if (mOutOpt[OutputOpt::MisRes]) {
908 (*mDBGOut) << "mis"
909 << "dy=" << res[0]
910 << "dz=" << res[1]
911 << "sens=" << sensorID
912 << "lay=" << layerID
913 << "z=" << frame.positionTrackingFrame[1]
914 << "phi=" << frame.alpha
915 << "\n";
916 }
917
918 return true;
919}
920
922{
923 mDBGOut->Close();
924 mDBGOut.reset();
925}
926
928{
930 return;
931 }
932 if (matcher == ConcreteDataMatcher("ITS", "CLUSDICT", 0)) {
933 LOG(info) << "its cluster dictionary updated";
934 mITSDict = (const o2::itsmft::TopologyDictionary*)obj;
935 return;
936 }
937 if (matcher == ConcreteDataMatcher("IT3", "CLUSDICT", 0)) {
938 LOG(info) << "it3 cluster dictionary updated";
939 mIT3Dict = (const o2::its3::TopologyDictionary*)obj;
940 return;
941 }
942}
943
944DataProcessorSpec getAlignmentSpec(GTrackID::mask_t srcTracks, GTrackID::mask_t srcClusters, bool useMC, bool withPV, bool withITS, OutputEnum out)
945{
946 auto dataRequest = std::make_shared<DataRequest>();
947 std::shared_ptr<o2::base::GRPGeomRequest> ggRequest{nullptr};
948 if (!out[OutputOpt::MilleRes]) {
949 dataRequest->requestTracks(srcTracks, useMC);
950 if (!withITS) {
951 dataRequest->requestIT3Clusters(useMC);
952 } else {
953 dataRequest->requestClusters(srcClusters, useMC);
954 }
955 if (withPV && !useMC) {
956 dataRequest->requestPrimaryVertices(useMC);
957 }
958 ggRequest = std::make_shared<o2::base::GRPGeomRequest>(false, // orbitResetTime
959 false, // GRPECS=true
960 true, // GRPLHCIF
961 true, // GRPMagField
962 true, // askMatLUT
964 dataRequest->inputs, // inputs
965 true, // askOnce
966 true); // propagatorD
967 } else {
968 dataRequest->inputs.emplace_back("dummy", "GLO", "DUMMY_OUT", 0);
969 ggRequest = std::make_shared<o2::base::GRPGeomRequest>(false, // orbitResetTime
970 false, // GRPECS=true
971 false, // GRPLHCIF
972 false, // GRPMagField
973 false, // askMatLUT
975 dataRequest->inputs);
976 }
977
978 Options opts{
979 {"nthreads", VariantType::Int, 1, {"number of threads"}},
980 };
981
982 return DataProcessorSpec{
983 .name = "its3-alignment",
984 .inputs = dataRequest->inputs,
985 .outputs = {},
986 .algorithm = AlgorithmSpec{adaptFromTask<AlignmentSpec>(dataRequest, ggRequest, srcTracks, useMC, withPV, withITS, out)},
987 .options = opts};
988}
989} // namespace o2::its3::align
Wrapper container for different reconstructed object types.
Definition of the ClusterTopology class.
Definition of the BuildTopologyDictionary class for ITS3.
int32_t i
#define failed(...)
Definition Utils.h:42
Helper for geometry and GRP related CCDB requests.
Definition of the GeometryTGeo class.
uint32_t j
Definition RawData.h:0
uint32_t res
Definition RawData.h:0
nlohmann::json json
Referenc on track indices contributing to the vertex, with possibility chose tracks from specific sou...
Measurement
int nDOFs() const
std::string asString() const
Class for time synchronization of RawReader instances.
void fillDerivatives(const DerivativeContext &ctx, Eigen::Ref< Eigen::MatrixXd > out) const override
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:178
void printKeyValues(bool showProv=true, bool useLogger=false, bool withPadding=true, bool showHash=true) const final
Static class with identifiers, bitmasks and names for ALICE detectors.
Definition DetID.h:58
ConfigParamRegistry const & options()
Definition InitContext.h:33
std::map< GlobalLabel, AlignableVolume * > SensorMapping
void endOfStream(EndOfStreamContext &ec) final
This is invoked whenever we have an EndOfStream event.
void run(ProcessingContext &pc) final
void init(InitContext &ic) final
void finaliseCCDB(ConcreteDataMatcher &matcher, void *obj) final
static GeometryTGeo * Instance()
void fillMatrixCache(int mask) override
int getClusterEntry(int i) const
Definition TrackITS.h:67
std::string pstring(bool withNewline=false) const
Definition EnumFlags.h:527
GLfloat GLfloat GLfloat alpha
Definition glcorearb.h:279
GLint GLenum GLint x
Definition glcorearb.h:403
GLenum src
Definition glcorearb.h:1767
const GLdouble * v
Definition glcorearb.h:832
GLdouble f
Definition glcorearb.h:310
GLboolean * data
Definition glcorearb.h:298
GLuint GLfloat x0
Definition glcorearb.h:5034
GLuint id
Definition glcorearb.h:650
Node par(int index)
Parameters.
Defining ITS Vertex explicitly as messageable.
Definition Cartesian.h:288
std::vector< ConfigParamSpec > Options
o2::track::TrackParCovD TrackD
Definition TrackFit.h:27
MisalignmentShift evaluateInextensionalShift(const SensorMisalignment &sensor, const MisalignmentFrame &frame, const TrackSlopes &slopes)
Eigen::Matrix< double, 3, 6 > Matrix36
AlignableVolume::Ptr buildHierarchyITS(AlignableVolume::SensorMapping &sensorMap)
MisalignmentModel loadMisalignmentModel(const std::string &jsonPath)
Eigen::Matrix< double, 6, 6 > Matrix66
o2::framework::DataProcessorSpec getAlignmentSpec(o2::dataformats::GlobalTrackID::mask_t srcTracks, o2::dataformats::GlobalTrackID::mask_t srcClus, bool useMC, bool withPV, bool withITS3, OutputEnum out)
MisalignmentShift evaluateLegendreShift(const SensorMisalignment &sensor, const MisalignmentFrame &frame, const TrackSlopes &slopes)
TrackSlopes computeTrackSlopes(double snp, double tgl)
void writeMillepedeResults(AlignableVolume *root, const std::string &milleResPath, const std::string &outJsonPath, const std::string &injectedJsonPath="")
void applyDOFConfig(AlignableVolume *root, const std::string &jsonPath)
AlignableVolume::Ptr buildHierarchyIT3(AlignableVolume::SensorMapping &sensorMap)
bool isDetITS3(T detID)
Definition SpecsV2.h:210
o2::math_utils::Point3D< T > extractClusterData(const itsmft::CompClusterExt &c, iterator &iter, const o2::its3::TopologyDictionary *dict, T &sig2y, T &sig2z)
Definition IOUtils.h:37
o2::math_utils::Point3D< T > extractClusterData(const itsmft::CompClusterExt &c, iterator &iter, const itsmft::TopologyDictionary *dict, T &sig2y, T &sig2z)
Definition IOUtils.h:41
int int int float float float int nCl
const TrackingFrameInfo *const const Cluster *const const float const float bz
void bringToPMPid(double &phi)
Definition Utils.h:105
constexpr int kNParams
TrackParametrizationWithError< double > TrackParCovD
Definition Track.h:32
a couple of static helper functions to create timestamp values for CCDB queries or override obsolete ...
void collectData(o2::framework::ProcessingContext &pc, const DataRequest &request)
o2::base::PropagatorD::MatCorrType corrType
std::array< double, 2 > positionTrackingFrame
std::vector< Measurement > points
o2::track::TrackParCovD track
std::vector< FrameInfoExt > info
static constexpr int L2G
Definition Cartesian.h:54
static constexpr int T2L
Definition Cartesian.h:55
static constexpr int T2G
Definition Cartesian.h:56
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"
std::vector< Cluster > clusters
std::unique_ptr< TTree > tree((TTree *) flIn.Get(std::string(o2::base::NameConf::CTFTREENAME).c_str()))