|
21 | 21 |
|
22 | 22 | #include <Eigen/Dense> |
23 | 23 |
|
| 24 | +#include "ITS3Align/AlignmentLabel.h" |
| 25 | + |
24 | 26 | struct DerivativeContext { |
25 | 27 | int sensorID{-1}; |
26 | 28 | int layerID{-1}; |
@@ -62,7 +64,7 @@ class DOFSet |
62 | 64 | } |
63 | 65 |
|
64 | 66 | protected: |
65 | | - DOFSet(int n) : mFree(n, true) {} |
| 67 | + DOFSet(int n) : mFree(n, true) { GlobalLabel::checkDOFCount(n); } |
66 | 68 | std::vector<bool> mFree; |
67 | 69 | }; |
68 | 70 |
|
@@ -126,51 +128,77 @@ class LegendreDOFSet final : public DOFSet |
126 | 128 | int mOrder; |
127 | 129 | }; |
128 | 130 |
|
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. |
133 | 147 | class InextensionalDOFSet final : public DOFSet |
134 | 148 | { |
135 | 149 | 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) |
137 | 155 | { |
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"); |
141 | 159 | } |
| 160 | + // f_0 / g_0 are rigid: fixed unless explicitly freed |
| 161 | + setFree(fIdx(0), false); |
| 162 | + setFree(gIdx(0), false); |
142 | 163 | } |
| 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 | + |
143 | 174 | Type type() const override { return Type::Inextensional; } |
144 | 175 | 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; } |
145 | 179 |
|
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); } |
152 | 182 |
|
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); } |
156 | 187 |
|
157 | 188 | std::string dofName(int idx) const override |
158 | 189 | { |
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); |
164 | 192 | } |
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); |
169 | 195 | } |
170 | 196 | void fillDerivatives(const DerivativeContext& ctx, Eigen::Ref<Eigen::MatrixXd> out) const override; |
171 | 197 |
|
172 | 198 | private: |
173 | 199 | int mMaxOrder; |
| 200 | + int mExtOrderPhi; |
| 201 | + int mExtOrderZ; |
174 | 202 | }; |
175 | 203 |
|
176 | 204 | #endif |
0 commit comments