Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions spatialmath/quaternion.py
Original file line number Diff line number Diff line change
Expand Up @@ -1551,6 +1551,9 @@ def AngVec(
"""
v = smb.getvector(v, 3)
theta = smb.getunit(theta, unit, vector=False)
if theta == 0:
return cls()
v = smb.unitvec(v)
return cls(
s=math.cos(theta / 2), v=math.sin(theta / 2) * v, norm=False, check=False
)
Expand Down
52 changes: 52 additions & 0 deletions tests/test_angvec_nonunit_axis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""Angle-axis construction preserves unit-quaternion and zero-angle contracts."""

import math
import unittest

import numpy as np
from numpy.testing import assert_allclose

from spatialmath import UnitQuaternion
from spatialmath.base import angvec2r


class TestAngVecNonunitAxis(unittest.TestCase):
def test_unit_norm(self):
assert_allclose(np.linalg.norm(UnitQuaternion.AngVec(0.5, [1, 2, 3]).vec), 1)

def test_angle_round_trip(self):
theta, axis = UnitQuaternion.AngVec(0.5, [1, 2, 3]).angvec()
assert_allclose(theta, 0.5)
assert_allclose(axis, np.array([1, 2, 3]) / np.sqrt(14))

def test_rotation_matrix(self):
assert_allclose(
UnitQuaternion.AngVec(0.5, [1, 2, 3]).R,
angvec2r(0.5, [1, 2, 3]),
atol=1e-12,
)

def test_degrees(self):
assert_allclose(
UnitQuaternion.AngVec(90, [0, 0, 4], unit="deg").R,
angvec2r(math.pi / 2, [0, 0, 1]),
atol=1e-12,
)

def test_unit_axis_control(self):
assert_allclose(
UnitQuaternion.AngVec(0.5, [1, 0, 0]).vec,
[math.cos(0.25), math.sin(0.25), 0, 0],
)

def test_zero_angle_zero_axis(self):
assert_allclose(UnitQuaternion.AngVec(0, [0, 0, 0]).vec, [1, 0, 0, 0])

def test_zero_degrees_zero_axis(self):
assert_allclose(
UnitQuaternion.AngVec(0, [0, 0, 0], unit="deg").vec, [1, 0, 0, 0]
)

def test_nonzero_angle_requires_nonzero_axis(self):
with self.assertRaises(ValueError):
UnitQuaternion.AngVec(0.5, [0, 0, 0])