Skip to content

Commit 5a2bb75

Browse files
authored
Improvements for drawing random numbers (#42)
* Add Random as dependency * Add optional rng parameter * Add rand/randn mirroring same for Complex * Add quaternion random tests * Use irrational multiplication instead of integer division * Increment version number * Add rand methods for Octionion and DualQuaternion * Test all rand methods * Add functions to readme
1 parent fd991e3 commit 5a2bb75

File tree

7 files changed

+131
-3
lines changed

7 files changed

+131
-3
lines changed

Project.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
name = "Quaternions"
22
uuid = "94ee1d12-ae83-5a48-8b1c-48b8ff168ae0"
3-
version = "0.4.2"
3+
version = "0.4.3"
44

55
[deps]
66
DualNumbers = "fa6b7ba4-c1ee-5f82-b5fc-ecf0adba8f74"
77
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
8+
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
89

910
[compat]
1011
DualNumbers = "0.5, 0.6"

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ Implemented functions are:
2828
cos
2929
sqrt
3030
linpol (interpolate between 2 normalized quaternions)
31+
rand
32+
randn
3133

3234
[Dual quaternions](http://en.wikipedia.org/wiki/Dual_quaternion) are an extension, combining quaternions with
3335
[dual numbers](https://github.com/scidom/DualNumbers.jl).
@@ -53,6 +55,7 @@ further implemented here:
5355
exp
5456
log
5557
sqrt
58+
rand
5659

5760
[Octonions](http://en.wikipedia.org/wiki/Octonion) form the logical next step on the Complex-Quaternion path.
5861
They play a role, for instance, in the mathematical foundation of String theory.
@@ -70,3 +73,5 @@ They play a role, for instance, in the mathematical foundation of String theory.
7073
exp
7174
log
7275
sqrt
76+
rand
77+
randn

src/DualQuaternion.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,3 +169,7 @@ end
169169

170170
dualquatrand() = dualquat(quatrand(), quatrand())
171171
ndualquatrand() = normalize(dualquatrand())
172+
173+
function rand(rng::AbstractRNG, ::Random.SamplerType{DualQuaternion{T}}) where {T<:Real}
174+
return DualQuaternion{T}(rand(rng, Quaternion{T}), rand(rng, Quaternion{T}), false)
175+
end

src/Octonion.jl

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,22 @@ function sqrt(o::Octonion)
159159
end
160160

161161
octorand() = octo(randn(), randn(), randn(), randn(), randn(), randn(), randn(), randn())
162+
163+
function rand(rng::AbstractRNG, ::Random.SamplerType{Octonion{T}}) where {T<:Real}
164+
Octonion{T}(rand(rng, T), rand(rng, T), rand(rng, T), rand(rng, T),
165+
rand(rng, T), rand(rng, T), rand(rng, T), rand(rng, T), false)
166+
end
167+
168+
function randn(rng::AbstractRNG, ::Type{Octonion{T}}) where {T<:AbstractFloat}
169+
Octonion{T}(
170+
randn(rng, T) * INV_SQRT_EIGHT,
171+
randn(rng, T) * INV_SQRT_EIGHT,
172+
randn(rng, T) * INV_SQRT_EIGHT,
173+
randn(rng, T) * INV_SQRT_EIGHT,
174+
randn(rng, T) * INV_SQRT_EIGHT,
175+
randn(rng, T) * INV_SQRT_EIGHT,
176+
randn(rng, T) * INV_SQRT_EIGHT,
177+
randn(rng, T) * INV_SQRT_EIGHT,
178+
false,
179+
)
180+
end

src/Quaternion.jl

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,22 @@ function linpol(p::Quaternion, q::Quaternion, t::Real)
184184
end
185185
end
186186

187-
quatrand() = quat(randn(), randn(), randn(), randn())
188-
nquatrand() = normalize(quatrand())
187+
quatrand(rng = Random.GLOBAL_RNG) = quat(randn(rng), randn(rng), randn(rng), randn(rng))
188+
nquatrand(rng = Random.GLOBAL_RNG) = normalize(quatrand(rng))
189+
190+
function rand(rng::AbstractRNG, ::Random.SamplerType{Quaternion{T}}) where {T<:Real}
191+
Quaternion{T}(rand(rng, T), rand(rng, T), rand(rng, T), rand(rng, T), false)
192+
end
193+
194+
function randn(rng::AbstractRNG, ::Type{Quaternion{T}}) where {T<:AbstractFloat}
195+
Quaternion{T}(
196+
randn(rng, T) * 1//2,
197+
randn(rng, T) * 1//2,
198+
randn(rng, T) * 1//2,
199+
randn(rng, T) * 1//2,
200+
false,
201+
)
202+
end
189203

190204
## Rotations
191205

src/Quaternions.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ module Quaternions
55
import Base: +, -, *, /, ^
66
import Base: abs, abs2, angle, conj, cos, exp, inv, isfinite, log, real, sin, sqrt
77
import Base: convert, promote_rule, float
8+
import Base: rand, randn
89
import LinearAlgebra: norm, normalize
10+
using Random
11+
12+
Base.@irrational INV_SQRT_EIGHT 0.3535533905932737622004 sqrt(big(0.125))
913

1014
include("Quaternion.jl")
1115
include("Octonion.jl")

test/test_Quaternion.jl

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11

22
using Quaternions: argq
33
using LinearAlgebra
4+
using Random
45

56
# creating random examples
67
sample(QT::Type{Quaternion{T}}) where {T <: Integer} = QT(rand(-100:100, 4)..., false)
@@ -139,3 +140,83 @@ for _ in 1:100
139140
@test q linpol(q1, q2, t) linpol(q q1, q q2, t)
140141
end
141142
end
143+
144+
@testset "random quaternions" begin
145+
@testset "quatrand" begin
146+
rng = Random.MersenneTwister(42)
147+
q1 = quatrand(rng)
148+
@test q1 isa Quaternion
149+
@test !q1.norm
150+
151+
q2 = quatrand()
152+
@test q2 isa Quaternion
153+
@test !q2.norm
154+
end
155+
156+
@testset "nquatrand" begin
157+
rng = Random.MersenneTwister(42)
158+
q1 = nquatrand(rng)
159+
@test q1 isa Quaternion
160+
@test q1.norm
161+
162+
q2 = nquatrand()
163+
@test q2 isa Quaternion
164+
@test q2.norm
165+
end
166+
167+
@testset "rand($H)" for H in (Quaternion, DualQuaternion, Octonion)
168+
rng = Random.MersenneTwister(42)
169+
q1 = rand(rng, H{Float64})
170+
@test q1 isa H{Float64}
171+
@test !q1.norm
172+
173+
q2 = rand(rng, H{Float32})
174+
@test q2 isa H{Float32}
175+
@test !q2.norm
176+
177+
qs = rand(rng, H{Float64}, 1000)
178+
@test eltype(qs) === H{Float64}
179+
@test length(qs) == 1000
180+
xs = map(qs) do q
181+
if q isa DualQuaternion
182+
return [real(q.q0); Quaternions.imag(q.q0); real(q.qe); Quaternions.imag(q.qe)]
183+
else
184+
return [real(q); Quaternions.imag(q)]
185+
end
186+
end
187+
xs_mean = sum(xs) / length(xs)
188+
xs_var = sum(x -> abs2.(x .- xs_mean), xs) / (length(xs) - 1)
189+
@test all(isapprox.(xs_mean, 0.5; atol=0.1))
190+
@test all(isapprox.(xs_var, 1/12; atol=0.01))
191+
end
192+
193+
@testset "randn($H)" for H in (Quaternion, Octonion)
194+
rng = Random.MersenneTwister(42)
195+
q1 = randn(rng, H{Float64})
196+
@test q1 isa H{Float64}
197+
@test !q1.norm
198+
199+
q2 = randn(rng, H{Float32})
200+
@test q2 isa H{Float32}
201+
@test !q2.norm
202+
203+
qs = randn(rng, H{Float64}, 10000)
204+
@test eltype(qs) === H{Float64}
205+
@test length(qs) == 10000
206+
xs = map(qs) do q
207+
if q isa DualQuaternion
208+
return [real(q.q0); Quaternions.imag(q.q0); real(q.qe); Quaternions.imag(q.qe)]
209+
else
210+
return [real(q); Quaternions.imag(q)]
211+
end
212+
end
213+
xs_mean = sum(xs) / length(xs)
214+
xs_var = sum(x -> abs2.(x .- xs_mean), xs) / (length(xs) - 1)
215+
@test all(isapprox.(xs_mean, 0; atol=0.1))
216+
if H === Quaternion
217+
@test all(isapprox.(xs_var, 1/4; atol=0.1))
218+
else
219+
@test all(isapprox.(xs_var, 1/8; atol=0.1))
220+
end
221+
end
222+
end

0 commit comments

Comments
 (0)