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 * q2
Quaternion{Int64}(-60, 12, 30, 24)
julia> q2 * q1
Quaternion{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) * q1
QuaternionF64(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 compatibility
ERROR: promotion of types Complex{Int64} and Quaternion{Int64} failed to change any arguments
julia> complex(1,2) == quat(1,2,0,0) # no compatibility
ERROR: promotion of types Complex{Int64} and Quaternion{Int64} failed to change any arguments
julia> complex(1) == quat(1) # no compatibility
ERROR: 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.