quaternion.py 46 KB

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