Skip to content

Commit 1678fce

Browse files
committed
Added constrained uniform sampling of angles
1 parent aa2269e commit 1678fce

File tree

1 file changed

+57
-23
lines changed

1 file changed

+57
-23
lines changed

spatialmath/base/quaternions.py

Lines changed: 57 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,29 +18,10 @@
1818
from spatialmath.base.types import *
1919
import scipy.interpolate as interpolate
2020
from typing import Optional
21+
from functools import lru_cache
2122

2223
_eps = np.finfo(np.float64).eps
2324

24-
_NUMBER_CDF_SIN_SQUARED_INTERP_POINTS = 256
25-
_CDF_SIN_SQUARED_INTERP_ANGLES = np.linspace(0, np.pi, _NUMBER_CDF_SIN_SQUARED_INTERP_POINTS)
26-
27-
def _compute_cdf_sin_squared(theta: float):
28-
"""
29-
Computes the CDF for the distribution of angular magnitude for uniformly sampled rotations.
30-
31-
:arg theta: angular magnitude
32-
:rtype: float
33-
:return: cdf of a given angular magnitude
34-
:rtype: float
35-
36-
Helper function for uniform sampling of rotations with constrained angular magnitude.
37-
This function returns the integral of the pdf of angular magnitudes (2/pi * sin^2(theta/2)).
38-
"""
39-
return (theta - np.sin(theta)) / np.pi
40-
41-
_CDF_SIN_SQUARED_INTERP_VALUES = _compute_cdf_sin_squared(_CDF_SIN_SQUARED_INTERP_ANGLES)
42-
_inv_cdf_sin_squared_interp = interpolate.interp1d(_CDF_SIN_SQUARED_INTERP_VALUES, _CDF_SIN_SQUARED_INTERP_ANGLES)
43-
4425
def qeye() -> QuaternionArray:
4526
"""
4627
Create an identity quaternion
@@ -865,14 +846,66 @@ def qslerp(
865846
return q0
866847

867848

868-
def qrand(theta_range:Optional[ArrayLike2] = None, unit: str = "rad") -> UnitQuaternionArray:
849+
def _compute_cdf_sin_squared(theta: float):
850+
"""
851+
Computes the CDF for the distribution of angular magnitude for uniformly sampled rotations.
852+
853+
:arg theta: angular magnitude
854+
:rtype: float
855+
:return: cdf of a given angular magnitude
856+
:rtype: float
857+
858+
Helper function for uniform sampling of rotations with constrained angular magnitude.
859+
This function returns the integral of the pdf of angular magnitudes (2/pi * sin^2(theta/2)).
860+
"""
861+
return (theta - np.sin(theta)) / np.pi
862+
863+
@lru_cache(maxsize=1)
864+
def _generate_inv_cdf_sin_squared_interp(num_interpolation_points) -> interpolate.interp1d:
865+
"""
866+
Computes an interpolation function for the inverse CDF of the distribution of angular magnitude.
867+
868+
:arg num_interpolation_points: number of points to use in the interpolation function
869+
:rtype: int
870+
:return: interpolation function for the inverse cdf of a given angular magnitude
871+
:rtype: interpolate.interp1d
872+
873+
Helper function for uniform sampling of rotations with constrained angular magnitude.
874+
This function returns interpolation function for the inverse of the integral of the
875+
pdf of angular magnitudes (2/pi * sin^2(theta/2)), which is not analytically defined.
876+
"""
877+
cdf_sin_squared_interp_angles = np.linspace(0, np.pi, num_interpolation_points)
878+
cdf_sin_squared_interp_values = _compute_cdf_sin_squared(cdf_sin_squared_interp_angles)
879+
return interpolate.interp1d(cdf_sin_squared_interp_values, cdf_sin_squared_interp_angles)
880+
881+
def _compute_inv_cdf_sin_squared(x: ArrayLike, num_interpolation_points=256) -> ArrayLike:
882+
"""
883+
Computes the inverse CDF of the distribution of angular magnitude.
884+
885+
:arg x: value for cdf of angular magnitudes
886+
:rtype: ArrayLike
887+
:arg num_interpolation_points: number of points to use in the interpolation function
888+
:rtype: int
889+
:return: angular magnitude associate with cdf value
890+
:rtype: ArrayLike
891+
892+
Helper function for uniform sampling of rotations with constrained angular magnitude.
893+
This function returns the angle associated with the cdf value derived form integral of
894+
the pdf of angular magnitudes (2/pi * sin^2(theta/2)), which is not analytically defined.
895+
"""
896+
inv_cdf_sin_squared_interp = _generate_inv_cdf_sin_squared_interp(num_interpolation_points)
897+
return inv_cdf_sin_squared_interp(x)
898+
899+
def qrand(theta_range:Optional[ArrayLike2] = None, unit: str = "rad", num_interpolation_points:int = 256) -> UnitQuaternionArray:
869900
"""
870901
Random unit-quaternion
871902
872903
:arg theta_range: angular magnitude range [min,max], defaults to None.
873904
:type xrange: 2-element sequence, optional
874905
:arg unit: angular units: 'rad' [default], or 'deg'
875906
:type unit: str
907+
:arg num_interpolation_points: number of points to use in the interpolation function
908+
:rtype: int
876909
:return: random unit-quaternion
877910
:rtype: ndarray(4)
878911
@@ -896,11 +929,12 @@ def qrand(theta_range:Optional[ArrayLike2] = None, unit: str = "rad") -> UnitQua
896929

897930
# Sample angle using inverse transform sampling based on CDF
898931
# of the angular distribution (2/pi * sin^2(theta/2))
899-
theta = _inv_cdf_sin_squared_interp(
932+
theta = _compute_inv_cdf_sin_squared(
900933
np.random.uniform(
901934
low=_compute_cdf_sin_squared(theta_range[0]),
902935
high=_compute_cdf_sin_squared(theta_range[1]),
903-
)
936+
),
937+
num_interpolation_points=num_interpolation_points,
904938
)
905939
# Sample axis uniformly using 3D normal distributed
906940
v = np.random.randn(3)

0 commit comments

Comments
 (0)