Skip to content
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

Remove norm field and update normalization functions #108

Merged
merged 20 commits into from
Dec 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
4 changes: 4 additions & 0 deletions docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ imag_part
conj
```

```@docs
sign
```

```@docs
slerp
```
Expand Down
126 changes: 44 additions & 82 deletions src/Quaternion.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ struct Quaternion{T<:Real} <: Number
v1::T
v2::T
v3::T
norm::Bool
end

const QuaternionF16 = Quaternion{Float16}
Expand All @@ -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)
Expand All @@ -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])

Expand All @@ -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)

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)

Expand All @@ -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)

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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}
Expand All @@ -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

Expand All @@ -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
Expand All @@ -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}
Expand All @@ -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

Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down
2 changes: 0 additions & 2 deletions src/Quaternions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ module Quaternions
export angleaxis
export angle
export axis
export normalize
export normalizea
export quatrand
export nquatrand
export qrotation
Expand Down
Loading