Quaternion
Quaternions are hypercomplex number with 4 dimensions that can be used to represent 3D rotations. In this package, a quaternion $\mathbf{q}$ is represented by
using the following immutable structure:
struct Quaternion{T<:Real}
q0::T
q1::T
q2::T
q3::T
end
Initialization
There are several ways to create a quaternion.
Provide all the elements:
julia> q = Quaternion(1.0, 0.0, 0.0, 0.0)
Quaternion{Float64}:
+ 1.0 + 0.0.i + 0.0.j + 0.0.k
Provide the real and imaginary parts as separated numbers:
julia> r = sqrt(2)/2;
julia> v = [sqrt(2)/2; 0; 0];
julia> q = Quaternion(r,v)
Quaternion{Float64}:
+ 0.7071067811865476 + 0.7071067811865476.i + 0.0.j + 0.0.k
Provide the real and imaginary parts as one single vector:
julia> v = [1.;2.;3.;4.];
julia> q = Quaternion(v)
Quaternion{Float64}:
+ 1.0 + 2.0.i + 3.0.j + 4.0.k
Provide just the imaginary part, in this case the real part will be 0:
julia> v = [1.;0.;0.];
julia> q = Quaternion(v)
Quaternion{Float64}:
+ 0.0 + 1.0.i + 0.0.j + 0.0.k
Create an identity quaternion using the
eye
function:
julia> q = eye(Quaternion) # Creates an identity quaternion of type `Float64`.
Quaternion{Float64}:
+ 1.0 + 0.0.i + 0.0.j + 0.0.k
julia> q = eye(Quaternion{Float32}) # Creates an identity quaternion of type `Float32`.
Quaternion{Float32}:
+ 1.0 + 0.0.i + 0.0.j + 0.0.k
julia> a = eye(q) # Creates an identity quaternion with the same type of `q`.
Quaternion{Float32}:
+ 1.0 + 0.0.i + 0.0.j + 0.0.k
Create a zero quaternion using the
zeros
function:
julia> q = zeros(Quaternion) # Creates a zero quaternion of type `Float64`.
Quaternion{Float64}:
+ 0.0 + 0.0.i + 0.0.j + 0.0.k
julia> q = zeros(Quaternion{Float32}) # Creates a zero quaternion of type `Float32`.
Quaternion{Float32}:
+ 0.0 + 0.0.i + 0.0.j + 0.0.k
julia> a = zeros(q) # Creates a zero quaternion with the same type of `q`.
Quaternion{Float32}:
+ 0.0 + 0.0.i + 0.0.j + 0.0.k
Individual elements of the quaternion can be accessed by:
q.q0
q.q1
q.q2
q.q3
Since the type Quaternion
is immutable, its components cannot be changed individually after the creation. Hence, the following operation will lead to an error:
q.q0 = 1.0 # This is not defined and will not work.
If you want to modify a single value for the quaternion, then you need to create another one:
q = Quaternion(1.0, q.q1, q.q2, q.q3)
This can be annoying sometimes, but using an immutable type provided a huge performance boost for the algorithm.
Operations
The sum between quaternions and the multiplication between a quaternion and a scalar are defined as usual:
julia> q1 = Quaternion(1.0,1.0,0.0,0.0);
julia> q2 = Quaternion(1.0,2.0,3.0,4.0);
julia> q1+q2
Quaternion{Float64}:
+ 2.0 + 3.0.i + 3.0.j + 4.0.k
julia> q1 = Quaternion(1.0,2.0,3.0,4.0);
julia> q1*3
Quaternion{Float64}:
+ 3.0 + 6.0.i + 9.0.j + 12.0.k
julia> 4*q1
Quaternion{Float64}:
+ 4.0 + 8.0.i + 12.0.j + 16.0.k
There are also the following functions available:
julia> q = Quaternion(1.0,2.0,3.0,4.0);
julia> conj(q) # Returns the complex conjugate of the quaternion.
Quaternion{Float64}:
+ 1.0 - 2.0.i - 3.0.j - 4.0.k
julia> copy(q) # Creates a copy of the quaternion.
Quaternion{Float64}:
+ 1.0 + 2.0.i + 3.0.j + 4.0.k
julia> inv(q) # Computes the multiplicative inverse of the quaternion.
Quaternion{Float64}:
+ 0.03333333333333333 - 0.06666666666666667.i - 0.1.j - 0.13333333333333333.k
julia> inv(q)*q
Quaternion{Float64}:
+ 1.0 + 0.0.i + 5.551115123125783e-17.j + 0.0.k
julia> imag(q) # Returns the vectorial / imaginary part of the quaternion.
3-element StaticArrays.SArray{Tuple{3},Float64,1,3}:
2.0
3.0
4.0
julia> norm(q) # Computes the norm of the quaternion.
5.477225575051661
julia> real(q) # Returns the real part of the quaternion.
1.0
julia> vect(q) # Returns the vectorial / imaginary part of the quaternion.
3-element StaticArrays.SArray{Tuple{3},Float64,1,3}:
2.0
3.0
4.0
The operation a/q
is equal to a*inv(q)
if a
is a scalar.
The multiplication between quaternions is also defined using the Hamilton product:
Hence:
julia> q1 = Quaternion(cosd(15), sind(15), 0.0, 0.0);
julia> q2 = Quaternion(cosd(30), sind(30), 0.0, 0.0);
julia> q1*q2
Quaternion{Float64}:
+ 0.7071067811865475 + 0.7071067811865475.i + 0.0.j + 0.0.k
If a quaternion $\mathbf{q}$ is multiplied by a vector $\mathbf{v}$, then the vector is converted to a quaternion with real part 0, $\mathbf{q}_v = 0 + \mathbf{v}$, and the quaternion multiplication is performed as usual:
Hence:
julia> q1 = Quaternion(cosd(22.5), sind(22.5), 0.0, 0.0);
julia> v = [0;1;0];
julia> v*q1
Quaternion{Float64}:
+ 0.0 + 0.0.i + 0.9238795325112867.j - 0.3826834323650898.k
julia> q1*v
Quaternion{Float64}:
+ 0.0 + 0.0.i + 0.9238795325112867.j + 0.3826834323650898.k
Converting reference frames using quaternions
Given the reference frames A and B, let $\mathbf{w}$ be a unitary vector in which a rotation about it of an angle $\theta$ aligns the reference frame A with the reference frame B (in this case, $\mathbf{w}$ is aligned with the Euler Axis and $\theta$ is the Euler angle). Construct the following quaternion:
Then, a vector $\mathbf{v}$ represented in reference frame A ($\mathbf{v}_a$) can be represented in reference frame B using:
Hence:
julia> qBA = Quaternion(cosd(22.5), sind(22.5), 0.0, 0.0);
julia> vA = [0;1;0];
julia> vB = vect(inv(qBA)*vA*qBA);
julia> vB
3-element StaticArrays.SArray{Tuple{3},Float64,1,3}:
0.0
0.707107
-0.707107
A SArray
is returned instead of the usual Array
. This is a static vector created by the package StaticArrays. Generally, you can treat this vector as any other one. The only downside is that you cannot modify individual components because it is immutable.