Spec-Zone.ru › Julia 1.8

Случайные числа

Генерация случайных чисел в Julia по умолчанию использует алгоритм Xoshiro256++ с состоянием на каждый Task. Другие типы ГСЧ можно подключить, унаследовав тип AbstractRNG; затем они могут быть использованы для получения нескольких потоков случайных чисел.

ГСЧ (генераторы псевдослучайных чисел), экспортируемые пакетом Random, это:

  • TaskLocalRNG: маркер, который представляет использование текущего активного потока Task-локального потока, детерминированно инициализированного из родительского задания или RandomDevice (с системной случайностью) при запуске программы
  • Xoshiro: генерирует поток высококачественных случайных чисел с небольшим вектором состояния и высокой производительностью, используя алгоритм Xoshiro256++
  • RandomDevice: для получения энтропии от ОС. Это может быть использовано для криптографически безопасных случайных чисел (CS(P)RNG).
  • MersenneTwister: альтернативный высококачественный ГСЧ, который был по умолчанию в старых версиях Julia и также довольно быстрый, но требует больше места для хранения вектора состояния и генерации случайной последовательности.

Большинство функций, связанных с генерацией случайных чисел, принимают необязательный объект AbstractRNG в качестве первого аргумента. Некоторые также принимают спецификации размерности dims... (которые также могут быть заданы как кортеж) для генерации массивов случайных значений. В многопоточной программе, как правило, следует использовать разные объекты ГСЧ из разных потоков или задач, чтобы обеспечить безопасность потоков. Однако по умолчанию ГСЧ безопасен для потоков, начиная с Julia 1.3 (используя ГСЧ на поток до версии 1.6, и на задачу после этого).

Предоставленные ГСЧ могут генерировать равномерные случайные числа следующих типов: 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()
"Lxz5hUwn"

julia> randstring(MersenneTwister(3), 'a':'z', 6)
"ocucay"

julia> randstring("ACGT")
"TGCTCCTC"

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.TaskLocalRNGТип

TaskLocalRNG

У TaskLocalRNG состояние локально для задачи, а не для потока. Инициализируется при создании задачи из состояния родительской задачи. Следовательно, создание задачи — событие, изменяющее состояние RNG родительской задачи.

В качестве преимущества, TaskLocalRNG довольно быстр и позволяет воспроизводимые многопоточные симуляции (при отсутствии гонок), независимо от решений планировщика. Пока количество потоков не используется для принятия решений о создании задач, результаты симуляции также независимы от доступного количества потоков/CPU. Поток случайных чисел не должен зависеть от конкретных особенностей оборудования, до порядка байт и, возможно, размера слова.

Использование или инициализация RNG любой другой задачи, кроме той, которая возвращается current_task() — неопределённое поведение: оно будет работать в большинстве случаев и может иногда без ошибок завершиться ошибкой.

Random.XoshiroТип

Xoshiro(seed)
Xoshiro()

Xoshiro256++ — быстрый псевдослучайный генератор, описанный Дэвидом Блэкманом и Себастьяно Винья в статье «Scrambled Linear Pseudorandom Number Generators», ACM Trans. Math. Softw., 2021. Ссылка на реализацию см. на http://prng.di.unimi.it

Помимо высокой скорости, Xoshiro имеет небольшой размер памяти, что делает его пригодным для приложений, где нужно хранить много различных случайных состояний в течение длительного времени.

Реализация Xoshiro в Julia имеет режим генерации блоков; это инициализирует новые виртуальные генераторы псевдослучайных чисел из родительского, и использует SIMD для одновременной генерации (т.е. поток блоков состоит из нескольких переплетённых экземпляров xoshiro). Виртуальные генераторы псевдослучайных чисел удаляются после обработки запроса блока (и не должны вызывать выделения памяти).

Примеры

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Тип

MersenneTwister(seed)
MersenneTwister()

Создаёт объект генератора случайных чисел MersenneTwister . У разных объектов генератора могут быть свои инициализирующие значения, что полезно для генерации различных потоков случайных чисел. 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

Существует два основных ортогональных способа расширения функциональности Random:

  1. генерация случайных значений пользовательских типов
  2. создание новых генераторов

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), но для большинства приложений могут быть достаточными следующие предварительно определённые выборщики:

  1. SamplerType{T}() можно использовать для реализации семплеров, которые выбирают значения из типа T (например, rand(Int)). Это значение по умолчанию, возвращаемое Sampler для типов.

  2. SamplerTrivial(self) — это простой обертка для self, к которой можно получить доступ с помощью []. Это рекомендуемый семплер, когда предварительно вычисленные данные не нужны (например, rand(1:3)). Это значение по умолчанию, возвращаемое Sampler для значений.

  3. SamplerSimple(self, data) также содержит дополнительное поле data, которое можно использовать для хранения произвольных предварительно вычисленных значений, которые должны быть вычислены в специальном методе Sampler.

Мы предоставляем примеры для каждого из них. Мы предполагаем, что выбор алгоритма независим от генератора случайных чисел, поэтому в наших сигнатурах используется 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(5)

julia> rand(MersenneTwister(0), Die)
Die(11)

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)

Простой семплер без предварительно вычисленных данных

Здесь мы определяем семплер для коллекции. Если предварительно вычисленные данные не требуются, он может быть реализован с помощью семплера 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.

Генерация значений для типа AbstractFloat

Типы AbstractFloat являются специальными случаями, потому что по умолчанию случайные значения генерируются не по всему диапазону типа, а в [0,1).

Для типа T <: AbstractFloat должен быть реализован следующий метод: Random.rand(::AbstractRNG, ::Random.SamplerTrivial{Random.CloseOpen01{T}}).

Оптимизированный семплер с предварительно вычисленными данными

Рассмотрим дискретное распределение, где числа 1:n выбираются с заданными вероятностями, сумма которых равна единице. Когда требуется много значений из этого распределения, самый быстрый метод — использовать таблицу alias.

Мы не приводим алгоритм построения такой таблицы здесь, но предположим, что он доступен в make_alias_table(probabilities) вместо этого, и draw_number(rng, alias_table) можно использовать для выбора случайного числа из неё.

Предположим, что распределение описывается

struct DiscreteDistribution{V <: AbstractVector}
    probabilities::V
end

и что мы всегда хотим строить таблицу alias, независимо от количества необходимых значений (мы узнаем, как настроить это ниже). Методы

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 пока не полностью определён, но в качестве правила:

  1. любой rand метод, производящий «базовые» типы (isbitstype целые и плавающие типы в Base) должен быть определён для этого конкретного генератора случайных чисел, если они необходимы;
  2. другие документированные rand методы, принимающие AbstractRNG, должны работать «из коробки» (при условии, что реализованы методы из пункта 1), но, конечно, могут быть специализированы для этого генератора случайных чисел, если есть место для оптимизации;
  3. copy для псевдогенераторов случайных чисел должен возвращать независимую копию, генерирующую точно такую же последовательность случайных чисел, как и оригинал, с того момента, когда она вызывается таким же образом. Когда это не представляется возможным (например, генераторы случайных чисел на основе аппаратного обеспечения), copy не должен быть реализован.

Что касается пункта 1), метод rand может работать автоматически, но он не поддерживается официально и может сломаться без предупреждений в последующей версии.

Для определения нового rand метода для гипотетического генератора MyRNG и указания значения s (например, s == Int, или s == 1:10) типа S==typeof(s) или S==Type{s}, если s является типом, должны быть определены те же два метода, что и ранее:

  1. Sampler(::Type{MyRNG}, ::S, ::Repetition), который возвращает объект типа, скажем, SamplerS
  2. 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 автоматически вызовет эту специализацию внутри.

Воспроизводимость

Используя параметр генератора случайных чисел, инициализированный заданным начальным значением, вы можете воспроизвести ту же последовательность псевдослучайных чисел при многократном запуске программы. Однако незначительное обновление Julia (например, с 1.3 до 1.4) может изменить последовательность псевдослучайных чисел, генерируемых из конкретного начального значения, особенно если используется MersenneTwister. (Даже если последовательность, производимая низкоуровневой функцией, как rand, не меняется, вывод функций более высокого уровня, таких как randsubseq, может измениться из-за обновлений алгоритма.) Обоснование: гарантия неизменности потоков псевдослучайных чисел препятствует многим улучшениям алгоритмов.

Если вам нужна гарантия точной воспроизводимости случайных данных, рекомендуется просто сохранить данные (например, в качестве дополнительного приложения в научной публикации). (Вы также можете, конечно, указать определённую версию Julia и манифест пакета, особенно если вам нужна воспроизводимость до уровня битов.)

Тесты программного обеспечения, которые полагаются на конкретные «случайные» данные, также, как правило, должны либо сохранять данные, встраивать их в код теста, либо использовать пакеты сторонних разработчиков, такие как StableRNGs.jl. С другой стороны, тесты, которые должны быть пройдены для большинства случайных данных (например, тестирование A \ (A*x) ≈ x для случайной матрицы A = randn(n,n) могут использовать генератор случайных чисел с фиксированным начальным значением, чтобы убедиться, что простой многократный запуск теста не приводит к сбою из-за очень маловероятных данных (например, чрезвычайно плохо обусловленной матрицы).

Статистическое распределение, из которого извлекаются случайные образцы, гарантировано будет таким же при любых незначительных обновлениях Julia.

© 2009–2022 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.8/stdlib/Random/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API