From 9c62d64596c66a2ef7985f84bb19d00a565a6710 Mon Sep 17 00:00:00 2001 From: Seth Axen Date: Thu, 1 Dec 2022 10:54:07 +0100 Subject: [PATCH] Remove norm field and update normalization functions (#108) * Remove norm field * Update tests * Replace normalize with sign * Rename normalizea to sign_abs * Make sign_abs type-stable * Document sign function * Remove normalizeq * Add sign and sign_abs to docs * Increase version number * Remove sign_abs * Mark test non-broken * Remove sign_abs actually * Add missing period * Remove argq * Apply suggestions from code review Co-authored-by: Yuto Horikawa * Remove depwarn * Remove old method * Fix docstring * Make doctest less like complex Co-authored-by: Yuto Horikawa --- Project.toml | 2 +- docs/src/api.md | 4 ++ src/Quaternion.jl | 126 +++++++++++++++---------------------------- src/Quaternions.jl | 2 - test/Quaternion.jl | 132 ++++++++++++++------------------------------- 5 files changed, 88 insertions(+), 178 deletions(-) diff --git a/Project.toml b/Project.toml index e4a4b2b0..28100ade 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "Quaternions" uuid = "94ee1d12-ae83-5a48-8b1c-48b8ff168ae0" -version = "0.6.1" +version = "0.7.0-DEV" [deps] LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" diff --git a/docs/src/api.md b/docs/src/api.md index 9cfe2307..6251f38f 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -22,6 +22,10 @@ imag_part conj ``` +```@docs +sign +``` + ```@docs slerp ``` diff --git a/src/Quaternion.jl b/src/Quaternion.jl index 39e4efb2..d7b97338 100644 --- a/src/Quaternion.jl +++ b/src/Quaternion.jl @@ -13,7 +13,6 @@ struct Quaternion{T<:Real} <: Number v1::T v2::T v3::T - norm::Bool end const QuaternionF16 = Quaternion{Float16} @@ -25,15 +24,12 @@ function Quaternion{T}(x::Complex) where {T<:Real} Base.depwarn("`Complex`-`Quaternion` compatibility is deprecated and will be removed in the next breaking release (v0.7.0).", :Quaternion) Quaternion(convert(Complex{T}, x)) end -Quaternion{T}(q::Quaternion) where {T<:Real} = Quaternion{T}(q.s, q.v1, q.v2, q.v3, q.norm) -function Quaternion(s::Real, v1::Real, v2::Real, v3::Real, n::Bool = false) - Base.depwarn("The `norm` field is deprecated and will be removed in the next breaking release (v0.7.0).", :Quaternion) - Quaternion(promote(s, v1, v2, v3)..., n) -end -Quaternion(x::Real) = Quaternion(x, zero(x), zero(x), zero(x), abs(x) == one(x)) +Quaternion{T}(q::Quaternion) where {T<:Real} = Quaternion{T}(q.s, q.v1, q.v2, q.v3) +Quaternion(s::Real, v1::Real, v2::Real, v3::Real) = Quaternion(promote(s, v1, v2, v3)...) +Quaternion(x::Real) = Quaternion(x, zero(x), zero(x), zero(x)) function Quaternion(z::Complex) Base.depwarn("`Complex`-`Quaternion` compatibility is deprecated and will be removed in the next breaking release (v0.7.0).", :Quaternion) - Quaternion(z.re, z.im, zero(z.re), zero(z.re), abs(z) == one(z.re)) + Quaternion(z.re, z.im, zero(z.re), zero(z.re)) end function Quaternion(s::Real, a::AbstractVector) Base.depwarn("`Quaternion(s::Real, a::AbstractVector)` is deprecated and will be removed in the next breaking release (v0.7.0). Please use `Quaternion(s, a[1], a[2], a[3])` instead.", :Quaternion) @@ -51,15 +47,6 @@ function Base.promote_rule(::Type{Quaternion{T}}, ::Type{Complex{S}}) where {T < end Base.promote_rule(::Type{Quaternion{T}}, ::Type{Quaternion{S}}) where {T <: Real, S <: Real} = Quaternion{promote_type(T, S)} -function Base.getproperty(q::Quaternion, s::Symbol) - if s === :norm - Base.depwarn("The `norm` field is deprecated and will be removed in the next breaking release (v0.7.0).", :Quaternion) - getfield(q,:norm) - else - getfield(q,s) - end -end - """ quat(r, [i, j, k]) @@ -68,19 +55,18 @@ Convert real numbers or arrays to quaternion. `i, j, k` defaults to zero. # Examples ```jldoctest julia> quat(7) -Quaternion{Int64}(7, 0, 0, 0, false) +Quaternion{Int64}(7, 0, 0, 0) julia> quat(1.0, 2, 3, 4) -QuaternionF64(1.0, 2.0, 3.0, 4.0, false) +QuaternionF64(1.0, 2.0, 3.0, 4.0) julia> quat([1, 2, 3]) # This output will be changed in the next breaking release for consistency. (#94) -Quaternion{Int64}(0, 1, 2, 3, false) +Quaternion{Int64}(0, 1, 2, 3) ``` """ quat quat(p, v1, v2, v3) = Quaternion(p, v1, v2, v3) -quat(p, v1, v2, v3, n) = Quaternion(p, v1, v2, v3, n) quat(x) = Quaternion(x) quat(s, a) = Quaternion(s, a) @@ -116,7 +102,7 @@ Base.real(q::Quaternion) = q.s """ real(A::AbstractArray{<:Quaternion}) - + Return an array containing the real part of each quaternion in `A`. # Examples @@ -158,53 +144,40 @@ Compute the quaternion conjugate of a quaternion `q`. # Examples ```jldoctest julia> conj(Quaternion(1,2,3,4)) -Quaternion{Int64}(1, -2, -3, -4, false) +Quaternion{Int64}(1, -2, -3, -4) ``` """ -Base.conj(q::Quaternion) = Quaternion(q.s, -q.v1, -q.v2, -q.v3, q.norm) +Base.conj(q::Quaternion) = Quaternion(q.s, -q.v1, -q.v2, -q.v3) Base.abs(q::Quaternion) = sqrt(abs2(q)) Base.float(q::Quaternion{T}) where T = convert(Quaternion{float(T)}, q) abs_imag(q::Quaternion) = sqrt(q.v2 * q.v2 + (q.v1 * q.v1 + q.v3 * q.v3)) # ordered to match abs2 Base.abs2(q::Quaternion) = (q.s * q.s + q.v2 * q.v2) + (q.v1 * q.v1 + q.v3 * q.v3) -Base.inv(q::Quaternion) = q.norm ? conj(q) : conj(q) / abs2(q) +Base.inv(q::Quaternion) = conj(q) / abs2(q) Base.isreal(q::Quaternion) = iszero(q.v1) & iszero(q.v2) & iszero(q.v3) -Base.isfinite(q::Quaternion) = q.norm | (isfinite(q.s) & isfinite(q.v1) & isfinite(q.v2) & isfinite(q.v3)) -Base.iszero(q::Quaternion) = ~q.norm & iszero(real(q)) & iszero(q.v1) & iszero(q.v2) & iszero(q.v3) +Base.isfinite(q::Quaternion) = isfinite(q.s) & isfinite(q.v1) & isfinite(q.v2) & isfinite(q.v3) +Base.iszero(q::Quaternion) = iszero(real(q)) & iszero(q.v1) & iszero(q.v2) & iszero(q.v3) Base.isnan(q::Quaternion) = isnan(real(q)) | isnan(q.v1) | isnan(q.v2) | isnan(q.v3) -Base.isinf(q::Quaternion) = ~q.norm & (isinf(q.s) | isinf(q.v1) | isinf(q.v2) | isinf(q.v3)) +Base.isinf(q::Quaternion) = isinf(q.s) | isinf(q.v1) | isinf(q.v2) | isinf(q.v3) -function LinearAlgebra.normalize(q::Quaternion) - Base.depwarn("`LinearAlgebra.normalize(q::Quaternion)` is deprecated. Please use `sign(q)` instead.", :normalize) - if (q.norm) - return q - end - q = q / abs(q) - Quaternion(q.s, q.v1, q.v2, q.v3, true) -end +# included strictly for documentation; the base implementation is sufficient +""" + sign(q::Quaternion) -> Quaternion -function normalizea(q::Quaternion) - Base.depwarn("`normalizea(q::Quaternion)` is deprecated. Please use `sign(q), abs(q)` instead.", :normalizea) - if (q.norm) - return (q, one(q.s)) - end - a = abs(q) - q = q / a - (Quaternion(q.s, q.v1, q.v2, q.v3, true), a) -end +Return zero if `q==0` and ``q/|q|`` otherwise. -function normalizeq(q::Quaternion) - Base.depwarn("`normalizeq(q::Quaternion)` is deprecated. Please use `sign(q)` instead.", :normalizea) - a = abs(q) - if a > 0 - q = q / a - Quaternion(q.s, q.v1, q.v2, q.v3, true) - else - Quaternion(0.0, 1.0, 0.0, 0.0, true) - end -end +# Examples +```jldoctest +julia> sign(Quaternion(4, 0, 0, 0)) +QuaternionF64(1.0, 0.0, 0.0, 0.0) -Base.:-(q::Quaternion) = Quaternion(-q.s, -q.v1, -q.v2, -q.v3, q.norm) +julia> sign(Quaternion(1, 0, 1, 0)) +QuaternionF64(0.7071067811865475, 0.0, 0.7071067811865475, 0.0) +``` +""" +sign(::Quaternion) + +Base.:-(q::Quaternion) = Quaternion(-q.s, -q.v1, -q.v2, -q.v3) Base.:+(q::Quaternion, w::Quaternion) = Quaternion(q.s + w.s, q.v1 + w.v1, q.v2 + w.v2, q.v3 + w.v3) @@ -217,12 +190,12 @@ function Base.:*(q::Quaternion, w::Quaternion) v1 = (q.s * w.v1 + q.v1 * w.s) + (q.v2 * w.v3 - q.v3 * w.v2) v2 = (q.s * w.v2 + q.v2 * w.s) + (q.v3 * w.v1 - q.v1 * w.v3) v3 = (q.s * w.v3 + q.v3 * w.s) + (q.v1 * w.v2 - q.v2 * w.v1) - return Quaternion(s, v1, v2, v3, q.norm & w.norm) + return Quaternion(s, v1, v2, v3) end Base.:/(q::Quaternion, w::Quaternion) = q * inv(w) -Base.:(==)(q::Quaternion, w::Quaternion) = (q.s == w.s) & (q.v1 == w.v1) & (q.v2 == w.v2) & (q.v3 == w.v3) # ignore .norm field +Base.:(==)(q::Quaternion, w::Quaternion) = (q.s == w.s) & (q.v1 == w.v1) & (q.v2 == w.v2) & (q.v3 == w.v3) angleaxis(q::Quaternion) = angle(q), axis(q) @@ -233,18 +206,13 @@ end function axis(q::Quaternion) Base.depwarn("`axis(::Quaternion)` is deprecated. Please consider using Rotations package instead.", :axis) - q = normalize(q) + q = sign(q) s = sin(angle(q) / 2) abs(s) > 0 ? [q.v1, q.v2, q.v3] / s : [1.0, 0.0, 0.0] end -function argq(q::Quaternion) - Base.depwarn("`argq(q::Quaternion)` is deprecated. Use `quat(0, imag_part(q)...)` instead.", :argq) - normalizeq(Quaternion(0, q.v1, q.v2, q.v3)) -end - """ extend_analytic(f, q::Quaternion) @@ -273,20 +241,16 @@ function extend_analytic(f, q::Quaternion) w = f(z) wr, wi = reim(w) scale = wi / a - norm = _isexpfun(f) && iszero(s) if a > 0 - return Quaternion(wr, scale * q.v1, scale * q.v2, scale * q.v3, norm) + return Quaternion(wr, scale * q.v1, scale * q.v2, scale * q.v3) else # q == real(q), so f(real(q)) may be real or complex, i.e. wi may be nonzero. # we choose to embed complex numbers in the quaternions by identifying the first # imaginary quaternion basis with the complex imaginary basis. - return Quaternion(wr, oftype(scale, wi), zero(scale), zero(scale), norm) + return Quaternion(wr, oftype(scale, wi), zero(scale), zero(scale)) end end -_isexpfun(::Union{typeof(exp),typeof(exp2),typeof(exp10)}) = true -_isexpfun(::Any) = false - for f in ( :sqrt, :exp, :exp2, :exp10, :expm1, :log2, :log10, :log1p, :sin, :cos, :tan, :asin, :acos, :atan, :sinh, :cosh, :tanh, :asinh, :acosh, :atanh, @@ -332,10 +296,10 @@ end Base.:^(q::Quaternion, w::Quaternion) = exp(w * log(q)) quatrand(rng = Random.GLOBAL_RNG) = quat(randn(rng), randn(rng), randn(rng), randn(rng)) -nquatrand(rng = Random.GLOBAL_RNG) = normalize(quatrand(rng)) +nquatrand(rng = Random.GLOBAL_RNG) = sign(quatrand(rng)) function Base.rand(rng::AbstractRNG, ::Random.SamplerType{Quaternion{T}}) where {T<:Real} - Quaternion{T}(rand(rng, T), rand(rng, T), rand(rng, T), rand(rng, T), false) + Quaternion{T}(rand(rng, T), rand(rng, T), rand(rng, T), rand(rng, T)) end function Base.randn(rng::AbstractRNG, ::Type{Quaternion{T}}) where {T<:AbstractFloat} @@ -344,7 +308,6 @@ function Base.randn(rng::AbstractRNG, ::Type{Quaternion{T}}) where {T<:AbstractF randn(rng, T) * 1//2, randn(rng, T) * 1//2, randn(rng, T) * 1//2, - false, ) end @@ -362,7 +325,7 @@ function qrotation(axis::AbstractVector{T}, theta) where {T <: Real} end s,c = sincos(theta / 2) scaleby = s / normaxis - Quaternion(c, scaleby * axis[1], scaleby * axis[2], scaleby * axis[3], true) + Quaternion(c, scaleby * axis[1], scaleby * axis[2], scaleby * axis[3]) end # Variant of the above where norm(rotvec) encodes theta @@ -374,7 +337,7 @@ function qrotation(rotvec::AbstractVector{T}) where {T <: Real} theta = norm(rotvec) s,c = sincos(theta / 2) scaleby = s / (iszero(theta) ? one(theta) : theta) - Quaternion(c, scaleby * rotvec[1], scaleby * rotvec[2], scaleby * rotvec[3], true) + Quaternion(c, scaleby * rotvec[1], scaleby * rotvec[2], scaleby * rotvec[3]) end function qrotation(dcm::AbstractMatrix{T}) where {T<:Real} @@ -399,9 +362,9 @@ function qrotation(dcm::AbstractMatrix{T}) where {T<:Real} a,b,c = (dcm[2,1]-dcm[1,2])/4d, (dcm[1,3]+dcm[3,1])/4d, (dcm[3,2]+dcm[2,3])/4d end if a > 0 - return Quaternion(a,b,c,d,true) + return Quaternion(a,b,c,d) else - return Quaternion(-a,-b,-c,-d,true) + return Quaternion(-a,-b,-c,-d) end end @@ -411,7 +374,7 @@ function qrotation(dcm::AbstractMatrix{T}, qa::Quaternion) where {T<:Real} abs(q-qa) < abs(q+qa) ? q : -q end -rotationmatrix(q::Quaternion) = rotationmatrix_normalized(normalize(q)) +rotationmatrix(q::Quaternion) = rotationmatrix_normalized(sign(q)) function rotationmatrix_normalized(q::Quaternion) Base.depwarn("`rotationmatrix_normalized(::Quaternion)` is deprecated. Please consider using Rotations package instead.", :rotationmatrix_normalized) @@ -434,13 +397,13 @@ Since the input is normalized inside the function, the absolute value of the ret julia> using Quaternions julia> qa = Quaternion(1,0,0,0) -Quaternion{Int64}(1, 0, 0, 0, false) +Quaternion{Int64}(1, 0, 0, 0) julia> qb = Quaternion(0,1,0,0) -Quaternion{Int64}(0, 1, 0, 0, false) +Quaternion{Int64}(0, 1, 0, 0) julia> slerp(qa, qb, 0.6) -QuaternionF64(0.5877852522924731, 0.8090169943749475, 0.0, 0.0, true) +QuaternionF64(0.5877852522924731, 0.8090169943749475, 0.0, 0.0) julia> ans ≈ Quaternion(cospi(0.3), sinpi(0.3), 0, 0) true @@ -475,7 +438,6 @@ true qa.v1 * ratio_a + qb.v1 * ratio_b, qa.v2 * ratio_a + qb.v2 * ratio_b, qa.v3 * ratio_a + qb.v3 * ratio_b, - true ) end diff --git a/src/Quaternions.jl b/src/Quaternions.jl index c5079597..ae0a4cb3 100644 --- a/src/Quaternions.jl +++ b/src/Quaternions.jl @@ -16,8 +16,6 @@ module Quaternions export angleaxis export angle export axis - export normalize - export normalizea export quatrand export nquatrand export qrotation diff --git a/test/Quaternion.jl b/test/Quaternion.jl index 8f3b65aa..1f761340 100644 --- a/test/Quaternion.jl +++ b/test/Quaternion.jl @@ -18,23 +18,19 @@ Base.:(/)(a::MyReal, b::Real) = a.val / b @testset "Constructors" begin @testset "from coefficients" begin cs = [(1, 2.0, 3.0f0, 4//1), (1//1, 2.0f0, 3.0f0, 4)] - @testset for coef in cs, T in (Float32, Float64, Int), norm in (true, false) - q = @inferred Quaternion{T}(coef..., norm) + @testset for coef in cs, T in (Float32, Float64, Int) + q = @inferred Quaternion{T}(coef...) @test q isa Quaternion{T} - @test q.norm === norm - @test q === Quaternion{T}(convert.(T, coef)..., norm) - q2 = @inferred Quaternion(convert.(T, coef)..., norm) - @test Quaternion(convert.(T, coef)..., norm) === q - if !norm - @test Quaternion(convert.(T, coef)...) === q - end + @test q === Quaternion{T}(convert.(T, coef)...) + q2 = @inferred Quaternion(convert.(T, coef)...) + @test Quaternion(convert.(T, coef)...) === q end end @testset "from real" begin @testset for x in (-1//1, 1.0, 2.0), T in (Float32, Float64, Int, Rational{Int}) coef = T.((x, 0, 0, 0)) - @test @inferred(Quaternion{T}(x)) === Quaternion{T}(coef..., isone(abs(x))) - @test @inferred(Quaternion(T(x))) === Quaternion{T}(coef..., isone(abs(x))) + @test @inferred(Quaternion{T}(x)) === Quaternion{T}(coef...) + @test @inferred(Quaternion(T(x))) === Quaternion{T}(coef...) end end @testset "from complex" begin @@ -43,17 +39,16 @@ Base.:(/)(a::MyReal, b::Real) = a.val / b coef = T.((reim(z)..., 0, 0)) z2 = Complex{T}(z) - norm = isone(abs(z)) - @test Quaternion{T}(z) === Quaternion{T}(coef..., norm) - @test @inferred(Quaternion(z2)) === Quaternion{T}(coef..., norm) + @test Quaternion{T}(z) === Quaternion{T}(coef...) + @test @inferred(Quaternion(z2)) === Quaternion{T}(coef...) end end @testset "from quaternion" begin - @testset for q in (Quaternion(1, 2, 3, 4), QuaternionF64(0, 1, 0, 0, true)), + @testset for q in (Quaternion(1, 2, 3, 4), QuaternionF64(0, 1, 0, 0)), T in (Float32, Float64) coef = T.((q.s, q.v1, q.v2, q.v3)) - @test @inferred(Quaternion{T}(q)) === Quaternion{T}(coef..., q.norm) + @test @inferred(Quaternion{T}(q)) === Quaternion{T}(coef...) @test @inferred(Quaternion(q)) === q end end @@ -71,9 +66,6 @@ Base.:(/)(a::MyReal, b::Real) = a.val / b @test Quaternion(1, 2, 3, 4) != Quaternion(1, 5, 3, 4) @test Quaternion(1, 2, 3, 4) != Quaternion(1, 2, 5, 4) @test Quaternion(1, 2, 3, 4) != Quaternion(1, 2, 3, 5) - x = randn(4) - # test that .norm field does not affect equality - @test Quaternion(1, 2, 3, 4, false) == Quaternion(1, 2, 3, 4, true) end @testset "convert" begin @@ -83,8 +75,8 @@ Base.:(/)(a::MyReal, b::Real) = a.val / b Quaternion(1.0, 2.0, 3.0, 4.0) @test convert(Quaternion{Float64}, Quaternion(1.0, 2.0, 3.0, 4.0)) === Quaternion(1.0, 2.0, 3.0, 4.0) - @test convert(Quaternion{Float64}, Quaternion(0, 1, 0, 0, true)) === - Quaternion(0.0, 1.0, 0.0, 0.0, true) + @test convert(Quaternion{Float64}, Quaternion(0, 1, 0, 0)) === + Quaternion(0.0, 1.0, 0.0, 0.0) end @testset "promote" begin @@ -102,13 +94,9 @@ Base.:(/)(a::MyReal, b::Real) = a.val / b end @testset "shorthands" begin - @test quat(1) === Quaternion(1) # checking the .norm field in particular - @test quat(1, 0, 0, 0) === Quaternion(1, 0, 0, 0) # checking the .norm field in particular + @test quat(1) === Quaternion(1) @test quat(1, 2, 3, 4) === Quaternion(1, 2, 3, 4) - @test quat(Quaternion(1, 0, 0, 0)) === Quaternion(1, 0, 0, 0) # checking the .norm field in particular @test quat(Quaternion(1, 2, 3, 4)) === Quaternion(1, 2, 3, 4) - @test quat(1, 0, 0, 0, false).norm == false # respect the .norm input (even if wrong) - @test quat(1, 2, 3, 4, true).norm == true # respect the .norm input (even if wrong) @test quat(1, [2, 3, 4]) === Quaternion(1, 2, 3, 4) @test quat([2, 3, 4]) === Quaternion(0, 2, 3, 4) @test_deprecated quat([2, 3, 4]) @@ -119,29 +107,24 @@ Base.:(/)(a::MyReal, b::Real) = a.val / b rng = Random.MersenneTwister(42) q1 = quatrand(rng) @test q1 isa Quaternion - @test !q1.norm q2 = quatrand() @test q2 isa Quaternion - @test !q2.norm end @testset "nquatrand" begin rng = Random.MersenneTwister(42) q1 = nquatrand(rng) @test q1 isa Quaternion - @test q1.norm q2 = nquatrand() @test q2 isa Quaternion - @test q2.norm end @testset "rand($H)" for H in (QuaternionF32, QuaternionF64) rng = Random.MersenneTwister(42) q = rand(rng, H) @test q isa H - @test !q.norm qs = rand(rng, H, 1000) @test eltype(qs) === H @@ -159,7 +142,6 @@ Base.:(/)(a::MyReal, b::Real) = a.val / b rng = Random.MersenneTwister(42) q = randn(rng, H) @test q isa H - @test !q.norm qs = randn(rng, H, 10000) @test eltype(qs) === H @@ -176,12 +158,12 @@ Base.:(/)(a::MyReal, b::Real) = a.val / b @testset "basic" begin q = randn(QuaternionF64) - qnorm = normalize(q) + qnorm = sign(q) @test real(q) === q.s @test_throws MethodError imag(q) @test imag_part(q) === (q.v1, q.v2, q.v3) - @test conj(q) === Quaternion(q.s, -q.v1, -q.v2, -q.v3, q.norm) - @test conj(qnorm) === Quaternion(qnorm.s, -qnorm.v1, -qnorm.v2, -qnorm.v3, true) + @test conj(q) === Quaternion(q.s, -q.v1, -q.v2, -q.v3) + @test conj(qnorm) === Quaternion(qnorm.s, -qnorm.v1, -qnorm.v2, -qnorm.v3) @test conj(conj(q)) === q @test conj(conj(qnorm)) === qnorm @test float(Quaternion(1, 2, 3, 4)) === float(Quaternion(1.0, 2.0, 3.0, 4.0)) @@ -222,7 +204,6 @@ Base.:(/)(a::MyReal, b::Real) = a.val / b @test !iszero(Quaternion(0.0, 1.0, 0.0, 0.0)) @test !iszero(Quaternion(0.0, 0.0, 1.0, 0.0)) @test !iszero(Quaternion(0.0, 0.0, 0.0, 1.0)) - @test !iszero(Quaternion(0.0, 0.0, 0.0, 0.0, true)) end @testset "isone" begin @@ -241,7 +222,7 @@ Base.:(/)(a::MyReal, b::Real) = a.val / b @test !isfinite(Quaternion(0.0, value, 0.0, 0.0)) @test !isfinite(Quaternion(0.0, 0.0, value, 0.0)) @test !isfinite(Quaternion(0.0, 0.0, 0.0, value)) - @test isfinite(Quaternion(fill(value, 4)..., true)) + @test !isfinite(Quaternion(fill(value, 4)...)) end end @@ -253,7 +234,6 @@ Base.:(/)(a::MyReal, b::Real) = a.val / b @test isinf(Quaternion(0.0, inf, 0.0, 0.0)) @test isinf(Quaternion(0.0, 0.0, inf, 0.0)) @test isinf(Quaternion(0.0, 0.0, 0.0, inf)) - @test !isinf(Quaternion(inf, inf, inf, inf, true)) end end @@ -419,11 +399,11 @@ Base.:(/)(a::MyReal, b::Real) = a.val / b end @testset "exp" begin - @test exp(Quaternion(0, 0, 0, 0)) === Quaternion(1.0, 0.0, 0.0, 0.0, true) - @test exp(Quaternion(2, 0, 0, 0)) === Quaternion(exp(2), 0, 0, 0, false) - @test exp(Quaternion(0, 2, 0, 0)) === Quaternion(cos(2), sin(2), 0, 0, true) - @test exp(Quaternion(0, 0, 2, 0)) === Quaternion(cos(2), 0, sin(2), 0, true) - @test exp(Quaternion(0, 0, 0, 2)) === Quaternion(cos(2), 0, 0, sin(2), true) + @test exp(Quaternion(0, 0, 0, 0)) === Quaternion(1.0, 0.0, 0.0, 0.0) + @test exp(Quaternion(2, 0, 0, 0)) === Quaternion(exp(2), 0, 0, 0) + @test exp(Quaternion(0, 2, 0, 0)) === Quaternion(cos(2), sin(2), 0, 0) + @test exp(Quaternion(0, 0, 2, 0)) === Quaternion(cos(2), 0, sin(2), 0) + @test exp(Quaternion(0, 0, 0, 2)) === Quaternion(cos(2), 0, 0, sin(2)) @test norm(exp(Quaternion(0, 0, 0, 0))) ≈ 1 @test norm(exp(Quaternion(2, 0, 0, 0))) ≠ 1 @@ -432,15 +412,15 @@ Base.:(/)(a::MyReal, b::Real) = a.val / b @test norm(exp(Quaternion(0, 0, 0, 2))) ≈ 1 @test exp(Quaternion(0.0, 0.0, 0.0, 0.0)) === - Quaternion(1.0, 0.0, 0.0, 0.0, true) + Quaternion(1.0, 0.0, 0.0, 0.0) @test exp(Quaternion(2.0, 0.0, 0.0, 0.0)) === - Quaternion(exp(2), 0, 0, 0, false) + Quaternion(exp(2), 0, 0, 0) @test exp(Quaternion(0.0, 2.0, 0.0, 0.0)) === - Quaternion(cos(2), sin(2), 0, 0, true) + Quaternion(cos(2), sin(2), 0, 0) @test exp(Quaternion(0.0, 0.0, 2.0, 0.0)) === - Quaternion(cos(2), 0, sin(2), 0, true) + Quaternion(cos(2), 0, sin(2), 0) @test exp(Quaternion(0.0, 0.0, 0.0, 2.0)) === - Quaternion(cos(2), 0, 0, sin(2), true) + Quaternion(cos(2), 0, 0, sin(2)) @test norm(exp(Quaternion(0.0, 0.0, 0.0, 0.0))) ≈ 1 @test norm(exp(Quaternion(2.0, 0.0, 0.0, 0.0))) ≠ 1 @@ -461,48 +441,15 @@ Base.:(/)(a::MyReal, b::Real) = a.val / b end end - @testset "normalize" begin + @testset "sign" begin for _ in 1:100 q = quatrand() - qnorm = @inferred normalize(q) + qnorm = @inferred sign(q) @test abs(qnorm) ≈ 1 - @test qnorm.norm @test q ≈ abs(q) * qnorm - @test normalize(qnorm) === qnorm - end - @test_broken @inferred(normalize(Quaternion(1, 2, 3, 4))) - end - - @testset "normalizea" begin - for _ in 1:100 - q = quatrand() - qnorm, a = @inferred normalizea(q) - @test abs(qnorm) ≈ 1 - @test qnorm.norm - @test a isa Real - @test a ≈ abs(q) - @test q ≈ a * qnorm - @test normalizea(qnorm) === (qnorm, one(real(q))) - end - @test_broken @inferred(normalizea(Quaternion(1, 2, 3, 4))) - end - - @testset "Quaternions.normalizeq" begin - for _ in 1:100 - q = quatrand() - @test Quaternions.normalizeq(q) === normalize(q) - end - @test Quaternions.normalizeq(zero(QuaternionF64)) == im - @test_broken @inferred(Quaternions.normalizeq(Quaternion(1, 2, 3, 4))) - end - - @testset "Quaternions.argq" begin - for _ in 1:100 - q, q2 = randn(QuaternionF64, 2) - @test q2 * Quaternions.argq(q) * inv(q2) ≈ Quaternions.argq(q2 * q * inv(q2)) - v = Quaternion(0, randn(3)...) - @test Quaternions.argq(v) * norm(v) ≈ v + @test sign(qnorm) ≈ qnorm end + @inferred(sign(Quaternion(1, 2, 3, 4))) end @testset "rotations" begin @@ -553,9 +500,8 @@ Base.:(/)(a::MyReal, b::Real) = a.val / b @test q1 ≈ q2 @test q2 === q3 || q2 === -q3 @test real(q3) ≥ 0 - @test q1.norm - @test q2.norm - @test q3.norm + @test abs(q2) ≈ 1 + @test abs(q3) ≈ 1 end end @@ -592,14 +538,14 @@ Base.:(/)(a::MyReal, b::Real) = a.val / b @testset "slerp" begin @testset "q1=1" begin - a = quat(1, 0, 0, 0.0, true) - b = quat(0, 0, 0, 1.0, true) + a = quat(1, 0, 0, 0.0) + b = quat(0, 0, 0, 1.0) @test slerp(a, b, 0.0) ≈ a @test slerp(a, b, 1.0) ≈ b @test slerp(a, b, 0.5) ≈ qrotation([0, 0, 1], deg2rad(90)) - @test slerp(a, b, 0.0).norm - @test slerp(a, b, 1.0).norm - @test slerp(a, b, 0.5).norm + @test abs(slerp(a, b, 0.0)) ≈ 1 + @test abs(slerp(a, b, 1.0)) ≈ 1 + @test abs(slerp(a, b, 0.5)) ≈ 1 for _ in 1:100, scale in (1, 1e-5, 1e-10) q1 = quat(1, 0, 0, 0.0) θ = rand() * π * scale