Generateurv2/backend/env/lib/python3.10/site-packages/sympy/algebras/quaternion.py

753 lines
21 KiB
Python
Raw Normal View History

2022-06-24 17:14:37 +02:00
# References :
# http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/
# https://en.wikipedia.org/wiki/Quaternion
from sympy import S, Rational
from sympy import re, im, conjugate, sign
from sympy import sqrt, sin, cos, acos, exp, ln
from sympy import trigsimp
from sympy import integrate
from sympy import Matrix
from sympy import sympify
from sympy.core.evalf import prec_to_dps
from sympy.core.expr import Expr
class Quaternion(Expr):
"""Provides basic quaternion operations.
Quaternion objects can be instantiated as Quaternion(a, b, c, d)
as in (a + b*i + c*j + d*k).
Examples
========
>>> from sympy.algebras.quaternion import Quaternion
>>> q = Quaternion(1, 2, 3, 4)
>>> q
1 + 2*i + 3*j + 4*k
Quaternions over complex fields can be defined as :
>>> from sympy.algebras.quaternion import Quaternion
>>> from sympy import symbols, I
>>> x = symbols('x')
>>> q1 = Quaternion(x, x**3, x, x**2, real_field = False)
>>> q2 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False)
>>> q1
x + x**3*i + x*j + x**2*k
>>> q2
(3 + 4*I) + (2 + 5*I)*i + 0*j + (7 + 8*I)*k
"""
_op_priority = 11.0
is_commutative = False
def __new__(cls, a=0, b=0, c=0, d=0, real_field=True):
a = sympify(a)
b = sympify(b)
c = sympify(c)
d = sympify(d)
if any(i.is_commutative is False for i in [a, b, c, d]):
raise ValueError("arguments have to be commutative")
else:
obj = Expr.__new__(cls, a, b, c, d)
obj._a = a
obj._b = b
obj._c = c
obj._d = d
obj._real_field = real_field
return obj
@property
def a(self):
return self._a
@property
def b(self):
return self._b
@property
def c(self):
return self._c
@property
def d(self):
return self._d
@property
def real_field(self):
return self._real_field
@classmethod
def from_axis_angle(cls, vector, angle):
"""Returns a rotation quaternion given the axis and the angle of rotation.
Parameters
==========
vector : tuple of three numbers
The vector representation of the given axis.
angle : number
The angle by which axis is rotated (in radians).
Returns
=======
Quaternion
The normalized rotation quaternion calculated from the given axis and the angle of rotation.
Examples
========
>>> from sympy.algebras.quaternion import Quaternion
>>> from sympy import pi, sqrt
>>> q = Quaternion.from_axis_angle((sqrt(3)/3, sqrt(3)/3, sqrt(3)/3), 2*pi/3)
>>> q
1/2 + 1/2*i + 1/2*j + 1/2*k
"""
(x, y, z) = vector
norm = sqrt(x**2 + y**2 + z**2)
(x, y, z) = (x / norm, y / norm, z / norm)
s = sin(angle * S.Half)
a = cos(angle * S.Half)
b = x * s
c = y * s
d = z * s
# note that this quaternion is already normalized by construction:
# 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
# so, what we return is a normalized quaternion
return cls(a, b, c, d)
@classmethod
def from_rotation_matrix(cls, M):
"""Returns the equivalent quaternion of a matrix. The quaternion will be normalized
only if the matrix is special orthogonal (orthogonal and det(M) = 1).
Parameters
==========
M : Matrix
Input matrix to be converted to equivalent quaternion. M must be special
orthogonal (orthogonal and det(M) = 1) for the quaternion to be normalized.
Returns
=======
Quaternion
The quaternion equivalent to given matrix.
Examples
========
>>> from sympy.algebras.quaternion import Quaternion
>>> from sympy import Matrix, symbols, cos, sin, trigsimp
>>> x = symbols('x')
>>> M = Matrix([[cos(x), -sin(x), 0], [sin(x), cos(x), 0], [0, 0, 1]])
>>> q = trigsimp(Quaternion.from_rotation_matrix(M))
>>> q
sqrt(2)*sqrt(cos(x) + 1)/2 + 0*i + 0*j + sqrt(2 - 2*cos(x))*sign(sin(x))/2*k
"""
absQ = M.det()**Rational(1, 3)
a = sqrt(absQ + M[0, 0] + M[1, 1] + M[2, 2]) / 2
b = sqrt(absQ + M[0, 0] - M[1, 1] - M[2, 2]) / 2
c = sqrt(absQ - M[0, 0] + M[1, 1] - M[2, 2]) / 2
d = sqrt(absQ - M[0, 0] - M[1, 1] + M[2, 2]) / 2
b = b * sign(M[2, 1] - M[1, 2])
c = c * sign(M[0, 2] - M[2, 0])
d = d * sign(M[1, 0] - M[0, 1])
return Quaternion(a, b, c, d)
def __add__(self, other):
return self.add(other)
def __radd__(self, other):
return self.add(other)
def __sub__(self, other):
return self.add(other*-1)
def __mul__(self, other):
return self._generic_mul(self, other)
def __rmul__(self, other):
return self._generic_mul(other, self)
def __pow__(self, p):
return self.pow(p)
def __neg__(self):
return Quaternion(-self._a, -self._b, -self._c, -self.d)
def __truediv__(self, other):
return self * sympify(other)**-1
def __rtruediv__(self, other):
return sympify(other) * self**-1
def _eval_Integral(self, *args):
return self.integrate(*args)
def diff(self, *symbols, **kwargs):
kwargs.setdefault('evaluate', True)
return self.func(*[a.diff(*symbols, **kwargs) for a in self.args])
def add(self, other):
"""Adds quaternions.
Parameters
==========
other : Quaternion
The quaternion to add to current (self) quaternion.
Returns
=======
Quaternion
The resultant quaternion after adding self to other
Examples
========
>>> from sympy.algebras.quaternion import Quaternion
>>> from sympy import symbols
>>> q1 = Quaternion(1, 2, 3, 4)
>>> q2 = Quaternion(5, 6, 7, 8)
>>> q1.add(q2)
6 + 8*i + 10*j + 12*k
>>> q1 + 5
6 + 2*i + 3*j + 4*k
>>> x = symbols('x', real = True)
>>> q1.add(x)
(x + 1) + 2*i + 3*j + 4*k
Quaternions over complex fields :
>>> from sympy.algebras.quaternion import Quaternion
>>> from sympy import I
>>> q3 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False)
>>> q3.add(2 + 3*I)
(5 + 7*I) + (2 + 5*I)*i + 0*j + (7 + 8*I)*k
"""
q1 = self
q2 = sympify(other)
# If q2 is a number or a sympy expression instead of a quaternion
if not isinstance(q2, Quaternion):
if q1.real_field and q2.is_complex:
return Quaternion(re(q2) + q1.a, im(q2) + q1.b, q1.c, q1.d)
elif q2.is_commutative:
return Quaternion(q1.a + q2, q1.b, q1.c, q1.d)
else:
raise ValueError("Only commutative expressions can be added with a Quaternion.")
return Quaternion(q1.a + q2.a, q1.b + q2.b, q1.c + q2.c, q1.d
+ q2.d)
def mul(self, other):
"""Multiplies quaternions.
Parameters
==========
other : Quaternion or symbol
The quaternion to multiply to current (self) quaternion.
Returns
=======
Quaternion
The resultant quaternion after multiplying self with other
Examples
========
>>> from sympy.algebras.quaternion import Quaternion
>>> from sympy import symbols
>>> q1 = Quaternion(1, 2, 3, 4)
>>> q2 = Quaternion(5, 6, 7, 8)
>>> q1.mul(q2)
(-60) + 12*i + 30*j + 24*k
>>> q1.mul(2)
2 + 4*i + 6*j + 8*k
>>> x = symbols('x', real = True)
>>> q1.mul(x)
x + 2*x*i + 3*x*j + 4*x*k
Quaternions over complex fields :
>>> from sympy.algebras.quaternion import Quaternion
>>> from sympy import I
>>> q3 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False)
>>> q3.mul(2 + 3*I)
(2 + 3*I)*(3 + 4*I) + (2 + 3*I)*(2 + 5*I)*i + 0*j + (2 + 3*I)*(7 + 8*I)*k
"""
return self._generic_mul(self, other)
@staticmethod
def _generic_mul(q1, q2):
"""Generic multiplication.
Parameters
==========
q1 : Quaternion or symbol
q2 : Quaternion or symbol
It's important to note that if neither q1 nor q2 is a Quaternion,
this function simply returns q1 * q2.
Returns
=======
Quaternion
The resultant quaternion after multiplying q1 and q2
Examples
========
>>> from sympy.algebras.quaternion import Quaternion
>>> from sympy import Symbol
>>> q1 = Quaternion(1, 2, 3, 4)
>>> q2 = Quaternion(5, 6, 7, 8)
>>> Quaternion._generic_mul(q1, q2)
(-60) + 12*i + 30*j + 24*k
>>> Quaternion._generic_mul(q1, 2)
2 + 4*i + 6*j + 8*k
>>> x = Symbol('x', real = True)
>>> Quaternion._generic_mul(q1, x)
x + 2*x*i + 3*x*j + 4*x*k
Quaternions over complex fields :
>>> from sympy.algebras.quaternion import Quaternion
>>> from sympy import I
>>> q3 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False)
>>> Quaternion._generic_mul(q3, 2 + 3*I)
(2 + 3*I)*(3 + 4*I) + (2 + 3*I)*(2 + 5*I)*i + 0*j + (2 + 3*I)*(7 + 8*I)*k
"""
q1 = sympify(q1)
q2 = sympify(q2)
# None is a Quaternion:
if not isinstance(q1, Quaternion) and not isinstance(q2, Quaternion):
return q1 * q2
# If q1 is a number or a sympy expression instead of a quaternion
if not isinstance(q1, Quaternion):
if q2.real_field and q1.is_complex:
return Quaternion(re(q1), im(q1), 0, 0) * q2
elif q1.is_commutative:
return Quaternion(q1 * q2.a, q1 * q2.b, q1 * q2.c, q1 * q2.d)
else:
raise ValueError("Only commutative expressions can be multiplied with a Quaternion.")
# If q2 is a number or a sympy expression instead of a quaternion
if not isinstance(q2, Quaternion):
if q1.real_field and q2.is_complex:
return q1 * Quaternion(re(q2), im(q2), 0, 0)
elif q2.is_commutative:
return Quaternion(q2 * q1.a, q2 * q1.b, q2 * q1.c, q2 * q1.d)
else:
raise ValueError("Only commutative expressions can be multiplied with a Quaternion.")
return Quaternion(-q1.b*q2.b - q1.c*q2.c - q1.d*q2.d + q1.a*q2.a,
q1.b*q2.a + q1.c*q2.d - q1.d*q2.c + q1.a*q2.b,
-q1.b*q2.d + q1.c*q2.a + q1.d*q2.b + q1.a*q2.c,
q1.b*q2.c - q1.c*q2.b + q1.d*q2.a + q1.a * q2.d)
def _eval_conjugate(self):
"""Returns the conjugate of the quaternion."""
q = self
return Quaternion(q.a, -q.b, -q.c, -q.d)
def norm(self):
"""Returns the norm of the quaternion."""
q = self
# trigsimp is used to simplify sin(x)^2 + cos(x)^2 (these terms
# arise when from_axis_angle is used).
return sqrt(trigsimp(q.a**2 + q.b**2 + q.c**2 + q.d**2))
def normalize(self):
"""Returns the normalized form of the quaternion."""
q = self
return q * (1/q.norm())
def inverse(self):
"""Returns the inverse of the quaternion."""
q = self
if not q.norm():
raise ValueError("Cannot compute inverse for a quaternion with zero norm")
return conjugate(q) * (1/q.norm()**2)
def pow(self, p):
"""Finds the pth power of the quaternion.
Parameters
==========
p : int
Power to be applied on quaternion.
Returns
=======
Quaternion
Returns the p-th power of the current quaternion.
Returns the inverse if p = -1.
Examples
========
>>> from sympy.algebras.quaternion import Quaternion
>>> q = Quaternion(1, 2, 3, 4)
>>> q.pow(4)
668 + (-224)*i + (-336)*j + (-448)*k
"""
p = sympify(p)
q = self
if p == -1:
return q.inverse()
res = 1
if not p.is_Integer:
return NotImplemented
if p < 0:
q, p = q.inverse(), -p
while p > 0:
if p % 2 == 1:
res = q * res
p = p//2
q = q * q
return res
def exp(self):
"""Returns the exponential of q (e^q).
Returns
=======
Quaternion
Exponential of q (e^q).
Examples
========
>>> from sympy.algebras.quaternion import Quaternion
>>> q = Quaternion(1, 2, 3, 4)
>>> q.exp()
E*cos(sqrt(29))
+ 2*sqrt(29)*E*sin(sqrt(29))/29*i
+ 3*sqrt(29)*E*sin(sqrt(29))/29*j
+ 4*sqrt(29)*E*sin(sqrt(29))/29*k
"""
# exp(q) = e^a(cos||v|| + v/||v||*sin||v||)
q = self
vector_norm = sqrt(q.b**2 + q.c**2 + q.d**2)
a = exp(q.a) * cos(vector_norm)
b = exp(q.a) * sin(vector_norm) * q.b / vector_norm
c = exp(q.a) * sin(vector_norm) * q.c / vector_norm
d = exp(q.a) * sin(vector_norm) * q.d / vector_norm
return Quaternion(a, b, c, d)
def _ln(self):
"""Returns the natural logarithm of the quaternion (_ln(q)).
Examples
========
>>> from sympy.algebras.quaternion import Quaternion
>>> q = Quaternion(1, 2, 3, 4)
>>> q._ln()
log(sqrt(30))
+ 2*sqrt(29)*acos(sqrt(30)/30)/29*i
+ 3*sqrt(29)*acos(sqrt(30)/30)/29*j
+ 4*sqrt(29)*acos(sqrt(30)/30)/29*k
"""
# _ln(q) = _ln||q|| + v/||v||*arccos(a/||q||)
q = self
vector_norm = sqrt(q.b**2 + q.c**2 + q.d**2)
q_norm = q.norm()
a = ln(q_norm)
b = q.b * acos(q.a / q_norm) / vector_norm
c = q.c * acos(q.a / q_norm) / vector_norm
d = q.d * acos(q.a / q_norm) / vector_norm
return Quaternion(a, b, c, d)
def _eval_evalf(self, prec):
"""Returns the floating point approximations (decimal numbers) of the quaternion.
Returns
=======
Quaternion
Floating point approximations of quaternion(self)
Examples
========
>>> from sympy.algebras.quaternion import Quaternion
>>> from sympy import sqrt
>>> q = Quaternion(1/sqrt(1), 1/sqrt(2), 1/sqrt(3), 1/sqrt(4))
>>> q.evalf()
1.00000000000000
+ 0.707106781186547*i
+ 0.577350269189626*j
+ 0.500000000000000*k
"""
return Quaternion(*[arg.evalf(n=prec_to_dps(prec)) for arg in self.args])
def pow_cos_sin(self, p):
"""Computes the pth power in the cos-sin form.
Parameters
==========
p : int
Power to be applied on quaternion.
Returns
=======
Quaternion
The p-th power in the cos-sin form.
Examples
========
>>> from sympy.algebras.quaternion import Quaternion
>>> q = Quaternion(1, 2, 3, 4)
>>> q.pow_cos_sin(4)
900*cos(4*acos(sqrt(30)/30))
+ 1800*sqrt(29)*sin(4*acos(sqrt(30)/30))/29*i
+ 2700*sqrt(29)*sin(4*acos(sqrt(30)/30))/29*j
+ 3600*sqrt(29)*sin(4*acos(sqrt(30)/30))/29*k
"""
# q = ||q||*(cos(a) + u*sin(a))
# q^p = ||q||^p * (cos(p*a) + u*sin(p*a))
q = self
(v, angle) = q.to_axis_angle()
q2 = Quaternion.from_axis_angle(v, p * angle)
return q2 * (q.norm()**p)
def integrate(self, *args):
"""Computes integration of quaternion.
Returns
=======
Quaternion
Integration of the quaternion(self) with the given variable.
Examples
========
Indefinite Integral of quaternion :
>>> from sympy.algebras.quaternion import Quaternion
>>> from sympy.abc import x
>>> q = Quaternion(1, 2, 3, 4)
>>> q.integrate(x)
x + 2*x*i + 3*x*j + 4*x*k
Definite integral of quaternion :
>>> from sympy.algebras.quaternion import Quaternion
>>> from sympy.abc import x
>>> q = Quaternion(1, 2, 3, 4)
>>> q.integrate((x, 1, 5))
4 + 8*i + 12*j + 16*k
"""
# TODO: is this expression correct?
return Quaternion(integrate(self.a, *args), integrate(self.b, *args),
integrate(self.c, *args), integrate(self.d, *args))
@staticmethod
def rotate_point(pin, r):
"""Returns the coordinates of the point pin(a 3 tuple) after rotation.
Parameters
==========
pin : tuple
A 3-element tuple of coordinates of a point which needs to be
rotated.
r : Quaternion or tuple
Axis and angle of rotation.
It's important to note that when r is a tuple, it must be of the form
(axis, angle)
Returns
=======
tuple
The coordinates of the point after rotation.
Examples
========
>>> from sympy.algebras.quaternion import Quaternion
>>> from sympy import symbols, trigsimp, cos, sin
>>> x = symbols('x')
>>> q = Quaternion(cos(x/2), 0, 0, sin(x/2))
>>> trigsimp(Quaternion.rotate_point((1, 1, 1), q))
(sqrt(2)*cos(x + pi/4), sqrt(2)*sin(x + pi/4), 1)
>>> (axis, angle) = q.to_axis_angle()
>>> trigsimp(Quaternion.rotate_point((1, 1, 1), (axis, angle)))
(sqrt(2)*cos(x + pi/4), sqrt(2)*sin(x + pi/4), 1)
"""
if isinstance(r, tuple):
# if r is of the form (vector, angle)
q = Quaternion.from_axis_angle(r[0], r[1])
else:
# if r is a quaternion
q = r.normalize()
pout = q * Quaternion(0, pin[0], pin[1], pin[2]) * conjugate(q)
return (pout.b, pout.c, pout.d)
def to_axis_angle(self):
"""Returns the axis and angle of rotation of a quaternion
Returns
=======
tuple
Tuple of (axis, angle)
Examples
========
>>> from sympy.algebras.quaternion import Quaternion
>>> q = Quaternion(1, 1, 1, 1)
>>> (axis, angle) = q.to_axis_angle()
>>> axis
(sqrt(3)/3, sqrt(3)/3, sqrt(3)/3)
>>> angle
2*pi/3
"""
q = self
if q.a.is_negative:
q = q * -1
q = q.normalize()
angle = trigsimp(2 * acos(q.a))
# Since quaternion is normalised, q.a is less than 1.
s = sqrt(1 - q.a*q.a)
x = trigsimp(q.b / s)
y = trigsimp(q.c / s)
z = trigsimp(q.d / s)
v = (x, y, z)
t = (v, angle)
return t
def to_rotation_matrix(self, v=None):
"""Returns the equivalent rotation transformation matrix of the quaternion
which represents rotation about the origin if v is not passed.
Parameters
==========
v : tuple or None
Default value: None
Returns
=======
tuple
Returns the equivalent rotation transformation matrix of the quaternion
which represents rotation about the origin if v is not passed.
Examples
========
>>> from sympy.algebras.quaternion import Quaternion
>>> from sympy import symbols, trigsimp, cos, sin
>>> x = symbols('x')
>>> q = Quaternion(cos(x/2), 0, 0, sin(x/2))
>>> trigsimp(q.to_rotation_matrix())
Matrix([
[cos(x), -sin(x), 0],
[sin(x), cos(x), 0],
[ 0, 0, 1]])
Generates a 4x4 transformation matrix (used for rotation about a point
other than the origin) if the point(v) is passed as an argument.
Examples
========
>>> from sympy.algebras.quaternion import Quaternion
>>> from sympy import symbols, trigsimp, cos, sin
>>> x = symbols('x')
>>> q = Quaternion(cos(x/2), 0, 0, sin(x/2))
>>> trigsimp(q.to_rotation_matrix((1, 1, 1)))
Matrix([
[cos(x), -sin(x), 0, sin(x) - cos(x) + 1],
[sin(x), cos(x), 0, -sin(x) - cos(x) + 1],
[ 0, 0, 1, 0],
[ 0, 0, 0, 1]])
"""
q = self
s = q.norm()**-2
m00 = 1 - 2*s*(q.c**2 + q.d**2)
m01 = 2*s*(q.b*q.c - q.d*q.a)
m02 = 2*s*(q.b*q.d + q.c*q.a)
m10 = 2*s*(q.b*q.c + q.d*q.a)
m11 = 1 - 2*s*(q.b**2 + q.d**2)
m12 = 2*s*(q.c*q.d - q.b*q.a)
m20 = 2*s*(q.b*q.d - q.c*q.a)
m21 = 2*s*(q.c*q.d + q.b*q.a)
m22 = 1 - 2*s*(q.b**2 + q.c**2)
if not v:
return Matrix([[m00, m01, m02], [m10, m11, m12], [m20, m21, m22]])
else:
(x, y, z) = v
m03 = x - x*m00 - y*m01 - z*m02
m13 = y - x*m10 - y*m11 - z*m12
m23 = z - x*m20 - y*m21 - z*m22
m30 = m31 = m32 = 0
m33 = 1
return Matrix([[m00, m01, m02, m03], [m10, m11, m12, m13],
[m20, m21, m22, m23], [m30, m31, m32, m33]])