Skip to content

Commit 4a60a81

Browse files
authored
Merge pull request #15 from jw3126/manytests
Tons of tests
2 parents ac488e9 + cbbabf8 commit 4a60a81

File tree

3 files changed

+167
-56
lines changed

3 files changed

+167
-56
lines changed

src/Quaternion.jl

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ imag(q::Quaternion) = [q.v1, q.v2, q.v3]
4444

4545
conj(q::Quaternion) = Quaternion(q.s, -q.v1, -q.v2, -q.v3, q.norm)
4646
abs(q::Quaternion) = sqrt(q.s*q.s + q.v1*q.v1 + q.v2*q.v2 + q.v3*q.v3)
47+
abs_imag(q::Quaternion) = sqrt(q.v1*q.v1 + q.v2*q.v2 + q.v3*q.v3)
4748
abs2(q::Quaternion) = q.s*q.s + q.v1*q.v1 + q.v2*q.v2 + q.v3*q.v3
4849
inv(q::Quaternion) = q.norm ? conj(q) : conj(q)/abs2(q)
4950

@@ -93,7 +94,7 @@ end
9394

9495
angleaxis(q::Quaternion) = angle(q), axis(q)
9596

96-
angle(q::Quaternion) = 2 * acos(real(normalize(q)))
97+
angle(q::Quaternion) = 2*atan2((q.v1^2 + q.v2^2 + q.v3^2), q.s)
9798

9899
function axis(q::Quaternion)
99100
q = normalize(q)
@@ -103,17 +104,13 @@ function axis(q::Quaternion)
103104
[1.0, 0.0, 0.0]
104105
end
105106

106-
function argq(q::Quaternion)
107-
q = normalize(q)
108-
q = Quaternion(imag(q))
109-
normalizeq(q)
110-
end
107+
argq(q::Quaternion) = normalizeq(Quaternion(0, q.v1, q.v2, q.v3))
111108

112109
function exp(q::Quaternion)
113110
s = q.s
114111
se = exp(s)
115112
scale = se
116-
th = abs(Quaternion(imag(q)))
113+
th = abs_imag(q)
117114
if th > 0
118115
scale *= sin(th) / th
119116
end
@@ -123,7 +120,7 @@ end
123120
function log(q::Quaternion)
124121
q, a = normalizea(q)
125122
s = q.s
126-
M = abs(Quaternion(imag(q)))
123+
M = abs_imag(q)
127124
th = atan2(M, s)
128125
if M > 0
129126
M = th / M
@@ -162,24 +159,24 @@ function linpol(p::Quaternion, q::Quaternion, t::Real)
162159
sp = sin((1 - t)*o)/s
163160
sq = sin(t*o)/s
164161
else
165-
sp = 1 - t
166-
sq = t
167-
end
168-
Quaternion(sp*p.s + sq*q.s,
169-
sp*p.v1 + sq*q.v1,
170-
sp*p.v2 + sq*q.v2,
171-
sp*p.v3 + sq*q.v3, true)
162+
sp = 1 - t
163+
sq = t
164+
end
165+
Quaternion(sp*p.s + sq*q.s,
166+
sp*p.v1 + sq*q.v1,
167+
sp*p.v2 + sq*q.v2,
168+
sp*p.v3 + sq*q.v3, true)
172169
else
173-
s = p.v3
174-
v1 = -p.v2
175-
v2 = p.v1
176-
v3 = -p.s
177-
sp = sin((0.5 - t)*pi)
178-
sq = sin(t*pi)
179-
Quaternion(s,
180-
sp * p.v1 + sq * v1,
181-
sp * p.v2 + sq * v2,
182-
sp * p.v3 + sq * v3, true)
170+
s = p.v3
171+
v1 = -p.v2
172+
v2 = p.v1
173+
v3 = -p.s
174+
sp = sin((0.5 - t)*pi)
175+
sq = sin(t*pi)
176+
Quaternion(s,
177+
sp * p.v1 + sq * v1,
178+
sp * p.v2 + sq * v2,
179+
sp * p.v3 + sq * v3, true)
183180
end
184181
end
185182

test/runtests.jl

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,12 @@
11
using Base.Test
22
using Quaternions
33

4-
qx = qrotation([1,0,0], pi/4)
5-
@test_approx_eq qx*qx qrotation([1,0,0], pi/2)
6-
@test_approx_eq qx^2 qrotation([1,0,0], pi/2)
7-
theta = pi/8
8-
qx = qrotation([1,0,0], theta)
9-
c = cos(theta); s = sin(theta)
10-
Rx = [1 0 0; 0 c -s; 0 s c]
11-
@test_approx_eq rotationmatrix(qx) Rx
12-
theta = pi/6
13-
qy = qrotation([0,1,0], theta)
14-
c = cos(theta); s = sin(theta)
15-
Ry = [c 0 s; 0 1 0; -s 0 c]
16-
@test_approx_eq rotationmatrix(qy) Ry
17-
theta = 4pi/3
18-
qz = qrotation([0,0,1], theta)
19-
c = cos(theta); s = sin(theta)
20-
Rz = [c -s 0; s c 0; 0 0 1]
21-
@test_approx_eq rotationmatrix(qz) Rz
22-
23-
@test_approx_eq rotationmatrix(qx*qy*qz) Rx*Ry*Rz
24-
@test_approx_eq rotationmatrix(qy*qx*qz) Ry*Rx*Rz
25-
@test_approx_eq rotationmatrix(qz*qx*qy) Rz*Rx*Ry
26-
27-
a, b = qrotation([0,0,1], deg2rad(0)), qrotation([0,0,1], deg2rad(180))
28-
@test_approx_eq slerp(a,b,0.0) a
29-
@test_approx_eq slerp(a,b,1.0) b
30-
@test_approx_eq slerp(a,b,0.5) qrotation([0,0,1], deg2rad(90))
31-
32-
@test_approx_eq angle(qrotation([1,0,0], 0)) 0
33-
@test_approx_eq angle(qrotation([0,1,0], pi/4)) pi/4
34-
@test_approx_eq angle(qrotation([0,0,1], pi/2)) pi/2
4+
test_associative(x, y, z, ) = @test (xy)z x(yz)
5+
test_commutative(x, y, ) = @test xy yx
6+
test_inverse(x, eins, , inv) = (@test xinv(x) eins; @test inv(x)x eins)
7+
test_neutral(x, eins, ) = (@test xeins x; @test einsx x)
8+
test_monoid(x, y, z, , eins) = (test_associative(x, y, z, ); test_neutral(x, eins, ))
9+
test_group(x, y, z, , eins, inv) = (test_monoid(x,y,z, , eins);test_inverse(x, eins, , inv))
10+
test_multiplicative(x,y,, f) = @test f(xy) f(x) f(y)
11+
12+
include("test_Quaternion.jl")

test/test_Quaternion.jl

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
2+
import Quaternions.argq
3+
4+
# creating random examples
5+
sample{T <: Integer}(QT::Type{Quaternion{T}}) = QT(rand(-100:100,4)..., false)
6+
sample{T <: AbstractFloat}(QT::Type{Quaternion{T}}) = QT(rand(Bool)? quatrand() : nquatrand())
7+
sample{T <: Integer}(CT::Type{Complex{T}}) = CT(rand(-100:100,2)...)
8+
sample{T <: AbstractFloat}(CT::Type{Complex{T}}) = CT(randn(2)...)
9+
sample(T, n) = T[sample(T) for _ in 1:n]
10+
11+
# test algebraic properties of quaternions
12+
for _ in 1:10, T in (Float32, Float64, Int32, Int64)
13+
q,q1,q2,q3 = sample(Quaternion{T}, 4)
14+
15+
# skewfield
16+
test_group(q1,q2,q3, +, zero(q), -)
17+
test_group(q1,q2,q3, *, one(q), inv)
18+
test_multiplicative(q1,q2,*,norm)
19+
20+
# complex embedding
21+
c1, c2 = sample(Complex{T}, 2)
22+
test_multiplicative(c1,c2,*, Quaternion)
23+
test_multiplicative(c1,c2,+, Quaternion)
24+
end
25+
26+
let # test rotations
27+
qx = qrotation([1,0,0], pi/4)
28+
@test_approx_eq qx*qx qrotation([1,0,0], pi/2)
29+
@test_approx_eq qx^2 qrotation([1,0,0], pi/2)
30+
theta = pi/8
31+
qx = qrotation([1,0,0], theta)
32+
c = cos(theta); s = sin(theta)
33+
Rx = [1 0 0; 0 c -s; 0 s c]
34+
@test_approx_eq rotationmatrix(qx) Rx
35+
theta = pi/6
36+
qy = qrotation([0,1,0], theta)
37+
c = cos(theta); s = sin(theta)
38+
Ry = [c 0 s; 0 1 0; -s 0 c]
39+
@test_approx_eq rotationmatrix(qy) Ry
40+
theta = 4pi/3
41+
qz = qrotation([0,0,1], theta)
42+
c = cos(theta); s = sin(theta)
43+
Rz = [c -s 0; s c 0; 0 0 1]
44+
@test_approx_eq rotationmatrix(qz) Rz
45+
46+
@test_approx_eq rotationmatrix(qx*qy*qz) Rx*Ry*Rz
47+
@test_approx_eq rotationmatrix(qy*qx*qz) Ry*Rx*Rz
48+
@test_approx_eq rotationmatrix(qz*qx*qy) Rz*Rx*Ry
49+
50+
a, b = qrotation([0,0,1], deg2rad(0)), qrotation([0,0,1], deg2rad(180))
51+
@test_approx_eq slerp(a,b,0.0) a
52+
@test_approx_eq slerp(a,b,1.0) b
53+
@test_approx_eq slerp(a,b,0.5) qrotation([0,0,1], deg2rad(90))
54+
55+
@test_approx_eq angle(qrotation([1,0,0], 0)) 0
56+
@test_approx_eq angle(qrotation([0,1,0], pi/4)) pi/4
57+
@test_approx_eq angle(qrotation([0,0,1], pi/2)) pi/2
58+
59+
60+
61+
let # test numerical stability of angle
62+
ax = randn(3)
63+
for θ in [1e-9, π - 1e-9]
64+
q = qrotation(ax, θ)
65+
@test_approx_eq angle(q) θ
66+
end
67+
end
68+
end
69+
70+
for _ in 1:100
71+
let # test specialfunctions
72+
c = Complex(randn(2)...)
73+
q,q2 = sample(Quaternion{Float64}, 4)
74+
unary_funs = [exp, log, sin, cos, sqrt, inv, conj, abs2, norm]
75+
# since every quaternion is conjugate to a complex number,
76+
# one can establish correctness as follows:
77+
for fun in unary_funs
78+
@test fun(Quaternion(c)) Quaternion(fun(c))
79+
@test q2*fun(q)*inv(q2) fun(q2*q*inv(q2))
80+
end
81+
82+
@test exp(log(q)) q
83+
@test exp(zero(q)) one(q)
84+
end
85+
86+
let # test qrotation and angleaxis inverse
87+
ax = randn(3); ax = ax/norm(ax)
88+
Θ = π * rand()
89+
q = qrotation(ax, Θ)
90+
@test angle(q) Θ
91+
@test axis(q) ax
92+
@test angleaxis(q)[1] Θ
93+
@test angleaxis(q)[2] ax
94+
end
95+
96+
let # test argq
97+
q,q2 = sample(Quaternion{Float64}, 2)
98+
@test q2*argq(q)*inv(q2) argq(q2*q*inv(q2))
99+
v = Quaternion(0, randn(3)...)
100+
@test argq(v)*norm(v) v
101+
end
102+
103+
let # test normalize
104+
q = quatrand()
105+
@test norm(normalize(q)) 1
106+
@test normalize(q).norm
107+
@test q norm(q) * normalize(q)
108+
qn = nquatrand()
109+
@test qn.norm
110+
@test normalize(qn) === qn
111+
end
112+
113+
let # test slerp and linpol if q1 = 1
114+
q1 = quat(1,0,0,0.)
115+
# there are numerical stability issues with slerp atm
116+
θ = clamp(rand() * 3.5, deg2rad(5e-1) ,π)
117+
ax = randn(3)
118+
q2 = qrotation(ax, θ)
119+
t = rand()
120+
slerp(q1, q2, 0.) q1
121+
@test slerp(q1, q2, 0.) q1
122+
@test slerp(q1, q2, 1.) q2
123+
@test slerp(q1, q2, t) qrotation(ax, t*θ)
124+
@test norm(slerp(q1, q2, t)) 1
125+
@test slerp(q1, q2, 0.5) qrotation(ax, 0.5*θ)
126+
@test linpol(q1, q2, 0.5) qrotation(ax, 0.5*θ)
127+
128+
end
129+
let # test conjugation invariance
130+
q, q1, q2 = sample(Quaternion{Float64}, 3)
131+
(s, t) = s*t*inv(s)
132+
t = rand()
133+
@test q slerp(q1, q2, t) slerp(q q1, q q2, t)
134+
@test q linpol(q1, q2, t) linpol(q q1, q q2, t)
135+
end
136+
end

0 commit comments

Comments
 (0)