Custom Sampler with multiple parameters

Hello, with the following structures I’d like to do this:

struct A
  a::Float64
  b::Float64
end


struct B
  x::Float64
end

b = B(32.5)

res = rand(A, b, 3) # Vector of 3 A

with the computation of the random 3 A factoring in the parameter b.

Is there any way to do this using a custom Sampler without having to write a wrapper

struct AB
  a::A
  b::B
end

Random.Sampler(RNG::Type{<:AbstractRNG}, ab::AB, r::Random.Repetition) = #code returning A

That would be neat as I don’t want to write a wrapper for every type that I’d like to generate that uses B parameters!

Thank you :heart:

I would probably do something like this

using Random

Random.rand(T::Type, b::B, k) = Random.rand(Random.default_rng(), T, b, k)

function Random.rand(rng::AbstractRNG, T::Type, b::B, k)
	# random operations
	a = rand(rng) * b.x + k
	T(a, k)
end

Modify the code inside the function as you wish, e.g. to get your vector.

Then

julia> rand(A, B(3), 5) # uses default random generator
A(5.48452407660821, 5.0)

julia> rand(A, B(3), 5)
A(5.93748059591882, 5.0)

julia> rand(MersenneTwister(0), A, B(3), 5) # custom
A(7.470942523932237, 5.0)

julia> rand(MersenneTwister(0), A, B(3), 5)  # same result
A(7.470942523932237, 5.0)

1 Like

Awesome.

Didn’t think of not using the Sampler interface from the documentation!

Thank you!

1 Like

The RandomExtension.jl is specifically designed for this use-case. One limitation of the Random module is that it’s designed to accept only one object to specify the sampling space. As you mentioned in the OP, the obvious way to specify multiple parameters is to define a struct wrapping these parameters (like e.g. Normal from the Distribution package), but it can quickly become tedious in some situations.

In RandomExtensions, there is one generic such struct, called Make (think of it as a Tuple), and you construct these objects by calling the helper function make. Your problem could be solved for example as follows:

struct A
  a::Float64
  b::Float64
end

struct B
  x::Float64
end

b = B(32.5)

using Random
using RandomExtensions: make, Make

# use the Sampler interface from Random:
Random.rand(rng::AbstractRNG, sp::Random.SamplerTrivial{<:Make{<:A}}) =
       A(rand(rng), sp[][1].x)

# now call `rand` via `make`:
julia> rand(make(A, b))
A(0.935066169840552, 32.5)

julia> rand(Xoshiro(0), make(A, b), 3)
3-element Vector{A}:
 A(0.4056994708920292, 32.5)
 A(0.06854582438651502, 32.5)
 A(0.8621408571954849, 32.5)

Of course, you can also extend rand to accept directly more parameters, but this would be “sugar”; the make approach is more composable. E.g.

Random.rand(rng::AbstractRNG, ::Type{A}, b::B, k::Integer) =
    rand(rng, make(A, b), k)
1 Like

Bonjour, @rfourquet! This is so awesome.

The extension looks only natural to get merged into the base Random API!

Thank you! This could be marked as a solution to my issue.

1 Like