qmath.py: A Quaternion Math Module
I am pleased to present the first version of
qmath, a
quaternion math module for Python 3. The module contains a Quaternion
class and math functions that operate on quaternions. My priorities in
creating qmath are:
- portability I've implemented qmath in pure Python 3 using only standard library modules and functions. This guarantees that the module can be included in or with a project with minimal hassle.
- Pythonicity A module or class is Pythonic to the extent that it
behaves how a Python programmer would expect it to behave. To limit
programmer surprise, I've made the
Quaternion
class behave as similarly as possible to the standard number classesint
,float
, andcomplex
. qmath will ultimately contain quaternionic versions of all the methods provided by the standard library's cmath module. - performance Numerical software is useful in proportion to its speed. Within the constraints imposed by the previous two goals, I set out to make the functions in qmath as fast as possible. I used the timeit module to test out different function definitions, keeping the fastest versions.
- readability I've tried to make the module as readable as possible without compromising the above goals. Readability ranks below performance in my list of priorities, so wherever a function could be made faster at the cost of readability, readability has been sacrificed. The purpose of this module is to make it easier for others to write clean code, not to demonstrate clean coding practises.
Installing qmath
Simply put qmath.py
in the same directory as your program or somewhere
else in Python's load path. qmath is written in Python 3 and should be
compatible with versions as old as 3.0. I have no intention of
supporting Python 2 in the future, as support for Python 2 will be
completely phased out within 18 months.
Using qmath
Import qmath and the Quaternion
class.
>>> import qmath
>>> from qmath import Quaternion
Constructors
A Quaternion
can be constructed from real numbers (int
or float
),
using keyword arguments, from other quaternions, from complex numbers,
and from strings.
>>> q0 = Quaternion() # default constructor
>>> q0
Quaternion(0, 0, 0, 0)
>>> q1 = Quaternion(0.5, 0.5, 0.5, 0.5) # real number constructor
>>> q1
Quaternion(0.5, 0.5, 0.5, 0.5)
>>> q2 = Quaternion(a=2**-0.5, k=-2**-0.5) # keyword arguments
>>> q2
Quaternion(0.707107, 0, 0, -0.707107)
>>> q3 = Quaternion(q0) # copy constructor
>>> q3
Quaternion(0.5, 0.5, 0.5, 0.5)
>>> q4 = Quaternion(1-1j) # complex number constructor
>>> q4
Quaternion(1, -1, 0, 0)
>>> q5 = Quaternion('1+2i+3j+4k') # string constructor
>>> q5
Quaternion(1, 2, 3, 4)
One gotcha hidden in the above examples is that Python uses j
to
denote the (first) imaginary unit. It is not coincidence that Python is
a language loved by electrical engineers. Conversely, qmath uses the
more common convention that i is the first imaginary unit and j is
the second. Consequently:
>>> complex('1j') == Quaternion('1j')
False
>>> complex('1j') == Quaternion('1i')
True
If you construct a quaternion from a string, the imaginary components
must be presented in order, and i
, j
, k
must have a real-valued
factor in front of them; Quaternion('1i+1j')
works, but
Quaternion('1j+1i')
does not. Furthermore, Quaternion('1k')
works,
but Quaternion('k')
does not. This consistent with this
comment
in the CPython source code, which says that construction of complex
numbers from strings such as '1+j', '-j', or 'j' is deprecated.
If you really want to, you can use the string constructor to make mixed
nan
and inf
quaternions:
>>> Quaternion('-inf-nani+infj-nank')
Quaternion(-inf, nan, inf, nan)
Quaternion
also supports Cayley-Dickson
construction:
the construction of a quaternion from a pair of complex numbers. In
order for this to work, both arguments must be of type complex
.
>>> q6 = Quaternion(4-3j, -2-1j)
>>> q6
Quaternion(4, -3, -2, -1)
Properties, methods and operators
Quaternion
supports all the operators and properties that complex
does. The imag
property returns the vector part of the quaternion.
Individual imagniary components or slices can be accessed using the
getitem
operator.
>>> q = Quaternion(1,2,3,4)
>>> q.real # real part
1.0
>>> q.imag # imaginary part
(2.0, 3.0, 4.0)
>>> q[1] # i component using getitem
2.0
>>> q[2:] # (j component, k component)
(3.0, 4.0)
>>> q.conjugate() # conjugation
Quaternion(1, -2, -3, -4)
>>> q.inverse() # multplicative inverse (1/q)
Quaternion(0.0333333, -0.0666667, -0.1, -0.133333)
>>> abs(q) # magnitude (Euclidean norm)
5.477225575051661
>>> bool(q) # q != 0.0
True
>>> +q # pos operator makes a copy
Quaternion(1, 2, 3, 4)
>>> -q # negation (additive inverse)
Quaternion(-1, -2, -3, -4)
Quaternions can be added, subtracted, multiplied, divided, raised to real, complex, or quaternionic powers, and compared for equality. Like the complex numbers, they are not ordered, so greater-than and less-than operators cannot be used with them.
>>> h = Quaternion(4,3,2,1)
>>> q + h # addition
Quaternion(5, 5, 5, 5)
>>> q - h # subtraction
Quaternion(-3, -1, 1, 3)
>>> q*h # multiplication
Quaternion(-12, 6, 24, 12)
>>> q/h # division
Quaternion(0.666667, 0.333333, 0, 0.666667)
>>> q**h # Quaternionic exponentiation
Quaternion(9.64823, -7.90041, -3.66143, -6.64723)
Functions in qmath
Functions with equivalents in cmath
log(q: Quaternion) -> Quaternion
- Return the quaternionic logarithm of
q
. exp(q: Quaternion) -> Quaternion
- Return the quaternionic exponential of
q
;exp(w+xi+yj+zk)
. sqrt(q: Quaternion) -> Quaternion
- Return one square root of
q
. Equivalent toexp(log(q)/2)
. polar(q: Quaternion) -> Tuple[float, float, float, float]
- Return a polar representation of Quaternion
q
; returns(r, φ, θ, ψ)
such thatq == r*exp(φi+θj+ψk)
. phase(q: Quaternion) -> Tuple[float, float, float]
- Return the angle components of
polar
. Equivalent topolar(q)[1:]
. rect(r: float, φ: float, θ: float, ψ: float) -> Quaternion
- Inverse of
polar
;q == rect(*polar(q))
.
log
, exp
, sqrt
, polar
, and phase
fall back on their cmath
equivalents when applied to real or complex numbers.
Other functions
norm(q: Quaternion, p: float) -> float
- Return the p-norm of the quaternion.
rotation_matrix(q: Quaternion) -> 3x3 Matrix
- Returns the quaternion interpreted as a rotation matrix. The matrix is represented using a 3-tuple of 3-tuples.
axis_angle(q: Quaternion) -> Tuple[float, float, float, float]
- Returns
(uᵢ, uⱼ, uₖ, θ)
where u is the unit vector around which a rotation represented byq
takes place and θ is the angle of the rotation. slerp(q₀: Quaternion, q₁: Quaternion, t: float) -> Quaternion
- Spherical linear interpolation
(Slerp).
slerp(q₀, q₁, t)
returnsq₀
rotated a fractiont
of the way towardsq₁
. qmath also includes similarlerp
(linear interpolation) andnlerp
(normalized linear interpolation) functions.
qmath includes equivalents of math and cmath's functions for dealing with infinities and NaNs.
Planned features
There are a number of useful features planned for addition into qmath.py:
- Euler angles Quaternions are commonly used as a way of representing rotations. qmath.py does not yet include functions for converting quaternions to and from Euler angles. The implementation of these methods is complicated by the different conventions used for Euler angles.
- Trigonometric functions The cmath module provides a number of trigonometric functions (cos, sin, cosh, etc…) over the complex numbers; these are useful for numerical complex analysis. These same functions can be extended over the quaternions.
- Quaternion construction from rotation matrices
Interoperability with NumPy is not planned. quaternion is an active project which extends numpy to support quaternions; it is implemented in C and is likely much faster than qmath.
Issues and requests
If you have any feature requests, improvements, or issues with qmath, please visit the project's issues page.