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