Skip to content

Module pyastrobee.utils.dynamics

Functions for free-body dynamics, state-space control, and other tools

Note that any equations involving quaternions or angular velocity use our convention of XYZW quaternions and world-frame angular velocity definition (to match Pybullet)

Key equations: F = m * a T = I * alpha + omega x (I * omega) + m * r x a

Notation: F: Force, in world frame m: Mass a: Linear acceleration of the body's center of mass, in world frame T = Torque, in world frame I = Inertia tensor of the body, in world frame alpha: Angular acceleration of the body, in world frame omega: Angular velocity of the body, in world frame r: Position of the body's center of mass w.r.t the point of interest on the body, in world frame

Notes: - If the point of interest is the center of mass of the body, the third term in the torque expression is 0 - For Astrobee, this point (the base frame) is sightly different from the center of mass, but they are close enough that the errors are minimal

View Source
"""Functions for free-body dynamics, state-space control, and other tools

Note that any equations involving quaternions or angular velocity use our convention of XYZW quaternions and

world-frame angular velocity definition (to match Pybullet)

Key equations:

F = m * a

T = I * alpha + omega x (I * omega) + m * r x a

Notation:

F: Force, in world frame

m: Mass

a: Linear acceleration of the body's center of mass, in world frame

T = Torque, in world frame

I = Inertia tensor of the body, in world frame

alpha: Angular acceleration of the body, in world frame

omega: Angular velocity of the body, in world frame

r: Position of the body's center of mass w.r.t the point of interest on the body, in world frame

Notes:

- If the point of interest is the center of mass of the body, the third term in the torque expression is 0

- For Astrobee, this point (the base frame) is sightly different from the center of mass, but they are close enough

  that the errors are minimal

"""

import numpy as np

import numpy.typing as npt

def inertial_transformation(

    mass: float, inertia: np.ndarray, transform: np.ndarray

) -> np.ndarray:

    """Transform an inertia tensor defined for a local reference frame into a new reference frame

    Reference: Stanford ME320 Intro to Robotics course reader, chapter 5

    Args:

        mass (float): Mass of the object

        inertia (np.ndarray): Inertia tensor of the object, determined for its local frame. Shape (3, 3)

        transform (np.ndarray): "Local to desired" transformation matrix, shape (4, 4)

    Returns:

        np.ndarray: Transformed inertia tensor

    """

    p = transform[:3, 3]

    R = transform[:3, :3]

    # Parallel axis theorem for the translation component

    I = inertia + mass * (np.dot(p, p) * np.eye(3) - np.outer(p, p))

    # Similarity transform for the rotation component

    return R @ I @ R.T

def state_matrix(

    q: npt.ArrayLike,

    w: npt.ArrayLike,

    inertia: np.ndarray,

    inv_inertia: np.ndarray,

) -> np.ndarray:

    """The A matrix, such that x_dot = Ax + Bu

    We assume that the state x = [position, velocity, quaternion, angular velocity] ∈ R13

    and that the control u = [force, torque] ∈ R6

    This is dependent on the current quaternion/angular velocity due to the nonlinearities

    in the orientation representation

    Args:

        q (npt.ArrayLike): XYZW quaternion, shape (4,)

        w (npt.ArrayLike): Angular velocity, shape (3,)

        inertia (np.ndarray): Inertia tensor (world frame), shape (3, 3)

        inv_inertia (np.ndarray): Inverse of the inertia tensor (world frame), shape (3, 3).

    Returns:

        np.ndarray: The A (state) matrix

    """

    if inv_inertia is None:

        inv_inertia = np.linalg.inv(inertia)

    A = np.zeros((13, 13))

    qx, qy, qz, qw = q

    w1, w2, w3 = w

    # Relationship between velocity / acceleration and current position / velocity

    A[:6, :6] = np.kron([[0, 1], [0, 0]], np.eye(3))

    # Relationship between quaternion derivative and current quaternion / angular velocity

    # NOTE: this is effectively a concatenation of the standard quaternion derivative matrices, HOWEVER, we multiply by

    # an extra factor of 1/2 here because of the way that this matrix multiplication A @ x will "double count" the

    # quaternion derivative effect, since we will have q_dot = f(w) @ q + f(q) @ w

    A[6:10, 6:] = (1 / 2) * np.array(

        [

            [0, -w3 / 2, w2 / 2, w1 / 2, qw / 2, qz / 2, -qy / 2],

            [w3 / 2, 0, -w1 / 2, w2 / 2, -qz / 2, qw / 2, qx / 2],

            [-w2 / 2, w1 / 2, 0, w3 / 2, qy / 2, -qx / 2, qw / 2],

            [-w1 / 2, -w2 / 2, -w3 / 2, 0, -qx / 2, -qy / 2, -qz / 2],

        ]

    )

    # Relationship between angular acceleration and angular velocity

    A[10:, 10:] = -1 * inv_inertia @ _jac_w_of_wxIw(inertia, w)

    return A

def control_matrix(mass: float, inv_inertia: np.ndarray) -> np.ndarray:

    """The B matrix, such that x_dot = Ax + Bu

    We assume that the state x = [position, velocity, quaternion, angular velocity] ∈ R13

    and that the control u = [force, torque] ∈ R6

    Args:

        mass (float): Mass of the system

        inv_inertia (np.ndarray): Inverse of the inertia tensor (world frame), shape (3, 3)

    Returns:

        np.ndarray: The B (control) matrix

    """

    B = np.zeros((13, 6))

    B[3:6, :3] = (1 / mass) * np.eye(3)

    B[10:, 3:] = inv_inertia

    return B

def _jac_w_of_wxIw(I: np.ndarray, w: npt.ArrayLike) -> np.ndarray:

    """Helper function. Computes the Jacobian of the expression (w x I w) with respect to w

    Derivation is via the MATLAB symbolic toolbox. e.g. jacobian(cross(w, I*w), [w])

    with w and I defined symbolically w.r.t their components

    Args:

        I (np.ndarray): Inertia tensor (world frame). Symmetric, positive semidefinite. Shape (3, 3)

        w (npt.ArrayLike): Angular velocity vector, shape (3,)

    Returns:

        np.ndarray: Jacobian matrix, shape (3, 3)

    """

    Ixx, Iyy, Izz = np.diag(I)

    Ixy = I[0, 1]

    Ixz = I[0, 2]

    Iyz = I[1, 2]

    w1, w2, w3 = w

    return np.array(

        [

            [

                Ixz * w2 - Ixy * w3,

                Ixz * w1 - Iyy * w3 + 2 * Iyz * w2 + Izz * w3,

                Izz * w2 - Iyy * w2 - 2 * Iyz * w3 - Ixy * w1,

            ],

            [

                Ixx * w3 - 2 * Ixz * w1 - Iyz * w2 - Izz * w3,

                Ixy * w3 - Iyz * w1,

                Ixx * w1 + Ixy * w2 + 2 * Ixz * w3 - Izz * w1,

            ],

            [

                2 * Ixy * w1 - Ixx * w2 + Iyy * w2 + Iyz * w3,

                Iyy * w1 - 2 * Ixy * w2 - Ixz * w3 - Ixx * w1,

                Iyz * w1 - Ixz * w2,

            ],

        ]

    )

def box_inertia(m: float, l: float, w: float, h: float) -> np.ndarray:

    """Inertia tensor for a solid, uniform-material box

    Args:

        m (float): Mass, kg

        l (float): Length (x-axis dimension), meters

        w (float): Width (y-axis dimension), meters

        h (float): Height (z-axis dimension), meters

    Returns:

        np.ndarray: Inertia tensor, shape (3, 3)

    """

    return (1 / 12) * m * np.diag([w**2 + h**2, l**2 + h**2, l**2 + w**2])

Functions

box_inertia

def box_inertia(
    m: float,
    l: float,
    w: float,
    h: float
) -> numpy.ndarray

Inertia tensor for a solid, uniform-material box

Parameters:

Name Type Description Default
m float Mass, kg None
l float Length (x-axis dimension), meters None
w float Width (y-axis dimension), meters None
h float Height (z-axis dimension), meters None

Returns:

Type Description
np.ndarray Inertia tensor, shape (3, 3)
View Source
def box_inertia(m: float, l: float, w: float, h: float) -> np.ndarray:

    """Inertia tensor for a solid, uniform-material box

    Args:

        m (float): Mass, kg

        l (float): Length (x-axis dimension), meters

        w (float): Width (y-axis dimension), meters

        h (float): Height (z-axis dimension), meters

    Returns:

        np.ndarray: Inertia tensor, shape (3, 3)

    """

    return (1 / 12) * m * np.diag([w**2 + h**2, l**2 + h**2, l**2 + w**2])

control_matrix

def control_matrix(
    mass: float,
    inv_inertia: numpy.ndarray
) -> numpy.ndarray

The B matrix, such that x_dot = Ax + Bu

We assume that the state x = [position, velocity, quaternion, angular velocity] ∈ R13 and that the control u = [force, torque] ∈ R6

Parameters:

Name Type Description Default
mass float Mass of the system None
inv_inertia np.ndarray Inverse of the inertia tensor (world frame), shape (3, 3) None

Returns:

Type Description
np.ndarray The B (control) matrix
View Source
def control_matrix(mass: float, inv_inertia: np.ndarray) -> np.ndarray:

    """The B matrix, such that x_dot = Ax + Bu

    We assume that the state x = [position, velocity, quaternion, angular velocity] ∈ R13

    and that the control u = [force, torque] ∈ R6

    Args:

        mass (float): Mass of the system

        inv_inertia (np.ndarray): Inverse of the inertia tensor (world frame), shape (3, 3)

    Returns:

        np.ndarray: The B (control) matrix

    """

    B = np.zeros((13, 6))

    B[3:6, :3] = (1 / mass) * np.eye(3)

    B[10:, 3:] = inv_inertia

    return B

inertial_transformation

def inertial_transformation(
    mass: float,
    inertia: numpy.ndarray,
    transform: numpy.ndarray
) -> numpy.ndarray

Transform an inertia tensor defined for a local reference frame into a new reference frame

Reference: Stanford ME320 Intro to Robotics course reader, chapter 5

Parameters:

Name Type Description Default
mass float Mass of the object None
inertia np.ndarray Inertia tensor of the object, determined for its local frame. Shape (3, 3) None
transform np.ndarray "Local to desired" transformation matrix, shape (4, 4) None

Returns:

Type Description
np.ndarray Transformed inertia tensor
View Source
def inertial_transformation(

    mass: float, inertia: np.ndarray, transform: np.ndarray

) -> np.ndarray:

    """Transform an inertia tensor defined for a local reference frame into a new reference frame

    Reference: Stanford ME320 Intro to Robotics course reader, chapter 5

    Args:

        mass (float): Mass of the object

        inertia (np.ndarray): Inertia tensor of the object, determined for its local frame. Shape (3, 3)

        transform (np.ndarray): "Local to desired" transformation matrix, shape (4, 4)

    Returns:

        np.ndarray: Transformed inertia tensor

    """

    p = transform[:3, 3]

    R = transform[:3, :3]

    # Parallel axis theorem for the translation component

    I = inertia + mass * (np.dot(p, p) * np.eye(3) - np.outer(p, p))

    # Similarity transform for the rotation component

    return R @ I @ R.T

state_matrix

def state_matrix(
    q: Union[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]],
    w: Union[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, float, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]],
    inertia: numpy.ndarray,
    inv_inertia: numpy.ndarray
) -> numpy.ndarray

The A matrix, such that x_dot = Ax + Bu

We assume that the state x = [position, velocity, quaternion, angular velocity] ∈ R13 and that the control u = [force, torque] ∈ R6

This is dependent on the current quaternion/angular velocity due to the nonlinearities in the orientation representation

Parameters:

Name Type Description Default
q npt.ArrayLike XYZW quaternion, shape (4,) None
w npt.ArrayLike Angular velocity, shape (3,) None
inertia np.ndarray Inertia tensor (world frame), shape (3, 3) None
inv_inertia np.ndarray Inverse of the inertia tensor (world frame), shape (3, 3). None

Returns:

Type Description
np.ndarray The A (state) matrix
View Source
def state_matrix(

    q: npt.ArrayLike,

    w: npt.ArrayLike,

    inertia: np.ndarray,

    inv_inertia: np.ndarray,

) -> np.ndarray:

    """The A matrix, such that x_dot = Ax + Bu

    We assume that the state x = [position, velocity, quaternion, angular velocity] ∈ R13

    and that the control u = [force, torque] ∈ R6

    This is dependent on the current quaternion/angular velocity due to the nonlinearities

    in the orientation representation

    Args:

        q (npt.ArrayLike): XYZW quaternion, shape (4,)

        w (npt.ArrayLike): Angular velocity, shape (3,)

        inertia (np.ndarray): Inertia tensor (world frame), shape (3, 3)

        inv_inertia (np.ndarray): Inverse of the inertia tensor (world frame), shape (3, 3).

    Returns:

        np.ndarray: The A (state) matrix

    """

    if inv_inertia is None:

        inv_inertia = np.linalg.inv(inertia)

    A = np.zeros((13, 13))

    qx, qy, qz, qw = q

    w1, w2, w3 = w

    # Relationship between velocity / acceleration and current position / velocity

    A[:6, :6] = np.kron([[0, 1], [0, 0]], np.eye(3))

    # Relationship between quaternion derivative and current quaternion / angular velocity

    # NOTE: this is effectively a concatenation of the standard quaternion derivative matrices, HOWEVER, we multiply by

    # an extra factor of 1/2 here because of the way that this matrix multiplication A @ x will "double count" the

    # quaternion derivative effect, since we will have q_dot = f(w) @ q + f(q) @ w

    A[6:10, 6:] = (1 / 2) * np.array(

        [

            [0, -w3 / 2, w2 / 2, w1 / 2, qw / 2, qz / 2, -qy / 2],

            [w3 / 2, 0, -w1 / 2, w2 / 2, -qz / 2, qw / 2, qx / 2],

            [-w2 / 2, w1 / 2, 0, w3 / 2, qy / 2, -qx / 2, qw / 2],

            [-w1 / 2, -w2 / 2, -w3 / 2, 0, -qx / 2, -qy / 2, -qz / 2],

        ]

    )

    # Relationship between angular acceleration and angular velocity

    A[10:, 10:] = -1 * inv_inertia @ _jac_w_of_wxIw(inertia, w)

    return A