Skip to content

Commit 487cd1f

Browse files
authored
Merge pull request #102 from bashtage/fast-gamma
ENH: Ziggurat for gammas
2 parents 13fd21b + 1d77a40 commit 487cd1f

File tree

7 files changed

+224
-50
lines changed

7 files changed

+224
-50
lines changed

README.md

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,24 @@ z = rnd.randn(10,10)
2222
* Default random generator is identical to NumPy's RandomState (i.e.,
2323
same seed, same random numbers).
2424
* Support for random number generators that support independent streams
25-
and jumping ahead so that substreams can be generated
26-
* Faster random number generation, especially for Normals using the
27-
Ziggurat method
25+
and jumping ahead so that sub-streams can be generated
26+
* Faster random number generation, especially for normal, standard
27+
exponential and standard gamma using the Ziggurat method
2828

2929
```python
3030
import randomstate as rnd
3131
w = rnd.standard_normal(10000, method='zig')
32+
x = rnd.standard_exponential(10000, method='zig')
33+
y = rnd.standard_gamma(5.5, 10000, method='zig')
3234
```
3335

3436
* Support for 32-bit floating randoms for core generators.
3537
Currently supported:
3638

3739
* Uniforms (`random_sample`)
38-
* Exponentials (`standard_exponential`)
40+
* Exponentials (`standard_exponential`, both Inverse CDF and Ziggurat)
3941
* Normals (`standard_normal`, both Box-Muller and Ziggurat)
40-
* Standard Gammas (via `standard_gamma`)
42+
* Standard Gammas (via `standard_gamma`, both Inverse CDF and Ziggurat)
4143

4244
**WARNING**: The 32-bit generators are **experimental** and subject
4345
to change.
@@ -64,7 +66,10 @@ The RNGs include:
6466
* [dSFMT](http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/) a
6567
SSE2-aware version of the MT19937 generator that is especially fast at
6668
generating doubles
67-
* [xorshift128+](http://xorshift.di.unimi.it/),
69+
* [SFMT](http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/) a
70+
SSE2-aware version of the MT19937 generator that is optimized for
71+
integer values
72+
* [xorshift128+](http://xorshift.di.unimi.it/),
6873
[xoroshiro128+](http://xoroshiro.di.unimi.it/) and
6974
[xorshift1024*](http://xorshift.di.unimi.it/)
7075
* [PCG32](http://www.pcg-random.org/) and
@@ -76,14 +81,18 @@ The RNGs include:
7681

7782
### New Features
7883
* `standard_normal`, `normal`, `randn` and `multivariate_normal` all
79-
support an additional `method` keyword argument which can be `bm` or
80-
`zig` where `bm` corresponds to the current method using the Box-Muller
81-
transformation and `zig` uses the much faster (100%+) Ziggurat method.
84+
support an additional `method` keyword argument which can be `bm` or
85+
`zig` where `bm` corresponds to the current method using the Box-Muller
86+
transformation and `zig` uses the much faster (100%+) Ziggurat method.
87+
* `standard_exponential` and `standard_gamma` both support an additional
88+
`method` keyword argument which can be `inv` or
89+
`zig` where `inv` corresponds to the current method using the inverse
90+
CDF and `zig` uses the much faster (100%+) Ziggurat method.
8291
* Core random number generators can produce either single precision
83-
(`np.float32`) or double precision (`np.float64`, the default) using
84-
an the optional keyword argument `dtype`
92+
(`np.float32`) or double precision (`np.float64`, the default) using
93+
an the optional keyword argument `dtype`
8594
* Core random number generators can fill existing arrays using the
86-
`out` keyword argument
95+
`out` keyword argument
8796

8897

8998
### New Functions
@@ -110,8 +119,8 @@ will produce an identical sequence of random numbers for a given seed.
110119
* Linux 32/64 bit, Python 2.7, 3.4, 3.5, 3.6 (probably works on 2.6 and 3.3)
111120
* PC-BSD (FreeBSD) 64-bit, Python 2.7
112121
* OSX 64-bit, Python 2.7
113-
* Windows 32/64 bit (only tested on Python 2.7 and 3.5, but should
114-
work on 3.3/3.4)
122+
* Windows 32/64 bit (only tested on Python 2.7, 3.5 and 3.6, but
123+
should work on 3.3/3.4)
115124

116125
## Version
117126
The version matched the latest version of NumPy where
@@ -138,7 +147,7 @@ Building requires:
138147
* Cython (0.22, 0.23, 0.24, 0.25)
139148
* tempita (0.5+), if not provided by Cython
140149

141-
Testing requires nose (1.3+).
150+
Testing requires pytest (3.0+).
142151

143152
**Note:** it might work with other versions but only tested with these
144153
versions.

README.rst

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,26 @@ Features
2222
- Default random generator is identical to NumPy's RandomState (i.e.,
2323
same seed, same random numbers).
2424
- Support for random number generators that support independent streams
25-
and jumping ahead so that substreams can be generated
26-
- Faster random number generation, especially for Normals using the
27-
Ziggurat method
25+
and jumping ahead so that sub-streams can be generated
26+
- Faster random number generation, especially for normal, standard
27+
exponential and standard gamma using the Ziggurat method
2828

2929
.. code:: python
3030
3131
import randomstate as rnd
3232
w = rnd.standard_normal(10000, method='zig')
33+
x = rnd.standard_exponential(10000, method='zig')
34+
y = rnd.standard_gamma(5.5, 10000, method='zig')
3335
3436
- Support for 32-bit floating randoms for core generators. Currently
3537
supported:
3638

3739
- Uniforms (``random_sample``)
38-
- Exponentials (``standard_exponential``)
40+
- Exponentials (``standard_exponential``, both Inverse CDF and
41+
Ziggurat)
3942
- Normals (``standard_normal``, both Box-Muller and Ziggurat)
40-
- Standard Gammas (via ``standard_gamma``)
43+
- Standard Gammas (via ``standard_gamma``, both Inverse CDF and
44+
Ziggurat)
4145

4246
**WARNING**: The 32-bit generators are **experimental** and subject to
4347
change.
@@ -64,6 +68,9 @@ in addition to the MT19937 that is included in NumPy. The RNGs include:
6468
- `dSFMT <http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/>`__ a
6569
SSE2-aware version of the MT19937 generator that is especially fast
6670
at generating doubles
71+
- `SFMT <http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/>`__ a
72+
SSE2-aware version of the MT19937 generator that is optimized for
73+
integer values
6774
- `xorshift128+ <http://xorshift.di.unimi.it/>`__,
6875
`xoroshiro128+ <http://xoroshiro.di.unimi.it/>`__ and
6976
`xorshift1024\* <http://xorshift.di.unimi.it/>`__
@@ -83,6 +90,10 @@ New Features
8390
argument which can be ``bm`` or ``zig`` where ``bm`` corresponds to
8491
the current method using the Box-Muller transformation and ``zig``
8592
uses the much faster (100%+) Ziggurat method.
93+
- ``standard_exponential`` and ``standard_gamma`` both support an
94+
additional ``method`` keyword argument which can be ``inv`` or
95+
``zig`` where ``inv`` corresponds to the current method using the
96+
inverse CDF and ``zig`` uses the much faster (100%+) Ziggurat method.
8697
- Core random number generators can produce either single precision
8798
(``np.float32``) or double precision (``np.float64``, the default)
8899
using an the optional keyword argument ``dtype``
@@ -117,8 +128,8 @@ Status
117128
3.3)
118129
- PC-BSD (FreeBSD) 64-bit, Python 2.7
119130
- OSX 64-bit, Python 2.7
120-
- Windows 32/64 bit (only tested on Python 2.7 and 3.5, but should work
121-
on 3.3/3.4)
131+
- Windows 32/64 bit (only tested on Python 2.7, 3.5 and 3.6, but should
132+
work on 3.3/3.4)
122133

123134
Version
124135
-------
@@ -152,7 +163,7 @@ Building requires:
152163
- Cython (0.22, 0.23, 0.24, 0.25)
153164
- tempita (0.5+), if not provided by Cython
154165

155-
Testing requires nose (1.3+).
166+
Testing requires pytest (3.0+).
156167

157168
**Note:** it might work with other versions but only tested with these
158169
versions.

doc/source/change-log.rst

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@ Change Log
55

66
Since Version 1.13
77
------------------
8+
* Add Ziggurat generation for standard gamma
9+
(:meth:`~randomstate.prng.mt19937.standard_gamma`) for both floats and
10+
doubles. The gamma generator uses a rejection sampler that
11+
depends on random double, normal and/or exponential values.
12+
13+
.. ipython:: python
14+
15+
import numpy as np
16+
import randomstate as rs
17+
rs.seed(23456)
18+
rs.standard_gamma(3, method='zig') # New method
19+
20+
rs.standard_gamma(3, method='inv') # Old method
21+
822
* Add Ziggurat generator for standard exponential
923
(:meth:`~randomstate.prng.mt19937.standard_exponential`) for both floats and
1024
doubles
@@ -18,7 +32,6 @@ Since Version 1.13
1832
1933
rs.standard_exponential(3, method='inv') # Old method
2034
21-
2235
* Add SIMD-oriented Fast Mersenne Twister
2336
(`SFMT <http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/>`_) generator.
2437
* Add complex normal (:meth:`~randomstate.prng.mt19937.complex_normal`)
@@ -83,19 +96,16 @@ Version 1.11.1
8396

8497
Version 1.11
8598
------------
86-
8799
* Update to recent changes in NumPy's RandomState
88100
* Expose system entropy through :meth:`randomstate.entropy.random_entropy`
89101
* Add vector initialization for all PRNGs
90102

91103
Version 1.10.1
92104
--------------
93-
94105
* Added support for jumping the MRG32K3A generator
95106
* Added support for jumping the dSFMT generator
96107
* Update to recent changes in NumPy's RandomState
97108

98109
Version 1.10
99110
------------
100-
101111
* This is the initial release with compatibility with NumPy 1.10

doc/source/index.rst

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,18 @@ 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 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'``.
13+
* The normal, exponential and gamma generators support 256-step Ziggurat
14+
methods which are 2-10 times faster than NumPy's default implementation in
15+
``standard_normal``, ``standard_exponential`` or ``standard_gamma``.
16+
The Ziggurat generator can be accessed by passing the keyword
17+
argument ``method='zig'``.
1718

1819
.. ipython:: python
1920
2021
from randomstate import standard_normal, standard_exponential
21-
standard_normal(100000, method='zig')
22-
standard_exponential(100000, method='zig')
22+
x = standard_normal(100000, method='zig')
23+
y = standard_exponential(100000, method='zig')
24+
z = standard_gamma(100000, method='zig')
2325
2426
* Optional ``dtype`` argument that accepts ``np.float32`` or ``np.float64``
2527
to produce either single or double prevision uniform random variables for

randomstate/distributions.c

Lines changed: 123 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
*
1010
*/
1111

12+
/* Internal Prototypes */
13+
static inline double standard_exponential_zig_double(aug_state* state);
14+
static inline float standard_exponential_zig_float(aug_state* state);
15+
1216
static inline float random_float(aug_state* state)
1317
{
1418
return (random_uint32(state) >> 9) * (1.0f / 8388608.0f);
@@ -1558,8 +1562,6 @@ void random_bounded_bool_fill(aug_state *state, npy_bool off, npy_bool rng, npy_
15581562
}
15591563
}
15601564

1561-
static inline double standard_exponential_zig_double(aug_state* state);
1562-
15631565
static double standard_exponential_zig_double_unlikely(aug_state* state, uint8_t idx, double x)
15641566
{
15651567
if (idx == 0)
@@ -1605,9 +1607,7 @@ void random_standard_exponential_zig_double_fill(aug_state* state, npy_intp coun
16051607
}
16061608
}
16071609

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)
1610+
static float standard_exponential_zig_float_unlikely(aug_state* state, uint8_t idx, float x)
16111611
{
16121612
if (idx == 0)
16131613
{
@@ -1653,3 +1653,121 @@ void random_standard_exponential_zig_float_fill(aug_state* state, npy_intp count
16531653
out[i] = standard_exponential_zig_float(state);
16541654
}
16551655
}
1656+
1657+
static inline double standard_gamma_zig_double(aug_state* state, double shape)
1658+
{
1659+
double b, c;
1660+
double U, V, X, Y;
1661+
1662+
if (shape == 1.0)
1663+
{
1664+
return standard_exponential_zig_double(state);
1665+
}
1666+
else if (shape < 1.0)
1667+
{
1668+
for (;;)
1669+
{
1670+
U = random_double(state);
1671+
V = standard_exponential_zig_double(state);
1672+
if (U <= 1.0 - shape)
1673+
{
1674+
X = pow(U, 1./shape);
1675+
if (X <= V)
1676+
{
1677+
return X;
1678+
}
1679+
}
1680+
else
1681+
{
1682+
Y = -log((1-U)/shape);
1683+
X = pow(1.0 - shape + shape*Y, 1./shape);
1684+
if (X <= (V + Y))
1685+
{
1686+
return X;
1687+
}
1688+
}
1689+
}
1690+
}
1691+
else
1692+
{
1693+
b = shape - 1./3.;
1694+
c = 1./sqrt(9*b);
1695+
for (;;)
1696+
{
1697+
do
1698+
{
1699+
X = gauss_zig_double(state);
1700+
V = 1.0 + c*X;
1701+
} while (V <= 0.0);
1702+
1703+
V = V*V*V;
1704+
U = random_double(state);
1705+
if (U < 1.0 - 0.0331*(X*X)*(X*X)) return (b*V);
1706+
if (log(U) < 0.5*X*X + b*(1. - V + log(V))) return (b*V);
1707+
}
1708+
}
1709+
}
1710+
1711+
static inline float standard_gamma_zig_float(aug_state* state, float shape)
1712+
{
1713+
float b, c;
1714+
float U, V, X, Y;
1715+
1716+
if (shape == 1.0f)
1717+
{
1718+
return standard_exponential_zig_float(state);
1719+
}
1720+
else if (shape < 1.0f)
1721+
{
1722+
for (;;)
1723+
{
1724+
U = random_float(state);
1725+
V = standard_exponential_zig_float(state);
1726+
if (U <= 1.0f - shape)
1727+
{
1728+
X = powf(U, 1.0f/shape);
1729+
if (X <= V)
1730+
{
1731+
return X;
1732+
}
1733+
}
1734+
else
1735+
{
1736+
Y = -logf((1.0f-U)/shape);
1737+
X = powf(1.0f - shape + shape*Y, 1.0f/shape);
1738+
if (X <= (V + Y))
1739+
{
1740+
return X;
1741+
}
1742+
}
1743+
}
1744+
}
1745+
else
1746+
{
1747+
b = shape - 1.0f/3.0f;
1748+
c = 1.0f / sqrtf(9.0f*b);
1749+
for (;;)
1750+
{
1751+
do
1752+
{
1753+
X = gauss_zig_float(state);
1754+
V = 1.0f + c*X;
1755+
} while (V <= 0.0f);
1756+
1757+
V = V*V*V;
1758+
U = random_float(state);
1759+
if (U < 1.0f - 0.0331f * (X*X)*(X*X)) return (b*V);
1760+
if (logf(U) < 0.5f * X*X + b*(1.0f - V + logf(V))) return (b*V);
1761+
}
1762+
}
1763+
}
1764+
1765+
double random_standard_gamma_zig_double(aug_state* state, double shape)
1766+
{
1767+
return standard_gamma_zig_double(state, shape);
1768+
}
1769+
1770+
float random_standard_gamma_zig_float(aug_state* state, float shape)
1771+
{
1772+
return standard_gamma_zig_float(state, shape);
1773+
}

randomstate/distributions.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,4 +170,8 @@ extern void random_standard_exponential_zig_double_fill(aug_state* state, npy_in
170170

171171
extern float random_standard_exponential_zig_float(aug_state* state);
172172

173-
extern void random_standard_exponential_zig_float_fill(aug_state* state, npy_intp count, float *out);
173+
extern void random_standard_exponential_zig_float_fill(aug_state* state, npy_intp count, float *out);
174+
175+
extern double random_standard_gamma_zig_double(aug_state* state, double shape);
176+
177+
extern float random_standard_gamma_zig_float(aug_state* state, float shape);

0 commit comments

Comments
 (0)