Skip to content

Commit 3dba220

Browse files
committed
perf: compute Twist3.Ad() directly instead of via a throwaway SE3
Ad() was self.SE3().Ad() -- constructs a full validated SE3 object just to immediately extract its array and discard the object. Computes the same result directly (trexp then tr2adjoint, skipping the SE3 constructor/validation overhead): bit-identical output, ~1.4x faster. No existing test covered Ad() at all; added one first (hand-verified pure-rotation and pure-translation cases, plus a cross-check against SE3.Ad() over 20 random transforms) and confirmed it passes unchanged against both the old and new implementation.
1 parent 8b83c1f commit 3dba220

2 files changed

Lines changed: 30 additions & 3 deletions

File tree

spatialmath/twist.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -887,12 +887,13 @@ def Ad(self):
887887
>>> S = Twist3.Rx(0.3)
888888
>>> S.Ad()
889889
890-
.. note:: This method computes the equivalent SE(3) matrix, then the adjoint
891-
of that.
890+
.. note:: Equivalent to, but faster than, ``self.SE3().Ad()`` -- computes
891+
the adjoint directly from the twist's exponential without
892+
constructing an intermediate ``SE3`` instance.
892893
893894
:seealso: :func:`Twist3.ad`, :func:`Twist3.SE3`, :func:`Twist3.exp`
894895
"""
895-
return self.SE3().Ad()
896+
return smb.tr2adjoint(smb.trexp(self.S, check=False))
896897

897898
def skewa(self):
898899
"""

tests/test_twist.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,32 @@ def test_exp(self):
179179
tw = Twist3.UnitRevolute([0, 0, 1], [0, 0, 0])
180180
array_compare(tw.exp(pi / 2), SE3.Rz(pi / 2))
181181

182+
def test_Ad(self):
183+
# pure rotation: Ad is block-diagonal [[R,0],[0,R]], no translation
184+
# coupling -- hand-verified ground truth, not derived from Ad() itself.
185+
R = SE3.Rx(0.4).R
186+
S = Twist3(SE3.Rx(0.4))
187+
expected = np.zeros((6, 6))
188+
expected[:3, :3] = R
189+
expected[3:, 3:] = R
190+
nt.assert_almost_equal(S.Ad(), expected)
191+
192+
# pure translation: Ad couples translation into the top-right block
193+
# via skew(t), identity rotation blocks -- also hand-verified.
194+
t = np.r_[1, 2, 3]
195+
S = Twist3(SE3(t))
196+
expected = np.eye(6)
197+
expected[:3, 3:] = skew(t)
198+
nt.assert_almost_equal(S.Ad(), expected)
199+
200+
# general case: cross-check against SE3.Ad(), computed from the same
201+
# twist's own exponential, for several random transforms.
202+
for _ in range(20):
203+
T = SE3.Rand()
204+
S = Twist3(T)
205+
nt.assert_almost_equal(S.Ad(), S.SE3().Ad())
206+
nt.assert_almost_equal(S.Ad(), T.Ad())
207+
182208
def test_arith(self):
183209
# check overloaded *
184210

0 commit comments

Comments
 (0)