Случайные числа
Генерация случайных чисел в Julia использует библиотеку Mersenne Twister через объекты MersenneTwister. Julia имеет глобальный генератор случайных чисел (RNG), который используется по умолчанию. Другие типы RNG можно подключать, наследуя тип AbstractRNG; затем они могут быть использованы для получения нескольких потоков случайных чисел. Помимо MersenneTwister, Julia также предоставляет тип RNG RandomDevice, который является оболочкой над предоставленной ОС энтропией.
Большинство функций, связанных с генерацией случайных чисел, принимают необязательный объект AbstractRNG в качестве первого аргумента, который по умолчанию является глобальным, если не указан. Кроме того, некоторые из них принимают необязательные спецификации размерности dims... (которые могут быть заданы как кортеж) для генерации массивов случайных значений. В многопоточной программе, как правило, следует использовать разные объекты RNG из разных потоков для обеспечения потоковой безопасности. Однако глобальный RNG по умолчанию безопасен для использования в многопоточных приложениях с версии Julia 1.3 (поскольку он внутренне соответствует генератору RNG на основе каждого потока).
Генератор MersenneTwister или RandomDevice может генерировать равномерно распределенные случайные числа следующих типов: 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 для подробностей.
Поскольку точный способ генерации случайных чисел считается деталью реализации, исправления ошибок и улучшения производительности могут изменить поток генерируемых чисел после изменения версии. Поэтому не рекомендуется полагаться на конкренное семя или сгенерированный поток чисел во время тестирования модулей — предпочтительнее проверять свойства самих методов.
Модуль случайных чисел
Random.RandomМодуль
Random
Поддержка генерации случайных чисел. Предоставляет rand, randn, AbstractRNG, MersenneTwister и RandomDevice.
Функции генерации случайных чисел
Base.randФункция
rand([rng=GLOBAL_RNG], [S], [dims...])
Выбирает случайный элемент или массив случайных элементов из набора значений, заданного S; S может быть
- индексируемым набором (например,
1:9или('x', "y", :z)), - объектом
AbstractDictилиAbstractSet, - строкой (рассматриваемой как набор символов), или
- типом: множество значений для выбора тогда эквивалентно
typemin(S):typemax(S)для целых чисел (это не применимо кBigInt), к $[0, 1)$ для чисел с плавающей запятой и к $[0, 1)+i[0, 1)$ для комплексных чисел с плавающей запятой;
S по умолчанию Float64. Когда передается только один аргумент помимо необязательного rng и является Tuple, он интерпретируется как набор значений (S) а не как dims.
Поддержка S как кортежа требует как минимум Julia 1.1.
Примеры
julia> rand(Int, 2)
2-element Array{Int64,1}:
1339893410598768192
1575814717733606317
julia> using Random
julia> rand(MersenneTwister(0), Dict(1=>2, 3=>4))
1=>2
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!Функция
rand!([rng=GLOBAL_RNG], A, [S=eltype(A)])
Заполняет массив A случайными значениями. Если указан S (S может быть типом или набором, см. rand для подробностей), значения выбираются случайным образом из S. Это эквивалентно copyto!(A, rand(rng, S, size(A))), но без выделения нового массива.
Примеры
julia> rng = MersenneTwister(1234);
julia> rand!(rng, zeros(5))
5-element Vector{Float64}:
0.5908446386657102
0.7667970365022592
0.5662374165061859
0.4600853424625171
0.7940257103317943
Исходный код
Random.bitrandФункция
bitrand([rng=GLOBAL_RNG], [dims...])
Генерирует массив BitArray случайных логических значений.
Примеры
julia> rng = MersenneTwister(1234); julia> bitrand(rng, 10) 10-element BitVector: 0 0 0 0 1 0 0 0 1 1Исходный код
Base.randnФункция
randn([rng=GLOBAL_RNG], [T=Float64], [dims...])
Генерирует случайное число типа T с нормальным распределением со средним значением 0 и стандартным отклонением 1. Необязательно генерирует массив чисел с нормальным распределением. Модуль Base в настоящее время предоставляет реализацию для типов Float16, Float32 и Float64 (по умолчанию) и их комплексных аналогов. Когда аргумент типа — комплексный, значения выбираются из кругово-симметричного комплексного нормального распределения с дисперсией 1 (что соответствует действительной и мнимой частям, имеющим независимые нормальные распределения со средним значением ноль и дисперсией 1/2).
Примеры
julia> using Random
julia> rng = MersenneTwister(1234);
julia> randn(rng, ComplexF64)
0.6133070881429037 - 0.6376291670853887im
julia> randn(rng, ComplexF32, (2, 3))
2×3 Matrix{ComplexF32}:
-0.349649-0.638457im 0.376756-0.192146im -0.396334-0.0136413im
0.611224+1.56403im 0.355204-0.365563im 0.0905552+1.31012im
Исходный код
Random.randn!Функция
randn!([rng=GLOBAL_RNG], A::AbstractArray) -> A
Заполняет массив A числами с нормальным распределением (среднее 0, стандартное отклонение 1). Также см. функцию rand.
Примеры
julia> rng = MersenneTwister(1234);
julia> randn!(rng, zeros(5))
5-element Vector{Float64}:
0.8673472019512456
-0.9017438158568171
-0.4944787535042339
-0.9029142938652416
0.8644013132535154
Исходный код
Random.randexpФункция
randexp([rng=GLOBAL_RNG], [T=Float64], [dims...])
Генерирует случайное число типа T в соответствии с экспоненциальным распределением со масштабом 1. Необязательно генерирует массив таких случайных чисел. Модуль Base в настоящее время предоставляет реализацию для типов Float16, Float32 и Float64 (по умолчанию).
Примеры
julia> rng = MersenneTwister(1234);
julia> randexp(rng, Float32)
2.4835055f0
julia> randexp(rng, 3, 3)
3×3 Matrix{Float64}:
1.5167 1.30652 0.344435
0.604436 2.78029 0.418516
0.695867 0.693292 0.643644
Исходный код
Random.randexp!Функция
randexp!([rng=GLOBAL_RNG], A::AbstractArray) -> A
Заполняет массив A случайными числами, следующими экспоненциальному распределению (со масштабом 1).
Примеры
julia> rng = MersenneTwister(1234);
julia> randexp!(rng, zeros(5))
5-element Vector{Float64}:
2.4835053723904896
1.516703605376473
0.6044364871025417
0.6958665886385867
1.3065196315496677
Исходный код
Random.randstringФункция
randstring([rng=GLOBAL_RNG], [chars], [len=8])
Создайте случайную строку длины len, состоящую из символов из chars, по умолчанию — набор прописных и строчных букв, а также цифр от 0 до 9. Необязательный аргумент rng задаёт генератор случайных чисел, см. Случайные числа.
Примеры
julia> Random.seed!(3); randstring()
"Y7m62wOj"
julia> randstring(MersenneTwister(3), 'a':'z', 6)
"ocucay"
julia> randstring("ACGT")
"ATTTGCGT"
chars может быть любым набором символов, типа Char или UInt8 (более эффективно), при условии, что rand может случайным образом выбирать символы из него.
Подпоследовательности, перестановки и перемешивание
Random.randsubseqФункция
randsubseq([rng=GLOBAL_RNG,] A, p) -> Vector
Возвращает вектор, состоящий из случайной подпоследовательности заданного массива A, где каждый элемент A включается (в порядке) с независимой вероятностью p. (Сложность линейна по отношению к p*length(A), поэтому эта функция эффективна даже если p мал, а A велик.) В техническом плане этот процесс известен как «выборка Бернулли» из A.
Примеры
julia> rng = MersenneTwister(1234);
julia> randsubseq(rng, 1:8, 0.3)
2-element Vector{Int64}:
7
8
исходный код
Random.randsubseq!Функция
randsubseq!([rng=GLOBAL_RNG,] S, A, p)
Подобно randsubseq, но результаты сохраняются в S (который переразмеряется по мере необходимости).
Примеры
julia> rng = MersenneTwister(1234);
julia> S = Int64[];
julia> randsubseq!(rng, S, 1:8, 0.3)
2-element Vector{Int64}:
7
8
julia> S
2-element Vector{Int64}:
7
8
исходный код
Random.randpermФункция
randperm([rng=GLOBAL_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(MersenneTwister(1234), 4)
4-element Vector{Int64}:
2
1
4
3
исходный код
Random.randperm!Функция
randperm!([rng=GLOBAL_RNG,] A::Array{<:Integer})
Создаёт в A случайную перестановку длины length(A). Необязательный аргумент rng задаёт генератор случайных чисел (см. Случайные числа). Для случайного перемешивания произвольного вектора см. shuffle или shuffle!.
Примеры
julia> randperm!(MersenneTwister(1234), Vector{Int}(undef, 4))
4-element Vector{Int64}:
2
1
4
3
исходный код
Random.randcycleФункция
randcycle([rng=GLOBAL_RNG,] n::Integer)
Создаёт случайную циклическую перестановку длины n. Необязательный аргумент rng задаёт генератор случайных чисел, см. Случайные числа. Тип элементов результата совпадает с типом n.
В Julia 1.1 randcycle возвращает вектор v с eltype(v) == typeof(n), в то время как в Julia 1.0 eltype(v) == Int.
Примеры
julia> randcycle(MersenneTwister(1234), 6)
6-element Vector{Int64}:
3
5
4
6
1
2
исходный код
Random.randcycle!Функция
randcycle!([rng=GLOBAL_RNG,] A::Array{<:Integer})
Создаёт в A случайную циклическую перестановку длины length(A). Необязательный аргумент rng задаёт генератор случайных чисел, см. Случайные числа.
Примеры
julia> randcycle!(MersenneTwister(1234), Vector{Int}(undef, 6))
6-element Vector{Int64}:
3
5
4
6
1
2
исходный код
Random.shuffleФункция
shuffle([rng=GLOBAL_RNG,] v::AbstractArray)
Возвращает случайную перестановку копии v. Необязательный аргумент rng задаёт генератор случайных чисел (см. Случайные числа). Для перемешивания v на месте см. shuffle!. Для получения случайных переставленных индексов см. randperm.
Примеры
julia> rng = MersenneTwister(1234);
julia> shuffle(rng, Vector(1:10))
10-element Vector{Int64}:
6
1
10
2
3
9
5
7
4
8
исходный код
Random.shuffle!Функция
shuffle!([rng=GLOBAL_RNG,] v::AbstractArray)
Функция-аналог shuffle на месте: случайное перемешивание v на месте, при необходимости с указанием генератора случайных чисел rng.
Примеры
julia> rng = MersenneTwister(1234);
julia> shuffle!(rng, Vector(1:16))
16-element Vector{Int64}:
2
15
5
14
1
9
10
6
11
3
16
7
4
12
8
13
исходный кодГенераторы (создание и посев)
Random.seed!Функция
seed!([rng=GLOBAL_RNG], seed) -> rng seed!([rng=GLOBAL_RNG]) -> rng
Повторный посев генератора случайных чисел: rng даст воспроизводимую последовательность чисел только в том случае, если указан seed. Некоторые генераторы случайных чисел не принимают seed, например, RandomDevice. После вызова seed!, rng эквивалентен вновь созданному объекту, инициализированному тем же seed.
Если rng не указан, используется посев состояния общего генератора для текущей нити.
Примеры
julia> Random.seed!(1234);
julia> x1 = rand(2)
2-element Array{Float64,1}:
0.590845
0.766797
julia> Random.seed!(1234);
julia> x2 = rand(2)
2-element Array{Float64,1}:
0.590845
0.766797
julia> x1 == x2
true
julia> rng = MersenneTwister(1234); rand(rng, 2) == x1
true
julia> MersenneTwister(1) == Random.seed!(rng, 1)
true
julia> rand(Random.seed!(rng), Bool) # not reproducible
true
julia> rand(Random.seed!(rng), Bool)
false
julia> rand(MersenneTwister(), Bool) # not reproducible either
true
исходный код
Random.AbstractRNGТип
AbstractRNG
Базовый тип для генераторов случайных чисел, таких как MersenneTwister и RandomDevice.
Random.MersenneTwisterТип
MersenneTwister(seed) MersenneTwister()
Создаёт объект генератора случайных чисел MersenneTwister. Разные объекты генераторов могут иметь свои seed, что может быть полезно для генерации разных потоков случайных чисел. seed может быть неотрицательным целым числом или вектором UInt32 целых чисел. Если seed не указан, генерируется случайный (с использованием энтропии системы).
См. функцию seed! для повторного посева существующего объекта MersenneTwister.
Примеры
julia> rng = MersenneTwister(1234);
julia> x1 = rand(rng, 2)
2-element Vector{Float64}:
0.5908446386657102
0.7667970365022592
julia> rng = MersenneTwister(1234);
julia> x2 = rand(rng, 2)
2-element Vector{Float64}:
0.5908446386657102
0.7667970365022592
julia> x1 == x2
true
исходный код
Random.RandomDeviceТип
RandomDevice()
Создаёт объект генератора случайных чисел RandomDevice. Два таких объекта всегда будут генерировать разные потоки случайных чисел. Энтропия извлекается из операционной системы.
Включение в API случайных чисел
Существует два основных ортогональных способа расширения функциональности Random:
- генерация случайных значений пользовательских типов
- создание новых генераторов
API для 1) довольно функциональный, но относительно новый, поэтому ему, возможно, придётся развиваться в последующих выпусках модуля Random. Например, обычно достаточно реализовать один метод rand для автоматической работы остальных.
API для 2) всё ещё примитивен и может потребовать от разработчика больше работы, чем строго необходимо, чтобы поддерживать обычные типы генерируемых значений.
Генерация случайных значений пользовательских типов
Генерация случайных значений для некоторых распределений может включать различные компромиссы. Предвычисленные значения, такие как таблица алиасов для дискретных распределений, или «сжимающие» функции для одномерных распределений, могут значительно ускорить выборку. Сколько информации следует предвычислить, может зависеть от количества значений, которые мы планируем получить из распределения. Кроме того, у некоторых генераторов случайных чисел могут быть определенные свойства, которые различные алгоритмы могут захотеть использовать.
Модуль 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.
Мы предоставляем примеры для каждого из них. Мы предполагаем здесь, что выбор алгоритма не зависит от RNG, поэтому мы используем AbstractRNG в наших сигнатурах.
Random.SamplerТип
Sampler(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Тип
SamplerType{T}()
Сэмплер для типов, не содержащий другой информации. Стандартный резервный вариант для Sampler при вызове с типами.
Random.SamplerTrivialТип
SamplerTrivial(x)
Создает сэмплер, который просто оборачивает заданное значение x. Это стандартный резервный вариант для значений. Значение eltype этого сэмплера равно eltype(x).
Рекомендуемый случай использования — выборка из значений без предварительно вычисленных данных.
исходный код
Random.SamplerSimpleТип
SamplerSimple(x, data)
Создает сэмплер, который оборачивает заданное значение x и data. Значение eltype этого сэмплера равно eltype(x).
Рекомендуемый случай использования — выборка из значений с предварительно вычисленными данными.
исходный кодРазделение предварительного вычисления от фактической генерации значений является частью API и доступно пользователю. Например, предположим, что rand(rng, 1:20) нужно вызывать многократно в цикле: способ воспользоваться этим разделением — следующий:
rng = MersenneTwister()
sp = Random.Sampler(rng, 1:20) # or Random.Sampler(MersenneTwister, 1:20)
for x in X
n = rand(rng, sp) # similar to n = rand(rng, 1:20)
# use n
end
Этот механизм используется также в стандартной библиотеке, например, в реализации генерации случайных массивов (как в rand(1:20, 10)).
Генерация значений из типа
Учитывая тип 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(15)
julia> rand(MersenneTwister(0), Die)
Die(11)
julia> rand(Die, 3)
3-element Vector{Die}:
Die(18)
Die(5)
Die(4)
julia> a = Vector{Die}(undef, 3); rand!(a)
3-element Vector{Die}:
Die(5)
Die(20)
Die(15)
Простой сэмплер без предварительно вычисленных данных
Здесь мы определяем сэмплер для коллекции. Если предварительно вычисленные данные не нужны, его можно реализовать с помощью сэмплера 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))
3
julia> rand(Die(4), 3)
3-element Vector{Any}:
4
1
1
Учитывая тип коллекции S, в настоящее время предполагается, что если rand(::S) определен, будет создан объект типа eltype(S). В последнем примере создается Vector{Any}; причина в том, что eltype(Die) == Any. Решением является определение Base.eltype(::Type{Die}) = Int.
Генерация значений для типа AbstractFloat
Типы AbstractFloat являются особым случаем, потому что по умолчанию случайные значения не генерируются по всему диапазону типа, а скорее в [0,1). Следующий метод должен быть реализован для T <: AbstractFloat: Random.rand(::AbstractRNG, ::Random.SamplerTrivial{Random.CloseOpen01{T}}).
Оптимизированный сэмплер с предварительно вычисленными данными
Рассмотрим дискретное распределение, где числа 1:n выбираются с заданными вероятностями, которые суммируются до единицы. Когда требуется много значений из этого распределения, самым быстрым методом является использование таблицы алиасов. Мы не будем здесь приводить алгоритм построения такой таблицы, но предположим, что он доступен в 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(disribution, make_alias_table(distribution.probabilities))
end
должны быть определены для возвращения сэмплера с предварительно вычисленными данными, а затем
function rand(rng::AbstractRNG, sp::SamplerSimple{<:DiscreteDistribution})
draw_number(rng, sp.data)
end
будет использоваться для генерации значений.
Пользовательские типы сэмплеров
Тип 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 и может быть изменён или удалён в любой момент без надлежащей подготовки.
Использование различных алгоритмов для генерации скалярных или массивов
В некоторых случаях выбор алгоритма зависит от того, требуется сгенерировать несколько значений или большое количество. Это обрабатывается третьим параметром конструктора 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}}.
Создание новых генераторов
API пока не определён чётко, но, как правило:
- любой метод
rand, производящий «базовые» типы (isbitstypeцелые и плавающие типы вBase) должен быть определён для этого конкретного генератора случайных чисел, если они нужны; - другие документированные методы
rand, принимающиеAbstractRNG, должны работать «из коробки» (при условии, что методы из пункта 1), на которых они опираются, реализованы), но, конечно, могут быть специализированы для этого генератора, если есть возможность оптимизации; -
copyдля псевдогенераторов случайных чисел должен возвращать независимую копию, генерирующую точно такую же последовательность случайных чисел, как и оригинал, с того момента, когда она вызывается таким же образом. Если это не представляется возможным (например, для генераторов случайных чисел на основе аппаратного обеспечения),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) на практике (если нужно специализировать генерацию для данного типа генератора), но соответствующий тип SamplerS считается внутренней деталью и может быть изменён без предупреждения.
Специализация генерации массивов
В некоторых случаях для данного типа генератора случайных чисел генерация массива случайных значений может быть более эффективной с помощью специализированного метода, чем путём простого использования техники декомпозиции, описанной ранее. Например, это относится к MersenneTwister, который напрямую записывает случайные значения в массив.
Чтобы реализовать эту специализацию для MyRNG и спецификации s, производящей элементы типа S, можно определить следующий метод: rand!(rng::MyRNG, a::AbstractArray{S}, ::SamplerS), где SamplerS — тип генератора, возвращаемого Sampler(MyRNG, s, Val(Inf)). Вместо AbstractArray, можно реализовать функциональность только для подтипа, например, Array{S}. Неизменяющий метод массива rand автоматически вызовет эту специализацию в своём внутреннем коде.
Воспроизводимость
Используя генератор случайных чисел, инициализированный заданным seed, можно воспроизвести ту же последовательность псевдослучайных чисел при многократном запуске программы. Однако незначительное обновление Julia (например, с 1.3 до 1.4) может изменить последовательность псевдослучайных чисел, генерируемых из определённого seed, особенно если используется MersenneTwister. (Даже если последовательность, производимая низкоуровневой функцией, например, rand, не изменяется, вывод функций более высокого уровня, таких как randsubseq, может измениться из-за обновлений алгоритма.) Объяснение: гарантирование неизменности потоков псевдослучайных чисел препятствует многим алгоритмическим усовершенствованиям.
Если вам необходимо гарантировать точную воспроизводимость случайных данных, рекомендуется просто сохранить данные (например, как дополнительный файл в научной публикации). (Вы также можете, конечно, указать конкретную версию Julia и пакетный манифест, особенно если вам нужна воспроизводимость до бита.)
Тесты программного обеспечения, которые полагаются на конкретные «случайные» данные, обычно должны либо сохранять эти данные, встраивать их в код теста, либо использовать сторонние пакеты, такие как StableRNGs.jl. С другой стороны, тесты, которые должны проходить для большинства случайных данных (например, тестирование A \ (A*x) ≈ x для случайной матрицы A = randn(n,n)) могут использовать генератор случайных чисел с фиксированным seed, чтобы гарантировать, что многократный запуск теста не приведёт к сбоям из-за очень маловероятных данных (например, чрезвычайно плохо обусловленной матрицы).
Статистическое распределение, из которого генерируются случайные выборки, гарантируется неизменным между незначительными обновлениями Julia.
© 2009–2021 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.6.0/stdlib/Random/