Random Numbers
La génération de nombres aléatoires en Julia utilise par défaut l'algorithme Xoshiro256++, avec un état par Task
. D'autres types de générateurs de nombres aléatoires peuvent être intégrés en héritant du type AbstractRNG
; ils peuvent ensuite être utilisés pour obtenir plusieurs flux de nombres aléatoires.
Les PRNG (générateurs de nombres pseudo-aléatoires) exportés par le paquet Random
sont :
TaskLocalRNG
: un jeton qui représente l'utilisation du flux local à la tâche actuellement actif, semé de manière déterministe à partir de la tâche parente, ou parRandomDevice
(avec de l'aléa système) au démarrage du programme.Xoshiro
: génère un flux de nombres aléatoires de haute qualité avec un petit vecteur d'état et des performances élevées en utilisant l'algorithme Xoshiro256++RandomDevice
: pour l'entropie fournie par le système d'exploitation. Cela peut être utilisé pour des nombres aléatoires cryptographiquement sécurisés (CS(P)RNG).MersenneTwister
: un PRNG de haute qualité alternatif qui était le défaut dans les anciennes versions de Julia, et qui est également assez rapide, mais nécessite beaucoup plus d'espace pour stocker le vecteur d'état et générer une séquence aléatoire.
La plupart des fonctions liées à la génération aléatoire acceptent un objet AbstractRNG
optionnel comme premier argument. Certaines acceptent également des spécifications de dimensions dims...
(qui peuvent également être données sous forme de tuple) pour générer des tableaux de valeurs aléatoires. Dans un programme multi-thread, vous devriez généralement utiliser différents objets RNG provenant de différents threads ou tâches afin d'être thread-safe. Cependant, le RNG par défaut est thread-safe depuis Julia 1.3 (utilisant un RNG par thread jusqu'à la version 1.6, et par tâche par la suite).
Les RNG fournis peuvent générer des nombres aléatoires uniformes des types suivants : Float16
, Float32
, Float64
, BigFloat
, Bool
, Int8
, UInt8
, Int16
, UInt16
, Int32
, UInt32
, Int64
, UInt64
, Int128
, UInt128
, BigInt
(ou des nombres complexes de ces types). Des nombres flottants aléatoires sont générés uniformément dans $[0, 1)$. Comme BigInt
représente des entiers non bornés, l'intervalle doit être spécifié (par exemple, rand(big.(1:6))
).
De plus, des distributions normales et exponentielles sont implémentées pour certains types AbstractFloat
et Complex
, voir randn
et randexp
pour plus de détails.
Pour générer des nombres aléatoires à partir d'autres distributions, consultez le package Distributions.jl.
Parce que la manière précise dont les nombres aléatoires sont générés est considérée comme un détail d'implémentation, les corrections de bogues et les améliorations de vitesse peuvent modifier le flux de nombres générés après un changement de version. Il est donc déconseillé de s'appuyer sur une graine spécifique ou un flux de nombres générés lors des tests unitaires - envisagez plutôt de tester les propriétés des méthodes en question.
Random numbers module
Random.Random
— ModuleAléatoire
Support pour la génération de nombres aléatoires. Fournit rand
, randn
, AbstractRNG
, MersenneTwister
, et RandomDevice
.
Random generation functions
Base.rand
— Functionrand([rng=default_rng()], [S], [dims...])
Choisissez un élément aléatoire ou un tableau d'éléments aléatoires à partir de l'ensemble de valeurs spécifié par S
; S
peut être
une collection indexable (par exemple
1:9
ou('x', "y", :z)
)un objet
AbstractDict
ouAbstractSet
une chaîne de caractères (considérée comme une collection de caractères), ou
un type de la liste ci-dessous, correspondant à l'ensemble de valeurs spécifié
- les types entiers concrets échantillonnent de
typemin(S):typemax(S)
(à l'exception deBigInt
qui n'est pas supporté) - les types de nombres à virgule flottante concrets échantillonnent de
[0, 1)
- les types complexes concrets
Complex{T}
siT
est un type échantillonnable prennent leurs composants réels et imaginaires indépendamment de l'ensemble de valeurs correspondant àT
, mais ne sont pas supportés siT
n'est pas échantillonnable. - tous les types
<:AbstractChar
échantillonnent de l'ensemble des scalaires Unicode valides - un type défini par l'utilisateur et un ensemble de valeurs ; pour des conseils de mise en œuvre, veuillez consulter Hooking into the
Random
API - un type de tuple de taille connue et où chaque paramètre de
S
est lui-même un type échantillonnable ; renvoie une valeur de typeS
. Notez que les types de tuple tels queTuple{Vararg{T}}
(taille inconnue) etTuple{1:2}
(paramétré avec une valeur) ne sont pas supportés - un type
Pair
, par exemplePair{X, Y}
tel querand
est défini pourX
etY
, auquel cas des paires aléatoires sont produites.
- les types entiers concrets échantillonnent de
S
par défaut à Float64
. Lorsque seul un argument est passé en plus de l'optionnel rng
et est un Tuple
, il est interprété comme une collection de valeurs (S
) et non comme dims
.
Voir aussi randn
pour des nombres distribués normalement, et rand!
et randn!
pour les équivalents en place.
Le support de S
en tant que tuple nécessite au moins Julia 1.1.
Le support de S
en tant que type Tuple
nécessite au moins Julia 1.11.
Exemples
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
La complexité de rand(rng, s::Union{AbstractDict,AbstractSet})
est linéaire par rapport à la longueur de s
, sauf si une méthode optimisée avec une complexité constante est disponible, ce qui est le cas pour Dict
, Set
et les BitSet
s denses. Pour plus de quelques appels, utilisez rand(rng, collect(s))
à la place, ou soit rand(rng, Dict(s))
ou rand(rng, Set(s))
selon le cas.
Random.rand!
— Functionrand!([rng=default_rng()], A, [S=eltype(A)])
Remplissez le tableau A
avec des valeurs aléatoires. Si S
est spécifié (S
peut être un type ou une collection, cf. rand
pour plus de détails), les valeurs sont choisies aléatoirement dans S
. Cela équivaut à copyto!(A, rand(rng, S, size(A)))
mais sans allouer un nouveau tableau.
Exemples
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...])
Génère un BitArray
de valeurs booléennes aléatoires.
Exemples
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...])
Génère un nombre aléatoire distribué normalement de type T
avec une moyenne de 0 et un écart type de 1. Étant donné les arguments dims
optionnels, générez un tableau de taille dims
de tels nombres. La bibliothèque standard de Julia prend en charge randn
pour tout type à virgule flottante qui implémente rand
, par exemple les types Base
Float16
, Float32
, Float64
(le défaut), et BigFloat
, ainsi que leurs homologues Complex
.
(Lorsque T
est complexe, les valeurs sont tirées de la distribution normale complexe circulairement symétrique de variance 1, correspondant à des parties réelles et imaginaires ayant une distribution normale indépendante avec une moyenne de zéro et une variance de 1/2
).
Voir aussi randn!
pour agir en place.
Exemples
Génération d'un seul nombre aléatoire (avec le type par défaut Float64
) :
julia> randn()
-0.942481877315864
Génération d'une matrice de nombres aléatoires normaux (avec le type par défaut Float64
) :
julia> randn(2,3)
2×3 Matrix{Float64}:
1.18786 -0.678616 1.49463
-0.342792 -0.134299 -1.45005
Configuration du générateur de nombres aléatoires rng
avec une graine définie par l'utilisateur (pour des nombres reproductibles) et utilisation de celui-ci pour générer un nombre aléatoire Float32
ou une matrice de nombres aléatoires 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
Random.randn!
— Functionrandn!([rng=default_rng()], A::AbstractArray) -> A
Remplit le tableau A
avec des nombres aléatoires distribués normalement (moyenne 0, écart type 1). Voir aussi la fonction rand
.
Exemples
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...])
Génère un nombre aléatoire de type T
selon la distribution exponentielle avec une échelle de 1. Génère éventuellement un tableau de tels nombres aléatoires. Le module Base
fournit actuellement une implémentation pour les types Float16
, Float32
et Float64
(le défaut).
Exemples
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
Remplit le tableau A
avec des nombres aléatoires suivant la distribution exponentielle (avec une échelle de 1).
Exemples
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])
Crée une chaîne aléatoire de longueur len
, composée de caractères de chars
, qui par défaut est l'ensemble des lettres majuscules et minuscules et des chiffres 0-9. L'argument optionnel rng
spécifie un générateur de nombres aléatoires, voir Random Numbers.
Exemples
julia> Random.seed!(3); randstring()
"Lxz5hUwn"
julia> randstring(Xoshiro(3), 'a':'z', 6)
"iyzcsm"
julia> randstring("ACGT")
"TGCTCCTC"
chars
peut être n'importe quelle collection de caractères, de type Char
ou UInt8
(plus efficace), à condition que rand
puisse choisir des caractères au hasard à partir de celle-ci.
Subsequences, permutations and shuffling
Random.randsubseq
— Functionrandsubseq([rng=default_rng(),] A, p) -> Vector
Retourne un vecteur consistant en une sous-séquence aléatoire du tableau donné A
, où chaque élément de A
est inclus (dans l'ordre) avec une probabilité indépendante p
. (La complexité est linéaire en p*length(A)
, donc cette fonction est efficace même si p
est petit et A
est grand.) Techniquement, ce processus est connu sous le nom de "sampling de Bernoulli" de A
.
Exemples
julia> randsubseq(Xoshiro(123), 1:8, 0.3)
2-element Vector{Int64}:
4
7
Random.randsubseq!
— Functionrandsubseq!([rng=default_rng(),] S, A, p)
Comme randsubseq
, mais les résultats sont stockés dans S
(qui est redimensionné si nécessaire).
Exemples
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)
Construit une permutation aléatoire de longueur n
. L'argument optionnel rng
spécifie un générateur de nombres aléatoires (voir Random Numbers). Le type d'élément du résultat est le même que le type de n
.
Pour permuter aléatoirement un vecteur arbitraire, voir shuffle
ou shuffle!
.
Dans Julia 1.1, randperm
renvoie un vecteur v
avec eltype(v) == typeof(n)
tandis que dans Julia 1.0, eltype(v) == Int
.
Exemples
julia> randperm(Xoshiro(123), 4)
4-element Vector{Int64}:
1
4
2
3
Random.randperm!
— Functionrandperm!([rng=default_rng(),] A::Array{<:Integer})
Construit dans A
une permutation aléatoire de longueur length(A)
. L'argument optionnel rng
spécifie un générateur de nombres aléatoires (voir Nombres aléatoires). Pour permuter aléatoirement un vecteur arbitraire, voir shuffle
ou shuffle!
.
Exemples
julia> randperm!(Xoshiro(123), Vector{Int}(undef, 4))
4-element Vector{Int64}:
1
4
2
3
Random.randcycle
— Functionrandcycle([rng=default_rng(),] n::Integer)
Construit une permutation cyclique aléatoire de longueur n
. L'argument optionnel rng
spécifie un générateur de nombres aléatoires, voir Random Numbers. Le type d'élément du résultat est le même que le type de n
.
Ici, une "permutation cyclique" signifie que tous les éléments se trouvent dans un seul cycle. Si n > 0
, il y a $(n-1)!$ permutations cycliques possibles, qui sont échantillonnées uniformément. Si n == 0
, randcycle
renvoie un vecteur vide.
randcycle!
est une variante en place de cette fonction.
Dans Julia 1.1 et versions supérieures, randcycle
renvoie un vecteur v
avec eltype(v) == typeof(n)
tandis que dans Julia 1.0 eltype(v) == Int
.
Exemples
julia> randcycle(Xoshiro(123), 6)
6-element Vector{Int64}:
5
4
2
6
3
1
Random.randcycle!
— Functionrandcycle!([rng=default_rng(),] A::Array{<:Integer})
Construit dans A
une permutation cyclique aléatoire de longueur n = length(A)
. L'argument optionnel rng
spécifie un générateur de nombres aléatoires, voir Random Numbers.
Ici, une "permutation cyclique" signifie que tous les éléments se trouvent dans un seul cycle. Si A
n'est pas vide (n > 0
), il y a $(n-1)!$ permutations cycliques possibles, qui sont échantillonnées uniformément. Si A
est vide, randcycle!
le laisse inchangé.
randcycle
est une variante de cette fonction qui alloue un nouveau vecteur.
Exemples
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)
Retourne une copie de v
permutée aléatoirement. L'argument optionnel rng
spécifie un générateur de nombres aléatoires (voir Nombres aléatoires). Pour permuter v
sur place, voir shuffle!
. Pour obtenir des indices permutés aléatoirement, voir randperm
.
Exemples
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)
Version en place de shuffle
: permute aléatoirement v
en place, en fournissant éventuellement le générateur de nombres aléatoires rng
.
Exemples
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
Renvoie le générateur de nombres aléatoires (RNG) global par défaut, qui est utilisé par les fonctions liées à rand
lorsque aucun RNG explicite n'est fourni.
Lorsque le module Random
est chargé, le RNG par défaut est aléatoirement initialisé, via Random.seed!()
: cela signifie qu'à chaque fois qu'une nouvelle session julia est démarrée, le premier appel à rand()
produit un résultat différent, à moins que seed!(seed)
ne soit appelé en premier.
Il est thread-safe : des threads distincts peuvent appeler en toute sécurité des fonctions liées à rand
sur default_rng()
de manière concurrente, par exemple rand(default_rng())
.
Le type du RNG par défaut est un détail d'implémentation. À travers différentes versions de Julia, vous ne devez pas vous attendre à ce que le RNG par défaut ait toujours le même type, ni qu'il produise le même flux de nombres aléatoires pour une graine donnée.
Cette fonction a été introduite dans Julia 1.3.
Random.seed!
— Functionseed!([rng=default_rng()], seed) -> rng
seed!([rng=default_rng()]) -> rng
Rensemence le générateur de nombres aléatoires : rng
donnera une séquence de nombres reproductible si et seulement si un seed
est fourni. Certains RNG n'acceptent pas de seed, comme RandomDevice
. Après l'appel à seed!
, rng
est équivalent à un nouvel objet créé et initialisé avec le même seed. Les types de seeds acceptés dépendent du type de rng
, mais en général, les seeds entiers devraient fonctionner.
Si rng
n'est pas spécifié, il par défaut à la mise à jour de l'état du générateur local partagé.
Exemples
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) # pas reproductible
true
julia> rand(Random.seed!(rng), Bool) # pas reproductible non plus
false
julia> rand(Xoshiro(), Bool) # pas reproductible non plus
true
Random.AbstractRNG
— TypeAbstractRNG
Supertype pour les générateurs de nombres aléatoires tels que MersenneTwister
et RandomDevice
.
Random.TaskLocalRNG
— TypeTaskLocalRNG
Le TaskLocalRNG
a un état qui est local à sa tâche, et non à son thread. Il est initialisé lors de la création de la tâche, à partir de l'état de sa tâche parente, mais sans faire avancer l'état du RNG de la tâche parente.
Comme avantage, le TaskLocalRNG
est assez rapide et permet des simulations multithreadées reproductibles (en évitant les conditions de course), indépendamment des décisions du planificateur. Tant que le nombre de threads n'est pas utilisé pour prendre des décisions sur la création de tâches, les résultats de simulation sont également indépendants du nombre de threads / CPU disponibles. Le flux aléatoire ne devrait pas dépendre des spécificités matérielles, jusqu'à l'endianness et éventuellement la taille des mots.
Utiliser ou initialiser le RNG de toute autre tâche que celle retournée par current_task()
est un comportement indéfini : cela fonctionnera la plupart du temps, et peut parfois échouer silencieusement.
Lors de l'initialisation de TaskLocalRNG()
avec seed!
, la graine passée, le cas échéant, peut être n'importe quel entier.
L'initialisation de TaskLocalRNG()
avec une graine entière négative nécessite au moins Julia 1.11.
La création de tâches n'avance plus l'état du RNG de la tâche parente depuis Julia 1.10.
Random.Xoshiro
— TypeXoshiro(seed::Union{Integer, AbstractString})
Xoshiro()
Xoshiro256++ est un générateur de nombres pseudorandom rapides décrit par David Blackman et Sebastiano Vigna dans "Scrambled Linear Pseudorandom Number Generators", ACM Trans. Math. Softw., 2021. L'implémentation de référence est disponible à https://prng.di.unimi.it
En plus de sa grande vitesse, Xoshiro a une petite empreinte mémoire, ce qui le rend adapté aux applications où de nombreux états aléatoires différents doivent être conservés pendant longtemps.
L'implémentation de Xoshiro en Julia dispose d'un mode de génération en masse ; cela sème de nouveaux PRNG virtuels à partir du parent et utilise SIMD pour générer en parallèle (c'est-à-dire que le flux en masse consiste en plusieurs instances de xoshiro entrelacées). Les PRNG virtuels sont éliminés une fois la demande en masse traitée (et ne devraient pas entraîner d'allocations de tas).
Si aucun semence n'est fournie, une semence générée aléatoirement est créée (en utilisant l'entropie du système). Voir la fonction seed!
pour re-semer un objet Xoshiro
déjà existant.
Passer une semence entière négative nécessite au moins Julia 1.11.
Exemples
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()
Créez un objet RNG MersenneTwister
. Différents objets RNG peuvent avoir leurs propres graines, ce qui peut être utile pour générer différents flux de nombres aléatoires. La seed
peut être un entier, une chaîne de caractères ou un vecteur d'entiers UInt32
. Si aucune graine n'est fournie, une graine générée aléatoirement est créée (en utilisant l'entropie du système). Consultez la fonction seed!
pour réinitialiser une graine d'un objet MersenneTwister
déjà existant.
Passer une graine entière négative nécessite au moins Julia 1.11.
Exemples
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()
Créez un objet RNG RandomDevice
. Deux de ces objets généreront toujours des flux différents de nombres aléatoires. L'entropie est obtenue à partir du système d'exploitation.
Hooking into the Random
API
Il existe deux manières principalement orthogonales d'étendre les fonctionnalités de Random
:
- génération de valeurs aléatoires de types personnalisés
- créer de nouveaux générateurs
L'API pour 1) est assez fonctionnelle, mais elle est relativement récente, donc elle pourrait encore devoir évoluer dans les versions ultérieures du module Random
. Par exemple, il est généralement suffisant d'implémenter une méthode rand
pour que toutes les autres méthodes habituelles fonctionnent automatiquement.
L'API pour 2) est encore rudimentaire et peut nécessiter plus de travail que strictement nécessaire de la part de l'implémenteur, afin de prendre en charge les types habituels de valeurs générées.
Generating random values of custom types
Générer des valeurs aléatoires pour certaines distributions peut impliquer divers compromis. Les valeurs pré-calculées, telles qu'un alias table pour les distributions discrètes, ou “squeezing” functions pour les distributions univariées, peuvent considérablement accélérer l'échantillonnage. La quantité d'informations à pré-calculer peut dépendre du nombre de valeurs que nous prévoyons de tirer d'une distribution. De plus, certains générateurs de nombres aléatoires peuvent avoir certaines propriétés que divers algorithmes peuvent vouloir exploiter.
Le module Random
définit un cadre personnalisable pour obtenir des valeurs aléatoires qui peuvent résoudre ces problèmes. Chaque invocation de rand
génère un échantillonneur qui peut être personnalisé en tenant compte des compromis ci-dessus, en ajoutant des méthodes à Sampler
, qui peut à son tour dispatcher sur le générateur de nombres aléatoires, l'objet qui caractérise la distribution, et une suggestion pour le nombre de répétitions. Actuellement, pour ce dernier, Val{1}
(pour un seul échantillon) et Val{Inf}
(pour un nombre arbitraire) sont utilisés, avec Random.Repetition
comme alias pour les deux.
L'objet retourné par Sampler
est ensuite utilisé pour générer les valeurs aléatoires. Lors de l'implémentation de l'interface de génération aléatoire pour une valeur X
qui peut être échantillonnée, l'implémenteur doit définir la méthode
rand(rng, sampler)
pour le sampler
particulier retourné par Sampler(rng, X, repetition)
.
Les échantillonneurs peuvent être des valeurs arbitraires qui implémentent rand(rng, sampler)
, mais pour la plupart des applications, les échantillonneurs prédéfinis suivants peuvent être suffisants :
SamplerType{T}()
peut être utilisé pour implémenter des échantillonneurs qui tirent du typeT
(par exemple,rand(Int)
). C'est le type par défaut retourné parSampler
pour les types.SamplerTrivial(self)
est un simple wrapper pourself
, qui peut être accédé avec[]
. C'est le sampler recommandé lorsque aucune information pré-calculée n'est nécessaire (par exemple,rand(1:3)
), et c'est celui qui est retourné par défaut parSampler
pour valeurs.SamplerSimple(self, data)
contient également le champdata
supplémentaire, qui peut être utilisé pour stocker des valeurs pré-calculées arbitraires, qui devraient être calculées dans une méthode personnalisée deSampler
.
Nous fournissons des exemples pour chacun de ceux-ci. Nous supposons ici que le choix de l'algorithme est indépendant du générateur de nombres aléatoires (RNG), donc nous utilisons AbstractRNG
dans nos signatures.
Random.Sampler
— TypeSampler(rng, x, repetition = Val(Inf))
Retourne un objet sampler qui peut être utilisé pour générer des valeurs aléatoires à partir de rng
pour x
.
Lorsque sp = Sampler(rng, x, repetition)
, rand(rng, sp)
sera utilisé pour tirer des valeurs aléatoires, et devrait être défini en conséquence.
repetition
peut être Val(1)
ou Val(Inf)
, et devrait être utilisé comme une suggestion pour décider de la quantité de pré-calcul, si applicable.
Random.SamplerType
et Random.SamplerTrivial
sont des solutions par défaut pour types et valeurs, respectivement. Random.SamplerSimple
peut être utilisé pour stocker des valeurs pré-calculées sans définir des types supplémentaires uniquement à cette fin.
Random.SamplerType
— TypeSamplerType{T}()
Un échantillonneur pour les types, ne contenant aucune autre information. Le repli par défaut pour Sampler
lorsqu'il est appelé avec des types.
Random.SamplerTrivial
— TypeSamplerTrivial(x)
Créez un échantillonneur qui enveloppe simplement la valeur donnée x
. C'est le recours par défaut pour les valeurs. Le eltype
de cet échantillonneur est égal à eltype(x)
.
Le cas d'utilisation recommandé est l'échantillonnage à partir de valeurs sans données pré-calculées.
Random.SamplerSimple
— TypeSamplerSimple(x, data)
Créez un échantillonneur qui enveloppe la valeur donnée x
et les data
. Le eltype
de cet échantillonneur est égal à eltype(x)
.
Le cas d'utilisation recommandé est l'échantillonnage à partir de valeurs avec des données précalculées.
Le découplage de la pré-computation de la génération effective des valeurs fait partie de l'API et est également disponible pour l'utilisateur. Par exemple, supposons que rand(rng, 1:20)
doive être appelé plusieurs fois dans une boucle : la manière de tirer parti de ce découplage est la suivante :
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
C'est le mécanisme qui est également utilisé dans la bibliothèque standard, par exemple par l'implémentation par défaut de la génération de tableaux aléatoires (comme dans rand(1:20, 10)
).
Generating values from a type
Étant donné un type T
, on suppose actuellement que si rand(T)
est défini, un objet de type T
sera produit. SamplerType
est le sampler par défaut pour les types. Afin de définir la génération aléatoire de valeurs de type T
, la méthode rand(rng::AbstractRNG, ::Random.SamplerType{T})
doit être définie et doit retourner des valeurs que rand(rng, T)
est censé retourner.
Prenons l'exemple suivant : nous implémentons un type Die
, avec un nombre variable n
de faces, numérotées de 1
à n
. Nous voulons que rand(Die)
produise un Die
avec un nombre aléatoire allant jusqu'à 20 faces (et au moins 4) :
struct Die
nsides::Int # number of sides
end
Random.rand(rng::AbstractRNG, ::Random.SamplerType{Die}) = Die(rand(rng, 4:20))
# output
Les méthodes scalaires et de tableau pour Die
fonctionnent désormais comme prévu :
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
Ici, nous définissons un échantillonneur pour une collection. Si aucune donnée pré-calculée n'est requise, il peut être implémenté avec un échantillonneur SamplerTrivial
, qui est en fait le retour par défaut pour les valeurs.
Pour définir la génération aléatoire à partir d'objets de type S
, la méthode suivante doit être définie : rand(rng::AbstractRNG, sp::Random.SamplerTrivial{S})
. Ici, sp
enveloppe simplement un objet de type S
, qui peut être accédé via sp[]
. En continuant l'exemple du dé, nous voulons maintenant définir rand(d::Die)
pour produire un Int
correspondant à l'une des faces de d
:
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
Étant donné un type de collection S
, on suppose actuellement que si rand(::S)
est défini, un objet de type eltype(S)
sera produit. Dans le dernier exemple, un Vector{Any}
est produit ; la raison en est que eltype(Die) == Any
. Le remède consiste à définir Base.eltype(::Type{Die}) = Int
.
Generating values for an AbstractFloat
type
Les types AbstractFloat
sont traités de manière spéciale, car par défaut, les valeurs aléatoires ne sont pas produites dans l'ensemble complet du type, mais plutôt dans [0,1)
. La méthode suivante doit être implémentée pour T <: AbstractFloat
: Random.rand(::AbstractRNG, ::Random.SamplerTrivial{Random.CloseOpen01{T}})
An optimized sampler with pre-computed data
Considérez une distribution discrète, où les nombres 1:n
sont tirés avec des probabilités données qui s'additionnent à un. Lorsque de nombreuses valeurs sont nécessaires à partir de cette distribution, la méthode la plus rapide consiste à utiliser un alias table. Nous ne fournissons pas l'algorithme pour construire une telle table ici, mais supposons qu'il soit disponible dans make_alias_table(probabilities)
à la place, et draw_number(rng, alias_table)
peut être utilisé pour tirer un nombre aléatoire à partir de celle-ci.
Supposons que la distribution soit décrite par
struct DiscreteDistribution{V <: AbstractVector}
probabilities::V
end
et que nous voulons toujours construire une table d'alias, peu importe le nombre de valeurs nécessaires (nous apprendrons à personnaliser cela ci-dessous). Les méthodes
Random.eltype(::Type{<:DiscreteDistribution}) = Int
function Random.Sampler(::Type{<:AbstractRNG}, distribution::DiscreteDistribution, ::Repetition)
SamplerSimple(distribution, make_alias_table(distribution.probabilities))
end
devrait être défini pour retourner un échantillonneur avec des données pré-calculées, puis
function rand(rng::AbstractRNG, sp::SamplerSimple{<:DiscreteDistribution})
draw_number(rng, sp.data)
end
sera utilisé pour dessiner les valeurs.
Custom sampler types
Le type SamplerSimple
est suffisant pour la plupart des cas d'utilisation avec des données précalculées. Cependant, afin de démontrer comment utiliser des types de samplers personnalisés, nous implémentons ici quelque chose de similaire à SamplerSimple
.
Revenons à notre exemple de Die
: rand(::Die)
utilise la génération aléatoire à partir d'une plage, donc il y a une opportunité pour cette optimisation. Nous appelons notre échantillonneur personnalisé 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)
Il est maintenant possible d'obtenir un échantillonneur avec sp = Sampler(rng, die)
, et d'utiliser sp
au lieu de die
dans tout appel rand
impliquant rng
. Dans l'exemple simpliste ci-dessus, die
n'a pas besoin d'être stocké dans SamplerDie
, mais c'est souvent le cas en pratique.
Bien sûr, ce modèle est si fréquent que le type d'assistance utilisé ci-dessus, à savoir Random.SamplerSimple
, est disponible, nous évitant ainsi la définition de SamplerDie
: nous aurions pu implémenter notre découplage avec :
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)
Ici, sp.data
fait référence au deuxième paramètre dans l'appel au constructeur SamplerSimple
(dans ce cas égal à Sampler(rng, 1:die.nsides, r)
), tandis que l'objet Die
peut être accédé via sp[]
.
Comme SamplerDie
, tout échantillonneur personnalisé doit être un sous-type de Sampler{T}
où T
est le type des valeurs générées. Notez que SamplerSimple(x, data) isa Sampler{eltype(x)}
, ce qui limite ce que le premier argument de SamplerSimple
peut être (il est recommandé d'utiliser SamplerSimple
comme dans l'exemple Die
, où x
est simplement transmis lors de la définition d'une méthode Sampler
). De même, SamplerTrivial(x) isa Sampler{eltype(x)}
.
Un autre type d'assistance est actuellement disponible pour d'autres cas, Random.SamplerTag
, mais est considéré comme une API interne et peut se briser à tout moment sans dépréciations appropriées.
Using distinct algorithms for scalar or array generation
Dans certains cas, que l'on souhaite générer seulement quelques valeurs ou un grand nombre de valeurs aura un impact sur le choix de l'algorithme. Cela est géré avec le troisième paramètre du constructeur Sampler
. Supposons que nous ayons défini deux types d'assistance pour Die
, disons SamplerDie1
qui devrait être utilisé pour générer seulement quelques valeurs aléatoires, et SamplerDieMany
pour de nombreuses valeurs. Nous pouvons utiliser ces types comme suit :
Sampler(RNG::Type{<:AbstractRNG}, die::Die, ::Val{1}) = SamplerDie1(...)
Sampler(RNG::Type{<:AbstractRNG}, die::Die, ::Val{Inf}) = SamplerDieMany(...)
Bien sûr, rand
doit également être défini sur ces types (c'est-à-dire rand(::AbstractRNG, ::SamplerDie1)
et rand(::AbstractRNG, ::SamplerDieMany)
). Notez que, comme d'habitude, SamplerTrivial
et SamplerSimple
peuvent être utilisés si des types personnalisés ne sont pas nécessaires.
Remarque : Sampler(rng, x)
est simplement un raccourci pour Sampler(rng, x, Val(Inf))
, et Random.Repetition
est un alias pour Union{Val{1}, Val{Inf}}
.
Creating new generators
L'API n'est pas encore clairement définie, mais en règle générale :
- tout
rand
méthode produisant des types "de base" (isbitstype
entier et types flottants dansBase
) devrait être définie pour ce RNG spécifique, si nécessaire ; - d'autres méthodes
rand
documentées acceptant unAbstractRNG
devraient fonctionner sans problème, (à condition que les méthodes de 1) sur lesquelles on s'appuie soient implémentées), mais peuvent bien sûr être spécialisées pour ce RNG s'il y a de la place pour l'optimisation ; copy
pour les pseudo-GRNs doit renvoyer une copie indépendante qui génère exactement la même séquence aléatoire que l'original à partir du moment où elle est appelée de la même manière. Lorsque cela n'est pas réalisable (par exemple, les GRNs basés sur du matériel),copy
ne doit pas être implémenté.
Concernant 1), une méthode rand
peut fonctionner automatiquement, mais elle n'est pas officiellement supportée et peut cesser de fonctionner sans avertissement dans une version ultérieure.
Pour définir une nouvelle méthode rand
pour un générateur hypothétique MyRNG
, et une spécification de valeur s
(par exemple s == Int
, ou s == 1:10
) de type S==typeof(s)
ou S==Type{s}
si s
est un type, les deux mêmes méthodes que nous avons vues auparavant doivent être définies :
Sampler(::Type{MyRNG}, ::S, ::Repetition)
, qui renvoie un objet de type disonsSamplerS
rand(rng::MyRNG, sp::SamplerS)
Il peut arriver que Sampler(rng::AbstractRNG, ::S, ::Repetition)
soit déjà défini dans le module Random
. Il serait alors possible de sauter l'étape 1) en pratique (si l'on souhaite spécialiser la génération pour ce type de RNG particulier), mais le type correspondant SamplerS
est considéré comme un détail interne et peut être modifié sans avertissement.
Specializing array generation
Dans certains cas, pour un type de générateur de nombres aléatoires donné, générer un tableau de valeurs aléatoires peut être plus efficace avec une méthode spécialisée qu'en utilisant simplement la technique de découplage expliquée précédemment. C'est par exemple le cas pour MersenneTwister
, qui écrit nativement des valeurs aléatoires dans un tableau.
Pour implémenter cette spécialisation pour MyRNG
et pour une spécification s
, produisant des éléments de type S
, la méthode suivante peut être définie : rand!(rng::MyRNG, a::AbstractArray{S}, ::SamplerS)
, où SamplerS
est le type de l'échantillonneur retourné par Sampler(MyRNG, s, Val(Inf))
. Au lieu de AbstractArray
, il est possible d'implémenter la fonctionnalité uniquement pour un sous-type, par exemple Array{S}
. La méthode de tableau non mutante de rand
appellera automatiquement cette spécialisation en interne.
Reproducibility
En utilisant un paramètre RNG initialisé avec une graine donnée, vous pouvez reproduire la même séquence de nombres pseudorandom lorsque vous exécutez votre programme plusieurs fois. Cependant, une version mineure de Julia (par exemple, de 1.3 à 1.4) peut changer la séquence de nombres pseudorandom générés à partir d'une graine spécifique, en particulier si MersenneTwister
est utilisé. (Même si la séquence produite par une fonction de bas niveau comme rand
ne change pas, la sortie de fonctions de niveau supérieur comme randsubseq
peut changer en raison des mises à jour d'algorithme.) Raison : garantir que les flux pseudorandom ne changent jamais interdit de nombreuses améliorations algorithmiques.
Si vous devez garantir la reproductibilité exacte des données aléatoires, il est conseillé de simplement sauvegarder les données (par exemple, en tant que pièce jointe supplémentaire dans une publication scientifique). (Vous pouvez également, bien sûr, spécifier une version particulière de Julia et un manifeste de package, surtout si vous exigez une reproductibilité au bit près.)
Les tests logiciels qui s'appuient sur des données "aléatoires" spécifiques devraient également généralement soit enregistrer les données, les intégrer dans le code de test, soit utiliser des packages tiers comme StableRNGs.jl. D'autre part, les tests qui devraient réussir pour des données aléatoires la plupart du temps (par exemple, tester A \ (A*x) ≈ x
pour une matrice aléatoire A = randn(n,n)
) peuvent utiliser un générateur de nombres aléatoires (RNG) avec une graine fixe pour s'assurer que le simple fait d'exécuter le test plusieurs fois ne rencontre pas un échec en raison de données très improbables (par exemple, une matrice extrêmement mal conditionnée).
La distribution statistique à partir de laquelle des échantillons aléatoires sont tirés est garantie d'être la même à travers toutes les petites versions de Julia.