Taking Quaternions Seriously

Given q = one(Quaternion{Float64}), it seems to me that
eltype(q) === Float64 makes the most sense. Given the second half of the help text:

Determine the type of the elements generated by iterating a collection of the given type. For dictionary types, this will be a Pair{KeyType,ValType}. The definition eltype(x) = eltype(typeof(x)) is provided for convenience so that instances can be passed instead of types. However the form that accepts a type argument should be defined for new types.

Base.eltype(::Type{Quaternion{T}) where {T<:Number} = T makes sense imo. Also Base.eltype(::Type{QuaternionAsVector{T}}) = T.

My understanding has been that a pure quaternion is a quaternion. Implementing real(q) and imag(q) makes sense. Would you have imag(q) return a vector or a tuple? Do you agree that real(q) should return a scalar?

In Grassmann you would get a bivector with imag of a quaternion, so a 2-form (neither vector nor tuple).

Of course, real would return a scalar, although that scalar might be wrapped in a 0-form, to preserve vector space the scalar exists in.

1 Like

Good questions. For the application I have in mind I actually would want both to end up as quaternions, since I’m feeding everything into a sparse linear system and the uniformity of dimensions helps book-keeping. But that’s related to my specific use case/implementation. Constructors with 1 and 3 airty could be used to easily convert.

eltype(Quaternion{Float64}) === Float64 is only okay if and only if collect(q::Quaternion{Float64}) == [q.w, q.x, q.y, q.z] (modulo the field order and field access), and I believe also iff 1 .+ q == [1 + q.w, 1+q.x, 1+q.y, 1+q.z] (to say something about broadcasting).

I don’t think it should be done, and that the Quaternion type (or any type that implements the quaternion interface) should behave like Complex and all the types defined in ColorTypes.jl. That is, they should be treated like scalars for the purpose of iteration, indexing, and broadcasting.

Note that Base defines iterate(x::Number) = (x, nothing) and getindex(x::Number) = x (which is questionable, but I think outside the scope of this conversation), and I think it would basically be incorrect for a <:Number to define anything else.

1 Like

Exactly in agreement with my experiences. This is why I have valuetype instead of eltype.

However, I am willing to change it, if a convincing argument can be made for something else.

The situation should be similar to Complex.

1 Like

I have become convinced (thank you @goretkin). Whether we call it valuetype or innertype or ?? … it should be distinct from eltype.

1 Like

Hi, in some literature they define the Quaternion in term of a scalar part and a 3D vector

using StaticArrays
using LinearAlgebra

import Base.*

struct Quaternion{T<:Real} <: Number
   q₀::T
   q::SVector{3, T}
end

(*)(a::Quaternion{T}, b::Quaternion{T}) where T<:Real = Quaternion(a.q₀ * b.q₀ - dot(a.q, b.q), a.q₀ * b.q + b.q₀ * a.q + cross(a.q, b.q))

and they define the product in terms of dot and cross products

2 Likes

So, does anyone working on this?

I’ve been trying to use Makie.Quaternion with ReferenceFrameRotations.Quaternion and I am beaten.

1 Like

Update: Note that the Quaternions.jl package is now actively maintained, and Rotations.jl depends on Quaternions.jl (Rotations.jl#175).
See [ANN] Quaternions.jl v0.7.0 for more information.

I’ve been trying to use Makie.Quaternion with ReferenceFrameRotations.Quaternion and I am beaten.

6 Likes