Random Numbers
Die Zufallszahlengenerierung in Julia verwendet standardmäßig den Xoshiro256++ Algorithmus mit zustandsabhängiger Task
. Andere RNG-Typen können durch Vererbung des AbstractRNG
Typs integriert werden; sie können dann verwendet werden, um mehrere Ströme von Zufallszahlen zu erhalten.
Die PRNGs (pseudorandom number generatoren), die vom Random
-Paket exportiert werden, sind:
TaskLocalRNG
: ein Token, das die Verwendung des derzeit aktiven aufgabenlokalen Streams darstellt, der deterministisch aus der übergeordneten Aufgabe oder durchRandomDevice
(mit Systemzufälligkeit) beim Programmstart initialisiert wird.Xoshiro
: erzeugt einen hochwertigen Strom von Zufallszahlen mit einem kleinen Zustandsvektor und hoher Leistung unter Verwendung des Xoshiro256++-Algorithmus.RandomDevice
: für vom Betriebssystem bereitgestellte Entropie. Dies kann für kryptografisch sichere Zufallszahlen (CS(P)RNG) verwendet werden.MersenneTwister
: ein alternativer hochwertiger PRNG, der in älteren Versionen von Julia standardmäßig verwendet wurde und ebenfalls recht schnell ist, jedoch viel mehr Speicherplatz benötigt, um den Zustandsvektor zu speichern und eine Zufallssequenz zu erzeugen.
Die meisten Funktionen, die mit der zufälligen Generierung zu tun haben, akzeptieren ein optionales AbstractRNG
-Objekt als erstes Argument. Einige akzeptieren auch Dimensionsspezifikationen dims...
(die auch als Tuple angegeben werden können), um Arrays mit zufälligen Werten zu generieren. In einem mehrthreadigen Programm sollten Sie im Allgemeinen unterschiedliche RNG-Objekte aus verschiedenen Threads oder Aufgaben verwenden, um thread-sicher zu sein. Der Standard-RNG ist jedoch ab Julia 1.3 thread-sicher (unter Verwendung eines pro-Thread-RNG bis Version 1.6 und pro-Aufgabe danach).
Die bereitgestellten RNGs können gleichmäßig zufällige Zahlen der folgenden Typen generieren: Float16
, Float32
, Float64
, BigFloat
, Bool
, Int8
, UInt8
, Int16
, UInt16
, Int32
, UInt32
, Int64
, UInt64
, Int128
, UInt128
, BigInt
(oder komplexe Zahlen dieser Typen). Zufällige Fließkommazahlen werden gleichmäßig im $[0, 1)$ generiert. Da BigInt
unbegrenzte Ganzzahlen darstellt, muss das Intervall angegeben werden (z. B. rand(big.(1:6))
).
Zusätzlich werden normale und exponentielle Verteilungen für einige AbstractFloat
- und Complex
-Typen implementiert, siehe randn
und randexp
für Details.
Um Zufallszahlen aus anderen Verteilungen zu generieren, siehe das Distributions.jl Paket.
Da die genaue Art und Weise, wie Zufallszahlen generiert werden, als Implementierungsdetail betrachtet wird, können Fehlerbehebungen und Geschwindigkeitsverbesserungen den Strom von Zahlen, die nach einer Versionsänderung generiert werden, ändern. Es wird daher davon abgeraten, sich während der Unit-Tests auf einen bestimmten Seed oder einen generierten Zahlenstrom zu verlassen - ziehen Sie stattdessen in Betracht, die Eigenschaften der betreffenden Methoden zu testen.
Random numbers module
Random.Random
— ModuleZufällig
Unterstützung für die Generierung von Zufallszahlen. Bietet rand
, randn
, AbstractRNG
, MersenneTwister
und RandomDevice
.
Random generation functions
Base.rand
— Functionrand([rng=default_rng()], [S], [dims...])
Wählen Sie ein zufälliges Element oder ein Array von zufälligen Elementen aus der Menge von Werten, die durch S
angegeben sind; S
kann sein
eine indexierbare Sammlung (zum Beispiel
1:9
oder('x', "y", :z)
)ein
AbstractDict
oderAbstractSet
Objektein String (als Sammlung von Zeichen betrachtet), oder
ein Typ aus der untenstehenden Liste, der der angegebenen Menge von Werten entspricht
- konkrete Ganzzahltypen, die aus
typemin(S):typemax(S)
entnommen werden (außerBigInt
, das nicht unterstützt wird) - konkrete Gleitkommatypen, die aus
[0, 1)
entnommen werden - konkrete komplexe Typen
Complex{T}
, wennT
ein samplbarer Typ ist, nehmen Sie deren reale und imaginäre Komponenten unabhängig aus der Menge von Werten, dieT
entsprechen, werden jedoch nicht unterstützt, wennT
nicht samplbar ist. - alle
<:AbstractChar
Typen, die aus der Menge gültiger Unicode-Skalare entnommen werden - ein benutzerdefinierter Typ und eine Menge von Werten; für Implementierungsanleitungen siehe Hooking into the
Random
API - ein Tupeltyp bekannter Größe, bei dem jeder Parameter von
S
selbst ein samplbarer Typ ist; geben Sie einen Wert des TypsS
zurück. Beachten Sie, dass Tupeltypen wieTuple{Vararg{T}}
(unbekannte Größe) undTuple{1:2}
(parametrisiert mit einem Wert) nicht unterstützt werden - ein
Pair
Typ, z.B.Pair{X, Y}
, sodassrand
fürX
undY
definiert ist, in diesem Fall werden zufällige Paare erzeugt.
- konkrete Ganzzahltypen, die aus
S
hat standardmäßig den Wert Float64
. Wenn nur ein Argument neben dem optionalen rng
übergeben wird und es sich um ein Tuple
handelt, wird es als Sammlung von Werten (S
) und nicht als dims
interpretiert.
Siehe auch randn
für normalverteilte Zahlen und rand!
und randn!
für die In-Place-Äquivalente.
Die Unterstützung für S
als Tupel erfordert mindestens Julia 1.1.
Die Unterstützung für S
als Tuple
Typ erfordert mindestens Julia 1.11.
Beispiele
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
Die Komplexität von rand(rng, s::Union{AbstractDict,AbstractSet})
ist linear in der Länge von s
, es sei denn, eine optimierte Methode mit konstanter Komplexität ist verfügbar, was bei Dict
, Set
und dichten BitSet
s der Fall ist. Bei mehr als ein paar Aufrufen verwenden Sie stattdessen rand(rng, collect(s))
oder entweder rand(rng, Dict(s))
oder rand(rng, Set(s))
, je nach Bedarf.
Random.rand!
— Functionrand!([rng=default_rng()], A, [S=eltype(A)])
Füllen Sie das Array A
mit zufälligen Werten. Wenn S
angegeben ist (S
kann ein Typ oder eine Sammlung sein, siehe rand
für Details), werden die Werte zufällig aus S
ausgewählt. Dies ist äquivalent zu copyto!(A, rand(rng, S, size(A)))
, jedoch ohne ein neues Array zuzuweisen.
Beispiele
julia> rand!(Xoshiro(123), zeros(5))
5-element Vector{Float64}:
0.521213795535383
0.5868067574533484
0.8908786980927811
0.19090669902576285
0.5256623915420473
Random.bitrand
— Functionbitrand([rng=default_rng()], [dims...])
Generiert ein BitArray
mit zufälligen booleschen Werten.
Beispiele
julia> bitrand(Xoshiro(123), 10)
10-element BitVector:
0
1
0
1
0
1
0
0
1
1
Base.randn
— Functionrandn([rng=default_rng()], [T=Float64], [dims...])
Generiert eine normalverteilte Zufallszahl vom Typ T
mit einem Mittelwert von 0 und einer Standardabweichung von 1. Gegebenenfalls werden mit dem optionalen Argument dims
ein Array der Größe dims
solcher Zahlen generiert. Die Standardbibliothek von Julia unterstützt randn
für jeden Fließkommatyp, der rand
implementiert, z. B. die Base
-Typen Float16
, Float32
, Float64
(der Standard) und BigFloat
, zusammen mit ihren Complex
Gegenstücken.
(Wenn T
komplex ist, werden die Werte aus der zirkular symmetrischen komplexen Normalverteilung mit einer Varianz von 1 gezogen, wobei die reellen und imaginären Teile eine unabhängige Normalverteilung mit einem Mittelwert von null und einer Varianz von 1/2
haben).
Siehe auch randn!
, um in-place zu arbeiten.
Beispiele
Generierung einer einzelnen Zufallszahl (mit dem Standardtyp Float64
):
julia> randn()
-0.942481877315864
Generierung einer Matrix normalverteilter Zufallszahlen (mit dem Standardtyp Float64
):
julia> randn(2,3)
2×3 Matrix{Float64}:
1.18786 -0.678616 1.49463
-0.342792 -0.134299 -1.45005
Einrichtung des Zufallszahlengenerators rng
mit einem benutzerdefinierten Seed (für reproduzierbare Zahlen) und Verwendung zur Generierung einer zufälligen Float32
-Zahl oder einer Matrix von ComplexF32
-Zufallszahlen:
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
Random.randn!
— Functionrandn!([rng=default_rng()], A::AbstractArray) -> A
Füllt das Array A
mit normalverteilten (Mittelwert 0, Standardabweichung 1) Zufallszahlen. Siehe auch die rand
Funktion.
Beispiele
julia> randn!(Xoshiro(123), zeros(5))
5-element Vector{Float64}:
-0.6457306721039767
-1.4632513788889214
-1.6236037455860806
-0.21766510678354617
0.4922456865251828
Random.randexp
— Functionrandexp([rng=default_rng()], [T=Float64], [dims...])
Generiert eine Zufallszahl vom Typ T
gemäß der Exponentialverteilung mit einer Skala von 1. Optional kann ein Array solcher Zufallszahlen generiert werden. Das Base
-Modul bietet derzeit eine Implementierung für die Typen Float16
, Float32
und Float64
(der Standard).
Beispiele
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
Random.randexp!
— Functionrandexp!([rng=default_rng()], A::AbstractArray) -> A
Füllt das Array A
mit Zufallszahlen, die der Exponentialverteilung (mit Skala 1) folgen.
Beispiele
julia> randexp!(Xoshiro(123), zeros(5))
5-element Vector{Float64}:
1.1757716836348473
1.758884569451514
1.0083623637301151
0.3510644315565272
0.6348266443720407
Random.randstring
— Functionrandstring([rng=default_rng()], [chars], [len=8])
Erstellt einen zufälligen String der Länge len
, der aus Zeichen von chars
besteht, wobei chars
standardmäßig aus Groß- und Kleinbuchstaben sowie den Ziffern 0-9 besteht. Das optionale Argument rng
gibt einen Zufallszahlengenerator an, siehe Random Numbers.
Beispiele
julia> Random.seed!(3); randstring()
"Lxz5hUwn"
julia> randstring(Xoshiro(3), 'a':'z', 6)
"iyzcsm"
julia> randstring("ACGT")
"TGCTCCTC"
!!! Hinweis chars
kann jede Sammlung von Zeichen sein, vom Typ Char
oder UInt8
(effizienter), vorausgesetzt, dass rand
Zeichen zufällig daraus auswählen kann.
Subsequences, permutations and shuffling
Random.randsubseq
— Functionrandsubseq([rng=default_rng(),] A, p) -> Vector
Gibt einen Vektor zurück, der aus einer zufälligen Teilfolge des gegebenen Arrays A
besteht, wobei jedes Element von A
(in der Reihenfolge) mit einer unabhängigen Wahrscheinlichkeit p
einbezogen wird. (Die Komplexität ist linear in p*length(A)
, sodass diese Funktion auch dann effizient ist, wenn p
klein und A
groß ist.) Technisch wird dieser Prozess als "Bernoulli-Stichprobe" von A
bezeichnet.
Beispiele
julia> randsubseq(Xoshiro(123), 1:8, 0.3)
2-element Vector{Int64}:
4
7
Random.randsubseq!
— Functionrandsubseq!([rng=default_rng(),] S, A, p)
Wie randsubseq
, aber die Ergebnisse werden in S
gespeichert (das bei Bedarf angepasst wird).
Beispiele
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
Random.randperm
— Functionrandperm([rng=default_rng(),] n::Integer)
Konstruiere eine zufällige Permutation der Länge n
. Das optionale Argument rng
gibt einen Zufallszahlengenerator an (siehe Zufallszahlen). Der Elementtyp des Ergebnisses ist derselbe wie der Typ von n
.
Um einen beliebigen Vektor zufällig zu permutieren, siehe shuffle
oder shuffle!
.
In Julia 1.1 gibt randperm
einen Vektor v
zurück, bei dem eltype(v) == typeof(n)
ist, während in Julia 1.0 eltype(v) == Int
ist.
Beispiele
julia> randperm(Xoshiro(123), 4)
4-element Vector{Int64}:
1
4
2
3
Random.randperm!
— Functionrandperm!([rng=default_rng(),] A::Array{<:Integer})
Konstruiere in A
eine zufällige Permutation der Länge length(A)
. Das optionale Argument rng
gibt einen Zufallszahlengenerator an (siehe Zufallszahlen). Um einen beliebigen Vektor zufällig zu permutieren, siehe shuffle
oder shuffle!
.
Beispiele
julia> randperm!(Xoshiro(123), Vector{Int}(undef, 4))
4-element Vector{Int64}:
1
4
2
3
Random.randcycle
— Functionrandcycle([rng=default_rng(),] n::Integer)
Konstruiere eine zufällige zyklische Permutation der Länge n
. Das optionale Argument rng
gibt einen Zufallszahlengenerator an, siehe Zufallszahlen. Der Elementtyp des Ergebnisses ist derselbe wie der Typ von n
.
Hier bedeutet eine "zyklische Permutation", dass alle Elemente innerhalb eines einzigen Zyklus liegen. Wenn n > 0
ist, gibt es $(n-1)!$ mögliche zyklische Permutationen, die gleichmäßig ausgewählt werden. Wenn n == 0
ist, gibt randcycle
einen leeren Vektor zurück.
randcycle!
ist eine In-Place-Variante dieser Funktion.
In Julia 1.1 und höher gibt randcycle
einen Vektor v
zurück, bei dem eltype(v) == typeof(n)
ist, während in Julia 1.0 eltype(v) == Int
ist.
Beispiele
julia> randcycle(Xoshiro(123), 6)
6-element Vector{Int64}:
5
4
2
6
3
1
Random.randcycle!
— Functionrandcycle!([rng=default_rng(),] A::Array{<:Integer})
Konstruiere in A
eine zufällige zyklische Permutation der Länge n = length(A)
. Das optionale Argument rng
gibt einen Zufallszahlengenerator an, siehe Zufallszahlen.
Hier bedeutet eine "zyklische Permutation", dass alle Elemente innerhalb eines einzigen Zyklus liegen. Wenn A
nicht leer ist (n > 0
), gibt es $(n-1)!$ mögliche zyklische Permutationen, die gleichmäßig ausgewählt werden. Wenn A
leer ist, bleibt randcycle!
unverändert.
randcycle
ist eine Variante dieser Funktion, die einen neuen Vektor allokiert.
Beispiele
julia> randcycle!(Xoshiro(123), Vector{Int}(undef, 6))
6-element Vector{Int64}:
5
4
2
6
3
1
Random.shuffle
— Functionshuffle([rng=default_rng(),] v::AbstractArray)
Gibt eine zufällig permutierte Kopie von v
zurück. Das optionale Argument rng
gibt einen Zufallszahlengenerator an (siehe Zufallszahlen). Um v
in-place zu permutieren, siehe shuffle!
. Um zufällig permutierte Indizes zu erhalten, siehe randperm
.
Beispiele
julia> shuffle(Xoshiro(123), Vector(1:10))
10-element Vector{Int64}:
5
4
2
3
6
10
8
1
9
7
Random.shuffle!
— Functionshuffle!([rng=default_rng(),] v::AbstractArray)
In-Place-Version von shuffle
: permutiert v
zufällig in-place, wobei optional der Zufallszahlengenerator rng
angegeben werden kann.
Beispiele
julia> shuffle!(Xoshiro(123), Vector(1:10))
10-element Vector{Int64}:
5
4
2
3
6
10
8
1
9
7
Generators (creation and seeding)
Random.default_rng
— FunctionRandom.default_rng() -> rng
Gibt den standardmäßigen globalen Zufallszahlengenerator (RNG) zurück, der von rand
-bezogenen Funktionen verwendet wird, wenn kein expliziter RNG bereitgestellt wird.
Wenn das Random
-Modul geladen wird, wird der standardmäßige RNG zufällig initialisiert, über Random.seed!()
: das bedeutet, dass bei jedem Start einer neuen Julia-Sitzung der erste Aufruf von rand()
ein anderes Ergebnis liefert, es sei denn, seed!(seed)
wird zuerst aufgerufen.
Es ist threadsicher: Verschiedene Threads können sicher rand
-bezogene Funktionen auf default_rng()
gleichzeitig aufrufen, z. B. rand(default_rng())
.
Der Typ des standardmäßigen RNG ist ein Implementierungsdetail. In verschiedenen Versionen von Julia sollten Sie nicht erwarten, dass der standardmäßige RNG immer denselben Typ hat, noch dass er für einen gegebenen Seed denselben Strom von Zufallszahlen erzeugt.
Diese Funktion wurde in Julia 1.3 eingeführt.
Random.seed!
— Functionseed!([rng=default_rng()], seed) -> rng
seed!([rng=default_rng()]) -> rng
Setze den Zufallszahlengenerator zurück: rng
gibt eine reproduzierbare Zahlenfolge nur dann zurück, wenn ein seed
bereitgestellt wird. Einige RNGs akzeptieren keinen Seed, wie RandomDevice
. Nach dem Aufruf von seed!
ist rng
gleichwertig zu einem neu erstellten Objekt, das mit demselben Seed initialisiert wurde. Die Arten von akzeptierten Seeds hängen vom Typ von rng
ab, aber im Allgemeinen sollten ganze Zahlen als Seeds funktionieren.
Wenn rng
nicht angegeben ist, wird standardmäßig der Zustand des gemeinsamen, aufgabenlokalen Generators initialisiert.
Beispiele
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) # nicht reproduzierbar
true
julia> rand(Random.seed!(rng), Bool) # auch nicht reproduzierbar
false
julia> rand(Xoshiro(), Bool) # auch nicht reproduzierbar
true
Random.AbstractRNG
— TypeAbstractRNG
Supertyp für Zufallszahlengeneratoren wie MersenneTwister
und RandomDevice
.
Random.TaskLocalRNG
— TypeTaskLocalRNG
Der TaskLocalRNG
hat einen Zustand, der lokal zu seiner Aufgabe ist, nicht zu seinem Thread. Er wird bei der Erstellung der Aufgabe mit dem Zustand seiner übergeordneten Aufgabe initialisiert, jedoch ohne den Zustand des RNG der übergeordneten Aufgabe voranzutreiben.
Ein Vorteil des TaskLocalRNG
ist, dass er ziemlich schnell ist und reproduzierbare multithreaded Simulationen ermöglicht (abgesehen von Wettlaufbedingungen), unabhängig von den Entscheidungen des Planers. Solange die Anzahl der Threads nicht zur Entscheidungsfindung bei der Erstellung von Aufgaben verwendet wird, sind die Simulationsergebnisse auch unabhängig von der Anzahl der verfügbaren Threads / CPUs. Der Zufallsstrom sollte nicht von Hardware-Spezifika abhängen, bis hin zur Endianness und möglicherweise der Wortgröße.
Die Verwendung oder das Seed des RNG einer anderen Aufgabe als der von current_task()
zurückgegebenen ist undefiniertes Verhalten: Es wird die meiste Zeit funktionieren und kann manchmal stillschweigend fehlschlagen.
Beim Seed von TaskLocalRNG()
mit seed!
kann der übergebene Seed, falls vorhanden, jede ganze Zahl sein.
Das Seed von TaskLocalRNG()
mit einem negativen ganzzahligen Seed erfordert mindestens Julia 1.11.
Die Erstellung von Aufgaben führt seit Julia 1.10 nicht mehr zur Voranstellung des RNG-Zustands der übergeordneten Aufgabe.
Random.Xoshiro
— TypeXoshiro(seed::Union{Integer, AbstractString})
Xoshiro()
Xoshiro256++ ist ein schneller Pseudorandomzahlengenerator, der von David Blackman und Sebastiano Vigna in "Scrambled Linear Pseudorandom Number Generators", ACM Trans. Math. Softw., 2021 beschrieben wurde. Die Referenzimplementierung ist verfügbar unter https://prng.di.unimi.it
Neben der hohen Geschwindigkeit hat Xoshiro einen kleinen Speicherbedarf, was es für Anwendungen geeignet macht, bei denen viele verschiedene Zufallszustände über längere Zeit gehalten werden müssen.
Julias Xoshiro-Implementierung hat einen Bulk-Generierungsmodus; dieser erzeugt neue virtuelle PRNGs aus dem Elternteil und verwendet SIMD, um parallel zu generieren (d.h. der Bulk-Stream besteht aus mehreren interleaved xoshiro-Instanzen). Die virtuellen PRNGs werden verworfen, sobald die Bulk-Anfrage bearbeitet wurde (und sollten keine Heap-Allokationen verursachen).
Wenn kein Seed bereitgestellt wird, wird ein zufällig generierter erstellt (unter Verwendung von Entropie aus dem System). Siehe die seed!
Funktion zum Neusäen eines bereits vorhandenen Xoshiro
-Objekts.
Das Übergeben eines negativen ganzzahligen Seeds erfordert mindestens Julia 1.11.
Beispiele
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
— TypeMersenneTwister(seed)
MersenneTwister()
Erstellen Sie ein MersenneTwister
RNG-Objekt. Verschiedene RNG-Objekte können ihre eigenen Seeds haben, was nützlich sein kann, um verschiedene Ströme von Zufallszahlen zu generieren. Der seed
kann eine Ganzzahl, ein String oder ein Vektor von UInt32
Ganzzahlen sein. Wenn kein Seed angegeben wird, wird ein zufällig generierter Seed erstellt (unter Verwendung von Entropie aus dem System). Siehe die seed!
Funktion zum Neusetzen eines bereits vorhandenen MersenneTwister
Objekts.
Das Übergeben eines negativen ganzzahligen Seeds erfordert mindestens Julia 1.11.
Beispiele
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
Random.RandomDevice
— TypeRandomDevice()
Erstellen Sie ein RandomDevice
RNG-Objekt. Zwei solcher Objekte erzeugen immer unterschiedliche Ströme von Zufallszahlen. Die Entropie wird vom Betriebssystem bezogen.
Hooking into the Random
API
Es gibt zwei weitgehend orthogonale Möglichkeiten, die Funktionalitäten von Random
zu erweitern:
- generieren von zufälligen Werten benutzerdefinierter Typen
- neue Generatoren erstellen
Die API für 1) ist ziemlich funktional, aber relativ neu, sodass sie sich möglicherweise in zukünftigen Versionen des Random
-Moduls weiterentwickeln muss. Zum Beispiel ist es normalerweise ausreichend, eine rand
-Methode zu implementieren, damit alle anderen üblichen Methoden automatisch funktionieren.
Die API für 2) ist noch rudimentär und könnte mehr Arbeit erfordern, als unbedingt notwendig ist, um die üblichen Arten von generierten Werten zu unterstützen.
Generating random values of custom types
Das Generieren von Zufallswerten für einige Verteilungen kann verschiedene Kompromisse mit sich bringen. Vorab berechnete Werte, wie alias table für diskrete Verteilungen oder “squeezing” functions für univariate Verteilungen, können das Sampling erheblich beschleunigen. Wie viele Informationen vorab berechnet werden sollten, kann von der Anzahl der Werte abhängen, die wir aus einer Verteilung ziehen möchten. Außerdem können einige Zufallszahlengeneratoren bestimmte Eigenschaften haben, die verschiedene Algorithmen ausnutzen möchten.
Das Random
-Modul definiert ein anpassbares Framework zur Erzeugung von Zufallswerten, das diese Probleme adressieren kann. Jeder Aufruf von rand
erzeugt einen Sampler, der mit den oben genannten Kompromissen im Hinterkopf angepasst werden kann, indem Methoden zu Sampler
hinzugefügt werden, die wiederum auf den Zufallszahlengenerator, das Objekt, das die Verteilung charakterisiert, und einen Vorschlag für die Anzahl der Wiederholungen zugreifen können. Derzeit werden für letzteres Val{1}
(für eine einzelne Probe) und Val{Inf}
(für eine beliebige Anzahl) verwendet, wobei Random.Repetition
ein Alias für beide ist.
Das von Sampler
zurückgegebene Objekt wird dann verwendet, um die Zufallswerte zu generieren. Bei der Implementierung der Schnittstelle zur Zufallswertgenerierung für einen Wert X
, der ausgewählt werden kann, sollte der Implementierer die Methode definieren
rand(rng, sampler)
für den speziellen sampler
, der von Sampler(rng, X, repetition)
zurückgegeben wird.
Sampler können beliebige Werte sein, die rand(rng, sampler)
implementieren, aber für die meisten Anwendungen sind die folgenden vordefinierten Sampler möglicherweise ausreichend:
SamplerType{T}()
kann verwendet werden, um Sampler zu implementieren, die von TypT
ziehen (z. B.rand(Int)
). Dies ist der Standardwert, der vonSampler
für Typen zurückgegeben wird.SamplerTrivial(self)
ist ein einfacher Wrapper fürself
, der mit[]
zugegriffen werden kann. Dies ist der empfohlene Sampler, wenn keine vorab berechneten Informationen benötigt werden (z. B.rand(1:3)
), und ist der Standard, der vonSampler
für Werte zurückgegeben wird.SamplerSimple(self, data)
enthält auch das zusätzlichedata
-Feld, das verwendet werden kann, um beliebige vorab berechnete Werte zu speichern, die in einer benutzerdefinierten Methode vonSampler
berechnet werden sollten.
Wir geben Beispiele für jedes dieser. Wir gehen hier davon aus, dass die Wahl des Algorithmus unabhängig vom RNG ist, daher verwenden wir AbstractRNG
in unseren Signaturen.
Random.Sampler
— TypeSampler(rng, x, repetition = Val(Inf))
Gibt ein Sampler-Objekt zurück, das verwendet werden kann, um zufällige Werte aus rng
für x
zu generieren.
Wenn sp = Sampler(rng, x, repetition)
ist, wird rand(rng, sp)
verwendet, um zufällige Werte zu ziehen, und sollte entsprechend definiert werden.
repetition
kann Val(1)
oder Val(Inf)
sein und sollte als Vorschlag für die Entscheidung über die Menge an Vorberechnung verwendet werden, falls zutreffend.
Random.SamplerType
und Random.SamplerTrivial
sind Standardrückfalloptionen für Typen und Werte respektive. Random.SamplerSimple
kann verwendet werden, um vorab berechnete Werte zu speichern, ohne zusätzliche Typen nur für diesen Zweck zu definieren. ```
Random.SamplerType
— TypeSamplerType{T}()
Ein Sampler für Typen, der keine weiteren Informationen enthält. Der Standardfallback für Sampler
, wenn er mit Typen aufgerufen wird.
Random.SamplerTrivial
— TypeSamplerTrivial(x)
Erstellen Sie einen Sampler, der einfach den gegebenen Wert x
umschließt. Dies ist der standardmäßige Rückfall für Werte. Der eltype
dieses Samplers ist gleich eltype(x)
.
Der empfohlene Anwendungsfall ist das Sampling von Werten ohne vorab berechnete Daten.
Random.SamplerSimple
— TypeSamplerSimple(x, data)
Erstellen Sie einen Sampler, der den gegebenen Wert x
und die data
umschließt. Der eltype
dieses Samplers ist gleich eltype(x)
.
Der empfohlene Anwendungsfall ist das Sampling von Werten mit vorab berechneten Daten.
Die Entkopplung der Vorberechnung von der tatsächlichen Generierung der Werte ist Teil der API und steht auch dem Benutzer zur Verfügung. Als Beispiel nehmen wir an, dass rand(rng, 1:20)
wiederholt in einer Schleife aufgerufen werden muss: Die Möglichkeit, von dieser Entkopplung zu profitieren, ist wie folgt:
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
Dies ist der Mechanismus, der auch in der Standardbibliothek verwendet wird, z. B. durch die Standardimplementierung der zufälligen Array-Generierung (wie in rand(1:20, 10)
).
Generating values from a type
Gegeben einen Typ T
wird derzeit angenommen, dass, wenn rand(T)
definiert ist, ein Objekt des Typs T
erzeugt wird. SamplerType
ist der Standard-Sampler für Typen. Um die zufällige Generierung von Werten des Typs T
zu definieren, sollte die Methode rand(rng::AbstractRNG, ::Random.SamplerType{T})
definiert werden und Werte zurückgeben, die dem entsprechen, was rand(rng, T)
zurückgeben soll.
Lass uns folgendes Beispiel betrachten: Wir implementieren einen Die
-Typ, mit einer variablen Anzahl n
von Seiten, nummeriert von 1
bis n
. Wir möchten, dass rand(Die)
einen Die
mit einer zufälligen Anzahl von bis zu 20 Seiten (und mindestens 4) erzeugt:
struct Die
nsides::Int # number of sides
end
Random.rand(rng::AbstractRNG, ::Random.SamplerType{Die}) = Die(rand(rng, 4:20))
# output
Skalar- und Array-Methoden für Die
funktionieren jetzt wie erwartet:
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
Hier definieren wir einen Sampler für eine Sammlung. Wenn keine vorab berechneten Daten erforderlich sind, kann er mit einem SamplerTrivial
-Sampler implementiert werden, der tatsächlich der Standardfallback für Werte ist.
Um die Zufallsgenerierung aus Objekten des Typs S
zu definieren, sollte die folgende Methode definiert werden: rand(rng::AbstractRNG, sp::Random.SamplerTrivial{S})
. Hierbei umschließt sp
einfach ein Objekt des Typs S
, das über sp[]
zugegriffen werden kann. Fortfahrend mit dem Beispiel Die
wollen wir nun rand(d::Die)
definieren, um eine Int
zu erzeugen, die einer der Seiten von d
entspricht:
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
Gegeben einen Sammlungstyp S
wird derzeit angenommen, dass, wenn rand(::S)
definiert ist, ein Objekt des Typs eltype(S)
erzeugt wird. Im letzten Beispiel wird ein Vector{Any}
erzeugt; der Grund dafür ist, dass eltype(Die) == Any
ist. Die Lösung besteht darin, Base.eltype(::Type{Die}) = Int
zu definieren.
Generating values for an AbstractFloat
type
AbstractFloat
-Typen sind speziell behandelt, da standardmäßig keine Zufallswerte im gesamten Typbereich erzeugt werden, sondern eher im Bereich [0,1)
. Die folgende Methode sollte für T <: AbstractFloat
implementiert werden: Random.rand(::AbstractRNG, ::Random.SamplerTrivial{Random.CloseOpen01{T}})
An optimized sampler with pre-computed data
Betrachten Sie eine diskrete Verteilung, bei der Zahlen 1:n
mit gegebenen Wahrscheinlichkeiten gezogen werden, die sich zu eins summieren. Wenn viele Werte aus dieser Verteilung benötigt werden, ist die schnellste Methode die Verwendung einer alias table. Wir geben hier nicht den Algorithmus zum Erstellen einer solchen Tabelle an, aber nehmen Sie an, dass er stattdessen in make_alias_table(probabilities)
verfügbar ist, und draw_number(rng, alias_table)
verwendet werden kann, um eine Zufallszahl daraus zu ziehen.
Angenommen, die Verteilung wird beschrieben durch
struct DiscreteDistribution{V <: AbstractVector}
probabilities::V
end
und dass wir immer eine Alias-Tabelle erstellen möchten, unabhängig von der Anzahl der benötigten Werte (wir lernen unten, wie man dies anpasst). Die Methoden
Random.eltype(::Type{<:DiscreteDistribution}) = Int
function Random.Sampler(::Type{<:AbstractRNG}, distribution::DiscreteDistribution, ::Repetition)
SamplerSimple(distribution, make_alias_table(distribution.probabilities))
end
sollte definiert werden, um einen Sampler mit vorab berechneten Daten zurückzugeben, dann
function rand(rng::AbstractRNG, sp::SamplerSimple{<:DiscreteDistribution})
draw_number(rng, sp.data)
end
wird verwendet, um die Werte zu zeichnen.
Custom sampler types
Der SamplerSimple
-Typ ist für die meisten Anwendungsfälle mit vorab berechneten Daten ausreichend. Um jedoch zu demonstrieren, wie man benutzerdefinierte Samplertypen verwendet, implementieren wir hier etwas Ähnliches wie SamplerSimple
.
Zurück zu unserem Die
-Beispiel: rand(::Die)
verwendet die Zufallszahlengenerierung aus einem Bereich, sodass es hier eine Möglichkeit zur Optimierung gibt. Wir nennen unseren benutzerdefinierten Sampler 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)
Es ist jetzt möglich, einen Sampler mit sp = Sampler(rng, die)
zu erhalten und sp
anstelle von die
in jedem rand
-Aufruf zu verwenden, der rng
betrifft. In dem einfachen obigen Beispiel muss die
nicht in SamplerDie
gespeichert werden, aber das ist in der Praxis oft der Fall.
Natürlich ist dieses Muster so häufig, dass der oben verwendete Hilfstyp, nämlich Random.SamplerSimple
, verfügbar ist, was uns die Definition von SamplerDie
erspart: Wir hätten unsere Entkopplung mit folgendem umsetzen können:
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)
Hier bezieht sich sp.data
auf den zweiten Parameter im Aufruf des Konstruktors SamplerSimple
(in diesem Fall gleich Sampler(rng, 1:die.nsides, r)
), während das Die
-Objekt über sp[]
zugänglich ist.
Wie SamplerDie
muss jeder benutzerdefinierte Sampler ein Subtyp von Sampler{T}
sein, wobei T
der Typ der generierten Werte ist. Beachten Sie, dass SamplerSimple(x, data) isa Sampler{eltype(x)}
ist, was einschränkt, was das erste Argument von SamplerSimple
sein kann (es wird empfohlen, SamplerSimple
wie im Die
-Beispiel zu verwenden, wo x
einfach weitergegeben wird, während eine Sampler
-Methode definiert wird). Ebenso gilt, dass SamplerTrivial(x) isa Sampler{eltype(x)}
.
Ein weiterer Hilfstyp ist derzeit für andere Fälle verfügbar, Random.SamplerTag
, wird jedoch als interne API betrachtet und kann jederzeit ohne ordnungsgemäße Abkündigungen brechen.
Using distinct algorithms for scalar or array generation
In einigen Fällen hat es Einfluss auf die Wahl des Algorithmus, ob man nur eine Handvoll Werte oder eine große Anzahl von Werten generieren möchte. Dies wird mit dem dritten Parameter des Sampler
-Konstruktors behandelt. Angenommen, wir haben zwei Hilfstypen für Die
definiert, sagen wir SamplerDie1
, der verwendet werden sollte, um nur wenige zufällige Werte zu generieren, und SamplerDieMany
für viele Werte. Wir können diese Typen wie folgt verwenden:
Sampler(RNG::Type{<:AbstractRNG}, die::Die, ::Val{1}) = SamplerDie1(...)
Sampler(RNG::Type{<:AbstractRNG}, die::Die, ::Val{Inf}) = SamplerDieMany(...)
Natürlich muss rand
auch für diese Typen definiert werden (d.h. rand(::AbstractRNG, ::SamplerDie1)
und rand(::AbstractRNG, ::SamplerDieMany)
). Beachten Sie, dass wie gewohnt SamplerTrivial
und SamplerSimple
verwendet werden können, wenn benutzerdefinierte Typen nicht erforderlich sind.
Hinweis: Sampler(rng, x)
ist einfach eine Abkürzung für Sampler(rng, x, Val(Inf))
, und Random.Repetition
ist ein Alias für Union{Val{1}, Val{Inf}}
.
Creating new generators
Die API ist noch nicht klar definiert, aber als Faustregel:
- Jede
rand
-Methode, die "einfache" Typen (isbitstype
Ganzzahlen und Fließkommatypen inBase
) erzeugt, sollte für diesen spezifischen RNG definiert werden, falls sie benötigt wird; - Andere dokumentierte
rand
-Methoden, die einenAbstractRNG
akzeptieren, sollten sofort funktionieren (vorausgesetzt, die Methoden aus 1), auf die verwiesen wird, sind implementiert), können jedoch natürlich für diesen RNG spezialisiert werden, wenn Raum für Optimierungen besteht; copy
für pseudo-RNGs sollte eine unabhängige Kopie zurückgeben, die die exakt gleiche Zufallssequenz wie das Original ab dem Zeitpunkt generiert, an dem sie auf die gleiche Weise aufgerufen wird. Wenn dies nicht möglich ist (z. B. bei hardwarebasierten RNGs), darfcopy
nicht implementiert werden.
Bezüglich 1) kann eine rand
-Methode zufällig automatisch funktionieren, wird jedoch nicht offiziell unterstützt und kann in einer späteren Version ohne Vorwarnung brechen.
Um eine neue rand
-Methode für einen hypothetischen MyRNG
-Generator und eine Wertespezifikation s
(z. B. s == Int
oder s == 1:10
) vom Typ S==typeof(s)
oder S==Type{s}
zu definieren, müssen die gleichen beiden Methoden wie zuvor definiert werden:
Sampler(::Type{MyRNG}, ::S, ::Repetition)
, der ein Objekt des TypsSamplerS
zurückgibt.rand(rng::MyRNG, sp::SamplerS)
Es kann vorkommen, dass Sampler(rng::AbstractRNG, ::S, ::Repetition)
bereits im Random
-Modul definiert ist. In diesem Fall wäre es möglich, Schritt 1) in der Praxis zu überspringen (wenn man die Generierung für diesen speziellen RNG-Typ spezialisieren möchte), aber der entsprechende SamplerS
-Typ wird als internes Detail betrachtet und kann ohne Vorwarnung geändert werden.
Specializing array generation
In einigen Fällen kann es für einen bestimmten RNG-Typ effizienter sein, ein Array von Zufallswerten mit einer spezialisierten Methode zu generieren, als lediglich die zuvor erklärte Entkopplungstechnik zu verwenden. Dies ist beispielsweise der Fall bei MersenneTwister
, der nativ Zufallswerte in ein Array schreibt.
Um diese Spezialisierung für MyRNG
und für eine Spezifikation s
zu implementieren, die Elemente vom Typ S
erzeugt, kann die folgende Methode definiert werden: rand!(rng::MyRNG, a::AbstractArray{S}, ::SamplerS)
, wobei SamplerS
der Typ des Samplers ist, der von Sampler(MyRNG, s, Val(Inf))
zurückgegeben wird. Anstelle von AbstractArray
ist es möglich, die Funktionalität nur für einen Untertyp zu implementieren, z. B. Array{S}
. Die nicht-mutierende Array-Methode von rand
wird diese Spezialisierung intern automatisch aufrufen.
Reproducibility
Durch die Verwendung eines mit einem bestimmten Seed initialisierten RNG-Parameters können Sie die gleiche Pseudorandom-Zahlenfolge reproduzieren, wenn Sie Ihr Programm mehrmals ausführen. Eine kleinere Version von Julia (z. B. 1.3 bis 1.4) kann jedoch die Folge der aus einem bestimmten Seed generierten Pseudorandom-Zahlen ändern, insbesondere wenn MersenneTwister
verwendet wird. (Selbst wenn die von einer Low-Level-Funktion wie rand
erzeugte Folge sich nicht ändert, kann die Ausgabe von höherstufigen Funktionen wie randsubseq
aufgrund von Algorithmus-Updates variieren.) Begründung: Die Garantie, dass Pseudorandom-Streams sich niemals ändern, verbietet viele algorithmische Verbesserungen.
Wenn Sie die genaue Reproduzierbarkeit von Zufallsdaten garantieren müssen, ist es ratsam, einfach die Daten zu speichern (z. B. als ergänzenden Anhang in einer wissenschaftlichen Veröffentlichung). (Sie können natürlich auch eine bestimmte Julia-Version und ein Paketmanifest angeben, insbesondere wenn Sie eine bitgenaue Reproduzierbarkeit benötigen.)
Softwaretests, die auf spezifischen "zufälligen" Daten basieren, sollten in der Regel entweder die Daten speichern, sie in den Testcode einbetten oder Drittanbieter-Pakete wie StableRNGs.jl verwenden. Auf der anderen Seite können Tests, die für die meisten zufälligen Daten bestehen sollten (z. B. das Testen von A \ (A*x) ≈ x
für eine zufällige Matrix A = randn(n,n)
), einen RNG mit einem festen Seed verwenden, um sicherzustellen, dass das bloße Ausführen des Tests viele Male nicht auf einen Fehler aufgrund sehr unwahrscheinlicher Daten stößt (z. B. eine extrem schlecht konditionierte Matrix).
Die statistische Verteilung, aus der Zufallsstichproben gezogen werden, ist garantiert, dass sie über alle kleineren Julia-Versionen hinweg gleich bleibt.