@@ -943,6 +943,56 @@ def test_log(self):
943943 self .assertIsInstance (qq , UnitQuaternion )
944944 nt .assert_array_almost_equal (qq .vec , np .r_ [1 , 0 , 0 , 0 ])
945945
946+ def test_log_small_rotation (self ):
947+ axis = np .array ([1.0 , - 2.0 , 3.0 ]) / math .sqrt (14 )
948+ for angle in (1e-12 , - 1e-9 , 1e-7 , 1e-4 , 0.3 ):
949+ for scale in (1.0 , 3.0 ):
950+ with self .subTest (angle = angle , scale = scale ):
951+ values = scale * np .r_ [cos (angle / 2 ), sin (angle / 2 ) * axis ]
952+ q = UnitQuaternion (values ) if scale == 1 else Quaternion (values )
953+ original = q .vec .copy ()
954+ qlog = q .log ()
955+ nt .assert_allclose (qlog .v , angle / 2 * axis , rtol = 1e-13 , atol = 0 )
956+ self .assertAlmostEqual (qlog .s , math .log (scale ))
957+ nt .assert_allclose (qlog .exp ().vec , q .vec , rtol = 1e-13 , atol = 0 )
958+ nt .assert_array_equal (q .vec , original )
959+
960+ def test_log_small_rotation_sequence (self ):
961+ angles = np .array ([1e-12 , - 1e-9 , 1e-7 , 0.3 ])
962+ axes = np .array ([[1 , 0 , 0 ], [0 , 1 , 0 ], [0 , 0 , 1 ], [1 , 0 , 0 ]])
963+ values = np .column_stack (
964+ (np .cos (angles / 2 ), np .sin (angles [:, None ] / 2 ) * axes )
965+ )
966+ expected = angles [:, None ] / 2 * axes
967+ for scales in (np .ones (4 ), np .array ([2.0 , 3.0 , 4.0 , 5.0 ])):
968+ with self .subTest (scales = scales ):
969+ scaled = scales [:, None ] * values
970+ q = (
971+ UnitQuaternion (scaled )
972+ if np .all (scales == 1 )
973+ else Quaternion (list (scaled ))
974+ )
975+ original = q .vec .copy ()
976+ qlog = q .log ()
977+ self .assertEqual (len (qlog ), len (q ))
978+ nt .assert_allclose (qlog .v , expected , rtol = 1e-13 , atol = 0 )
979+ nt .assert_allclose (qlog .s , np .log (scales ), atol = 1e-15 )
980+ for i in range (len (q )):
981+ nt .assert_allclose (qlog [i ].exp ().vec , scaled [i ], rtol = 1e-13 , atol = 0 )
982+ nt .assert_allclose (qlog [i ].vec , q [i ].log ().vec , rtol = 1e-13 , atol = 0 )
983+ nt .assert_array_equal (q .vec , original )
984+
985+ def test_log_near_negative_real (self ):
986+ # Keep the principal quaternion branch near pi, rather than folding
987+ # a negative scalar part onto the small-angle branch.
988+ angle = 1e-7
989+ q = Quaternion ([- cos (angle ), sin (angle ), 0 , 0 ])
990+ expected = np .r_ [0 , pi - angle , 0 , 0 ]
991+ nt .assert_allclose (q .log ().vec , expected , rtol = 0 , atol = 1e-15 )
992+ nt .assert_allclose (
993+ Quaternion ([q , q ]).log ().vec , [expected , expected ], rtol = 0 , atol = 1e-15
994+ )
995+
946996 def test_concat (self ):
947997 u = Quaternion ()
948998 uu = Quaternion ([u , u , u , u ])
0 commit comments