Skip to content

Commit bc99be3

Browse files
committed
ENH: Ziggurat method for exponential generator
Add ziggurat method for exponential Paths for both float and double Add to docs
1 parent 97a5511 commit bc99be3

File tree

11 files changed

+1352
-420
lines changed

11 files changed

+1352
-420
lines changed

ci/performance.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
functions = {'randint': {'low': 2 ** 31, 'dtype': 'uint32'},
2626
'random_sample': {},
2727
'random_raw': {'output': False},
28-
'standard_exponential': {},
28+
'standard_exponential': {'method': 'zig'},
2929
'standard_gamma': {'shape': 2.4},
3030
'standard_normal': {'method': 'zig'},
3131
'multinomial': {'n': 20, 'pvals': [1.0 / 6.0] * np.ones(6)},
@@ -41,7 +41,7 @@ def timer(prng: str, fn: str, args: dict):
4141
# Differences with NumPy
4242
if fn in ('random_raw', 'complex_normal'):
4343
return np.nan
44-
if fn == 'standard_normal':
44+
if fn in ('standard_normal','standard_exponential'):
4545
args = {k: v for k, v in args.items() if k != 'method'}
4646
elif prng == 'mt19937' and fn == 'random_raw': # To make comparable
4747
args['size'] = 2 * args['size']
@@ -125,4 +125,4 @@ def timer(prng: str, fn: str, args: dict):
125125
lines.insert(1, ' \n')
126126
lines.insert(1, ' :widths: 14,14,14,14,14,14,14,14\n')
127127
lines.insert(0, '.. csv-table::\n')
128-
print(''.join(lines))
128+
print(''.join(lines))

doc/source/change-log.rst

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,22 @@ Change Log
55

66
Since Version 1.13
77
------------------
8-
* Add SIMD-oriented Fast Mersenne Twister (`SFMT <http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/>`) generator.
8+
* Add Ziggurat generator for standard exponential
9+
(:meth:`~randomstate.prng.mt19937.standard_exponential`) for both floats and
10+
doubles
11+
12+
.. ipython:: python
13+
14+
import numpy as np
15+
import randomstate as rs
16+
rs.seed(23456)
17+
rs.standard_exponential(3, method='zig') # New method
18+
19+
rs.standard_exponential(3, method='inv') # Old method
20+
21+
22+
* Add SIMD-oriented Fast Mersenne Twister
23+
(`SFMT <http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/>`_) generator.
924
* Add complex normal (:meth:`~randomstate.prng.mt19937.complex_normal`)
1025

1126
Version 1.13

doc/source/index.rst

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,16 @@ What's New or Different
1010
``/dev/urandom`` on Unix).
1111
* Simulate from the complex normal distribution
1212
(:meth:`~randomstate.prng.mt19937.complex_normal`)
13-
* The normal generator supports a 256-step Ziggurat method which is 2-6 times
14-
faster than NumPy's ``standard_normal``. This generator can be accessed
15-
by passing the keyword argument ``method='zig'``.
13+
* The both the normal and exponential generators support 256-step Ziggurat
14+
methods which are 2-6 times faster than NumPy's default implementation in
15+
``standard_normal`` or ``standard_exponential``. This Ziggurat generator can
16+
be accessed by passing the keyword argument ``method='zig'``.
1617

1718
.. ipython:: python
1819
19-
from randomstate import standard_normal
20+
from randomstate import standard_normal, standard_exponential
2021
standard_normal(100000, method='zig')
22+
standard_exponential(100000, method='zig')
2123
2224
* Optional ``dtype`` argument that accepts ``np.float32`` or ``np.float64``
2325
to produce either single or double prevision uniform random variables for
@@ -45,7 +47,7 @@ What's New or Different
4547
* Standard Gammas (:meth:`~randomstate.prng.mt19937.standard_gamma`)
4648
* Standard Exponentials (:meth:`~randomstate.prng.mt19937.standard_exponential`)
4749

48-
This allows mulththreading to fill large arrays in chunks using suitable
50+
This allows multithreading to fill large arrays in chunks using suitable
4951
PRNGs in parallel.
5052

5153
.. ipython:: python

papers/ziggurat.pdf

59.2 KB
Binary file not shown.

randomstate/distributions.c

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1557,3 +1557,99 @@ void random_bounded_bool_fill(aug_state *state, npy_bool off, npy_bool rng, npy_
15571557
out[i] = (buf & 0x00000001) != 0;
15581558
}
15591559
}
1560+
1561+
static inline double standard_exponential_zig_double(aug_state* state);
1562+
1563+
static double standard_exponential_zig_double_unlikely(aug_state* state, uint8_t idx, double x)
1564+
{
1565+
if (idx == 0)
1566+
{
1567+
return ziggurat_exp_r - log(random_double(state));
1568+
}
1569+
else if ((fe_double[idx - 1] - fe_double[idx]) * random_double(state) + fe_double[idx] < exp(-x))
1570+
{
1571+
return x;
1572+
}
1573+
else
1574+
{
1575+
return standard_exponential_zig_double(state);
1576+
}
1577+
}
1578+
1579+
static inline double standard_exponential_zig_double(aug_state* state)
1580+
{
1581+
uint64_t ri;
1582+
uint8_t idx;
1583+
double x;
1584+
ri = random_uint64(state);
1585+
ri >>= 3;
1586+
idx = ri & 0xFF;
1587+
ri >>= 8;
1588+
x = ri * we_double[idx];
1589+
if (ri < ke_double[idx]){
1590+
return x; // 98.9% of the time we return here 1st try
1591+
}
1592+
return standard_exponential_zig_double_unlikely(state, idx, x);
1593+
}
1594+
1595+
double random_standard_exponential_zig_double(aug_state* state)
1596+
{
1597+
return standard_exponential_zig_double(state);
1598+
}
1599+
1600+
void random_standard_exponential_zig_double_fill(aug_state* state, npy_intp count, double *out)
1601+
{
1602+
npy_intp i;
1603+
for (i=0; i < count; i++) {
1604+
out[i] = standard_exponential_zig_double(state);
1605+
}
1606+
}
1607+
1608+
static inline float standard_exponential_zig_float(aug_state* state);
1609+
1610+
static double standard_exponential_zig_float_unlikely(aug_state* state, uint8_t idx, float x)
1611+
{
1612+
if (idx == 0)
1613+
{
1614+
// TODO: Replace with float ?
1615+
return ziggurat_exp_r - logf(random_float(state));
1616+
}
1617+
else if ((fe_float[idx - 1] - fe_float[idx]) * random_float(state) + fe_float[idx] < expf(-x))
1618+
{
1619+
return x;
1620+
}
1621+
else
1622+
{
1623+
return standard_exponential_zig_float(state);
1624+
}
1625+
}
1626+
1627+
static inline float standard_exponential_zig_float(aug_state* state)
1628+
{
1629+
uint32_t ri;
1630+
uint8_t idx;
1631+
float x;
1632+
ri = random_uint32(state);
1633+
ri >>= 1;
1634+
idx = ri & 0xFF;
1635+
ri >>= 8;
1636+
x = ri * we_float[idx];
1637+
if (ri < ke_float[idx]){
1638+
return x; // 98.9% of the time we return here 1st try
1639+
}
1640+
return standard_exponential_zig_float_unlikely(state, idx, x);
1641+
}
1642+
1643+
1644+
float random_standard_exponential_zig_float(aug_state* state)
1645+
{
1646+
return standard_exponential_zig_float(state);
1647+
}
1648+
1649+
void random_standard_exponential_zig_float_fill(aug_state* state, npy_intp count, float *out)
1650+
{
1651+
npy_intp i;
1652+
for (i=0; i < count; i++) {
1653+
out[i] = standard_exponential_zig_float(state);
1654+
}
1655+
}

randomstate/distributions.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,4 +162,12 @@ extern void random_gauss_zig_julia_fill(aug_state *state, npy_intp count, double
162162

163163
extern void random_gauss_zig_float_fill(aug_state *state, npy_intp count, float *out);
164164

165-
extern void random_gauss_zig_double_fill(aug_state *state, npy_intp count, double *out);
165+
extern void random_gauss_zig_double_fill(aug_state *state, npy_intp count, double *out);
166+
167+
extern double random_standard_exponential_zig(aug_state* state);
168+
169+
extern void random_standard_exponential_zig_double_fill(aug_state* state, npy_intp count, double *out);
170+
171+
extern float random_standard_exponential_zig_float(aug_state* state);
172+
173+
extern void random_standard_exponential_zig_float_fill(aug_state* state, npy_intp count, float *out);

randomstate/randomstate.pyx

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ cdef extern from "distributions.h":
8080
cdef double random_gauss_zig(aug_state* state) nogil
8181
cdef double random_gauss_zig_julia(aug_state* state) nogil
8282
cdef double random_standard_exponential(aug_state* state) nogil
83+
cdef double random_standard_exponential_ziggurat(aug_state* state) nogil
8384
cdef double random_standard_cauchy(aug_state* state) nogil
8485

8586
cdef double random_exponential(aug_state *state, double scale) nogil
@@ -124,12 +125,14 @@ cdef extern from "distributions.h":
124125

125126
cdef void random_gauss_zig_float_fill(aug_state *state, intptr_t count, float *out) nogil
126127
cdef void random_uniform_fill_float(aug_state *state, intptr_t cnt, double *out) nogil
128+
cdef void random_standard_exponential_zig_float_fill(aug_state* state, intptr_t count, float *out) nogil
127129
cdef void random_standard_exponential_fill_float(aug_state* state, intptr_t count, float *out) nogil
128130
cdef void random_gauss_fill_float(aug_state* state, intptr_t count, float *out) nogil
129131

130132
cdef void random_gauss_zig_double_fill(aug_state* state, intptr_t count, double *out) nogil
131133
cdef void random_uniform_fill_double(aug_state *state, intptr_t cnt, double *out) nogil
132134
cdef void random_standard_exponential_fill_double(aug_state* state, intptr_t count, double *out) nogil
135+
cdef void random_standard_exponential_zig_double_fill(aug_state* state, intptr_t count, double *out) nogil
133136
cdef void random_gauss_fill(aug_state* state, intptr_t count, double *out) nogil
134137
cdef void random_gauss_zig_julia_fill(aug_state* state, intptr_t count, double *out) nogil
135138

@@ -2014,9 +2017,9 @@ cdef class RandomState:
20142017
0.0, '', CONS_NONE,
20152018
None)
20162019

2017-
def standard_exponential(self, size=None, dtype=np.float64, out=None):
2020+
def standard_exponential(self, size=None, dtype=np.float64, method=u'inv', out=None):
20182021
"""
2019-
standard_exponential(size=None, dtype=np.float64, out=None)
2022+
standard_exponential(size=None, dtype=np.float64, method='inv', out=None)
20202023
20212024
Draw samples from the standard exponential distribution.
20222025
@@ -2033,6 +2036,9 @@ cdef class RandomState:
20332036
Desired dtype of the result. All dtypes are determined by their
20342037
name, either 'float64' or 'float32'. The default value is
20352038
'float64'.
2039+
method : str, optional
2040+
Either 'inv' or 'zig'. 'inv' uses the default inverse CDF method.
2041+
'zig' uses the much faster Ziggurat method of Marsaglia and Tsang.
20362042
out : ndarray, optional
20372043
Alternative output array in which to place the result. If size is not None,
20382044
it must have the same shape as the provided size and must match the type of
@@ -2048,17 +2054,28 @@ cdef class RandomState:
20482054
Output a 3x8000 array:
20492055
20502056
>>> n = np.random.standard_exponential((3, 8000))
2051-
20522057
"""
2058+
if method != u'zig' and method != u'inv':
2059+
raise ValueError("method must be either 'bm' or 'zig'")
20532060
key = np.dtype(dtype).name
20542061
if key == 'float64':
2055-
return double_fill(&self.rng_state,
2056-
&random_standard_exponential_fill_double,
2057-
size, self.lock, out)
2062+
if method == 'zig':
2063+
return double_fill(&self.rng_state,
2064+
&random_standard_exponential_zig_double_fill,
2065+
size, self.lock, out)
2066+
else:
2067+
return double_fill(&self.rng_state,
2068+
&random_standard_exponential_fill_double,
2069+
size, self.lock, out)
20582070
elif key == 'float32':
2059-
return float_fill(&self.rng_state,
2060-
&random_standard_exponential_fill_float,
2061-
size, self.lock, out)
2071+
if method == 'zig':
2072+
return float_fill(&self.rng_state,
2073+
&random_standard_exponential_zig_float_fill,
2074+
size, self.lock, out)
2075+
else:
2076+
return float_fill(&self.rng_state,
2077+
&random_standard_exponential_fill_float,
2078+
size, self.lock, out)
20622079
else:
20632080
raise TypeError('Unsupported dtype "%s" for standard_exponential'
20642081
% key)

randomstate/src/xoroshiro128plus/xoroshiro128plus.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
extern inline uint64_t xoroshiro128plus_next(xoroshiro128plus_state* state);
55

6-
static inline uint64_t rotl(const uint64_t x, int k);
6+
inline uint64_t rotl(const uint64_t x, int k);
77

88
void xoroshiro128plus_jump(xoroshiro128plus_state* state) {
99
static const uint64_t JUMP[] = { 0xbeac0467eba5facb, 0xd86b048b86aa9922 };

randomstate/src/xoroshiro128plus/xoroshiro128plus.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ void xoroshiro128plus_seed_by_array(xoroshiro128plus_state* state, uint64_t *see
2020

2121
void xoroshiro128plus_init_state(xoroshiro128plus_state* state, uint64_t seed, uint64_t inc);
2222

23-
static inline uint64_t rotl(const uint64_t x, int k) {
23+
inline uint64_t rotl(const uint64_t x, int k) {
2424
return (x << k) | (x >> (64 - k));
2525
}
2626

0 commit comments

Comments
 (0)