亂數
Julia 中的亂數產生預設使用 Xoshiro256++ 演算法,每個 Task
都有自己的狀態。其他 RNG 類型可以透過繼承 AbstractRNG
類型來插入;然後可以使用它們來取得多個亂數串流。
Random
套件匯出的 PRNG(偽亂數產生器)為
TaskLocalRNG
:一個代表使用目前活動 Task 本地串流的權杖,由父 Task 確定性設定種子,或在程式啟動時由RandomDevice
(使用系統亂數)設定種子Xoshiro
:使用 Xoshiro256++ 演算法,以小型狀態向量和高性能產生高品質的亂數串流RandomDevice
:用於作業系統提供的熵。這可用於加密安全亂數 (CS(P)RNG)。MersenneTwister
:另一種高品質的 PRNG,是 Julia 舊版本中的預設值,而且速度也很快,但需要更大的空間來儲存狀態向量並產生亂數序列。
大多數與亂數產生相關的函式都接受一個選用的 AbstractRNG
物件作為第一個參數。有些也接受維度規格 dims...
(也可以表示為元組)以產生亂數值的陣列。在多執行緒程式中,通常應該從不同的執行緒或任務中使用不同的 RNG 物件,以確保執行緒安全。然而,預設的 RNG 在 Julia 1.3 中是執行緒安全的(在 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 套件。
由於產生隨機數的確切方式被視為實作細節,因此錯誤修正和速度改善可能會在版本變更後變更產生的數字串流。因此,不建議在單元測試期間依賴特定種子或產生的數字串流 - 考慮改為測試相關方法的屬性。
隨機數模組
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
作為一個 tuple 至少需要 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.938235
rand(rng, s::Union{AbstractDict,AbstractSet})
的複雜度與 s
的長度成線性關係,除非有最佳化方法可提供常數複雜度,而 Dict
、Set
和密集的 BitSet
s 就是這種情況。如果呼叫次數超過幾次,請改用 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.7940257103317943
Random.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
1
Base.randn
— 函數randn([rng=default_rng()], [T=Float64], [dims...])
產生一個平均值為 0、標準差為 1 的常態分佈隨機數,類型為 T
。也可以選擇產生一個常態分佈隨機數陣列。Base
模組目前提供 Float16
、Float32
和 Float64
(預設值)以及其 Complex
對應項目的實作。當類型引數為複數時,值會從方差為 1 的圓對稱複數常態分佈中抽出(對應到實部和虛部具有平均值為 0 和方差為 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.31012im
Random.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.8644013132535154
Random.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.643644
Random.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.3065196315496677
Random.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
8
Random.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
8
Random.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
3
Random.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
2
Random.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
2
Random.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
true
Random.AbstractRNG
— 類型AbstractRNG
隨機數字產生器的超類型,例如 MersenneTwister
和 RandomDevice
。
Random.TaskLocalRNG
— 類型TaskLocalRNG
TaskLocalRNG
的狀態是其任務本地的,而不是其執行緒本地的。它在任務建立時根據其父任務的狀態進行初始化。因此,任務建立是一個會變更父項 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
true
Random.MersenneTwister
— 類型MersenneTwister(seed)
MersenneTwister()
建立一個 MersenneTwister
RNG 物件。不同的 RNG 物件可以有自己的種子,這對於產生不同的亂數串流很有用。種子
可以是非負整數或 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
true
Random.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))
傳回一個取樣器物件,可用於針對 x
從 rng
產生亂數值。
當 sp = Sampler(rng, x, repetition)
時,rand(rng, sp)
將用於繪製亂數值,且應定義為相應的。
repetition
可以是 Val(1)
或 Val(Inf)
,且應當用於作為建議,以決定預先運算的數量(如果適用)。
Random.SamplerType
和 Random.SamplerTrivial
分別是 類型 和 值 的預設後備。Random.SamplerSimple
可用於儲存預先運算的值,而無需僅為此目的定義額外類型。
Random.SamplerType
— 類型SamplerType{T}()
類型取樣器,不包含其他資訊。當以類型呼叫 Sampler
時的預設後備。
Random.SamplerTrivial
— 類型SamplerTrivial(x)
建立一個僅封裝給定值 x
的取樣器。這是值的預設後備。此取樣器的 eltype
等於 eltype(x)
。
建議的使用案例是從沒有預先運算資料的值中取樣。
Random.SamplerSimple
— 類型SamplerSimple(x, data)
建立一個封裝給定值 x
和 資料
的取樣器。此取樣器的 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)` 產生一個 `Die`,其隨機面的數量最多為 20 面(至少為 4 面)
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 尚未明確定義,但依經驗法則
- 任何產生「基本」類型的
rand
方法(Base
中的isbitstype
整數和浮點類型)都應該針對這個特定的 RNG 定義,如果需要的話; - 其他接受
AbstractRNG
的文件化rand
方法應該可以立即使用,(假設從 1) 中依賴的方法已實作),但如果可以最佳化,當然可以針對這個 RNG 特別調整; - 偽隨機數產生器的
copy
應該傳回一個獨立的複製,從那個點開始產生與原始產生器完全相同的隨機序列,當以相同方式呼叫時。如果不可行(例如硬體為基礎的 RNG),則不得實作copy
。
關於 1),rand
方法可能會自動運作,但未正式支援,且可能會在後續版本中在沒有警告的情況下中斷。
要為假設的 MyRNG
產生器定義新的 rand
方法,以及類型 S==typeof(s)
或 S==Type{s}
(如果 s
是類型)的值規格 s
(例如 s == Int
或 s == 1:10
),必須定義與之前看到的相同兩個方法
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 次要版本中都是相同的。