Source code for diffpes.maths.rotations

r"""Construct differentiable three-dimensional rotations.

Extended Summary
----------------
This module provides one shared Rodrigues primitive for detector, crystal,
polarization, and spin frames. The function accepts a traced axis and angle.
It keeps both values available to JAX transformations.

Routine Listings
----------------
:func:`rodrigues_rotation`
    Construct a rotation matrix with Rodrigues' formula.

Notes
-----
The function uses active rotations on column vectors. A zero axis gives the
identity matrix and a zero selected gradient.
"""

import jax.numpy as jnp
from beartype import beartype
from jaxtyping import Array, Float, jaxtyped

from diffpes.types import ScalarFloat

from .safe import safe_divide, safe_norm


[docs] @jaxtyped(typechecker=beartype) def rodrigues_rotation( axis: Float[Array, "3"], angle: ScalarFloat, ) -> Float[Array, "3 3"]: r"""Construct a rotation matrix with Rodrigues' formula. The function safely normalizes the rotation axis. It then constructs an active rotation matrix for column vectors. :see: :class:`~.test_rotations.TestRodriguesRotation` Parameters ---------- axis : Float[Array, "3"] Rotation axis in Cartesian coordinates. angle : ScalarFloat Active rotation angle in radians. Returns ------- rotation : Float[Array, "3 3"] Active Cartesian rotation matrix. Notes ----- The function uses :math:`R = I + \sin(\alpha)[\hat n]_\times + (1 - \cos(\alpha))[\hat n]_\times^2`. The safe normalization gives the identity matrix for a zero axis. """ angle_array: Float[Array, ""] = jnp.asarray(angle) axis_norm: Float[Array, ""] = safe_norm(axis) normalized_axis: Float[Array, "3"] = safe_divide(axis, axis_norm) axis_x: Float[Array, ""] = normalized_axis[0] axis_y: Float[Array, ""] = normalized_axis[1] axis_z: Float[Array, ""] = normalized_axis[2] zero: Float[Array, ""] = jnp.zeros_like(axis_x) skew: Float[Array, "3 3"] = jnp.stack( ( jnp.stack((zero, -axis_z, axis_y)), jnp.stack((axis_z, zero, -axis_x)), jnp.stack((-axis_y, axis_x, zero)), ) ) identity: Float[Array, "3 3"] = jnp.eye(3, dtype=axis.dtype) skew_squared: Float[Array, "3 3"] = skew @ skew rotation: Float[Array, "3 3"] = ( identity + jnp.sin(angle_array) * skew + (1.0 - jnp.cos(angle_array)) * skew_squared ) return rotation
__all__: list[str] = ["rodrigues_rotation"]