From dec32e1516a05db805ff75a9e016e8bea04d1d8e Mon Sep 17 00:00:00 2001 From: Gertjan van den Burg Date: Thu, 30 Jul 2015 16:07:50 +0200 Subject: name change to SyncRNG and documentation in the .c file --- .gitignore | 3 + Makefile | 9 +-- README.md | 16 +++-- SyncRNG.R | 29 ++++++++ SyncRNG.py | 21 ++++++ Tausworthe.R | 45 ------------ Tausworthe.py | 16 ----- setup.py | 12 ++-- syncrng.c | 223 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ taus.c | 189 ------------------------------------------------- 10 files changed, 299 insertions(+), 264 deletions(-) create mode 100644 .gitignore create mode 100644 SyncRNG.R create mode 100644 SyncRNG.py delete mode 100644 Tausworthe.R delete mode 100644 Tausworthe.py create mode 100644 syncrng.c delete mode 100644 taus.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed61eb2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.pyc +*.so +*.o diff --git a/Makefile b/Makefile index 4531139..da2d612 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,6 @@ PYTHON=python2 +RLIB=RSyncRNG.so +CFILE=syncrng.c .PHONY: all clean @@ -8,10 +10,9 @@ python: $(PYTHON) setup.py build_ext --inplace R: - R CMD SHLIB -o tausR.so taus.c + R CMD SHLIB -o $(RLIB) $(CFILE) clean: rm -rf build - rm -f taus.so - rm -f tausR.so - rm -f taus.o + rm -f *.so *.o + rm *.pyc diff --git a/README.md b/README.md index 4c402d8..6147774 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ build both shared libraries using: make Then, in a Python script located in the same directory as `syncrng.so` and -`pysyncrng.py`, you can do: +`SyncRNG.py`, you can do: from pysyncrng import SyncRNG @@ -33,10 +33,10 @@ Then, in a Python script located in the same directory as `syncrng.so` and for i in range(10): print(s.randi()) -Similarly, in an R script located in the same directory as `Rsyncrng.so` and -`Rsyncrng.R`, you can do: +Similarly, in an R script located in the same directory as `RSyncRNG.so` and +`SyncRNG.R`, you can do: - source('./Rsyncrng.R') + source('./SyncRNG.R') s = SyncRNG(seed=123456) for (i in 1:10) { @@ -55,3 +55,11 @@ random numbers are no longer uniformly distributed on `[0, 2^32 -1]`. For the intended use of SyncRNG this is not a problem, but it is a compromise worth considering when using SyncRNG. SyncRNG should definitely not be used for any cryptographic purposes. + + +TODO +---- + +Future versions may include a random number generator that does not need +capping, and is uniform. It may also provide easier system-wide installation +through an R package and a Python module. diff --git a/SyncRNG.R b/SyncRNG.R new file mode 100644 index 0000000..23be4d2 --- /dev/null +++ b/SyncRNG.R @@ -0,0 +1,29 @@ +library(methods) + +dyn.load('RSyncRNG.so') + +SyncRNG <- setRefClass('SyncRNG', + fields=list( + seed='numeric', + state='numeric' + ), + methods=list( + initialize=function(..., seed=0) { + seed <<- seed + tmp <- .Call('R_syncrng_seed', + as.integer(seed)) + state <<- tmp[1:4] + callSuper(...) + }, + randi=function() { + tmp <- .Call('R_syncrng_rand', + as.integer(state)) + state <<- tmp[1:4] + return(tmp[5]) + }, + rand=function() { + r <- randi() + return (r * 2.3283064365387e-10) + } + ) + ) diff --git a/SyncRNG.py b/SyncRNG.py new file mode 100644 index 0000000..7137439 --- /dev/null +++ b/SyncRNG.py @@ -0,0 +1,21 @@ +""" +Simple interface to SyncRNG. This file defines a SyncRNG object which can be +used to seed and pull numbers from the RNG. + +""" + +import syncrng + +class SyncRNG(object): + + def __init__(self, seed=0): + self.seed = seed + self.state = syncrng.seed(seed) + + def randi(self): + tmp = syncrng.rand(self.state) + self.state = tmp[:-1] + return(tmp[-1]) + + def rand(self): + return self.randi() * 2.3283064365387e-10 diff --git a/Tausworthe.R b/Tausworthe.R deleted file mode 100644 index e4fe355..0000000 --- a/Tausworthe.R +++ /dev/null @@ -1,45 +0,0 @@ -library(methods) - -dyn.load('tausR.so') - -TauswortheRNG <- setRefClass('TauswortheRNG', - fields=list( - seed='numeric', - state='numeric' - ), - methods=list( - initialize=function(..., seed=0) { - seed <<- seed - tmp <- .Call('R_tausworthe_seed', - as.integer(seed)) - state <<- tmp[1:4] - callSuper(...) - }, - randi=function() { - tmp <- .Call('R_tausworthe_rand', - as.integer(state)) - state <<- tmp[1:4] - return(tmp[5]) - }, - rand=function() { - r <- randi() - return (r * 2.3283064365387e-10) - } - ) - ) - -taus.seed <- function(seed=0) -{ - t <- TauswortheRNG(seed=seed) - return(t) -} - -taus.rand <- function(t) -{ - return(t$rand()) -} - -taus.randi <- function(t) -{ - return(t$randi()) -} diff --git a/Tausworthe.py b/Tausworthe.py deleted file mode 100644 index 303f358..0000000 --- a/Tausworthe.py +++ /dev/null @@ -1,16 +0,0 @@ - -import taus - -class TauswortheRNG(object): - - def __init__(self, seed=0): - self.seed = seed - self.state = taus.seed(seed) - - def randi(self): - tmp = taus.rand(self.state) - self.state = tmp[:-1] - return(tmp[-1]) - - def rand(self): - return self.randi() * 2.3283064365387e-10 diff --git a/setup.py b/setup.py index f3c23b0..3c6bf16 100644 --- a/setup.py +++ b/setup.py @@ -2,18 +2,18 @@ from distutils.core import setup, Extension """ -module1 = Extension('taus', +module1 = Extension('syncrng', define_macros = [('TARGETPYTHON', '1')], - sources=['taus.c']) + sources=['syncrng.c']) -setup (name = 'Tausworthe RNG', +setup (name = 'SyncRNG', version = '0.1', - description='The Tausworthe RNG for Python and R', + description='A synchronized Tausworthe RNG for Python and R', ext_modules = [module1]) """ setup( - ext_modules=[Extension("taus", + ext_modules=[Extension("syncrng", define_macros=[('TARGETPYTHON', '1')], - sources=["taus.c"])], + sources=["syncrng.c"])], ) diff --git a/syncrng.c b/syncrng.c new file mode 100644 index 0000000..deb9439 --- /dev/null +++ b/syncrng.c @@ -0,0 +1,223 @@ +#ifdef TARGETPYTHON +#include "Python.h" +#endif + +#ifndef TARGETPYTHON +#define STRICT_R_HEADERS +#include +#include +#include +#include +#endif + +/** + * @brief Generate a single random number using the capped Tausworthe RNG + * + * @details + * This generates random numbers according to the process described in [1]. As + * an additional step, the state variables are capped to 0x7FFFFFFF using a + * bitwise and. This is to overcome limitations of R. On return, the state + * variables are updated. + * + * [1]: @article{l1996maximally, + * title={Maximally equidistributed combined Tausworthe generators}, + * author={L’ecuyer, Pierre}, + * journal={Mathematics of Computation of the American Mathematical + * Society}, + * volume={65}, + * number={213}, + * pages={203--213}, + * year={1996} + * } + * + * @param[in,out] state pointer to current state array + * + * @return a generated random number + */ +int lfsr113(int **state) +{ + unsigned long z1, z2, z3, z4, b; + + z1 = (*state)[0]; + z2 = (*state)[1]; + z3 = (*state)[2]; + z4 = (*state)[3]; + + b = (((z1 << 6) ^ z1) >> 13); + z1 = (((z1 & 4294967294) << 18) ^ b); + + b = (((z2 << 2) ^ z2) >> 27); + z2 = (((z2 & 4294967288) << 2) ^ b); + + b = (((z3 << 13) ^ z3) >> 21); + z3 = (((z3 & 4294967280) << 7) ^ b); + + b = (((z4 << 3) ^ z4) >> 12); + z4 = (((z4 & 4294967168) << 13) ^ b); + + b = (z1 ^ z2 ^ z3 ^ z4); + + (*state)[0] = z1 & 0x7FFFFFFF; + (*state)[1] = z2 & 0x7FFFFFFF; + (*state)[2] = z3 & 0x7FFFFFFF; + (*state)[3] = z4 & 0x7FFFFFFF; + b = b & 0x7FFFFFFF; + + return(b); +} + +/** + * @brief Seed the Tausworthe RNG using a seed value + * + * @details + * This function seeds the state array using a supplied seed value. As noted + * in [1] (see lfsr113()), the values of z1, z2, z3, and z4 should be larger + * than 1, 7, 15, and 127 respectively. Here too the state variables are + * capped at 0x7FFFFFF. + * + * @param[in] seed user supplied seed value for the RNG + * @param[out] state state of the RNG + */ +void lfsr113_seed(unsigned long seed, int **state) +{ + unsigned long z1 = 2, + z2 = 8, + z3 = 16, + z4 = 128; + + z1 = (z1 * (seed + 1)) & 0x7FFFFFFF; + z2 = (z2 * (seed + 1)) & 0x7FFFFFFF; + z3 = (z3 * (seed + 1)) & 0x7FFFFFFF; + z4 = (z4 * (seed + 1)) & 0x7FFFFFFF; + + if (*state == NULL) { + (*state) = malloc(sizeof(int)*4); + } + + (*state)[0] = (int) z1; + (*state)[1] = (int) z2; + (*state)[2] = (int) z3; + (*state)[3] = (int) z4; +} + +#ifdef TARGETPYTHON +/* + * + * Start of Python code + * + */ + +static PyObject *syncrng_seed(PyObject *self, PyObject *args) +{ + int seed, *state = NULL; + + if (!PyArg_ParseTuple(args, "i", &seed)) + return NULL; + + lfsr113_seed(seed, &state); + PyObject *pystate = Py_BuildValue("[i, i, i, i]", + state[0], state[1], state[2], state[3]); + free(state); + return pystate; +} + +static PyObject *syncrng_rand(PyObject *self, PyObject *args) +{ + int i, value, numints, *localstate; + + PyObject *listObj; + PyObject *intObj; + + if (!PyArg_ParseTuple(args, "O!", &PyList_Type, &listObj)) + return NULL; + + // we're just assuming you would never pass more than 4 values + localstate = malloc(sizeof(int)*5); + numints = PyList_Size(listObj); + for (i=0; i -#include -#include -#include -#endif - -int lfsr113(int **state) -{ - unsigned long z1, z2, z3, z4, b; - - z1 = (*state)[0]; - z2 = (*state)[1]; - z3 = (*state)[2]; - z4 = (*state)[3]; - - b = (((z1 << 6) ^ z1) >> 13); - z1 = (((z1 & 4294967294) << 18) ^ b); - - b = (((z2 << 2) ^ z2) >> 27); - z2 = (((z2 & 4294967288) << 2) ^ b); - - b = (((z3 << 13) ^ z3) >> 21); - z3 = (((z3 & 4294967280) << 7) ^ b); - - b = (((z4 << 3) ^ z4) >> 12); - z4 = (((z4 & 4294967168) << 13) ^ b); - - b = (z1 ^ z2 ^ z3 ^ z4); - - (*state)[0] = z1 & 0x7FFFFFFF; - (*state)[1] = z2 & 0x7FFFFFFF; - (*state)[2] = z3 & 0x7FFFFFFF; - (*state)[3] = z4 & 0x7FFFFFFF; - b = b & 0x7FFFFFFF; - - return(b); -} - -void lfsr113_seed(unsigned long seed, int **state) -{ - unsigned long z1 = 2, - z2 = 8, - z3 = 16, - z4 = 128; - - z1 = (z1 * (seed + 1)) & 0x7FFFFFFF; - z2 = (z2 * (seed + 1)) & 0x7FFFFFFF; - z3 = (z3 * (seed + 1)) & 0x7FFFFFFF; - z4 = (z4 * (seed + 1)) & 0x7FFFFFFF; - - if (*state == NULL) { - (*state) = malloc(sizeof(int)*4); - } - - (*state)[0] = (int) z1; - (*state)[1] = (int) z2; - (*state)[2] = (int) z3; - (*state)[3] = (int) z4; -} - -#ifdef TARGETPYTHON -/* - * - * Start of Python code - * - */ - -static char module_docstring[] = -"This module provides the Tausworthe RNG for R and Python simultaneously"; - -static PyObject *taus_seed(PyObject *self, PyObject *args) -{ - int seed, *state = NULL; - - if (!PyArg_ParseTuple(args, "i", &seed)) - return NULL; - - lfsr113_seed(seed, &state); - PyObject *pystate = Py_BuildValue("[i, i, i, i]", - state[0], state[1], state[2], state[3]); - free(state); - return pystate; -} - -static PyObject *taus_rand(PyObject *self, PyObject *args) -{ - int i, value, numints, *localstate; - - PyObject *listObj; - PyObject *intObj; - - if (!PyArg_ParseTuple(args, "O!", &PyList_Type, &listObj)) - return NULL; - - // we're just assuming you would never pass more than 4 values - localstate = malloc(sizeof(int)*5); - numints = PyList_Size(listObj); - for (i=0; i