Skip to content

Commit cd5a142

Browse files
mitiguyPaul Mitiguy
andauthored
Correct and improve rpy_ball_mobilizer documentation, replace E matrix with standard N matrix. (#23006)
Correct and improve rpy_ball_mobilizer documentation, replace E matrix with standard N matrix. (#23006) Co-authored-by: Paul Mitiguy <[email protected]>
1 parent 38d2b10 commit cd5a142

File tree

1 file changed

+69
-115
lines changed

1 file changed

+69
-115
lines changed

multibody/tree/rpy_ball_mobilizer.cc

Lines changed: 69 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -134,28 +134,23 @@ void RpyBallMobilizer<T>::ProjectSpatialForce(
134134
template <typename T>
135135
void RpyBallMobilizer<T>::DoCalcNMatrix(const systems::Context<T>& context,
136136
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.
143140
//
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 ⌋
146144
//
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().
154147

148+
using std::abs;
149+
using std::cos;
150+
using std::sin;
155151
const Vector3<T> angles = get_angles(context);
156152
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.
159154
if (abs(cp) < 1.0e-3) {
160155
throw std::runtime_error(fmt::format(
161156
"The RpyBallMobilizer (implementing a BallRpyJoint) between "
@@ -171,7 +166,6 @@ void RpyBallMobilizer<T>::DoCalcNMatrix(const systems::Context<T>& context,
171166
const T sy = sin(angles[2]);
172167
const T cy = cos(angles[2]);
173168
const T cpi = 1.0 / cp;
174-
175169
const T cy_x_cpi = cy * cpi;
176170
const T sy_x_cpi = sy * cpi;
177171

@@ -188,21 +182,16 @@ void RpyBallMobilizer<T>::DoCalcNMatrix(const systems::Context<T>& context,
188182
template <typename T>
189183
void RpyBallMobilizer<T>::DoCalcNplusMatrix(const systems::Context<T>& context,
190184
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̇).
197188
//
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 ⌋ ⌊ ẏ ⌋
200192
//
201-
// See detailed developer comments for E_F(q) in the implementation for
202-
// MapQDotToVelocity().
203-
193+
// See related code and comments in MapQDotToVelocity().
204194
const Vector3<T> angles = get_angles(context);
205-
206195
const T sp = sin(angles[1]);
207196
const T cp = cos(angles[1]);
208197
const T sy = sin(angles[2]);
@@ -215,57 +204,33 @@ template <typename T>
215204
void RpyBallMobilizer<T>::DoMapVelocityToQDot(
216205
const systems::Context<T>& context, const Eigen::Ref<const VectorX<T>>& v,
217206
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.
224210
//
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 ⌋
227214
//
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.
232218
//
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.
263225
//
264226
// [Diebel 2006] Representing attitude: Euler angles, unit quaternions, and
265227
// 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.
268230

231+
using std::abs;
232+
using std::cos;
233+
using std::sin;
269234
const Vector3<T> angles = get_angles(context);
270235
const T cp = cos(angles[1]);
271236
if (abs(cp) < 1.0e-3) {
@@ -288,61 +253,50 @@ void RpyBallMobilizer<T>::DoMapVelocityToQDot(
288253
const T cy = cos(angles[2]);
289254
const T cpi = 1.0 / cp;
290255

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:
297265
// ṙ = (cos(y) * w0 + sin(y) * w1) / cos(p)
298266
// ṗ = -sin(y) * w0 + cos(y) * w1
299267
// ẏ = 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);
302270
}
303271

304272
template <typename T>
305273
void RpyBallMobilizer<T>::DoMapQDotToVelocity(
306274
const systems::Context<T>& context,
307275
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̇).
315279
//
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 ⌋ ⌊ ẏ ⌋
317283
//
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().
320285
//
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.
340292
//
341293
// [Diebel 2006] Representing attitude: Euler angles, unit quaternions, and
342294
// 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.
345297

298+
using std::cos;
299+
using std::sin;
346300
const Vector3<T> angles = get_angles(context);
347301
const T& rdot = qdot[0];
348302
const T& pdot = qdot[1];
@@ -354,8 +308,8 @@ void RpyBallMobilizer<T>::DoMapQDotToVelocity(
354308
const T cy = cos(angles[2]);
355309
const T cp_x_rdot = cp * rdot;
356310

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̇.
359313
*v = Vector3<T>(cy * cp_x_rdot - sy * pdot, /*+ 0 * ydot*/
360314
sy * cp_x_rdot + cy * pdot, /*+ 0 * ydot*/
361315
-sp * rdot /*+ 0 * pdot */ + ydot);

0 commit comments

Comments
 (0)