Module pyastrobee.utils.math_utils
Assorted helper functions related to math operations / linear algebra
View Source
"""Assorted helper functions related to math operations / linear algebra"""
from typing import Union
import numpy as np
import numpy.typing as npt
def normalize(vec: npt.ArrayLike) -> np.ndarray:
"""Normalizes a vector to have magnitude 1
Args:
vec (npt.ArrayLike): Input vector
Returns:
np.ndarray: A unit vector in the same direction as the input
"""
norm = np.linalg.norm(vec)
if abs(norm) < 1e-12:
raise ZeroDivisionError("Cannot normalize the vector, it has norm 0")
return np.array(vec) / norm
def is_diagonal(array: npt.ArrayLike) -> bool:
"""Checks if an array is diagonal or not
Args:
array (npt.ArrayLike): The array to check
Returns:
bool: True if the array is diagonal, False otherwise
"""
return np.array_equal(array, np.diag(np.diag(array)))
def skew(v: npt.ArrayLike) -> np.ndarray:
"""Skew-symmetric matrix form of a vector in R3
Args:
v (npt.ArrayLike): Vector to convert, shape (3,)
Returns:
np.ndarray: (3, 3) skew-symmetric matrix
"""
v = np.ravel(v)
if len(v) != 3:
raise ValueError(f"Vector needs to be of length 3.\nGot: {v}")
return np.array([[0, -v[2], v[1]], [v[2], 0, -v[0]], [-v[1], v[0], 0]])
def unskew(S: npt.ArrayLike) -> np.ndarray:
"""Gives the vector associated with a skew-symmetric matrix S such that skew(vec) = S
Args:
S (npt.ArrayLike): Skew-symmetric 3x3 matrix
Returns:
np.ndarray: "Unskewed" vector, shape (3,)
"""
S = np.asarray(S)
if S.shape != (3, 3):
raise ValueError(f"S must be a 3x3 matrix. Got shape: {S.shape}")
return np.array([S[2, 1], S[0, 2], S[1, 0]])
def is_skew(S: np.ndarray) -> bool:
"""Checks that a square matrix is skew-symmetric
Args:
S (np.ndarray): Matrix to check
Returns:
bool: True if skew-symmetric, False otherwise
"""
return np.allclose(S, -S.T)
def is_special_orthogonal(R: np.ndarray) -> bool:
"""Checks that a square matrix is in the special orthogonal group
Args:
R (np.ndarray): Matrix to check
Returns:
bool: True if special orthogonal (such as a rotation matrix), False otherwise
"""
m, n = R.shape
if m != n:
return False
return np.allclose(R @ R.T, np.eye(n)) and np.isclose(np.linalg.det(R), 1)
def spherical_vonmises_sampling(
mu: npt.ArrayLike, kappa: Union[float, npt.ArrayLike], n_pts: int
) -> np.ndarray:
"""Samples points on a (generalized) sphere based on the von Mises distribution
This is slightly more relevant to sampling on a sphere than a Gaussian because the
distribution is circular in nature. Technically, the von Mises-Fisher distribution is for
a sphere but Numpy doesn't seem to distinguish between these.
Args:
mu (npt.ArrayLike): Mean. Length defines the dimension of the sphere. Should be normalized
kappa (Union[float, npt.ArrayLike]): Concentration parameter. This can be thought of as the
"inverse of variance". Large kappa results in a distribution that approaches a gaussian;
small kappa approaches a uniform distribution. As a quick not-at-all precise measure,
kappa = 0: Uniform distribution around the sphere
kappa = 5: Angular dispersion of ~90 degrees from the mean
kappa = 10: Angular dispersion of ~60 degrees from the mean
kappa = 50: Angular dispersion of ~15 degrees from the mean
n_pts (int): Number of points to sample
Returns:
np.ndarray: Sampled points, shape (n_pts, dimension)
"""
mu = np.atleast_1d(mu)
kappa = np.atleast_1d(kappa)
sampled = np.random.vonmises(mu, kappa, size=(n_pts, len(mu)))
return sampled / np.linalg.norm(sampled, axis=1).reshape(-1, 1)
Functions
is_diagonal
def is_diagonal(
array: 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]]]
) -> bool
Checks if an array is diagonal or not
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
| array | npt.ArrayLike | The array to check | None |
Returns:
| Type | Description |
|---|---|
| bool | True if the array is diagonal, False otherwise |
View Source
def is_diagonal(array: npt.ArrayLike) -> bool:
"""Checks if an array is diagonal or not
Args:
array (npt.ArrayLike): The array to check
Returns:
bool: True if the array is diagonal, False otherwise
"""
return np.array_equal(array, np.diag(np.diag(array)))
is_skew
def is_skew(
S: numpy.ndarray
) -> bool
Checks that a square matrix is skew-symmetric
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
| S | np.ndarray | Matrix to check | None |
Returns:
| Type | Description |
|---|---|
| bool | True if skew-symmetric, False otherwise |
View Source
def is_skew(S: np.ndarray) -> bool:
"""Checks that a square matrix is skew-symmetric
Args:
S (np.ndarray): Matrix to check
Returns:
bool: True if skew-symmetric, False otherwise
"""
return np.allclose(S, -S.T)
is_special_orthogonal
def is_special_orthogonal(
R: numpy.ndarray
) -> bool
Checks that a square matrix is in the special orthogonal group
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
| R | np.ndarray | Matrix to check | None |
Returns:
| Type | Description |
|---|---|
| bool | True if special orthogonal (such as a rotation matrix), False otherwise |
View Source
def is_special_orthogonal(R: np.ndarray) -> bool:
"""Checks that a square matrix is in the special orthogonal group
Args:
R (np.ndarray): Matrix to check
Returns:
bool: True if special orthogonal (such as a rotation matrix), False otherwise
"""
m, n = R.shape
if m != n:
return False
return np.allclose(R @ R.T, np.eye(n)) and np.isclose(np.linalg.det(R), 1)
normalize
def normalize(
vec: 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]]]
) -> numpy.ndarray
Normalizes a vector to have magnitude 1
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
| vec | npt.ArrayLike | Input vector | None |
Returns:
| Type | Description |
|---|---|
| np.ndarray | A unit vector in the same direction as the input |
View Source
def normalize(vec: npt.ArrayLike) -> np.ndarray:
"""Normalizes a vector to have magnitude 1
Args:
vec (npt.ArrayLike): Input vector
Returns:
np.ndarray: A unit vector in the same direction as the input
"""
norm = np.linalg.norm(vec)
if abs(norm) < 1e-12:
raise ZeroDivisionError("Cannot normalize the vector, it has norm 0")
return np.array(vec) / norm
skew
def skew(
v: 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]]]
) -> numpy.ndarray
Skew-symmetric matrix form of a vector in R3
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
| v | npt.ArrayLike | Vector to convert, shape (3,) | None |
Returns:
| Type | Description |
|---|---|
| np.ndarray | (3, 3) skew-symmetric matrix |
View Source
def skew(v: npt.ArrayLike) -> np.ndarray:
"""Skew-symmetric matrix form of a vector in R3
Args:
v (npt.ArrayLike): Vector to convert, shape (3,)
Returns:
np.ndarray: (3, 3) skew-symmetric matrix
"""
v = np.ravel(v)
if len(v) != 3:
raise ValueError(f"Vector needs to be of length 3.\nGot: {v}")
return np.array([[0, -v[2], v[1]], [v[2], 0, -v[0]], [-v[1], v[0], 0]])
spherical_vonmises_sampling
def spherical_vonmises_sampling(
mu: 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]]],
kappa: Union[float, numpy._typing._array_like._SupportsArray[numpy.dtype[Any]], numpy._typing._nested_sequence._NestedSequence[numpy._typing._array_like._SupportsArray[numpy.dtype[Any]]], bool, int, complex, str, bytes, numpy._typing._nested_sequence._NestedSequence[Union[bool, int, float, complex, str, bytes]]],
n_pts: int
) -> numpy.ndarray
Samples points on a (generalized) sphere based on the von Mises distribution
This is slightly more relevant to sampling on a sphere than a Gaussian because the distribution is circular in nature. Technically, the von Mises-Fisher distribution is for a sphere but Numpy doesn't seem to distinguish between these.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
| mu | npt.ArrayLike | Mean. Length defines the dimension of the sphere. Should be normalized | None |
| kappa | Union[float, npt.ArrayLike] | Concentration parameter. This can be thought of as the "inverse of variance". Large kappa results in a distribution that approaches a gaussian; small kappa approaches a uniform distribution. As a quick not-at-all precise measure, kappa = 0: Uniform distribution around the sphere kappa = 5: Angular dispersion of ~90 degrees from the mean kappa = 10: Angular dispersion of ~60 degrees from the mean kappa = 50: Angular dispersion of ~15 degrees from the mean |
None |
| n_pts | int | Number of points to sample | None |
Returns:
| Type | Description |
|---|---|
| np.ndarray | Sampled points, shape (n_pts, dimension) |
View Source
def spherical_vonmises_sampling(
mu: npt.ArrayLike, kappa: Union[float, npt.ArrayLike], n_pts: int
) -> np.ndarray:
"""Samples points on a (generalized) sphere based on the von Mises distribution
This is slightly more relevant to sampling on a sphere than a Gaussian because the
distribution is circular in nature. Technically, the von Mises-Fisher distribution is for
a sphere but Numpy doesn't seem to distinguish between these.
Args:
mu (npt.ArrayLike): Mean. Length defines the dimension of the sphere. Should be normalized
kappa (Union[float, npt.ArrayLike]): Concentration parameter. This can be thought of as the
"inverse of variance". Large kappa results in a distribution that approaches a gaussian;
small kappa approaches a uniform distribution. As a quick not-at-all precise measure,
kappa = 0: Uniform distribution around the sphere
kappa = 5: Angular dispersion of ~90 degrees from the mean
kappa = 10: Angular dispersion of ~60 degrees from the mean
kappa = 50: Angular dispersion of ~15 degrees from the mean
n_pts (int): Number of points to sample
Returns:
np.ndarray: Sampled points, shape (n_pts, dimension)
"""
mu = np.atleast_1d(mu)
kappa = np.atleast_1d(kappa)
sampled = np.random.vonmises(mu, kappa, size=(n_pts, len(mu)))
return sampled / np.linalg.norm(sampled, axis=1).reshape(-1, 1)
unskew
def unskew(
S: 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]]]
) -> numpy.ndarray
Gives the vector associated with a skew-symmetric matrix S such that skew(vec) = S
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
| S | npt.ArrayLike | Skew-symmetric 3x3 matrix | None |
Returns:
| Type | Description |
|---|---|
| np.ndarray | "Unskewed" vector, shape (3,) |
View Source
def unskew(S: npt.ArrayLike) -> np.ndarray:
"""Gives the vector associated with a skew-symmetric matrix S such that skew(vec) = S
Args:
S (npt.ArrayLike): Skew-symmetric 3x3 matrix
Returns:
np.ndarray: "Unskewed" vector, shape (3,)
"""
S = np.asarray(S)
if S.shape != (3, 3):
raise ValueError(f"S must be a 3x3 matrix. Got shape: {S.shape}")
return np.array([S[2, 1], S[0, 2], S[1, 0]])