@@ -134,28 +134,23 @@ void RpyBallMobilizer<T>::ProjectSpatialForce(
134
134
template <typename T>
135
135
void RpyBallMobilizer<T>::DoCalcNMatrix(const systems::Context<T>& context,
136
136
EigenPtr<MatrixX<T>> N) const {
137
- using std::abs;
138
- using std::cos;
139
- using std::sin;
140
-
141
- // The linear map E_F(q) allows computing v from q̇ as:
142
- // w_FM = E_F(q) * q̇; q̇ = [ṙ, ṗ, ẏ]ᵀ
137
+ // The matrix N(q) relates q̇ to v as q̇ = N(q) * v, where q̇ = [ṙ, ṗ, ẏ]ᵀ and
138
+ // v = w_FM_F = [ω0, ω1, ω2]ᵀ is the mobilizer M frame's angular velocity in
139
+ // the mobilizer F frame, expressed in the F frame.
143
140
//
144
- // Here, following a convention used by many dynamicists, we are calling the
145
- // angles q0, q1, q2 as roll (r), pitch (p) and yaw (y), respectively.
141
+ // ⌈ ṙ ⌉ ⌈ cos(y) / cos(p), sin(y) / cos(p), 0 ⌉ ⌈ ω0 ⌉
142
+ // | ṗ | = | -sin(y), cos(y), 0 | | ω1 |
143
+ // ⌊ ẏ ⌋ ⌊ sin(p) * cos(y) / cos(p), sin(p) * sin(y) / cos(p), 1 ⌋ ⌊ ω2 ⌋
146
144
//
147
- // The linear map from v to q̇ is given by the inverse of E_F(q):
148
- // [ cos(y) / cos(p), sin(y) / cos(p), 0]
149
- // Einv_F = [ -sin(y), cos(y), 0]
150
- // [ sin(p) * cos(y) / cos(p), sin(p) * sin(y) / cos(p), 1]
151
- //
152
- // such that q̇ = Einv_F(q) * w_FM; q̇ = [ṙ, ṗ, ẏ]ᵀ
153
- // See developer notes in MapVelocityToQdot() for further details.
145
+ // Note: N(q) is singular for p = π/2 + kπ, for k = ±1, ±2, ...
146
+ // See related code and comments in MapVelocityToQdot().
154
147
148
+ using std::abs;
149
+ using std::cos;
150
+ using std::sin;
155
151
const Vector3<T> angles = get_angles (context);
156
152
const T cp = cos (angles[1 ]);
157
- // Demand for the computation to be away from a state for which Einv_F is
158
- // singular.
153
+ // Ensure the calculation is not near a state in which N(q) is singular.
159
154
if (abs (cp) < 1.0e-3 ) {
160
155
throw std::runtime_error (fmt::format (
161
156
" The RpyBallMobilizer (implementing a BallRpyJoint) between "
@@ -171,7 +166,6 @@ void RpyBallMobilizer<T>::DoCalcNMatrix(const systems::Context<T>& context,
171
166
const T sy = sin (angles[2 ]);
172
167
const T cy = cos (angles[2 ]);
173
168
const T cpi = 1.0 / cp;
174
-
175
169
const T cy_x_cpi = cy * cpi;
176
170
const T sy_x_cpi = sy * cpi;
177
171
@@ -188,21 +182,16 @@ void RpyBallMobilizer<T>::DoCalcNMatrix(const systems::Context<T>& context,
188
182
template <typename T>
189
183
void RpyBallMobilizer<T>::DoCalcNplusMatrix(const systems::Context<T>& context,
190
184
EigenPtr<MatrixX<T>> Nplus) const {
191
- // The linear map between q̇ and v is given by matrix E_F(q) defined by:
192
- // [ cos(y) * cos(p), -sin(y), 0]
193
- // E_F(q) = [ sin(y) * cos(p), cos(y), 0]
194
- // [ -sin(p), 0, 1]
195
- //
196
- // w_FM = E_F(q) * q̇; q̇ = [ṙ, ṗ, ẏ]ᵀ
185
+ // The matrix N⁺(q) relates v to q̇ as v = N⁺(q) * q̇, where q̇ = [ṙ, ṗ, ẏ]ᵀ and
186
+ // v = w_FM_F = [ω0, ω1, ω2]ᵀ is the mobilizer M frame's angular velocity in
187
+ // the mobilizer F frame, expressed in the F frame (thus w_FM_F = N⁺(q) * q̇).
197
188
//
198
- // Here, following a convention used by many dynamicists, we are calling the
199
- // angles q0, q1, q2 as roll (r), pitch (p) and yaw (y), respectively.
189
+ // ⌈ ω0 ⌉ ⌈ cos(y) * cos(p), -sin(y), 0 ⌉ ⌈ ṙ ⌉
190
+ // | ω1 | = | sin(y) * cos(p), cos(y), 0 | | ṗ |
191
+ // ⌊ ω2 ⌋ ⌊ -sin(p), 0, 1 ⌋ ⌊ ẏ ⌋
200
192
//
201
- // See detailed developer comments for E_F(q) in the implementation for
202
- // MapQDotToVelocity().
203
-
193
+ // See related code and comments in MapQDotToVelocity().
204
194
const Vector3<T> angles = get_angles (context);
205
-
206
195
const T sp = sin (angles[1 ]);
207
196
const T cp = cos (angles[1 ]);
208
197
const T sy = sin (angles[2 ]);
@@ -215,57 +204,33 @@ template <typename T>
215
204
void RpyBallMobilizer<T>::DoMapVelocityToQDot(
216
205
const systems::Context<T>& context, const Eigen::Ref<const VectorX<T>>& v,
217
206
EigenPtr<VectorX<T>> qdot) const {
218
- using std::abs;
219
- using std::cos;
220
- using std::sin;
221
-
222
- // The linear map E_F(q) allows computing v from q̇ as:
223
- // w_FM = E_F(q) * q̇; q̇ = [ṙ, ṗ, ẏ]ᵀ
207
+ // The matrix N(q) relates q̇ to v as q̇ = N(q) * v, where q̇ = [ṙ, ṗ, ẏ]ᵀ and
208
+ // v = w_FM_F = [ω0, ω1, ω2]ᵀ is the mobilizer M frame's angular velocity in
209
+ // the mobilizer F frame, expressed in the F frame.
224
210
//
225
- // Here, following a convention used by many dynamicists, we are calling the
226
- // angles θ₁, θ₂, θ₃ as roll (r), pitch (p) and yaw (y), respectively.
211
+ // ⌈ ṙ ⌉ ⌈ cos(y) / cos(p), sin(y) / cos(p), 0 ⌉ ⌈ ω0 ⌉
212
+ // | ṗ | = | -sin(y), cos(y), 0 | | ω1 |
213
+ // ⌊ ẏ ⌋ ⌊ sin(p) * cos(y) / cos(p), sin(p) * sin(y) / cos(p), 1 ⌋ ⌊ ω2 ⌋
227
214
//
228
- // The linear map from v to q̇ is given by the inverse of E_F(q):
229
- // [ cos(y) / cos(p), sin(y) / cos(p), 0]
230
- // Einv_F = [ -sin(y), cos(y), 0]
231
- // [ sin(p) * cos(y) / cos(p), sin(p) * sin(y) / cos(p), 1]
215
+ // Note: N(q) is singular for p = π/2 + kπ, for k = ±1, ±2, ...
216
+ // See related code and comments in CalcNMatrix().
217
+ // Note: The calculation below is more efficient than calculating N(q) * v.
232
218
//
233
- // such that q̇ = Einv_F(q) * w_FM; q̇ = [ṙ, ṗ, ẏ]ᵀ
234
- // where we intentionally wrote the expression for Einv_F in terms of sines
235
- // and cosines only to arrive to the more computationally efficient version
236
- // below.
237
- //
238
- // Notice Einv_F is singular for p = π/2 + kπ, ∀ k ∈ ℤ.
239
- //
240
- // Note to developers:
241
- // Matrix E_F(q) is obtained by computing w_FM as the composition of the
242
- // angular velocity induced by each Euler angle rate in its respective
243
- // body-fixed frame. This is outlined in [Diebel 2006, §5.2;
244
- // Mitiguy (July 22) 2016, §9.3]. Notice however that our rotation matrix R_FM
245
- // is the transpose of that in [Diebel 2006], Eq. 67, given the convention
246
- // used there. Still, the expression for Einv_F in [Diebel 2006], Eq. 76, is
247
- // exactly the same here presented.
248
- //
249
- // The expression for Einv_F was symbolically generated with the following
250
- // Maxima script (which can be copy/pasted and executed as is):
251
- //
252
- // Rx:matrix([1,0,0],[0,cos(r),-sin(r)],[0,sin(r),cos(r)]);
253
- // Ry:matrix([cos(p),0,sin(p)],[0,1,0],[-sin(p),0,cos(p)]);
254
- // Rz:matrix([cos(y),-sin(y),0],[sin(y),cos(y),0],[0,0,1]);
255
- // R_FM:Rz . Ry . Rx;
256
- // R_MF:transpose(R_FM);
257
- // E_F: transpose(append(transpose(
258
- // Rz . Ry . [1,0,0]),transpose(Rz . [0,1,0]),matrix([0,0,1])));
259
- // detout: true$
260
- // doallmxops: false$
261
- // doscmxops: false$
262
- // Einv_F: trigsimp(invert(E_F));
219
+ // Developer note: N(q) is calculated by first forming w_FM by adding three
220
+ // angular velocities, each related to an Euler angle rate (ṙ or ṗ or ẏ) in
221
+ // various frames (frame F, two intermediate frames, and frame M). This is
222
+ // discussed in [Diebel 2006, §5.2; Mitiguy (August 2019, §9.1].
223
+ // Note: Diebel's eq. 67 rotation matrix is the transpose of our R_FM. Still
224
+ // the expression for N(q) in [Diebel 2006], Eq. 76, is the same as herein.
263
225
//
264
226
// [Diebel 2006] Representing attitude: Euler angles, unit quaternions, and
265
227
// rotation vectors. Stanford University.
266
- // [Mitiguy (July 22) 2016 ] Mitiguy, P., 2016 . Advanced Dynamics & Motion
267
- // Simulation.
228
+ // [Mitiguy August 2019 ] Mitiguy, P., 2019 . Advanced Dynamics & Motion
229
+ // Simulation.
268
230
231
+ using std::abs;
232
+ using std::cos;
233
+ using std::sin;
269
234
const Vector3<T> angles = get_angles (context);
270
235
const T cp = cos (angles[1 ]);
271
236
if (abs (cp) < 1.0e-3 ) {
@@ -288,61 +253,50 @@ void RpyBallMobilizer<T>::DoMapVelocityToQDot(
288
253
const T cy = cos (angles[2 ]);
289
254
const T cpi = 1.0 / cp;
290
255
291
- // Although the linear equations relating v to q̇ can be used to explicitly
292
- // solve the equation w_FM = E_F(q) * q̇ for q̇, a more computational efficient
293
- // solution results by implicit solution of those linear equations.
294
- // Namely, the first two equations in w_FM = E_F(q) * q̇ are used to solve for
295
- // ṙ and ṗ, then the third equation is used to solve for ẏ in terms of just
296
- // ṙ and w2:
256
+ // Although we can calculate q̇ = N(q) * v, it is more efficient to implicitly
257
+ // invert the simpler equation v = N⁺(q) * q̇, whose matrix form is
258
+ //
259
+ // ⌈ ω0 ⌉ ⌈ cos(y) * cos(p), -sin(y), 0 ⌉ ⌈ ṙ ⌉
260
+ // | ω1 | = | sin(y) * cos(p), cos(y), 0 | | ṗ |
261
+ // ⌊ ω2 ⌋ ⌊ -sin(p), 0, 1 ⌋ ⌊ ẏ ⌋
262
+ //
263
+ // Namely, the first two equations are used to solve for ṙ and ṗ, then the
264
+ // third equation is used to solve for ẏ in terms of just ṙ and w2:
297
265
// ṙ = (cos(y) * w0 + sin(y) * w1) / cos(p)
298
266
// ṗ = -sin(y) * w0 + cos(y) * w1
299
267
// ẏ = sin(p) * ṙ + w2
300
- const T t = (cy * w0 + sy * w1) * cpi; // Common factor.
301
- *qdot = Vector3<T>(t , -sy * w0 + cy * w1, sp * t + w2);
268
+ const T rdot = (cy * w0 + sy * w1) * cpi;
269
+ *qdot = Vector3<T>(rdot , -sy * w0 + cy * w1, sp * rdot + w2);
302
270
}
303
271
304
272
template <typename T>
305
273
void RpyBallMobilizer<T>::DoMapQDotToVelocity(
306
274
const systems::Context<T>& context,
307
275
const Eigen::Ref<const VectorX<T>>& qdot, EigenPtr<VectorX<T>> v) const {
308
- using std::cos;
309
- using std::sin;
310
-
311
- // The linear map between q̇ and v is given by matrix E_F(q) defined by:
312
- // [ cos(y) * cos(p), -sin(y), 0]
313
- // E_F(q) = [ sin(y) * cos(p), cos(y), 0]
314
- // [ -sin(p), 0, 1]
276
+ // The matrix N⁺(q) relates v to q̇ as v = N⁺(q) * q̇, where q̇ = [ṙ, ṗ, ẏ]ᵀ and
277
+ // v = w_FM_F = [ω0, ω1, ω2]ᵀ is the mobilizer M frame's angular velocity in
278
+ // the mobilizer F frame, expressed in the F frame (thus w_FM_F = N⁺(q) * q̇).
315
279
//
316
- // w_FM = E_F(q) * q̇; q̇ = [ṙ, ṗ, ẏ]ᵀ
280
+ // ⌈ ω0 ⌉ ⌈ cos(y) * cos(p), -sin(y), 0 ⌉ ⌈ ṙ ⌉
281
+ // | ω1 | = | sin(y) * cos(p), cos(y), 0 | | ṗ |
282
+ // ⌊ ω2 ⌋ ⌊ -sin(p), 0, 1 ⌋ ⌊ ẏ ⌋
317
283
//
318
- // Here, following a convention used by many dynamicists, we are calling the
319
- // angles θ₁, θ₂, θ₃ as roll (r), pitch (p) and yaw (y), respectively.
284
+ // See related code and comments in DoCalcNplusMatrix().
320
285
//
321
- // Note to developers:
322
- // Matrix E_F(q) is obtained by computing w_FM as the composition of the
323
- // angular velocity induced by each Euler angle rate in its respective
324
- // body-fixed frame. This is outlined in [Diebel 2006, §5.2;
325
- // Mitiguy (July 22) 2016, §9.3]. Notice however that our rotation matrix R_FM
326
- // is the transpose of that in [Diebel 2006], Eq. 67, given the convention
327
- // used there. Still, the expression for E_F in [Diebel 2006], Eq. 74, is
328
- // exactly the same here presented.
329
- //
330
- // The expression for E_F was symbolically generated with the following
331
- // Maxima script (which can be copy/pasted and executed as is):
332
- //
333
- // Rx:matrix([1,0,0],[0,cos(r),-sin(r)],[0,sin(r),cos(r)]);
334
- // Ry:matrix([cos(p),0,sin(p)],[0,1,0],[-sin(p),0,cos(p)]);
335
- // Rz:matrix([cos(y),-sin(y),0],[sin(y),cos(y),0],[0,0,1]);
336
- // R_FM:Rz . Ry . Rx;
337
- // R_MF:transpose(R_FM);
338
- // E_F: transpose(append(transpose(
339
- // Rz . Ry . [1,0,0]),transpose(Rz . [0,1,0]),matrix([0,0,1])));
286
+ // Developer note: N(q) is calculated by first forming w_FM by adding three
287
+ // angular velocities, each related to an Euler angle rate (ṙ or ṗ or ẏ) in
288
+ // various frames (frame F, two intermediate frames, and frame M). This is
289
+ // discussed in [Diebel 2006, §5.2; Mitiguy (August 2019, §9.1].
290
+ // Note: Diebel's eq. 67 rotation matrix is the transpose of our R_FM. Still
291
+ // the expression for N(q) in [Diebel 2006], Eq. 76, is the same as herein.
340
292
//
341
293
// [Diebel 2006] Representing attitude: Euler angles, unit quaternions, and
342
294
// rotation vectors. Stanford University.
343
- // [Mitiguy (July 22) 2016 ] Mitiguy, P., 2016 . Advanced Dynamics & Motion
344
- // Simulation.
295
+ // [Mitiguy August 2019 ] Mitiguy, P., 2019 . Advanced Dynamics & Motion
296
+ // Simulation.
345
297
298
+ using std::cos;
299
+ using std::sin;
346
300
const Vector3<T> angles = get_angles (context);
347
301
const T& rdot = qdot[0 ];
348
302
const T& pdot = qdot[1 ];
@@ -354,8 +308,8 @@ void RpyBallMobilizer<T>::DoMapQDotToVelocity(
354
308
const T cy = cos (angles[2 ]);
355
309
const T cp_x_rdot = cp * rdot;
356
310
357
- // Compute the product w_FM = E_W * q̇ directly since it's cheaper than
358
- // explicitly forming E_F and then multiplying with q̇.
311
+ // Compute the product v = N⁺(q) * q̇ element-by-element to leverate the zeros
312
+ // in N⁺(q) -- which is more efficient than matrix multiplication N⁺(q) * q̇.
359
313
*v = Vector3<T>(cy * cp_x_rdot - sy * pdot, /* + 0 * ydot*/
360
314
sy * cp_x_rdot + cy * pdot, /* + 0 * ydot*/
361
315
-sp * rdot /* + 0 * pdot */ + ydot);
0 commit comments