summaryrefslogtreecommitdiff
path: root/doc/source/reference/random/extending.rst
diff options
context:
space:
mode:
authormattip <matti.picus@gmail.com>2019-04-12 10:27:33 +0300
committermattip <matti.picus@gmail.com>2019-05-20 18:45:27 +0300
commit9578dcfbe744854312690ea79063e50d67fc88a2 (patch)
treec81fd1e43aa3a2a5124aae8575dcb427746b8ef9 /doc/source/reference/random/extending.rst
parentc53b2eb729bae1f248a2654dfcfa4a3dd3e2902b (diff)
downloadnumpy-9578dcfbe744854312690ea79063e50d67fc88a2.tar.gz
BUG: __dealloc__ can be called without __init__ in some error modes
skip doctests that require scipy move original mtrand module to _mtrand adjust documentation for namespace change
Diffstat (limited to 'doc/source/reference/random/extending.rst')
-rw-r--r--doc/source/reference/random/extending.rst167
1 files changed, 167 insertions, 0 deletions
diff --git a/doc/source/reference/random/extending.rst b/doc/source/reference/random/extending.rst
new file mode 100644
index 000000000..f76e3984f
--- /dev/null
+++ b/doc/source/reference/random/extending.rst
@@ -0,0 +1,167 @@
+.. currentmodule:: numpy.random
+
+Extending
+---------
+The basic RNGs have been designed to be extendable using standard tools for
+high-performance Python -- numba and Cython.
+The `~RandomGenerator` object can also be used with
+user-provided basic RNGs as long as these export a small set of required
+functions.
+
+Numba
+=====
+Numba can be used with either CTypes or CFFI. The current iteration of the
+basic RNGs all export a small set of functions through both interfaces.
+
+This example shows how numba can be used to produce Box-Muller normals using
+a pure Python implementation which is then compiled. The random numbers are
+provided by ``ctypes.next_double``.
+
+.. code-block:: python
+
+ from numpy.random import Xoroshiro128
+ import numpy as np
+ import numba as nb
+
+ x = Xoroshiro128()
+ f = x.ctypes.next_double
+ s = x.ctypes.state
+ state_addr = x.ctypes.state_address
+
+ def normals(n, state):
+ out = np.empty(n)
+ for i in range((n+1)//2):
+ x1 = 2.0*f(state) - 1.0
+ x2 = 2.0*f(state) - 1.0
+ r2 = x1*x1 + x2*x2
+ while r2 >= 1.0 or r2 == 0.0:
+ x1 = 2.0*f(state) - 1.0
+ x2 = 2.0*f(state) - 1.0
+ r2 = x1*x1 + x2*x2
+ g = np.sqrt(-2.0*np.log(r2)/r2)
+ out[2*i] = g*x1
+ if 2*i+1 < n:
+ out[2*i+1] = g*x2
+ return out
+
+ # Compile using Numba
+ print(normals(10, s).var())
+ # Warm up
+ normalsj = nb.jit(normals, nopython=True)
+ # Must use state address not state with numba
+ normalsj(1, state_addr)
+ %timeit normalsj(1000000, state_addr)
+ print('1,000,000 Box-Muller (numba/Xoroshiro128) randoms')
+ %timeit np.random.standard_normal(1000000)
+ print('1,000,000 Box-Muller (NumPy) randoms')
+
+
+Both CTypes and CFFI allow the more complicated distributions to be used
+directly in Numba after compiling the file distributions.c into a DLL or so.
+An example showing the use of a more complicated distribution is in the
+examples folder.
+
+.. _randomgen_cython:
+
+Cython
+======
+
+Cython can be used to unpack the ``PyCapsule`` provided by a basic RNG.
+This example uses `~xoroshiro128.Xoroshiro128` and
+``random_gauss_zig``, the Ziggurat-based generator for normals, to fill an
+array. The usual caveats for writing high-performance code using Cython --
+removing bounds checks and wrap around, providing array alignment information
+-- still apply.
+
+.. code-block:: cython
+
+ import numpy as np
+ cimport numpy as np
+ cimport cython
+ from cpython.pycapsule cimport PyCapsule_IsValid, PyCapsule_GetPointer
+ from numpy.random.common cimport *
+ from numpy.random.distributions cimport random_gauss_zig
+ from numpy.random.xoroshiro128 import Xoroshiro128
+
+
+ @cython.boundscheck(False)
+ @cython.wraparound(False)
+ def normals_zig(Py_ssize_t n):
+ cdef Py_ssize_t i
+ cdef brng_t *rng
+ cdef const char *capsule_name = "BasicRNG"
+ cdef double[::1] random_values
+
+ x = Xoroshiro128()
+ capsule = x.capsule
+ # Optional check that the capsule if from a Basic RNG
+ if not PyCapsule_IsValid(capsule, capsule_name):
+ raise ValueError("Invalid pointer to anon_func_state")
+ # Cast the pointer
+ rng = <brng_t *> PyCapsule_GetPointer(capsule, capsule_name)
+ random_values = np.empty(n)
+ for i in range(n):
+ # Call the function
+ random_values[i] = random_gauss_zig(rng)
+ randoms = np.asarray(random_values)
+ return randoms
+
+
+The basic RNG can also be directly accessed using the members of the basic
+RNG structure.
+
+.. code-block:: cython
+
+ @cython.boundscheck(False)
+ @cython.wraparound(False)
+ def uniforms(Py_ssize_t n):
+ cdef Py_ssize_t i
+ cdef brng_t *rng
+ cdef const char *capsule_name = "BasicRNG"
+ cdef double[::1] random_values
+
+ x = Xoroshiro128()
+ capsule = x.capsule
+ # Optional check that the capsule if from a Basic RNG
+ if not PyCapsule_IsValid(capsule, capsule_name):
+ raise ValueError("Invalid pointer to anon_func_state")
+ # Cast the pointer
+ rng = <brng_t *> PyCapsule_GetPointer(capsule, capsule_name)
+ random_values = np.empty(n)
+ for i in range(n):
+ # Call the function
+ random_values[i] = rng.next_double(rng.state)
+ randoms = np.asarray(random_values)
+ return randoms
+
+These functions along with a minimal setup file are included in the
+examples folder.
+
+New Basic RNGs
+==============
+`~RandomGenerator` can be used with other
+user-provided basic RNGs. The simplest way to write a new basic RNG is to
+examine the pyx file of one of the existing basic RNGs. The key structure
+that must be provided is the ``capsule`` which contains a ``PyCapsule`` to a
+struct pointer of type ``brng_t``,
+
+.. code-block:: c
+
+ typedef struct brng {
+ 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);
+ } brng_t;
+
+which provides 5 pointers. The first is an opaque pointer to the data structure
+used by the basic RNG. The next three are function pointers which return the
+next 64- and 32-bit unsigned integers, the next random double and the next
+raw value. This final function is used for testing and so can be set to
+the next 64-bit unsigned integer function if not needed. Functions inside
+``RandomGenerator`` use this structure as in
+
+.. code-block:: c
+
+ brng_state->next_uint64(brng_state->state)