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

Warning

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

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

  • 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 de BigInt qui n'est pas supporté)
    • les types de nombres à virgule flottante concrets échantillonnent de [0, 1)
    • les types complexes concrets Complex{T} si T 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 si T 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 type S. Notez que les types de tuple tels que Tuple{Vararg{T}} (taille inconnue) et Tuple{1:2} (paramétré avec une valeur) ne sont pas supportés
    • un type Pair, par exemple Pair{X, Y} tel que rand est défini pour X et Y, auquel cas des paires aléatoires sont produites.

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.

Julia 1.1

Le support de S en tant que tuple nécessite au moins Julia 1.1.

Julia 1.11

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
Note

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

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

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.

source

Subsequences, permutations and shuffling

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

Julia 1.1

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

Julia 1.1

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

Generators (creation and seeding)

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

Note

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.

Julia 1.3

Cette fonction a été introduite dans Julia 1.3.

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

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.

Julia 1.11

L'initialisation de TaskLocalRNG() avec une graine entière négative nécessite au moins Julia 1.11.

Julia 1.10

La création de tâches n'avance plus l'état du RNG de la tâche parente depuis Julia 1.10.

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

Julia 1.11

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

Julia 1.11

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

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.

source

Hooking into the Random API

Il existe deux manières principalement orthogonales d'étendre les fonctionnalités de Random :

  1. génération de valeurs aléatoires de types personnalisés
  2. 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 :

  1. SamplerType{T}() peut être utilisé pour implémenter des échantillonneurs qui tirent du type T (par exemple, rand(Int)). C'est le type par défaut retourné par Sampler pour les types.

  2. SamplerTrivial(self) est un simple wrapper pour self, 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 par Sampler pour valeurs.

  3. SamplerSimple(self, data) contient également le champ data 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 de Sampler.

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.SamplerType
Sampler(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.

source
Random.SamplerTypeType
SamplerType{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.

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

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

source

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}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 :

  1. tout rand méthode produisant des types "de base" (isbitstype entier et types flottants dans Base) devrait être définie pour ce RNG spécifique, si nécessaire ;
  2. d'autres méthodes rand documentées acceptant un AbstractRNG 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 ;
  3. 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 :

  1. Sampler(::Type{MyRNG}, ::S, ::Repetition), qui renvoie un objet de type disons SamplerS
  2. 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.