Spec-Zone.ru › Julia 1.7

Генерация случайных чисел

Генерация случайных чисел в Julia по умолчанию использует алгоритм Xoshiro256++ с индивидуальным состоянием. Другие типы генераторов случайных чисел (ГСЧ) можно подключить, унаследовав тип AbstractRNG; затем их можно использовать для получения нескольких потоков случайных чисел. Помимо стандартного типа TaskLocalRNG, пакет Random также предоставляет MersenneTwister, RandomDevice (который использует энтропию операционной системы) и Xoshiro (для явного управления потоками Xoshiro256++).

Большинство функций, связанных с генерацией случайных чисел, принимают необязательный объект 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. Некоторые генераторы случайных чисел не принимают семя, например, RandomDevice. После вызова seed!, rng эквивалентен вновь созданному объекту, инициализированному с тем же семян.

Если 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 довольно быстр и позволяет воспроизводимые многопоточные симуляции (исключая гонки), независимо от решений планировщика. Пока количество потоков не используется для принятия решений о создании задач, результаты симуляции также не зависят от доступного количества потоков/ЦП. Поток случайных чисел не должен зависеть от аппаратных особенностей, за исключением порядка байтов и, возможно, размера слова.

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

исходный код

Random.XoshiroТип

Xoshiro

Xoshiro256++ — это быстрый генератор псевдослучайных чисел, первоначально разработанный Себастьяном Вигна. Ссылка на реализацию доступна по адресу http://prng.di.unimi.it

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

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

исходный код

Random.MersenneTwisterТип

MersenneTwister(seed)
MersenneTwister()

Создайте объект MersenneTwister RNG. Разные объекты RNG могут иметь свои собственные семена, что может быть полезно для генерации разных потоков случайных чисел. Семено seed может быть целым неотрицательным числом или вектором целых UInt32 чисел. Если семя не указано, генерируется случайное семя (используя энтропию из системы). Обратитесь к функции 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 RNG. Два таких объекта всегда будут генерировать разные потоки случайных чисел. Энтропия берется из операционной системы.

исходный код

Встраивание в 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.

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

  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–2021 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.7.0/stdlib/Random/

Spec-Zone.ru

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