Hi All,
I’m learning Julia and coming from R and a bit of Python. To help me figure Julia out (in a way that’s directly related to my current work), I’ve decided to write a module based on the Vose/Walker Alias method for sampling randomly from arbitrary discrete distributions. I realize there are some relevant functions in the StatsBase and Distributions packages, but I find learning through errors is helpful. I have encountered an error, though, that’s proven to be hard for me to resolve on my own and I’m hoping someone can point me in the right direction. I’m sure this problem boils down to confusion about Types and how to work with them properly.
Here’s the code:
#= implement the Vose algorithm in Julia http://www.keithschwarz.com/darts-dice-coins/
=#
using Random
import Base.rand
import Random: Sampler, SamplerSimple, SamplerTrivial, Repetition, eltype
rng = MersenneTwister()
n = 5
a = collect(1:n)
b = collect(1:n)
c = rand(Float32,n)
# discrete distribution struct
struct DiscreteDistribution
prob::Vector
support::Vector
end
struct AliasTable
prob::Vector
support::Vector
accept::Vector
alias::Vector
end
dd = DiscreteDistribution(c,a)
# build alias table
function vose_method(dd::DiscreteDistribution)
n_prob = length(dd.prob)
n_support = length(dd.support)
sum_prob = sum(dd.prob)
prob_scaled = dd.prob * n_prob
accept = Vector(undef, n_prob)
alias = Vector{typeof(dd.support[1])}(undef, n_prob)
small = collect(1:n_prob)[prob_scaled .< 1]
large = collect(1:n_prob)[prob_scaled .>= 1]
while length(large) > 0 && length(small) > 0
s = popfirst!(small)
l = popfirst!(large)
accept[s] = prob_scaled[s]
alias[s] = dd.support[l]
prob_scaled[l] = (prob_scaled[l] + prob_scaled[s]) - 1
prob_scaled[l] < 1 ? push!(small, l) : push!(large, l)
end
while length(large) > 0
l = popfirst!(large)
accept[l] = 1
end
while length(small) > 0
s = popfirst!(small)
accept[s] = 1
end
return AliasTable(dd.prob, dd.support, accept, alias)
end
# sampling method
function sample_vose(rng, aliastable::AliasTable)
j = rand(rng, 1:length(aliastable.support))
outcome = rand(rng) <= aliastable.prob[j] ? aliastable.support[j] : aliastable.alias[j]
end
# new sampler
function Random.Sampler(::Type{<:AbstractRNG}, d::DiscreteDistribution, ::Repetition)
SamplerSimple(d, vose_method(d))
end
Random.eltype(::Type{<:DiscreteDistribution}) = Int
sp = Random.Sampler(rng, dd)
isa(sp, SamplerSimple{<:DiscreteDistribution})
# extending rand
function Random.rand(rng::Type{<:AbstractRNG}, sp::SamplerSimple{<:DiscreteDistribution})
sample_vose(rng, sp.data)
end
rand(rng, sp)
The last function call returns the error,
LoadError: ArgumentError: Sampler for this object is not defined
But, isa(sp, SamplerSimple{<:DiscreteDistribution})
returns true
, so I’m not sure where the dispatch is running into problems.
Any information would be greatly appreciated. I did read the docs in the Standard Library for the Random module and was able to produce a similar error when the example function make_alias_table
is defined to return a custom type (without that, if it returns a vector for example, the dispatch seems to work as expected). But, I can’t see why that’s not working either. In case it helps, here’s the code for the example straight out of the docs with the modification to make_alias_table
just described,
using Random
import Base.rand
import Random: Sampler, SamplerSimple, Repetition, eltype
rng = MersenneTwister()
struct DiscreteDistribution
probabilities::Vector
end
struct OtherType
b::Vector
end
Random.eltype(::Type{<:DiscreteDistribution}) = Int
dd = DiscreteDistribution([0.2,0.2,0.2,0.2,0.2])
function make_alias_table(p)
OtherType(p)
end
function Random.Sampler(::Type{<:AbstractRNG}, distribution::DiscreteDistribution, ::Repetition)
SamplerSimple(distribution, make_alias_table(distribution.probabilities))
end
sp = Sampler(rng, dd)
function draw_number(rng, sp)
rand(rng, sp.data)
end
function rand(rng::AbstractRNG, sp::SamplerSimple{<:DiscreteDistribution})
draw_number(rng, sp)
end
Many thanks for any help!
Chris