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 durch RandomDevice (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.

Warning

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 generation functions

Base.randFunction
rand([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 oder AbstractSet Objekt

  • ein 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ßer BigInt, das nicht unterstützt wird)
    • konkrete Gleitkommatypen, die aus [0, 1) entnommen werden
    • konkrete komplexe Typen Complex{T}, wenn T ein samplbarer Typ ist, nehmen Sie deren reale und imaginäre Komponenten unabhängig aus der Menge von Werten, die T entsprechen, werden jedoch nicht unterstützt, wenn T 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 Typs S zurück. Beachten Sie, dass Tupeltypen wie Tuple{Vararg{T}} (unbekannte Größe) und Tuple{1:2} (parametrisiert mit einem Wert) nicht unterstützt werden
    • ein Pair Typ, z.B. Pair{X, Y}, sodass rand für X und Y definiert ist, in diesem Fall werden zufällige Paare erzeugt.

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.

Julia 1.1

Die Unterstützung für S als Tupel erfordert mindestens Julia 1.1.

Julia 1.11

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
Note

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 BitSets 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.

source
Random.rand!Function
rand!([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
source
Random.bitrandFunction
bitrand([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
source
Base.randnFunction
randn([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
source
Random.randn!Function
randn!([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
source
Random.randexpFunction
randexp([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
source
Random.randexp!Function
randexp!([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
source
Random.randstringFunction
randstring([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.

source

Subsequences, permutations and shuffling

Random.randsubseqFunction
randsubseq([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
source
Random.randsubseq!Function
randsubseq!([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
source
Random.randpermFunction
randperm([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!.

Julia 1.1

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
source
Random.randperm!Function
randperm!([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
source
Random.randcycleFunction
randcycle([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.

Julia 1.1

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
source
Random.randcycle!Function
randcycle!([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
source
Random.shuffleFunction
shuffle([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
source
Random.shuffle!Function
shuffle!([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
source

Generators (creation and seeding)

Random.default_rngFunction
Random.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()).

Note

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.

Julia 1.3

Diese Funktion wurde in Julia 1.3 eingeführt.

source
Random.seed!Function
seed!([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
source
Random.TaskLocalRNGType
TaskLocalRNG

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.

Julia 1.11

Das Seed von TaskLocalRNG() mit einem negativen ganzzahligen Seed erfordert mindestens Julia 1.11.

Julia 1.10

Die Erstellung von Aufgaben führt seit Julia 1.10 nicht mehr zur Voranstellung des RNG-Zustands der übergeordneten Aufgabe.

source
Random.XoshiroType
Xoshiro(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.

Julia 1.11

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

Julia 1.11

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
source
Random.RandomDeviceType
RandomDevice()

Erstellen Sie ein RandomDevice RNG-Objekt. Zwei solcher Objekte erzeugen immer unterschiedliche Ströme von Zufallszahlen. Die Entropie wird vom Betriebssystem bezogen.

source

Hooking into the Random API

Es gibt zwei weitgehend orthogonale Möglichkeiten, die Funktionalitäten von Random zu erweitern:

  1. generieren von zufälligen Werten benutzerdefinierter Typen
  2. 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:

  1. SamplerType{T}() kann verwendet werden, um Sampler zu implementieren, die von Typ T ziehen (z. B. rand(Int)). Dies ist der Standardwert, der von Sampler für Typen zurückgegeben wird.

  2. SamplerTrivial(self) ist ein einfacher Wrapper für self, 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 von Sampler für Werte zurückgegeben wird.

  3. SamplerSimple(self, data) enthält auch das zusätzliche data-Feld, das verwendet werden kann, um beliebige vorab berechnete Werte zu speichern, die in einer benutzerdefinierten Methode von Sampler 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.SamplerType
Sampler(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. ```

source
Random.SamplerTypeType
SamplerType{T}()

Ein Sampler für Typen, der keine weiteren Informationen enthält. Der Standardfallback für Sampler, wenn er mit Typen aufgerufen wird.

source
Random.SamplerTrivialType
SamplerTrivial(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.

source
Random.SamplerSimpleType
SamplerSimple(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.

source

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:

  1. Jede rand-Methode, die "einfache" Typen (isbitstype Ganzzahlen und Fließkommatypen in Base) erzeugt, sollte für diesen spezifischen RNG definiert werden, falls sie benötigt wird;
  2. Andere dokumentierte rand-Methoden, die einen AbstractRNG 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;
  3. 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), darf copy 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:

  1. Sampler(::Type{MyRNG}, ::S, ::Repetition), der ein Objekt des Typs SamplerS zurückgibt.
  2. 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.