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

Warning

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

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

  • 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) (excepto BigInt que no es compatible)
    • tipos de punto flotante concretos muestrean de [0, 1)
    • tipos complejos concretos Complex{T} si T es un tipo muestreable toman sus componentes reales e imaginarias independientemente del conjunto de valores correspondiente a T, pero no son compatibles si T 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 tipo S. Tenga en cuenta que tipos de tupla como Tuple{Vararg{T}} (tamaño desconocido) y Tuple{1:2} (parametrizado con un valor) no son compatibles
    • un tipo Pair, por ejemplo Pair{X, Y} tal que rand está definido para X y Y, en cuyo caso se producen pares aleatorios.

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.

Julia 1.1

El soporte para S como una tupla requiere al menos Julia 1.1.

Julia 1.11

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
Note

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

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

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.

source

Subsequences, permutations and shuffling

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

Julia 1.1

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

Julia 1.1

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

Generators (creation and seeding)

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

Nota

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.

Julia 1.3

Esta función fue introducida en Julia 1.3.

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

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.

Julia 1.11

Inicializar TaskLocalRNG() con una semilla entera negativa requiere al menos Julia 1.11.

Julia 1.10

La creación de tareas ya no avanza el estado del RNG de la tarea padre a partir de Julia 1.10.

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

Julia 1.11

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

Julia 1.11

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

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.

source

Hooking into the Random API

Hay dos formas mayormente ortogonales de extender las funcionalidades de Random:

  1. generando valores aleatorios de tipos personalizados
  2. 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:

  1. SamplerType{T}() se puede utilizar para implementar muestreadores que extraen del tipo T (por ejemplo, rand(Int)). Este es el valor predeterminado devuelto por Sampler para tipos.
  2. SamplerTrivial(self) es un simple envoltorio para self, 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 por Sampler para valores.
  3. SamplerSimple(self, data) también contiene el campo adicional data, que se puede utilizar para almacenar valores precomputados arbitrarios, que deben ser calculados en un método personalizado de Sampler.

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

source
Random.SamplerTypeType
SamplerType{T}()

Un muestreador para tipos, que no contiene otra información. La opción predeterminada para Sampler cuando se llama con tipos.

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

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

source

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:

  1. cualquier método rand que produzca tipos "básicos" (isbitstype tipos enteros y de punto flotante en Base) debe definirse para este RNG específico, si son necesarios;
  2. otros métodos documentados de rand que aceptan un AbstractRNG 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;
  3. 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:

  1. Sampler(::Tipo{MyRNG}, ::S, ::Repetición), que devuelve un objeto de tipo, digamos, SamplerS
  2. 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.