Расширение
Генераторы случайных чисел BitGenerators разработаны для расширения с помощью стандартных инструментов для высокопроизводительного Python – numba и Cython. Объект Generator также может использоваться с пользовательскими генераторами BitGenerators, если они экспортируют небольшой набор необходимых функций.
Numba
Numba может использоваться с CTypes или CFFI. Текущая итерация генераторов BitGenerators экспортирует небольшой набор функций через оба интерфейса.
Этот пример показывает, как numba может использоваться для генерации гауссовых выборок с использованием чистого Python-реализации, которая затем компилируется. Случайные числа предоставляются ctypes.next_double.
import numpy as np
import numba as nb
from numpy.random import PCG64
from timeit import timeit
bit_gen = PCG64()
next_d = bit_gen.cffi.next_double
state_addr = bit_gen.cffi.state_address
def normals(n, state):
out = np.empty(n)
for i in range((n + 1) // 2):
x1 = 2.0 * next_d(state) - 1.0
x2 = 2.0 * next_d(state) - 1.0
r2 = x1 * x1 + x2 * x2
while r2 >= 1.0 or r2 == 0.0:
x1 = 2.0 * next_d(state) - 1.0
x2 = 2.0 * next_d(state) - 1.0
r2 = x1 * x1 + x2 * x2
f = np.sqrt(-2.0 * np.log(r2) / r2)
out[2 * i] = f * x1
if 2 * i + 1 < n:
out[2 * i + 1] = f * x2
return out
# Compile using Numba
normalsj = nb.jit(normals, nopython=True)
# Must use state address not state with numba
n = 10000
def numbacall():
return normalsj(n, state_addr)
rg = np.random.Generator(PCG64())
def numpycall():
return rg.normal(size=n)
# Check that the functions work
r1 = numbacall()
r2 = numpycall()
assert r1.shape == (n,)
assert r1.shape == r2.shape
t1 = timeit(numbacall, number=1000)
print('{:.2f} secs for {} PCG64 (Numba/PCG64) gaussian randoms'.format(t1, n))
t2 = timeit(numpycall, number=1000)
print('{:.2f} secs for {} PCG64 (NumPy/PCG64) gaussian randoms'.format(t2, n))
И CTypes, и CFFI позволяют использовать более сложные распределения непосредственно в Numba после компиляции файла distributions.c в DLL или so. Пример использования более сложного распределения находится в разделе examples ниже.
Cython
Cython может использоваться для распаковки PyCapsule, предоставляемых генератором BitGenerator. В этом примере используется PCG64 и пример из вышеуказанного раздела. Обычные замечания по написанию высокопроизводительного кода с использованием Cython – удаление проверок границ и обхода, предоставление информации о выравнивании массивов – по-прежнему действуют.
#!/usr/bin/env python
#cython: language_level=3
"""
This file shows how the to use a BitGenerator to create a distribution.
"""
import numpy as np
cimport numpy as np
cimport cython
from cpython.pycapsule cimport PyCapsule_IsValid, PyCapsule_GetPointer
from libc.stdint cimport uint16_t, uint64_t
from numpy.random cimport bitgen_t
from numpy.random import PCG64
@cython.boundscheck(False)
@cython.wraparound(False)
def uniforms(Py_ssize_t n):
"""
Create an array of `n` uniformly distributed doubles.
A 'real' distribution would want to process the values into
some non-uniform distribution
"""
cdef Py_ssize_t i
cdef bitgen_t *rng
cdef const char *capsule_name = "BitGenerator"
cdef double[::1] random_values
x = PCG64()
capsule = x.capsule
# Optional check that the capsule if from a BitGenerator
if not PyCapsule_IsValid(capsule, capsule_name):
raise ValueError("Invalid pointer to anon_func_state")
# Cast the pointer
rng = <bitgen_t *> PyCapsule_GetPointer(capsule, capsule_name)
random_values = np.empty(n, dtype='float64')
with x.lock, nogil:
for i in range(n):
# Call the function
random_values[i] = rng.next_double(rng.state)
randoms = np.asarray(random_values)
return randoms
Генератор BitGenerator также можно напрямую получить, используя члены структуры базового генератора RNG.
@cython.boundscheck(False)
@cython.wraparound(False)
def uint10_uniforms(Py_ssize_t n):
"""Uniform 10 bit integers stored as 16-bit unsigned integers"""
cdef Py_ssize_t i
cdef bitgen_t *rng
cdef const char *capsule_name = "BitGenerator"
cdef uint16_t[::1] random_values
cdef int bits_remaining
cdef int width = 10
cdef uint64_t buff, mask = 0x3FF
x = PCG64()
capsule = x.capsule
if not PyCapsule_IsValid(capsule, capsule_name):
raise ValueError("Invalid pointer to anon_func_state")
rng = <bitgen_t *> PyCapsule_GetPointer(capsule, capsule_name)
random_values = np.empty(n, dtype='uint16')
# Best practice is to release GIL and acquire the lock
bits_remaining = 0
with x.lock, nogil:
for i in range(n):
if bits_remaining < width:
buff = rng.next_uint64(rng.state)
random_values[i] = buff & mask
buff >>= width
randoms = np.asarray(random_values)
return randoms
См. Расширение numpy.random через Cython для полного рабочего примера, включая минимальную настройку и файлы cython.
CFFI
CFFI может использоваться для прямого доступа к функциям в include/numpy/random/distributions.h. Требуется некоторое «обработка» заголовочного файла:
"""
Use cffi to access any of the underlying C functions from distributions.h
"""
import os
import numpy as np
import cffi
from .parse import parse_distributions_h
ffi = cffi.FFI()
inc_dir = os.path.join(np.get_include(), 'numpy')
# Basic numpy types
ffi.cdef('''
typedef intptr_t npy_intp;
typedef unsigned char npy_bool;
''')
parse_distributions_h(ffi, inc_dir)
После того, как заголовочный файл был проанализирован ffi.cdef, к функциям можно получить прямой доступ из _generator разделяемой библиотеки, используя интерфейс BitGenerator.cffi.
# Compare the distributions.h random_standard_normal_fill to
# Generator.standard_random
bit_gen = np.random.PCG64()
rng = np.random.Generator(bit_gen)
state = bit_gen.state
interface = rng.bit_generator.cffi
n = 100
vals_cffi = ffi.new('double[%d]' % n)
lib.random_standard_normal_fill(interface.bit_generator, n, vals_cffi)
# reset the state
bit_gen.state = state
vals = rng.standard_normal(n)
for i in range(n):
assert vals[i] == vals_cffi[i]
Новые базовые генераторы RNG
Generator может использоваться с другими пользовательскими генераторами BitGenerators. Простейший способ написать новый генератор BitGenerator – изучить файл pyx одного из существующих генераторов. Ключевая структура, которая должна быть предоставлена, – это capsule, содержащий PyCapsule на указатель структуры типа bitgen_t,
typedef struct bitgen {
void *state;
uint64_t (*next_uint64)(void *st);
uint32_t (*next_uint32)(void *st);
double (*next_double)(void *st);
uint64_t (*next_raw)(void *st);
} bitgen_t;
который предоставляет 5 указателей. Первый – это неявный указатель на структуру данных, используемую генераторами BitGenerators. Следующие три – указатели на функции, которые возвращают следующие 64- и 32-битные целые числа без знака, следующее случайное двойное значение и следующее значение в сыром виде. Эта последняя функция используется для тестирования и поэтому может быть установлена на функцию следующего 64-битного целого числа без знака, если она не нужна. Функции внутри Generator используют эту структуру, как в
bitgen_state->next_uint64(bitgen_state->state)
Примеры
© 2005–2020 NumPy Developers
Licensed under the 3-clause BSD License.
https://numpy.org/doc/1.18/reference/random/extending.html