-
Notifications
You must be signed in to change notification settings - Fork 43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Correct UnitQuaternion
with Quaternions.jl
#175
Changes from 17 commits
3ec59bc
630509b
e0dc787
8324da6
12fa6df
9f49e46
4300706
49f4bd4
67f4f1a
c78ca5a
2a5b7a6
ff13068
7613de0
983286b
4b630d0
666a4cb
ca48fc5
ea6f600
934cf9b
09e0313
cfb4b5a
9647c84
de9cb6c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# 3d | ||
function Base.log(R::RotationVec) | ||
x, y, z = params(R) | ||
return @SMatrix [0 -z y | ||
z 0 -x | ||
-y x 0] | ||
end | ||
|
||
function Base.log(R::Rotation{3}) | ||
log(RotationVec(R)) | ||
end | ||
|
||
|
||
# 2d | ||
function Base.log(R::Angle2d) | ||
θ, = params(R) | ||
return @SMatrix [0 -θ | ||
θ 0] | ||
end | ||
|
||
function Base.log(R::Rotation{2}) | ||
log(Angle2d(R)) | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
import Base: +, -, *, /, \, exp, log, ≈, ==, inv, conj | ||
import Base: *, /, \, exp, ≈, ==, inv | ||
|
||
""" | ||
UnitQuaternion{T} <: Rotation | ||
|
@@ -32,6 +32,7 @@ struct UnitQuaternion{T} <: Rotation{3,T} | |
end | ||
|
||
UnitQuaternion{T}(q::UnitQuaternion) where T = new{T}(q.w, q.x, q.y, q.z) | ||
UnitQuaternion{T}(q::Quaternion) where T = new{T}(q.s, q.v1, q.v2, q.v3) | ||
end | ||
|
||
# ~~~~~~~~~~~~~~~ Constructors ~~~~~~~~~~~~~~~ # | ||
|
@@ -41,6 +42,18 @@ function UnitQuaternion(w,x,y,z, normalize::Bool = true) | |
UnitQuaternion{eltype(types)}(w,x,y,z, normalize) | ||
end | ||
|
||
function UnitQuaternion(q::T) where T<:Quaternion | ||
if q.norm | ||
return UnitQuaternion(q.s, q.v1, q.v2, q.v3) | ||
else | ||
throw(InexactError(nameof(T), T, q)) | ||
end | ||
end | ||
|
||
function Quaternions.Quaternion(q::UnitQuaternion) | ||
Quaternion(q.w, q.x, q.y, q.z, true) | ||
end | ||
|
||
# Pass in Vectors | ||
@inline function (::Type{Q})(q::AbstractVector, normalize::Bool = true) where Q <: UnitQuaternion | ||
check_length(q, 4) | ||
|
@@ -183,6 +196,7 @@ end | |
# ~~~~~~~~~~~~~~~ Getters ~~~~~~~~~~~~~~~ # | ||
@inline scalar(q::UnitQuaternion) = q.w | ||
@inline vector(q::UnitQuaternion) = SVector{3}(q.x, q.y, q.z) | ||
@inline vector(q::Quaternion) = SVector{3}(q.v1, q.v2, q.v3) | ||
|
||
""" | ||
params(R::Rotation) | ||
|
@@ -197,77 +211,70 @@ Rotations.params(p) == @SVector [1.0, 2.0, 3.0] # true | |
""" | ||
@inline params(q::UnitQuaternion) = SVector{4}(q.w, q.x, q.y, q.z) | ||
|
||
# TODO: this will be removed, because Quaternion is not a subtype of Rotation | ||
@inline params(q::Quaternion) = SVector{4}(q.s, q.v1, q.v2, q.v3) | ||
|
||
# ~~~~~~~~~~~~~~~ Initializers ~~~~~~~~~~~~~~~ # | ||
function Random.rand(rng::AbstractRNG, ::Random.SamplerType{<:UnitQuaternion{T}}) where T | ||
normalize(UnitQuaternion{T}(randn(rng,T), randn(rng,T), randn(rng,T), randn(rng,T))) | ||
_normalize(UnitQuaternion{T}(randn(rng,T), randn(rng,T), randn(rng,T), randn(rng,T))) | ||
end | ||
@inline Base.one(::Type{Q}) where Q <: UnitQuaternion = Q(1.0, 0.0, 0.0, 0.0) | ||
|
||
|
||
# ~~~~~~~~~~~~~~~ Math Operations ~~~~~~~~~~~~~~~ # | ||
|
||
# Inverses | ||
conj(q::Q) where Q <: UnitQuaternion = Q(q.w, -q.x, -q.y, -q.z, false) | ||
inv(q::UnitQuaternion) = conj(q) | ||
(-)(q::Q) where Q <: UnitQuaternion = Q(-q.w, -q.x, -q.y, -q.z, false) | ||
inv(q::Q) where Q <: UnitQuaternion = Q(q.w, -q.x, -q.y, -q.z, false) | ||
|
||
# Norms | ||
LinearAlgebra.norm(q::UnitQuaternion) = sqrt(q.w^2 + q.x^2 + q.y^2 + q.z^2) | ||
vecnorm(q::UnitQuaternion) = sqrt(q.x^2 + q.y^2 + q.z^2) | ||
vecnorm(q::Quaternion) = sqrt(q.v1^2 + q.v2^2 + q.v3^2) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We shouldn't be defining things on behalf of Quaternions.jl. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, that was my mistake. I've removed the line. |
||
|
||
function LinearAlgebra.normalize(q::Q) where Q <: UnitQuaternion | ||
n = inv(norm(q)) | ||
Q(q.w*n, q.x*n, q.y*n, q.z*n) | ||
function _normalize(q::Q) where Q <: UnitQuaternion | ||
n = norm(params(q)) | ||
Q(q.w/n, q.x/n, q.y/n, q.z/n) | ||
end | ||
|
||
# Identity | ||
(::Type{Q})(I::UniformScaling) where Q <: UnitQuaternion = one(Q) | ||
|
||
# Exponentials and Logarithms | ||
""" | ||
pure_quaternion(v::AbstractVector) | ||
pure_quaternion(x, y, z) | ||
_pure_quaternion(v::AbstractVector) | ||
_pure_quaternion(x, y, z) | ||
|
||
Create a `UnitQuaternion` with zero scalar part (i.e. `q.w == 0`). | ||
Create a `Quaternion` with zero scalar part (i.e. `q.w == 0`). | ||
""" | ||
function pure_quaternion(v::AbstractVector) | ||
function _pure_quaternion(v::AbstractVector) | ||
check_length(v, 3) | ||
UnitQuaternion(zero(eltype(v)), v[1], v[2], v[3], false) | ||
Quaternion(zero(eltype(v)), v[1], v[2], v[3], false) | ||
end | ||
|
||
@inline pure_quaternion(x::Real, y::Real, z::Real) = | ||
UnitQuaternion(zero(x), x, y, z, false) | ||
|
||
function exp(q::Q) where Q <: UnitQuaternion | ||
θ = vecnorm(q) | ||
sθ,cθ = sincos(θ) | ||
es = exp(q.w) | ||
M = es*sθ/θ | ||
Q(es*cθ, q.x*M, q.y*M, q.z*M, false) | ||
end | ||
@inline _pure_quaternion(x::Real, y::Real, z::Real) = | ||
Quaternion(zero(x), x, y, z, false) | ||
|
||
function expm(ϕ::AbstractVector) | ||
check_length(ϕ, 3) | ||
θ = norm(ϕ) | ||
sθ,cθ = sincos(θ/2) | ||
M = 1//2 *sinc(θ/π/2) | ||
M = sinc(θ/π/2)/2 | ||
UnitQuaternion(cθ, ϕ[1]*M, ϕ[2]*M, ϕ[3]*M, false) | ||
end | ||
|
||
function log(q::Q, eps=1e-6) where Q <: UnitQuaternion | ||
function _log_as_quat(q::Q, eps=1e-6) where Q <: UnitQuaternion | ||
# Assumes unit quaternion | ||
θ = vecnorm(q) | ||
if θ > eps | ||
M = atan(θ, q.w)/θ | ||
else | ||
M = (1-(θ^2/(3q.w^2)))/q.w | ||
end | ||
pure_quaternion(M*vector(q)) | ||
_pure_quaternion(M*vector(q)) | ||
end | ||
|
||
function logm(q::UnitQuaternion) | ||
# Assumes unit quaternion | ||
2*vector(log(q)) | ||
return 2*vector(_log_as_quat(q)) | ||
end | ||
|
||
# Composition | ||
|
@@ -305,22 +312,10 @@ function Base.:*(q::UnitQuaternion, r::StaticVector) # must be StaticVector to | |
(w^2 - v'v)*r + 2*v*(v'r) + 2*w*cross(v,r) | ||
end | ||
|
||
""" | ||
(*)(q::UnitQuaternion, w::Real) | ||
|
||
Scalar multiplication of a quaternion. Breaks unit norm. | ||
""" | ||
function (*)(q::Q, w::Real) where Q<:UnitQuaternion | ||
return Q(q.w*w, q.x*w, q.y*w, q.z*w, false) | ||
end | ||
(*)(w::Real, q::UnitQuaternion) = q*w | ||
|
||
(\)(q1::UnitQuaternion, q2::UnitQuaternion) = inv(q1)*q2 # Equivalent to inv(q1)*q2 | ||
(/)(q1::UnitQuaternion, q2::UnitQuaternion) = q1*inv(q2) # Equivalent to q1*inv(q2) | ||
|
||
|
||
(\)(q1::UnitQuaternion, q2::UnitQuaternion) = conj(q1)*q2 # Equivalent to inv(q1)*q2 | ||
(/)(q1::UnitQuaternion, q2::UnitQuaternion) = q1*conj(q2) # Equivalent to q1*inv(q2) | ||
|
||
(\)(q::UnitQuaternion, r::SVector{3}) = conj(q)*r # Equivalent to inv(q)*r | ||
(\)(q::UnitQuaternion, r::SVector{3}) = inv(q)*r # Equivalent to inv(q)*r | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aren't these defined for all rotations? (I.e. can they be deleted instead?) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With original definition: julia> @benchmark q\SVector(1,2,3) # q isa UnitQuaternion
BenchmarkTools.Trial: 10000 samples with 996 evaluations.
Range (min … max): 25.933 ns … 1.724 μs ┊ GC (min … max): 0.00% … 97.61%
Time (median): 26.546 ns ┊ GC (median): 0.00%
Time (mean ± σ): 28.727 ns ± 42.051 ns ┊ GC (mean ± σ): 3.85% ± 2.59%
▂▆█▇▄▂ ▁▂▂▂▂▂▁▁ ▁
██████▇▆▇▆▆▆▅▅▅▅▃▅▅▆▆▆▅▅▅▆▇▇▇██████████▇▆▆▆▅▄▄▆▆▆▅▅▆▅▄▅▄▄▄▆ █
25.9 ns Histogram: log(frequency) by time 38.8 ns <
Memory estimate: 32 bytes, allocs estimate: 1. If Removing the definition: julia> @benchmark q\SVector(1,2,3) # q isa UnitQuaternion
BenchmarkTools.Trial: 10000 samples with 780 evaluations.
Range (min … max): 160.083 ns … 2.153 μs ┊ GC (min … max): 0.00% … 92.03%
Time (median): 164.683 ns ┊ GC (median): 0.00%
Time (mean ± σ): 167.640 ns ± 44.395 ns ┊ GC (mean ± σ): 0.59% ± 2.06%
▂▃█▁▁
▂▂▁▁▃▇█████▇▇▇▄▄▃▃▃▃▃▃▃▄▄▄▄▃▃▃▃▃▃▃▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂ ▃
160 ns Histogram: frequency by time 186 ns <
Memory estimate: 32 bytes, allocs estimate: 1. This performance deterioration is because There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, we need to update the julia> using Rotations, StaticArrays, BenchmarkTools
julia> r = RotX(2.3)
3×3 RotX{Float64} with indices SOneTo(3)×SOneTo(3)(2.3):
1.0 0.0 0.0
0.0 -0.666276 -0.745705
0.0 0.745705 -0.666276
julia> @benchmark r\SVector(1,2,3)
BenchmarkTools.Trial: 10000 samples with 861 evaluations.
Range (min … max): 138.636 ns … 1.605 μs ┊ GC (min … max): 0.00% … 90.34%
Time (median): 154.602 ns ┊ GC (median): 0.00%
Time (mean ± σ): 156.485 ns ± 31.911 ns ┊ GC (mean ± σ): 0.45% ± 2.01%
▂▇▇▅█▆▄▃▄
▂▁▁▁▁▂▁▂▂▁▂▂▂▂▂▂▂▂▃▂▂▃▃▄▄▄▆█████████▇█▄▃▄▃▄▃▄▄▄▄▅▅▅▄▃▃▂▂▂▂▂▂ ▄
139 ns Histogram: frequency by time 168 ns <
Memory estimate: 32 bytes, allocs estimate: 1.
julia> Base.:\(r::Rotation, v::SVector{3}) = inv(r)*v
julia> @benchmark r\SVector(1,2,3)
BenchmarkTools.Trial: 10000 samples with 994 evaluations.
Range (min … max): 29.926 ns … 1.726 μs ┊ GC (min … max): 0.00% … 97.98%
Time (median): 30.742 ns ┊ GC (median): 0.00%
Time (mean ± σ): 32.839 ns ± 44.139 ns ┊ GC (mean ± σ): 3.54% ± 2.59%
▃███▇▅▃▂ ▁▂▂▂▂▂▂▂▁ ▂
██████████▇▇▇▆▇█▆▆▆▆▅▆▇▆▆▇▇▇███████████▇▆▆▅▅▅▆▅▅▆▅▄▄▄▅▅▅▅▅▆ █
29.9 ns Histogram: log(frequency) by time 43.2 ns <
Memory estimate: 32 bytes, allocs estimate: 1. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've added the method. (9647c84) |
||
|
||
""" | ||
rotation_between(from, to) | ||
|
@@ -361,7 +356,7 @@ See "Fundamentals of Spacecraft Attitude Determination and Control" by Markley a | |
Sections 3.1-3.2 for more details. | ||
""" | ||
function kinematics(q::Q, ω::AbstractVector) where Q <: UnitQuaternion | ||
1//2 * params(q*Q(0.0, ω[1], ω[2], ω[3], false)) | ||
params(q*Q(0.0, ω[1], ω[2], ω[3], false))/2 | ||
end | ||
|
||
# ~~~~~~~~~~~~~~~ Linear Algebraic Conversions ~~~~~~~~~~~~~~~ # | ||
|
@@ -379,6 +374,14 @@ function lmult(q::UnitQuaternion) | |
q.z -q.y q.x q.w; | ||
] | ||
end | ||
function lmult(q::Quaternion) | ||
SA[ | ||
q.s -q.v1 -q.v2 -q.v3; | ||
q.v1 q.s -q.v3 q.v2; | ||
q.v2 q.v3 q.s -q.v1; | ||
q.v3 -q.v2 q.v1 q.s; | ||
] | ||
end | ||
lmult(q::StaticVector{4}) = lmult(UnitQuaternion(q, false)) | ||
|
||
""" | ||
|
@@ -395,6 +398,14 @@ function rmult(q::UnitQuaternion) | |
q.z q.y -q.x q.w; | ||
] | ||
end | ||
function rmult(q::Quaternion) | ||
SA[ | ||
q.s -q.v1 -q.v2 -q.v3; | ||
q.v1 q.s q.v3 -q.v2; | ||
q.v2 -q.v3 q.s q.v1; | ||
q.v3 q.v2 -q.v1 q.s; | ||
] | ||
end | ||
rmult(q::SVector{4}) = rmult(UnitQuaternion(q, false)) | ||
|
||
""" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
@testset "log" begin | ||
all_types = (RotMatrix{3}, AngleAxis, RotationVec, | ||
UnitQuaternion, RodriguesParam, MRP, | ||
RotXYZ, RotYZX, RotZXY, RotXZY, RotYXZ, RotZYX, | ||
RotXYX, RotYZY, RotZXZ, RotXZX, RotYXY, RotZYZ, | ||
RotX, RotY, RotZ, | ||
RotXY, RotYZ, RotZX, RotXZ, RotYX, RotZY, | ||
RotMatrix{2}, Angle2d) | ||
|
||
@testset "$(T)" for T in all_types, F in (one, rand) | ||
R = F(T) | ||
@test R ≈ exp(log(R)) | ||
@test log(R) isa SMatrix | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
vecnorm
must be defined in terms of the matrix. Which is orthogonal, so that's easy. Should we just define this instead?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
vecnorm
function was to calculate the norm of the imaginary part of a quaternion, and it was used only in_log_as_quat
. So, I've removed the function. (09e0313)