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 LOGP(info, "Starting fits with {} threads", mNThreads);
210
211 // Data
212 std::vector<std::vector<gbl::GblTrajectory>> gblTrajSlots(mNThreads);
213 std::vector<std::vector<Track>> resTrackSlots(mNThreads);
214
215 auto timeStart = std::chrono::high_resolution_clock::now();
216 int cFailedRefit{0}, cFailedProp{0}, cSelected{0}, cGBLFit{0}, cGBLFitFail{0}, cGBLChi2Rej{0}, cGBLConstruct{0};
217 double chi2Sum{0}, lostWeightSum{0};
218 int ndfSum{0};
219#ifdef WITH_OPENMP
220#pragma omp parallel num_threads(mNThreads) \
221 reduction(+ : cFailedRefit) \
222 reduction(+ : cFailedProp) \
223 reduction(+ : cSelected) \
224 reduction(+ : cGBLFit) \
225 reduction(+ : cGBLFitFail) \
226 reduction(+ : cGBLChi2Rej) \
227 reduction(+ : cGBLConstruct) \
228 reduction(+ : chi2Sum) \
229 reduction(+ : lostWeightSum) \
230 reduction(+ : ndfSum)
231#endif
232 {
233#ifdef WITH_OPENMP
234 const int tid = omp_get_thread_num();
235#else
236 const int tid = 0;
237#endif
238 auto& gblTrajSlot = gblTrajSlots[tid];
239 auto& resTrackSlot = resTrackSlots[tid];
240
241#ifdef WITH_OPENMP
242#pragma omp for schedule(dynamic)
243#endif
244 for (size_t iTrk = 0; iTrk < (int)itsTracks.size(); ++iTrk) {
245 const auto& trk = itsTracks[iTrk];
246 if (trk.getNClusters() < mParams->minITSCls ||
247 (trk.getChi2() / ((float)trk.getNClusters() * 2 - 5)) >= mParams->maxITSChi2Ndf ||
248 trk.getPt() < mParams->minPt ||
249 (mUseMC && (!mcLbls[iTrk].isValid() || !mcLbls[iTrk].isCorrect()))) {
250 continue;
251 }
252 ++cSelected;
253 Track& resTrack = resTrackSlot.emplace_back();
254 if (!prepareITSTrack((int)iTrk, trk, resTrack)) {
255 ++cFailedRefit;
256 resTrackSlot.pop_back();
257 continue;
258 }
259
260 o2::track::TrackParD* refLin = nullptr;
261 if (mParams->useStableRef) {
262 refLin = &resTrack.track;
263 }
264
265 // outward stepping from track IU
266 auto wTrk = resTrack.track;
267 const bool hasPV = resTrack.info[0].lr == -1;
268 std::vector<gbl::GblPoint> points;
269 bool failed = false;
270 const int np = (int)resTrack.points.size();
272 lt.setTimeNotNeeded();
273 constexpr int perm[5] = {4, 2, 3, 0, 1}; // ALICE->GBL: Q/Pt,Snp,Tgl,Y,Z
274 for (int ip{0}; ip < np; ++ip) {
275 const auto& frame = resTrack.info[ip];
276 gbl::Matrix5d err = gbl::Matrix5d::Identity(), jacALICE = gbl::Matrix5d::Identity(), jacGBL;
277 float msErr = 0.f;
278 if (ip) {
279 // numerically calculates the transport jacobian from prev. point to this point
280 // then we actually do the step to the point and accumulate the material
281 if (!getTransportJacobian(wTrk, frame.x, frame.alpha, jacALICE, err) ||
282 !prop->propagateToAlphaX(wTrk, refLin, frame.alpha, frame.x, false, mParams->maxSnp, mParams->maxStep, 1, mParams->corrType, &lt)) {
283 ++cFailedProp;
284 failed = true;
285 break;
286 }
287 msErr = its::math_utils::MSangle(trk.getPID().getMass(), trk.getP(), lt.getX2X0());
288 // after computing jac, reorder to GBL convention
289 for (int i = 0; i < 5; i++) {
290 for (int j = 0; j < 5; j++) {
291 jacGBL(i, j) = jacALICE(perm[i], perm[j]);
292 }
293 }
294 }
295
296 // wTrk is now in the measurment frame
297 gbl::GblPoint point(jacGBL);
298 // measurement
299 Eigen::Vector2d res, prec;
300 res << frame.positionTrackingFrame[0] - wTrk.getY(), frame.positionTrackingFrame[1] - wTrk.getZ();
301
302 // here we can apply some misalignment on the measurment
303 if (!applyMisalignment(res, frame, wTrk, iTrk)) {
304 failed = true;
305 break;
306 }
307
308 prec << 1. / resTrack.points[ip].sig2y, 1. / resTrack.points[ip].sig2z;
309 // the projection matrix is in the tracking frame the idendity so no need to diagonalize it
310 point.addMeasurement(res, prec);
311 if (msErr > mParams->minMS && ip < np - 1) {
312 Eigen::Vector2d scat(0., 0.), scatPrec = Eigen::Vector2d::Constant(1. / (msErr * msErr));
313 point.addScatterer(scat, scatPrec);
314 lt.clearFast(); // clear if accounted
315 }
316
317 if (frame.lr >= 0) {
318 GlobalLabel lbl(0, frame.sens, true);
319 if (mChip2Hiearchy.find(lbl) == mChip2Hiearchy.end()) {
320 LOGP(fatal, "Cannot find global label: {}", lbl.asString());
321 }
322
323 // derivatives for all sensitive volumes and their parents
324 // this is the derivative in TRK but we want to align in LOC
325 // so dr/da_(LOC) = dr/da_(TRK) * da_(TRK)/da_(LOC)
326 const auto* tileVol = mChip2Hiearchy.at(lbl);
327 const auto derCtx = makeDerivativeContext(frame, wTrk);
328 Matrix36 der = getRigidBodyBaseDerivatives(derCtx);
329
330 // count rigid body columns: only volumes with real DOFs (not DOFPseudo)
331 int nColRB{0};
332 for (const auto* v = tileVol; v && !v->isRoot(); v = v->getParent()) {
333 if (v->getRigidBody()) {
334 nColRB += v->getRigidBody()->nDOFs();
335 }
336 }
337
338 // count calibration columns
339 const auto* sensorVol = tileVol->getParent();
340 const auto* calibSet = sensorVol ? sensorVol->getCalib() : nullptr;
341 const int nCalib = calibSet ? calibSet->nDOFs() : 0;
342 const int nCol = nColRB + nCalib;
343
344 std::vector<int> gLabels;
345 gLabels.reserve(nCol);
346 Eigen::MatrixXd gDer(3, nCol);
347 gDer.setZero();
348 Eigen::Index curCol{0};
349
350 // 1) tile: TRK -> LOC via precomputed T2L and J_L2T
351 const double posTrk[3] = {frame.x, 0., 0.};
352 double posLoc[3];
353 tileVol->getT2L().LocalToMaster(posTrk, posLoc);
354 Matrix66 jacL2T;
355 tileVol->computeJacobianL2T(posLoc, jacL2T);
356 der *= jacL2T;
357 if (tileVol->getRigidBody()) {
358 const int nd = tileVol->getRigidBody()->nDOFs();
359 for (int iDOF = 0; iDOF < nd; ++iDOF) {
360 gLabels.push_back(tileVol->getLabel().rawGBL(iDOF));
361 }
362 gDer.middleCols(curCol, nd) = der;
363 curCol += nd;
364 }
365
366 // 2) chain through parents: child's J_L2P
367 for (const auto* child = tileVol; child->getParent() && !child->getParent()->isRoot(); child = child->getParent()) {
368 der *= child->getJL2P();
369 const auto* parent = child->getParent();
370 if (parent->getRigidBody()) {
371 const int nd = parent->getRigidBody()->nDOFs();
372 for (int iDOF = 0; iDOF < nd; ++iDOF) {
373 gLabels.push_back(parent->getLabel().rawGBL(iDOF));
374 }
375 gDer.middleCols(curCol, nd) = der;
376 curCol += nd;
377 }
378 }
379
380 // 3) calibration derivatives (apply directly on the whole sensor, not on individual tiles)
381 if (calibSet) {
382 const int nd = calibSet->nDOFs();
383 Eigen::MatrixXd calDer(3, nd);
384 calibSet->fillDerivatives(derCtx, calDer);
385 for (int iDOF = 0; iDOF < nd; ++iDOF) {
386 gLabels.push_back(sensorVol->getLabel().asCalib().rawGBL(iDOF));
387 }
388 gDer.middleCols(curCol, nd) = calDer;
389 curCol += nd;
390 }
391 point.addGlobals(gLabels, gDer);
392 }
393
394 if (mOutOpt[OutputOpt::VerboseGBL]) {
395 static Eigen::IOFormat fmt(4, 0, ", ", "\n", "[", "]");
396 LOGP(info, "WORKING-POINT {}", ip);
397 LOGP(info, "Track: {}", wTrk.asString());
398 LOGP(info, "FrameInfo: {}", frame.asString());
399 std::cout << "jacALICE:\n"
400 << jacALICE.format(fmt) << '\n';
401 std::cout << "jacGBL:\n"
402 << jacGBL.format(fmt) << '\n';
403 LOGP(info, "Point {}: GBL res=({}, {}), KF stored res=({}, {})",
404 ip, res[0], res[1], resTrack.points[ip].dy, resTrack.points[ip].dz);
405 LOGP(info, "residual: dy={} dz={}", res[0], res[1]);
406 LOGP(info, "precision: precY={} precZ={}", prec[0], prec[1]);
407 point.printPoint(5);
408 }
409 points.push_back(point);
410 }
411 if (!failed) {
412 gbl::GblTrajectory traj(points, std::abs(bz) > 0.01);
413 if (traj.isValid()) {
414 double chi2 = NAN, lostWeight = NAN;
415 int ndf = 0;
416 if (auto ierr = traj.fit(chi2, ndf, lostWeight); !ierr) {
417 if (mOutOpt[OutputOpt::VerboseGBL]) {
418 LOGP(info, "GBL FIT chi2 {} ndf {}", chi2, ndf);
419 traj.printTrajectory(5);
420 }
421 if (chi2 / ndf > mParams->maxChi2Ndf && cGBLChi2Rej++ < 10) {
422 LOGP(error, "GBL fit exceeded red chi2 {}", chi2 / ndf);
423 if (std::abs(resTrack.kfFit.chi2Ndf - 1) < 0.02) {
424 LOGP(error, "\tGBL is far away from good KF fit!!!!");
425 continue;
426 }
427 } else {
428 ++cGBLFit;
429 chi2Sum += chi2;
430 lostWeightSum += lostWeight;
431 ndfSum += ndf;
432 if (mOutOpt[OutputOpt::MilleData]) {
433 gblTrajSlot.push_back(traj);
434 }
435 FitInfo fit;
436 fit.ndf = ndf;
437 fit.chi2 = (float)chi2;
438 fit.chi2Ndf = (float)chi2 / (float)ndf;
439 resTrack.gblFit = fit;
440 }
441 } else {
442 ++cGBLFitFail;
443 }
444 } else {
445 ++cGBLConstruct;
446 }
447 }
448 }
449 }
450 auto timeEnd = std::chrono::high_resolution_clock::now();
451 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(timeEnd - timeStart);
452 LOGP(info, "Fitted {} tracks out of {} (selected {}) in {} sec", cGBLFit, itsTracks.size(), cSelected, duration.count() / 1e3);
453 LOGP(info, "\tRefit failed for {} tracks; Failed prop for {} tracks", cFailedRefit, cFailedProp);
454 LOGP(info, "\tGBL SUMMARY:");
455 LOGP(info, "\t\tGBL construction failed {}", cGBLConstruct);
456 LOGP(info, "\t\tGBL fit failed {}", cGBLFitFail);
457 LOGP(info, "\t\tGBL chi2Ndf rejected {}", cGBLChi2Rej);
458 if (!ndfSum) {
459 LOGP(info, "\t\tGBL Chi2/Ndf = NDF IS 0");
460 } else {
461 LOGP(info, "\t\tGBL Chi2/Ndf = {}", chi2Sum / ndfSum);
462 }
463 LOGP(info, "\t\tGBL LostWeight = {}", lostWeightSum);
464 LOGP(info, "Streaming results to output");
465 if (mOutOpt[OutputOpt::MilleData]) {
466 gbl::MilleBinary mille(mParams->milleBinFile, true);
467 for (auto& slot : gblTrajSlots) {
468 for (auto& traj : slot) {
469 traj.milleOut(mille);
470 }
471 }
472 }
473 if (mOutOpt[OutputOpt::Debug]) {
474 for (auto& slot : resTrackSlots) {
475 for (auto& res : slot) {
476 (*mDBGOut) << "res"
477 << "trk=" << res
478 << "\n";
479 }
480 }
481 }
482}
483
484void AlignmentSpec::updateTimeDependentParams(ProcessingContext& pc)
485{
487 if (static bool initOnce{false}; !initOnce) {
488 initOnce = true;
491 mParams = &AlignmentParams::Instance();
492 mParams->printKeyValues(true, true);
493
494 buildHierarchy();
495
496 if (mParams->doMisalignmentLeg || mParams->doMisalignmentRB || mParams->doMisalignmentInex) {
497 mMisalignment = {};
498 for (auto& rb : mRigidBodyParams) {
499 rb.setZero();
500 }
501 if (!mParams->misAlgJson.empty()) {
502 mMisalignment = loadMisalignmentModel(mParams->misAlgJson);
503 if (mParams->doMisalignmentRB) {
504 using json = nlohmann::json;
505 std::ifstream f(mParams->misAlgJson);
506 auto data = json::parse(f);
507 for (const auto& item : data) {
508 int id = item["id"].get<int>();
509 if (!item.contains("rigidBody")) {
510 continue;
511 }
512 auto rb = item["rigidBody"].get<std::vector<double>>();
513 for (int k = 0; k < 6 && k < static_cast<int>(rb.size()); ++k) {
514 mRigidBodyParams[id](k) = rb[k];
515 }
516 }
517 }
518 }
519 }
520 }
521}
522
523void AlignmentSpec::buildHierarchy()
524{
525 if (mIsITS3) {
526 mHierarchy = buildHierarchyIT3(mChip2Hiearchy);
527 } else {
528 mHierarchy = buildHierarchyITS(mChip2Hiearchy);
529 }
530
531 if (!mParams->dofConfigJson.empty()) {
532 applyDOFConfig(mHierarchy.get(), mParams->dofConfigJson);
533 }
534
535 mHierarchy->finalise();
536 if (mOutOpt[OutputOpt::MilleSteer]) {
537 std::ofstream tree(mParams->milleTreeFile);
538 mHierarchy->writeTree(tree);
539 std::ofstream cons(mParams->milleConFile);
540 mHierarchy->writeRigidBodyConstraints(cons);
541 std::ofstream par(mParams->milleParamFile);
542 mHierarchy->writeParameters(par);
543 }
544}
545
546bool AlignmentSpec::getTransportJacobian(const TrackD& track, double xTo, double alphaTo, gbl::Matrix5d& jac, gbl::Matrix5d& err)
547{
549 const auto bz = prop->getNominalBz();
550 const auto minStep = std::sqrt(std::numeric_limits<double>::epsilon());
551 const gbl::Vector5d x0(track.getParams());
552 auto trackC = track;
553 o2::track::TrackParD* refLin{nullptr};
554 if (mParams->useStableRef) {
555 refLin = &trackC;
556 }
557
558 auto propagate = [&](gbl::Vector5d& p) -> bool {
559 TrackD tmp(track);
560 for (int i{0}; i < track::kNParams; ++i) {
561 tmp.setParam(p[i], i);
562 }
563 if (!prop->propagateToAlphaX(tmp, refLin, alphaTo, xTo, false, mParams->maxSnp, mParams->maxStep, 1, mParams->corrType)) {
564 return false;
565 }
566 p = gbl::Vector5d(tmp.getParams());
567 return true;
568 };
569
570 for (int iPar{0}; iPar < track::kNParams; ++iPar) {
571 // step size
572 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));
573 ;
574 // romberg tableu
575 Eigen::MatrixXd cur(track::kNParams, mParams->ridderMaxExtrap);
576 Eigen::MatrixXd pre(track::kNParams, mParams->ridderMaxExtrap);
577 double normErr = std::numeric_limits<double>::max();
578 gbl::Vector5d bestDeriv = gbl::Vector5d::Constant(std::numeric_limits<double>::max());
579 for (int iExt{0}; iExt < mParams->ridderMaxExtrap; ++iExt) {
580 gbl::Vector5d xPlus = x0, xMinus = x0;
581 xPlus(iPar) += h;
582 xMinus(iPar) -= h;
583 if (!propagate(xPlus) || !propagate(xMinus)) {
584 return false;
585 }
586 cur.col(0) = (xPlus - xMinus) / (2.0 * h);
587 if (!iExt) {
588 bestDeriv = cur.col(0);
589 }
590 // shrink step in next iteration
591 h /= mParams->ridderShrinkFac;
592 // richardson extrapolation
593 double fac = mParams->ridderShrinkFac * mParams->ridderShrinkFac;
594 for (int k{1}; k <= iExt; ++k) {
595 cur.col(k) = (fac * cur.col(k - 1) - pre.col(k - 1)) / (fac - 1.0);
596 fac *= mParams->ridderShrinkFac * mParams->ridderShrinkFac;
597 double e = std::max((cur.col(k) - cur.col(k - 1)).norm(), (cur.col(k) - pre.col(k - 1)).norm());
598 if (e <= normErr) {
599 normErr = e;
600 bestDeriv = cur.col(k);
601 if (normErr < mParams->ridderEps) {
602 break;
603 }
604 }
605 }
606 if (normErr < mParams->ridderEps) {
607 break;
608 }
609 // check stability
610 if (iExt > 0) {
611 double tableauErr = (cur.col(iExt) - pre.col(iExt - 1)).norm();
612 if (tableauErr >= 2.0 * normErr) {
613 break;
614 }
615 }
616 std::swap(cur, pre);
617 }
618 if (bestDeriv.isApproxToConstant(std::numeric_limits<double>::max())) {
619 return false;
620 }
621 jac.col(iPar) = bestDeriv;
622 err.col(iPar) = gbl::Vector5d::Constant(normErr);
623 }
624
625 if (jac.isIdentity(1e-8)) {
626 LOGP(error, "Near jacobian idendity for taking track from {} to {}", track.getX(), xTo);
627 return false;
628 }
629
630 return true;
631}
632
633bool AlignmentSpec::prepareITSTrack(int iTrk, const o2::its::TrackITS& itsTrack, align::Track& resTrack)
634{
635 const auto itsClRefs = mRecoData->getITSTracksClusterRefs();
636 auto trFit = convertTrack<double>(itsTrack.getParamOut()); // take outer track fit as start of refit
639 const auto bz = prop->getNominalBz();
640 std::array<const FrameInfoExt*, 8> frameArr{};
641 o2::track::TrackParD trkOut, *refLin = nullptr;
642 if (mParams->useStableRef) {
643 refLin = &(trkOut = trFit);
644 }
645
646 auto accountCluster = [&](int i, TrackD& tr, float& chi2, Measurement& meas, o2::track::TrackParD* refLin) {
647 if (frameArr[i]) { // update with cluster
648 if (!prop->propagateToAlphaX(tr, refLin, frameArr[i]->alpha, frameArr[i]->x, false, mParams->maxSnp, mParams->maxStep, 1, mParams->corrType)) {
649 return 2;
650 }
651 meas.dy = frameArr[i]->positionTrackingFrame[0] - tr.getY();
652 meas.dz = frameArr[i]->positionTrackingFrame[1] - tr.getZ();
653 meas.sig2y = frameArr[i]->covarianceTrackingFrame[0];
654 meas.sig2z = frameArr[i]->covarianceTrackingFrame[2];
655 meas.z = tr.getZ();
656 meas.phi = tr.getPhi();
657 o2::math_utils::bringTo02Pid(meas.phi);
658 chi2 += (float)tr.getPredictedChi2Quiet(frameArr[i]->positionTrackingFrame, frameArr[i]->covarianceTrackingFrame);
659 if (!tr.update(frameArr[i]->positionTrackingFrame, frameArr[i]->covarianceTrackingFrame)) {
660 return 2;
661 }
662 if (refLin) { // displace the reference to the last updated cluster
663 refLin->setY(frameArr[i]->positionTrackingFrame[0]);
664 refLin->setZ(frameArr[i]->positionTrackingFrame[1]);
665 }
666 return 0;
667 }
668 return 1;
669 };
670
671 FrameInfoExt pvInfo;
672 if (mWithPV) { // add PV as constraint
673 const int iPV = mT2PV[iTrk];
674 if (iPV < 0) {
675 return false;
676 }
677 const auto& pv = mPVs[iPV];
678 auto tmp = convertTrack<double>(itsTrack.getParamIn());
679 if (!prop->propagateToDCA(pv, tmp, bz)) {
680 return false;
681 }
682 pvInfo.alpha = (float)tmp.getAlpha();
683 double ca{0}, sa{0};
684 o2::math_utils::bringToPMPid(pvInfo.alpha);
685 o2::math_utils::sincosd(pvInfo.alpha, sa, ca);
686 pvInfo.x = tmp.getX();
687 pvInfo.positionTrackingFrame[0] = -pv.getX() * sa + pv.getY() * ca;
688 pvInfo.positionTrackingFrame[1] = pv.getZ();
689 pvInfo.covarianceTrackingFrame[0] = 0.5 * (pv.getSigmaX2() + pv.getSigmaY2());
690 pvInfo.covarianceTrackingFrame[2] = pv.getSigmaY2();
691 pvInfo.sens = -1;
692 pvInfo.lr = -1;
693 frameArr[0] = &pvInfo;
694 }
695
696 // collect all track clusters to array, placing them to layer+1 slot
697 int nCl = itsTrack.getNClusters();
698 for (int i = 0; i < nCl; i++) { // clusters are ordered from the outermost to the innermost
699 const auto& curInfo = mITSTrackingInfo[itsClRefs[itsTrack.getClusterEntry(i)]];
700 frameArr[1 + curInfo.lr] = &curInfo;
701 }
702
703 // start refit
704 resTrack.points.clear();
705 resTrack.info.clear();
706 trFit.resetCovariance();
707 trFit.setCov(trFit.getQ2Pt() * trFit.getQ2Pt() * trFit.getCov()[14], 14);
708 float chi2{0};
709 for (int i{7}; i >= 0; --i) {
710 Measurement point;
711 int res = accountCluster(i, trFit, chi2, point, refLin);
712 if (res == 2) {
713 return false;
714 } else if (res == 0) {
715 resTrack.points.push_back(point);
716 resTrack.info.push_back(*frameArr[i]);
717 resTrack.track = trFit; // put track to whatever the IU is
718 }
719 }
720 // reverse inserted points so they are in the same order as the track
721 std::reverse(resTrack.info.begin(), resTrack.info.end());
722 std::reverse(resTrack.points.begin(), resTrack.points.end());
723 resTrack.kfFit.chi2 = chi2;
724 resTrack.kfFit.ndf = (int)resTrack.info.size() * 2 - 5;
725 resTrack.kfFit.chi2Ndf = chi2 / (float)resTrack.kfFit.ndf;
726
727 return true;
728}
729
730void AlignmentSpec::prepareMeasurments(std::span<const itsmft::CompClusterExt> clusters, std::span<const unsigned char> patterns)
731{
732 LOGP(info, "Preparing {} measurments", clusters.size());
733 auto geom = its::GeometryTGeo::Instance();
734 geom->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L, o2::math_utils::TransformType::L2G));
735 mITSTrackingInfo.clear();
736 mITSTrackingInfo.reserve(clusters.size());
737 auto pattIt = patterns.begin();
738 for (const auto& cls : clusters) {
739 const auto sens = cls.getSensorID();
740 const auto lay = geom->getLayer(sens);
741 double sigmaY2{0}, sigmaZ2{0};
743 if (mIsITS3) {
744 locXYZ = o2::its3::ioutils::extractClusterData(cls, pattIt, mIT3Dict, sigmaY2, sigmaZ2);
745 } else {
746 locXYZ = o2::its::ioutils::extractClusterData(cls, pattIt, mITSDict, sigmaY2, sigmaZ2);
747 }
748 sigmaY2 += mParams->extraClsErrY[lay] * mParams->extraClsErrY[lay];
749 sigmaZ2 += mParams->extraClsErrZ[lay] * mParams->extraClsErrZ[lay];
750 // Transformation to the local --> global
751 const auto gloXYZ = geom->getMatrixL2G(sens) * locXYZ;
752 // Inverse transformation to the local --> tracking
753 auto trkXYZf = geom->getMatrixT2L(sens) ^ locXYZ;
755 trkXYZ.SetCoordinates(trkXYZf.X(), trkXYZf.Y(), trkXYZf.Z());
756 // Tracking alpha angle
757 // We want that each cluster rotates its tracking frame to the clusters phi
758 // that way the track linearization around the measurement is less biases to the arc
759 // this means automatically that the measurement on the arc is at 0 for the curved layers
760 double alpha = geom->getSensorRefAlpha(sens);
761 double x = trkXYZ.x();
762 if (mIsITS3 && constants::detID::isDetITS3(sens)) {
763 trkXYZ.SetY(0.f);
764 // alpha&x always have to be defined wrt to the global Z axis!
765 x = std::hypot(gloXYZ.x(), gloXYZ.y());
766 trkXYZ.SetX(x);
767 alpha = std::atan2(gloXYZ.y(), gloXYZ.x());
768 auto chip = constants::detID::getSensorID(sens);
769 sigmaY2 += mParams->extraClsErrY[chip] * mParams->extraClsErrY[chip];
770 sigmaZ2 += mParams->extraClsErrZ[chip] * mParams->extraClsErrZ[chip];
771 }
773 mITSTrackingInfo.emplace_back(sens, lay, x, alpha,
774 std::array<double, 2>{trkXYZ.y(), trkXYZ.z()},
775 std::array<double, 3>{sigmaY2, 0., sigmaZ2});
776 }
777}
778
779void AlignmentSpec::buildT2V()
780{
781 const auto& itsTracks = mRecoData->getITSTracks();
782 mT2PV.clear();
783 mT2PV.resize(itsTracks.size(), -1);
784 if (mUseMC) {
785 mPVs.reserve(mcReader->getNEvents(0));
786 for (int iEve{0}; iEve < mcReader->getNEvents(0); ++iEve) {
787 const auto& eve = mcReader->getMCEventHeader(0, iEve);
789 constexpr float err{22e-4f};
790 vtx.setX((float)eve.GetX());
791 vtx.setY((float)eve.GetY());
792 vtx.setZ((float)eve.GetZ());
793 vtx.setSigmaX(err);
794 vtx.setSigmaY(err);
795 vtx.setSigmaZ(err);
796 mPVs.push_back(vtx);
797 }
798 const auto& mcLbls = mRecoData->getITSTracksMCLabels();
799 for (size_t iTrk{0}; iTrk < mcLbls.size(); ++iTrk) {
800 const auto& lbl = mcLbls[iTrk];
801 if (!lbl.isValid() || !lbl.isCorrect()) {
802 continue;
803 }
804 const auto& mcTrk = mcReader->getTrack(lbl);
805 if (mcTrk->isPrimary()) {
806 mT2PV[iTrk] = lbl.getEventID();
807 }
808 }
809 } else {
810 LOGP(fatal, "Data PV to track TODO");
811 }
812}
813
814bool AlignmentSpec::applyMisalignment(Eigen::Vector2d& res, const FrameInfoExt& frame, const TrackD& wTrk, size_t iTrk)
815{
816 if (!constants::detID::isDetITS3(frame.sens)) {
817 return true;
818 }
819
820 const int sensorID = constants::detID::getSensorID(frame.sens);
821 const int layerID = constants::detID::getDetID2Layer(frame.sens);
822 const MisalignmentFrame misFrame{
823 .sensorID = sensorID,
824 .layerID = layerID,
825 .x = frame.x,
826 .alpha = frame.alpha,
827 .z = frame.positionTrackingFrame[1]};
828
829 // --- Legendre deformation (non-rigid-body) ---
830 if (mParams->doMisalignmentLeg && mIsITS3 && mUseMC) {
831 const auto prop = o2::base::PropagatorD::Instance();
832
833 const auto lbl = mRecoData->getITSTracksMCLabels()[iTrk];
834 const auto mcTrk = mcReader->getTrack(lbl);
835 if (!mcTrk) {
836 return false;
837 }
838 std::array<double, 3> xyz{mcTrk->GetStartVertexCoordinatesX(), mcTrk->GetStartVertexCoordinatesY(), mcTrk->GetStartVertexCoordinatesZ()};
839 std::array<double, 3> pxyz{mcTrk->GetStartVertexMomentumX(), mcTrk->GetStartVertexMomentumY(), mcTrk->GetStartVertexMomentumZ()};
840 TParticlePDG* pPDG = TDatabasePDG::Instance()->GetParticle(mcTrk->GetPdgCode());
841 if (!pPDG) {
842 return false;
843 }
844 o2::track::TrackParD mcPar(xyz, pxyz, TMath::Nint(pPDG->Charge() / 3), false);
845
846 auto mcAtCl = mcPar;
847 if (!mcAtCl.rotate(frame.alpha) || !prop->PropagateToXBxByBz(mcAtCl, frame.x)) {
848 return false;
849 }
850
851 const auto shift = evaluateLegendreShift(mMisalignment[sensorID], misFrame, computeTrackSlopes(mcAtCl.getSnp(), mcAtCl.getTgl()));
852 if (!shift.accepted) {
853 return false;
854 }
855
856 res[0] += shift.dy;
857 res[1] += shift.dz;
858 }
859
860 // --- Rigid body misalignment ---
861 // Must use the same derivative chain as GBL:
862 // dres/da_parent = dres/da_TRK * J_L2T_tile * J_L2P_tile
863 // The tile is a pseudo-volume; Millepede fits at the halfBarrel (parent) level.
864 if (mParams->doMisalignmentRB) {
865 GlobalLabel lbl(0, frame.sens, true);
866 if (mChip2Hiearchy.find(lbl) == mChip2Hiearchy.end()) {
867 return true; // sensor not in hierarchy, skip
868 }
869 const auto* tileVol = mChip2Hiearchy.at(lbl);
870
871 // derivative in TRK frame (3x6: rows = dy, dz, dsnp)
872 Matrix36 der = getRigidBodyBaseDerivatives(makeDerivativeContext(frame, wTrk));
873
874 // TRK -> tile LOC
875 const double posTrk[3] = {frame.x, 0., 0.};
876 double posLoc[3];
877 tileVol->getT2L().LocalToMaster(posTrk, posLoc);
878 Matrix66 jacL2T;
879 tileVol->computeJacobianL2T(posLoc, jacL2T);
880 der *= jacL2T;
881
882 // tile LOC -> halfBarrel LOC (same chain as GBL hierarchy walk)
883 der *= tileVol->getJL2P();
884
885 // apply: delta_res = der * delta_a_halfBarrel
886 Eigen::Vector3d shift = der * mRigidBodyParams[sensorID];
887 res[0] += shift[0]; // dy
888 res[1] += shift[1]; // dz
889 }
890
891 // --- In-extensional deformation ---
892 // displacement field u(phi,z) = (u_phi, u_z, u_r)
893 // dy = -u_phi + y' * u_r, dz = -u_z + z' * u_r
894 if (mParams->doMisalignmentInex) {
895 const auto shift = evaluateInextensionalShift(mMisalignment[sensorID], misFrame, computeTrackSlopes(wTrk.getSnp(), wTrk.getTgl()));
896 res[0] += shift.dy;
897 res[1] += shift.dz;
898 }
899
900 if (mOutOpt[OutputOpt::MisRes]) {
901 (*mDBGOut) << "mis"
902 << "dy=" << res[0]
903 << "dz=" << res[1]
904 << "sens=" << sensorID
905 << "lay=" << layerID
906 << "z=" << frame.positionTrackingFrame[1]
907 << "phi=" << frame.alpha
908 << "\n";
909 }
910
911 return true;
912}
913
915{
916 mDBGOut->Close();
917 mDBGOut.reset();
918}
919
921{
923 return;
924 }
925 if (matcher == ConcreteDataMatcher("ITS", "CLUSDICT", 0)) {
926 LOG(info) << "its cluster dictionary updated";
927 mITSDict = (const o2::itsmft::TopologyDictionary*)obj;
928 return;
929 }
930 if (matcher == ConcreteDataMatcher("IT3", "CLUSDICT", 0)) {
931 LOG(info) << "it3 cluster dictionary updated";
932 mIT3Dict = (const o2::its3::TopologyDictionary*)obj;
933 return;
934 }
935}
936
937DataProcessorSpec getAlignmentSpec(GTrackID::mask_t srcTracks, GTrackID::mask_t srcClusters, bool useMC, bool withPV, bool withITS, OutputEnum out)
938{
939 auto dataRequest = std::make_shared<DataRequest>();
940 std::shared_ptr<o2::base::GRPGeomRequest> ggRequest{nullptr};
941 if (!out[OutputOpt::MilleRes]) {
942 dataRequest->requestTracks(srcTracks, useMC);
943 if (!withITS) {
944 dataRequest->requestIT3Clusters(useMC);
945 } else {
946 dataRequest->requestClusters(srcClusters, useMC);
947 }
948 if (withPV && !useMC) {
949 dataRequest->requestPrimaryVertices(useMC);
950 }
951 ggRequest = std::make_shared<o2::base::GRPGeomRequest>(false, // orbitResetTime
952 false, // GRPECS=true
953 true, // GRPLHCIF
954 true, // GRPMagField
955 true, // askMatLUT
957 dataRequest->inputs, // inputs
958 true, // askOnce
959 true); // propagatorD
960 } else {
961 dataRequest->inputs.emplace_back("dummy", "GLO", "DUMMY_OUT", 0);
962 ggRequest = std::make_shared<o2::base::GRPGeomRequest>(false, // orbitResetTime
963 false, // GRPECS=true
964 false, // GRPLHCIF
965 false, // GRPMagField
966 false, // askMatLUT
968 dataRequest->inputs);
969 }
970
971 Options opts{
972 {"nthreads", VariantType::Int, 1, {"number of threads"}},
973 };
974
975 return DataProcessorSpec{
976 .name = "its3-alignment",
977 .inputs = dataRequest->inputs,
978 .outputs = {},
979 .algorithm = AlgorithmSpec{adaptFromTask<AlignmentSpec>(dataRequest, ggRequest, srcTracks, useMC, withPV, withITS, out)},
980 .options = opts};
981}
982} // 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:510
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
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()))