Project
Loading...
Searching...
No Matches
MisalignmentUtils.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
13
14#include <algorithm>
15#include <cmath>
16#include <fstream>
17#include <string>
18#include <vector>
19#include <array>
20
21#include <TMatrixD.h>
22#include <nlohmann/json.hpp>
23
24#include "Framework/Logger.h"
25#include "ITS3Base/SpecsV2.h"
26
27namespace o2::its3::align
28{
29
30bool MisalignmentModel::empty() const noexcept
31{
32 return std::all_of(sensors.begin(), sensors.end(), [](const auto& sensor) { return sensor.empty(); });
33}
34
35MisalignmentModel loadMisalignmentModel(const std::string& jsonPath)
36{
38 if (jsonPath.empty()) {
39 return model;
40 }
41
42 std::ifstream f(jsonPath);
43 if (!f.is_open()) {
44 LOGP(fatal, "Cannot open misalignment JSON file: {}", jsonPath);
45 }
46
47 using json = nlohmann::json;
48 const auto data = json::parse(f);
49 for (const auto& item : data) {
50 const int id = item["id"].get<int>();
51 if (id < 0 || id >= static_cast<int>(MisalignmentModel::NSensors)) {
52 LOGP(fatal, "Misalignment sensor id {} out of range [0, {}) in {}", id, MisalignmentModel::NSensors, jsonPath);
53 }
54
55 auto& sensor = model[id];
56 if (item.contains("matrix")) {
57 auto v = item["matrix"].get<std::vector<std::vector<double>>>();
58 if (v.empty()) {
59 LOGP(fatal, "Legendre matrix for sensor {} is empty in {}", id, jsonPath);
60 }
61 TMatrixD m(v.size(), v.back().size());
62 for (std::size_t r{0}; r < v.size(); ++r) {
63 for (std::size_t c{0}; c < v[r].size(); ++c) {
64 m(r, c) = v[r][c];
65 }
66 }
68 sensor.hasLegendre = true;
69 }
70 if (item.contains("inextensional")) {
71 const auto& inex = item["inextensional"];
72 sensor.hasInextensional = true;
73 // {"f": {"1": ..., "2": ...}, "g": {...}, "h": {"2_1": ..., "4_2": ...}}
74 if (inex.contains("f")) {
75 for (const auto& [key, val] : inex["f"].items()) {
76 sensor.inextensional.f[std::stoi(key)] = val.get<double>();
77 }
78 }
79 if (inex.contains("g")) {
80 for (const auto& [key, val] : inex["g"].items()) {
81 sensor.inextensional.g[std::stoi(key)] = val.get<double>();
82 }
83 }
84 if (inex.contains("h")) {
85 for (const auto& [key, val] : inex["h"].items()) {
86 const auto sep = key.find('_');
87 if (sep == std::string::npos) {
88 LOGP(fatal, "Inextensional h key '{}' for sensor {} must be of the form '<k>_<l>' in {}", key, id, jsonPath);
89 }
90 const int k = std::stoi(key.substr(0, sep));
91 const int l = std::stoi(key.substr(sep + 1));
92 if (l < 1) {
93 LOGP(fatal, "Inextensional h key '{}' for sensor {}: l must be >= 1 (l = 0 is spanned by g) in {}", key, id, jsonPath);
94 }
95 sensor.inextensional.h[{k, l}] = val.get<double>();
96 }
97 }
98 // An "inextensional" block that yields no coefficients would silently
99 // produce a zero displacement field, i.e. a misalignment study that
100 // looks identical to the ideal one. Most likely cause: a file still in
101 // the old Fourier schema ("modes"/"alpha"/"beta").
102 const auto& parsed = sensor.inextensional;
103 if (parsed.f.empty() && parsed.g.empty() && parsed.h.empty()) {
104 LOGP(fatal,
105 "Sensor {}: 'inextensional' block in {} contains none of the expected "
106 "keys 'f', 'g', 'h' - no deformation would be applied. Keys present: {}",
107 id, jsonPath, [&inex] { std::string s; for (const auto& [k, v] : inex.items()) { s += (s.empty() ? "" : ", ") + k; } return s; }());
108 }
109 }
110 }
111
112 return model;
113}
114
116{
117 MisalignmentShift shift;
118 if (!sensor.hasLegendre) {
119 return shift;
120 }
121
122 const double gloX = frame.x * std::cos(frame.alpha);
123 const double gloY = frame.x * std::sin(frame.alpha);
124 const double gloZ = frame.z;
125 auto [u, v] = computeUV(gloX, gloY, gloZ, frame.sensorID, constants::radii[frame.layerID]);
126 const double h = sensor.legendre(u, v);
127
128 // this is the shift due to back-projection of the track on the ideal surface
129 shift.dy = slopes.dydx * h;
130 shift.dz = slopes.dzdx * h;
131
132 const double newGloY = gloY + (shift.dy * std::cos(frame.alpha));
133 const double newGloX = gloX - (shift.dy * std::sin(frame.alpha));
134 const double newGloZ = gloZ + shift.dz;
135 auto [uNew, vNew] = computeUV(newGloX, newGloY, newGloZ, frame.sensorID, constants::radii[frame.layerID]);
136 shift.accepted = std::abs(uNew) <= 1. && std::abs(vNew) <= 1.;
137 return shift;
138}
139
141{
142 MisalignmentShift shift;
143 if (!sensor.hasInextensional) {
144 return shift;
145 }
146
147 const double r = constants::radii[frame.layerID];
148 const double gloX = frame.x * std::cos(frame.alpha);
149 const double gloY = frame.x * std::sin(frame.alpha);
150 const auto [u, v] = computeUV(gloX, gloY, frame.z, frame.sensorID, r);
151 const double cPhi = phiScale(r);
152 const double zOverR = frame.z / r;
153 const auto& inex = sensor.inextensional;
154
155 int maxK = 0;
156 for (const auto& [k, val] : inex.f) {
157 maxK = std::max(maxK, k);
158 }
159 for (const auto& [k, val] : inex.g) {
160 maxK = std::max(maxK, k);
161 }
162 int maxKh = 0, maxL = 0;
163 for (const auto& [kl, val] : inex.h) {
164 maxKh = std::max(maxKh, kl.first);
165 maxL = std::max(maxL, kl.second);
166 }
167
168 const auto pu = legendrePols(std::max(maxK, maxKh), u);
169 const auto pu1 = legendrePolsD1(maxK, u);
170 const auto pu2 = legendrePolsD2(maxK, u);
171
172 // u_z = f, u_phi = -(z/r) f' + g, u_r = (z/r) f'' - g'
173 double uz = 0., uphi = 0., ur = 0.;
174 for (const auto& [k, fk] : inex.f) {
175 uz += fk * pu[k];
176 uphi += -zOverR * cPhi * fk * pu1[k];
177 ur += zOverR * cPhi * cPhi * fk * pu2[k];
178 }
179 for (const auto& [k, gk] : inex.g) {
180 uphi += gk * pu[k];
181 ur += -cPhi * gk * pu1[k];
182 }
183 if (!inex.h.empty()) {
184 const auto pv = legendrePols(maxL, v);
185 for (const auto& [kl, hkl] : inex.h) {
186 ur += hkl * pu[kl.first] * pv[kl.second];
187 }
188 }
189
190 shift.dy = -uphi + (slopes.dydx * ur);
191 shift.dz = -uz + (slopes.dzdx * ur);
192 return shift;
193}
194
195} // namespace o2::its3::align
uint32_t c
Definition RawData.h:2
StringRef key
Class for time synchronization of RawReader instances.
const GLfloat * m
Definition glcorearb.h:4066
const GLdouble * v
Definition glcorearb.h:832
GLdouble f
Definition glcorearb.h:310
GLboolean * data
Definition glcorearb.h:298
GLuint GLfloat * val
Definition glcorearb.h:1582
GLboolean r
Definition glcorearb.h:1233
GLuint id
Definition glcorearb.h:650
std::vector< double > legendrePolsD2(int order, double x)
MisalignmentShift evaluateInextensionalShift(const SensorMisalignment &sensor, const MisalignmentFrame &frame, const TrackSlopes &slopes)
MisalignmentModel loadMisalignmentModel(const std::string &jsonPath)
std::vector< double > legendrePolsD1(int order, double x)
MisalignmentShift evaluateLegendreShift(const SensorMisalignment &sensor, const MisalignmentFrame &frame, const TrackSlopes &slopes)
double phiScale(double radius)
std::vector< double > legendrePols(int order, double x)
std::pair< double, double > computeUV(double gloX, double gloY, double gloZ, int sensorID, double radius)
constexpr std::array< double, nLayers > radii
Definition SpecsV2.h:134
nlohmann::json json
static constexpr std::size_t NSensors
std::array< SensorMisalignment, NSensors > sensors
o2::math_utils::Legendre2DPolynominal legendre
InextensionalMisalignment inextensional