Случайные числа
Генерация случайных чисел в Julia по умолчанию использует алгоритм Xoshiro256++ с состоянием на Task уровне. Другие типы генераторов случайных чисел (PRNG) могут быть подключены путем наследования типа AbstractRNG; они могут затем использоваться для получения нескольких потоков случайных чисел.
PRNG (генераторы псевдослучайных чисел), экспортированные пакетом Random, это:
-
TaskLocalRNG: маркер, представляющий использование текущего активного потока задачи, детерминированно инициализированного из родительской задачи или с использованиемRandomDevice(с системной случайностью) в начале программы -
Xoshiro: генерирует поток высококачественных случайных чисел с небольшим вектором состояния и высокой производительностью, используя алгоритм Xoshiro256++ -
RandomDevice: для получения энтропии из операционной системы. Может быть использован для криптографически безопасных случайных чисел (CS(P)RNG). -
MersenneTwister: альтернативный высококачественный генератор псевдослучайных чисел, который был по умолчанию в более ранних версиях Julia и также достаточно быстрый, но требует больше места для хранения вектора состояния и генерации случайной последовательности.
Большинство функций, связанных с генерацией случайных чисел, принимают необязательный объект AbstractRNG в качестве первого аргумента. Некоторые также принимают спецификации размерности dims... (которые также могут быть заданы как кортеж) для генерации массивов случайных значений. В многопоточной программе, как правило, следует использовать разные объекты RNG для разных потоков или задач, чтобы обеспечить потокобезопасность. Однако, генератор RNG по умолчанию является потокобезопасным начиная с Julia 1.3 (используя генератор RNG на уровне потока до версии 1.6, и на уровне задачи после).
Предоставленные генераторы RNG могут генерировать равномерно распределенные случайные числа следующих типов: Float16, Float32, Float64, BigFloat, Bool, Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Int128, UInt128, BigInt (или комплексные числа этих типов). Случайные числа с плавающей точкой генерируются равномерно в $[0, 1)$. Так как BigInt представляет неограниченные целые числа, интервал должен быть указан (например, rand(big.(1:6))).
Кроме того, нормальное и экспоненциальное распределения реализованы для некоторых типов AbstractFloat и Complex, см. randn и randexp для подробностей.
Для генерации случайных чисел из других распределений, см. пакет Distributions.jl.
Поскольку точный способ генерации случайных чисел считается деталью реализации, исправления ошибок и улучшения производительности могут изменить поток генерируемых чисел после изменения версии. Поэтому не рекомендуется полагаться на конкренное семя или сгенерированный поток чисел во время тестирования единиц. Вместо этого, следует тестировать свойства самих методов.
Модуль случайных чисел
Random.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)конкретные комплексные типы
Complex{T}еслиTявляется типом, допускающим выбор, то их вещественные и мнимые части выбираются независимо из набора значений, соответствующегоT, но не поддерживаются, еслиTне является типом, допускающим выбор.все типы
<:AbstractCharвыбираются из набора допустимых скаляров Юникодапользовательский тип и набор значений; для руководства по реализации см. Интеграция с API
Randomкортеж типа известного размера, где каждый параметр
Sсам является типом, допускающим выбор; возвращает значение типаS. Обратите внимание, что типы кортежей, такие какTuple{Vararg{T}}(неизвестный размер) иTuple{1:2}(параметризованный значением), не поддерживаютсятип
Pair, например,Pair{X, Y}, такой, чтоrandопределен дляXиY, в этом случае генерируются случайные пары.
S по умолчанию Float64. Когда передается только один аргумент помимо необязательного rng и он является Tuple, он интерпретируется как коллекция значений (S) и не как dims.
См. также randn для нормальных распределений и rand! и randn! для эквивалентов in-place.
Поддержка S в качестве кортежа требует как минимум Julia 1.1.
Поддержка S в качестве типа Tuple требует как минимум Julia 1.11.
Примеры
julia> rand(Int, 2)
2-element Array{Int64,1}:
1339893410598768192
1575814717733606317
julia> using Random
julia> rand(Xoshiro(0), Dict(1=>2, 3=>4))
3 => 4
julia> rand((2, 3))
3
julia> rand(Float64, (2, 3))
2×3 Array{Float64,2}:
0.999717 0.0143835 0.540787
0.696556 0.783855 0.938235
Сложность rand(rng, s::Union{AbstractDict,AbstractSet}) линейна по длине s, если только не доступен оптимизированный метод с постоянной сложностью, что имеет место для Dict, Set и плотных BitSet. Для более чем нескольких вызовов используйте rand(rng, collect(s)) вместо этого, или rand(rng, Dict(s)) или rand(rng, Set(s)) в зависимости от случая.
Random.rand!Функция
rand!([rng=default_rng()], A, [S=eltype(A)])
Заполняет массив A случайными значениями. Если S указан (S может быть типом или коллекцией, см. rand для деталей), значения случайным образом выбираются из S. Это эквивалентно copyto!(A, rand(rng, S, size(A))), но без выделения нового массива.
Примеры
julia> rand!(Xoshiro(123), zeros(5))
5-element Vector{Float64}:
0.521213795535383
0.5868067574533484
0.8908786980927811
0.19090669902576285
0.5256623915420473
Random.bitrandФункция
bitrand([rng=default_rng()], [dims...])
Генерирует массив BitArray случайных логических значений.
Примеры
julia> bitrand(Xoshiro(123), 10) 10-element BitVector: 0 1 0 1 0 1 0 0 1 1
Base.randnФункция
randn([rng=default_rng()], [T=Float64], [dims...])
Генерирует случайное число с нормальным распределением со средним значением 0 и стандартным отклонением 1 типа T. С необязательными аргументами dims, генерирует массив размера dims таких чисел. Стандартная библиотека Julia поддерживает randn для любого типа чисел с плавающей точкой, реализующего rand, например, типы Base Float16, Float32, Float64 (по умолчанию) и BigFloat, вместе с их комплексными аналогами Complex.
(Когда T является комплексным, значения выбираются из комплексного нормального распределения с круговой симметрией и дисперсией 1, что соответствует вещественной и мнимой частям, имеющим независимое нормальное распределение со средним значением ноль и дисперсией 1/2).
См. также randn! для in-place обработки.
Примеры
Генерация одного случайного числа (с типом Float64 по умолчанию):
julia> randn() -0.942481877315864
Генерация матрицы нормальных случайных чисел (с типом Float64 по умолчанию):
julia> randn(2,3)
2×3 Matrix{Float64}:
1.18786 -0.678616 1.49463
-0.342792 -0.134299 -1.45005
Настройка генератора случайных чисел rng с пользовательским семенем (для воспроизводимых чисел) и использование его для генерации случайного Float32 числа или матрицы ComplexF32 случайных чисел:
julia> using Random
julia> rng = Xoshiro(123);
julia> randn(rng, Float32)
-0.6457307f0
julia> randn(rng, ComplexF32, (2, 3))
2×3 Matrix{ComplexF32}:
-1.03467-1.14806im 0.693657+0.056538im 0.291442+0.419454im
-0.153912+0.34807im 1.0954-0.948661im -0.543347-0.0538589im
Random.randn!Функция
randn!([rng=default_rng()], A::AbstractArray) -> A
Заполняет массив A нормальными случайными числами (среднее 0, стандартное отклонение 1). Также см. функцию rand.
Примеры
julia> randn!(Xoshiro(123), zeros(5))
5-element Vector{Float64}:
-0.6457306721039767
-1.4632513788889214
-1.6236037455860806
-0.21766510678354617
0.4922456865251828
Random.randexpФункция
randexp([rng=default_rng()], [T=Float64], [dims...])
Сгенерировать случайное число типа T в соответствии с экспоненциальным распределением со масштабом 1. При необходимости сгенерировать массив таких случайных чисел. Модуль Base в настоящее время предоставляет реализацию для типов Float16, Float32 и Float64 (по умолчанию).
Примеры
julia> rng = Xoshiro(123);
julia> randexp(rng, Float32)
1.1757717f0
julia> randexp(rng, 3, 3)
3×3 Matrix{Float64}:
1.37766 0.456653 0.236418
3.40007 0.229917 0.0684921
0.48096 0.577481 0.71835
Random.randexp!Функция
randexp!([rng=default_rng()], A::AbstractArray) -> A
Заполнить массив A случайными числами, следующими экспоненциальному распределению (со масштабом 1).
Примеры
julia> randexp!(Xoshiro(123), zeros(5))
5-element Vector{Float64}:
1.1757716836348473
1.758884569451514
1.0083623637301151
0.3510644315565272
0.6348266443720407
Random.randstringФункция
randstring([rng=default_rng()], [chars], [len=8])
Создать случайную строку длины len, состоящую из символов из chars, что по умолчанию соответствует набору заглавных и строчных букв и цифр от 0 до 9. Опциональный аргумент rng указывает генератор случайных чисел, см. Случайные числа.
Примеры
julia> Random.seed!(3); randstring()
"Lxz5hUwn"
julia> randstring(Xoshiro(3), 'a':'z', 6)
"iyzcsm"
julia> randstring("ACGT")
"TGCTCCTC"
chars может быть любым набором символов, типа Char или UInt8 (более эффективно), при условии, что rand может случайным образом выбирать символы из него.
Подпоследовательности, перестановки и перемешивание
Random.randsubseqФункция
randsubseq([rng=default_rng(),] A, p) -> Vector
Возвращает вектор, состоящий из случайной подпоследовательности заданного массива A, где каждый элемент A включается (в порядке) с независимой вероятностью p. (Сложность линейна относительно p*length(A), поэтому эта функция эффективна даже если p небольшой, а A большой.) Технически, этот процесс известен как «выборка Бернулли» из A.
Примеры
julia> randsubseq(Xoshiro(123), 1:8, 0.3)
2-element Vector{Int64}:
4
7
Random.randsubseq!Функция
randsubseq!([rng=default_rng(),] S, A, p)
Как randsubseq, но результаты хранятся в S (размер которого изменяется по мере необходимости).
Примеры
julia> S = Int64[];
julia> randsubseq!(Xoshiro(123), S, 1:8, 0.3)
2-element Vector{Int64}:
4
7
julia> S
2-element Vector{Int64}:
4
7
Random.randpermФункция
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(Xoshiro(123), 4)
4-element Vector{Int64}:
1
4
2
3
Random.randperm!Функция
randperm!([rng=default_rng(),] A::Array{<:Integer})
Создать в A случайную перестановку длины length(A). Опциональный аргумент rng указывает генератор случайных чисел (см. Случайные числа). Для случайной перестановки произвольного вектора, см. shuffle или shuffle!.
Примеры
julia> randperm!(Xoshiro(123), Vector{Int}(undef, 4))
4-element Vector{Int64}:
1
4
2
3
Random.randcycleФункция
randcycle([rng=default_rng(),] n::Integer)
Создать случайную циклическую перестановку длины n. Опциональный аргумент rng указывает генератор случайных чисел, см. Случайные числа. Тип элементов результата совпадает с типом n.
Здесь «циклическая перестановка» означает, что все элементы лежат в одном цикле. Если n > 0, существует $(n-1)!$ возможных циклических перестановок, которые выбираются равномерно. Если n == 0, randcycle возвращает пустой вектор.
randcycle! — инвариантная версия этой функции.
В Julia 1.1 и выше randcycle возвращает вектор v с eltype(v) == typeof(n), в то время как в Julia 1.0 eltype(v) == Int.
Примеры
julia> randcycle(Xoshiro(123), 6)
6-element Vector{Int64}:
5
4
2
6
3
1
Random.randcycle!Функция
randcycle!([rng=default_rng(),] A::Array{<:Integer})
Создать в A случайную циклическую перестановку длины n = length(A). Опциональный аргумент rng указывает генератор случайных чисел, см. Случайные числа.
Здесь «циклическая перестановка» означает, что все элементы лежат в одном цикле. Если A непустой (n > 0), существует $(n-1)!$ возможных циклических перестановок, которые выбираются равномерно. Если A пустой, randcycle! оставляет его без изменений.
randcycle — вариант этой функции, который выделяет новый вектор.
Примеры
julia> randcycle!(Xoshiro(123), Vector{Int}(undef, 6))
6-element Vector{Int64}:
5
4
2
6
3
1
Random.shuffleФункция
shuffle([rng=default_rng(),] v::AbstractArray)
Возвращает случайную переставленную копию v. Опциональный аргумент rng указывает генератор случайных чисел (см. Случайные числа). Для перестановки v на месте, см. shuffle!. Для получения случайных перемешанных индексов, см. randperm.
Примеры
julia> shuffle(Xoshiro(123), Vector(1:10))
10-element Vector{Int64}:
5
4
2
3
6
10
8
1
9
7
Random.shuffle!Функция
shuffle!([rng=default_rng(),] v::AbstractArray)
Инвариантная версия shuffle: случайная перестановка v на месте, при необходимости указав генератор случайных чисел rng.
Примеры
julia> shuffle!(Xoshiro(123), Vector(1:10))
10-element Vector{Int64}:
5
4
2
3
6
10
8
1
9
7Генераторы (создание и инициализация)
Random.default_rngФункция
Random.default_rng() -> rng
Возвращает глобальный генератор случайных чисел (ГСЧ) по умолчанию, который используется функциями, связанными с rand, когда явный ГСЧ не предоставлен.
При загрузке модуля Random, ГСЧ по умолчанию случайно инициализируется с помощью Random.seed!(): это означает, что каждый раз при запуске новой сессии julia первый вызов rand() даёт другой результат, если не вызвать seed!(seed) предварительно.
Он потокобезопасен: различные потоки могут безопасно вызывать связанные с rand функции на default_rng() одновременно, например, rand(default_rng()).
Тип ГСЧ по умолчанию — это деталь реализации. В разных версиях Julia вы не должны ожидать, что ГСЧ по умолчанию всегда будет иметь один и тот же тип, а также что он будет генерировать одну и ту же последовательность случайных чисел для заданного семени.
Эта функция была представлена в Julia 1.3.
Random.seed!Функция
seed!([rng=default_rng()], seed) -> rng seed!([rng=default_rng()]) -> rng
Инициализация генератора случайных чисел: rng даст воспроизводимую последовательность чисел только в том случае, если указано seed. Некоторые ГСЧ не принимают семя, как RandomDevice. После вызова seed!, rng эквивалентен вновь созданному объекту, инициализированному тем же семенем. Типы допустимых семян зависят от типа rng, но, как правило, целые числа должны работать.
Если rng не указано, используется инициализация состояния общего локального генератора задачи.
Примеры
julia> Random.seed!(1234);
julia> x1 = rand(2)
2-element Vector{Float64}:
0.32597672886359486
0.5490511363155669
julia> Random.seed!(1234);
julia> x2 = rand(2)
2-element Vector{Float64}:
0.32597672886359486
0.5490511363155669
julia> x1 == x2
true
julia> rng = Xoshiro(1234); rand(rng, 2) == x1
true
julia> Xoshiro(1) == Random.seed!(rng, 1)
true
julia> rand(Random.seed!(rng), Bool) # 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 состояние локально для его задачи, а не для его потока. Он инициализируется при создании задачи из состояния родительской задачи, но без продвижения состояния ГСЧ родителя.
В качестве преимущества, TaskLocalRNG довольно быстрый и позволяет воспроизводимые многопоточные симуляции (без гонок), независимо от решений планировщика. Пока количество потоков не используется для принятия решений о создании задач, результаты симуляции также не зависят от количества доступных потоков/процессоров. Поток случайных чисел не должен зависеть от особенностей оборудования, за исключением порядка байтов и, возможно, размера слова.
Использование или инициализация ГСЧ любой другой задачи, кроме той, что возвращается current_task(), — это неопределённое поведение: оно будет работать в большинстве случаев и иногда может замолчать с ошибкой.
При инициализации TaskLocalRNG() с помощью seed!, переданное семя, если есть, может быть любым целым числом.
Инициализация TaskLocalRNG() отрицательным целым числом требует, как минимум, Julia 1.11.
Создание задачи больше не продвигает состояние ГСЧ родительской задачи начиная с Julia 1.10.
Random.XoshiroТип
Xoshiro(seed::Union{Integer, AbstractString})
Xoshiro()
Xoshiro256++ — это быстрый псевдослучайный генератор, описанный Дэвидом Блэкменом и Себастьяно Вигной в статье «Scrambled Linear Pseudorandom Number Generators», ACM Trans. Math. Softw., 2021. Реализация ссылки доступна по адресу https://prng.di.unimi.it
Помимо высокой скорости, Xoshiro имеет небольшой размер занимаемой памяти, что делает его подходящим для применений, где требуется хранение многих различных случайных состояний в течение длительного времени.
Реализация Xoshiro в Julia имеет режим массовой генерации; он использует родительское состояние для инициализации новых виртуальных псевдослучайных генераторов и использует SIMD для параллельной генерации (т. е. поток массовой генерации состоит из нескольких переплетённых экземпляров xoshiro). Виртуальные псевдослучайные генераторы удаляются после обработки запроса на массовую генерацию (и не должны вызывать выделение памяти).
Если семя не предоставлено, генерируется случайное семя (используя энтропию из системы). Обратитесь к функции seed! для повторной инициализации существующего объекта Xoshiro.
Для задания отрицательного целочисленного семени требуется Julia версии 1.11 или выше.
Примеры
julia> using Random
julia> rng = Xoshiro(1234);
julia> x1 = rand(rng, 2)
2-element Vector{Float64}:
0.32597672886359486
0.5490511363155669
julia> rng = Xoshiro(1234);
julia> x2 = rand(rng, 2)
2-element Vector{Float64}:
0.32597672886359486
0.5490511363155669
julia> x1 == x2
true
Random.MersenneTwisterТип
MersenneTwister(seed) MersenneTwister()
Создаёт объект генератора псевдослучайных чисел (RNG). Разные объекты RNG могут иметь свои семена, что может быть полезно для генерации различных потоков случайных чисел. Семено (seed) может быть целым числом, строкой или вектором UInt32 целых чисел. Если семя не предоставлено, генерируется случайное семя (используя энтропию из системы). Обратитесь к функции seed! для повторной инициализации существующего объекта MersenneTwister.
Для задания отрицательного целочисленного семени требуется Julia версии 1.11 или выше.
Примеры
julia> rng = MersenneTwister(123);
julia> x1 = rand(rng, 2)
2-element Vector{Float64}:
0.37453777969575874
0.8735343642013971
julia> x2 = rand(MersenneTwister(123), 2)
2-element Vector{Float64}:
0.37453777969575874
0.8735343642013971
julia> x1 == x2
true
Random.RandomDeviceТип
RandomDevice()
Создаёт объект генератора псевдослучайных чисел (RNG). Два таких объекта всегда будут генерировать разные потоки случайных чисел. Энтропия получается из операционной системы.
Подключение к API Random
Существует два основных ортогональных способа расширения функциональности Random:
- генерация случайных значений пользовательских типов
- создание новых генераторов
API для 1) довольно функционален, но он относительно новый, поэтому ему может потребоваться дальнейшее развитие в последующих версиях модуля Random. Например, обычно достаточно реализовать один метод rand , чтобы все остальные обычные методы работали автоматически.
API для 2) всё ещё находится на ранней стадии, и разработчику может потребоваться больше работы, чем строго необходимо, чтобы поддерживать обычные типы генерируемых значений.
Генерация случайных значений пользовательских типов
Генерация случайных значений для некоторых распределений может предполагать различные компромиссы. Предвычисленные значения, такие как таблица алиасов для дискретных распределений или «сжимающие» функции для одномерных распределений, могут значительно ускорить выборку. Сколько информации следует предварительно вычислить, может зависеть от количества значений, которые планируется извлечь из распределения. Кроме того, некоторые генераторы случайных чисел могут иметь определённые свойства, которые могут быть использованы различными алгоритмами.
Модуль Random определяет настраиваемую структуру для получения случайных значений, которая может справиться с этими проблемами. Каждый вызов rand генерирует самплер, который можно настроить с учётом вышеупомянутых компромиссов, добавив методы в Sampler, которые, в свою очередь, могут отправлять запросы к генератору случайных чисел, объекту, характеризующему распределение, и предложению о количестве повторений. В настоящее время для последнего используются Val{1} (для одной выборки) и Val{Inf} (для произвольного количества), причём Random.Repetition является псевдонимом для обоих.
Затем объект, возвращённый Sampler, используется для генерации случайных значений. При реализации интерфейса случайной генерации для значения X, которое может быть выбрано, разработчик должен определить метод
rand(rng, sampler)
для конкретного sampler , возвращённого Sampler(rng, X, repetition).
Самплеры могут быть произвольными значениями, реализующими rand(rng, sampler), но для большинства применений могут быть достаточны следующие предопределённые самплеры:
SamplerType{T}()может быть использован для реализации самплеров, которые выбирают значения типаT(например,rand(Int)). Это значение по умолчанию, возвращаемоеSamplerдля типов.SamplerTrivial(self)— это простой обертывающий объект дляself, к которому можно получить доступ с помощью[]. Это рекомендуемый самплер, когда предварительно вычисленные данные не нужны (например,rand(1:3)), и это значение по умолчанию, возвращаемоеSamplerдля значений.SamplerSimple(self, data)также содержит дополнительное полеdata, которое может быть использовано для хранения произвольных предварительно вычисленных значений, которые должны быть вычислены в специальном методеSampler.
Мы предоставляем примеры для каждого из них. Мы предполагаем здесь, что выбор алгоритма не зависит от RNG, поэтому в наших сигнатурах используется AbstractRNG.
Random.SamplerТип
Sampler(rng, x, repetition = Val(Inf))
Возвращает объект самплера, который можно использовать для генерации случайных значений из rng для x.
При использовании sp = Sampler(rng, x, repetition) , rand(rng, sp) будет использоваться для выборки случайных значений и должно быть определено соответствующим образом.
repetition может быть Val(1) или Val(Inf), и должно использоваться как подсказка для выбора объёма предварительного вычисления, если это применимо.
Random.SamplerType и Random.SamplerTrivial — это значения по умолчанию для типов и значений соответственно. Random.SamplerSimple может быть использован для хранения предварительно вычисленных значений без определения дополнительных типов только для этой цели.
Random.SamplerTypeТип
SamplerType{T}()
Самплер для типов, не содержащий дополнительной информации. Значение по умолчанию для Sampler при вызове с типами.
Random.SamplerTrivialТип
SamplerTrivial(x)
Создаёт самплер, который просто оборачивает данное значение x. Это значение по умолчанию для значений. Значение eltype этого самплера равно eltype(x).
Рекомендуемое использование — выборка из значений без предварительно вычисленных данных.
Random.SamplerSimpleТип
SamplerSimple(x, data)
Создаёт самплер, который оборачивает данное значение x и data. Значение eltype этого самплера равно eltype(x).
Рекомендуемое использование — выборка из значений с предварительно вычисленными данными.
Отделение предварительного вычисления от фактического генерирования значений — это часть API, доступная и пользователю. Например, предположим, что rand(rng, 1:20) нужно вызывать многократно в цикле: вот как воспользоваться этим разделением:
rng = Xoshiro()
sp = Random.Sampler(rng, 1:20) # or Random.Sampler(Xoshiro, 1:20)
for x in X
n = rand(rng, sp) # similar to n = rand(rng, 1:20)
# use n
end
Этот механизм также используется в стандартной библиотеке, например, в реализации генерации случайных массивов (как в rand(1:20, 10)).
Генерация значений из типа
Для типа T, в настоящее время предполагается, что если определён rand(T), будет создан объект типа T. SamplerType — это самплер по умолчанию для типов. Чтобы определить генерацию случайных значений типа T, нужно определить метод rand(rng::AbstractRNG, ::Random.SamplerType{T}), который должен возвращать значения, которые rand(rng, T) ожидает получить.
Рассмотрим следующий пример: мы реализуем тип Die, с переменным числом n сторон, пронумерованными от 1 до n . Мы хотим, чтобы rand(Die) создавал Die с случайным числом сторон до 20 (и не менее 4):
struct Die
nsides::Int # number of sides
end
Random.rand(rng::AbstractRNG, ::Random.SamplerType{Die}) = Die(rand(rng, 4:20))
# output
Скалярные и массивно-ориентированные методы для Die теперь работают как ожидается:
julia> rand(Die)
Die(5)
julia> rand(Xoshiro(0), Die)
Die(10)
julia> rand(Die, 3)
3-element Vector{Die}:
Die(9)
Die(15)
Die(14)
julia> a = Vector{Die}(undef, 3); rand!(a)
3-element Vector{Die}:
Die(19)
Die(7)
Die(17)
Простой самплер без предварительно вычисленных данных
Здесь мы определяем самплер для коллекции. Если предварительные вычисления не требуются, его можно реализовать с помощью самплера 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(distribution, make_alias_table(distribution.probabilities))
end
должны быть определены для возврата генератора выборки с предварительно вычисленными данными, а затем
function rand(rng::AbstractRNG, sp::SamplerSimple{<:DiscreteDistribution})
draw_number(rng, sp.data)
end
будут использованы для генерации значений.
Настраиваемые типы генераторов выборок
Тип SamplerSimple достаточен для большинства случаев использования с предварительно вычисленными данными. Однако, чтобы продемонстрировать, как использовать настраиваемые типы генераторов выборок, здесь мы реализуем нечто подобное SamplerSimple.
Возвращаясь к нашему Die примеру: rand(::Die) использует генерацию случайных чисел из диапазона, поэтому есть возможность этой оптимизации. Мы называем наш настраиваемый генератор SamplerDie.
import Random: Sampler, rand
struct SamplerDie <: Sampler{Int} # generates values of type Int
die::Die
sp::Sampler{Int} # this is an abstract type, so this could be improved
end
Sampler(RNG::Type{<:AbstractRNG}, die::Die, r::Random.Repetition) =
SamplerDie(die, Sampler(RNG, 1:die.nsides, r))
# the `r` parameter will be explained later on
rand(rng::AbstractRNG, sp::SamplerDie) = rand(rng, sp.sp)
Теперь можно получить генератор с sp = Sampler(rng, die), и использовать sp вместо die в любом rand вызове, включающем rng. В упрощённом примере выше, die не нужно хранить в SamplerDie, но это часто бывает на практике.
Конечно, эта схема настолько часта, что вспомогательный тип, используемый выше, а именно Random.SamplerSimple, доступен, что экономит нам определение SamplerDie: мы могли бы реализовать нашу декомпозицию с:
Sampler(RNG::Type{<:AbstractRNG}, die::Die, r::Random.Repetition) =
SamplerSimple(die, Sampler(RNG, 1:die.nsides, r))
rand(rng::AbstractRNG, sp::SamplerSimple{Die}) = rand(rng, sp.data)
Здесь sp.data относится ко второму параметру в вызове конструктора SamplerSimple (в этом случае равен Sampler(rng, 1:die.nsides, r)), в то время как объект Die можно получить через sp[].
Как и SamplerDie, любой настраиваемый генератор должен быть подтипом Sampler{T}, где T является типом генерируемых значений. Обратите внимание, что SamplerSimple(x, data) isa Sampler{eltype(x)}, поэтому это ограничивает то, каким может быть первый аргумент SamplerSimple (рекомендуется использовать SamplerSimple как в примере Die, где x просто передаётся при определении метода Sampler). Аналогично, SamplerTrivial(x) isa Sampler{eltype(x)}.
В настоящее время доступен ещё один вспомогательный тип для других случаев, Random.SamplerTag, но он рассматривается как внутренний API и может сломаться в любое время без надлежащей деприкации.
Использование различных алгоритмов для генерации скаляров или массивов
В некоторых случаях то, нужно ли сгенерировать лишь несколько значений или большое количество, повлияет на выбор алгоритма. Это обрабатывается третьим параметром конструктора Sampler . Предположим, мы определили два вспомогательных типа для Die, скажем, SamplerDie1, который должен использоваться для генерации лишь нескольких случайных значений, и SamplerDieMany для многих значений. Мы можем использовать эти типы следующим образом:
Sampler(RNG::Type{<:AbstractRNG}, die::Die, ::Val{1}) = SamplerDie1(...)
Sampler(RNG::Type{<:AbstractRNG}, die::Die, ::Val{Inf}) = SamplerDieMany(...)
Конечно, rand также должен быть определён на этих типах (т.е. rand(::AbstractRNG, ::SamplerDie1) и rand(::AbstractRNG, ::SamplerDieMany)). Обратите внимание, что, как обычно, SamplerTrivial и SamplerSimple могут быть использованы, если настраиваемые типы не нужны.
Примечание: Sampler(rng, x) просто сокращение для Sampler(rng, x, Val(Inf)), а Random.Repetition – псевдоним для Union{Val{1}, Val{Inf}}.
Создание новых генераторов
API пока не определён чётко, но как правило:
- любой метод
rand, производящий «базовые» типы (isbitstypeцелые и плавающие типы вBase) должен быть определён для этого конкретного генератора случайных чисел, если они нужны; - другие документированные методы
rand, принимающиеAbstractRNG, должны работать «из коробки» (при условии реализации методов из 1), на которых они полагаются), но, конечно, могут быть специализированы для этого генератора случайных чисел, если есть возможность оптимизации; -
copyдля псевдогенераторов случайных чисел должен возвращать независимую копию, генерирующую ровно ту же последовательность случайных чисел, что и оригинал с того момента, когда вызывается аналогичным образом. Когда это не возможно (например, генераторы случайных чисел на основе оборудования),copyне должен быть реализован.
Что касается 1), метод rand может работать автоматически, но это не поддерживается официально и может сломаться без предупреждений в последующих версиях.
Для определения нового метода rand для гипотетического генератора MyRNG и спецификации значений s (например, s == Int, или s == 1:10 ) типа S==typeof(s) или S==Type{s} если s является типом, должны быть определены те же два метода, что и раньше:
-
Sampler(::Type{MyRNG}, ::S, ::Repetition), который возвращает объект типа, скажем,SamplerS rand(rng::MyRNG, sp::SamplerS)
Может оказаться, что Sampler(rng::AbstractRNG, ::S, ::Repetition) уже определён в модуле Random. Тогда на практике можно пропустить шаг 1) (если нужно специализировать генерацию для этого конкретного типа генератора случайных чисел), но соответствующий тип SamplerS считается внутренней деталью и может быть изменён без предупреждения.
Специализация генерации массивов
В некоторых случаях для данного типа генератора случайных чисел генерация массива случайных значений может быть более эффективной с помощью специализированного метода, чем просто использование описанной ранее техники декомпозиции. Это, например, случай с MersenneTwister, который напрямую записывает случайные значения в массив.
Для реализации этой специализации для MyRNG и спецификации s, производящей элементы типа S, можно определить следующий метод: rand!(rng::MyRNG, a::AbstractArray{S}, ::SamplerS), где SamplerS – тип генератора, возвращаемый Sampler(MyRNG, s, Val(Inf)). Вместо AbstractArray, можно реализовать функциональность только для подтипа, например, Array{S}. Неизменяющий метод массива rand будет автоматически вызывать эту специализацию внутри.
Воспроизводимость
Используя генератор случайных чисел, инициализированный заданным начальным значением, можно воспроизвести ту же последовательность псевдослучайных чисел при многократном запуске программы. Однако незначительный релиз 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.11/stdlib/Random/