Skip to content
Closed
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
4 changes: 3 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ LogExpFunctions = "0.3.14, 1.0"
Pkg = "1"
PrecompileTools = "1.2.0"
Random = "1.10"
SafeTestsets = "0.1, 1"
Statistics = "1"
Test = "1"
julia = "1.10"
Expand All @@ -28,8 +29,9 @@ Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
ExplicitImports = "7d51a73a-1435-4ff3-83d9-f097790105c7"
JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["AllocCheck", "Aqua", "Statistics", "Test", "Distributions", "JET", "ExplicitImports", "Pkg"]
test = ["AllocCheck", "Aqua", "Statistics", "Test", "Distributions", "JET", "ExplicitImports", "Pkg", "SafeTestsets"]
25 changes: 25 additions & 0 deletions test/bigfloat_tests.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using PoissonRandom
using Statistics
using Test

@testset "count_rand with BigFloat (λ < 6)" begin
for _ in 1:100
result = pois_rand(BigFloat(3.0))
@test result isa Integer
@test result >= 0
end
end
@testset "ad_rand with BigFloat (λ >= 6)" begin
for _ in 1:100
result = pois_rand(BigFloat(15.0))
@test result isa Integer
@test result >= 0
end
end
@testset "statistical validity with BigFloat" begin
n = 10000
λ = BigFloat(10.0)
samples = [pois_rand(λ) for _ in 1:n]
sample_mean = mean(samples)
@test abs(sample_mean - Float64(λ)) < 3 * sqrt(Float64(λ))
end
16 changes: 16 additions & 0 deletions test/passthrough_rng_tests.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using PoissonRandom
using Random: Random, UInt52Raw
using Test

prng = PassthroughRNG()
# The CUDA.jl @device_override Random.randexp(::AbstractRNG) shadows our
# specific Random.randexp(::PassthroughRNG) on the GPU because Julia's
# OverlayMethodTable returns overlay matches without consulting the base
# table when the overlay fully covers the signature. The override body
# then calls these against PassthroughRNG; if they MethodError, kernel
# compilation fails with InvalidIRError on jl_f_throw_methoderror.
@test Random.rng_native_52(prng) === UInt64
@test Random.rand(prng, UInt52Raw()) isa UInt64
@test Random.rand(prng, UInt64) isa UInt64
@test Random.rand(prng, Float32) isa Float32
@test Random.rand(prng, Float64) isa Float64
2 changes: 2 additions & 0 deletions test/qa/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ ExplicitImports = "7d51a73a-1435-4ff3-83d9-f097790105c7"
JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
PoissonRandom = "e409e4f3-bfea-5376-8464-e040bb5c01ab"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[sources]
Expand All @@ -14,5 +15,6 @@ Aqua = "0.8"
ExplicitImports = "1.14.0"
JET = "0.9, 0.10, 0.11"
Random = "1.10"
SafeTestsets = "0.1, 1"
Test = "1"
julia = "1.10"
37 changes: 7 additions & 30 deletions test/qa/qa.jl
Original file line number Diff line number Diff line change
@@ -1,36 +1,13 @@
using PoissonRandom, Aqua, JET, ExplicitImports
using Random
using Test
using SafeTestsets

@testset "Aqua" begin
Aqua.find_persistent_tasks_deps(PoissonRandom)
Aqua.test_ambiguities(PoissonRandom, recursive = false)
Aqua.test_deps_compat(PoissonRandom)
Aqua.test_piracies(
PoissonRandom,
treat_as_own = []
)
Aqua.test_project_extras(PoissonRandom)
Aqua.test_stale_deps(PoissonRandom)
Aqua.test_unbound_args(PoissonRandom)
Aqua.test_undefined_exports(PoissonRandom)
@safetestset "Aqua" begin
include("qa_aqua.jl")
end

@testset "JET static analysis" begin
@testset "Type stability" begin
JET.@test_opt target_modules = (PoissonRandom,) pois_rand(10.0)
JET.@test_opt target_modules = (PoissonRandom,) pois_rand(Random.default_rng(), 10.0)
JET.@test_opt target_modules = (PoissonRandom,) pois_rand(PassthroughRNG(), 10.0)
end

@testset "Error analysis" begin
JET.@test_call target_modules = (PoissonRandom,) pois_rand(10.0)
JET.@test_call target_modules = (PoissonRandom,) pois_rand(Random.default_rng(), 10.0)
JET.@test_call target_modules = (PoissonRandom,) pois_rand(PassthroughRNG(), 10.0)
end
@safetestset "JET static analysis" begin
include("qa_jet.jl")
end

@testset "ExplicitImports" begin
@test check_no_implicit_imports(PoissonRandom) === nothing
@test check_no_stale_explicit_imports(PoissonRandom) === nothing
@safetestset "ExplicitImports" begin
include("qa_explicitimports.jl")
end
14 changes: 14 additions & 0 deletions test/qa/qa_aqua.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using PoissonRandom, Aqua
using Test

Aqua.find_persistent_tasks_deps(PoissonRandom)
Aqua.test_ambiguities(PoissonRandom, recursive = false)
Aqua.test_deps_compat(PoissonRandom)
Aqua.test_piracies(
PoissonRandom,
treat_as_own = []
)
Aqua.test_project_extras(PoissonRandom)
Aqua.test_stale_deps(PoissonRandom)
Aqua.test_unbound_args(PoissonRandom)
Aqua.test_undefined_exports(PoissonRandom)
5 changes: 5 additions & 0 deletions test/qa/qa_explicitimports.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using PoissonRandom, ExplicitImports
using Test

@test check_no_implicit_imports(PoissonRandom) === nothing
@test check_no_stale_explicit_imports(PoissonRandom) === nothing
15 changes: 15 additions & 0 deletions test/qa/qa_jet.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using PoissonRandom, JET
using Random
using Test

@testset "Type stability" begin
JET.@test_opt target_modules = (PoissonRandom,) pois_rand(10.0)
JET.@test_opt target_modules = (PoissonRandom,) pois_rand(Random.default_rng(), 10.0)
JET.@test_opt target_modules = (PoissonRandom,) pois_rand(PassthroughRNG(), 10.0)
end

@testset "Error analysis" begin
JET.@test_call target_modules = (PoissonRandom,) pois_rand(10.0)
JET.@test_call target_modules = (PoissonRandom,) pois_rand(Random.default_rng(), 10.0)
JET.@test_call target_modules = (PoissonRandom,) pois_rand(PassthroughRNG(), 10.0)
end
131 changes: 8 additions & 123 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using PoissonRandom
import Distributions
using Test, Statistics
using SafeTestsets

const GROUP = get(ENV, "GROUP", "All")

Expand All @@ -11,135 +12,19 @@ if GROUP == "QA"
Pkg.instantiate()
include(joinpath(@__DIR__, "qa", "qa.jl"))
else
n_tsamples = 10^5

function test_samples(
rand_func,
distr::Distributions.DiscreteUnivariateDistribution,
n::Int; # number of samples to generate
q::Float64 = 1.0e-8, # confidence interval, 1 - q as confidence
verbose::Bool = false
) # show intermediate info (for debugging)

# The basic idea
# ------------------
# Generate n samples, and count the occurrences of each value within a reasonable range.
# For each distinct value, it computes an confidence interval of the counts
# and checks whether the count is within this interval.
#
# If the distribution has a bounded range, it also checks whether
# the samples are all within this range.
#
# By setting a small q, we ensure that failure of the tests rarely
# happen in practice.
#
λ = distr.λ
n > 1 || error("The number of samples must be greater than 1.")
0.0 < q < 0.1 || error("The value of q must be within the open interval (0.0, 0.1).")

# determine the range of values to examine
vmin = minimum(distr)
vmax = maximum(distr)

rmin = floor(Int, quantile(distr, 0.00001))::Int
rmax = floor(Int, quantile(distr, 0.99999))::Int
m = rmax - rmin + 1 # length of the range
p0 = Distributions.pdf.((distr,), rmin:rmax) # reference probability masses
@assert length(p0) == m

# determine confidence intervals for counts:
# with probability q, the count will be out of this interval.
#
clb = Vector{Int}(undef, m)
cub = Vector{Int}(undef, m)
for i in 1:m
bp = Distributions.Binomial(n, p0[i])
clb[i] = floor(Int, quantile(bp, q / 2))
cub[i] = ceil(Int, Distributions.cquantile(bp, q / 2))
@assert cub[i] >= clb[i]
end

# generate samples
samples = [rand_func(λ) for i in 1:n]
@assert length(samples) == n

# scan samples and get counts
cnts = zeros(Int, m)
for i in 1:n
@inbounds si = samples[i]
if rmin <= si <= rmax
cnts[si - rmin + 1] += 1
else
vmin <= si <= vmax ||
error("Sample value out of valid range.")
end
end

# check the counts
for i in 1:m
verbose && println("v = $(rmin + i - 1) ==> ($(clb[i]), $(cub[i])): $(cnts[i])")
clb[i] <= cnts[i] <= cub[i] ||
error("The counts are out of the confidence interval.")
end
return samples
end

println("testing count random sampler")
for λ in [0.2, 0.5, 1.0, 2.0, 5.0, 10.0, 15.0, 20.0, 30.0]
test_samples(PoissonRandom.count_rand, Distributions.Poisson(λ), n_tsamples)
end

println("testing ad random sampler")
for λ in [5.0, 10.0, 15.0, 20.0, 30.0]
test_samples(PoissonRandom.ad_rand, Distributions.Poisson(λ), n_tsamples)
end

println("testing mixed random sampler")
for λ in [5.0, 10.0, 15.0, 20.0, 30.0]
test_samples(pois_rand, Distributions.Poisson(λ), n_tsamples)
@safetestset "Sampler distribution tests" begin
include("sampler_tests.jl")
end

@testset "BigFloat support" begin
@testset "count_rand with BigFloat (λ < 6)" begin
for _ in 1:100
result = pois_rand(BigFloat(3.0))
@test result isa Integer
@test result >= 0
end
end
@testset "ad_rand with BigFloat (λ >= 6)" begin
for _ in 1:100
result = pois_rand(BigFloat(15.0))
@test result isa Integer
@test result >= 0
end
end
@testset "statistical validity with BigFloat" begin
n = 10000
λ = BigFloat(10.0)
samples = [pois_rand(λ) for _ in 1:n]
sample_mean = mean(samples)
@test abs(sample_mean - Float64(λ)) < 3 * sqrt(Float64(λ))
end
@safetestset "BigFloat support" begin
include("bigfloat_tests.jl")
end

@testset "PassthroughRNG dispatch" begin
using Random: Random, UInt52Raw
prng = PassthroughRNG()
# The CUDA.jl @device_override Random.randexp(::AbstractRNG) shadows our
# specific Random.randexp(::PassthroughRNG) on the GPU because Julia's
# OverlayMethodTable returns overlay matches without consulting the base
# table when the overlay fully covers the signature. The override body
# then calls these against PassthroughRNG; if they MethodError, kernel
# compilation fails with InvalidIRError on jl_f_throw_methoderror.
@test Random.rng_native_52(prng) === UInt64
@test Random.rand(prng, UInt52Raw()) isa UInt64
@test Random.rand(prng, UInt64) isa UInt64
@test Random.rand(prng, Float32) isa Float32
@test Random.rand(prng, Float64) isa Float64
@safetestset "PassthroughRNG dispatch" begin
include("passthrough_rng_tests.jl")
end

@testset "Allocation Tests" begin
@safetestset "Allocation Tests" begin
include("alloc_tests.jl")
end
end
Loading
Loading