Skip to content

Commit ffe1bf8

Browse files
sethaxenhyrodium
andauthored
Add missing tests (#74)
* Add missing tests from #65 * Add basic tests * Add constructor tests * Test type-inferribility * Add equality checks * Rename parameters * Add more tests for conversions and promotions * Test isreal and isone * Remove unnecessary methods * Swap order of arguments * Add missing shorthand tests * Test normalization functions * Test slerp and linpol for overlapping points * Test exceptions raised for incorrect axis length * Remove unreachable branch * Test slerp approximately linear interpolation * Reformat type constructor * Remove unnecessary constructor * Add missing constructor * Add constructor tests * Add conversion and promotion tests * Add shorthand tests * Remove unnecessary promotions * Define type constructors instead of conversions * Add basic tests * Add abs_imag for Octonion * Use abs_imag * Add and test isXXX functions * Test pow functions * Test analytic and nob-analytic functions * Ensure log(0) is -Inf * Add more checks for inferribility * Quaternion to Octonion * Fix test for 1.0 * Test octorand * Test division * Fix test bug * Add and expand constructors * Test new constructors * Remove whitespace * Remove unneeded convert methods * Allow passing single quaternion * Repair constructors and add tests * Correctly determine if is norm * Simplify and test promote rules * Test remaining rand functions * Test basic functionality * Remove duplicate constructor * Add missing type parameter * Test algebraic properties * Add missing imports * Repair normalizea * Add more tests * Fix tests * Add ForwardDiff as test dependency * Add missing tests * Test missing branches * Run formatter * Increment version number * Apply suggestions from code review Co-authored-by: Yuto Horikawa <hyrodium@gmail.com> * Add tests for consistency * Update test/DualQuaternion.jl Co-authored-by: Yuto Horikawa <hyrodium@gmail.com> * Test 100 instead of 10 Co-authored-by: Yuto Horikawa <hyrodium@gmail.com>
1 parent 247d149 commit ffe1bf8

File tree

8 files changed

+875
-167
lines changed

8 files changed

+875
-167
lines changed

Project.toml

Lines changed: 3 additions & 2 deletions
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.5.1"
3+
version = "0.5.2"
44

55
[deps]
66
DualNumbers = "fa6b7ba4-c1ee-5f82-b5fc-ecf0adba8f74"
@@ -12,7 +12,8 @@ DualNumbers = "0.5, 0.6"
1212
julia = "1"
1313

1414
[extras]
15+
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
1516
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
1617

1718
[targets]
18-
test = ["Test"]
19+
test = ["ForwardDiff", "Test"]

src/DualQuaternion.jl

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,43 +6,43 @@ struct DualQuaternion{T<:Real} <: Number
66
norm::Bool
77
end
88

9-
DualQuaternion(q0::Quaternion, qe::Quaternion, n::Bool = false) =
10-
DualQuaternion(promote(q0, qe)..., n)
9+
DualQuaternion{T}(dq::DualQuaternion) where {T<:Real} = DualQuaternion{T}(dq.q0, dq.qe, dq.norm)
10+
function DualQuaternion{T}(d1::Dual, d2::Dual, d3::Dual, d4::Dual, n::Bool=false) where {T<:Real}
11+
return DualQuaternion{T}(
12+
Quaternion(DualNumbers.value(d1), DualNumbers.value(d2), DualNumbers.value(d3), DualNumbers.value(d4), n),
13+
Quaternion(DualNumbers.epsilon(d1), DualNumbers.epsilon(d2), DualNumbers.epsilon(d3), DualNumbers.epsilon(d4)),
14+
n,
15+
)
16+
end
17+
function DualQuaternion{T}(q0::Quaternion) where {T<:Real}
18+
return DualQuaternion{T}(convert(Quaternion{T}, q0), zero(Quaternion{T}), q0.norm)
19+
end
20+
function DualQuaternion{T}(d::Dual) where {T<:Real}
21+
return DualQuaternion(
22+
Quaternion{T}(DualNumbers.value(d)),
23+
Quaternion{T}(DualNumbers.epsilon(d)),
24+
(DualNumbers.value(d)==one(DualNumbers.value(d))) & iszero(DualNumbers.epsilon(d)))
25+
end
26+
function DualQuaternion{T}(x::Real) where {T<:Real}
27+
return DualQuaternion(convert(Quaternion{T}, x), zero(Quaternion{T}), abs(x) == one(x))
28+
end
1129

12-
DualQuaternion(d1::Dual, d2::Dual, d3::Dual, d4::Dual) =
13-
DualQuaternion(Quaternion(DualNumbers.value(d1), DualNumbers.value(d2), DualNumbers.value(d3), DualNumbers.value(d4)),
14-
Quaternion(DualNumbers.epsilon(d1), DualNumbers.epsilon(d2), DualNumbers.epsilon(d3), DualNumbers.epsilon(d4)))
15-
DualQuaternion(d1::Dual, d2::Dual, d3::Dual, d4::Dual, n::Bool) =
30+
DualQuaternion(q0::Quaternion, qe::Quaternion) = DualQuaternion(promote(q0, qe)..., false)
31+
DualQuaternion(d1::Dual, d2::Dual, d3::Dual, d4::Dual, n::Bool=false) =
1632
DualQuaternion(Quaternion(DualNumbers.value(d1), DualNumbers.value(d2), DualNumbers.value(d3), DualNumbers.value(d4), n),
1733
Quaternion(DualNumbers.epsilon(d1), DualNumbers.epsilon(d2), DualNumbers.epsilon(d3), DualNumbers.epsilon(d4)), n)
18-
1934
DualQuaternion(x::Real) = DualQuaternion(Quaternion(x), Quaternion(zero(x)), abs(x) == one(x))
20-
2135
DualQuaternion(d::Dual) = DualQuaternion(Quaternion(DualNumbers.value(d)), Quaternion(DualNumbers.epsilon(d)), (DualNumbers.value(d)==one(DualNumbers.value(d))) & iszero(DualNumbers.epsilon(d)))
22-
2336
DualQuaternion(q::Quaternion) = DualQuaternion(q, zero(q), q.norm)
24-
2537
DualQuaternion(a::Vector) = DualQuaternion(zero(Quaternion{typeof(a[1])}), Quaternion(a))
2638

2739
const DualQuaternionF16 = DualQuaternion{Float16}
2840
const DualQuaternionF32 = DualQuaternion{Float32}
2941
const DualQuaternionF64 = DualQuaternion{Float64}
3042

31-
convert(::Type{DualQuaternion{T}}, x::Real) where {T} = DualQuaternion(convert(T, x))
32-
33-
convert(::Type{DualQuaternion{T}}, d::Dual) where {T} = DualQuaternion(convert(Dual{T}, d))
34-
35-
convert(::Type{DualQuaternion{T}}, q::Quaternion) where {T} = DualQuaternion(convert(Quaternion{T}, q))
36-
37-
convert(::Type{DualQuaternion{T}}, q::DualQuaternion{T}) where {T <: Real} = q
38-
39-
convert(::Type{DualQuaternion{T}}, dq::DualQuaternion) where {T} =
40-
DualQuaternion(convert(Quaternion{T}, dq.q0), convert(Quaternion{T}, dq.qe), dq.norm)
41-
42-
promote_rule(::Type{DualQuaternion{T}}, ::Type{T}) where {T <: Real} = DualQuaternion{T}
43-
promote_rule(::Type{DualQuaternion}, ::Type{T}) where {T <: Real} = DualQuaternion
4443
promote_rule(::Type{DualQuaternion{T}}, ::Type{S}) where {T <: Real, S <: Real} = DualQuaternion{promote_type(T, S)}
45-
promote_rule(::Type{Quaternion{T}}, ::Type{DualQuaternion{S}}) where {T <: Real, S <: Real} = DualQuaternion{promote_type(T, S)}
44+
promote_rule(::Type{DualQuaternion{T}}, ::Type{Dual{S}}) where {T <: Real, S <: Real} = DualQuaternion{promote_type(T, S)}
45+
promote_rule(::Type{DualQuaternion{T}}, ::Type{Quaternion{S}}) where {T <: Real, S <: Real} = DualQuaternion{promote_type(T, S)}
4646
promote_rule(::Type{DualQuaternion{T}}, ::Type{DualQuaternion{S}}) where {T <: Real, S <: Real} = DualQuaternion{promote_type(T, S)}
4747

4848
dualquat(q1, q2) = DualQuaternion(q1, q2)
@@ -97,16 +97,16 @@ function normalize(dq::DualQuaternion)
9797
end
9898
end
9999

100-
function normalizea(dq::DualQuaternion)
100+
function normalizea(dq::DualQuaternion{T}) where {T}
101101
if (dq.norm)
102-
return (dq, one(dual))
102+
return (dq, one(Dual{T}))
103103
end
104104
a = abs(dq)
105105
if abs(a) > 0
106106
qa = dq / a
107107
dualquat(qa.q0, qa.qe, true), a
108108
else
109-
dq, zero(dual)
109+
dq, zero(Dual{T})
110110
end
111111
end
112112

@@ -117,6 +117,8 @@ end
117117
(*)(dq::DualQuaternion, dw::DualQuaternion) = DualQuaternion(dq.q0 * dw.q0,
118118
dq.q0 * dw.qe + dq.qe * dw.q0,
119119
dq.norm && dw.norm)
120+
(*)(dq::DualQuaternion, d::Dual) = (*)(Base.promote(dq, d)...)
121+
(*)(d::Dual, dq::DualQuaternion) = (*)(Base.promote(d, dq)...)
120122
(/)(dq::DualQuaternion, dw::DualQuaternion) = dq * inv(dw)
121123
(==)(q::DualQuaternion, w::DualQuaternion) = (q.q0 == w.q0) & (q.qe == w.qe) # ignore .norm field
122124

src/Octonion.jl

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ struct Octonion{T<:Real} <: Number
1010
norm::Bool
1111
end
1212

13+
Octonion{T}(x::Real) where {T<:Real} = Octonion(convert(T, x))
14+
Octonion{T}(x::Complex) where {T<:Real} = Octonion(convert(Complex{T}, x))
15+
Octonion{T}(q::Quaternion) where {T<:Real} = Octonion(convert(Quaternion{T}, q))
16+
Octonion{T}(o::Octonion) where {T<:Real} =
17+
Octonion{T}(o.s, o.v1, o.v2, o.v3, o.v4, o.v5, o.v6, o.v7, o.norm)
18+
1319
Octonion(s::Real, v1::Real, v2::Real, v3::Real, v4::Real, v5::Real, v6::Real, v7::Real, n::Bool = false) =
1420
Octonion(promote(s, v1, v2, v3, v4, v5, v6, v7)..., n)
1521
Octonion(x::Real) = Octonion(x, zero(x), zero(x), zero(x), zero(x), zero(x), zero(x), zero(x), abs(x) == one(x))
@@ -22,18 +28,9 @@ const OctonionF16 = Octonion{Float16}
2228
const OctonionF32 = Octonion{Float32}
2329
const OctonionF64 = Octonion{Float64}
2430

25-
convert(::Type{Octonion{T}}, x::Real) where {T} = Octonion(convert(T, x))
26-
convert(::Type{Octonion{T}}, z::Complex) where {T} = Octonion(convert(Complex{T}, z))
27-
convert(::Type{Octonion{T}}, q::Quaternion) where {T} = Octonion(convert(Quaternion{T}, q))
28-
convert(::Type{Octonion{T}}, o::Octonion{T}) where {T <: Real} = o
29-
convert(::Type{Octonion{T}}, o::Octonion) where {T} =
30-
Octonion(convert(T, o.s), convert(T, o.v1), convert(T, o.v2), convert(T, o.v3), convert(T, o.v4), convert(T, o.v5), convert(T, o.v6), convert(T, o.v7), o.norm)
31-
32-
promote_rule(::Type{Octonion{T}}, ::Type{T}) where {T <: Real} = Octonion{T}
33-
promote_rule(::Type{Octonion}, ::Type{T}) where {T <: Real} = Octonion
3431
promote_rule(::Type{Octonion{T}}, ::Type{S}) where {T <: Real, S <: Real} = Octonion{promote_type(T, S)}
35-
promote_rule(::Type{Complex{T}}, ::Type{Octonion{S}}) where {T <: Real, S <: Real} = Octonion{promote_type(T, S)}
36-
promote_rule(::Type{Quaternion{T}}, ::Type{Octonion{S}}) where {T <: Real, S <: Real} = Octonion{promote_type(T, S)}
32+
promote_rule(::Type{Octonion{T}}, ::Type{Complex{S}}) where {T <: Real, S <: Real} = Octonion{promote_type(T, S)}
33+
promote_rule(::Type{Octonion{T}}, ::Type{Quaternion{S}}) where {T <: Real, S <: Real} = Octonion{promote_type(T, S)}
3734
promote_rule(::Type{Octonion{T}}, ::Type{Octonion{S}}) where {T <: Real, S <: Real} = Octonion{promote_type(T, S)}
3835

3936
octo(p, v1, v2, v3, v4, v5, v6, v7) = Octonion(p, v1, v2, v3, v4, v5, v6, v7)
@@ -54,9 +51,16 @@ imag(o::Octonion) = [o.v1, o.v2, o.v3, o.v4, o.v5, o.v6, o.v7]
5451
conj(o::Octonion) = Octonion(o.s, -o.v1, -o.v2, -o.v3, -o.v4, -o.v5, -o.v6, -o.v7, o.norm)
5552
abs(o::Octonion) = sqrt(o.s * o.s + o.v1 * o.v1 + o.v2 * o.v2 + o.v3 * o.v3 + o.v4 * o.v4 + o.v5 * o.v5 + o.v6 * o.v6 + o.v7 * o.v7)
5653
float(q::Octonion{T}) where T = convert(Octonion{float(T)}, q)
54+
abs_imag(o::Octonion) = sqrt(o.v1 * o.v1 + o.v2 * o.v2 + o.v3 * o.v3 + o.v4 * o.v4 + o.v5 * o.v5 + o.v6 * o.v6 + o.v7 * o.v7)
5755
abs2(o::Octonion) = o.s * o.s + o.v1 * o.v1 + o.v2 * o.v2 + o.v3 * o.v3 + o.v4 * o.v4 + o.v5 * o.v5 + o.v6 * o.v6 + o.v7 * o.v7
5856
inv(o::Octonion) = o.norm ? conj(o) : conj(o) / abs2(o)
5957

58+
isreal(o::Octonion) = iszero(o.v1) & iszero(o.v2) & iszero(o.v3) & iszero(o.v4) & iszero(o.v5) & iszero(o.v6) & iszero(o.v7)
59+
isfinite(o::Octonion) = o.norm | (isfinite(real(o)) & isfinite(o.v1) & isfinite(o.v2) & isfinite(o.v3) & isfinite(o.v4) & isfinite(o.v5) & isfinite(o.v6) & isfinite(o.v7))
60+
iszero(o::Octonion) = ~o.norm & iszero(real(o)) & iszero(o.v1) & iszero(o.v2) & iszero(o.v3) & iszero(o.v4) & iszero(o.v5) & iszero(o.v6) & iszero(o.v7)
61+
isnan(o::Octonion) = isnan(real(o)) | isnan(o.v1) | isnan(o.v2) | isnan(o.v3) | isnan(o.v4) | isnan(o.v5) | isnan(o.v6) | isnan(o.v7)
62+
isinf(o::Octonion) = ~o.norm & (isinf(real(o)) | isinf(o.v1) | isinf(o.v2) | isinf(o.v3) | isinf(o.v4) | isinf(o.v5) | isinf(o.v6) | isinf(o.v7))
63+
6064
function normalize(o::Octonion)
6165
if (o.norm)
6266
return o
@@ -122,7 +126,7 @@ function exp(o::Octonion)
122126
s = o.s
123127
se = exp(s)
124128
scale = se
125-
th = abs(Octonion(imag(o)))
129+
th = abs_imag(o)
126130
if th > 0
127131
scale *= sin(th) / th
128132
end
@@ -140,7 +144,7 @@ end
140144
function log(o::Octonion)
141145
o, a = normalizea(o)
142146
s = o.s
143-
M = abs(Octonion(imag(o)))
147+
M = abs_imag(o)
144148
th = atan(M, s)
145149
if M > 0
146150
M = th / M
@@ -153,7 +157,7 @@ function log(o::Octonion)
153157
o.v6 * M,
154158
o.v7 * M)
155159
else
156-
return Octonion(log(a), th, 0.0, 0.0)
160+
return Octonion(complex(log(a), ifelse(iszero(a), zero(th), th)))
157161
end
158162
end
159163

src/Quaternion.jl

Lines changed: 15 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,18 @@ const QuaternionF16 = Quaternion{Float16}
1010
const QuaternionF32 = Quaternion{Float32}
1111
const QuaternionF64 = Quaternion{Float64}
1212

13-
(::Type{Quaternion{T}})(x::Real) where {T<:Real} = Quaternion(convert(T, x))
14-
(::Type{Quaternion{T}})(q::Quaternion{T}) where {T<:Real} = q
15-
(::Type{Quaternion{T}})(q::Quaternion) where {T<:Real} = Quaternion{T}(q.s, q.v1, q.v2, q.v3, q.norm)
13+
Quaternion{T}(x::Real) where {T<:Real} = Quaternion(convert(T, x))
14+
Quaternion{T}(x::Complex) where {T<:Real} = Quaternion(convert(Complex{T}, x))
15+
Quaternion{T}(q::Quaternion) where {T<:Real} = Quaternion{T}(q.s, q.v1, q.v2, q.v3, q.norm)
1616
Quaternion(s::Real, v1::Real, v2::Real, v3::Real, n::Bool = false) =
1717
Quaternion(promote(s, v1, v2, v3)..., n)
1818
Quaternion(x::Real) = Quaternion(x, zero(x), zero(x), zero(x), abs(x) == one(x))
1919
Quaternion(z::Complex) = Quaternion(z.re, z.im, zero(z.re), zero(z.re), abs(z) == one(z.re))
2020
Quaternion(s::Real, a::AbstractVector) = Quaternion(s, a[1], a[2], a[3])
2121
Quaternion(a::AbstractVector) = Quaternion(0, a[1], a[2], a[3])
2222

23-
convert(::Type{Quaternion{T}}, x::Real) where {T} = Quaternion(convert(T, x))
24-
convert(::Type{Quaternion{T}}, z::Complex) where {T} = Quaternion(convert(Complex{T}, z))
25-
convert(::Type{Quaternion{T}}, q::Quaternion{T}) where {T <: Real} = q
26-
convert(::Type{Quaternion{T}}, q::Quaternion) where {T} =
27-
Quaternion(convert(T, q.s), convert(T, q.v1), convert(T, q.v2), convert(T, q.v3), q.norm)
28-
29-
promote_rule(::Type{Quaternion{T}}, ::Type{T}) where {T <: Real} = Quaternion{T}
30-
promote_rule(::Type{Quaternion}, ::Type{T}) where {T <: Real} = Quaternion
3123
promote_rule(::Type{Quaternion{T}}, ::Type{S}) where {T <: Real, S <: Real} = Quaternion{promote_type(T, S)}
32-
promote_rule(::Type{Complex{T}}, ::Type{Quaternion{S}}) where {T <: Real, S <: Real} = Quaternion{promote_type(T, S)}
24+
promote_rule(::Type{Quaternion{T}}, ::Type{Complex{S}}) where {T <: Real, S <: Real} = Quaternion{promote_type(T, S)}
3325
promote_rule(::Type{Quaternion{T}}, ::Type{Quaternion{S}}) where {T <: Real, S <: Real} = Quaternion{promote_type(T, S)}
3426

3527
quat(p, v1, v2, v3) = Quaternion(p, v1, v2, v3)
@@ -240,32 +232,19 @@ function linpol(p::Quaternion, q::Quaternion, t::Real)
240232
q = qm
241233
end
242234
c = p.s * q.s + p.v1 * q.v1 + p.v2 * q.v2 + p.v3 * q.v3
243-
if c > - 1.0
244-
if c < 1.0
245-
o = acos(c)
246-
s = sin(o)
247-
sp = sin((1 - t) * o) / s
248-
sq = sin(t * o) / s
249-
else
250-
sp = 1 - t
251-
sq = t
252-
end
253-
Quaternion(sp * p.s + sq * q.s,
254-
sp * p.v1 + sq * q.v1,
255-
sp * p.v2 + sq * q.v2,
256-
sp * p.v3 + sq * q.v3, true)
235+
if c < 1.0
236+
o = acos(c)
237+
s = sin(o)
238+
sp = sin((1 - t) * o) / s
239+
sq = sin(t * o) / s
257240
else
258-
s = p.v3
259-
v1 = -p.v2
260-
v2 = p.v1
261-
v3 = -p.s
262-
sp = sin((0.5 - t) * pi)
263-
sq = sin(t * pi)
264-
Quaternion(s,
265-
sp * p.v1 + sq * v1,
266-
sp * p.v2 + sq * v2,
267-
sp * p.v3 + sq * v3, true)
241+
sp = 1 - t
242+
sq = t
268243
end
244+
Quaternion(sp * p.s + sq * q.s,
245+
sp * p.v1 + sq * q.v1,
246+
sp * p.v2 + sq * q.v2,
247+
sp * p.v3 + sq * q.v3, true)
269248
end
270249

271250
quatrand(rng = Random.GLOBAL_RNG) = quat(randn(rng), randn(rng), randn(rng), randn(rng))

src/Quaternions.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module Quaternions
77
import Base: convert, promote_rule, float
88
import Base: rand, randn
99
import LinearAlgebra: lyap, norm, normalize, sylvester
10+
using LinearAlgebra: cross, dot
1011
using Random
1112

1213
Base.@irrational INV_SQRT_EIGHT 0.3535533905932737622004 sqrt(big(0.125))

0 commit comments

Comments
 (0)