Basics

Basic operations for quaternions

Quaternions can be defined with the Quaternion constructor or quat function. Note that the order of the arguments is $w+xi+yj+zk$, not $xi+yj+zk+w$.

julia> using Quaternions
julia> q1 = Quaternion(1,2,3,4)Quaternion{Int64}(1, 2, 3, 4)
julia> q2 = quat(5,6,7,8)Quaternion{Int64}(5, 6, 7, 8)
julia> q3 = quat(9)Quaternion{Int64}(9, 0, 0, 0)

The multiplication is not commutative.

julia> q1 * q2Quaternion{Int64}(-60, 12, 30, 24)
julia> q2 * q1Quaternion{Int64}(-60, 20, 14, 32)

The multiplicative inverse can be calculated with Base.inv.

julia> inv(q1)QuaternionF64(0.03333333333333333, -0.06666666666666667, -0.1, -0.13333333333333333)
julia> inv(q1) * q1QuaternionF64(1.0, 0.0, 0.0, 0.0)

The division is also not commutative.

julia> q1 / q2  # Same as `q1*inv(q2)` mathematically.QuaternionF64(0.40229885057471265, 0.04597701149425287, 0.0, 0.09195402298850575)
julia> q2 \ q1 # Same as `inv(q2)*q1` mathematically.QuaternionF64(0.40229885057471265, -0.0, 0.09195402298850575, 0.04597701149425287)

A conjugate of a quaternion can be calculated with Base.conj. But Base.imag(::Quaternion) is not defined because it should return three real values which is not consistent with imag(::Complex) and imag(::Real). Instead, the imag_part function can be used to obtain the imaginary part of a quaternion. See issue#61 for more discussion.

julia> conj(q1)Quaternion{Int64}(1, -2, -3, -4)
julia> imag(q1) # Not supported.ERROR: MethodError: no method matching imag(::Quaternion{Int64}) Closest candidates are: imag(::Missing) @ Base missing.jl:101 imag(::Complex) @ Base complex.jl:87 imag(::LinearAlgebra.Tridiagonal) @ LinearAlgebra /usr/local/julia1.10.0/share/julia/stdlib/v1.10/LinearAlgebra/src/tridiag.jl:583 ...
julia> imag_part(q1) # Use this instead.(2, 3, 4)

Unit quaternions can be obtained with sign.

julia> sign(q1)QuaternionF64(0.18257418583505536, 0.3651483716701107, 0.5477225575051661, 0.7302967433402214)
julia> sign(q2)QuaternionF64(0.3790490217894517, 0.454858826147342, 0.5306686305052324, 0.6064784348631227)
julia> sign(q3)QuaternionF64(1.0, 0.0, 0.0, 0.0)
julia> sign(quat(0)) # Zero-quaternion will not be normalized.QuaternionF64(0.0, 0.0, 0.0, 0.0)

Quaternion vs quat

The general rule is that quat is to Quaternion as complex is to Complex. Complex and Quaternion are both constructors so should return an object of the corresponding type, whereas quat and complex both can operate on types and arrays.

julia> Quaternion(1,2,3,4)Quaternion{Int64}(1, 2, 3, 4)
julia> quat(1,2,3,4)Quaternion{Int64}(1, 2, 3, 4)
julia> Quaternion(Int) # Similar to `Complex(Int)`.ERROR: MethodError: no method matching Quaternion(::Type{Int64}) Closest candidates are: Quaternion(::T, ::T, ::T, ::T) where T<:Real @ Quaternions ~/work/Quaternions.jl/Quaternions.jl/src/Quaternion.jl:12 Quaternion(::Real, ::Real, ::Real, ::Real) @ Quaternions ~/work/Quaternions.jl/Quaternions.jl/src/Quaternion.jl:24 Quaternion(::Real) @ Quaternions ~/work/Quaternions.jl/Quaternions.jl/src/Quaternion.jl:25 ...
julia> quat(Int) # Similar to `complex(Int)`.Quaternion{Int64}

Compatibility with Complex

There is no natural embedding $\mathbb{C}\to\mathbb{H}$. Thus, quat(w,x,0,0) is not equal to complex(w,x), i.e.

\[\mathbb{C} \ni w+ix \ne w+ix+0j+0k \in \mathbb{H}.\]

julia> 1 + complex(1,2)   # `Complex` is compatible with `Real`2 + 2im
julia> 1 + quat(1,2,3,4) # `Quaternion` is compatible with `Real`Quaternion{Int64}(2, 2, 3, 4)
julia> 1 + complex(1,2) + quat(1,2,3,4) # no compatibilityERROR: promotion of types Complex{Int64} and Quaternion{Int64} failed to change any arguments
julia> complex(1,2) == quat(1,2,0,0) # no compatibilityERROR: promotion of types Complex{Int64} and Quaternion{Int64} failed to change any arguments
julia> complex(1) == quat(1) # no compatibilityERROR: promotion of types Complex{Int64} and Quaternion{Int64} failed to change any arguments
julia> complex(1) == 1 == quat(1) # Both `quat(1)` and `complex(1)` are equal to `1`.true

See issue#62 for more discussion.