1818from spatialmath .base .types import *
1919import scipy .interpolate as interpolate
2020from 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-
4425def 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