随机数
Julia 中的随机数生成默认使用 Xoshiro256++ 算法,每个 Task 都有自己的状态。其他 RNG 类型可以通过继承 AbstractRNG 类型来插入;然后可以使用它们来获取多个随机数流。
Random 包导出的 PRNG(伪随机数生成器)是
TaskLocalRNG:一个代表使用当前活动任务本地流的标记,它从父任务中确定性地播种,或者在程序启动时由RandomDevice(使用系统随机性)播种。Xoshiro:使用 Xoshiro256++ 算法生成高质量的随机数流,具有小的状态向量和高性能。RandomDevice:用于操作系统提供的熵。这可以用于密码安全的随机数(CS(P)RNG)。MersenneTwister:一个可选的高质量 PRNG,它在 Julia 的旧版本中是默认的,也很快,但需要更多空间来存储状态向量和生成随机序列。
大多数与随机生成相关的函数接受一个可选的 AbstractRNG 对象作为第一个参数。有些函数还接受维度规范 dims...(也可以作为元组给出)来生成随机值的数组。在多线程程序中,你通常应该从不同的线程或任务中使用不同的 RNG 对象,以确保线程安全。然而,从 Julia 1.3 开始,默认的 RNG 是线程安全的(在 1.6 版本之前使用每个线程的 RNG,之后使用每个任务的 RNG)。
提供的 RNG 可以生成以下类型的均匀随机数:Float16、Float32、Float64、BigFloat、Bool、Int8、UInt8、Int16、UInt16、Int32、UInt32、Int64、UInt64、Int128、UInt128、BigInt(或这些类型的复数)。随机浮点数在 $[0, 1)$ 中均匀生成。由于 BigInt 表示无界整数,因此必须指定区间(例如 rand(big.(1:6)))。
此外,还为一些 AbstractFloat 和 Complex 类型实现了正态分布和指数分布,有关详细信息,请参见 randn 和 randexp。
要从其他分布生成随机数,请参见 Distributions.jl 包。
由于随机数的生成方式被认为是实现细节,因此在版本变更后,bug 修复和速度改进可能会更改生成的数字流。因此,在单元测试中依赖特定的种子或生成的数字流是不鼓励的 - 考虑测试相关方法的属性。
随机数模块
Random.Random — 模块Random支持生成随机数。提供 rand、randn、AbstractRNG、MersenneTwister 和 RandomDevice。
随机生成函数
Base.rand — 函数rand([rng=default_rng()], [S], [dims...])从 S 指定的值集中随机选取一个元素或随机元素数组;S 可以是
- 一个可索引的集合(例如
1:9或('x', "y", :z)), - 一个
AbstractDict或AbstractSet对象, - 一个字符串(被视为字符集合),或
- 一个类型:要从中选取的值集等同于整数的
typemin(S):typemax(S)(不适用于BigInt),对于浮点数是 $[0, 1)$,对于复数浮点数是 $[0, 1)+i[0, 1)$;
S 默认值为 Float64。当除了可选的 rng 之外只传递一个参数并且该参数是一个 Tuple 时,它被解释为一个值集合(S),而不是 dims。
另请参见 randn 获取正态分布的数字,以及 rand! 和 randn! 获取就地等效项。
将 S 作为元组的支持需要至少 Julia 1.1。
示例
julia> rand(Int, 2)
2-element Array{Int64,1}:
1339893410598768192
1575814717733606317
julia> using Random
julia> rand(MersenneTwister(0), Dict(1=>2, 3=>4))
1=>2
julia> rand((2, 3))
3
julia> rand(Float64, (2, 3))
2×3 Array{Float64,2}:
0.999717 0.0143835 0.540787
0.696556 0.783855 0.938235rand(rng, s::Union{AbstractDict,AbstractSet}) 的复杂度与 s 的长度成线性关系,除非存在具有常数复杂度的优化方法,这种情况适用于 Dict、Set 和密集的 BitSet。对于多次调用,请改用 rand(rng, collect(s)),或者根据需要使用 rand(rng, Dict(s)) 或 rand(rng, Set(s))。
Random.rand! — 函数rand!([rng=default_rng()], A, [S=eltype(A)])用随机值填充数组 A。如果指定了 S(S 可以是类型或集合,有关详细信息,请参见 rand),则从 S 中随机选择值。这等效于 copyto!(A, rand(rng, S, size(A))),但不会分配新的数组。
示例
julia> rng = MersenneTwister(1234);
julia> rand!(rng, zeros(5))
5-element Vector{Float64}:
0.5908446386657102
0.7667970365022592
0.5662374165061859
0.4600853424625171
0.7940257103317943Random.bitrand — 函数bitrand([rng=default_rng()], [dims...])生成一个随机布尔值的 BitArray。
示例
julia> rng = MersenneTwister(1234);
julia> bitrand(rng, 10)
10-element BitVector:
0
0
0
0
1
0
0
0
1
1Base.randn — 函数randn([rng=default_rng()], [T=Float64], [dims...])生成一个均值为 0、标准差为 1 的类型为 T 的正态分布随机数。可以选择生成一个正态分布随机数数组。Base 模块目前提供了对 Float16、Float32 和 Float64(默认)类型及其 Complex 对应类型支持。当类型参数为复数时,值从方差为 1 的圆对称复数正态分布中抽取(对应于实部和虚部具有均值为零、方差为 1/2 的独立正态分布)。
另请参见 randn! 获取就地操作。
示例
julia> using Random
julia> rng = MersenneTwister(1234);
julia> randn(rng, ComplexF64)
0.6133070881429037 - 0.6376291670853887im
julia> randn(rng, ComplexF32, (2, 3))
2×3 Matrix{ComplexF32}:
-0.349649-0.638457im 0.376756-0.192146im -0.396334-0.0136413im
0.611224+1.56403im 0.355204-0.365563im 0.0905552+1.31012imRandom.randn! — 函数randn!([rng=default_rng()], A::AbstractArray) -> A用正态分布(均值为 0,标准差为 1)的随机数填充数组 A。另请参阅 rand 函数。
示例
julia> rng = MersenneTwister(1234);
julia> randn!(rng, zeros(5))
5-element Vector{Float64}:
0.8673472019512456
-0.9017438158568171
-0.4944787535042339
-0.9029142938652416
0.8644013132535154Random.randexp — 函数randexp([rng=default_rng()], [T=Float64], [dims...])根据尺度为 1 的指数分布生成类型为 T 的随机数。可以选择生成此类随机数的数组。Base 模块目前为 Float16、Float32 和 Float64(默认值)类型提供了实现。
示例
julia> rng = MersenneTwister(1234);
julia> randexp(rng, Float32)
2.4835055f0
julia> randexp(rng, 3, 3)
3×3 Matrix{Float64}:
1.5167 1.30652 0.344435
0.604436 2.78029 0.418516
0.695867 0.693292 0.643644Random.randexp! — 函数randexp!([rng=default_rng()], A::AbstractArray) -> A用遵循指数分布(尺度为 1)的随机数填充数组 A。
示例
julia> rng = MersenneTwister(1234);
julia> randexp!(rng, zeros(5))
5-element Vector{Float64}:
2.4835053723904896
1.516703605376473
0.6044364871025417
0.6958665886385867
1.3065196315496677Random.randstring — 函数randstring([rng=default_rng()], [chars], [len=8])创建一个长度为 len 的随机字符串,该字符串由 chars 中的字符组成,默认情况下为大小写字母和数字 0-9。可选的 rng 参数指定一个随机数生成器,请参阅 随机数。
示例
julia> Random.seed!(3); randstring()
"Lxz5hUwn"
julia> randstring(MersenneTwister(3), 'a':'z', 6)
"ocucay"
julia> randstring("ACGT")
"TGCTCCTC"chars 可以是任何类型的字符集合,如 Char 或 UInt8(更有效率),前提是 rand 可以从中随机选择字符。
子序列、排列和洗牌
Random.randsubseq — 函数randsubseq([rng=default_rng(),] A, p) -> Vector返回一个向量,该向量包含给定数组 A 的随机子序列,其中 A 的每个元素都以独立概率 p(按顺序)包含在内。(复杂度为 p*length(A) 的线性,所以即使 p 很小而 A 很大,此函数也效率很高。)从技术上讲,此过程称为 A 的“伯努利采样”。
示例
julia> rng = MersenneTwister(1234);
julia> randsubseq(rng, 1:8, 0.3)
2-element Vector{Int64}:
7
8Random.randsubseq! — 函数randsubseq!([rng=default_rng(),] S, A, p)与 randsubseq 相似,但结果存储在 S 中(根据需要调整大小)。
示例
julia> rng = MersenneTwister(1234);
julia> S = Int64[];
julia> randsubseq!(rng, S, 1:8, 0.3)
2-element Vector{Int64}:
7
8
julia> S
2-element Vector{Int64}:
7
8Random.randperm — 函数randperm([rng=default_rng(),] n::Integer)构造一个长度为 n 的随机排列。可选的 rng 参数指定一个随机数生成器(请参阅 随机数)。结果的元素类型与 n 的类型相同。
要随机排列任意向量,请参阅 shuffle 或 shuffle!。
在 Julia 1.1 中,randperm 返回一个向量 v,其中 eltype(v) == typeof(n),而在 Julia 1.0 中,eltype(v) == Int。
示例
julia> randperm(MersenneTwister(1234), 4)
4-element Vector{Int64}:
2
1
4
3Random.randperm! — 函数Random.randcycle — 函数randcycle([rng=default_rng(),] n::Integer)构造一个长度为 n 的随机循环排列。可选的 rng 参数指定一个随机数生成器,请参阅 随机数。结果的元素类型与 n 的类型相同。
在 Julia 1.1 中,randcycle 返回一个向量 v,其中 eltype(v) == typeof(n),而在 Julia 1.0 中,eltype(v) == Int。
示例
julia> randcycle(MersenneTwister(1234), 6)
6-element Vector{Int64}:
3
5
4
6
1
2Random.randcycle! — 函数randcycle!([rng=default_rng(),] A::Array{<:Integer})在 A 中构造一个长度为 length(A) 的随机循环排列。可选的 rng 参数指定一个随机数生成器,请参阅 随机数。
示例
julia> randcycle!(MersenneTwister(1234), Vector{Int}(undef, 6))
6-element Vector{Int64}:
3
5
4
6
1
2Random.shuffle — 函数Random.shuffle! — 函数shuffle!([rng=default_rng(),] v::AbstractArray)shuffle 的就地版本:就地随机排列 v,可以选择提供随机数生成器 rng。
示例
julia> rng = MersenneTwister(1234);
julia> shuffle!(rng, Vector(1:16))
16-element Vector{Int64}:
2
15
5
14
1
9
10
6
11
3
16
7
4
12
8
13生成器(创建和播种)
Random.default_rng — 函数default_rng() -> rng返回默认的全局随机数生成器 (RNG)。
默认的 RNG 是什么是一个实现细节。在不同版本的 Julia 中,您不应该期望默认的 RNG 始终相同,也不应该期望它对给定的种子返回相同的随机数流。
此函数在 Julia 1.3 中引入。
Random.seed! — 函数seed!([rng=default_rng()], seed) -> rng
seed!([rng=default_rng()]) -> rng重新播种随机数生成器:当且仅当提供 seed 时,rng 才会产生可重复的数字序列。某些 RNG 不接受种子,例如 RandomDevice。在调用 seed! 之后,rng 等同于使用相同种子初始化的全新对象。
如果未指定 rng,则它默认为播种共享的任务本地生成器状态。
示例
julia> Random.seed!(1234);
julia> x1 = rand(2)
2-element Vector{Float64}:
0.32597672886359486
0.5490511363155669
julia> Random.seed!(1234);
julia> x2 = rand(2)
2-element Vector{Float64}:
0.32597672886359486
0.5490511363155669
julia> x1 == x2
true
julia> rng = Xoshiro(1234); rand(rng, 2) == x1
true
julia> Xoshiro(1) == Random.seed!(rng, 1)
true
julia> rand(Random.seed!(rng), Bool) # not reproducible
true
julia> rand(Random.seed!(rng), Bool) # not reproducible either
false
julia> rand(Xoshiro(), Bool) # not reproducible either
trueRandom.AbstractRNG — 类型AbstractRNG随机数生成器的超类型,例如 MersenneTwister 和 RandomDevice。
Random.TaskLocalRNG — 类型TaskLocalRNGTaskLocalRNG 的状态与其任务相关,与其线程无关。它在任务创建时播种,来自其父任务的状态。因此,任务创建是一个更改父 RNG 状态的事件。
作为优势,TaskLocalRNG 非常快,并且允许可重复的多线程模拟(不包括竞争条件),与调度程序决策无关。只要线程数量不用于决定任务创建,模拟结果也与可用线程/CPU 数量无关。随机流不应依赖于硬件细节,直到字节序,甚至可能包括字长。
使用或播种除 current_task() 返回的任务之外的任何其他任务的 RNG 都是未定义的行为:它在大多数情况下都能正常工作,有时可能会静默失败。
Random.Xoshiro — 类型Xoshiro(seed)
Xoshiro()Xoshiro256++ 是一种快速伪随机数生成器,由 David Blackman 和 Sebastiano Vigna 在“Scrambled Linear Pseudorandom Number Generators”中描述,ACM Trans. Math. Softw.,2021 年。参考实现可在 http://prng.di.unimi.it 获得。
除了速度快之外,Xoshiro 的内存占用也很小,这使其适合需要长时间保存许多不同随机状态的应用程序。
Julia 的 Xoshiro 实现具有批量生成模式;它从父级播种新的虚拟 PRNG,并使用 SIMD 并行生成(即,批量流由多个交织的 xoshiro 实例组成)。虚拟 PRNG 在批量请求得到满足后就被丢弃(并且不应该导致堆分配)。
示例
julia> using Random
julia> rng = Xoshiro(1234);
julia> x1 = rand(rng, 2)
2-element Vector{Float64}:
0.32597672886359486
0.5490511363155669
julia> rng = Xoshiro(1234);
julia> x2 = rand(rng, 2)
2-element Vector{Float64}:
0.32597672886359486
0.5490511363155669
julia> x1 == x2
trueRandom.MersenneTwister — 类型MersenneTwister(seed)
MersenneTwister()创建一个 MersenneTwister RNG 对象。不同的 RNG 对象可以拥有自己的种子,这对于生成不同的随机数流可能很有用。seed 可以是非负整数或 UInt32 整数向量。如果未提供种子,则会创建随机生成的种子(使用来自系统的熵)。请参阅 seed! 函数以重新播种现有的 MersenneTwister 对象。
示例
julia> rng = MersenneTwister(1234);
julia> x1 = rand(rng, 2)
2-element Vector{Float64}:
0.5908446386657102
0.7667970365022592
julia> rng = MersenneTwister(1234);
julia> x2 = rand(rng, 2)
2-element Vector{Float64}:
0.5908446386657102
0.7667970365022592
julia> x1 == x2
trueRandom.RandomDevice — 类型RandomDevice()创建一个 RandomDevice RNG 对象。两个这样的对象将始终生成不同的随机数流。熵来自操作系统。
连接到 Random API
有两种基本正交的方法可以扩展 Random 功能
- 生成自定义类型的随机值
- 创建新的生成器
1) 的 API 非常实用,但相对较新,因此它可能还需要在 Random 模块的后续版本中发展。例如,通常只需实现一个 rand 方法即可让所有其他常用方法自动工作。
2) 的 API 仍然很初级,可能需要实现者比严格必要的工作更多,才能支持生成的值的常用类型。
生成自定义类型的随机值
为某些分布生成随机值可能涉及各种权衡。预先计算的值,例如离散分布的 别名表,或单变量分布的 “压缩”函数,可以显着加快采样速度。预先计算多少信息可能取决于我们计划从分布中抽取的值的数量。此外,某些随机数生成器可能具有各种算法想要利用的某些属性。
Random 模块定义了一个可定制的框架来获取随机值,可以解决这些问题。每次调用 rand 都会生成一个采样器,该采样器可以通过向 Sampler 添加方法来定制,而 Sampler 又可以根据随机数生成器、表征分布的对象和重复次数的建议进行分派。目前,对于后者,使用 Val{1}(对于单个样本)和 Val{Inf}(对于任意数量),Random.Repetition 是它们的别名。
Sampler 返回的对象随后用于生成随机值。当为可以从中采样的值 X 实现随机生成接口时,实现者应该定义方法
rand(rng, sampler)对于 Sampler(rng, X, repetition) 返回的特定 sampler。
采样器可以是实现 rand(rng, sampler) 的任意值,但对于大多数应用程序,以下预定义的采样器可能就足够了
SamplerType{T}()可用于实现从类型T中抽取的采样器(例如rand(Int))。这是Sampler为类型返回的默认值。SamplerTrivial(self)是self的简单包装器,可以通过[]访问。当不需要预先计算的信息时(例如rand(1:3)),这是推荐的采样器,并且是Sampler为值返回的默认值。SamplerSimple(self, data)还包含额外的data字段,可用于存储任意预计算的值,这些值应该在Sampler的自定义方法中计算。
我们为每个示例提供示例。我们假设这里的算法选择与 RNG 无关,因此在我们的签名中使用 AbstractRNG。
Random.Sampler — 类型Sampler(rng, x, repetition = Val(Inf))返回一个采样器对象,可用于从 rng 为 x 生成随机值。
当 sp = Sampler(rng, x, repetition) 时,rand(rng, sp) 将用于绘制随机值,并且应该相应地定义。
repetition 可以是 Val(1) 或 Val(Inf),并且应该用作决定预计算量的建议,如果适用。
Random.SamplerType 和 Random.SamplerTrivial 分别是类型和值的默认回退。 Random.SamplerSimple 可用于存储预计算的值,而无需为仅此目的定义额外的类型。
Random.SamplerType — 类型SamplerType{T}()类型采样器,不包含其他信息。当使用类型调用 Sampler 时,它是 Sampler 的默认回退。
Random.SamplerTrivial — 类型SamplerTrivial(x)创建一个仅包装给定值 x 的采样器。这是值的默认回退。此采样器的 eltype 等于 eltype(x)。
推荐的用例是从没有预计算数据的数值中采样。
Random.SamplerSimple — 类型SamplerSimple(x, data)创建一个包装给定值 x 和 data 的采样器。此采样器的 eltype 等于 eltype(x)。
推荐的用例是从具有预计算数据的数值中采样。
将预计算与实际生成值解耦是 API 的一部分,用户也可以使用它。例如,假设 rand(rng, 1:20) 必须在循环中重复调用:利用这种解耦的方法如下
rng = MersenneTwister()
sp = Random.Sampler(rng, 1:20) # or Random.Sampler(MersenneTwister, 1:20)
for x in X
n = rand(rng, sp) # similar to n = rand(rng, 1:20)
# use n
end这也是标准库中使用的机制,例如由随机数组生成(如 rand(1:20, 10) 中)的默认实现。
从类型生成值
给定一个类型 T,目前假设如果定义了 rand(T),则将生成一个类型为 T 的对象。 SamplerType 是类型默认采样器。为了定义类型 T 值的随机生成,应该定义 rand(rng::AbstractRNG, ::Random.SamplerType{T}) 方法,并且应该返回 rand(rng, T) 预计返回的值。
让我们看一下以下示例:我们实现了一个 Die 类型,具有可变数量的 n 面,从 1 到 n 编号。我们希望 rand(Die) 生成一个最多 20 面(至少 4 面)的 Die
struct Die
nsides::Int # number of sides
end
Random.rand(rng::AbstractRNG, ::Random.SamplerType{Die}) = Die(rand(rng, 4:20))
# output
Die 的标量和数组方法现在按预期工作
julia> rand(Die)
Die(5)
julia> rand(MersenneTwister(0), Die)
Die(11)
julia> rand(Die, 3)
3-element Vector{Die}:
Die(9)
Die(15)
Die(14)
julia> a = Vector{Die}(undef, 3); rand!(a)
3-element Vector{Die}:
Die(19)
Die(7)
Die(17)没有预计算数据的简单采样器
这里我们为一个集合定义一个采样器。如果不需要预计算数据,它可以使用 SamplerTrivial 采样器实现,实际上它也是值的默认回退。
为了定义从类型 S 的对象中生成随机值,应该定义以下方法:rand(rng::AbstractRNG, sp::Random.SamplerTrivial{S})。这里,sp 只是包装了类型 S 的对象,可以通过 sp[] 访问该对象。继续 Die 示例,我们现在希望定义 rand(d::Die) 来生成对应于 d 的一个面的 Int
julia> Random.rand(rng::AbstractRNG, d::Random.SamplerTrivial{Die}) = rand(rng, 1:d[].nsides);
julia> rand(Die(4))
1
julia> rand(Die(4), 3)
3-element Vector{Any}:
2
3
3给定一个集合类型 S,目前假设如果定义了 rand(::S),则将生成一个类型为 eltype(S) 的对象。在最后一个示例中,生成了一个 Vector{Any};原因是 eltype(Die) == Any。解决方法是定义 Base.eltype(::Type{Die}) = Int。
为 AbstractFloat 类型生成值
AbstractFloat 类型是特殊情况,因为默认情况下,随机值不是在整个类型域中生成,而是在 [0,1) 中生成。对于 T <: AbstractFloat,应该实现以下方法:Random.rand(::AbstractRNG, ::Random.SamplerTrivial{Random.CloseOpen01{T}})
使用预计算数据的优化采样器
考虑一个离散分布,其中数字 1:n 以给定的概率绘制,这些概率之和为 1。当需要从该分布中绘制许多值时,最快的 方法是使用别名表。我们不会在这里提供构建此表的算法,但假设它在 make_alias_table(probabilities) 中可用,并且 draw_number(rng, alias_table) 可用于从中绘制随机数。
假设分布由
struct DiscreteDistribution{V <: AbstractVector}
probabilities::V
end描述,并且我们始终希望构建一个别名表,无论需要多少个值(我们将在下面学习如何自定义它)。方法
Random.eltype(::Type{<:DiscreteDistribution}) = Int
function Random.Sampler(::Type{<:AbstractRNG}, distribution::DiscreteDistribution, ::Repetition)
SamplerSimple(disribution, make_alias_table(distribution.probabilities))
end应该定义为返回一个具有预计算数据的采样器,然后
function rand(rng::AbstractRNG, sp::SamplerSimple{<:DiscreteDistribution})
draw_number(rng, sp.data)
end将用于绘制值。
自定义采样器类型
SamplerSimple 类型足以满足大多数具有预计算数据的用例。但是,为了演示如何使用自定义采样器类型,这里我们将实现类似于 SamplerSimple 的内容。
回到我们的 Die 示例:rand(::Die) 使用从范围内的随机生成,因此有机会进行这种优化。我们称我们的自定义采样器为 SamplerDie。
import Random: Sampler, rand
struct SamplerDie <: Sampler{Int} # generates values of type Int
die::Die
sp::Sampler{Int} # this is an abstract type, so this could be improved
end
Sampler(RNG::Type{<:AbstractRNG}, die::Die, r::Random.Repetition) =
SamplerDie(die, Sampler(RNG, 1:die.nsides, r))
# the `r` parameter will be explained later on
rand(rng::AbstractRNG, sp::SamplerDie) = rand(rng, sp.sp)现在可以通过 sp = Sampler(rng, die) 获取采样器,并在任何涉及 rng 的 rand 调用中使用 sp 而不是 die。在上面的简单示例中,die 不需要存储在 SamplerDie 中,但这在实践中通常是这种情况。
当然,这种模式非常频繁,以至于上面使用的辅助类型,即 Random.SamplerSimple 是可用的,这节省了我们定义 SamplerDie 的工作:我们可以用以下方法实现我们的解耦
Sampler(RNG::Type{<:AbstractRNG}, die::Die, r::Random.Repetition) =
SamplerSimple(die, Sampler(RNG, 1:die.nsides, r))
rand(rng::AbstractRNG, sp::SamplerSimple{Die}) = rand(rng, sp.data)这里,sp.data 指的是对 SamplerSimple 构造函数的调用的第二个参数(在本例中等于 Sampler(rng, 1:die.nsides, r)),而 Die 对象可以通过 sp[] 访问。
与 SamplerDie 一样,任何自定义采样器都必须是 Sampler{T} 的子类型,其中 T 是生成值的类型。请注意,SamplerSimple(x, data) isa Sampler{eltype(x)},因此这限制了 SamplerSimple 的第一个参数可以是什么(建议像 Die 示例中那样使用 SamplerSimple,其中 x 只是在定义 Sampler 方法时转发)。类似地,SamplerTrivial(x) isa Sampler{eltype(x)}。
目前还有另一种辅助类型可用于其他情况,Random.SamplerTag,但它被认为是内部 API,并且可能在任何时候都会在没有适当弃用通知的情况下中断。
对标量或数组生成使用不同的算法
在某些情况下,是否要生成少量值或大量值将影响算法的选择。这由 Sampler 构造函数的第三个参数处理。让我们假设我们为 Die 定义了两个辅助类型,比如 SamplerDie1,应该用于仅生成少量随机值,而 SamplerDieMany 用于生成许多值。我们可以像下面这样使用这些类型
Sampler(RNG::Type{<:AbstractRNG}, die::Die, ::Val{1}) = SamplerDie1(...)
Sampler(RNG::Type{<:AbstractRNG}, die::Die, ::Val{Inf}) = SamplerDieMany(...)当然,rand 也必须在这些类型上定义(即 rand(::AbstractRNG, ::SamplerDie1) 和 rand(::AbstractRNG, ::SamplerDieMany))。请注意,与往常一样,如果不需要自定义类型,则可以使用 SamplerTrivial 和 SamplerSimple。
注意:Sampler(rng, x) 只是 Sampler(rng, x, Val(Inf)) 的简写,而 Random.Repetition 是 Union{Val{1}, Val{Inf}} 的别名。
创建新的生成器
API 尚未明确定义,但作为经验法则
- 任何生成“基本”类型(
Base中的isbitstype整数和浮点类型)的rand方法都应该为该特定 RNG 定义,如果需要的话; - 其他接受
AbstractRNG的已记录rand方法应该开箱即用(假设 1) 中所依赖的方法已实现),但当然也可以为该 RNG 特化,如果还有优化空间的话; - 伪 RNG 的
copy应该返回一个独立的副本,该副本在以相同方式调用时,从该点开始生成与原始副本完全相同的随机序列。当这不可行时(例如基于硬件的 RNG),copy必须不实现。
关于 1),rand 方法可能恰好自动工作,但它不受官方支持,并且可能在后续版本中在没有警告的情况下中断。
为了为假设的 MyRNG 生成器定义一个新的 rand 方法,以及一个值规范 s(例如 s == Int 或 s == 1:10)类型 S==typeof(s) 或 S==Type{s}(如果 s 是一个类型),则必须定义与之前相同的两个方法
Sampler(::Type{MyRNG}, ::S, ::Repetition),它返回一个类型为SamplerS的对象rand(rng::MyRNG, sp::SamplerS)
可能 Sampler(rng::AbstractRNG, ::S, ::Repetition) 已经在 Random 模块中定义。然后在实践中可以跳过步骤 1)(如果要将生成专门化为该特定 RNG 类型),但相应的 SamplerS 类型被认为是内部细节,可能会在没有警告的情况下更改。
专门化数组生成
在某些情况下,对于给定的 RNG 类型,使用专门的方法生成随机值数组比仅仅使用之前解释的解耦技术更有效。例如,对于 MersenneTwister 来说就是这样,它本机将随机值写入数组。
为了实现 MyRNG 的特化,以及对规格 s 的特化,生成类型为 S 的元素,可以定义以下方法:rand!(rng::MyRNG, a::AbstractArray{S}, ::SamplerS),其中 SamplerS 是由 Sampler(MyRNG, s, Val(Inf)) 返回的采样器的类型。代替 AbstractArray,也可以仅针对子类型实现功能,例如 Array{S}。rand 的非变异数组方法会自动在内部调用此特化。
可重复性
通过使用一个用给定种子初始化的 RNG 参数,您可以在多次运行程序时重现相同的伪随机数序列。但是,Julia 的一个小版本发布(例如从 1.3 到 1.4)可能会更改从特定种子生成的伪随机数序列,特别是如果使用的是 MersenneTwister。(即使由像 rand 这样的底层函数生成的序列没有变化,像 randsubseq 这样的更高级函数的输出可能会由于算法更新而发生变化。)理由:保证伪随机流永远不会改变会禁止许多算法改进。
如果您需要保证随机数据的完全可重复性,建议您简单地保存数据(例如,作为科学出版物中的补充附件)。(当然,您也可以指定特定的 Julia 版本和包清单,尤其是在您需要位级可重复性的情况下。)
依赖于特定“随机”数据的软件测试通常也应该保存数据、将其嵌入测试代码中,或者使用第三方包,例如 StableRNGs.jl。另一方面,对于大多数随机数据应该通过的测试(例如,针对随机矩阵 A = randn(n,n) 测试 A \ (A*x) ≈ x)可以使用带有固定种子的 RNG 来确保仅仅多次运行测试不会遇到由于非常不可能的数据而导致的失败(例如,一个极其病态的矩阵)。
从其中抽取随机样本的统计分布是保证在任何 Julia 小版本发布中保持一致的。