Source code for pytransform3d.rotations._jacobians
"""Jacobians of SO(3)."""
import math
import numpy as np
from ._rot_log import cross_product_matrix
[docs]
def left_jacobian_SO3(omega):
r"""Left Jacobian of SO(3) at theta (angle of rotation).
.. math::
\boldsymbol{J}(\theta)
=
\frac{\sin{\theta}}{\theta} \boldsymbol{I}
+ \left(\frac{1 - \cos{\theta}}{\theta}\right)
\left[\hat{\boldsymbol{\omega}}\right]
+ \left(1 - \frac{\sin{\theta}}{\theta} \right)
\hat{\boldsymbol{\omega}} \hat{\boldsymbol{\omega}}^T
Parameters
----------
omega : array-like, shape (3,)
Compact axis-angle representation.
Returns
-------
J : array, shape (3, 3)
Left Jacobian of SO(3).
See also
--------
left_jacobian_SO3_series :
Left Jacobian of SO(3) at theta from Taylor series.
left_jacobian_SO3_inv :
Inverse left Jacobian of SO(3) at theta (angle of rotation).
"""
omega = np.asarray(omega)
theta = np.linalg.norm(omega)
# theta is the rotation angle in radians. Float64 machine epsilon
# eps = np.finfo(float).eps is one ULP above 1.0 (unit in the last
# place, i.e. the spacing between adjacent floats).
# The coefficient 1 - sin(theta)/theta = theta**2/6 + O(theta**4)
# subtracts values near 1.0, losing relative precision as theta**2
# approaches eps. The inverse coefficient starts with theta**2/12;
# its conservative sqrt(6*eps) cutoff puts that leading term at eps/2.
# Mirror this cutoff so both Jacobians use their series, avoiding
# these scalar subtractions in the same tiny-angle range.
if theta < math.sqrt(6.0 * np.finfo(float).eps):
return left_jacobian_SO3_series(omega, 10)
omega_unit = omega / theta
omega_matrix = cross_product_matrix(omega_unit)
return (
np.eye(3)
# This coefficient is (1 - cos(theta))/theta. The half-angle
# identity 1 - cos(theta) = 2*sin(theta/2)**2 avoids subtracting
# two values near 1.0. For theta << 1 rad, the difference is about
# theta**2/2, so O(eps) rounding in cos causes O(eps/theta**2)
# relative error, even above the series cutoff; around sqrt(eps)
# it can round to zero. sin(theta/2) is instead about theta/2 and
# preserves the small value without this subtraction.
+ 2.0 * math.sin(0.5 * theta) ** 2 / theta * omega_matrix
+ (1.0 - math.sin(theta) / theta) * np.dot(omega_matrix, omega_matrix)
)
[docs]
def left_jacobian_SO3_series(omega, n_terms):
"""Left Jacobian of SO(3) at theta from Taylor series.
Parameters
----------
omega : array-like, shape (3,)
Compact axis-angle representation.
n_terms : int
Number of terms to include in the series.
Returns
-------
J : array, shape (3, 3)
Left Jacobian of SO(3).
See Also
--------
left_jacobian_SO3 : Left Jacobian of SO(3) at theta (angle of rotation).
"""
omega = np.asarray(omega)
J = np.eye(3)
pxn = np.eye(3)
px = cross_product_matrix(omega)
for n in range(n_terms):
pxn = np.dot(pxn, px) / (n + 2)
J += pxn
return J
[docs]
def left_jacobian_SO3_inv(omega):
r"""Inverse left Jacobian of SO(3) at theta (angle of rotation).
.. math::
\boldsymbol{J}^{-1}(\theta)
=
\frac{\theta}{2 \tan{\frac{\theta}{2}}} \boldsymbol{I}
- \frac{\theta}{2} \left[\hat{\boldsymbol{\omega}}\right]
+ \left(1 - \frac{\theta}{2 \tan{\frac{\theta}{2}}}\right)
\hat{\boldsymbol{\omega}} \hat{\boldsymbol{\omega}}^T
Parameters
----------
omega : array-like, shape (3,)
Compact axis-angle representation.
Returns
-------
J_inv : array, shape (3, 3)
Inverse left Jacobian of SO(3).
See Also
--------
left_jacobian_SO3 : Left Jacobian of SO(3) at theta (angle of rotation).
left_jacobian_SO3_inv_series :
Inverse left Jacobian of SO(3) at theta from Taylor series.
"""
omega = np.asarray(omega)
theta = np.linalg.norm(omega)
# theta is the rotation angle in radians; eps is float64 machine
# epsilon. The coefficient 1 - theta/(2*tan(theta/2)) starts with
# theta**2/12. At theta = sqrt(6*eps), this leading correction is
# eps/2, comparable to roundoff in the values near 1.0 being subtracted.
# The difference can lose relative precision or round to zero.
# Use the series below this conservative cutoff.
if theta < math.sqrt(6.0 * np.finfo(float).eps):
return left_jacobian_SO3_inv_series(omega, 10)
omega_unit = omega / theta
omega_matrix = cross_product_matrix(omega_unit)
return (
np.eye(3)
- 0.5 * omega_matrix * theta
+ (1.0 - 0.5 * theta / np.tan(theta / 2.0))
* np.dot(omega_matrix, omega_matrix)
)
[docs]
def left_jacobian_SO3_inv_series(omega, n_terms):
"""Inverse left Jacobian of SO(3) at theta from Taylor series.
Parameters
----------
omega : array-like, shape (3,)
Compact axis-angle representation.
n_terms : int
Number of terms to include in the series.
Returns
-------
J_inv : array, shape (3, 3)
Inverse left Jacobian of SO(3).
See Also
--------
left_jacobian_SO3_inv :
Inverse left Jacobian of SO(3) at theta (angle of rotation).
"""
from scipy.special import bernoulli
omega = np.asarray(omega)
J_inv = np.eye(3)
pxn = np.eye(3)
px = cross_product_matrix(omega)
b = bernoulli(n_terms + 1)
for n in range(n_terms):
pxn = np.dot(pxn, px / (n + 1))
J_inv += b[n + 1] * pxn
return J_inv