Table of Contents:

Conjugation in Group Theory

Surprisingly, movement in \(SE(2)\) can always be achieved by moving somewhere, making a rotation there, and then moving back.

\[SE(2) = (\cdot) SO(2) (\cdot)\]

If we move to a point by vector movement \(p\), essentially we perform: \(B-p\), then go back with \(p + \dots\).

\[B^{\prime} = \begin{bmatrix} R & t \\ 0 & 1 \end{bmatrix}_B = \begin{bmatrix} I & p \\ 0 & 1 \end{bmatrix} \begin{bmatrix} R & 0 \\ 0 & 1 \end{bmatrix} \begin{bmatrix} I & -p \\ 0 & 1 \end{bmatrix}_B\]

As an aside: Quaternions



"""
make sure quaternions are normalized (to be unit quaternions)
unit quaternions are a way to compactly represent 3D rotations
while avoiding singularities or discontinuities (e.g. gimbal lock).
"""

import numpy as np

# import quaternion


EPSILON = 1e-10

def quat_mult(q1, q2):
    """Multiply two quaternions.

    Args:
        q1: NumPy n-d array, with dim (4 x 1)
        q2: NumPy n-d array, with dim (4 x 1)

    We recall that a cross product (u x v) is defined as
        (u_2 * v_3) - (u_3 * v_2)
        (u_3 * v_1) - (u_1 * v_3)
        (u_1 * v_2) - (u_2 * v_1)
    """
    w1, x1, y1, z1 = q1
    w2, x2, y2, z2 = q2

    # w = (w_1 * w_2) - < v_1, v_2 >
    w = (w1 * w2) - (x1 * x2) - (y1 * y2) - (z1 * z2)

    # v = w_1 * v_2 + w_2 * v_1 + (v_1 x v_2)
    x = (w1 * x2) + (x1 * w2) + y1 * z2 - z1 * y2
    y = (w1 * y2) + (y1 * w2) + z1 * x2 - x1 * z2
    z = (w1 * z2) + (z1 * w2) + x1 * y2 - y1 * x2
    return np.array([w, x, y, z])


def quat_conjugate(q):
    """

    Args:
        q: NumPy n-d array, with dim (4 x 1)

    The conjugate of a quaternion is the same as the inverse, as long as the quaternion is unit-length.
    """
    w, x, y, z = q
    return (w, -x, -y, -z)


def quat_vec_mult(q1, v1):
    """Quaternion-vector multiplication (apply a quaternion-rotation to a vector).

        v' = q * v * q_conj

    Args:
        q1:
        v1:

    Returns:
        TODO:
    """
    q2 = (0.0,) + v1
    return q_mult(q_mult(q1, q2), q_conjugate(q1))[1:]


def quat_normalize(q):
    """Normalize a quaternion.

    Args:
        q: NumPy n-d array, with dim (4 x 1) representing (w,x,y,z)
    """
    return q / np.linalg.norm(q)


def quat2rotmat(q: np.ndarray) -> np.ndarray:
    """Convert a quaternion into a matrix.

    Args:
        q: NumPy n-d array, with dim (4 x 1), representing (w,x,y,z)

    Returns:
        R: Numpy array of shape (3,3)
    """
    if (np.linalg.norm(q) - 1.0) > EPSILON:
        q /= np.linalg.norm(q)
        # q = np.multiply(q, np.tile(1./np.linalg.norm(q),4))
    w, x, y, z = q

    x2 = x * x
    y2 = y * y
    z2 = z * z
    xy = x * y
    xz = x * z
    yz = y * z
    wx = w * x
    wy = w * y
    wz = w * z

    return np.array(
        [
            [1.0 - 2.0 * (y2 + z2), 2.0 * (xy - wz), 2.0 * (xz + wy)],
            [2.0 * (xy + wz), 1.0 - 2.0 * (x2 + z2), 2.0 * (yz - wx)],
            [2.0 * (xz - wy), 2.0 * (yz + wx), 1.0 - 2.0 * (x2 + y2)],
        ]
    )


def test_quat_normalize() -> None:
    """Unit test on quaternion normalization to unit-length."""
    q_quat = None
    q_arr = None

    # print( quaternion.quaternion_normalized(q_quat) )

    print(quat_normalize(q_arr))


def test_quat_conjugate() -> None:
    """Unit test Conjugate functionality"""
    q_quat = None
    q_arr = None
    print(q_quat.conjugate())
    print(quat_conjugate(q_arr))


def test_quat2rotmat() -> None:
    """Test quaternion-vector product functionality"""
    # print( quaternion.as_rotation_matrix(q_quat) )

    q_quat = None
    q_arr = None
    print(quat2rotmat(q_arr))


def unit_tests() -> None:
    """Check against the Numpy Quaternion add-on package"""
    q_quat = np.quaternion(4, 3, 2, 1)
    q_arr = np.array([4, 3, 2, 1])

    # q_quat = np.quaternion(   0.73029674, 0.54772256, 0.36514837, 0.18257419)
    # q_arr = np.array([        0.73029674, 0.54772256, 0.36514837, 0.18257419])

    # quat_normalization_unit_test(q_quat, q_arr)

    # conjugate_unit_test(q_quat, q_arr)
    quat_2_rot_mat_unit_test(q_quat, q_arr)


if __name__ == "__main__":
    unit_tests()