Random Numbers

Juliaにおける乱数生成は、デフォルトでXoshiro256++アルゴリズムを使用し、Taskごとに状態を持ちます。他のRNGタイプはAbstractRNGタイプを継承することでプラグインでき、その後、複数の乱数ストリームを取得するために使用できます。

RandomパッケージによってエクスポートされるPRNG(擬似乱数生成器)は次のとおりです:

  • TaskLocalRNG: 現在アクティブなタスクローカルストリームの使用を表すトークンで、親タスクから決定的にシードされるか、プログラム開始時にRandomDevice(システムのランダム性を使用)によってシードされます。
  • Xoshiro: Xoshiro256++アルゴリズムを使用して、小さな状態ベクトルと高いパフォーマンスで高品質の乱数ストリームを生成します。
  • RandomDevice: OS提供のエントロピー用。これは暗号的に安全な乱数(CS(P)RNG)に使用される場合があります。
  • MersenneTwister: 古いバージョンのJuliaでデフォルトだった代替の高品質PRNGで、非常に高速ですが、状態ベクトルを保存しランダムシーケンスを生成するためにはるかに多くのスペースを必要とします。

ほとんどのランダム生成に関連する関数は、最初の引数としてオプションの AbstractRNG オブジェクトを受け入れます。また、ランダム値の配列を生成するために、次元指定 dims...(タプルとしても指定可能)を受け入れるものもあります。マルチスレッドプログラムでは、一般的にスレッドセーフであるために、異なるスレッドやタスクから異なるRNGオブジェクトを使用するべきです。ただし、デフォルトのRNGはJulia 1.3以降スレッドセーフであり(バージョン1.6まではスレッドごとのRNGを使用し、それ以降はタスクごとに使用します)。

提供されたRNGは、以下のタイプの均一なランダム数を生成できます: Float16Float32Float64BigFloatBoolInt8UInt8Int16UInt16Int32UInt32Int64UInt64Int128UInt128BigInt(またはそれらのタイプの複素数)。ランダムな浮動小数点数は $[0, 1)$ の範囲で均一に生成されます。 BigInt は無制限の整数を表すため、区間を指定する必要があります(例: rand(big.(1:6)))。

さらに、いくつかの AbstractFloat および Complex タイプに対して、正規分布と指数分布が実装されています。詳細については、 randn および randexp を参照してください。

他の分布からランダムな数を生成するには、Distributions.jl パッケージを参照してください。

Warning

乱数が生成される正確な方法は実装の詳細と見なされるため、バグ修正や速度改善により、バージョン変更後に生成される数のストリームが変わる可能性があります。ユニットテスト中に特定のシードや生成された数のストリームに依存することは推奨されません - 代わりに、問題のメソッドの特性をテストすることを検討してください。

Random numbers module

Random generation functions

Base.randFunction
rand([rng=default_rng()], [S], [dims...])

指定された値の集合 S からランダムな要素またはランダムな要素の配列を選択します。S は以下のいずれかであることができます。

  • インデックス可能なコレクション(例えば 1:9('x', "y", :z)

  • AbstractDict または AbstractSet オブジェクト

  • 文字列(文字のコレクションと見なされる)、または

  • 指定された値の集合に対応する以下のリストからの型

    • 具体的な整数型は typemin(S):typemax(S) からサンプリングします(BigInt はサポートされていません)
    • 具体的な浮動小数点型は [0, 1) からサンプリングします
    • 具体的な複素数型 Complex{T} は、T がサンプリング可能な型であれば、実部と虚部をそれぞれ T に対応する値の集合から独立して取得しますが、T がサンプリング不可能な場合はサポートされません。
    • すべての <:AbstractChar 型は有効なUnicodeスカラーの集合からサンプリングします
    • ユーザー定義型と値の集合; 実装ガイダンスについては Hooking into the Random API を参照してください
    • 知られたサイズのタプル型で、S の各パラメータがサンプリング可能な型である場合; 型 S の値を返します。Tuple{Vararg{T}}(サイズ不明)や Tuple{1:2}(値でパラメータ化された)などのタプル型はサポートされていないことに注意してください
    • Pair 型、例えば Pair{X, Y} で、randXY に対して定義されている場合、ランダムなペアが生成されます。

S のデフォルトは Float64 です。オプションの rng 以外に1つの引数が渡され、かつそれが Tuple の場合、それは値のコレクション(S)として解釈され、dims とは見なされません。

通常分布の数値については randn を、インプレースの同等物については rand! および randn! を参照してください。

Julia 1.1

タプルとしての S のサポートには少なくとも Julia 1.1 が必要です。

Julia 1.11

Tuple 型としての S のサポートには少なくとも Julia 1.11 が必要です。

julia> rand(Int, 2)
2-element Array{Int64,1}:
 1339893410598768192
 1575814717733606317

julia> using Random

julia> rand(Xoshiro(0), Dict(1=>2, 3=>4))
3 => 4

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
Note

rand(rng, s::Union{AbstractDict,AbstractSet}) の複雑さは s の長さに対して線形ですが、定数の複雑さを持つ最適化されたメソッドが利用可能な場合(DictSet および密な BitSet の場合)、その限りではありません。数回以上の呼び出しには、代わりに rand(rng, collect(s)) を使用するか、適切に rand(rng, Dict(s)) または rand(rng, Set(s)) を使用してください。

source
Random.rand!Function
rand!([rng=default_rng()], A, [S=eltype(A)])

配列 A をランダムな値で埋めます。S が指定されている場合(S は型またはコレクションであり、詳細は rand を参照)、値は S からランダムに選ばれます。これは copyto!(A, rand(rng, S, size(A))) と同等ですが、新しい配列を割り当てることはありません。

julia> rand!(Xoshiro(123), zeros(5))
5-element Vector{Float64}:
 0.521213795535383
 0.5868067574533484
 0.8908786980927811
 0.19090669902576285
 0.5256623915420473
source
Random.bitrandFunction
bitrand([rng=default_rng()], [dims...])

ランダムなブール値の BitArray を生成します。

julia> bitrand(Xoshiro(123), 10)
10-element BitVector:
 0
 1
 0
 1
 0
 1
 0
 0
 1
 1
source
Base.randnFunction
randn([rng=default_rng()], [T=Float64], [dims...])

平均0、標準偏差1の正規分布に従う型Tの乱数を生成します。オプションのdims引数を指定すると、そのサイズの配列を生成します。Juliaの標準ライブラリは、randを実装している任意の浮動小数点型に対してrandnをサポートしています。例えば、Base型のFloat16Float32Float64(デフォルト)、およびBigFloatと、それらのComplex対応型です。

Tが複素数の場合、値は分散1の円対称複素正規分布から引き出され、実部と虚部は平均0、分散1/2の独立した正規分布を持ちます)。

インプレースで動作するrandn!も参照してください。

単一の乱数を生成する(デフォルトのFloat64型を使用):

julia> randn()
-0.942481877315864

正規乱数の行列を生成する(デフォルトのFloat64型を使用):

julia> randn(2,3)
2×3 Matrix{Float64}:
  1.18786   -0.678616   1.49463
 -0.342792  -0.134299  -1.45005

ユーザー定義のシードで乱数生成器rngを設定し(再現可能な数値のため)、それを使用して乱数Float32またはComplexF32の乱数行列を生成する:

julia> using Random

julia> rng = Xoshiro(123);

julia> randn(rng, Float32)
-0.6457307f0

julia> randn(rng, ComplexF32, (2, 3))
2×3 Matrix{ComplexF32}:
  -1.03467-1.14806im  0.693657+0.056538im   0.291442+0.419454im
 -0.153912+0.34807im    1.0954-0.948661im  -0.543347-0.0538589im
source
Random.randn!Function
randn!([rng=default_rng()], A::AbstractArray) -> A

配列 A を平均 0、標準偏差 1 の正規分布に従うランダムな数値で埋めます。 rand 関数も参照してください。

julia> randn!(Xoshiro(123), zeros(5))
5-element Vector{Float64}:
 -0.6457306721039767
 -1.4632513788889214
 -1.6236037455860806
 -0.21766510678354617
  0.4922456865251828
source
Random.randexpFunction
randexp([rng=default_rng()], [T=Float64], [dims...])

スケール1の指数分布に従って、型 T のランダムな数を生成します。オプションで、そのようなランダムな数の配列を生成します。Base モジュールは現在、Float16Float32、および Float64(デフォルト)の型に対する実装を提供しています。

julia> rng = Xoshiro(123);

julia> randexp(rng, Float32)
1.1757717f0

julia> randexp(rng, 3, 3)
3×3 Matrix{Float64}:
 1.37766  0.456653  0.236418
 3.40007  0.229917  0.0684921
 0.48096  0.577481  0.71835
source
Random.randexp!Function
randexp!([rng=default_rng()], A::AbstractArray) -> A

配列 A をスケール1の指数分布に従うランダムな数値で埋めます。

julia> randexp!(Xoshiro(123), zeros(5))
5-element Vector{Float64}:
 1.1757716836348473
 1.758884569451514
 1.0083623637301151
 0.3510644315565272
 0.6348266443720407
source
Random.randstringFunction
randstring([rng=default_rng()], [chars], [len=8])

長さ len のランダムな文字列を作成します。文字列は chars の文字から構成され、デフォルトでは大文字と小文字のアルファベットおよび数字 0-9 のセットになります。オプションの rng 引数は乱数生成器を指定します。詳細は Random Numbers を参照してください。

julia> Random.seed!(3); randstring()
"Lxz5hUwn"

julia> randstring(Xoshiro(3), 'a':'z', 6)
"iyzcsm"

julia> randstring("ACGT")
"TGCTCCTC"
Note

charsChar または UInt8 型の任意の文字コレクションであり、rand がそこからランダムに文字を選択できる限り、使用可能です。

source

Subsequences, permutations and shuffling

Random.randsubseqFunction
randsubseq([rng=default_rng(),] A, p) -> Vector

与えられた配列 A のランダム部分列からなるベクターを返します。A の各要素は、独立した確率 p で(順序を保ったまま)含まれます。(複雑さは p*length(A) に対して線形であるため、この関数は p が小さく A が大きい場合でも効率的です。)技術的には、このプロセスは A の「ベルヌーイサンプリング」として知られています。

julia> randsubseq(Xoshiro(123), 1:8, 0.3)
2-element Vector{Int64}:
 4
 7
source
Random.randsubseq!Function
randsubseq!([rng=default_rng(),] S, A, p)

randsubseqと同様ですが、結果はSに格納されます(必要に応じてサイズが変更されます)。

julia> S = Int64[];

julia> randsubseq!(Xoshiro(123), S, 1:8, 0.3)
2-element Vector{Int64}:
 4
 7

julia> S
2-element Vector{Int64}:
 4
 7
source
Random.randpermFunction
randperm([rng=default_rng(),] n::Integer)

長さ n のランダムな順列を構築します。オプションの rng 引数は乱数生成器を指定します(乱数を参照)。結果の要素型は n の型と同じです。

任意のベクトルをランダムに並べ替えるには、shuffle または shuffle! を参照してください。

Julia 1.1

Julia 1.1 では randpermeltype(v) == typeof(n) のベクトル v を返しますが、Julia 1.0 では eltype(v) == Int です。

julia> randperm(Xoshiro(123), 4)
4-element Vector{Int64}:
 1
 4
 2
 3
source
Random.randperm!Function
randperm!([rng=default_rng(),] A::Array{<:Integer})

Aの中に長さlength(A)のランダムな順列を構築します。オプションのrng引数は乱数生成器を指定します(乱数を参照)。任意のベクトルをランダムに順列するには、shuffleまたはshuffle!を参照してください。

julia> randperm!(Xoshiro(123), Vector{Int}(undef, 4))
4-element Vector{Int64}:
 1
 4
 2
 3
source
Random.randcycleFunction
randcycle([rng=default_rng(),] n::Integer)

長さ n のランダムな循環置換を構築します。オプションの rng 引数は乱数生成器を指定します。詳細は Random Numbers を参照してください。結果の要素型は n の型と同じです。

ここで「循環置換」とは、すべての要素が単一のサイクル内にあることを意味します。n > 0 の場合、可能な循環置換は $(n-1)!$ 個あり、均等にサンプリングされます。n == 0 の場合、randcycle は空のベクターを返します。

randcycle! はこの関数のインプレースバリアントです。

Julia 1.1

Julia 1.1 以降では、randcycleeltype(v) == typeof(n) のベクター v を返しますが、Julia 1.0 では eltype(v) == Int です。

julia> randcycle(Xoshiro(123), 6)
6-element Vector{Int64}:
 5
 4
 2
 6
 3
 1
source
Random.randcycle!Function
randcycle!([rng=default_rng(),] A::Array{<:Integer})

Aに長さn = length(A)のランダムな循環置換を構築します。オプションのrng引数は乱数生成器を指定します。詳細はRandom Numbersを参照してください。

ここで「循環置換」とは、すべての要素が単一のサイクル内にあることを意味します。Aが空でない場合(n > 0)、可能な循環置換は$(n-1)!$個あり、これらは均等にサンプリングされます。Aが空の場合、randcycle!はそれを変更しません。

randcycleは、新しいベクトルを割り当てるこの関数のバリアントです。

julia> randcycle!(Xoshiro(123), Vector{Int}(undef, 6))
6-element Vector{Int64}:
 5
 4
 2
 6
 3
 1
source
Random.shuffleFunction
shuffle([rng=default_rng(),] v::AbstractArray)

vのランダムに順序を入れ替えたコピーを返します。オプションのrng引数は乱数生成器を指定します(乱数を参照)。vをインプレースで順序を入れ替えるには、shuffle!を参照してください。ランダムに順序を入れ替えたインデックスを取得するには、randpermを参照してください。

julia> shuffle(Xoshiro(123), Vector(1:10))
10-element Vector{Int64}:
  5
  4
  2
  3
  6
 10
  8
  1
  9
  7
source
Random.shuffle!Function
shuffle!([rng=default_rng(),] v::AbstractArray)

shuffle のインプレースバージョン:v をインプレースでランダムに順序を入れ替え、オプションで乱数生成器 rng を指定します。

julia> shuffle!(Xoshiro(123), Vector(1:10))
10-element Vector{Int64}:
  5
  4
  2
  3
  6
 10
  8
  1
  9
  7
source

Generators (creation and seeding)

Random.default_rngFunction
Random.default_rng() -> rng

デフォルトのグローバル乱数生成器 (RNG) を返します。これは、明示的な RNG が提供されていない場合に rand 関連の関数によって使用されます。

Random モジュールがロードされると、デフォルトの RNG は ランダムに シードされます。これは Random.seed!() を介して行われます。つまり、新しい Julia セッションが開始されるたびに、最初の rand() の呼び出しは異なる結果を生成します。最初に seed!(seed) が呼ばれない限りです。

これはスレッドセーフです:異なるスレッドは安全に default_rng() に対して rand 関連の関数を同時に呼び出すことができます。例えば、rand(default_rng()) のようにです。

Note

デフォルトの RNG の型は実装の詳細です。異なるバージョンの Julia において、デフォルトの RNG が常に同じ型を持つとは限らず、特定のシードに対して同じ乱数のストリームを生成するとは限りません。

Julia 1.3

この関数は Julia 1.3 で導入されました。

source
Random.seed!Function
seed!([rng=default_rng()], seed) -> rng
seed!([rng=default_rng()]) -> rng

乱数生成器を再シードします:rngは、seedが提供される場合に限り、再現可能な数列を生成します。一部のRNGはシードを受け付けません(例:RandomDevice)。seed!の呼び出し後、rngは同じシードで初期化された新しく作成されたオブジェクトと同等になります。受け入れられるシードの型は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) # 再現性なし
true

julia> rand(Random.seed!(rng), Bool) # 再現性なし
false

julia> rand(Xoshiro(), Bool) # 再現性なし
true
source
Random.TaskLocalRNGType
TaskLocalRNG

TaskLocalRNGは、そのスレッドではなくタスクにローカルな状態を持っています。これはタスクの作成時に親タスクの状態からシードされますが、親のRNGの状態を進めることはありません。

利点として、TaskLocalRNGは非常に高速で、スケジューラの決定に依存せず、レースコンディションを除けば再現可能なマルチスレッドシミュレーションを許可します。タスクの作成にスレッド数が使用されない限り、シミュレーション結果は利用可能なスレッド/CPUの数にも依存しません。ランダムストリームは、エンディアンやおそらくワードサイズを除いて、ハードウェアの特性に依存するべきではありません。

current_task()によって返されるタスク以外のタスクのRNGを使用したりシードしたりすることは未定義の動作です:ほとんどの場合は機能しますが、時には静かに失敗することがあります。

TaskLocalRNG()seed!でシードする際に渡されるシードは、あれば任意の整数であることができます。

Julia 1.11

負の整数シードでTaskLocalRNG()をシードするには、少なくともJulia 1.11が必要です。

Julia 1.10

Julia 1.10以降、タスクの作成は親タスクのRNG状態を進めなくなりました。

source
Random.XoshiroType
Xoshiro(seed::Union{Integer, AbstractString})
Xoshiro()

Xoshiro256++は、David BlackmanとSebastiano Vignaによって「Scrambled Linear Pseudorandom Number Generators」で説明された高速擬似乱数生成器です。参照実装は https://prng.di.unimi.it で入手可能です。

高速度に加えて、Xoshiroは小さなメモリフットプリントを持ち、多くの異なる乱数状態を長時間保持する必要があるアプリケーションに適しています。

JuliaのXoshiro実装にはバルク生成モードがあり、親から新しい仮想PRNGをシードし、SIMDを使用して並列に生成します(つまり、バルクストリームは複数のインタリーブされたxoshiroインスタンスで構成されます)。仮想PRNGは、バルクリクエストが処理されると破棄され(ヒープアロケーションを引き起こすことはありません)、

シードが提供されない場合は、ランダムに生成されたものが作成されます(システムからのエントロピーを使用)。既存のXoshiroオブジェクトの再シードについては、seed!関数を参照してください。

Julia 1.11

負の整数シードを渡すには、少なくともJulia 1.11が必要です。

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
source
Random.MersenneTwisterType
MersenneTwister(seed)
MersenneTwister()

MersenneTwister RNGオブジェクトを作成します。異なるRNGオブジェクトはそれぞれ独自のシードを持つことができ、異なるランダム数のストリームを生成するのに役立ちます。seedは整数、文字列、またはUInt32整数のベクターである可能性があります。シードが提供されない場合、ランダムに生成されたものが作成されます(システムからのエントロピーを使用)。既存のMersenneTwisterオブジェクトの再シードについては、seed!関数を参照してください。

Julia 1.11

負の整数シードを渡すには、少なくともJulia 1.11が必要です。

julia> rng = MersenneTwister(123);

julia> x1 = rand(rng, 2)
2-element Vector{Float64}:
 0.37453777969575874
 0.8735343642013971

julia> x2 = rand(MersenneTwister(123), 2)
2-element Vector{Float64}:
 0.37453777969575874
 0.8735343642013971

julia> x1 == x2
true
source
Random.RandomDeviceType
RandomDevice()

RandomDevice RNGオブジェクトを作成します。このような2つのオブジェクトは常に異なるランダム数のストリームを生成します。エントロピーはオペレーティングシステムから取得されます。

source

Hooking into the Random API

Random機能を拡張する主に直交する2つの方法があります:

  1. カスタムタイプのランダム値を生成する
  2. 新しいジェネレーターの作成

The API for 1) is quite functional, but is relatively recent so it may still have to evolve in subsequent releases of the Random module. For example, it's typically sufficient to implement one rand method in order to have all other usual methods work automatically.

The API for 2) is still rudimentary, and may require more work than strictly necessary from the implementor, in order to support usual types of generated values.

Generating random values of custom types

ランダム値を生成するためのいくつかの分布には、さまざまなトレードオフが関与する場合があります。事前計算された 値、例えば離散分布のための alias table や、単変量分布のための “squeezing” functions は、サンプリングを大幅に高速化することができます。事前に計算すべき情報の量は、分布から引き出す予定の値の数によって異なる場合があります。また、いくつかの乱数生成器には、さまざまなアルゴリズムが利用したい特定の特性がある場合があります。

Random モジュールは、これらの問題に対処するためのランダム値を取得するカスタマイズ可能なフレームワークを定義します。rand の各呼び出しは、上記のトレードオフを考慮してカスタマイズ可能な サンプラー を生成します。これは、Sampler にメソッドを追加することで実現され、ランダム数生成器、分布を特徴づけるオブジェクト、および繰り返し回数の提案に基づいてディスパッチできます。現在、後者については、Val{1}(単一サンプル用)と Val{Inf}(任意の数用)が使用されており、Random.Repetition は両者のエイリアスです。

Samplerによって返されるオブジェクトは、ランダムな値を生成するために使用されます。サンプリング可能な値Xのためのランダム生成インターフェースを実装する際、実装者はメソッドを定義する必要があります。

rand(rng, sampler)

Sampler(rng, X, repetition)によって返される特定のsamplerについて。

サンプラーは rand(rng, sampler) を実装する任意の値である可能性がありますが、ほとんどのアプリケーションでは以下の定義済みサンプラーで十分かもしれません:

  1. SamplerType{T}() は、型 T からサンプリングを行うサンプラーを実装するために使用できます(例: rand(Int))。これは、 に対して Sampler が返すデフォルトです。
  2. SamplerTrivial(self)selfのシンプルなラッパーで、[]を使ってアクセスできます。これは、事前に計算された情報が必要ない場合(例:rand(1:3))に推奨されるサンプラーであり、valuesのためにSamplerが返すデフォルトです。
  3. SamplerSimple(self, data) は、任意の事前計算された値を格納するために使用できる追加の data フィールドも含まれています。これらの値は Samplerカスタムメソッド で計算されるべきです。

これらの各例を提供します。ここでは、アルゴリズムの選択がRNGとは独立していると仮定し、署名にはAbstractRNGを使用します。

Random.SamplerType
Sampler(rng, x, repetition = Val(Inf))

rngからxのランダムな値を生成するために使用できるサンプラーオブジェクトを返します。

sp = Sampler(rng, x, repetition) のとき、rand(rng, sp) がランダムな値を引き出すために使用され、適切に定義されるべきです。

repetitionVal(1) または Val(Inf) であり、適用可能な場合の前計算の量を決定するための提案として使用されるべきです。

Random.SamplerTypeRandom.SamplerTrivial はそれぞれ のデフォルトのフォールバックです。 Random.SamplerSimple は、この目的のためだけに追加の型を定義することなく、前計算された値を保存するために使用できます。

source
Random.SamplerTypeType
SamplerType{T}()

型のためのサンプラーで、他の情報は含まれていません。型で呼び出されたときのSamplerのデフォルトフォールバックです。

source
Random.SamplerTrivialType
SamplerTrivial(x)

与えられた値 x を単にラップするサンプラーを作成します。これは値のデフォルトのフォールバックです。このサンプラーの eltypeeltype(x) と等しいです。

推奨される使用ケースは、事前に計算されたデータなしで値からサンプリングすることです。

source
Random.SamplerSimpleType
SamplerSimple(x, data)

与えられた値 xdata をラップするサンプラーを作成します。このサンプラーの eltypeeltype(x) と等しくなります。

推奨される使用ケースは、事前に計算されたデータから値をサンプリングすることです。

source

事前計算を実際に値を生成することから切り離すことはAPIの一部であり、ユーザーにも利用可能です。例えば、rand(rng, 1:20)をループ内で繰り返し呼び出す必要があると仮定します。この切り離しを活用する方法は次のとおりです:

rng = Xoshiro()
sp = Random.Sampler(rng, 1:20) # or Random.Sampler(Xoshiro, 1:20)
for x in X
    n = rand(rng, sp) # similar to n = rand(rng, 1:20)
    # use n
end

これは、標準ライブラリでも使用されるメカニズムであり、例えば、ランダム配列生成のデフォルト実装(rand(1:20, 10)のような)によって使用されます。

Generating values from a type

与えられた型 T に対して、rand(T) が定義されている場合、型 T のオブジェクトが生成されると仮定されています。SamplerType型のデフォルトサンプラー です。型 T の値のランダム生成を定義するために、rand(rng::AbstractRNG, ::Random.SamplerType{T}) メソッドを定義する必要があり、これは rand(rng, T) が返すことが期待される値を返すべきです。

次の例を考えてみましょう:Die型を実装します。これは、1からnまでの番号が付けられた可変数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(Xoshiro(0), Die)
Die(10)

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)

A simple sampler without pre-computed data

ここではコレクションのサンプラーを定義します。事前に計算されたデータが必要ない場合、SamplerTrivialサンプラーを使用して実装できます。これは実際には値のデフォルトフォールバックです。

S型のオブジェクトからのランダム生成を定義するために、次のメソッドを定義する必要があります: rand(rng::AbstractRNG, sp::Random.SamplerTrivial{S})。ここで、spは単にS型のオブジェクトをラップしており、sp[]を介してアクセスできます。Dieの例を続けると、dの側面の1つに対応するIntを生成するためにrand(d::Die)を定義したいと思います:

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 を定義することです。

Generating values for an AbstractFloat type

AbstractFloat 型は特別扱いされます。なぜなら、デフォルトではランダムな値は全体の型ドメインではなく、むしろ [0,1) の範囲で生成されるからです。次のメソッドは T <: AbstractFloat に対して実装されるべきです: Random.rand(::AbstractRNG, ::Random.SamplerTrivial{Random.CloseOpen01{T}})

An optimized sampler with pre-computed data

離散分布を考えます。ここで、数値 1:n が与えられた確率で引かれ、その合計が1になります。この分布から多くの値が必要な場合、最も速い方法は alias table を使用することです。このようなテーブルを構築するためのアルゴリズムはここでは提供しませんが、代わりに 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(distribution, make_alias_table(distribution.probabilities))
end

サンプラーを事前に計算されたデータで返すように定義されるべきです、その後

function rand(rng::AbstractRNG, sp::SamplerSimple{<:DiscreteDistribution})
    draw_number(rng, sp.data)
end

値を描画するために使用されます。

Custom sampler types

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呼び出しでdieの代わりにspを使用することが可能になりました。上記の単純な例では、dieSamplerDieに保存する必要はありませんが、実際にはこれがよくあるケースです。

もちろん、このパターンは非常に頻繁に使用されるため、上記で使用したヘルパータイプである 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.dataSamplerSimpleコンストラクタへの呼び出しの第二引数を指します(この場合、Sampler(rng, 1:die.nsides, r)に等しいです)。一方、Dieオブジェクトはsp[]を介してアクセスできます。

SamplerDieのように、カスタムサンプラーはすべてSampler{T}のサブタイプでなければなりません。ここで、Tは生成される値の型です。SamplerSimple(x, data) isa Sampler{eltype(x)}であることに注意してください。これにより、SamplerSimpleの最初の引数が何であるべきかが制約されます(xが単に転送されるSamplerメソッドを定義するDieの例のように、SamplerSimpleを使用することをお勧めします)。同様に、SamplerTrivial(x) isa Sampler{eltype(x)}です。

別のヘルパータイプが他のケース用に現在利用可能であり、Random.SamplerTagですが、内部APIと見なされており、適切な非推奨なしにいつでも壊れる可能性があります。

Using distinct algorithms for scalar or array generation

場合によっては、少数の値を生成したいのか、大量の値を生成したいのかがアルゴリズムの選択に影響を与えることがあります。これは Sampler コンストラクタの第3引数で処理されます。ここでは、少数のランダム値を生成するために使用される SamplerDie1 と、多くの値のための SamplerDieMany という2つのヘルパータイプを定義したと仮定します。これらのタイプは次のように使用できます:

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.RepetitionUnion{Val{1}, Val{Inf}} のエイリアスです。

Creating new generators

APIはまだ明確に定義されていませんが、一般的な指針として:

  1. 任意の rand メソッドは、この特定のRNGのために定義されるべきであり、"基本的な" 型(isbitstype 整数および浮動小数点型)を生成する場合に必要です;
  2. 他の文書化された rand メソッドは、AbstractRNG を受け入れる場合、(1) で依存しているメソッドが実装されていれば)そのまま動作するはずですが、最適化の余地があれば、この RNG に特化させることももちろん可能です。
  3. copy は擬似乱数生成器(pseudo-RNG)に対して、同じ方法で呼び出されたときに元のものと同じ乱数列を生成する独立したコピーを返すべきです。これが実現不可能な場合(例:ハードウェアベースの乱数生成器)、copy は実装されてはなりません。

Concerning 1), a rand method may happen to work automatically, but it's not officially supported and may break without warnings in a subsequent release.

新しい rand メソッドを仮想の MyRNG ジェネレーターのために定義し、値の仕様 s(例:s == Int または s == 1:10)を S==typeof(s) または S==Type{s}s が型の場合)として、前に見たのと同じ2つのメソッドを定義する必要があります:

  1. Sampler(::Type{MyRNG}, ::S, ::Repetition)は、型SamplerSのオブジェクトを返します。
  2. rand(rng::MyRNG, sp::SamplerS)

Sampler(rng::AbstractRNG, ::S, ::Repetition)Randomモジュール内ですでに定義されていることがあります。その場合、特定のRNGタイプに対して生成を特化したい場合は、実際にはステップ1)をスキップすることが可能ですが、対応するSamplerSタイプは内部の詳細と見なされ、予告なしに変更される可能性があります。

Specializing array generation

場合によっては、特定のRNGタイプに対して、ランダム値の配列を生成することが、前述のデカップリング技術を単に使用するよりも、特化した方法を用いる方が効率的であることがあります。これは、MersenneTwisterの場合のように、ネイティブにランダム値を配列に書き込むことができる場合です。

MyRNGのこの特化を実装し、仕様sに対して、型Sの要素を生成するために、次のメソッドを定義できます: rand!(rng::MyRNG, a::AbstractArray{S}, ::SamplerS)。ここで、SamplerSSampler(MyRNG, s, Val(Inf))によって返されるサンプラーの型です。AbstractArrayの代わりに、機能をサブタイプ、例えばArray{S}のみに実装することも可能です。randの非変異配列メソッドは、内部でこの特化を自動的に呼び出します。

Reproducibility

与えられたシードで初期化されたRNGパラメータを使用することで、プログラムを複数回実行した際に同じ擬似乱数列を再現することができます。しかし、Juliaのマイナーリリース(例:1.3から1.4)では、特定のシードから生成される擬似乱数の列が変更される可能性があります。特にMersenneTwisterが使用されている場合です。(低レベルの関数randによって生成される列が変更されない場合でも、randsubseqのような高レベルの関数の出力はアルゴリズムの更新により変更される可能性があります。)理由:擬似乱数ストリームが決して変更されないことを保証することは、多くのアルゴリズムの改善を禁止します。

ランダムデータの正確な再現性を保証する必要がある場合は、単にデータを保存することをお勧めします(例えば、科学出版物の補足資料として)。 (もちろん、特定のJuliaバージョンやパッケージマニフェストを指定することもできます。特にビット再現性が必要な場合は。)

特定の「ランダム」データに依存するソフトウェアテストは、一般的にデータを保存するか、テストコードに埋め込むか、または StableRNGs.jl のようなサードパーティパッケージを使用するべきです。一方で、ほとんどのランダムデータに対して合格すべきテスト(例:ランダム行列 A = randn(n,n) に対して A \ (A*x) ≈ x をテストする)は、固定シードを持つRNGを使用して、テストを何度も実行しても非常にありえないデータ(例:極端に条件の悪い行列)による失敗が発生しないようにすることができます。

統計的な分布からランダムサンプルが抽出されることは、マイナーなJuliaリリース間で同じであることが保証されています。