Skip to content

Commit 9c62d64

Browse files
sethaxenhyrodium
andauthored
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 <[email protected]> * Remove depwarn * Remove old method * Fix docstring * Make doctest less like complex Co-authored-by: Yuto Horikawa <[email protected]>
1 parent b04badb commit 9c62d64

File tree

5 files changed

+88
-178
lines changed

5 files changed

+88
-178
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "Quaternions"
22
uuid = "94ee1d12-ae83-5a48-8b1c-48b8ff168ae0"
3-
version = "0.6.1"
3+
version = "0.7.0-DEV"
44

55
[deps]
66
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

docs/src/api.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ imag_part
2222
conj
2323
```
2424

25+
```@docs
26+
sign
27+
```
28+
2529
```@docs
2630
slerp
2731
```

src/Quaternion.jl

Lines changed: 44 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ struct Quaternion{T<:Real} <: Number
1313
v1::T
1414
v2::T
1515
v3::T
16-
norm::Bool
1716
end
1817

1918
const QuaternionF16 = Quaternion{Float16}
@@ -25,15 +24,12 @@ function Quaternion{T}(x::Complex) where {T<:Real}
2524
Base.depwarn("`Complex`-`Quaternion` compatibility is deprecated and will be removed in the next breaking release (v0.7.0).", :Quaternion)
2625
Quaternion(convert(Complex{T}, x))
2726
end
28-
Quaternion{T}(q::Quaternion) where {T<:Real} = Quaternion{T}(q.s, q.v1, q.v2, q.v3, q.norm)
29-
function Quaternion(s::Real, v1::Real, v2::Real, v3::Real, n::Bool = false)
30-
Base.depwarn("The `norm` field is deprecated and will be removed in the next breaking release (v0.7.0).", :Quaternion)
31-
Quaternion(promote(s, v1, v2, v3)..., n)
32-
end
33-
Quaternion(x::Real) = Quaternion(x, zero(x), zero(x), zero(x), abs(x) == one(x))
27+
Quaternion{T}(q::Quaternion) where {T<:Real} = Quaternion{T}(q.s, q.v1, q.v2, q.v3)
28+
Quaternion(s::Real, v1::Real, v2::Real, v3::Real) = Quaternion(promote(s, v1, v2, v3)...)
29+
Quaternion(x::Real) = Quaternion(x, zero(x), zero(x), zero(x))
3430
function Quaternion(z::Complex)
3531
Base.depwarn("`Complex`-`Quaternion` compatibility is deprecated and will be removed in the next breaking release (v0.7.0).", :Quaternion)
36-
Quaternion(z.re, z.im, zero(z.re), zero(z.re), abs(z) == one(z.re))
32+
Quaternion(z.re, z.im, zero(z.re), zero(z.re))
3733
end
3834
function Quaternion(s::Real, a::AbstractVector)
3935
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 <
5147
end
5248
Base.promote_rule(::Type{Quaternion{T}}, ::Type{Quaternion{S}}) where {T <: Real, S <: Real} = Quaternion{promote_type(T, S)}
5349

54-
function Base.getproperty(q::Quaternion, s::Symbol)
55-
if s === :norm
56-
Base.depwarn("The `norm` field is deprecated and will be removed in the next breaking release (v0.7.0).", :Quaternion)
57-
getfield(q,:norm)
58-
else
59-
getfield(q,s)
60-
end
61-
end
62-
6350
"""
6451
quat(r, [i, j, k])
6552
@@ -68,19 +55,18 @@ Convert real numbers or arrays to quaternion. `i, j, k` defaults to zero.
6855
# Examples
6956
```jldoctest
7057
julia> quat(7)
71-
Quaternion{Int64}(7, 0, 0, 0, false)
58+
Quaternion{Int64}(7, 0, 0, 0)
7259
7360
julia> quat(1.0, 2, 3, 4)
74-
QuaternionF64(1.0, 2.0, 3.0, 4.0, false)
61+
QuaternionF64(1.0, 2.0, 3.0, 4.0)
7562
7663
julia> quat([1, 2, 3]) # This output will be changed in the next breaking release for consistency. (#94)
77-
Quaternion{Int64}(0, 1, 2, 3, false)
64+
Quaternion{Int64}(0, 1, 2, 3)
7865
```
7966
"""
8067
quat
8168

8269
quat(p, v1, v2, v3) = Quaternion(p, v1, v2, v3)
83-
quat(p, v1, v2, v3, n) = Quaternion(p, v1, v2, v3, n)
8470
quat(x) = Quaternion(x)
8571
quat(s, a) = Quaternion(s, a)
8672

@@ -116,7 +102,7 @@ Base.real(q::Quaternion) = q.s
116102

117103
"""
118104
real(A::AbstractArray{<:Quaternion})
119-
105+
120106
Return an array containing the real part of each quaternion in `A`.
121107
122108
# Examples
@@ -158,53 +144,40 @@ Compute the quaternion conjugate of a quaternion `q`.
158144
# Examples
159145
```jldoctest
160146
julia> conj(Quaternion(1,2,3,4))
161-
Quaternion{Int64}(1, -2, -3, -4, false)
147+
Quaternion{Int64}(1, -2, -3, -4)
162148
```
163149
"""
164-
Base.conj(q::Quaternion) = Quaternion(q.s, -q.v1, -q.v2, -q.v3, q.norm)
150+
Base.conj(q::Quaternion) = Quaternion(q.s, -q.v1, -q.v2, -q.v3)
165151
Base.abs(q::Quaternion) = sqrt(abs2(q))
166152
Base.float(q::Quaternion{T}) where T = convert(Quaternion{float(T)}, q)
167153
abs_imag(q::Quaternion) = sqrt(q.v2 * q.v2 + (q.v1 * q.v1 + q.v3 * q.v3)) # ordered to match abs2
168154
Base.abs2(q::Quaternion) = (q.s * q.s + q.v2 * q.v2) + (q.v1 * q.v1 + q.v3 * q.v3)
169-
Base.inv(q::Quaternion) = q.norm ? conj(q) : conj(q) / abs2(q)
155+
Base.inv(q::Quaternion) = conj(q) / abs2(q)
170156

171157
Base.isreal(q::Quaternion) = iszero(q.v1) & iszero(q.v2) & iszero(q.v3)
172-
Base.isfinite(q::Quaternion) = q.norm | (isfinite(q.s) & isfinite(q.v1) & isfinite(q.v2) & isfinite(q.v3))
173-
Base.iszero(q::Quaternion) = ~q.norm & iszero(real(q)) & iszero(q.v1) & iszero(q.v2) & iszero(q.v3)
158+
Base.isfinite(q::Quaternion) = isfinite(q.s) & isfinite(q.v1) & isfinite(q.v2) & isfinite(q.v3)
159+
Base.iszero(q::Quaternion) = iszero(real(q)) & iszero(q.v1) & iszero(q.v2) & iszero(q.v3)
174160
Base.isnan(q::Quaternion) = isnan(real(q)) | isnan(q.v1) | isnan(q.v2) | isnan(q.v3)
175-
Base.isinf(q::Quaternion) = ~q.norm & (isinf(q.s) | isinf(q.v1) | isinf(q.v2) | isinf(q.v3))
161+
Base.isinf(q::Quaternion) = isinf(q.s) | isinf(q.v1) | isinf(q.v2) | isinf(q.v3)
176162

177-
function LinearAlgebra.normalize(q::Quaternion)
178-
Base.depwarn("`LinearAlgebra.normalize(q::Quaternion)` is deprecated. Please use `sign(q)` instead.", :normalize)
179-
if (q.norm)
180-
return q
181-
end
182-
q = q / abs(q)
183-
Quaternion(q.s, q.v1, q.v2, q.v3, true)
184-
end
163+
# included strictly for documentation; the base implementation is sufficient
164+
"""
165+
sign(q::Quaternion) -> Quaternion
185166
186-
function normalizea(q::Quaternion)
187-
Base.depwarn("`normalizea(q::Quaternion)` is deprecated. Please use `sign(q), abs(q)` instead.", :normalizea)
188-
if (q.norm)
189-
return (q, one(q.s))
190-
end
191-
a = abs(q)
192-
q = q / a
193-
(Quaternion(q.s, q.v1, q.v2, q.v3, true), a)
194-
end
167+
Return zero if `q==0` and ``q/|q|`` otherwise.
195168
196-
function normalizeq(q::Quaternion)
197-
Base.depwarn("`normalizeq(q::Quaternion)` is deprecated. Please use `sign(q)` instead.", :normalizea)
198-
a = abs(q)
199-
if a > 0
200-
q = q / a
201-
Quaternion(q.s, q.v1, q.v2, q.v3, true)
202-
else
203-
Quaternion(0.0, 1.0, 0.0, 0.0, true)
204-
end
205-
end
169+
# Examples
170+
```jldoctest
171+
julia> sign(Quaternion(4, 0, 0, 0))
172+
QuaternionF64(1.0, 0.0, 0.0, 0.0)
206173
207-
Base.:-(q::Quaternion) = Quaternion(-q.s, -q.v1, -q.v2, -q.v3, q.norm)
174+
julia> sign(Quaternion(1, 0, 1, 0))
175+
QuaternionF64(0.7071067811865475, 0.0, 0.7071067811865475, 0.0)
176+
```
177+
"""
178+
sign(::Quaternion)
179+
180+
Base.:-(q::Quaternion) = Quaternion(-q.s, -q.v1, -q.v2, -q.v3)
208181

209182
Base.:+(q::Quaternion, w::Quaternion) =
210183
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)
217190
v1 = (q.s * w.v1 + q.v1 * w.s) + (q.v2 * w.v3 - q.v3 * w.v2)
218191
v2 = (q.s * w.v2 + q.v2 * w.s) + (q.v3 * w.v1 - q.v1 * w.v3)
219192
v3 = (q.s * w.v3 + q.v3 * w.s) + (q.v1 * w.v2 - q.v2 * w.v1)
220-
return Quaternion(s, v1, v2, v3, q.norm & w.norm)
193+
return Quaternion(s, v1, v2, v3)
221194
end
222195

223196
Base.:/(q::Quaternion, w::Quaternion) = q * inv(w)
224197

225-
Base.:(==)(q::Quaternion, w::Quaternion) = (q.s == w.s) & (q.v1 == w.v1) & (q.v2 == w.v2) & (q.v3 == w.v3) # ignore .norm field
198+
Base.:(==)(q::Quaternion, w::Quaternion) = (q.s == w.s) & (q.v1 == w.v1) & (q.v2 == w.v2) & (q.v3 == w.v3)
226199

227200
angleaxis(q::Quaternion) = angle(q), axis(q)
228201

@@ -233,18 +206,13 @@ end
233206

234207
function axis(q::Quaternion)
235208
Base.depwarn("`axis(::Quaternion)` is deprecated. Please consider using Rotations package instead.", :axis)
236-
q = normalize(q)
209+
q = sign(q)
237210
s = sin(angle(q) / 2)
238211
abs(s) > 0 ?
239212
[q.v1, q.v2, q.v3] / s :
240213
[1.0, 0.0, 0.0]
241214
end
242215

243-
function argq(q::Quaternion)
244-
Base.depwarn("`argq(q::Quaternion)` is deprecated. Use `quat(0, imag_part(q)...)` instead.", :argq)
245-
normalizeq(Quaternion(0, q.v1, q.v2, q.v3))
246-
end
247-
248216
"""
249217
extend_analytic(f, q::Quaternion)
250218
@@ -273,20 +241,16 @@ function extend_analytic(f, q::Quaternion)
273241
w = f(z)
274242
wr, wi = reim(w)
275243
scale = wi / a
276-
norm = _isexpfun(f) && iszero(s)
277244
if a > 0
278-
return Quaternion(wr, scale * q.v1, scale * q.v2, scale * q.v3, norm)
245+
return Quaternion(wr, scale * q.v1, scale * q.v2, scale * q.v3)
279246
else
280247
# q == real(q), so f(real(q)) may be real or complex, i.e. wi may be nonzero.
281248
# we choose to embed complex numbers in the quaternions by identifying the first
282249
# imaginary quaternion basis with the complex imaginary basis.
283-
return Quaternion(wr, oftype(scale, wi), zero(scale), zero(scale), norm)
250+
return Quaternion(wr, oftype(scale, wi), zero(scale), zero(scale))
284251
end
285252
end
286253

287-
_isexpfun(::Union{typeof(exp),typeof(exp2),typeof(exp10)}) = true
288-
_isexpfun(::Any) = false
289-
290254
for f in (
291255
:sqrt, :exp, :exp2, :exp10, :expm1, :log2, :log10, :log1p,
292256
:sin, :cos, :tan, :asin, :acos, :atan, :sinh, :cosh, :tanh, :asinh, :acosh, :atanh,
@@ -332,10 +296,10 @@ end
332296
Base.:^(q::Quaternion, w::Quaternion) = exp(w * log(q))
333297

334298
quatrand(rng = Random.GLOBAL_RNG) = quat(randn(rng), randn(rng), randn(rng), randn(rng))
335-
nquatrand(rng = Random.GLOBAL_RNG) = normalize(quatrand(rng))
299+
nquatrand(rng = Random.GLOBAL_RNG) = sign(quatrand(rng))
336300

337301
function Base.rand(rng::AbstractRNG, ::Random.SamplerType{Quaternion{T}}) where {T<:Real}
338-
Quaternion{T}(rand(rng, T), rand(rng, T), rand(rng, T), rand(rng, T), false)
302+
Quaternion{T}(rand(rng, T), rand(rng, T), rand(rng, T), rand(rng, T))
339303
end
340304

341305
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
344308
randn(rng, T) * 1//2,
345309
randn(rng, T) * 1//2,
346310
randn(rng, T) * 1//2,
347-
false,
348311
)
349312
end
350313

@@ -362,7 +325,7 @@ function qrotation(axis::AbstractVector{T}, theta) where {T <: Real}
362325
end
363326
s,c = sincos(theta / 2)
364327
scaleby = s / normaxis
365-
Quaternion(c, scaleby * axis[1], scaleby * axis[2], scaleby * axis[3], true)
328+
Quaternion(c, scaleby * axis[1], scaleby * axis[2], scaleby * axis[3])
366329
end
367330

368331
# Variant of the above where norm(rotvec) encodes theta
@@ -374,7 +337,7 @@ function qrotation(rotvec::AbstractVector{T}) where {T <: Real}
374337
theta = norm(rotvec)
375338
s,c = sincos(theta / 2)
376339
scaleby = s / (iszero(theta) ? one(theta) : theta)
377-
Quaternion(c, scaleby * rotvec[1], scaleby * rotvec[2], scaleby * rotvec[3], true)
340+
Quaternion(c, scaleby * rotvec[1], scaleby * rotvec[2], scaleby * rotvec[3])
378341
end
379342

380343
function qrotation(dcm::AbstractMatrix{T}) where {T<:Real}
@@ -399,9 +362,9 @@ function qrotation(dcm::AbstractMatrix{T}) where {T<:Real}
399362
a,b,c = (dcm[2,1]-dcm[1,2])/4d, (dcm[1,3]+dcm[3,1])/4d, (dcm[3,2]+dcm[2,3])/4d
400363
end
401364
if a > 0
402-
return Quaternion(a,b,c,d,true)
365+
return Quaternion(a,b,c,d)
403366
else
404-
return Quaternion(-a,-b,-c,-d,true)
367+
return Quaternion(-a,-b,-c,-d)
405368
end
406369
end
407370

@@ -411,7 +374,7 @@ function qrotation(dcm::AbstractMatrix{T}, qa::Quaternion) where {T<:Real}
411374
abs(q-qa) < abs(q+qa) ? q : -q
412375
end
413376

414-
rotationmatrix(q::Quaternion) = rotationmatrix_normalized(normalize(q))
377+
rotationmatrix(q::Quaternion) = rotationmatrix_normalized(sign(q))
415378

416379
function rotationmatrix_normalized(q::Quaternion)
417380
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
434397
julia> using Quaternions
435398
436399
julia> qa = Quaternion(1,0,0,0)
437-
Quaternion{Int64}(1, 0, 0, 0, false)
400+
Quaternion{Int64}(1, 0, 0, 0)
438401
439402
julia> qb = Quaternion(0,1,0,0)
440-
Quaternion{Int64}(0, 1, 0, 0, false)
403+
Quaternion{Int64}(0, 1, 0, 0)
441404
442405
julia> slerp(qa, qb, 0.6)
443-
QuaternionF64(0.5877852522924731, 0.8090169943749475, 0.0, 0.0, true)
406+
QuaternionF64(0.5877852522924731, 0.8090169943749475, 0.0, 0.0)
444407
445408
julia> ans ≈ Quaternion(cospi(0.3), sinpi(0.3), 0, 0)
446409
true
@@ -475,7 +438,6 @@ true
475438
qa.v1 * ratio_a + qb.v1 * ratio_b,
476439
qa.v2 * ratio_a + qb.v2 * ratio_b,
477440
qa.v3 * ratio_a + qb.v3 * ratio_b,
478-
true
479441
)
480442
end
481443

src/Quaternions.jl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ module Quaternions
1616
export angleaxis
1717
export angle
1818
export axis
19-
export normalize
20-
export normalizea
2119
export quatrand
2220
export nquatrand
2321
export qrotation

0 commit comments

Comments
 (0)