1+ import numpy as np
2+ from spatialmath import Quaternion , UnitQuaternion , SE3
3+ from spatialmath import base
4+
5+ # TODO scalar multiplication
6+
7+ class DualQuaternion :
8+ r"""
9+ A dual number is an ordered pair :math:`\hat{a} = (a, b)` or written as
10+ :math:`a + \epsilon b` where :math:`\epsilon^2 = 0`.
11+
12+ A dual quaternion can be considered as either:
13+
14+ - a quaternion with dual numbers as coefficients
15+ - a dual of quaternions, written as an ordered pair of quaternions
16+
17+ The latter form is used here.
18+
19+ :References:
20+
21+ - http://web.cs.iastate.edu/~cs577/handouts/dual-quaternion.pdf
22+ - https://en.wikipedia.org/wiki/Dual_quaternion
23+ """
24+
25+ def __init__ (self , real = None , dual = None ):
26+ """
27+ Construct a new dual quaternion
28+
29+ :param real: real quaternion
30+ :type real: Quaternion or UnitQuaternion
31+ :param dual: dual quaternion
32+ :type dual: Quaternion or UnitQuaternion
33+ :raises ValueError: incorrect parameters
34+
35+ Example:
36+
37+ .. runblock:: pycon
38+
39+ >>> from spatialmath import DualQuaternion, Quaternion
40+ >>> d = DualQuaternion(Quaternion([1,2,3,4]), Quaternion([5,6,7,8]))
41+ >>> print(d)
42+ >>> d = DualQuaternion([1, 2, 3, 4, 5, 6, 7, 8])
43+ >>> print(d)
44+
45+ """
46+
47+ if real is None and dual is None :
48+ self .real = None
49+ self .dual = None
50+ return
51+ elif dual is None and base .isvector (real , 8 ):
52+ self .real = Quaternion (real [0 :4 ])
53+ self .dual = Quaternion (real [4 :8 ])
54+ elif real is not None and dual is not None :
55+ if not isinstance (real , Quaternion ):
56+ raise ValueError ('real part must be a Quaternion subclass' )
57+ if not isinstance (dual , Quaternion ):
58+ raise ValueError ('real part must be a Quaternion subclass' )
59+ self .real = real # quaternion, real part
60+ self .dual = dual # quaternion, dual part
61+ else :
62+ raise ValueError ('expecting zero or two parameters' )
63+
64+ def __repr__ (self ):
65+ return str (self )
66+
67+ def __str__ (self ):
68+ """
69+ String representation of dual quaternion
70+
71+ :return: compact string representation
72+ :rtype: str
73+
74+ Example:
75+
76+ .. runblock:: pycon
77+
78+ >>> from spatialmath import DualQuaternion, Quaternion
79+ >>> d = DualQuaternion(Quaternion([1,2,3,4]), Quaternion([5,6,7,8]))
80+ >>> str(d)
81+ """
82+ return str (self .real ) + " + ε " + str (self .dual )
83+
84+ def norm (self ):
85+ """
86+ Norm of a dual quaternion
87+
88+ :return: Norm as a dual number
89+ :rtype: 2-tuple
90+
91+ Example:
92+
93+ .. runblock:: pycon
94+
95+ >>> from spatialmath import DualQuaternion, Quaternion
96+ >>> d = DualQuaternion(Quaternion([1,2,3,4]), Quaternion([5,6,7,8]))
97+ >>> d.norm() # norm is a dual number
98+ """
99+ a = self .real * self .real .conj ()
100+ b = self .real * self .dual .conj () + self .dual * self .real .conj ()
101+ return (a .s , b .s )
102+
103+ def conj (self ):
104+ r"""
105+ Conjugate of dual quaternion
106+
107+ :return: Conjugate
108+ :rtype: DualQuaternion
109+
110+ There are several conjugates defined for a dual quaternion. This one
111+ mirrors conjugation for a regular quaternion. For the dual quaternion
112+ :math:`(p, q)` it returns :math:`(p^*, q^*)`.
113+
114+ Example:
115+
116+ .. runblock:: pycon
117+
118+ >>> from spatialmath import DualQuaternion, Quaternion
119+ >>> d = DualQuaternion(Quaternion([1,2,3,4]), Quaternion([5,6,7,8]))
120+ >>> d.conj()
121+ """
122+ return DualQuaternion (self .real .conj (), self .dual .conj ())
123+
124+ def __add__ (left , right ): # lgtm[py/not-named-self] pylint: disable=no-self-argument
125+ """
126+ Sum of two dual quaternions
127+
128+ :return: Product
129+ :rtype: DualQuaternion
130+
131+ Example:
132+
133+ .. runblock:: pycon
134+
135+ >>> from spatialmath import DualQuaternion, Quaternion
136+ >>> d = DualQuaternion(Quaternion([1,2,3,4]), Quaternion([5,6,7,8]))
137+ >>> d + d
138+ """
139+ return DualQuaternion (left .real + right .real , left .dual + right .dual )
140+
141+ def __sub__ (left , right ): # lgtm[py/not-named-self] pylint: disable=no-self-argument
142+ """
143+ Difference of two dual quaternions
144+
145+ :return: Product
146+ :rtype: DualQuaternion
147+
148+ Example:
149+
150+ .. runblock:: pycon
151+
152+ >>> from spatialmath import DualQuaternion, Quaternion
153+ >>> d = DualQuaternion(Quaternion([1,2,3,4]), Quaternion([5,6,7,8]))
154+ >>> d - d
155+ """
156+ return DualQuaternion (left .real - right .real , left .dual - right .dual )
157+
158+ def __mul__ (left , right ): # lgtm[py/not-named-self] pylint: disable=no-self-argument
159+ """
160+ Product of two dual quaternions
161+
162+ :return: Product
163+ :rtype: DualQuaternion
164+
165+ Example:
166+
167+ .. runblock:: pycon
168+
169+ >>> from spatialmath import DualQuaternion, Quaternion
170+ >>> d = DualQuaternion(Quaternion([1,2,3,4]), Quaternion([5,6,7,8]))
171+ >>> d * d
172+ """
173+ real = left .real * right .real
174+ dual = left .real * right .dual + left .dual * right .real
175+
176+ if isinstance (left , UnitDualQuaternion ) and isinstance (left , UnitDualQuaternion ):
177+ return UnitDualQuaternion (real , dual )
178+ else :
179+ return DualQuaternion (real , dual )
180+
181+ def matrix (self ):
182+ """
183+ Dual quaternion as a matrix
184+
185+ :return: Matrix represensation
186+ :rtype: ndarray(8,8)
187+
188+ Dual quaternion multiplication can also be written as a matrix-vector
189+ product.
190+
191+ Example:
192+
193+ .. runblock:: pycon
194+
195+ >>> from spatialmath import DualQuaternion, Quaternion
196+ >>> d = DualQuaternion(Quaternion([1,2,3,4]), Quaternion([5,6,7,8]))
197+ >>> d.matrix()
198+ >>> d.matrix() @ d.vec
199+ >>> d * d
200+ """
201+ return np .block ([
202+ [self .real .matrix , np .zeros ((4 ,4 ))],
203+ [self .dual .matrix , self .real .matrix ]
204+ ])
205+
206+ @property
207+ def vec (self ):
208+ """
209+ Dual quaternion as a vector
210+
211+ :return: Vector represensation
212+ :rtype: ndarray(8)
213+
214+ Example:
215+
216+ .. runblock:: pycon
217+
218+ >>> from spatialmath import DualQuaternion, Quaternion
219+ >>> d = DualQuaternion(Quaternion([1,2,3,4]), Quaternion([5,6,7,8]))
220+ >>> d.vec
221+ """
222+ return np .r_ [self .real .vec , self .dual .vec ]
223+
224+
225+ # def log(self):
226+ # pass
227+
228+ class UnitDualQuaternion (DualQuaternion ):
229+
230+ def __init__ (self , real = None , dual = None ):
231+ """
232+ Create new unit dual quaternion
233+
234+ :param real: real quaternion or SE(3) matrix
235+ :type real: Quaternion, UnitQuaternion or SE3
236+ :param dual: dual quaternion
237+ :type dual: Quaternion or UnitQuaternion
238+
239+ - ``UnitDualQuaternion(real, dual)`` is a new unit dual quaternion with
240+ real and dual parts as specified.
241+ - ``UnitDualQuaternion(T)`` is a new unit dual quaternion equivalent to
242+ the rigid-body motion described by the SE3 value ``T``.
243+
244+ Example:
245+
246+ .. runblock:: pycon
247+
248+ >>> from spatialmath import DualQuaternion
249+ >>> T = SE3.Rand()
250+ >>> print(T)
251+ >>> d = UnitDualQuaternion(T))
252+ >>> print(d)
253+ >>> type(d)
254+ >>> print(d.norm()) # norm is (1, 0)
255+ """
256+ if real is None and dual is None :
257+ self .real = None
258+ self .dual = None
259+ return
260+ elif real is not None and dual is not None :
261+ self .real = real # quaternion, real part
262+ self .dual = dual # quaternion, dual part
263+ elif dual is None and isinstance (real , SE3 ):
264+ T = real
265+ S = UnitQuaternion (T .R )
266+ D = Quaternion .Pure (T .t )
267+
268+ self .real = S
269+ self .dual = 0.5 * D * S
270+
271+ def T (self ):
272+ """
273+ Convert unit dual quaternion to SE(3) matrix
274+
275+ :return: SE(3) matrix
276+ :rtype: SE3
277+
278+ Example:
279+
280+ .. runblock:: pycon
281+
282+ >>> from spatialmath import DualQuaternion
283+ >>> T = SE3.Rand()
284+ >>> print(T)
285+ >>> d = UnitDualQuaternion(T))
286+ >>> print(d)
287+ >>> print(d.T)
288+ """
289+ R = base .q2r (self .real .A )
290+ t = 2 * self .dual * self .real .conj ()
291+
292+ return SE3 (base .rt2tr (R , t .v ))
293+
294+ # def exp(self):
295+ # w = self.real.v
296+ # v = self.dual.v
297+ # theta = base.norm(w)
298+
299+ if __name__ == "__main__" :
300+
301+ a = DualQuaternion (Quaternion ([1 ,2 ,3 ,4 ]), Quaternion ([5 ,6 ,7 ,8 ]))
302+ print (a )
303+ print (a .vec )
304+ print (a .conj ())
305+ print (a .norm ())
306+ print (a + a )
307+ print (a * a )
308+ print (a .matrix ())
309+
310+ T = SE3 .Rand ()
311+ print (T )
312+
313+ print (aa := UnitDualQuaternion (T ))
314+ print (aa .norm ())
315+ print (aa .T ())
0 commit comments