Spec-Zone.ru › Julia 1.11

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

Генерация случайных чисел в 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:

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

  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.11/stdlib/Random/

Spec-Zone.ru

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