14
14
import math
15
15
import numpy as np
16
16
import spatialmath .base as smb
17
+ from spatialmath .base .argcheck import getunit
17
18
from spatialmath .base .types import *
19
+ import scipy .interpolate as interpolate
20
+ from typing import Optional
21
+ from functools import lru_cache
18
22
19
23
_eps = np .finfo (np .float64 ).eps
20
24
21
-
22
25
def qeye () -> QuaternionArray :
23
26
"""
24
27
Create an identity quaternion
@@ -843,29 +846,112 @@ def qslerp(
843
846
return q0
844
847
845
848
846
- def qrand () -> UnitQuaternionArray :
849
+ def _compute_cdf_sin_squared ( theta : float ) :
847
850
"""
848
- Random unit-quaternion
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
849
862
863
+ @lru_cache (maxsize = 1 )
864
+ def _generate_inv_cdf_sin_squared_interp (num_interpolation_points : int = 256 ) -> 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 : int = 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 :
900
+ """
901
+ Random unit-quaternion
902
+
903
+ :arg theta_range: angular magnitude range [min,max], defaults to None.
904
+ :type xrange: 2-element sequence, optional
905
+ :arg unit: angular units: 'rad' [default], or 'deg'
906
+ :type unit: str
907
+ :arg num_interpolation_points: number of points to use in the interpolation function
908
+ :rtype: int
909
+ :arg num_interpolation_points: number of points to use in the interpolation function
910
+ :rtype: int
850
911
:return: random unit-quaternion
851
912
:rtype: ndarray(4)
852
913
853
- Computes a uniformly distributed random unit-quaternion which can be
854
- considered equivalent to a random SO(3) rotation.
914
+ Computes a uniformly distributed random unit-quaternion, with in a maximum
915
+ angular magnitude, which can be considered equivalent to a random SO(3) rotation.
855
916
856
917
.. runblock:: pycon
857
918
858
919
>>> from spatialmath.base import qrand, qprint
859
920
>>> qprint(qrand())
860
921
"""
861
- u = np .random .uniform (low = 0 , high = 1 , size = 3 ) # get 3 random numbers in [0,1]
862
- return np .r_ [
863
- math .sqrt (1 - u [0 ]) * math .sin (2 * math .pi * u [1 ]),
864
- math .sqrt (1 - u [0 ]) * math .cos (2 * math .pi * u [1 ]),
865
- math .sqrt (u [0 ]) * math .sin (2 * math .pi * u [2 ]),
866
- math .sqrt (u [0 ]) * math .cos (2 * math .pi * u [2 ]),
867
- ]
922
+ if theta_range is not None :
923
+ theta_range = getunit (theta_range , unit )
924
+
925
+ if (theta_range [0 ] < 0 or theta_range [1 ] > np .pi or theta_range [0 ] > theta_range [1 ]):
926
+ ValueError ('Invalid angular range. Must be within the range[0, pi].'
927
+ + f' Recieved { theta_range } .' )
928
+
929
+ # Sample axis and angle independently, respecting the CDF of the
930
+ # angular magnitude under uniform sampling.
931
+
932
+ # Sample angle using inverse transform sampling based on CDF
933
+ # of the angular distribution (2/pi * sin^2(theta/2))
934
+ theta = _compute_inv_cdf_sin_squared (
935
+ np .random .uniform (
936
+ low = _compute_cdf_sin_squared (theta_range [0 ]),
937
+ high = _compute_cdf_sin_squared (theta_range [1 ]),
938
+ ),
939
+ num_interpolation_points = num_interpolation_points ,
940
+ )
941
+ # Sample axis uniformly using 3D normal distributed
942
+ v = np .random .randn (3 )
943
+ v /= np .linalg .norm (v )
868
944
945
+ return np .r_ [math .cos (theta / 2 ), (math .sin (theta / 2 ) * v )]
946
+ else :
947
+ u = np .random .uniform (low = 0 , high = 1 , size = 3 ) # get 3 random numbers in [0,1]
948
+ return np .r_ [
949
+ math .sqrt (1 - u [0 ]) * math .sin (2 * math .pi * u [1 ]),
950
+ math .sqrt (1 - u [0 ]) * math .cos (2 * math .pi * u [1 ]),
951
+ math .sqrt (u [0 ]) * math .sin (2 * math .pi * u [2 ]),
952
+ math .sqrt (u [0 ]) * math .cos (2 * math .pi * u [2 ]),
953
+ ]
954
+
869
955
870
956
def qmatrix (q : ArrayLike4 ) -> R4x4 :
871
957
"""
0 commit comments