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 classes int, float, and complex. 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 to exp(log(q)/2).
polar(q: Quaternion) -> Tuple[float, float, float, float]
Return a polar representation of Quaternion q; returns (r, φ, θ, ψ) such that q == r*exp(φi+θj+ψk).
phase(q: Quaternion) -> Tuple[float, float, float]
Return the angle components of polar. Equivalent to polar(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 by q 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) returns q₀ rotated a fraction t of the way towards q₁. qmath also includes similar lerp (linear interpolation) and nlerp (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.