Random Numbers
Генерация случайных чисел в Julia по умолчанию использует алгоритм Xoshiro256++, с состоянием на уровне Task
. Другие типы генераторов случайных чисел могут быть подключены, унаследовав тип AbstractRNG
; их затем можно использовать для получения нескольких потоков случайных чисел.
Генераторы псевдослучайных чисел (PRNG), экспортируемые пакетом Random
, это:
TaskLocalRNG
: токен, который представляет использование текущего активного потока, локального для задачи, детерминированно инициализированного от родительской задачи или с помощьюRandomDevice
(с системной случайностью) при запуске программыXoshiro
: генерирует высококачественный поток случайных чисел с небольшим вектором состояния и высокой производительностью, используя алгоритм Xoshiro256++RandomDevice
: для предоставленной ОС энтропии. Это может быть использовано для криптографически безопасных случайных чисел (CS(P)RNG).MersenneTwister
: альтернативный высококачественный ПСГ, который был по умолчанию в старых версиях Julia и также довольно быстр, но требует гораздо больше места для хранения вектор состояния и генерации случайной последовательности.
Большинство функций, связанных с генерацией случайных чисел, принимают необязательный объект AbstractRNG
в качестве первого аргумента. Некоторые также принимают спецификации размерности dims...
(которые также могут быть заданы в виде кортежа) для генерации массивов случайных значений. В многопоточной программе вам обычно следует использовать разные объекты RNG из разных потоков или задач, чтобы обеспечить безопасность потоков. Однако стандартный RNG является безопасным для потоков, начиная с Julia 1.3 (используя RNG на поток в версии 1.6 и на задачу после этого).
Предоставленные RNG могут генерировать равномерные случайные числа следующих типов: Float16
, Float32
, Float64
, BigFloat
, Bool
, Int8
, UInt8
, Int16
, UInt16
, Int32
, UInt32
, Int64
, UInt64
, Int128
, UInt128
, BigInt
(или комплексные числа этих типов). Случайные числа с плавающей запятой генерируются равномерно в $[0, 1)$. Поскольку BigInt
представляет неограниченные целые числа, интервал должен быть указан (например, rand(big.(1:6))
).
Кроме того, нормальное и экспоненциальное распределения реализованы для некоторых типов AbstractFloat
и Complex
, см. randn
и randexp
для получения подробной информации.
Чтобы генерировать случайные числа из других распределений, смотрите пакет Distributions.jl.
Поскольку точный способ генерации случайных чисел считается деталью реализации, исправления ошибок и улучшения скорости могут изменить поток чисел, которые генерируются после изменения версии. Таким образом, не рекомендуется полагаться на конкретное начальное значение или сгенерированный поток чисел во время модульного тестирования - вместо этого рассмотрите возможность тестирования свойств рассматриваемых методов.
Random numbers module
Random.Random
— ModuleСлучайный
Поддержка генерации случайных чисел. Предоставляет rand
, randn
, AbstractRNG
, MersenneTwister
и RandomDevice
.
Random generation functions
Base.rand
— Functionrand([rng=default_rng()], [S], [dims...])
Выберите случайный элемент или массив случайных элементов из набора значений, указанных в S
; S
может быть
индексируемой коллекцией (например,
1:9
или('x', "y", :z)
)объектом
AbstractDict
илиAbstractSet
строкой (рассматриваемой как коллекция символов), или
типом из списка ниже, соответствующим указанному набору значений
- конкретные целочисленные типы выбираются из
typemin(S):typemax(S)
(за исключениемBigInt
, который не поддерживается) - конкретные типы с плавающей запятой выбираются из
[0, 1)
- конкретные комплексные типы
Complex{T}
, еслиT
является типом, который можно выбирать, берут их действительные и мнимые компоненты независимо из набора значений, соответствующихT
, но не поддерживаются, еслиT
не является выборочным. - все
<:AbstractChar
типы выбираются из набора допустимых скалярных значений Unicode - пользовательский тип и набор значений; для получения рекомендаций по реализации смотрите Hooking into the
Random
API - кортеж типа известного размера, где каждый параметр
S
сам по себе является выборочным типом; возвращает значение типаS
. Обратите внимание, что типы кортежей, такие какTuple{Vararg{T}}
(неизвестный размер) иTuple{1:2}
(параметризованный значением) не поддерживаются - тип
Pair
, например,Pair{X, Y}
, такой чтоrand
определен дляX
иY
, в этом случае производятся случайные пары.
- конкретные целочисленные типы выбираются из
S
по умолчанию равно Float64
. Когда передается только один аргумент, кроме необязательного rng
, и это Tuple
, он интерпретируется как коллекция значений (S
), а не как dims
.
Смотрите также randn
для нормально распределенных чисел, и rand!
и randn!
для эквивалентов на месте.
Поддержка S
в качестве кортежа требует как минимум Julia 1.1.
Поддержка S
в качестве типа Tuple
требует как минимум Julia 1.11.
Примеры
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
Сложность rand(rng, s::Union{AbstractDict,AbstractSet})
линейна по длине s
, если только не доступен оптимизированный метод с постоянной сложностью, что имеет место для Dict
, Set
и плотных BitSet
. Для большего количества вызовов используйте rand(rng, collect(s))
вместо этого, или rand(rng, Dict(s))
или rand(rng, Set(s))
по мере необходимости.
Random.rand!
— Functionrand!([rng=default_rng()], A, [S=eltype(A)])
Заполните массив A
случайными значениями. Если S
указано (S
может быть типом или коллекцией, см. rand
для подробностей), значения выбираются случайным образом из S
. Это эквивалентно copyto!(A, rand(rng, S, size(A)))
, но без выделения нового массива.
Примеры
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...])
Сгенерировать BitArray
случайных булевых значений.
Примеры
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...])
Сгенерировать нормально распределенное случайное число типа T
со средним 0 и стандартным отклонением 1. Учитывая необязательный аргумент dims
, сгенерировать массив размером dims
таких чисел. Стандартная библиотека Julia поддерживает randn
для любого типа с плавающей запятой, который реализует rand
, например, для типов Base
Float16
, Float32
, Float64
(по умолчанию) и BigFloat
, а также для их Complex
аналогов.
(Когда T
является комплексным, значения берутся из кругово-симметричного комплексного нормального распределения с дисперсией 1, соответствующего действительным и мнимым частям, имеющим независимое нормальное распределение со средним ноль и дисперсией 1/2
).
См. также randn!
для выполнения операций на месте.
Примеры
Генерация одного случайного числа (с типом по умолчанию Float64
):
julia> randn()
-0.942481877315864
Генерация матрицы нормальных случайных чисел (с типом по умолчанию Float64
):
julia> randn(2,3)
2×3 Matrix{Float64}:
1.18786 -0.678616 1.49463
-0.342792 -0.134299 -1.45005
Настройка генератора случайных чисел rng
с пользовательским начальным значением (для воспроизводимых чисел) и его использование для генерации случайного числа Float32
или матрицы случайных чисел 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
Заполните массив A
нормально распределёнными (среднее 0, стандартное отклонение 1) случайными числами. Также смотрите функцию rand
.
Примеры
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...])
Сгенерировать случайное число типа T
в соответствии с экспоненциальным распределением с масштабом 1. При желании можно сгенерировать массив таких случайных чисел. Модуль Base
в настоящее время предоставляет реализацию для типов Float16
, Float32
и Float64
(по умолчанию).
Примеры
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
Заполните массив A
случайными числами, следуя экспоненциальному распределению (с масштабом 1).
Примеры
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])
Создает случайную строку длиной len
, состоящую из символов из chars
, который по умолчанию представляет собой набор заглавных и строчных букв, а также цифр от 0 до 9. Необязательный аргумент rng
указывает генератор случайных чисел, см. Случайные числа.
Примеры
julia> Random.seed!(3); randstring()
"Lxz5hUwn"
julia> randstring(Xoshiro(3), 'a':'z', 6)
"iyzcsm"
julia> randstring("ACGT")
"TGCTCCTC"
!!! примечание chars
может быть любой коллекцией символов типа Char
или UInt8
(более эффективно), при условии, что rand
может случайным образом выбирать символы из нее.
Subsequences, permutations and shuffling
Random.randsubseq
— Functionrandsubseq([rng=default_rng(),] A, p) -> Vector
Возвращает вектор, состоящий из случайной подпоследовательности данного массива A
, где каждый элемент A
включается (в порядке) с независимой вероятностью p
. (Сложность линейна по отношению к p*length(A)
, поэтому эта функция эффективна даже если p
мал и A
велик.) Технически этот процесс известен как "выборка Бернулли" из A
.
Примеры
julia> randsubseq(Xoshiro(123), 1:8, 0.3)
2-element Vector{Int64}:
4
7
Random.randsubseq!
— Functionrandsubseq!([rng=default_rng(),] S, A, p)
Как и randsubseq
, но результаты хранятся в S
(который изменяется в размере по мере необходимости).
Примеры
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)
Создает случайную перестановку длины n
. Необязательный аргумент rng
указывает генератор случайных чисел (см. Случайные числа). Тип элементов результата такой же, как и тип n
.
Чтобы случайно перемешать произвольный вектор, смотрите shuffle
или shuffle!
.
В Julia 1.1 randperm
возвращает вектор v
с eltype(v) == typeof(n)
, в то время как в Julia 1.0 eltype(v) == Int
.
Примеры
julia> randperm(Xoshiro(123), 4)
4-element Vector{Int64}:
1
4
2
3
Random.randperm!
— Functionrandperm!([rng=default_rng(),] A::Array{<:Integer})
Сформируйте в A
случайную перестановку длины length(A)
. Необязательный аргумент rng
указывает генератор случайных чисел (см. Случайные числа). Чтобы случайно перемешать произвольный вектор, смотрите shuffle
или shuffle!
.
Примеры
julia> randperm!(Xoshiro(123), Vector{Int}(undef, 4))
4-element Vector{Int64}:
1
4
2
3
Random.randcycle
— Functionrandcycle([rng=default_rng(),] n::Integer)
Создает случайную циклическую перестановку длины n
. Необязательный аргумент rng
указывает генератор случайных чисел, см. Случайные числа. Тип элементов результата такой же, как и тип n
.
Здесь "циклическая перестановка" означает, что все элементы находятся в одном цикле. Если n > 0
, существует $(n-1)!$ возможных циклических перестановок, которые выбираются равномерно. Если n == 0
, randcycle
возвращает пустой вектор.
randcycle!
является вариантом этой функции, работающим на месте.
В Julia 1.1 и выше randcycle
возвращает вектор v
с eltype(v) == typeof(n)
, в то время как в Julia 1.0 eltype(v) == Int
.
Примеры
julia> randcycle(Xoshiro(123), 6)
6-element Vector{Int64}:
5
4
2
6
3
1
Random.randcycle!
— Functionrandcycle!([rng=default_rng(),] A::Array{<:Integer})
Создает в A
случайную циклическую перестановку длины n = length(A)
. Необязательный аргумент rng
указывает генератор случайных чисел, см. Случайные числа.
Здесь "циклическая перестановка" означает, что все элементы находятся в одном цикле. Если A
не пустой (n > 0
), существует $(n-1)!$ возможных циклических перестановок, которые выбираются равномерно. Если A
пустой, randcycle!
оставляет его без изменений.
randcycle
является вариантом этой функции, который выделяет новый вектор.
Примеры
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)
Вернуть случайно перемешанную копию v
. Необязательный аргумент rng
указывает генератор случайных чисел (см. Случайные числа). Чтобы перемешать v
на месте, смотрите shuffle!
. Чтобы получить случайно перемешанные индексы, смотрите randperm
.
Примеры
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)
Версия в месте выполнения shuffle
: случайным образом перемешивает v
на месте, при необходимости предоставляя генератор случайных чисел rng
.
Примеры
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
Возвращает глобальный генератор случайных чисел (RNG) по умолчанию, который используется функциями, связанными с rand
, когда явный RNG не предоставлен.
Когда модуль Random
загружается, RNG по умолчанию случайно инициализируется, через Random.seed!()
: это означает, что каждый раз, когда начинается новая сессия julia, первый вызов rand()
дает другой результат, если сначала не был вызван seed!(seed)
.
Он безопасен для потоков: разные потоки могут безопасно вызывать функции, связанные с rand
, на default_rng()
одновременно, например, rand(default_rng())
.
Тип RNG по умолчанию является деталью реализации. В разных версиях Julia не следует ожидать, что RNG по умолчанию всегда будет иметь один и тот же тип, или что он будет генерировать один и тот же поток случайных чисел для данного начального значения.
Эта функция была введена в Julia 1.3.
Random.seed!
— Functionseed!([rng=default_rng()], seed) -> rng
seed!([rng=default_rng()]) -> rng
Переинициализируйте генератор случайных чисел: rng
будет выдавать воспроизводимую последовательность чисел, если и только если задан seed
. Некоторые генераторы случайных чисел не принимают семя, такие как RandomDevice
. После вызова seed!
, rng
эквивалентен вновь созданному объекту, инициализированному с тем же семенем. Типы принимаемых семян зависят от типа rng
, но в общем случае целочисленные семена должны работать.
Если rng
не указан, по умолчанию происходит инициализация состояния общего локального генератора задач.
Примеры
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) # не воспроизводимо
true
julia> rand(Random.seed!(rng), Bool) # тоже не воспроизводимо
false
julia> rand(Xoshiro(), Bool) # тоже не воспроизводимо
true
Random.AbstractRNG
— TypeAbstractRNG
Супертип для генераторов случайных чисел, таких как MersenneTwister
и RandomDevice
.
Random.TaskLocalRNG
— TypeTaskLocalRNG
TaskLocalRNG
имеет состояние, которое локально для своей задачи, а не для своего потока. Он инициализируется при создании задачи, на основе состояния родительской задачи, но без продвижения состояния RNG родителя.
Преимуществом является то, что TaskLocalRNG
довольно быстр и позволяет воспроизводимые многопоточные симуляции (за исключением условий гонки), независимо от решений планировщика. Пока количество потоков не используется для принятия решений о создании задач, результаты симуляции также независимы от количества доступных потоков / ЦП. Случайный поток не должен зависеть от особенностей аппаратного обеспечения, включая порядок байтов и, возможно, размер слова.
Использование или инициализация RNG любой другой задачи, кроме той, что возвращается current_task()
, является неопределенным поведением: это будет работать большую часть времени, и иногда может завершиться без ошибок.
При инициализации TaskLocalRNG()
с помощью seed!
переданное значение семени, если таковое имеется, может быть любым целым числом.
Инициализация TaskLocalRNG()
с отрицательным целым числом требует как минимум Julia 1.11.
Создание задачи больше не продвигает состояние RNG родительской задачи, начиная с Julia 1.10.
Random.Xoshiro
— TypeXoshiro(seed::Union{Integer, AbstractString})
Xoshiro()
Xoshiro256++ — это быстрый генератор псевдослучайных чисел, описанный Дэвидом Блэкманом и Себастьяно Виньей в статье "Scrambled Linear Pseudorandom Number Generators", ACM Trans. Math. Softw., 2021. Ссылка на реализацию доступна по адресу https://prng.di.unimi.it
Помимо высокой скорости, Xoshiro имеет небольшой объем памяти, что делает его подходящим для приложений, где необходимо долго хранить множество различных случайных состояний.
Реализация Xoshiro в Julia имеет режим массовой генерации; он создает новые виртуальные PRNG из родительского и использует SIMD для параллельной генерации (т.е. массовый поток состоит из нескольких чередующихся экземпляров xoshiro). Виртуальные PRNG отбрасываются после обработки массового запроса (и не должны вызывать выделение памяти в куче).
Если семя не предоставлено, создается случайно сгенерированное (с использованием энтропии из системы). См. функцию seed!
для повторной инициализации уже существующего объекта Xoshiro
.
Передача отрицательного целого семени требует как минимум Julia 1.11.
Примеры
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()
Создайте объект RNG MersenneTwister
. Разные объекты RNG могут иметь свои собственные семена, что может быть полезно для генерации различных потоков случайных чисел. seed
может быть целым числом, строкой или вектором целых чисел UInt32
. Если семя не указано, создается случайно сгенерированное (с использованием энтропии из системы). См. функцию seed!
для повторной инициализации уже существующего объекта MersenneTwister
.
Передача отрицательного целого числа в качестве семени требует как минимум Julia 1.11.
Примеры
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()
Создайте объект RNG RandomDevice
. Два таких объекта всегда будут генерировать разные потоки случайных чисел. Энтропия получается от операционной системы.
Hooking into the Random
API
Существует два в основном ортогональных способа расширить функциональность Random
:
- генерация случайных значений пользовательских типов
- создание новых генераторов
API для 1) довольно функционален, но относительно новый, поэтому он все еще может развиваться в последующих релизах модуля Random
. Например, обычно достаточно реализовать один метод rand
, чтобы все остальные обычные методы работали автоматически.
API для 2) все еще является примитивным и может потребовать больше работы, чем строго необходимо от реализатора, чтобы поддерживать обычные типы сгенерированных значений.
Generating random values of custom types
Генерация случайных значений для некоторых распределений может включать различные компромиссы. Предварительно вычисленные значения, такие как alias table для дискретных распределений, или “squeezing” functions для унивариантных распределений, могут значительно ускорить выборку. Сколько информации следует предварительно вычислить, может зависеть от количества значений, которые мы планируем извлечь из распределения. Кроме того, некоторые генераторы случайных чисел могут иметь определенные свойства, которые различные алгоритмы могут захотеть использовать.
Модуль Random
определяет настраиваемую структуру для получения случайных значений, которая может решить эти проблемы. Каждое вызов rand
генерирует выборщик, который можно настроить с учетом вышеуказанных компромиссов, добавляя методы к Sampler
, который, в свою очередь, может использовать генератор случайных чисел, объект, который характеризует распределение, и предложение для количества повторений. В настоящее время для последнего используются Val{1}
(для одного образца) и Val{Inf}
(для произвольного числа), при этом Random.Repetition
является псевдонимом для обоих.
Объект, возвращаемый Sampler
, затем используется для генерации случайных значений. При реализации интерфейса случайной генерации для значения X
, которое можно выбрать, реализатор должен определить метод
rand(rng, sampler)
для конкретного sampler
, возвращаемого Sampler(rng, X, repetition)
.
Сэмплеры могут быть произвольными значениями, которые реализуют rand(rng, sampler)
, но для большинства приложений следующие предопределенные сэмплеры могут быть достаточными:
SamplerType{T}()
может быть использован для реализации выборщиков, которые выбирают из типаT
(например,rand(Int)
). Это значение по умолчанию, возвращаемоеSampler
для типов.SamplerTrivial(self)
— это простой обертка дляself
, к которой можно получить доступ с помощью[]
. Это рекомендуемый выбор выборщика, когда не требуется предварительно вычисленная информация (например,rand(1:3)
), и это значение по умолчанию, возвращаемоеSampler
для значений.SamplerSimple(self, data)
также содержит дополнительное полеdata
, которое можно использовать для хранения произвольных заранее вычисленных значений, которые должны быть вычислены в пользовательском методеSampler
.
Мы предоставляем примеры для каждого из этих случаев. Мы предполагаем, что выбор алгоритма независим от генератора случайных чисел, поэтому мы используем AbstractRNG
в наших сигнатурах.
Random.Sampler
— TypeSampler(rng, x, repetition = Val(Inf))
Возвращает объект выборщика, который можно использовать для генерации случайных значений из rng
для x
.
Когда sp = Sampler(rng, x, repetition)
, rand(rng, sp)
будет использоваться для получения случайных значений и должен быть определен соответствующим образом.
repetition
может быть Val(1)
или Val(Inf)
и должен использоваться в качестве рекомендации для определения объема предварительных вычислений, если это применимо.
Random.SamplerType
и Random.SamplerTrivial
являются стандартными запасными вариантами для типов и значений, соответственно. Random.SamplerSimple
можно использовать для хранения предварительно вычисленных значений без определения дополнительных типов только для этой цели.
Random.SamplerType
— TypeSamplerType{T}()
Сэмплер для типов, не содержащий другой информации. Значение по умолчанию для Sampler
, когда он вызывается с типами.
Random.SamplerTrivial
— TypeSamplerTrivial(x)
Создайте выборщик, который просто оборачивает данное значение x
. Это значение по умолчанию для значений. eltype
этого выборщика равен eltype(x)
.
Рекомендуемый случай использования — выборка из значений без предварительно вычисленных данных.
Random.SamplerSimple
— TypeSamplerSimple(x, data)
Создайте выборщик, который оборачивает данное значение x
и data
. eltype
этого выборщика равен eltype(x)
.
Рекомендуемый случай использования — выборка из значений с предвычисленными данными.
Отделение предварительных вычислений от фактической генерации значений является частью API и также доступно пользователю. В качестве примера предположим, что rand(rng, 1:20)
необходимо вызывать многократно в цикле: способ воспользоваться этим отделением выглядит следующим образом:
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
Это механизм, который также используется в стандартной библиотеке, например, в стандартной реализации генерации случайных массивов (как в rand(1:20, 10)
).
Generating values from a type
Учитывая тип T
, в настоящее время предполагается, что если rand(T)
определен, будет создан объект типа T
. SamplerType
является умолчательным образцом для типов. Чтобы определить случайную генерацию значений типа T
, должен быть определен метод rand(rng::AbstractRNG, ::Random.SamplerType{T})
, который должен возвращать значения, которые ожидается получить от rand(rng, T)
.
Давайте рассмотрим следующий пример: мы реализуем тип Die
, с переменным числом n
сторон, пронумерованных от 1
до n
. Мы хотим, чтобы rand(Die)
создавал Die
с случайным числом до 20 сторон (и как минимум 4):
struct Die
nsides::Int # number of sides
end
Random.rand(rng::AbstractRNG, ::Random.SamplerType{Die}) = Die(rand(rng, 4:20))
# output
Скалярные и массивные методы для Die
теперь работают как ожидалось:
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
Здесь мы определяем выборщик для коллекции. Если не требуется предварительно вычисленные данные, его можно реализовать с помощью выборщика SamplerTrivial
, который на самом деле является умолчательным запасным вариантом для значений.
Чтобы определить случайную генерацию объектов типа S
, следует определить следующий метод: rand(rng::AbstractRNG, sp::Random.SamplerTrivial{S})
. Здесь sp
просто оборачивает объект типа S
, который можно получить через sp[]
. Продолжая пример с Die
, теперь мы хотим определить rand(d::Die)
, чтобы получить Int
, соответствующий одной из сторон 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
Учитывая тип коллекции S
, в настоящее время предполагается, что если rand(::S)
определен, будет создан объект типа eltype(S)
. В последнем примере был создан Vector{Any}
; причина в том, что eltype(Die) == Any
. Решение состоит в том, чтобы определить Base.eltype(::Type{Die}) = Int
.
Generating values for an AbstractFloat
type
AbstractFloat
типы обрабатываются особым образом, потому что по умолчанию случайные значения не генерируются по всему домену типов, а скорее в [0,1)
. Следующий метод должен быть реализован для T <: AbstractFloat
: Random.rand(::AbstractRNG, ::Random.SamplerTrivial{Random.CloseOpen01{T}})
An optimized sampler with pre-computed data
Рассмотрим дискретное распределение, где числа 1:n
выбираются с заданными вероятностями, которые в сумме равны единице. Когда необходимо получить много значений из этого распределения, самым быстрым методом является использование alias table. Мы не предоставляем алгоритм для построения такой таблицы здесь, но предположим, что он доступен в make_alias_table(probabilities)
, а draw_number(rng, alias_table)
можно использовать для выбора случайного числа из нее.
Предположим, что распределение описывается следующим образом:
struct DiscreteDistribution{V <: AbstractVector}
probabilities::V
end
и что мы всегда хотим создать таблицу псевдонимов, независимо от количества необходимых значений (мы научимся настраивать это ниже). Методы
Random.eltype(::Type{<:DiscreteDistribution}) = Int
function Random.Sampler(::Type{<:AbstractRNG}, distribution::DiscreteDistribution, ::Repetition)
SamplerSimple(distribution, make_alias_table(distribution.probabilities))
end
должен быть определен для возврата выборщика с предвычисленными данными, затем
function rand(rng::AbstractRNG, sp::SamplerSimple{<:DiscreteDistribution})
draw_number(rng, sp.data)
end
будет использоваться для отображения значений.
Custom sampler types
Тип SamplerSimple
достаточно для большинства случаев использования с предвычисленными данными. Однако, чтобы продемонстрировать, как использовать пользовательские типы выборщиков, здесь мы реализуем нечто подобное SamplerSimple
.
Возвращаясь к нашему примеру с Die
: rand(::Die)
использует случайную генерацию из диапазона, поэтому есть возможность для этой оптимизации. Мы называем наш пользовательский выборщик 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)
Теперь можно получить выборщик с помощью sp = Sampler(rng, die)
, и использовать sp
вместо die
в любом вызове rand
, связанном с rng
. В приведенном выше упрощенном примере die
не нужно хранить в SamplerDie
, но на практике это часто бывает необходимо.
Конечно, этот шаблон настолько распространен, что используемый выше тип помощника, а именно Random.SamplerSimple
, доступен, что избавляет нас от определения SamplerDie
: мы могли бы реализовать наше разъединение с:
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)
Здесь sp.data
относится ко второму параметру в вызове конструктора SamplerSimple
(в данном случае равному Sampler(rng, 1:die.nsides, r)
), в то время как объект Die
можно получить через sp[]
.
Как и SamplerDie
, любой пользовательский семплер должен быть подтипом Sampler{T}
, где T
— это тип генерируемых значений. Обратите внимание, что SamplerSimple(x, data) isa Sampler{eltype(x)}
, поэтому это ограничивает, что первым аргументом для SamplerSimple
может быть (рекомендуется использовать SamplerSimple
, как в примере с Die
, где x
просто передается при определении метода Sampler
). Аналогично, SamplerTrivial(x) isa Sampler{eltype(x)}
.
Другой тип помощника в настоящее время доступен для других случаев, Random.SamplerTag
, но считается внутренним API и может быть изменен в любое время без надлежащих устареваний.
Using distinct algorithms for scalar or array generation
В некоторых случаях, хочет ли кто-то сгенерировать лишь несколько значений или большое количество значений, это повлияет на выбор алгоритма. Это обрабатывается третьим параметром конструктора Sampler
. Предположим, мы определили два вспомогательных типа для Die
, скажем, SamplerDie1
, который должен использоваться для генерации лишь нескольких случайных значений, и SamplerDieMany
для многих значений. Мы можем использовать эти типы следующим образом:
Sampler(RNG::Type{<:AbstractRNG}, die::Die, ::Val{1}) = SamplerDie1(...)
Sampler(RNG::Type{<:AbstractRNG}, die::Die, ::Val{Inf}) = SamplerDieMany(...)
Конечно, rand
также должен быть определен для этих типов (т.е. rand(::AbstractRNG, ::SamplerDie1)
и rand(::AbstractRNG, ::SamplerDieMany)
). Обратите внимание, что, как обычно, SamplerTrivial
и SamplerSimple
могут быть использованы, если пользовательские типы не нужны.
Примечание: Sampler(rng, x)
является просто сокращением для Sampler(rng, x, Val(Inf))
, а Random.Repetition
является псевдонимом для Union{Val{1}, Val{Inf}}
.
Creating new generators
API еще не четко определен, но в качестве правила:
- любой метод
rand
, производящий "базовые" типы (isbitstype
целые и плавающие типы вBase
), должен быть определен для этого конкретного RNG, если они необходимы; - другие задокументированные методы
rand
, принимающиеAbstractRNG
, должны работать из коробки, (при условии, что методы из 1), на которые полагаются, реализованы), но, конечно, могут быть специализированы для этого RNG, если есть возможность для оптимизации; copy
для псевдослучайных генераторов чисел (pseudo-RNGs) должен возвращать независимую копию, которая генерирует ту же самую случайную последовательность, что и оригинал, начиная с момента вызова тем же образом. Когда это невозможно (например, для аппаратных генераторов случайных чисел),copy
не должен быть реализован.
Что касается 1), метод rand
может работать автоматически, но он не поддерживается официально и может перестать работать без предупреждений в следующем релизе.
Чтобы определить новый метод rand
для гипотетического генератора MyRNG
, и спецификацию значения s
(например, s == Int
, или s == 1:10
) типа S==typeof(s)
или S==Type{s}
, если s
является типом, необходимо определить те же два метода, что и раньше:
Sampler(::Type{MyRNG}, ::S, ::Repetition)
, который возвращает объект типа, скажем,SamplerS
rand(rng::MyRNG, sp::SamplerS)
Может случиться так, что Sampler(rng::AbstractRNG, ::S, ::Repetition)
уже определен в модуле Random
. В этом случае можно было бы пропустить шаг 1) на практике (если кто-то хочет специализировать генерацию для этого конкретного типа RNG), но соответствующий тип SamplerS
считается внутренней деталью и может быть изменен без предупреждения.
Specializing array generation
В некоторых случаях для данного типа генератора случайных чисел (RNG) генерация массива случайных значений может быть более эффективной с помощью специализированного метода, чем просто с использованием техники декуплинга, объясненной ранее. Это, например, случай с MersenneTwister
, который изначально записывает случайные значения в массив.
Чтобы реализовать эту специализацию для MyRNG
и для спецификации s
, производя элементы типа S
, можно определить следующий метод: rand!(rng::MyRNG, a::AbstractArray{S}, ::SamplerS)
, где SamplerS
— это тип выборщика, возвращаемого Sampler(MyRNG, s, Val(Inf))
. Вместо AbstractArray
можно реализовать функциональность только для подтипа, например, Array{S}
. Невзаимодействующий метод массива rand
автоматически вызовет эту специализацию внутри.
Reproducibility
Используя параметр RNG, инициализированный с заданным начальным значением, вы можете воспроизвести ту же последовательность псевдослучайных чисел при многократном запуске вашей программы. Однако незначительное обновление Julia (например, с 1.3 до 1.4) может изменить последовательность псевдослучайных чисел, генерируемых из конкретного начального значения, особенно если используется MersenneTwister
. (Даже если последовательность, производимая низкоуровневой функцией, такой как rand
, не изменится, вывод более высокоуровневых функций, таких как randsubseq
, может измениться из-за обновлений алгоритма.) Обоснование: гарантирование того, что псевдослучайные потоки никогда не изменяются, запрещает многие алгоритмические улучшения.
Если вам нужно гарантировать точную воспроизводимость случайных данных, рекомендуется просто сохранить данные (например, в качестве дополнительного приложения в научной публикации). (Вы также можете, конечно, указать конкретную версию Julia и манифест пакетов, особенно если вам требуется битовая воспроизводимость.)
Тесты программного обеспечения, которые полагаются на конкретные "случайные" данные, также должны либо сохранять данные, встраивать их в код теста, либо использовать сторонние пакеты, такие как StableRNGs.jl. С другой стороны, тесты, которые должны проходить для большинства случайных данных (например, тестирование A \ (A*x) ≈ x
для случайной матрицы A = randn(n,n)
), могут использовать генератор случайных чисел с фиксированным начальным значением, чтобы гарантировать, что простое выполнение теста много раз не приведет к сбою из-за очень маловероятных данных (например, крайне плохо обусловленной матрицы).
Статистическое распределение, из которого берутся случайные выборки, гарантировано будет одинаковым для любых незначительных обновлений Julia.