Skip to content

Commit aee7315

Browse files
authored
Merge pull request #59 from bashtage/ziggurat-float
ENH: Initial work on 32-bit ziggurat
2 parents a513223 + e3ebdac commit aee7315

14 files changed

+1179
-84
lines changed

README.md

Lines changed: 36 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
[![Travis Build Status](https://travis-ci.org/bashtage/ng-numpy-randomstate.svg?branch=master)](https://travis-ci.org/bashtage/ng-numpy-randomstate)
44
[![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/odc5c4ukhru5xicl/branch/master?svg=true)](https://ci.appveyor.com/project/bashtage/ng-numpy-randomstate/branch/master)
55

6-
This is a library and generic interface for alternative random generators
7-
in Python and Numpy.
6+
This is a library and generic interface for alternative random
7+
generators in Python and Numpy.
88

99
## Features
1010

@@ -30,22 +30,19 @@ import randomstate as rnd
3030
w = rnd.standard_normal(10000, method='zig')
3131
```
3232

33-
* Preliminary support for 32-bit floating randoms for core generators.
34-
Currently only uniforms (`random_sample`), exponentials
35-
(`standard_exponential`) and normals (`standard_normal` but only
36-
using Box-Muller, so `method='bm'` is required) have been implemented.
37-
Ultimately support should be avialable for:
38-
39-
* Uniforms
40-
* Exponentials
41-
* Standard Gammas (via `standard_gamma`)
42-
* Normals (currently only implemented using Box-Muller transformation)
33+
* Support for 32-bit floating randoms for core generators.
34+
Currently supported:
35+
36+
* Uniforms (`random_sample`)
37+
* Exponentials (`standard_exponential`)
38+
* Normals (`standard_normal`, both Box-Muller and Ziggurat)
39+
* Standard Gammas (via `standard_gamma`)
4340

4441
**WARNING**: The 32-bit generators are **experimental** and subjust
4542
to change.
4643

47-
**Note**: There are no plans to extend the alternative precision generation to
48-
all random number types.
44+
**Note**: There are _no_ plans to extend the alternative precision
45+
generation to all random number types.
4946

5047

5148

@@ -59,11 +56,14 @@ The RNGs include:
5956

6057
* [MT19937](https://github.com/numpy/numpy/blob/master/numpy/random/mtrand/),
6158
the NumPy rng
62-
* [dSFMT](http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/) a SSE2-aware
63-
version of the MT19937 generator that is especially fast at generating doubles
64-
* [xorshift128+](http://xorshift.di.unimi.it/), [xoroshiro128+](http://xoroshiro.di.unimi.it/)
65-
[xorshift1024*](http://xorshift.di.unimi.it/)
66-
* [PCG32](http://www.pcg-random.org/) and [PCG64](http:w//www.pcg-random.org/)
59+
* [dSFMT](http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/) a
60+
SSE2-aware version of the MT19937 generator that is especially fast at
61+
generating doubles
62+
* [xorshift128+](http://xorshift.di.unimi.it/),
63+
[xoroshiro128+](http://xoroshiro.di.unimi.it/) and
64+
[xorshift1024*](http://xorshift.di.unimi.it/)
65+
* [PCG32](http://www.pcg-random.org/) and
66+
[PCG64](http:w//www.pcg-random.org/)
6767
* [MRG32K3A](http://simul.iro.umontreal.ca/rng)
6868
* A multiplicative lagged fibonacci generator (LFG(63, 1279, 861, *))
6969

@@ -75,8 +75,8 @@ support an additional `method` keyword argument which can be `bm` or
7575
`zig` where `bm` corresponds to the current method using the Box-Muller
7676
transformation and `zig` uses the much faster (100%+) ziggurat method.
7777
* `random_sample` can produce either single precision (`np.float32`) or
78-
double precision (`np.float64`, the default) using an the optional keyword
79-
argument `dtype`.
78+
double precision (`np.float64`, the default) using an the optional
79+
keyword argument `dtype`.
8080

8181
### New Functions
8282

@@ -93,9 +93,9 @@ the RNG._
9393

9494
## Status
9595

96-
* Complete drop-in replacement for `numpy.random.RandomState`. The `mt19937`
97-
generator is identical to `numpy.random.RandomState`, and will produce an
98-
identical sequence of random numbers for a given seed.
96+
* Complete drop-in replacement for `numpy.random.RandomState`. The
97+
`mt19937` generator is identical to `numpy.random.RandomState`, and
98+
will produce an identical sequence of random numbers for a given seed.
9999
* Builds and passes all tests on:
100100
* Linux 32/64 bit, Python 2.7, 3.4, 3.5 (should work on 2.6 and 3.3)
101101
* PC-BSD (FreeBSD) 64-bit, Python 2.7
@@ -133,13 +133,16 @@ Testing requires nose (1.3+).
133133
**Note:** it might work with other versions but only tested with these
134134
versions.
135135

136+
## Development and Testing
137+
136138
All development has been on 64-bit Linux, and it is regularly tested on
137-
Travis-CI. The library is occasionally tested on Linux 32-bit, OSX 10.10,
138-
PC-BSD 10.2 (should also work on Free BSD) and Windows (Python 2.7/3.5,
139-
both 32 and 64-bit).
139+
Travis-CI. The library is occasionally tested on Linux 32-bit,
140+
OSX 10.10, PC-BSD 10.2 (should also work on Free BSD) and Windows
141+
(Python 2.7/3.5, both 32 and 64-bit).
140142

141-
Basic tests are in place for all RNGs. The MT19937 is tested against NumPy's
142-
implementation for identical results. It also passes NumPy's test suite.
143+
Basic tests are in place for all RNGs. The MT19937 is tested against
144+
NumPy's implementation for identical results. It also passes NumPy's
145+
test suite.
143146

144147
## Installing
145148

@@ -179,8 +182,8 @@ rs = randomstate.prng.mt19937.RandomState()
179182
rs.random_sample(100)
180183
```
181184

182-
Like NumPy, `randomstate` also exposes a single instance of the `mt19937`
183-
generator directly at the module level so that commands like
185+
Like NumPy, `randomstate` also exposes a single instance of the
186+
`mt19937` generator directly at the module level so that commands like
184187

185188
```python
186189
import randomstate
@@ -194,7 +197,8 @@ will work.
194197
Standard NCSA, plus sub licenses for components.
195198

196199
## Performance
197-
Performance is promising, and even the mt19937 seems to be faster than NumPy's mt19937.
200+
Performance is promising, and even the mt19937 seems to be faster than
201+
NumPy's mt19937.
198202

199203
```
200204
Speed-up relative to NumPy (Uniform Doubles)

doc/make.bat

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ if "%1" == "help" (
4444
if "%1" == "clean" (
4545
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
4646
del /q /s %BUILDDIR%\*
47+
rmdir /q /s source\generated
4748
goto end
4849
)
4950

doc/source/change-log.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,20 @@
22

33
Change Log
44
==========
5+
6+
Head (not released)
7+
-------------------
8+
* Extended 32-bit generation to
9+
10+
* Uniforms (:meth:`~randomstate.entropy.random_sample` and :meth:`~randomstate.entropy.rand`)
11+
* Normals (:meth:`~randomstate.entropy.standard_normal` and :meth:`~randomstate.entropy.randn`)
12+
* Standard Gammas (:meth:`~randomstate.entropy.standard_gamma`)
13+
* Standard Exponentials (:meth:`~randomstate.entropy.standard_exponential`)
14+
15+
using the ``dtype`` keyword.
16+
* Removed ``random_uintegers`` since these are special cases of
17+
:meth:`~randomstate.entropy.randint`
18+
519
Version 1.11.2
620
--------------
721
* Added keyword argument `dtype` to `random_sample` which allows for single

doc/source/index.rst

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,34 @@ What's New or Different
1111
* The normal generator supports a 256-step Ziggurat method which is 2-6 times
1212
faster than NumPy's ``standard_normal``. This generator can be accessed
1313
by passing the keyword argument ``method='zig'``.
14-
* ``random_sample`` accepts the optional keyword argument ``dtype`` which
15-
accepts ``np.float32`` or ``np.float64`` to produce either single or
16-
double prevision uniform random variables
17-
* For changes since the previous release, see the :ref:`change-log`
1814

19-
.. code-block:: python
15+
.. ipython:: python
2016
2117
from randomstate import standard_normal
2218
standard_normal(100000, method='zig')
2319
20+
* Optional ``dtype`` argument that accepts ``np.float32`` or ``np.float64``
21+
to produce either single or double prevision uniform random variables for
22+
select distributions
23+
24+
* Uniforms (:meth:`~randomstate.entropy.random_sample` and :meth:`~randomstate.entropy.rand`)
25+
* Normals (:meth:`~randomstate.entropy.standard_normal` and :meth:`~randomstate.entropy.randn`)
26+
* Standard Gammas (:meth:`~randomstate.entropy.standard_gamma`)
27+
* Standard Exponentials (:meth:`~randomstate.entropy.standard_exponential`)
28+
29+
.. ipython:: python
30+
31+
import numpy as np
32+
import randomstate as rs
33+
rs.seed(0)
34+
rs.random_sample(3, dtype=np.float64)
35+
rs.seed(0)
36+
rs.random_sample(3, dtype=np.float32)
37+
38+
39+
40+
* For changes since the previous release, see the :ref:`change-log`
41+
2442
Parallel Generation
2543
-------------------
2644

papers/generate_consts.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import numpy as np
2+
from numpy import log, sqrt, exp
3+
4+
pdf = lambda x: exp(-0.5 * x * x)
5+
6+
def inverse_pdf(p):
7+
return sqrt(-2 * log(p))
8+
9+
10+
C = 256
11+
r = 3.6541528853610087963519472518
12+
Vr = 0.0049286732339746519
13+
x = np.zeros(C)
14+
x[0] = r
15+
for i in range(1, C - 1):
16+
x[i] = inverse_pdf(pdf(x[i - 1]) + Vr / x[i - 1])
17+
18+
x = x[::-1]
19+
20+
# ki
21+
ki_final = {}
22+
scales = (2 ** 52, 2 ** 23)
23+
names = ('double', 'float')
24+
for name, scale in zip(names, scales):
25+
ki = np.zeros(C, np.uint64)
26+
prob_ratio = x[:-1] / x[1:]
27+
ki[1:] = np.round(prob_ratio * scale).astype(np.uint64)
28+
ki[0] = np.uint64(np.round(((x.max() * pdf(x.max())) / Vr) * scale))
29+
digits = 10 if name == 'float' else 18
30+
out = ["{0:#0{1}X}".format((ki[i]), digits) for i in range(C)]
31+
ki_final[name] = out
32+
33+
# wi
34+
wi_final = {}
35+
scales = (2 ** 52, 2 ** 23)
36+
names = ('double', 'float')
37+
for name, scale in zip(names, scales):
38+
wi = x.copy()
39+
wi[0] = Vr / pdf(x.max())
40+
wi_final[name] = wi / scale
41+
42+
# fi
43+
fi_final = {'double': pdf(x), 'float': pdf(x)}
44+
45+
constants = {'ziggurat_nor_r': 3.6541528853610087963519472518,
46+
'ziggurat_nor_inv_r': 0.27366123732975827203338247596,
47+
'ziggurat_exp_r': 7.6971174701310497140446280481}
48+
49+
type_map = {'uint64': 'uint64_t', 'uint32': 'uint32_t', 'double': 'double',
50+
'float': 'float'}
51+
extra_text = {'uint64': 'ULL', 'uint32': 'UL', 'double': '', 'float': 'f'}
52+
53+
54+
def write(a, name, dtype):
55+
ctype = type_map[dtype]
56+
out = 'static const ' + ctype + ' ' + name + '[] = { \n'
57+
format_str = '{0: .20e}' if dtype in ('double', 'float') else '{0}'
58+
formatted = [format_str.format(a[i]) + extra_text[dtype] for i in range(len(a))]
59+
lines = len(formatted) // 4
60+
for i in range(lines):
61+
temp = ' ' + ', '.join(formatted[4 * i:4 * i + 4])
62+
if i < (lines - 1):
63+
temp += ',\n'
64+
else:
65+
temp += '\n'
66+
out += temp
67+
out += '};'
68+
return out
69+
70+
71+
with open('./ziggurat_constants.h', 'w') as f:
72+
f.write(write(ki_final['double'], 'ki_double', 'uint64'))
73+
f.write('\n\n')
74+
f.write(write(wi_final['double'], 'wi_double', 'double'))
75+
f.write('\n\n')
76+
f.write(write(fi_final['double'], 'fi_double', 'double'))
77+
78+
f.write('\n\n\n\n')
79+
f.write(write(ki_final['float'], 'ki_float', 'uint32'))
80+
f.write('\n\n')
81+
f.write(write(wi_final['float'], 'wi_float', 'float'))
82+
f.write('\n\n')
83+
f.write(write(fi_final['float'], 'fi_float', 'float'))

0 commit comments

Comments
 (0)