Spec-Zone.ru › Julia 1.10

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

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

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

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

Большинство функций, связанных с генерацией случайных чисел, принимают необязательный объект AbstractRNG в качестве первого аргумента. Некоторые также принимают спецификации размерности dims... (которые также могут быть заданы как кортеж) для генерации массивов случайных значений. В многопоточной программе, как правило, следует использовать разные объекты RNG для разных потоков или задач, чтобы обеспечить безопасность потоков. Однако, генератор RNG по умолчанию безопасен для потоков начиная с Julia 1.3 (используя генератор RNG на поток до версии 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 для подробностей.

Для генерации случайных чисел из других распределений, см. пакет Distributions.jl.

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

Модуль случайных чисел

Random.RandomМодуль

Random

Поддержка генерации случайных чисел. Предоставляет rand, randn, AbstractRNG, MersenneTwister и RandomDevice.

Функции генерации случайных чисел

Base.randФункция

rand([rng=default_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.

См. также randn для нормально распределённых чисел и rand! и randn! для эквивалентов на месте.

Поддержка 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=default_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=default_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=default_rng()], [T=Float64], [dims...])

Генерирует нормально распределённое случайное число типа T со средним значением 0 и стандартным отклонением 1. Необязательно генерировать массив нормально распределённых случайных чисел. Модуль Base в настоящее время предоставляет реализацию для типов Float16, Float32 и Float64 (по умолчанию) и их комплексных аналогов. Когда аргумент типа комплексный, значения выбираются из кругосимметричного комплексного нормального распределения с дисперсией 1 (что соответствует тому, что вещественная и мнимая части имеют независимое нормальное распределение со средним значением ноль и дисперсией 1/2).

См. также randn! для работы на месте.

Примеры

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=default_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=default_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=default_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=default_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=default_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=default_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=default_rng(),] n::Integer)

Строит случайную перестановку длины n. Необязательный аргумент rng задаёт генератор случайных чисел (см. Случайные числа). Тип элементов результата совпадает с типом n.

Для случайной перестановки произвольного вектора см. shuffle или shuffle!.

В Julia 1.1 randperm возвращает вектор v с eltype(v) == typeof(n), в то время как в Julia 1.0 eltype(v) == Int.

Примеры

julia> randperm(MersenneTwister(1234), 4)
4-element Vector{Int64}:
 2
 1
 4
 3

Random.randperm!Функция

randperm!([rng=default_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=default_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=default_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=default_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=default_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.default_rngФункция

default_rng() -> rng

Возвращает глобальный генератор случайных чисел (RNG) по умолчанию.

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

Эта функция была введена в Julia 1.3.

Random.seed!Функция

seed!([rng=default_rng()], seed) -> rng
seed!([rng=default_rng()]) -> rng

Перезадаёт зерно генератора случайных чисел: rng обеспечит воспроизводимую последовательность чисел только при условии предоставления seed. Некоторые генераторы RNG не принимают зерно, например, RandomDevice. После вызова seed!, rng эквивалентен вновь созданному объекту, инициализированному тем же зерном.

Если rng не указано, используется зерно состояния общего генератора локальной задачи.

Примеры

julia> Random.seed!(1234);

julia> x1 = rand(2)
2-element Vector{Float64}:
 0.32597672886359486
 0.5490511363155669

julia> Random.seed!(1234);

julia> x2 = rand(2)
2-element Vector{Float64}:
 0.32597672886359486
 0.5490511363155669

julia> x1 == x2
true

julia> rng = Xoshiro(1234); rand(rng, 2) == x1
true

julia> Xoshiro(1) == Random.seed!(rng, 1)
true

julia> rand(Random.seed!(rng), Bool) # not reproducible
true

julia> rand(Random.seed!(rng), Bool) # not reproducible either
false

julia> rand(Xoshiro(), Bool) # not reproducible either
true

Random.AbstractRNGТип

AbstractRNG

Базовый тип для генераторов случайных чисел, таких как MersenneTwister и RandomDevice.

Random.TaskLocalRNGТип

TaskLocalRNG

У TaskLocalRNG состояние локально для задачи, а не для потока. Оно инициализируется при создании задачи из состояния родительской задачи, но без продвижения состояния RNG родителя.

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

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

Начиная с Julia 1.10, создание задачи больше не продвигает состояние RNG родительской задачи.

Random.XoshiroТип

Xoshiro(seed)
Xoshiro()

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

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

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

Примеры

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

Мы предоставляем примеры для каждого из них. Мы предполагаем здесь, что выбор алгоритма не зависит от генератора случайных чисел, поэтому в наших сигнатурах используется 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–2024 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.10/stdlib/Random/

Spec-Zone.ru

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