From 2971238c8957df1bce0629c12d8c209b39328590 Mon Sep 17 00:00:00 2001 From: Gertjan van den Burg Date: Fri, 31 Jul 2015 16:21:25 +0200 Subject: reformat to proper python and R packages --- DESCRIPTION | 11 +++ NAMESPACE | 3 + Python/SyncRNG.py | 47 +++++++++++ R/SyncRNG.R | 69 +++++++++++++++ SyncRNG.R | 63 -------------- SyncRNG.py | 47 ----------- man/SyncRNG-class.Rd | 37 ++++++++ setup.py | 5 +- src/syncrng.c | 233 +++++++++++++++++++++++++++++++++++++++++++++++++++ syncrng.c | 233 --------------------------------------------------- test.R | 44 ---------- test.py | 34 -------- test/test.R | 44 ++++++++++ test/test.py | 34 ++++++++ 14 files changed, 481 insertions(+), 423 deletions(-) create mode 100644 DESCRIPTION create mode 100644 NAMESPACE create mode 100644 Python/SyncRNG.py create mode 100644 R/SyncRNG.R delete mode 100644 SyncRNG.R delete mode 100644 SyncRNG.py create mode 100644 man/SyncRNG-class.Rd create mode 100644 src/syncrng.c delete mode 100644 syncrng.c delete mode 100644 test.R delete mode 100644 test.py create mode 100644 test/test.R create mode 100644 test/test.py diff --git a/DESCRIPTION b/DESCRIPTION new file mode 100644 index 0000000..6620df7 --- /dev/null +++ b/DESCRIPTION @@ -0,0 +1,11 @@ +Package: SyncRNG +Version: 0.1 +Date: 2015-07-31 +Title: A Synchronized Tausworthe RNG for R and Python +Author: Gertjan van den Burg +Maintainer: Gertjan van den Burg +Depends: R (>= 3.0.0) +Description: Random number generation designed for cross-language usage. +License: file LICENSE +Imports: + methods diff --git a/NAMESPACE b/NAMESPACE new file mode 100644 index 0000000..bb2ff1a --- /dev/null +++ b/NAMESPACE @@ -0,0 +1,3 @@ +useDynLib(SyncRNG) +export(SyncRNG) +import(methods) diff --git a/Python/SyncRNG.py b/Python/SyncRNG.py new file mode 100644 index 0000000..7ef6fd6 --- /dev/null +++ b/Python/SyncRNG.py @@ -0,0 +1,47 @@ +""" +Simple interface to SyncRNG. This file defines a SyncRNG object which can be +used to seed and pull numbers from the RNG. + +""" + +from __future__ import division + +from warnings import warn as _warn + +import syncrng + +class SyncRNG(object): + + def __init__(self, seed=0): + self.BPF = 32 + 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 + + def randbelow(self, n): + maxsize = 1<= maxsize: + _warn("Underlying random generator does not supply \n" + "enough bits to choose from a population range this " + "large.\n") + return int(self.rand() * n) + rem = maxsize % n + limit = (maxsize - rem) / maxsize + r = self.rand() + while r >= limit: + r = self.rand() + return int(r*maxsize) % n + + def shuffle(self, x): + y = x[:] + for i in reversed(range(1, len(y))): + j = self.randbelow(i+1) + y[i], y[j] = y[j], y[i] + return y diff --git a/R/SyncRNG.R b/R/SyncRNG.R new file mode 100644 index 0000000..84088b0 --- /dev/null +++ b/R/SyncRNG.R @@ -0,0 +1,69 @@ +library(methods) + +#' A Reference Class for SyncRNG +#' +#' @field seed The seed for the random number generator +#' @field state The current state of the RNG, should not be modified by the +#' user +#' +#' @examples +#' s = SyncRNG(seed=123456) +#' for (i in 1:10) +#' cat(s$randi(), '\n') +#' +SyncRNG <- setRefClass('SyncRNG', + fields=list( + seed='numeric', + state='numeric' + ), + methods=list( + initialize=function(..., seed=0) { + "Initialize the RNG using the C function R_syncrng_seed" + seed <<- seed + tmp <- .Call('R_syncrng_seed', + as.numeric(seed)) + state <<- tmp[1:4] + callSuper(...) + }, + randi=function() { + "Generate a single random 32-bit integer" + tmp <- .Call('R_syncrng_rand', + as.numeric(state)) + state <<- tmp[1:4] + return(tmp[5]) + }, + rand=function() { + "Generate a single random float in the range [0, 1)" + r <- randi() + return (r * 2.3283064365387e-10) + }, + randbelow=function(n) { + "Generate a random integer below a given number" + maxsize <- 2^32 + if (n >= maxsize) { + warning(paste("Underlying random generator ", + "does not supply\n enough bits ", + "to choose from a population ", + "range this large.\n")) + return(round(rand() * n)) + } + rem <- maxsize %% n + limit <- (maxsize - rem) / maxsize + r <- rand() + while (r >= limit) + r <- rand() + return(round(r*maxsize) %% n) + }, + shuffle=function(x) { + "Randomly shuffle a provided array of values" + y <- x + for (i in rev(1:(length(y)-1))) { + j <- randbelow(i+1) + tmp <- y[i+1] + y[i+1] <- y[j+1] + y[j+1] <- tmp + } + return(y) + } + ) + ) diff --git a/SyncRNG.R b/SyncRNG.R deleted file mode 100644 index 90e6fd5..0000000 --- a/SyncRNG.R +++ /dev/null @@ -1,63 +0,0 @@ -library(methods) - -frame.files <- lapply(sys.frames(), function(x) x$ofile) -frame.files <- Filter(Negate(is.null), frame.files) - -script.dir <- normalizePath(dirname(frame.files[[length(frame.files)]])) -source.file <- paste(script.dir, '/', 'RSyncRNG.so', sep='') - -dyn.load(source.file) - -SyncRNG <- setRefClass('SyncRNG', - fields=list( - seed='numeric', - state='numeric', - BPF='numeric' - ), - methods=list( - initialize=function(..., seed=0) { - BPF <<- 32 - seed <<- seed - tmp <- .Call('R_syncrng_seed', - as.numeric(seed)) - state <<- tmp[1:4] - callSuper(...) - }, - randi=function() { - tmp <- .Call('R_syncrng_rand', - as.numeric(state)) - state <<- tmp[1:4] - return(tmp[5]) - }, - rand=function() { - r <- randi() - return (r * 2.3283064365387e-10) - }, - randbelow=function(n) { - maxsize <- 2^BPF - if (n >= maxsize) { - warning(paste("Underlying random generator ", - "does not supply\n enough bits ", - "to choose from a population ", - "range this large.\n")) - return(round(rand() * n)) - } - rem <- maxsize %% n - limit <- (maxsize - rem) / maxsize - r <- rand() - while (r >= limit) - r <- rand() - return(round(r*maxsize) %% n) - }, - shuffle=function(x) { - y <- x - for (i in rev(1:(length(y)-1))) { - j <- randbelow(i+1) - tmp <- y[i+1] - y[i+1] <- y[j+1] - y[j+1] <- tmp - } - return(y) - } - ) - ) diff --git a/SyncRNG.py b/SyncRNG.py deleted file mode 100644 index 7ef6fd6..0000000 --- a/SyncRNG.py +++ /dev/null @@ -1,47 +0,0 @@ -""" -Simple interface to SyncRNG. This file defines a SyncRNG object which can be -used to seed and pull numbers from the RNG. - -""" - -from __future__ import division - -from warnings import warn as _warn - -import syncrng - -class SyncRNG(object): - - def __init__(self, seed=0): - self.BPF = 32 - 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 - - def randbelow(self, n): - maxsize = 1<= maxsize: - _warn("Underlying random generator does not supply \n" - "enough bits to choose from a population range this " - "large.\n") - return int(self.rand() * n) - rem = maxsize % n - limit = (maxsize - rem) / maxsize - r = self.rand() - while r >= limit: - r = self.rand() - return int(r*maxsize) % n - - def shuffle(self, x): - y = x[:] - for i in reversed(range(1, len(y))): - j = self.randbelow(i+1) - y[i], y[j] = y[j], y[i] - return y diff --git a/man/SyncRNG-class.Rd b/man/SyncRNG-class.Rd new file mode 100644 index 0000000..599b540 --- /dev/null +++ b/man/SyncRNG-class.Rd @@ -0,0 +1,37 @@ +% Generated by roxygen2 (4.1.1): do not edit by hand +% Please edit documentation in R/SyncRNG.R +\docType{class} +\name{SyncRNG-class} +\alias{SyncRNG} +\alias{SyncRNG-class} +\title{A Reference Class for SyncRNG} +\description{ +A Reference Class for SyncRNG +} +\section{Fields}{ + +\describe{ +\item{\code{seed}}{The seed for the random number generator} + +\item{\code{state}}{The current state of the RNG, should not be modified by the +user} +}} +\section{Methods}{ + +\describe{ +\item{\code{initialize(..., seed = 0)}}{Initialize the RNG using the C function R_syncrng_seed} + +\item{\code{rand()}}{Generate a single random float in the range [0, 1)} + +\item{\code{randbelow(n)}}{Generate a random integer below a given number} + +\item{\code{randi()}}{Generate a single random 32-bit integer} + +\item{\code{shuffle(x)}}{Randomly shuffle a provided array of values} +}} +\examples{ +s = SyncRNG(seed=123456) +for (i in 1:10) + cat(s$randi(), '\\n') +} + diff --git a/setup.py b/setup.py index 80ac5bd..b94684e 100644 --- a/setup.py +++ b/setup.py @@ -7,12 +7,13 @@ setup( version='0.1', description='A synchronized Tausworthe RNG for Python and R', license='GPL v2', - py_modules=['SyncRNG'], + package_dir={'': 'Python'}, + packages=[''], ext_modules=[ Extension( "syncrng", define_macros=[('TARGETPYTHON', '1')], - sources=["syncrng.c"] + sources=["src/syncrng.c"] ) ], ) diff --git a/src/syncrng.c b/src/syncrng.c new file mode 100644 index 0000000..668c39e --- /dev/null +++ b/src/syncrng.c @@ -0,0 +1,233 @@ +#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 resulting random number is capped to 0xFFFFFFFF + * using a bitwise and. This is done to yield the range [0, 2^32-1]. 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 + */ +unsigned long lfsr113(unsigned long **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; + (*state)[1] = z2; + (*state)[2] = z3; + (*state)[3] = z4; + + b = b & 0xFFFFFFFF; + + 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. + * + * @param[in] seed user supplied seed value for the RNG + * @param[out] state state of the RNG + */ +void lfsr113_seed(unsigned long seed, unsigned long **state) +{ + unsigned long z1 = 2, + z2 = 8, + z3 = 16, + z4 = 128; + + z1 = (z1 * (seed + 1)); + z2 = (z2 * (seed + 1)); + z3 = (z3 * (seed + 1)); + z4 = (z4 * (seed + 1)); + + z1 = (z1 > 1) ? z1 : z1 + 1; + z2 = (z2 > 7) ? z2 : z2 + 7; + z3 = (z3 > 15) ? z3 : z3 + 15; + z4 = (z4 > 127) ? z4 : z4 + 127; + + if (*state == NULL) { + (*state) = malloc(sizeof(unsigned long)*4); + } + + (*state)[0] = z1; + (*state)[1] = z2; + (*state)[2] = z3; + (*state)[3] = z4; +} + +#ifdef TARGETPYTHON +/* + * + * Start of Python code + * + */ + +static PyObject *syncrng_seed(PyObject *self, PyObject *args) +{ + unsigned long seed, *state = NULL; + + if (!PyArg_ParseTuple(args, "k", &seed)) + return NULL; + + lfsr113_seed(seed, &state); + + PyObject *pystate = Py_BuildValue("[k, k, k, k]", + state[0], state[1], state[2], state[3]); + free(state); + return pystate; +} + +static PyObject *syncrng_rand(PyObject *self, PyObject *args) +{ + unsigned long 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(unsigned long)*5); + numints = PyList_Size(listObj); + for (i=0; i -#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 resulting random number is capped to 0xFFFFFFFF - * using a bitwise and. This is done to yield the range [0, 2^32-1]. 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 - */ -unsigned long lfsr113(unsigned long **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; - (*state)[1] = z2; - (*state)[2] = z3; - (*state)[3] = z4; - - b = b & 0xFFFFFFFF; - - 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. - * - * @param[in] seed user supplied seed value for the RNG - * @param[out] state state of the RNG - */ -void lfsr113_seed(unsigned long seed, unsigned long **state) -{ - unsigned long z1 = 2, - z2 = 8, - z3 = 16, - z4 = 128; - - z1 = (z1 * (seed + 1)); - z2 = (z2 * (seed + 1)); - z3 = (z3 * (seed + 1)); - z4 = (z4 * (seed + 1)); - - z1 = (z1 > 1) ? z1 : z1 + 1; - z2 = (z2 > 7) ? z2 : z2 + 7; - z3 = (z3 > 15) ? z3 : z3 + 15; - z4 = (z4 > 127) ? z4 : z4 + 127; - - if (*state == NULL) { - (*state) = malloc(sizeof(unsigned long)*4); - } - - (*state)[0] = z1; - (*state)[1] = z2; - (*state)[2] = z3; - (*state)[3] = z4; -} - -#ifdef TARGETPYTHON -/* - * - * Start of Python code - * - */ - -static PyObject *syncrng_seed(PyObject *self, PyObject *args) -{ - unsigned long seed, *state = NULL; - - if (!PyArg_ParseTuple(args, "k", &seed)) - return NULL; - - lfsr113_seed(seed, &state); - - PyObject *pystate = Py_BuildValue("[k, k, k, k]", - state[0], state[1], state[2], state[3]); - free(state); - return pystate; -} - -static PyObject *syncrng_rand(PyObject *self, PyObject *args) -{ - unsigned long 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(unsigned long)*5); - numints = PyList_Size(listObj); - for (i=0; i