Random Numbers
La generación de números aleatorios en Julia utiliza el algoritmo Xoshiro256++ por defecto, con estado por Task
. Otros tipos de RNG se pueden integrar heredando el tipo AbstractRNG
; luego se pueden usar para obtener múltiples flujos de números aleatorios.
Los PRNGs (generadores de números seudoaleatorios) exportados por el paquete Random
son:
TaskLocalRNG
: un token que representa el uso del flujo local de tarea actualmente activo, sembrado de manera determinista a partir de la tarea padre, o porRandomDevice
(con aleatoriedad del sistema) al inicio del programa.Xoshiro
: genera un flujo de números aleatorios de alta calidad con un pequeño vector de estado y un alto rendimiento utilizando el algoritmo Xoshiro256++RandomDevice
: para la entropía proporcionada por el sistema operativo. Esto puede ser utilizado para números aleatorios criptográficamente seguros (CS(P)RNG).MersenneTwister
: un generador de números aleatorios de alta calidad alternativo que era el predeterminado en versiones anteriores de Julia, y también es bastante rápido, pero requiere mucho más espacio para almacenar el vector de estado y generar una secuencia aleatoria.
La mayoría de las funciones relacionadas con la generación aleatoria aceptan un objeto AbstractRNG
opcional como primer argumento. Algunas también aceptan especificaciones de dimensiones dims...
(que también se pueden dar como una tupla) para generar arreglos de valores aleatorios. En un programa multihilo, generalmente deberías usar diferentes objetos RNG de diferentes hilos o tareas para ser seguro en cuanto a hilos. Sin embargo, el RNG predeterminado es seguro para hilos a partir de Julia 1.3 (utilizando un RNG por hilo hasta la versión 1.6, y por tarea a partir de entonces).
Los RNG proporcionados pueden generar números aleatorios uniformes de los siguientes tipos: Float16
, Float32
, Float64
, BigFloat
, Bool
, Int8
, UInt8
, Int16
, UInt16
, Int32
, UInt32
, Int64
, UInt64
, Int128
, UInt128
, BigInt
(o números complejos de esos tipos). Los números de punto flotante aleatorios se generan uniformemente en $[0, 1)$. Como BigInt
representa enteros sin límites, el intervalo debe ser especificado (por ejemplo, rand(big.(1:6))
).
Además, las distribuciones normal y exponencial están implementadas para algunos tipos de AbstractFloat
y Complex
, consulta randn
y randexp
para más detalles.
Para generar números aleatorios de otras distribuciones, consulte el paquete Distributions.jl.
Debido a que la forma precisa en que se generan los números aleatorios se considera un detalle de implementación, las correcciones de errores y las mejoras de velocidad pueden cambiar la secuencia de números que se generan después de un cambio de versión. Por lo tanto, se desaconseja confiar en una semilla específica o en una secuencia generada de números durante las pruebas unitarias; en su lugar, considere probar las propiedades de los métodos en cuestión.
Random numbers module
Random.Random
— ModuleAleatorio
Soporte para generar números aleatorios. Proporciona rand
, randn
, AbstractRNG
, MersenneTwister
y RandomDevice
.
Random generation functions
Base.rand
— Functionrand([rng=default_rng()], [S], [dims...])
Elige un elemento aleatorio o un arreglo de elementos aleatorios del conjunto de valores especificado por S
; S
puede ser
una colección indexable (por ejemplo
1:9
o('x', "y", :z)
)un objeto
AbstractDict
oAbstractSet
una cadena (considerada como una colección de caracteres), o
un tipo de la lista a continuación, correspondiente al conjunto de valores especificado
- tipos de enteros concretos muestrean de
typemin(S):typemax(S)
(exceptoBigInt
que no es compatible) - tipos de punto flotante concretos muestrean de
[0, 1)
- tipos complejos concretos
Complex{T}
siT
es un tipo muestreable toman sus componentes reales e imaginarias independientemente del conjunto de valores correspondiente aT
, pero no son compatibles siT
no es muestreable. - todos los tipos
<:AbstractChar
muestrean del conjunto de escalares Unicode válidos - un tipo definido por el usuario y conjunto de valores; para orientación sobre la implementación, consulte Hooking into the
Random
API - un tipo de tupla de tamaño conocido y donde cada parámetro de
S
es en sí mismo un tipo muestreable; devuelve un valor del tipoS
. Tenga en cuenta que tipos de tupla comoTuple{Vararg{T}}
(tamaño desconocido) yTuple{1:2}
(parametrizado con un valor) no son compatibles - un tipo
Pair
, por ejemploPair{X, Y}
tal querand
está definido paraX
yY
, en cuyo caso se producen pares aleatorios.
- tipos de enteros concretos muestrean de
S
por defecto es Float64
. Cuando solo se pasa un argumento además del opcional rng
y es un Tuple
, se interpreta como una colección de valores (S
) y no como dims
.
Vea también randn
para números distribuidos normalmente, y rand!
y randn!
para los equivalentes en su lugar.
El soporte para S
como una tupla requiere al menos Julia 1.1.
El soporte para S
como un tipo Tuple
requiere al menos Julia 1.11.
Ejemplos
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 complejidad de rand(rng, s::Union{AbstractDict,AbstractSet})
es lineal en la longitud de s
, a menos que un método optimizado con complejidad constante esté disponible, que es el caso para Dict
, Set
y BitSet
s densos. Para más de unas pocas llamadas, use rand(rng, collect(s))
en su lugar, o bien rand(rng, Dict(s))
o rand(rng, Set(s))
según corresponda.
Random.rand!
— Functionrand!([rng=default_rng()], A, [S=eltype(A)])
Puebla el arreglo A
con valores aleatorios. Si S
se especifica (S
puede ser un tipo o una colección, cf. rand
para más detalles), los valores se eligen aleatoriamente de S
. Esto es equivalente a copyto!(A, rand(rng, S, size(A)))
pero sin asignar un nuevo arreglo.
Ejemplos
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...])
Genera un BitArray
de valores booleanos aleatorios.
Ejemplos
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...])
Genera un número aleatorio distribuido normalmente de tipo T
con media 0 y desviación estándar 1. Dado el argumento opcional dims
, genera un arreglo de tamaño dims
de tales números. La biblioteca estándar de Julia soporta randn
para cualquier tipo de punto flotante que implemente rand
, por ejemplo, los tipos de Base
Float16
, Float32
, Float64
(el predeterminado) y BigFloat
, junto con sus contrapartes Complex
.
(Cuando T
es complejo, los valores se extraen de la distribución normal compleja circularmente simétrica de varianza 1, correspondiente a partes real e imaginaria que tienen una distribución normal independiente con media cero y varianza 1/2
).
Consulta también randn!
para actuar en su lugar.
Ejemplos
Generando un solo número aleatorio (con el tipo predeterminado Float64
):
julia> randn()
-0.942481877315864
Generando una matriz de números aleatorios normales (con el tipo predeterminado Float64
):
julia> randn(2,3)
2×3 Matrix{Float64}:
1.18786 -0.678616 1.49463
-0.342792 -0.134299 -1.45005
Configurando el generador de números aleatorios rng
con una semilla definida por el usuario (para números reproducibles) y usándolo para generar un número aleatorio Float32
o una matriz de números aleatorios 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
Llena el arreglo A
con números aleatorios distribuidos normalmente (media 0, desviación estándar 1). También consulta la función rand
.
Ejemplos
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...])
Genera un número aleatorio del tipo T
de acuerdo con la distribución exponencial con escala 1. Opcionalmente, genera un arreglo de tales números aleatorios. El módulo Base
actualmente proporciona una implementación para los tipos Float16
, Float32
y Float64
(el predeterminado).
Ejemplos
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
Llena el arreglo A
con números aleatorios siguiendo la distribución exponencial (con escala 1).
Ejemplos
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])
Crea una cadena aleatoria de longitud len
, que consiste en caracteres de chars
, que por defecto es el conjunto de letras mayúsculas y minúsculas y los dígitos 0-9. El argumento opcional rng
especifica un generador de números aleatorios, consulta Random Numbers.
Ejemplos
julia> Random.seed!(3); randstring()
"Lxz5hUwn"
julia> randstring(Xoshiro(3), 'a':'z', 6)
"iyzcsm"
julia> randstring("ACGT")
"TGCTCCTC"
chars
puede ser cualquier colección de caracteres, de tipo Char
o UInt8
(más eficiente), siempre que rand
pueda seleccionar caracteres aleatoriamente de ella.
Subsequences, permutations and shuffling
Random.randsubseq
— Functionrandsubseq([rng=default_rng(),] A, p) -> Vector
Devuelve un vector que consiste en una subsecuencia aleatoria del arreglo dado A
, donde cada elemento de A
se incluye (en orden) con una probabilidad independiente p
. (La complejidad es lineal en p*length(A)
, por lo que esta función es eficiente incluso si p
es pequeño y A
es grande). Técnicamente, este proceso se conoce como "muestreo de Bernoulli" de A
.
Ejemplos
julia> randsubseq(Xoshiro(123), 1:8, 0.3)
2-element Vector{Int64}:
4
7
Random.randsubseq!
— Functionrandsubseq!([rng=default_rng(),] S, A, p)
Como randsubseq
, pero los resultados se almacenan en S
(que se redimensiona según sea necesario).
Ejemplos
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)
Construye una permutación aleatoria de longitud n
. El argumento opcional rng
especifica un generador de números aleatorios (ver Números Aleatorios). El tipo de elemento del resultado es el mismo que el tipo de n
.
Para permutar aleatoriamente un vector arbitrario, consulta shuffle
o shuffle!
.
En Julia 1.1 randperm
devuelve un vector v
con eltype(v) == typeof(n)
mientras que en Julia 1.0 eltype(v) == Int
.
Ejemplos
julia> randperm(Xoshiro(123), 4)
4-element Vector{Int64}:
1
4
2
3
Random.randperm!
— Functionrandperm!([rng=default_rng(),] A::Array{<:Integer})
Construye en A
una permutación aleatoria de longitud length(A)
. El argumento opcional rng
especifica un generador de números aleatorios (ver Números Aleatorios). Para permutar aleatoriamente un vector arbitrario, consulta shuffle
o shuffle!
.
Ejemplos
julia> randperm!(Xoshiro(123), Vector{Int}(undef, 4))
4-element Vector{Int64}:
1
4
2
3
Random.randcycle
— Functionrandcycle([rng=default_rng(),] n::Integer)
Construye una permutación cíclica aleatoria de longitud n
. El argumento opcional rng
especifica un generador de números aleatorios, consulta Random Numbers. El tipo de elemento del resultado es el mismo que el tipo de n
.
Aquí, una "permutación cíclica" significa que todos los elementos están dentro de un solo ciclo. Si n > 0
, hay $(n-1)!$ posibles permutaciones cíclicas, que se muestrean uniformemente. Si n == 0
, randcycle
devuelve un vector vacío.
randcycle!
es una variante en su lugar de esta función.
En Julia 1.1 y versiones posteriores, randcycle
devuelve un vector v
con eltype(v) == typeof(n)
mientras que en Julia 1.0 eltype(v) == Int
.
Ejemplos
julia> randcycle(Xoshiro(123), 6)
6-element Vector{Int64}:
5
4
2
6
3
1
Random.randcycle!
— Functionrandcycle!([rng=default_rng(),] A::Array{<:Integer})
Construye en A
una permutación cíclica aleatoria de longitud n = length(A)
. El argumento opcional rng
especifica un generador de números aleatorios, consulta Números Aleatorios.
Aquí, una "permutación cíclica" significa que todos los elementos se encuentran dentro de un solo ciclo. Si A
no está vacío (n > 0
), hay $(n-1)!$ posibles permutaciones cíclicas, que se muestrean de manera uniforme. Si A
está vacío, randcycle!
lo deja sin cambios.
randcycle
es una variante de esta función que asigna un nuevo vector.
Ejemplos
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)
Devuelve una copia de v
aleatoriamente permutada. El argumento opcional rng
especifica un generador de números aleatorios (ver Números Aleatorios). Para permutar v
en su lugar, consulta shuffle!
. Para obtener índices aleatoriamente permutados, consulta randperm
.
Ejemplos
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)
Versión en su lugar de shuffle
: permuta aleatoriamente v
en su lugar, opcionalmente suministrando el generador de números aleatorios rng
.
Ejemplos
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
Devuelve el generador de números aleatorios (RNG) global predeterminado, que es utilizado por las funciones relacionadas con rand
cuando no se proporciona un RNG explícito.
Cuando se carga el módulo Random
, el RNG predeterminado se inicializa aleatoriamente, a través de Random.seed!()
: esto significa que cada vez que se inicia una nueva sesión de julia, la primera llamada a rand()
produce un resultado diferente, a menos que se llame primero a seed!(seed)
.
Es seguro para hilos: hilos distintos pueden llamar de manera segura a funciones relacionadas con rand
en default_rng()
de forma concurrente, por ejemplo, rand(default_rng())
.
El tipo del RNG predeterminado es un detalle de implementación. A través de diferentes versiones de Julia, no deberías esperar que el RNG predeterminado siempre tenga el mismo tipo, ni que produzca la misma secuencia de números aleatorios para una semilla dada.
Esta función fue introducida en Julia 1.3.
Random.seed!
— Functionseed!([rng=default_rng()], seed) -> rng
seed!([rng=default_rng()]) -> rng
Reinicializa el generador de números aleatorios: rng
dará una secuencia reproducible de números si y solo si se proporciona una semilla
. Algunos RNG no aceptan una semilla, como RandomDevice
. Después de la llamada a seed!
, rng
es equivalente a un objeto recién creado inicializado con la misma semilla. Los tipos de semillas aceptadas dependen del tipo de rng
, pero en general, las semillas enteras deberían funcionar.
Si rng
no se especifica, se inicializa el estado del generador local compartido por defecto.
Ejemplos
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) # no reproducible
true
julia> rand(Random.seed!(rng), Bool) # tampoco reproducible
false
julia> rand(Xoshiro(), Bool) # tampoco reproducible
true
Random.AbstractRNG
— TypeAbstractRNG
Supertipo para generadores de números aleatorios como MersenneTwister
y RandomDevice
.
Random.TaskLocalRNG
— TypeTaskLocalRNG
El TaskLocalRNG
tiene un estado que es local a su tarea, no a su hilo. Se inicializa al crear la tarea, a partir del estado de su tarea padre, pero sin avanzar el estado del RNG del padre.
Como ventaja, el TaskLocalRNG
es bastante rápido y permite simulaciones multihilo reproducibles (salvo condiciones de carrera), independientes de las decisiones del programador. Siempre que el número de hilos no se utilice para tomar decisiones sobre la creación de tareas, los resultados de la simulación también son independientes del número de hilos / CPUs disponibles. La secuencia aleatoria no debería depender de especificaciones de hardware, hasta el endianness y posiblemente el tamaño de palabra.
Usar o inicializar el RNG de cualquier otra tarea que no sea la devuelta por current_task()
es un comportamiento indefinido: funcionará la mayor parte del tiempo y puede fallar silenciosamente en ocasiones.
Al inicializar TaskLocalRNG()
con seed!
, la semilla pasada, si la hay, puede ser cualquier entero.
Inicializar TaskLocalRNG()
con una semilla entera negativa requiere al menos Julia 1.11.
La creación de tareas ya no avanza el estado del RNG de la tarea padre a partir de Julia 1.10.
Random.Xoshiro
— TypeXoshiro(seed::Union{Integer, AbstractString})
Xoshiro()
Xoshiro256++ es un generador de números pseudorandom rápidos descrito por David Blackman y Sebastiano Vigna en "Scrambled Linear Pseudorandom Number Generators", ACM Trans. Math. Softw., 2021. La implementación de referencia está disponible en https://prng.di.unimi.it
Aparte de la alta velocidad, Xoshiro tiene una pequeña huella de memoria, lo que lo hace adecuado para aplicaciones donde se necesitan mantener muchos estados aleatorios diferentes durante mucho tiempo.
La implementación de Xoshiro en Julia tiene un modo de generación masiva; esto siembra nuevos PRNG virtuales desde el padre y utiliza SIMD para generar en paralelo (es decir, el flujo masivo consiste en múltiples instancias de xoshiro entrelazadas). Los PRNG virtuales se descartan una vez que se ha atendido la solicitud masiva (y no deberían causar asignaciones en el heap).
Si no se proporciona una semilla, se crea una generada aleatoriamente (utilizando la entropía del sistema). Consulte la función seed!
para volver a sembrar un objeto Xoshiro
ya existente.
Pasar una semilla entera negativa requiere al menos Julia 1.11.
Ejemplos
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()
Crea un objeto RNG MersenneTwister
. Diferentes objetos RNG pueden tener sus propias semillas, lo que puede ser útil para generar diferentes secuencias de números aleatorios. La seed
puede ser un entero, una cadena o un vector de enteros UInt32
. Si no se proporciona una semilla, se crea una generada aleatoriamente (utilizando la entropía del sistema). Consulta la función seed!
para volver a sembrar un objeto MersenneTwister
ya existente.
Pasar una semilla entera negativa requiere al menos Julia 1.11.
Ejemplos
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()
Crea un objeto RNG RandomDevice
. Dos de estos objetos siempre generarán flujos diferentes de números aleatorios. La entropía se obtiene del sistema operativo.
Hooking into the Random
API
Hay dos formas mayormente ortogonales de extender las funcionalidades de Random
:
- generando valores aleatorios de tipos personalizados
- creando nuevos generadores
La API para 1) es bastante funcional, pero es relativamente reciente, por lo que aún puede tener que evolucionar en lanzamientos posteriores del módulo Random
. Por ejemplo, generalmente es suficiente implementar un método rand
para que todos los demás métodos habituales funcionen automáticamente.
La API para 2) sigue siendo rudimentaria y puede requerir más trabajo del estrictamente necesario por parte del implementador, para poder soportar los tipos habituales de valores generados.
Generating random values of custom types
Generar valores aleatorios para algunas distribuciones puede implicar varios compromisos. Valores precomputados, como un alias table para distribuciones discretas, o “squeezing” functions para distribuciones univariadas, pueden acelerar considerablemente el muestreo. Cuánta información debe ser precomputada puede depender del número de valores que planeamos extraer de una distribución. Además, algunos generadores de números aleatorios pueden tener ciertas propiedades que varios algoritmos pueden querer explotar.
El módulo Random
define un marco personalizable para obtener valores aleatorios que puede abordar estos problemas. Cada invocación de rand
genera un muestreador que se puede personalizar teniendo en cuenta los compromisos mencionados anteriormente, añadiendo métodos a Sampler
, que a su vez puede despachar sobre el generador de números aleatorios, el objeto que caracteriza la distribución y una sugerencia para el número de repeticiones. Actualmente, para este último, se utilizan Val{1}
(para una sola muestra) y Val{Inf}
(para un número arbitrario), siendo Random.Repetition
un alias para ambos.
El objeto devuelto por Sampler
se utiliza para generar los valores aleatorios. Al implementar la interfaz de generación aleatoria para un valor X
que se puede muestrear, el implementador debe definir el método
rand(rng, sampler)
para el sampler
particular devuelto por Sampler(rng, X, repetition)
.
Los muestreadores pueden ser valores arbitrarios que implementan rand(rng, sampler)
, pero para la mayoría de las aplicaciones, los siguientes muestreadores predefinidos pueden ser suficientes:
SamplerType{T}()
se puede utilizar para implementar muestreadores que extraen del tipoT
(por ejemplo,rand(Int)
). Este es el valor predeterminado devuelto porSampler
para tipos.SamplerTrivial(self)
es un simple envoltorio paraself
, que se puede acceder con[]
. Este es el muestreador recomendado cuando no se necesita información precomputada (por ejemplo,rand(1:3)
), y es el valor predeterminado devuelto porSampler
para valores.SamplerSimple(self, data)
también contiene el campo adicionaldata
, que se puede utilizar para almacenar valores precomputados arbitrarios, que deben ser calculados en un método personalizado deSampler
.
Proporcionamos ejemplos para cada uno de estos. Asumimos aquí que la elección del algoritmo es independiente del RNG, por lo que usamos AbstractRNG
en nuestras firmas.
Random.Sampler
— TypeSampler(rng, x, repetition = Val(Inf))
Devuelve un objeto sampler que se puede usar para generar valores aleatorios de rng
para x
.
Cuando sp = Sampler(rng, x, repetition)
, rand(rng, sp)
se utilizará para extraer valores aleatorios y debe definirse en consecuencia.
repetition
puede ser Val(1)
o Val(Inf)
, y debe usarse como una sugerencia para decidir la cantidad de precomputación, si corresponde.
Random.SamplerType
y Random.SamplerTrivial
son retrocesos predeterminados para tipos y valores, respectivamente. Random.SamplerSimple
se puede usar para almacenar valores precomputados sin definir tipos adicionales solo para este propósito.
Random.SamplerType
— TypeSamplerType{T}()
Un muestreador para tipos, que no contiene otra información. La opción predeterminada para Sampler
cuando se llama con tipos.
Random.SamplerTrivial
— TypeSamplerTrivial(x)
Crea un muestreador que simplemente envuelve el valor dado x
. Este es el valor predeterminado de reserva para valores. El eltype
de este muestreador es igual a eltype(x)
.
El caso de uso recomendado es muestrear de valores sin datos precomputados.
Random.SamplerSimple
— TypeSamplerSimple(x, data)
Crea un muestreador que envuelve el valor dado x
y los data
. El eltype
de este muestreador es igual a eltype(x)
.
El caso de uso recomendado es muestrear de valores con datos precomputados.
Desacoplar la precomputación de la generación real de los valores es parte de la API y también está disponible para el usuario. Como ejemplo, supongamos que rand(rng, 1:20)
tiene que ser llamado repetidamente en un bucle: la forma de aprovechar este desacoplamiento es la siguiente:
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
Este es el mecanismo que también se utiliza en la biblioteca estándar, por ejemplo, en la implementación predeterminada de generación de arreglos aleatorios (como en rand(1:20, 10)
).
Generating values from a type
Dado un tipo T
, actualmente se asume que si rand(T)
está definido, se producirá un objeto del tipo T
. SamplerType
es el muestreador predeterminado para tipos. Para definir la generación aleatoria de valores del tipo T
, se debe definir el método rand(rng::AbstractRNG, ::Random.SamplerType{T})
, y debe devolver valores que se espera que devuelva rand(rng, T)
.
Tomemos el siguiente ejemplo: implementamos un tipo Die
, con un número variable n
de lados, numerados del 1
al n
. Queremos que rand(Die)
produzca un Die
con un número aleatorio de hasta 20 lados (y al menos 4):
struct Die
nsides::Int # number of sides
end
Random.rand(rng::AbstractRNG, ::Random.SamplerType{Die}) = Die(rand(rng, 4:20))
# output
Los métodos escalares y de matriz para Die
ahora funcionan como se esperaba:
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
Aquí definimos un muestreador para una colección. Si no se requieren datos precomputados, se puede implementar con un muestreador SamplerTrivial
, que de hecho es el fallback predeterminado para valores.
Para definir la generación aleatoria a partir de objetos del tipo S
, se debe definir el siguiente método: rand(rng::AbstractRNG, sp::Random.SamplerTrivial{S})
. Aquí, sp
simplemente envuelve un objeto del tipo S
, que se puede acceder a través de sp[]
. Continuando con el ejemplo del Die
, ahora queremos definir rand(d::Die)
para producir un Int
correspondiente a uno de los lados 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
Dada un tipo de colección S
, actualmente se asume que si rand(::S)
está definido, se producirá un objeto del tipo eltype(S)
. En el último ejemplo, se produce un Vector{Any}
; la razón es que eltype(Die) == Any
. La solución es definir Base.eltype(::Type{Die}) = Int
.
Generating values for an AbstractFloat
type
Los tipos AbstractFloat
son tratados de manera especial, porque por defecto no se producen valores aleatorios en todo el dominio del tipo, sino más bien en [0,1)
. El siguiente método debe ser implementado para T <: AbstractFloat
: Random.rand(::AbstractRNG, ::Random.SamplerTrivial{Random.CloseOpen01{T}})
An optimized sampler with pre-computed data
Considere una distribución discreta, donde los números 1:n
se extraen con probabilidades dadas que suman uno. Cuando se necesitan muchos valores de esta distribución, el método más rápido es usar un alias table. No proporcionamos el algoritmo para construir tal tabla aquí, pero supongamos que está disponible en make_alias_table(probabilities)
en su lugar, y draw_number(rng, alias_table)
se puede usar para extraer un número aleatorio de ella.
Supongamos que la distribución está descrita por
struct DiscreteDistribution{V <: AbstractVector}
probabilities::V
end
y que siempre queremos construir una tabla de alias, independientemente del número de valores necesarios (aprendemos a personalizar esto a continuación). Los métodos
Random.eltype(::Type{<:DiscreteDistribution}) = Int
function Random.Sampler(::Type{<:AbstractRNG}, distribution::DiscreteDistribution, ::Repetition)
SamplerSimple(distribution, make_alias_table(distribution.probabilities))
end
debe definirse para devolver un muestreador con datos precomputados, luego
function rand(rng::AbstractRNG, sp::SamplerSimple{<:DiscreteDistribution})
draw_number(rng, sp.data)
end
se utilizará para dibujar los valores.
Custom sampler types
El tipo SamplerSimple
es suficiente para la mayoría de los casos de uso con datos precomputados. Sin embargo, para demostrar cómo usar tipos de muestreador personalizados, aquí implementamos algo similar a SamplerSimple
.
Volviendo a nuestro ejemplo de Die
: rand(::Die)
utiliza generación aleatoria de un rango, por lo que hay una oportunidad para esta optimización. Llamamos a nuestro muestreador personalizado 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)
Ahora es posible obtener un muestreador con sp = Sampler(rng, die)
, y usar sp
en lugar de die
en cualquier llamada a rand
que involucre rng
. En el ejemplo simplista anterior, die
no necesita ser almacenado en SamplerDie
, pero esto es a menudo el caso en la práctica.
Por supuesto, este patrón es tan frecuente que el tipo de ayudante utilizado arriba, a saber, Random.SamplerSimple
, está disponible, ahorrándonos la definición de SamplerDie
: podríamos haber implementado nuestro desacoplamiento con:
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)
Aquí, sp.data
se refiere al segundo parámetro en la llamada al constructor SamplerSimple
(en este caso igual a Sampler(rng, 1:die.nsides, r)
), mientras que el objeto Die
se puede acceder a través de sp[]
.
Como SamplerDie
, cualquier muestreador personalizado debe ser un subtipo de Sampler{T}
donde T
es el tipo de los valores generados. Tenga en cuenta que SamplerSimple(x, data) isa Sampler{eltype(x)}
, por lo que esto restringe lo que el primer argumento de SamplerSimple
puede ser (se recomienda usar SamplerSimple
como en el ejemplo de Die
, donde x
se reenvía simplemente al definir un método Sampler
). De manera similar, SamplerTrivial(x) isa Sampler{eltype(x)}
.
Otro tipo de ayudante está actualmente disponible para otros casos, Random.SamplerTag
, pero se considera una API interna y puede romperse en cualquier momento sin las debidas deprecaciones.
Using distinct algorithms for scalar or array generation
En algunos casos, si uno quiere generar solo un puñado de valores o un gran número de valores tendrá un impacto en la elección del algoritmo. Esto se maneja con el tercer parámetro del constructor Sampler
. Supongamos que definimos dos tipos auxiliares para Die
, digamos SamplerDie1
que debería usarse para generar solo unos pocos valores aleatorios, y SamplerDieMany
para muchos valores. Podemos usar esos tipos de la siguiente manera:
Sampler(RNG::Type{<:AbstractRNG}, die::Die, ::Val{1}) = SamplerDie1(...)
Sampler(RNG::Type{<:AbstractRNG}, die::Die, ::Val{Inf}) = SamplerDieMany(...)
Por supuesto, rand
también debe estar definido en esos tipos (es decir, rand(::AbstractRNG, ::SamplerDie1)
y rand(::AbstractRNG, ::SamplerDieMany)
). Tenga en cuenta que, como de costumbre, SamplerTrivial
y SamplerSimple
se pueden usar si no son necesarios tipos personalizados.
Nota: Sampler(rng, x)
es simplemente una abreviatura para Sampler(rng, x, Val(Inf))
, y Random.Repetition
es un alias para Union{Val{1}, Val{Inf}}
.
Creating new generators
La API aún no está claramente definida, pero como regla general:
- cualquier método
rand
que produzca tipos "básicos" (isbitstype
tipos enteros y de punto flotante enBase
) debe definirse para este RNG específico, si son necesarios; - otros métodos documentados de
rand
que aceptan unAbstractRNG
deberían funcionar sin problemas, (siempre que se implementen los métodos de 1) de los que se depende), pero por supuesto pueden ser especializados para este RNG si hay espacio para optimización; copy
para pseudo-RNGs debe devolver una copia independiente que genere la misma secuencia aleatoria que el original a partir de ese punto cuando se llame de la misma manera. Cuando esto no sea factible (por ejemplo, RNGs basados en hardware),copy
no debe ser implementado.
Con respecto a 1), un método rand
puede funcionar automáticamente, pero no está oficialmente soportado y puede romperse sin advertencias en una versión posterior.
Para definir un nuevo método rand
para un generador hipotético MyRNG
, y una especificación de valor s
(por ejemplo, s == Int
, o s == 1:10
) de tipo S==typeof(s)
o S==Type{s}
si s
es un tipo, se deben definir los mismos dos métodos que vimos antes:
Sampler(::Tipo{MyRNG}, ::S, ::Repetición)
, que devuelve un objeto de tipo, digamos,SamplerS
rand(rng::MyRNG, sp::SamplerS)
Puede suceder que Sampler(rng::AbstractRNG, ::S, ::Repetition)
ya esté definido en el módulo Random
. En ese caso, sería posible omitir el paso 1) en la práctica (si se desea especializar la generación para este tipo particular de RNG), pero el tipo correspondiente SamplerS
se considera un detalle interno y puede cambiar sin previo aviso.
Specializing array generation
En algunos casos, para un tipo de RNG dado, generar un array de valores aleatorios puede ser más eficiente con un método especializado que simplemente utilizando la técnica de desacoplamiento explicada anteriormente. Este es, por ejemplo, el caso de MersenneTwister
, que escribe de forma nativa valores aleatorios en un array.
Para implementar esta especialización para MyRNG
y para una especificación s
, produciendo elementos del tipo S
, se puede definir el siguiente método: rand!(rng::MyRNG, a::AbstractArray{S}, ::SamplerS)
, donde SamplerS
es el tipo del muestreador devuelto por Sampler(MyRNG, s, Val(Inf))
. En lugar de AbstractArray
, es posible implementar la funcionalidad solo para un subtipo, por ejemplo, Array{S}
. El método de array no mutante de rand
llamará automáticamente a esta especialización internamente.
Reproducibility
Al utilizar un parámetro RNG inicializado con una semilla dada, puedes reproducir la misma secuencia de números pseudorandom cuando ejecutas tu programa múltiples veces. Sin embargo, una versión menor de Julia (por ejemplo, de 1.3 a 1.4) puede cambiar la secuencia de números pseudorandom generados a partir de una semilla específica, en particular si se utiliza MersenneTwister
. (Incluso si la secuencia producida por una función de bajo nivel como rand
no cambia, la salida de funciones de nivel superior como randsubseq
puede cambiar debido a actualizaciones de algoritmos.) Razonamiento: garantizar que los flujos pseudorandom nunca cambien prohíbe muchas mejoras algorítmicas.
Si necesitas garantizar la reproducibilidad exacta de datos aleatorios, es aconsejable guardar los datos (por ejemplo, como un archivo adjunto suplementario en una publicación científica). (También puedes, por supuesto, especificar una versión particular de Julia y un manifiesto de paquetes, especialmente si requieres reproducibilidad a nivel de bits.)
Las pruebas de software que dependen de datos "aleatorios" específicos también deberían, en general, guardar los datos, incrustarlos en el código de prueba o utilizar paquetes de terceros como StableRNGs.jl. Por otro lado, las pruebas que deberían pasar para la mayoría de los datos aleatorios (por ejemplo, probar A \ (A*x) ≈ x
para una matriz aleatoria A = randn(n,n)
) pueden usar un generador de números aleatorios (RNG) con una semilla fija para asegurar que simplemente ejecutar la prueba muchas veces no encuentre un fallo debido a datos muy improbables (por ejemplo, una matriz extremadamente mal condicionada).
La distribución estadística de la cual se extraen muestras aleatorias está garantizada que sea la misma en cualquier versión menor de Julia.