Skip to content

Commit 1a4325e

Browse files
committed
ITS3: fix inextensional model + opt. radial
Signed-off-by: Felix Schlepper <felix.schlepper@cern.ch>
1 parent a3e5117 commit 1a4325e

11 files changed

Lines changed: 429 additions & 246 deletions

File tree

Detectors/Upgrades/ITS3/alignment/README.md

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,23 @@ dofSet.json:
3131
```
3232

3333

34-
## In-existensional modes
34+
## In-extensional modes
35+
36+
The deformation of the open half-shell is parameterised by two 1D functions expanded in Legendre polynomials of the
37+
normalised azimuth `u` (the same coordinate as the radial Legendre model):
38+
39+
```
40+
f(phi) = sum_k f_k P_k(u), g(phi) = sum_k g_k P_k(u)
41+
u_z = f, u_phi = -(z/r) f' + g, u_r = (z/r) f'' - g'
42+
```
43+
44+
`order` sets the maximum `k`. Optionally, strictly radial ("extensional") modes can be added on top, `u_r += sum_{k,l}
45+
h_{k,l} P_k(u) P_l(v)` with `l >= 1`, enabled via `extOrderPhi` (max `k`) and `extOrderZ` (max `l`); `l = 0` is excluded
46+
because a z-independent radial field is already spanned by the `g` family.
47+
48+
Note that `f_0` (translation along the cylinder axis) and `g_0` (rotation about it) are rigid-body motions and are fixed
49+
by default; free them only if the rigid-body DOFs of the same volume are not fitted.
50+
3551
```json
3652
{
3753
"defaults": { "rigidBody": "fixed" },
@@ -40,24 +56,26 @@ dofSet.json:
4056
"match": "ITS3Layer1/ITS3CarbonForm0",
4157
"calib": {
4258
"type": "inextensional",
43-
"order": 2,
44-
"free": ["a_2", "b_2", "c_2", "d_2", "alpha", "beta"]
59+
"order": 10,
60+
"extOrderPhi": 7,
61+
"extOrderZ": 8,
62+
"fix": ["f_0", "g_0"]
4563
}
4664
}
4765
]
4866
}
4967
```
5068

69+
Injected/fitted coefficients (`h` keys are `"<k>_<l>"`):
70+
5171
```json
5272
[
5373
{
5474
"id": 2,
5575
"inextensional": {
56-
"modes": {
57-
"2": [0.0008, -0.0005, 0.0006, -0.0007]
58-
},
59-
"alpha": 0.0004,
60-
"beta": -0.0003
76+
"f": { "1": 0.0001, "2": -0.0002 },
77+
"g": { "1": 0.0625, "3": 0.0335, "5": -0.0453 },
78+
"h": { "4_2": -0.0421, "6_2": 0.0252, "4_4": 0.0435 }
6179
}
6280
}
6381
]

Detectors/Upgrades/ITS3/alignment/include/ITS3Align/AlignmentDOF.h

Lines changed: 55 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
#include <Eigen/Dense>
2323

24+
#include "ITS3Align/AlignmentLabel.h"
25+
2426
struct DerivativeContext {
2527
int sensorID{-1};
2628
int layerID{-1};
@@ -62,7 +64,7 @@ class DOFSet
6264
}
6365

6466
protected:
65-
DOFSet(int n) : mFree(n, true) {}
67+
DOFSet(int n) : mFree(n, true) { GlobalLabel::checkDOFCount(n); }
6668
std::vector<bool> mFree;
6769
};
6870

@@ -126,51 +128,77 @@ class LegendreDOFSet final : public DOFSet
126128
int mOrder;
127129
};
128130

129-
// In-extensional deformation DOFs for cylindrical half-shells
130-
// Fourier modes n=2..N: 4 params each (a_n, b_n, c_n, d_n)
131-
// Plus 2 non-periodic modes (alpha, beta) for the half-cylinder open edges
132-
// Total: 4*(N-1) + 2
131+
// Deformation DOFs for an open cylindrical half-shell.
132+
//
133+
// Inextensional part. Vanishing linear membrane strains admit the general solution (u in the local (r, phi, z)
134+
// directions) u_z = f(phi) u_phi = -(z/r) f'(phi) + g(phi) u_r = (z/r) f''(phi) - g'(phi) with two arbitrary
135+
// one-dimensional functions f, g. Because the shell is open in phi these are expanded in Legendre polynomials of the
136+
// normalised azimuth u in [-1, 1]: f(phi) = sum_k f_k P_k(u), g(phi) = sum_k g_k P_k(u).
137+
//
138+
// Extensional part (optional). The inextensional u_r is at most linear in z, so radial deformations with curvature
139+
// along z lie outside it. They are added as strictly radial modes u_r += sum_{k,l} h_{k,l} P_k(u) P_l(v), l >= 1, with
140+
// v the normalised axial coordinate. l = 0 is excluded because a z-independent radial field is already spanned by the g
141+
// family.
142+
//
143+
// Flat index layout: [f_0, g_0, f_1, g_1, ..., f_K, g_K, h_{0,1} ... h_{0,Lz}, h_{1,1} ... h_{Kphi,Lz}]
144+
//
145+
// NOTE on degeneracies: f_0 is a rigid translation along the cylinder axis and g_0 a rigid rotation about it, i.e. they
146+
// duplicate rigid-body DOFs of the same volume.
133147
class InextensionalDOFSet final : public DOFSet
134148
{
135149
public:
136-
explicit InextensionalDOFSet(int maxOrder) : DOFSet((4 * (maxOrder - 1)) + 2), mMaxOrder(maxOrder)
150+
explicit InextensionalDOFSet(int maxOrder, int extOrderPhi = -1, int extOrderZ = 0)
151+
: DOFSet(nDOFsFor(maxOrder, extOrderPhi, extOrderZ)),
152+
mMaxOrder(maxOrder),
153+
mExtOrderPhi(extOrderZ > 0 ? extOrderPhi : -1),
154+
mExtOrderZ(extOrderPhi >= 0 ? extOrderZ : 0)
137155
{
138-
if (maxOrder < 2) {
139-
// the rest is eq. to rigid body
140-
throw std::invalid_argument("InextensionalDOFSet requires maxOrder >= 2");
156+
if (maxOrder < 1) {
157+
// only k = 0 is left, which is equivalent to a rigid body motion
158+
throw std::invalid_argument("InextensionalDOFSet requires maxOrder >= 1");
141159
}
160+
// f_0 / g_0 are rigid: fixed unless explicitly freed
161+
setFree(fIdx(0), false);
162+
setFree(gIdx(0), false);
142163
}
164+
165+
static int nDOFsFor(int maxOrder, int extOrderPhi, int extOrderZ)
166+
{
167+
int n = 2 * (maxOrder + 1);
168+
if (extOrderPhi >= 0 && extOrderZ > 0) {
169+
n += (extOrderPhi + 1) * extOrderZ;
170+
}
171+
return n;
172+
}
173+
143174
Type type() const override { return Type::Inextensional; }
144175
int maxOrder() const { return mMaxOrder; }
176+
int extOrderPhi() const { return mExtOrderPhi; }
177+
int extOrderZ() const { return mExtOrderZ; }
178+
bool hasExtensional() const { return mExtOrderPhi >= 0 && mExtOrderZ > 0; }
145179

146-
// number of periodic DOFs (before alpha, beta)
147-
int nPeriodic() const { return 4 * (mMaxOrder - 1); }
148-
149-
// flat index layout: [a_2, b_2, c_2, d_2, a_3, b_3, c_3, d_3, ..., alpha, beta]
150-
// index of first DOF for mode n
151-
static int modeOffset(int n) { return 4 * (n - 2); }
180+
// number of inextensional DOFs (before the radial h modes)
181+
int nInextensional() const { return 2 * (mMaxOrder + 1); }
152182

153-
// indices of the non-periodic modes
154-
int alphaIdx() const { return nPeriodic(); }
155-
int betaIdx() const { return nPeriodic() + 1; }
183+
// flat indices
184+
static int fIdx(int k) { return 2 * k; }
185+
static int gIdx(int k) { return (2 * k) + 1; }
186+
int hIdx(int k, int l) const { return nInextensional() + (k * mExtOrderZ) + (l - 1); }
156187

157188
std::string dofName(int idx) const override
158189
{
159-
if (idx == alphaIdx()) {
160-
return "alpha";
161-
}
162-
if (idx == betaIdx()) {
163-
return "beta";
190+
if (idx < nInextensional()) {
191+
return std::format("{}_{}", (idx % 2 == 0) ? "f" : "g", idx / 2);
164192
}
165-
int n = (idx / 4) + 2;
166-
int sub = idx % 4;
167-
static constexpr const char* subNames[] = {"a", "b", "c", "d"};
168-
return std::format("{}_{}", subNames[sub], n);
193+
const int e = idx - nInextensional();
194+
return std::format("h_{}_{}", e / mExtOrderZ, (e % mExtOrderZ) + 1);
169195
}
170196
void fillDerivatives(const DerivativeContext& ctx, Eigen::Ref<Eigen::MatrixXd> out) const override;
171197

172198
private:
173199
int mMaxOrder;
200+
int mExtOrderPhi;
201+
int mExtOrderZ;
174202
};
175203

176204
#endif

Detectors/Upgrades/ITS3/alignment/include/ITS3Align/AlignmentLabel.h

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,19 @@
1313
#define O2_ITS3_ALIGNMENT_LABEL_H
1414

1515
#include <cstdint>
16+
#include <stdexcept>
1617
#include <string>
1718
#include <format>
1819

1920
class GlobalLabel
2021
{
2122
// Millepede label is any positive integer [1....)
22-
// Layout: DOF(5) | CALIB(1) | ID(22) | SENS(1) | DET(2) = 31 usable bits (MSB reserved, GBL uses signed int)
23+
// Layout: DOF(8) | CALIB(1) | ID(19) | SENS(1) | DET(2) = 31 usable bits (MSB reserved, GBL uses signed int)
2324
public:
2425
using T = uint32_t;
25-
static constexpr int DOF_BITS = 5; // bits 0-4
26-
static constexpr int CALIB_BITS = 1; // bit 5: 0 = rigid body, 1 = calibration (only allow for one calibration, could be extended if needed)
27-
static constexpr int ID_BITS = 22; // bits 6-27
26+
static constexpr int DOF_BITS = 8; // bits 0-7
27+
static constexpr int CALIB_BITS = 1; // bit 8: 0 = rigid body, 1 = calibration (only allow for one calibration, could be extended if needed)
28+
static constexpr int ID_BITS = 19; // bits 9-27
2829
static constexpr int SENS_BITS = 1; // bit 28
2930
static constexpr int TOTAL_BITS = sizeof(T) * 8;
3031
static constexpr int DET_BITS = TOTAL_BITS - (DOF_BITS + CALIB_BITS + ID_BITS + SENS_BITS) - 1; // one less bit since GBL uses int!
@@ -48,12 +49,30 @@ class GlobalLabel
4849
static constexpr T DET_MAX = (T(1) << DET_BITS) - T(1);
4950
static constexpr T DET_MASK = DET_MAX << DET_SHIFT;
5051

52+
/// maximum number of DOFs that can be labelled on one volume (per calib bit)
53+
static constexpr int MAX_DOFS = static_cast<int>(DOF_MAX) + 1;
54+
55+
/// throws if a DOF set is too large to be labelled without aliasing
56+
static void checkDOFCount(int nDOFs)
57+
{
58+
if (nDOFs > MAX_DOFS) {
59+
throw std::out_of_range(std::format(
60+
"DOF set with {} parameters exceeds the {} labelable DOFs (DOF_BITS={}); "
61+
"distinct parameters would alias onto the same Millepede label",
62+
nDOFs, MAX_DOFS, DOF_BITS));
63+
}
64+
}
65+
5166
GlobalLabel(T det, T id, bool sens, bool calib = false)
5267
: mID((((id + 1) & ID_MAX) << ID_SHIFT) |
5368
((det & DET_MAX) << DET_SHIFT) |
5469
((T(sens) & SENS_MAX) << SENS_SHIFT) |
5570
((T(calib) & CALIB_MAX) << CALIB_SHIFT))
5671
{
72+
if ((id + 1) > ID_MAX) {
73+
throw std::out_of_range(std::format("Volume id {} exceeds the {} labelable ids (ID_BITS={})",
74+
id, ID_MAX - 1, ID_BITS));
75+
}
5776
}
5877

5978
/// produce the raw Millepede label for a given DOF index (rigid body: calib=0 in label)

Detectors/Upgrades/ITS3/alignment/include/ITS3Align/AlignmentMath.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,18 @@ struct TrackSlopes {
2323
double dzdx{0.};
2424
};
2525

26-
double getSensorPhiWidth(int sensorID, double radius);
2726
std::pair<double, double> computeUV(double gloX, double gloY, double gloZ, int sensorID, double radius);
2827
TrackSlopes computeTrackSlopes(double snp, double tgl);
2928
std::vector<double> legendrePols(int order, double x);
3029

30+
// First and second derivatives dP_n/dx, d^2P_n/dx^2 for n = 0..order.
31+
std::vector<double> legendrePolsD1(int order, double x);
32+
std::vector<double> legendrePolsD2(int order, double x);
33+
34+
// Jacobian factor of the angular normalisation used by computeUV: c_phi = du/dphi = 2 / (phiBorder2 - phiBorder1).
35+
// Needed to convert derivatives with respect to the normalised u back to derivatives with respect to the azimuth phi.
36+
double phiScale(double radius);
37+
3138
} // namespace o2::its3::align
3239

3340
#endif

Detectors/Upgrades/ITS3/alignment/include/ITS3Align/MisalignmentUtils.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,13 @@
2424
namespace o2::its3::align
2525
{
2626

27+
// Legendre parameterisation of the open half-shell deformation, matching InextensionalDOFSet: inextensional
28+
// coefficients f_k, g_k of the normalised azimuth u, plus optional strictly radial modes h_{k,l} (l >= 1) in P_k(u)
29+
// P_l(v). See AlignmentDOF.h for the displacement field.
2730
struct InextensionalMisalignment {
28-
std::map<int, std::array<double, 4>> modes; // n -> (a_n, b_n, c_n, d_n)
29-
double alpha{0.};
30-
double beta{0.};
31+
std::map<int, double> f; // k -> f_k
32+
std::map<int, double> g; // k -> g_k
33+
std::map<std::pair<int, int>, double> h; // (k, l) -> h_{k,l}, l >= 1
3134
};
3235

3336
struct SensorMisalignment {

0 commit comments

Comments
 (0)