quaternion.py 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673
  1. from sympy.core.numbers import Rational
  2. from sympy.core.singleton import S
  3. from sympy.core.relational import is_eq
  4. from sympy.functions.elementary.complexes import (conjugate, im, re, sign)
  5. from sympy.functions.elementary.exponential import (exp, log as ln)
  6. from sympy.functions.elementary.miscellaneous import sqrt
  7. from sympy.functions.elementary.trigonometric import (acos, asin, atan2)
  8. from sympy.functions.elementary.trigonometric import (cos, sin)
  9. from sympy.simplify.trigsimp import trigsimp
  10. from sympy.integrals.integrals import integrate
  11. from sympy.matrices.dense import MutableDenseMatrix as Matrix
  12. from sympy.core.sympify import sympify, _sympify
  13. from sympy.core.expr import Expr
  14. from sympy.core.logic import fuzzy_not, fuzzy_or
  15. from mpmath.libmp.libmpf import prec_to_dps
  16. def _check_norm(elements, norm):
  17. """validate if input norm is consistent"""
  18. if norm is not None and norm.is_number:
  19. if norm.is_positive is False:
  20. raise ValueError("Input norm must be positive.")
  21. numerical = all(i.is_number and i.is_real is True for i in elements)
  22. if numerical and is_eq(norm**2, sum(i**2 for i in elements)) is False:
  23. raise ValueError("Incompatible value for norm.")
  24. def _is_extrinsic(seq):
  25. """validate seq and return True if seq is lowercase and False if uppercase"""
  26. if type(seq) != str:
  27. raise ValueError('Expected seq to be a string.')
  28. if len(seq) != 3:
  29. raise ValueError("Expected 3 axes, got `{}`.".format(seq))
  30. intrinsic = seq.isupper()
  31. extrinsic = seq.islower()
  32. if not (intrinsic or extrinsic):
  33. raise ValueError("seq must either be fully uppercase (for extrinsic "
  34. "rotations), or fully lowercase, for intrinsic "
  35. "rotations).")
  36. i, j, k = seq.lower()
  37. if (i == j) or (j == k):
  38. raise ValueError("Consecutive axes must be different")
  39. bad = set(seq) - set('xyzXYZ')
  40. if bad:
  41. raise ValueError("Expected axes from `seq` to be from "
  42. "['x', 'y', 'z'] or ['X', 'Y', 'Z'], "
  43. "got {}".format(''.join(bad)))
  44. return extrinsic
  45. class Quaternion(Expr):
  46. """Provides basic quaternion operations.
  47. Quaternion objects can be instantiated as Quaternion(a, b, c, d)
  48. as in (a + b*i + c*j + d*k).
  49. Parameters
  50. ==========
  51. norm : None or number
  52. Pre-defined quaternion norm. If a value is given, Quaternion.norm
  53. returns this pre-defined value instead of calculating the norm
  54. Examples
  55. ========
  56. >>> from sympy import Quaternion
  57. >>> q = Quaternion(1, 2, 3, 4)
  58. >>> q
  59. 1 + 2*i + 3*j + 4*k
  60. Quaternions over complex fields can be defined as :
  61. >>> from sympy import Quaternion
  62. >>> from sympy import symbols, I
  63. >>> x = symbols('x')
  64. >>> q1 = Quaternion(x, x**3, x, x**2, real_field = False)
  65. >>> q2 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False)
  66. >>> q1
  67. x + x**3*i + x*j + x**2*k
  68. >>> q2
  69. (3 + 4*I) + (2 + 5*I)*i + 0*j + (7 + 8*I)*k
  70. Defining symbolic unit quaternions:
  71. >>> from sympy import Quaternion
  72. >>> from sympy.abc import w, x, y, z
  73. >>> q = Quaternion(w, x, y, z, norm=1)
  74. >>> q
  75. w + x*i + y*j + z*k
  76. >>> q.norm()
  77. 1
  78. References
  79. ==========
  80. .. [1] https://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/
  81. .. [2] https://en.wikipedia.org/wiki/Quaternion
  82. """
  83. _op_priority = 11.0
  84. is_commutative = False
  85. def __new__(cls, a=0, b=0, c=0, d=0, real_field=True, norm=None):
  86. a, b, c, d = map(sympify, (a, b, c, d))
  87. if any(i.is_commutative is False for i in [a, b, c, d]):
  88. raise ValueError("arguments have to be commutative")
  89. else:
  90. obj = Expr.__new__(cls, a, b, c, d)
  91. obj._a = a
  92. obj._b = b
  93. obj._c = c
  94. obj._d = d
  95. obj._real_field = real_field
  96. obj.set_norm(norm)
  97. return obj
  98. def set_norm(self, norm):
  99. """Sets norm of an already instantiated quaternion.
  100. Parameters
  101. ==========
  102. norm : None or number
  103. Pre-defined quaternion norm. If a value is given, Quaternion.norm
  104. returns this pre-defined value instead of calculating the norm
  105. Examples
  106. ========
  107. >>> from sympy import Quaternion
  108. >>> from sympy.abc import a, b, c, d
  109. >>> q = Quaternion(a, b, c, d)
  110. >>> q.norm()
  111. sqrt(a**2 + b**2 + c**2 + d**2)
  112. Setting the norm:
  113. >>> q.set_norm(1)
  114. >>> q.norm()
  115. 1
  116. Removing set norm:
  117. >>> q.set_norm(None)
  118. >>> q.norm()
  119. sqrt(a**2 + b**2 + c**2 + d**2)
  120. """
  121. norm = sympify(norm)
  122. _check_norm(self.args, norm)
  123. self._norm = norm
  124. @property
  125. def a(self):
  126. return self._a
  127. @property
  128. def b(self):
  129. return self._b
  130. @property
  131. def c(self):
  132. return self._c
  133. @property
  134. def d(self):
  135. return self._d
  136. @property
  137. def real_field(self):
  138. return self._real_field
  139. @property
  140. def product_matrix_left(self):
  141. r"""Returns 4 x 4 Matrix equivalent to a Hamilton product from the
  142. left. This can be useful when treating quaternion elements as column
  143. vectors. Given a quaternion $q = a + bi + cj + dk$ where a, b, c and d
  144. are real numbers, the product matrix from the left is:
  145. .. math::
  146. M = \begin{bmatrix} a &-b &-c &-d \\
  147. b & a &-d & c \\
  148. c & d & a &-b \\
  149. d &-c & b & a \end{bmatrix}
  150. Examples
  151. ========
  152. >>> from sympy import Quaternion
  153. >>> from sympy.abc import a, b, c, d
  154. >>> q1 = Quaternion(1, 0, 0, 1)
  155. >>> q2 = Quaternion(a, b, c, d)
  156. >>> q1.product_matrix_left
  157. Matrix([
  158. [1, 0, 0, -1],
  159. [0, 1, -1, 0],
  160. [0, 1, 1, 0],
  161. [1, 0, 0, 1]])
  162. >>> q1.product_matrix_left * q2.to_Matrix()
  163. Matrix([
  164. [a - d],
  165. [b - c],
  166. [b + c],
  167. [a + d]])
  168. This is equivalent to:
  169. >>> (q1 * q2).to_Matrix()
  170. Matrix([
  171. [a - d],
  172. [b - c],
  173. [b + c],
  174. [a + d]])
  175. """
  176. return Matrix([
  177. [self.a, -self.b, -self.c, -self.d],
  178. [self.b, self.a, -self.d, self.c],
  179. [self.c, self.d, self.a, -self.b],
  180. [self.d, -self.c, self.b, self.a]])
  181. @property
  182. def product_matrix_right(self):
  183. r"""Returns 4 x 4 Matrix equivalent to a Hamilton product from the
  184. right. This can be useful when treating quaternion elements as column
  185. vectors. Given a quaternion $q = a + bi + cj + dk$ where a, b, c and d
  186. are real numbers, the product matrix from the left is:
  187. .. math::
  188. M = \begin{bmatrix} a &-b &-c &-d \\
  189. b & a & d &-c \\
  190. c &-d & a & b \\
  191. d & c &-b & a \end{bmatrix}
  192. Examples
  193. ========
  194. >>> from sympy import Quaternion
  195. >>> from sympy.abc import a, b, c, d
  196. >>> q1 = Quaternion(a, b, c, d)
  197. >>> q2 = Quaternion(1, 0, 0, 1)
  198. >>> q2.product_matrix_right
  199. Matrix([
  200. [1, 0, 0, -1],
  201. [0, 1, 1, 0],
  202. [0, -1, 1, 0],
  203. [1, 0, 0, 1]])
  204. Note the switched arguments: the matrix represents the quaternion on
  205. the right, but is still considered as a matrix multiplication from the
  206. left.
  207. >>> q2.product_matrix_right * q1.to_Matrix()
  208. Matrix([
  209. [ a - d],
  210. [ b + c],
  211. [-b + c],
  212. [ a + d]])
  213. This is equivalent to:
  214. >>> (q1 * q2).to_Matrix()
  215. Matrix([
  216. [ a - d],
  217. [ b + c],
  218. [-b + c],
  219. [ a + d]])
  220. """
  221. return Matrix([
  222. [self.a, -self.b, -self.c, -self.d],
  223. [self.b, self.a, self.d, -self.c],
  224. [self.c, -self.d, self.a, self.b],
  225. [self.d, self.c, -self.b, self.a]])
  226. def to_Matrix(self, vector_only=False):
  227. """Returns elements of quaternion as a column vector.
  228. By default, a Matrix of length 4 is returned, with the real part as the
  229. first element.
  230. If vector_only is True, returns only imaginary part as a Matrix of
  231. length 3.
  232. Parameters
  233. ==========
  234. vector_only : bool
  235. If True, only imaginary part is returned.
  236. Default value: False
  237. Returns
  238. =======
  239. Matrix
  240. A column vector constructed by the elements of the quaternion.
  241. Examples
  242. ========
  243. >>> from sympy import Quaternion
  244. >>> from sympy.abc import a, b, c, d
  245. >>> q = Quaternion(a, b, c, d)
  246. >>> q
  247. a + b*i + c*j + d*k
  248. >>> q.to_Matrix()
  249. Matrix([
  250. [a],
  251. [b],
  252. [c],
  253. [d]])
  254. >>> q.to_Matrix(vector_only=True)
  255. Matrix([
  256. [b],
  257. [c],
  258. [d]])
  259. """
  260. if vector_only:
  261. return Matrix(self.args[1:])
  262. else:
  263. return Matrix(self.args)
  264. @classmethod
  265. def from_Matrix(cls, elements):
  266. """Returns quaternion from elements of a column vector`.
  267. If vector_only is True, returns only imaginary part as a Matrix of
  268. length 3.
  269. Parameters
  270. ==========
  271. elements : Matrix, list or tuple of length 3 or 4. If length is 3,
  272. assume real part is zero.
  273. Default value: False
  274. Returns
  275. =======
  276. Quaternion
  277. A quaternion created from the input elements.
  278. Examples
  279. ========
  280. >>> from sympy import Quaternion
  281. >>> from sympy.abc import a, b, c, d
  282. >>> q = Quaternion.from_Matrix([a, b, c, d])
  283. >>> q
  284. a + b*i + c*j + d*k
  285. >>> q = Quaternion.from_Matrix([b, c, d])
  286. >>> q
  287. 0 + b*i + c*j + d*k
  288. """
  289. length = len(elements)
  290. if length != 3 and length != 4:
  291. raise ValueError("Input elements must have length 3 or 4, got {} "
  292. "elements".format(length))
  293. if length == 3:
  294. return Quaternion(0, *elements)
  295. else:
  296. return Quaternion(*elements)
  297. @classmethod
  298. def from_euler(cls, angles, seq):
  299. """Returns quaternion equivalent to rotation represented by the Euler
  300. angles, in the sequence defined by ``seq``.
  301. Parameters
  302. ==========
  303. angles : list, tuple or Matrix of 3 numbers
  304. The Euler angles (in radians).
  305. seq : string of length 3
  306. Represents the sequence of rotations.
  307. For intrinsic rotations, seq must be all lowercase and its elements
  308. must be from the set ``{'x', 'y', 'z'}``
  309. For extrinsic rotations, seq must be all uppercase and its elements
  310. must be from the set ``{'X', 'Y', 'Z'}``
  311. Returns
  312. =======
  313. Quaternion
  314. The normalized rotation quaternion calculated from the Euler angles
  315. in the given sequence.
  316. Examples
  317. ========
  318. >>> from sympy import Quaternion
  319. >>> from sympy import pi
  320. >>> q = Quaternion.from_euler([pi/2, 0, 0], 'xyz')
  321. >>> q
  322. sqrt(2)/2 + sqrt(2)/2*i + 0*j + 0*k
  323. >>> q = Quaternion.from_euler([0, pi/2, pi] , 'zyz')
  324. >>> q
  325. 0 + (-sqrt(2)/2)*i + 0*j + sqrt(2)/2*k
  326. >>> q = Quaternion.from_euler([0, pi/2, pi] , 'ZYZ')
  327. >>> q
  328. 0 + sqrt(2)/2*i + 0*j + sqrt(2)/2*k
  329. """
  330. if len(angles) != 3:
  331. raise ValueError("3 angles must be given.")
  332. extrinsic = _is_extrinsic(seq)
  333. i, j, k = seq.lower()
  334. # get elementary basis vectors
  335. ei = [1 if n == i else 0 for n in 'xyz']
  336. ej = [1 if n == j else 0 for n in 'xyz']
  337. ek = [1 if n == k else 0 for n in 'xyz']
  338. # calculate distinct quaternions
  339. qi = cls.from_axis_angle(ei, angles[0])
  340. qj = cls.from_axis_angle(ej, angles[1])
  341. qk = cls.from_axis_angle(ek, angles[2])
  342. if extrinsic:
  343. return trigsimp(qk * qj * qi)
  344. else:
  345. return trigsimp(qi * qj * qk)
  346. def to_euler(self, seq, angle_addition=True, avoid_square_root=False):
  347. r"""Returns Euler angles representing same rotation as the quaternion,
  348. in the sequence given by ``seq``. This implements the method described
  349. in [1]_.
  350. For degenerate cases (gymbal lock cases), the third angle is
  351. set to zero.
  352. Parameters
  353. ==========
  354. seq : string of length 3
  355. Represents the sequence of rotations.
  356. For intrinsic rotations, seq must be all lowercase and its elements
  357. must be from the set ``{'x', 'y', 'z'}``
  358. For extrinsic rotations, seq must be all uppercase and its elements
  359. must be from the set ``{'X', 'Y', 'Z'}``
  360. angle_addition : bool
  361. When True, first and third angles are given as an addition and
  362. subtraction of two simpler ``atan2`` expressions. When False, the
  363. first and third angles are each given by a single more complicated
  364. ``atan2`` expression. This equivalent expression is given by:
  365. .. math::
  366. \operatorname{atan_2} (b,a) \pm \operatorname{atan_2} (d,c) =
  367. \operatorname{atan_2} (bc\pm ad, ac\mp bd)
  368. Default value: True
  369. avoid_square_root : bool
  370. When True, the second angle is calculated with an expression based
  371. on ``acos``, which is slightly more complicated but avoids a square
  372. root. When False, second angle is calculated with ``atan2``, which
  373. is simpler and can be better for numerical reasons (some
  374. numerical implementations of ``acos`` have problems near zero).
  375. Default value: False
  376. Returns
  377. =======
  378. Tuple
  379. The Euler angles calculated from the quaternion
  380. Examples
  381. ========
  382. >>> from sympy import Quaternion
  383. >>> from sympy.abc import a, b, c, d
  384. >>> euler = Quaternion(a, b, c, d).to_euler('zyz')
  385. >>> euler
  386. (-atan2(-b, c) + atan2(d, a),
  387. 2*atan2(sqrt(b**2 + c**2), sqrt(a**2 + d**2)),
  388. atan2(-b, c) + atan2(d, a))
  389. References
  390. ==========
  391. .. [1] https://doi.org/10.1371/journal.pone.0276302
  392. """
  393. if self.is_zero_quaternion():
  394. raise ValueError('Cannot convert a quaternion with norm 0.')
  395. angles = [0, 0, 0]
  396. extrinsic = _is_extrinsic(seq)
  397. i, j, k = seq.lower()
  398. # get index corresponding to elementary basis vectors
  399. i = 'xyz'.index(i) + 1
  400. j = 'xyz'.index(j) + 1
  401. k = 'xyz'.index(k) + 1
  402. if not extrinsic:
  403. i, k = k, i
  404. # check if sequence is symmetric
  405. symmetric = i == k
  406. if symmetric:
  407. k = 6 - i - j
  408. # parity of the permutation
  409. sign = (i - j) * (j - k) * (k - i) // 2
  410. # permutate elements
  411. elements = [self.a, self.b, self.c, self.d]
  412. a = elements[0]
  413. b = elements[i]
  414. c = elements[j]
  415. d = elements[k] * sign
  416. if not symmetric:
  417. a, b, c, d = a - c, b + d, c + a, d - b
  418. if avoid_square_root:
  419. if symmetric:
  420. n2 = self.norm()**2
  421. angles[1] = acos((a * a + b * b - c * c - d * d) / n2)
  422. else:
  423. n2 = 2 * self.norm()**2
  424. angles[1] = asin((c * c + d * d - a * a - b * b) / n2)
  425. else:
  426. angles[1] = 2 * atan2(sqrt(c * c + d * d), sqrt(a * a + b * b))
  427. if not symmetric:
  428. angles[1] -= S.Pi / 2
  429. # Check for singularities in numerical cases
  430. case = 0
  431. if is_eq(c, S.Zero) and is_eq(d, S.Zero):
  432. case = 1
  433. if is_eq(a, S.Zero) and is_eq(b, S.Zero):
  434. case = 2
  435. if case == 0:
  436. if angle_addition:
  437. angles[0] = atan2(b, a) + atan2(d, c)
  438. angles[2] = atan2(b, a) - atan2(d, c)
  439. else:
  440. angles[0] = atan2(b*c + a*d, a*c - b*d)
  441. angles[2] = atan2(b*c - a*d, a*c + b*d)
  442. else: # any degenerate case
  443. angles[2 * (not extrinsic)] = S.Zero
  444. if case == 1:
  445. angles[2 * extrinsic] = 2 * atan2(b, a)
  446. else:
  447. angles[2 * extrinsic] = 2 * atan2(d, c)
  448. angles[2 * extrinsic] *= (-1 if extrinsic else 1)
  449. # for Tait-Bryan angles
  450. if not symmetric:
  451. angles[0] *= sign
  452. if extrinsic:
  453. return tuple(angles[::-1])
  454. else:
  455. return tuple(angles)
  456. @classmethod
  457. def from_axis_angle(cls, vector, angle):
  458. """Returns a rotation quaternion given the axis and the angle of rotation.
  459. Parameters
  460. ==========
  461. vector : tuple of three numbers
  462. The vector representation of the given axis.
  463. angle : number
  464. The angle by which axis is rotated (in radians).
  465. Returns
  466. =======
  467. Quaternion
  468. The normalized rotation quaternion calculated from the given axis and the angle of rotation.
  469. Examples
  470. ========
  471. >>> from sympy import Quaternion
  472. >>> from sympy import pi, sqrt
  473. >>> q = Quaternion.from_axis_angle((sqrt(3)/3, sqrt(3)/3, sqrt(3)/3), 2*pi/3)
  474. >>> q
  475. 1/2 + 1/2*i + 1/2*j + 1/2*k
  476. """
  477. (x, y, z) = vector
  478. norm = sqrt(x**2 + y**2 + z**2)
  479. (x, y, z) = (x / norm, y / norm, z / norm)
  480. s = sin(angle * S.Half)
  481. a = cos(angle * S.Half)
  482. b = x * s
  483. c = y * s
  484. d = z * s
  485. # note that this quaternion is already normalized by construction:
  486. # c^2 + (s*x)^2 + (s*y)^2 + (s*z)^2 = c^2 + s^2*(x^2 + y^2 + z^2) = c^2 + s^2 * 1 = c^2 + s^2 = 1
  487. # so, what we return is a normalized quaternion
  488. return cls(a, b, c, d)
  489. @classmethod
  490. def from_rotation_matrix(cls, M):
  491. """Returns the equivalent quaternion of a matrix. The quaternion will be normalized
  492. only if the matrix is special orthogonal (orthogonal and det(M) = 1).
  493. Parameters
  494. ==========
  495. M : Matrix
  496. Input matrix to be converted to equivalent quaternion. M must be special
  497. orthogonal (orthogonal and det(M) = 1) for the quaternion to be normalized.
  498. Returns
  499. =======
  500. Quaternion
  501. The quaternion equivalent to given matrix.
  502. Examples
  503. ========
  504. >>> from sympy import Quaternion
  505. >>> from sympy import Matrix, symbols, cos, sin, trigsimp
  506. >>> x = symbols('x')
  507. >>> M = Matrix([[cos(x), -sin(x), 0], [sin(x), cos(x), 0], [0, 0, 1]])
  508. >>> q = trigsimp(Quaternion.from_rotation_matrix(M))
  509. >>> q
  510. sqrt(2)*sqrt(cos(x) + 1)/2 + 0*i + 0*j + sqrt(2 - 2*cos(x))*sign(sin(x))/2*k
  511. """
  512. absQ = M.det()**Rational(1, 3)
  513. a = sqrt(absQ + M[0, 0] + M[1, 1] + M[2, 2]) / 2
  514. b = sqrt(absQ + M[0, 0] - M[1, 1] - M[2, 2]) / 2
  515. c = sqrt(absQ - M[0, 0] + M[1, 1] - M[2, 2]) / 2
  516. d = sqrt(absQ - M[0, 0] - M[1, 1] + M[2, 2]) / 2
  517. b = b * sign(M[2, 1] - M[1, 2])
  518. c = c * sign(M[0, 2] - M[2, 0])
  519. d = d * sign(M[1, 0] - M[0, 1])
  520. return Quaternion(a, b, c, d)
  521. def __add__(self, other):
  522. return self.add(other)
  523. def __radd__(self, other):
  524. return self.add(other)
  525. def __sub__(self, other):
  526. return self.add(other*-1)
  527. def __mul__(self, other):
  528. return self._generic_mul(self, _sympify(other))
  529. def __rmul__(self, other):
  530. return self._generic_mul(_sympify(other), self)
  531. def __pow__(self, p):
  532. return self.pow(p)
  533. def __neg__(self):
  534. return Quaternion(-self._a, -self._b, -self._c, -self.d)
  535. def __truediv__(self, other):
  536. return self * sympify(other)**-1
  537. def __rtruediv__(self, other):
  538. return sympify(other) * self**-1
  539. def _eval_Integral(self, *args):
  540. return self.integrate(*args)
  541. def diff(self, *symbols, **kwargs):
  542. kwargs.setdefault('evaluate', True)
  543. return self.func(*[a.diff(*symbols, **kwargs) for a in self.args])
  544. def add(self, other):
  545. """Adds quaternions.
  546. Parameters
  547. ==========
  548. other : Quaternion
  549. The quaternion to add to current (self) quaternion.
  550. Returns
  551. =======
  552. Quaternion
  553. The resultant quaternion after adding self to other
  554. Examples
  555. ========
  556. >>> from sympy import Quaternion
  557. >>> from sympy import symbols
  558. >>> q1 = Quaternion(1, 2, 3, 4)
  559. >>> q2 = Quaternion(5, 6, 7, 8)
  560. >>> q1.add(q2)
  561. 6 + 8*i + 10*j + 12*k
  562. >>> q1 + 5
  563. 6 + 2*i + 3*j + 4*k
  564. >>> x = symbols('x', real = True)
  565. >>> q1.add(x)
  566. (x + 1) + 2*i + 3*j + 4*k
  567. Quaternions over complex fields :
  568. >>> from sympy import Quaternion
  569. >>> from sympy import I
  570. >>> q3 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False)
  571. >>> q3.add(2 + 3*I)
  572. (5 + 7*I) + (2 + 5*I)*i + 0*j + (7 + 8*I)*k
  573. """
  574. q1 = self
  575. q2 = sympify(other)
  576. # If q2 is a number or a SymPy expression instead of a quaternion
  577. if not isinstance(q2, Quaternion):
  578. if q1.real_field and q2.is_complex:
  579. return Quaternion(re(q2) + q1.a, im(q2) + q1.b, q1.c, q1.d)
  580. elif q2.is_commutative:
  581. return Quaternion(q1.a + q2, q1.b, q1.c, q1.d)
  582. else:
  583. raise ValueError("Only commutative expressions can be added with a Quaternion.")
  584. return Quaternion(q1.a + q2.a, q1.b + q2.b, q1.c + q2.c, q1.d
  585. + q2.d)
  586. def mul(self, other):
  587. """Multiplies quaternions.
  588. Parameters
  589. ==========
  590. other : Quaternion or symbol
  591. The quaternion to multiply to current (self) quaternion.
  592. Returns
  593. =======
  594. Quaternion
  595. The resultant quaternion after multiplying self with other
  596. Examples
  597. ========
  598. >>> from sympy import Quaternion
  599. >>> from sympy import symbols
  600. >>> q1 = Quaternion(1, 2, 3, 4)
  601. >>> q2 = Quaternion(5, 6, 7, 8)
  602. >>> q1.mul(q2)
  603. (-60) + 12*i + 30*j + 24*k
  604. >>> q1.mul(2)
  605. 2 + 4*i + 6*j + 8*k
  606. >>> x = symbols('x', real = True)
  607. >>> q1.mul(x)
  608. x + 2*x*i + 3*x*j + 4*x*k
  609. Quaternions over complex fields :
  610. >>> from sympy import Quaternion
  611. >>> from sympy import I
  612. >>> q3 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False)
  613. >>> q3.mul(2 + 3*I)
  614. (2 + 3*I)*(3 + 4*I) + (2 + 3*I)*(2 + 5*I)*i + 0*j + (2 + 3*I)*(7 + 8*I)*k
  615. """
  616. return self._generic_mul(self, _sympify(other))
  617. @staticmethod
  618. def _generic_mul(q1, q2):
  619. """Generic multiplication.
  620. Parameters
  621. ==========
  622. q1 : Quaternion or symbol
  623. q2 : Quaternion or symbol
  624. It is important to note that if neither q1 nor q2 is a Quaternion,
  625. this function simply returns q1 * q2.
  626. Returns
  627. =======
  628. Quaternion
  629. The resultant quaternion after multiplying q1 and q2
  630. Examples
  631. ========
  632. >>> from sympy import Quaternion
  633. >>> from sympy import Symbol, S
  634. >>> q1 = Quaternion(1, 2, 3, 4)
  635. >>> q2 = Quaternion(5, 6, 7, 8)
  636. >>> Quaternion._generic_mul(q1, q2)
  637. (-60) + 12*i + 30*j + 24*k
  638. >>> Quaternion._generic_mul(q1, S(2))
  639. 2 + 4*i + 6*j + 8*k
  640. >>> x = Symbol('x', real = True)
  641. >>> Quaternion._generic_mul(q1, x)
  642. x + 2*x*i + 3*x*j + 4*x*k
  643. Quaternions over complex fields :
  644. >>> from sympy import I
  645. >>> q3 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False)
  646. >>> Quaternion._generic_mul(q3, 2 + 3*I)
  647. (2 + 3*I)*(3 + 4*I) + (2 + 3*I)*(2 + 5*I)*i + 0*j + (2 + 3*I)*(7 + 8*I)*k
  648. """
  649. # None is a Quaternion:
  650. if not isinstance(q1, Quaternion) and not isinstance(q2, Quaternion):
  651. return q1 * q2
  652. # If q1 is a number or a SymPy expression instead of a quaternion
  653. if not isinstance(q1, Quaternion):
  654. if q2.real_field and q1.is_complex:
  655. return Quaternion(re(q1), im(q1), 0, 0) * q2
  656. elif q1.is_commutative:
  657. return Quaternion(q1 * q2.a, q1 * q2.b, q1 * q2.c, q1 * q2.d)
  658. else:
  659. raise ValueError("Only commutative expressions can be multiplied with a Quaternion.")
  660. # If q2 is a number or a SymPy expression instead of a quaternion
  661. if not isinstance(q2, Quaternion):
  662. if q1.real_field and q2.is_complex:
  663. return q1 * Quaternion(re(q2), im(q2), 0, 0)
  664. elif q2.is_commutative:
  665. return Quaternion(q2 * q1.a, q2 * q1.b, q2 * q1.c, q2 * q1.d)
  666. else:
  667. raise ValueError("Only commutative expressions can be multiplied with a Quaternion.")
  668. # If any of the quaternions has a fixed norm, pre-compute norm
  669. if q1._norm is None and q2._norm is None:
  670. norm = None
  671. else:
  672. norm = q1.norm() * q2.norm()
  673. return Quaternion(-q1.b*q2.b - q1.c*q2.c - q1.d*q2.d + q1.a*q2.a,
  674. q1.b*q2.a + q1.c*q2.d - q1.d*q2.c + q1.a*q2.b,
  675. -q1.b*q2.d + q1.c*q2.a + q1.d*q2.b + q1.a*q2.c,
  676. q1.b*q2.c - q1.c*q2.b + q1.d*q2.a + q1.a * q2.d,
  677. norm=norm)
  678. def _eval_conjugate(self):
  679. """Returns the conjugate of the quaternion."""
  680. q = self
  681. return Quaternion(q.a, -q.b, -q.c, -q.d, norm=q._norm)
  682. def norm(self):
  683. """Returns the norm of the quaternion."""
  684. if self._norm is None: # check if norm is pre-defined
  685. q = self
  686. # trigsimp is used to simplify sin(x)^2 + cos(x)^2 (these terms
  687. # arise when from_axis_angle is used).
  688. self._norm = sqrt(trigsimp(q.a**2 + q.b**2 + q.c**2 + q.d**2))
  689. return self._norm
  690. def normalize(self):
  691. """Returns the normalized form of the quaternion."""
  692. q = self
  693. return q * (1/q.norm())
  694. def inverse(self):
  695. """Returns the inverse of the quaternion."""
  696. q = self
  697. if not q.norm():
  698. raise ValueError("Cannot compute inverse for a quaternion with zero norm")
  699. return conjugate(q) * (1/q.norm()**2)
  700. def pow(self, p):
  701. """Finds the pth power of the quaternion.
  702. Parameters
  703. ==========
  704. p : int
  705. Power to be applied on quaternion.
  706. Returns
  707. =======
  708. Quaternion
  709. Returns the p-th power of the current quaternion.
  710. Returns the inverse if p = -1.
  711. Examples
  712. ========
  713. >>> from sympy import Quaternion
  714. >>> q = Quaternion(1, 2, 3, 4)
  715. >>> q.pow(4)
  716. 668 + (-224)*i + (-336)*j + (-448)*k
  717. """
  718. p = sympify(p)
  719. q = self
  720. if p == -1:
  721. return q.inverse()
  722. res = 1
  723. if not p.is_Integer:
  724. return NotImplemented
  725. if p < 0:
  726. q, p = q.inverse(), -p
  727. while p > 0:
  728. if p % 2 == 1:
  729. res = q * res
  730. p = p//2
  731. q = q * q
  732. return res
  733. def exp(self):
  734. """Returns the exponential of q (e^q).
  735. Returns
  736. =======
  737. Quaternion
  738. Exponential of q (e^q).
  739. Examples
  740. ========
  741. >>> from sympy import Quaternion
  742. >>> q = Quaternion(1, 2, 3, 4)
  743. >>> q.exp()
  744. E*cos(sqrt(29))
  745. + 2*sqrt(29)*E*sin(sqrt(29))/29*i
  746. + 3*sqrt(29)*E*sin(sqrt(29))/29*j
  747. + 4*sqrt(29)*E*sin(sqrt(29))/29*k
  748. """
  749. # exp(q) = e^a(cos||v|| + v/||v||*sin||v||)
  750. q = self
  751. vector_norm = sqrt(q.b**2 + q.c**2 + q.d**2)
  752. a = exp(q.a) * cos(vector_norm)
  753. b = exp(q.a) * sin(vector_norm) * q.b / vector_norm
  754. c = exp(q.a) * sin(vector_norm) * q.c / vector_norm
  755. d = exp(q.a) * sin(vector_norm) * q.d / vector_norm
  756. return Quaternion(a, b, c, d)
  757. def _ln(self):
  758. """Returns the natural logarithm of the quaternion (_ln(q)).
  759. Examples
  760. ========
  761. >>> from sympy import Quaternion
  762. >>> q = Quaternion(1, 2, 3, 4)
  763. >>> q._ln()
  764. log(sqrt(30))
  765. + 2*sqrt(29)*acos(sqrt(30)/30)/29*i
  766. + 3*sqrt(29)*acos(sqrt(30)/30)/29*j
  767. + 4*sqrt(29)*acos(sqrt(30)/30)/29*k
  768. """
  769. # _ln(q) = _ln||q|| + v/||v||*arccos(a/||q||)
  770. q = self
  771. vector_norm = sqrt(q.b**2 + q.c**2 + q.d**2)
  772. q_norm = q.norm()
  773. a = ln(q_norm)
  774. b = q.b * acos(q.a / q_norm) / vector_norm
  775. c = q.c * acos(q.a / q_norm) / vector_norm
  776. d = q.d * acos(q.a / q_norm) / vector_norm
  777. return Quaternion(a, b, c, d)
  778. def _eval_subs(self, *args):
  779. elements = [i.subs(*args) for i in self.args]
  780. norm = self._norm
  781. try:
  782. norm = norm.subs(*args)
  783. except AttributeError:
  784. pass
  785. _check_norm(elements, norm)
  786. return Quaternion(*elements, norm=norm)
  787. def _eval_evalf(self, prec):
  788. """Returns the floating point approximations (decimal numbers) of the quaternion.
  789. Returns
  790. =======
  791. Quaternion
  792. Floating point approximations of quaternion(self)
  793. Examples
  794. ========
  795. >>> from sympy import Quaternion
  796. >>> from sympy import sqrt
  797. >>> q = Quaternion(1/sqrt(1), 1/sqrt(2), 1/sqrt(3), 1/sqrt(4))
  798. >>> q.evalf()
  799. 1.00000000000000
  800. + 0.707106781186547*i
  801. + 0.577350269189626*j
  802. + 0.500000000000000*k
  803. """
  804. nprec = prec_to_dps(prec)
  805. return Quaternion(*[arg.evalf(n=nprec) for arg in self.args])
  806. def pow_cos_sin(self, p):
  807. """Computes the pth power in the cos-sin form.
  808. Parameters
  809. ==========
  810. p : int
  811. Power to be applied on quaternion.
  812. Returns
  813. =======
  814. Quaternion
  815. The p-th power in the cos-sin form.
  816. Examples
  817. ========
  818. >>> from sympy import Quaternion
  819. >>> q = Quaternion(1, 2, 3, 4)
  820. >>> q.pow_cos_sin(4)
  821. 900*cos(4*acos(sqrt(30)/30))
  822. + 1800*sqrt(29)*sin(4*acos(sqrt(30)/30))/29*i
  823. + 2700*sqrt(29)*sin(4*acos(sqrt(30)/30))/29*j
  824. + 3600*sqrt(29)*sin(4*acos(sqrt(30)/30))/29*k
  825. """
  826. # q = ||q||*(cos(a) + u*sin(a))
  827. # q^p = ||q||^p * (cos(p*a) + u*sin(p*a))
  828. q = self
  829. (v, angle) = q.to_axis_angle()
  830. q2 = Quaternion.from_axis_angle(v, p * angle)
  831. return q2 * (q.norm()**p)
  832. def integrate(self, *args):
  833. """Computes integration of quaternion.
  834. Returns
  835. =======
  836. Quaternion
  837. Integration of the quaternion(self) with the given variable.
  838. Examples
  839. ========
  840. Indefinite Integral of quaternion :
  841. >>> from sympy import Quaternion
  842. >>> from sympy.abc import x
  843. >>> q = Quaternion(1, 2, 3, 4)
  844. >>> q.integrate(x)
  845. x + 2*x*i + 3*x*j + 4*x*k
  846. Definite integral of quaternion :
  847. >>> from sympy import Quaternion
  848. >>> from sympy.abc import x
  849. >>> q = Quaternion(1, 2, 3, 4)
  850. >>> q.integrate((x, 1, 5))
  851. 4 + 8*i + 12*j + 16*k
  852. """
  853. # TODO: is this expression correct?
  854. return Quaternion(integrate(self.a, *args), integrate(self.b, *args),
  855. integrate(self.c, *args), integrate(self.d, *args))
  856. @staticmethod
  857. def rotate_point(pin, r):
  858. """Returns the coordinates of the point pin(a 3 tuple) after rotation.
  859. Parameters
  860. ==========
  861. pin : tuple
  862. A 3-element tuple of coordinates of a point which needs to be
  863. rotated.
  864. r : Quaternion or tuple
  865. Axis and angle of rotation.
  866. It's important to note that when r is a tuple, it must be of the form
  867. (axis, angle)
  868. Returns
  869. =======
  870. tuple
  871. The coordinates of the point after rotation.
  872. Examples
  873. ========
  874. >>> from sympy import Quaternion
  875. >>> from sympy import symbols, trigsimp, cos, sin
  876. >>> x = symbols('x')
  877. >>> q = Quaternion(cos(x/2), 0, 0, sin(x/2))
  878. >>> trigsimp(Quaternion.rotate_point((1, 1, 1), q))
  879. (sqrt(2)*cos(x + pi/4), sqrt(2)*sin(x + pi/4), 1)
  880. >>> (axis, angle) = q.to_axis_angle()
  881. >>> trigsimp(Quaternion.rotate_point((1, 1, 1), (axis, angle)))
  882. (sqrt(2)*cos(x + pi/4), sqrt(2)*sin(x + pi/4), 1)
  883. """
  884. if isinstance(r, tuple):
  885. # if r is of the form (vector, angle)
  886. q = Quaternion.from_axis_angle(r[0], r[1])
  887. else:
  888. # if r is a quaternion
  889. q = r.normalize()
  890. pout = q * Quaternion(0, pin[0], pin[1], pin[2]) * conjugate(q)
  891. return (pout.b, pout.c, pout.d)
  892. def to_axis_angle(self):
  893. """Returns the axis and angle of rotation of a quaternion.
  894. Returns
  895. =======
  896. tuple
  897. Tuple of (axis, angle)
  898. Examples
  899. ========
  900. >>> from sympy import Quaternion
  901. >>> q = Quaternion(1, 1, 1, 1)
  902. >>> (axis, angle) = q.to_axis_angle()
  903. >>> axis
  904. (sqrt(3)/3, sqrt(3)/3, sqrt(3)/3)
  905. >>> angle
  906. 2*pi/3
  907. """
  908. q = self
  909. if q.a.is_negative:
  910. q = q * -1
  911. q = q.normalize()
  912. angle = trigsimp(2 * acos(q.a))
  913. # Since quaternion is normalised, q.a is less than 1.
  914. s = sqrt(1 - q.a*q.a)
  915. x = trigsimp(q.b / s)
  916. y = trigsimp(q.c / s)
  917. z = trigsimp(q.d / s)
  918. v = (x, y, z)
  919. t = (v, angle)
  920. return t
  921. def to_rotation_matrix(self, v=None, homogeneous=True):
  922. """Returns the equivalent rotation transformation matrix of the quaternion
  923. which represents rotation about the origin if v is not passed.
  924. Parameters
  925. ==========
  926. v : tuple or None
  927. Default value: None
  928. homogeneous : bool
  929. When True, gives an expression that may be more efficient for
  930. symbolic calculations but less so for direct evaluation. Both
  931. formulas are mathematically equivalent.
  932. Default value: True
  933. Returns
  934. =======
  935. tuple
  936. Returns the equivalent rotation transformation matrix of the quaternion
  937. which represents rotation about the origin if v is not passed.
  938. Examples
  939. ========
  940. >>> from sympy import Quaternion
  941. >>> from sympy import symbols, trigsimp, cos, sin
  942. >>> x = symbols('x')
  943. >>> q = Quaternion(cos(x/2), 0, 0, sin(x/2))
  944. >>> trigsimp(q.to_rotation_matrix())
  945. Matrix([
  946. [cos(x), -sin(x), 0],
  947. [sin(x), cos(x), 0],
  948. [ 0, 0, 1]])
  949. Generates a 4x4 transformation matrix (used for rotation about a point
  950. other than the origin) if the point(v) is passed as an argument.
  951. """
  952. q = self
  953. s = q.norm()**-2
  954. # diagonal elements are different according to parameter normal
  955. if homogeneous:
  956. m00 = s*(q.a**2 + q.b**2 - q.c**2 - q.d**2)
  957. m11 = s*(q.a**2 - q.b**2 + q.c**2 - q.d**2)
  958. m22 = s*(q.a**2 - q.b**2 - q.c**2 + q.d**2)
  959. else:
  960. m00 = 1 - 2*s*(q.c**2 + q.d**2)
  961. m11 = 1 - 2*s*(q.b**2 + q.d**2)
  962. m22 = 1 - 2*s*(q.b**2 + q.c**2)
  963. m01 = 2*s*(q.b*q.c - q.d*q.a)
  964. m02 = 2*s*(q.b*q.d + q.c*q.a)
  965. m10 = 2*s*(q.b*q.c + q.d*q.a)
  966. m12 = 2*s*(q.c*q.d - q.b*q.a)
  967. m20 = 2*s*(q.b*q.d - q.c*q.a)
  968. m21 = 2*s*(q.c*q.d + q.b*q.a)
  969. if not v:
  970. return Matrix([[m00, m01, m02], [m10, m11, m12], [m20, m21, m22]])
  971. else:
  972. (x, y, z) = v
  973. m03 = x - x*m00 - y*m01 - z*m02
  974. m13 = y - x*m10 - y*m11 - z*m12
  975. m23 = z - x*m20 - y*m21 - z*m22
  976. m30 = m31 = m32 = 0
  977. m33 = 1
  978. return Matrix([[m00, m01, m02, m03], [m10, m11, m12, m13],
  979. [m20, m21, m22, m23], [m30, m31, m32, m33]])
  980. def scalar_part(self):
  981. r"""Returns scalar part($\mathbf{S}(q)$) of the quaternion q.
  982. Explanation
  983. ===========
  984. Given a quaternion $q = a + bi + cj + dk$, returns $\mathbf{S}(q) = a$.
  985. Examples
  986. ========
  987. >>> from sympy.algebras.quaternion import Quaternion
  988. >>> q = Quaternion(4, 8, 13, 12)
  989. >>> q.scalar_part()
  990. 4
  991. """
  992. return self.a
  993. def vector_part(self):
  994. r"""
  995. Returns vector part($\mathbf{V}(q)$) of the quaternion q.
  996. Explanation
  997. ===========
  998. Given a quaternion $q = a + bi + cj + dk$, returns $\mathbf{V}(q) = bi + cj + dk$.
  999. Examples
  1000. ========
  1001. >>> from sympy.algebras.quaternion import Quaternion
  1002. >>> q = Quaternion(1, 1, 1, 1)
  1003. >>> q.vector_part()
  1004. 0 + 1*i + 1*j + 1*k
  1005. >>> q = Quaternion(4, 8, 13, 12)
  1006. >>> q.vector_part()
  1007. 0 + 8*i + 13*j + 12*k
  1008. """
  1009. return Quaternion(0, self.b, self.c, self.d)
  1010. def axis(self):
  1011. r"""
  1012. Returns the axis($\mathbf{Ax}(q)$) of the quaternion.
  1013. Explanation
  1014. ===========
  1015. Given a quaternion $q = a + bi + cj + dk$, returns $\mathbf{Ax}(q)$ i.e., the versor of the vector part of that quaternion
  1016. equal to $\mathbf{U}[\mathbf{V}(q)]$.
  1017. The axis is always an imaginary unit with square equal to $-1 + 0i + 0j + 0k$.
  1018. Examples
  1019. ========
  1020. >>> from sympy.algebras.quaternion import Quaternion
  1021. >>> q = Quaternion(1, 1, 1, 1)
  1022. >>> q.axis()
  1023. 0 + sqrt(3)/3*i + sqrt(3)/3*j + sqrt(3)/3*k
  1024. See Also
  1025. ========
  1026. vector_part
  1027. """
  1028. axis = self.vector_part().normalize()
  1029. return Quaternion(0, axis.b, axis.c, axis.d)
  1030. def is_pure(self):
  1031. """
  1032. Returns true if the quaternion is pure, false if the quaternion is not pure
  1033. or returns none if it is unknown.
  1034. Explanation
  1035. ===========
  1036. A pure quaternion (also a vector quaternion) is a quaternion with scalar
  1037. part equal to 0.
  1038. Examples
  1039. ========
  1040. >>> from sympy.algebras.quaternion import Quaternion
  1041. >>> q = Quaternion(0, 8, 13, 12)
  1042. >>> q.is_pure()
  1043. True
  1044. See Also
  1045. ========
  1046. scalar_part
  1047. """
  1048. return self.a.is_zero
  1049. def is_zero_quaternion(self):
  1050. """
  1051. Returns true if the quaternion is a zero quaternion or false if it is not a zero quaternion
  1052. and None if the value is unknown.
  1053. Explanation
  1054. ===========
  1055. A zero quaternion is a quaternion with both scalar part and
  1056. vector part equal to 0.
  1057. Examples
  1058. ========
  1059. >>> from sympy.algebras.quaternion import Quaternion
  1060. >>> q = Quaternion(1, 0, 0, 0)
  1061. >>> q.is_zero_quaternion()
  1062. False
  1063. >>> q = Quaternion(0, 0, 0, 0)
  1064. >>> q.is_zero_quaternion()
  1065. True
  1066. See Also
  1067. ========
  1068. scalar_part
  1069. vector_part
  1070. """
  1071. return self.norm().is_zero
  1072. def angle(self):
  1073. r"""
  1074. Returns the angle of the quaternion measured in the real-axis plane.
  1075. Explanation
  1076. ===========
  1077. Given a quaternion $q = a + bi + cj + dk$ where a, b, c and d
  1078. are real numbers, returns the angle of the quaternion given by
  1079. .. math::
  1080. angle := atan2(\sqrt{b^2 + c^2 + d^2}, {a})
  1081. Examples
  1082. ========
  1083. >>> from sympy.algebras.quaternion import Quaternion
  1084. >>> q = Quaternion(1, 4, 4, 4)
  1085. >>> q.angle()
  1086. atan(4*sqrt(3))
  1087. """
  1088. return atan2(self.vector_part().norm(), self.scalar_part())
  1089. def arc_coplanar(self, other):
  1090. """
  1091. Returns True if the transformation arcs represented by the input quaternions happen in the same plane.
  1092. Explanation
  1093. ===========
  1094. Two quaternions are said to be coplanar (in this arc sense) when their axes are parallel.
  1095. The plane of a quaternion is the one normal to its axis.
  1096. Parameters
  1097. ==========
  1098. other : a Quaternion
  1099. Returns
  1100. =======
  1101. True : if the planes of the two quaternions are the same, apart from its orientation/sign.
  1102. False : if the planes of the two quaternions are not the same, apart from its orientation/sign.
  1103. None : if plane of either of the quaternion is unknown.
  1104. Examples
  1105. ========
  1106. >>> from sympy.algebras.quaternion import Quaternion
  1107. >>> q1 = Quaternion(1, 4, 4, 4)
  1108. >>> q2 = Quaternion(3, 8, 8, 8)
  1109. >>> Quaternion.arc_coplanar(q1, q2)
  1110. True
  1111. >>> q1 = Quaternion(2, 8, 13, 12)
  1112. >>> Quaternion.arc_coplanar(q1, q2)
  1113. False
  1114. See Also
  1115. ========
  1116. vector_coplanar
  1117. is_pure
  1118. """
  1119. if (self.is_zero_quaternion()) or (other.is_zero_quaternion()):
  1120. raise ValueError('Neither of the given quaternions can be 0')
  1121. return fuzzy_or([(self.axis() - other.axis()).is_zero_quaternion(), (self.axis() + other.axis()).is_zero_quaternion()])
  1122. @classmethod
  1123. def vector_coplanar(cls, q1, q2, q3):
  1124. r"""
  1125. Returns True if the axis of the pure quaternions seen as 3D vectors
  1126. q1, q2, and q3 are coplanar.
  1127. Explanation
  1128. ===========
  1129. Three pure quaternions are vector coplanar if the quaternions seen as 3D vectors are coplanar.
  1130. Parameters
  1131. ==========
  1132. q1
  1133. A pure Quaternion.
  1134. q2
  1135. A pure Quaternion.
  1136. q3
  1137. A pure Quaternion.
  1138. Returns
  1139. =======
  1140. True : if the axis of the pure quaternions seen as 3D vectors
  1141. q1, q2, and q3 are coplanar.
  1142. False : if the axis of the pure quaternions seen as 3D vectors
  1143. q1, q2, and q3 are not coplanar.
  1144. None : if the axis of the pure quaternions seen as 3D vectors
  1145. q1, q2, and q3 are coplanar is unknown.
  1146. Examples
  1147. ========
  1148. >>> from sympy.algebras.quaternion import Quaternion
  1149. >>> q1 = Quaternion(0, 4, 4, 4)
  1150. >>> q2 = Quaternion(0, 8, 8, 8)
  1151. >>> q3 = Quaternion(0, 24, 24, 24)
  1152. >>> Quaternion.vector_coplanar(q1, q2, q3)
  1153. True
  1154. >>> q1 = Quaternion(0, 8, 16, 8)
  1155. >>> q2 = Quaternion(0, 8, 3, 12)
  1156. >>> Quaternion.vector_coplanar(q1, q2, q3)
  1157. False
  1158. See Also
  1159. ========
  1160. axis
  1161. is_pure
  1162. """
  1163. if fuzzy_not(q1.is_pure()) or fuzzy_not(q2.is_pure()) or fuzzy_not(q3.is_pure()):
  1164. raise ValueError('The given quaternions must be pure')
  1165. M = Matrix([[q1.b, q1.c, q1.d], [q2.b, q2.c, q2.d], [q3.b, q3.c, q3.d]]).det()
  1166. return M.is_zero
  1167. def parallel(self, other):
  1168. """
  1169. Returns True if the two pure quaternions seen as 3D vectors are parallel.
  1170. Explanation
  1171. ===========
  1172. Two pure quaternions are called parallel when their vector product is commutative which
  1173. implies that the quaternions seen as 3D vectors have same direction.
  1174. Parameters
  1175. ==========
  1176. other : a Quaternion
  1177. Returns
  1178. =======
  1179. True : if the two pure quaternions seen as 3D vectors are parallel.
  1180. False : if the two pure quaternions seen as 3D vectors are not parallel.
  1181. None : if the two pure quaternions seen as 3D vectors are parallel is unknown.
  1182. Examples
  1183. ========
  1184. >>> from sympy.algebras.quaternion import Quaternion
  1185. >>> q = Quaternion(0, 4, 4, 4)
  1186. >>> q1 = Quaternion(0, 8, 8, 8)
  1187. >>> q.parallel(q1)
  1188. True
  1189. >>> q1 = Quaternion(0, 8, 13, 12)
  1190. >>> q.parallel(q1)
  1191. False
  1192. """
  1193. if fuzzy_not(self.is_pure()) or fuzzy_not(other.is_pure()):
  1194. raise ValueError('The provided quaternions must be pure')
  1195. return (self*other - other*self).is_zero_quaternion()
  1196. def orthogonal(self, other):
  1197. """
  1198. Returns the orthogonality of two quaternions.
  1199. Explanation
  1200. ===========
  1201. Two pure quaternions are called orthogonal when their product is anti-commutative.
  1202. Parameters
  1203. ==========
  1204. other : a Quaternion
  1205. Returns
  1206. =======
  1207. True : if the two pure quaternions seen as 3D vectors are orthogonal.
  1208. False : if the two pure quaternions seen as 3D vectors are not orthogonal.
  1209. None : if the two pure quaternions seen as 3D vectors are orthogonal is unknown.
  1210. Examples
  1211. ========
  1212. >>> from sympy.algebras.quaternion import Quaternion
  1213. >>> q = Quaternion(0, 4, 4, 4)
  1214. >>> q1 = Quaternion(0, 8, 8, 8)
  1215. >>> q.orthogonal(q1)
  1216. False
  1217. >>> q1 = Quaternion(0, 2, 2, 0)
  1218. >>> q = Quaternion(0, 2, -2, 0)
  1219. >>> q.orthogonal(q1)
  1220. True
  1221. """
  1222. if fuzzy_not(self.is_pure()) or fuzzy_not(other.is_pure()):
  1223. raise ValueError('The given quaternions must be pure')
  1224. return (self*other + other*self).is_zero_quaternion()
  1225. def index_vector(self):
  1226. r"""
  1227. Returns the index vector of the quaternion.
  1228. Explanation
  1229. ===========
  1230. Index vector is given by $\mathbf{T}(q)$ multiplied by $\mathbf{Ax}(q)$ where $\mathbf{Ax}(q)$ is the axis of the quaternion q,
  1231. and mod(q) is the $\mathbf{T}(q)$ (magnitude) of the quaternion.
  1232. Returns
  1233. =======
  1234. Quaternion: representing index vector of the provided quaternion.
  1235. Examples
  1236. ========
  1237. >>> from sympy.algebras.quaternion import Quaternion
  1238. >>> q = Quaternion(2, 4, 2, 4)
  1239. >>> q.index_vector()
  1240. 0 + 4*sqrt(10)/3*i + 2*sqrt(10)/3*j + 4*sqrt(10)/3*k
  1241. See Also
  1242. ========
  1243. axis
  1244. norm
  1245. """
  1246. return self.norm() * self.axis()
  1247. def mensor(self):
  1248. """
  1249. Returns the natural logarithm of the norm(magnitude) of the quaternion.
  1250. Examples
  1251. ========
  1252. >>> from sympy.algebras.quaternion import Quaternion
  1253. >>> q = Quaternion(2, 4, 2, 4)
  1254. >>> q.mensor()
  1255. log(2*sqrt(10))
  1256. >>> q.norm()
  1257. 2*sqrt(10)
  1258. See Also
  1259. ========
  1260. norm
  1261. """
  1262. return ln(self.norm())