aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGertjan van den Burg <gertjanvandenburg@gmail.com>2021-01-14 17:06:19 +0000
committerGertjan van den Burg <gertjanvandenburg@gmail.com>2021-01-14 17:06:19 +0000
commit9187fc1fd45203c2e3501a8b55236e9db71fc2ed (patch)
tree572cffe74f5894f9cf1bd5168ee462cfc4afe9a9
parentupdate python version with R version (diff)
parentcast to int explicitly (diff)
downloadSyncRNG-9187fc1fd45203c2e3501a8b55236e9db71fc2ed.tar.gz
SyncRNG-9187fc1fd45203c2e3501a8b55236e9db71fc2ed.zip
Merge branch 'bugfix/python_error' into python
-rw-r--r--Makefile83
-rw-r--r--SyncRNG/__init__.py29
-rw-r--r--SyncRNG/__version__.py5
-rw-r--r--setup.py109
-rw-r--r--src/_syncrng.c (renamed from src/syncrng.c)58
-rw-r--r--tests/first_1000_seed_0.txt (renamed from test/first_1000_seed_0.txt)0
-rwxr-xr-xtests/run_tests.sh (renamed from test/run_tests.sh)0
-rw-r--r--tests/test.R (renamed from test/test.R)0
-rw-r--r--tests/test.py (renamed from test/test.py)25
9 files changed, 209 insertions, 100 deletions
diff --git a/Makefile b/Makefile
index b3e724d..64d2bbe 100644
--- a/Makefile
+++ b/Makefile
@@ -5,7 +5,12 @@
# http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
#
+SHELL := bash
+.SHELLFLAGS := -eu -o pipefail -c
+MAKEFLAGS += --no-builtin-rules
+
PACKAGE=SyncRNG
+VENV_DIR=/tmp/sync_venv/
.PHONY: help
@@ -16,39 +21,67 @@ help:
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m\
%s\n", $$1, $$2}'
-install: ## Install for the current user using the default python command
- python setup.py build_ext --inplace
- python setup.py install --user
+################
+# Installation #
+################
-install2: ## Install for the current user using the python2 command
- python2 setup.py build_ext --inplace
- python2 setup.py install --user
+.PHONY: inplace install
-test: develop ## Run nosetests using the default nosetests command
- nosetests -v
+inplace:
+ python setup.py build_ext --inplace
-test2: develop2 ## Run nosetests using the nosetests2 command
- nosetests2 -v
+install: ## Install for the current user using the default python command
+ python setup.py build_ext --inplace && \
+ python setup.py install --user
-cover: ## Test unit test coverage using default nosetests
- nosetests --with-coverage --cover-package=$(PACKAGE) \
- --cover-erase --cover-inclusive --cover-branches
+################
+# Distribution #
+################
-clean: ## Clean build dist and egg directories left after install
- rm -rf ./dist ./build ./$(PACKAGE).egg-info
- rm -rf *.so
- rm -f MANIFEST
- rm -f .coverage
+.PHONY: release dist
-develop: ## Install a development version of the package needed for testing
- python setup.py develop --user
-
-develop2: ## Install a development version of the package needed for testing (python2)
- python2 setup.py develop --user
+release: ## Make a release
+ python make_release.py
dist: ## Make Python source distribution
python setup.py sdist
-dist2: ## Make Python 2 source distribution
- python2 setup.py sdist
+###########
+# Testing #
+###########
+
+.PHONY: test
+
+test: venv ## Run nosetests using the default nosetests command
+ source $(VENV_DIR)/bin/activate && green -a -vv ./tests
+
+#######################
+# Virtual environment #
+#######################
+
+.PHONY: venv
+
+venv: $(VENV_DIR)/bin/activate
+
+$(VENV_DIR)/bin/activate:
+ test -d $(VENV_DIR) || python -m venv $(VENV_DIR)
+ source $(VENV_DIR)/bin/activate && pip install -e .[dev]
+ touch $(VENV_DIR)/bin/activate
+
+
+############
+# Clean up #
+############
+
+.PHONY: clean
+
+clean: ## Clean build dist and egg directories left after install
+ rm -rf ./dist ./build ./$(PACKAGE).egg-info
+ rm -rf ./build
+ rm -rf ./$(PACKAGE).egg-info
+ rm -rf *.so
+ rm -f MANIFEST
+ rm -f .coverage
+ find . -type f -iname '*.pyc' -delete
+ find . -type d -name '__pycache__' -empty -delete
diff --git a/SyncRNG/__init__.py b/SyncRNG/__init__.py
index 0ab4c06..6d67746 100644
--- a/SyncRNG/__init__.py
+++ b/SyncRNG/__init__.py
@@ -4,45 +4,46 @@ used to seed and pull numbers from the RNG.
"""
-from __future__ import division
-
from copy import deepcopy
-from warnings import warn as _warn
+from warnings import warn
-import syncrng
+from _syncrng import seed as _seed
+from _syncrng import rand as _rand
-class SyncRNG(object):
+class SyncRNG(object):
def __init__(self, seed=0):
self.BPF = 32
self.seed = seed
- self.state = syncrng.seed(seed)
+ self.state = _seed(self.seed)
def randi(self):
- tmp = syncrng.rand(self.state)
+ tmp = _rand(self.state)
self.state = tmp[:-1]
- return(tmp[-1])
+ return int(tmp[-1])
def rand(self):
return self.randi() * 2.3283064365387e-10
def randbelow(self, n):
- maxsize = 1<<self.BPF
+ maxsize = 1 << self.BPF
if n >= maxsize:
- _warn("Underlying random generator does not supply \n"
- "enough bits to choose from a population range this "
- "large.\n")
+ 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
+ return int(r * maxsize) % n
def shuffle(self, x):
y = deepcopy(x)
for i in reversed(range(1, len(y))):
- j = self.randbelow(i+1)
+ j = self.randbelow(i + 1)
y[i], y[j] = y[j], y[i]
return y
diff --git a/SyncRNG/__version__.py b/SyncRNG/__version__.py
new file mode 100644
index 0000000..cfd0ff9
--- /dev/null
+++ b/SyncRNG/__version__.py
@@ -0,0 +1,5 @@
+# -*- coding: utf-8 -*-
+
+VERSION = (1, 3 ,0)
+
+__version__ = '.'.join(map(str, VERSION))
diff --git a/setup.py b/setup.py
index 900cfc9..4c37187 100644
--- a/setup.py
+++ b/setup.py
@@ -1,44 +1,103 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
"""
- Setup file for SyncRNG
+Setup file for SyncRNG
- Author: Gertjan van den Burg
- Date: Oct. 12, 2016
+Author: Gertjan van den Burg
+Date: Oct. 12, 2016
"""
+import io
+import os
-from os import path
-from setuptools import setup, find_packages
+from setuptools import find_packages, setup
from distutils.extension import Extension
-here = path.abspath(path.dirname(__file__))
+# Package meta-data.
+AUTHOR = "Gertjan van den Burg"
+DESCRIPTION = "Generate the same random numbers in R and Python"
+EMAIL = "gertjanvandenburg@gmail.com"
+LICENSE = "GPLv2"
+LICENSE_TROVE = "License :: OSI Approved :: GNU General Public License v2 (GPLv2)"
+NAME = "SyncRNG"
+REQUIRES_PYTHON = ">=3.6.0"
+URL = "https://github.com/GjjvdBurg/SyncRNG"
+VERSION = None
+
+# What packages are required for this module to be executed?
+REQUIRED = []
+
+docs_require = []
+test_require = []
+dev_require = ["green"]
+
+# What packages are optional?
+EXTRAS = {
+ "docs": docs_require,
+ "test": test_require,
+ "dev": docs_require + test_require + dev_require,
+}
+
+# The rest you shouldn't have to touch too much :)
+# ------------------------------------------------
+# Except, perhaps the License and Trove Classifiers!
+# If you do change the License, remember to change the Trove Classifier for that!
+
+here = os.path.abspath(os.path.dirname(__file__))
+
+# Import the README and use it as the long-description.
+# Note: this will only work if 'README.md' is present in your MANIFEST.in file!
+try:
+ with io.open(os.path.join(here, "README.md"), encoding="utf-8") as f:
+ long_description = "\n" + f.read()
+except FileNotFoundError:
+ long_description = DESCRIPTION
-with open(path.join(here, 'README.rst'), 'r') as f:
- long_description = f.read()
+# Load the package's __version__.py module as a dictionary.
+about = {}
+if not VERSION:
+ project_slug = NAME.replace("-", "_").replace(" ", "_")
+ with open(os.path.join(here, project_slug, "__version__.py")) as f:
+ exec(f.read(), about)
+else:
+ about["__version__"] = VERSION
+# Where the magic happens:
setup(
- name='SyncRNG',
- author='Gertjan van den Burg',
- author_email='gertjanvandenburg@gmail.com',
- version='1.3.0',
- description='A synchronized Tausworthe RNG for Python and R',
- long_description=long_description,
- url='https://github.com/GjjvdBurg/SyncRNG',
- license='GPL v2',
- packages=find_packages(),
- ext_modules=[
+ name=NAME,
+ version=about["__version__"],
+ description=DESCRIPTION,
+ long_description=long_description,
+ long_description_content_type="text/markdown",
+ author=AUTHOR,
+ author_email=EMAIL,
+ python_requires=REQUIRES_PYTHON,
+ url=URL,
+ packages=find_packages(
+ exclude=["tests", "*.tests", "*.tests.*", "tests.*"]
+ ),
+ install_requires=REQUIRED,
+ extras_require=EXTRAS,
+ include_package_data=True,
+ license=LICENSE,
+ ext_modules=[
Extension(
- "syncrng",
+ "_syncrng",
define_macros=[('TARGETPYTHON', '1')],
- sources=["src/syncrng.c"]
+ sources=["src/_syncrng.c"],
+ extra_compile_args=['-g']
)
- ],
- keywords='RNG R Python',
- classifiers=[
+ ],
+ classifiers=[
+ # Trove classifiers
+ # Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers
+ LICENSE_TROVE,
'Intended Audience :: Developers',
'Intended Audience :: Science/Research',
'License :: OSI Approved :: GNU General Public License v2 (GPLv2)',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Topic :: Scientific/Engineering :: Mathematics'
- ]
- )
+ ],
+)
diff --git a/src/syncrng.c b/src/_syncrng.c
index 3cc77fa..bd1612a 100644
--- a/src/syncrng.c
+++ b/src/_syncrng.c
@@ -117,56 +117,62 @@ void lfsr113_seed(uint32_t seed, uint64_t **state)
*
*/
-static PyObject *syncrng_seed(PyObject *self, PyObject *args)
+static PyObject *_syncrng_seed(PyObject *self, PyObject *args)
{
uint32_t seed;
uint64_t *state = NULL;
- if (!PyArg_ParseTuple(args, "k", &seed))
+ PyObject *dblObj;
+
+ if (!PyArg_ParseTuple(args, "O", &dblObj))
return NULL;
+ seed = (uint32_t) PyLong_AsLong(dblObj);
lfsr113_seed(seed, &state);
- PyObject *pystate = Py_BuildValue("[k, k, k, k]",
- state[0], state[1], state[2], state[3]);
+ PyObject *pystate = Py_BuildValue("[d, d, d, d, d]",
+ (double) state[0],
+ (double) state[1],
+ (double) state[2],
+ (double) state[3],
+ -1.0);
free(state);
return pystate;
}
-static PyObject *syncrng_rand(PyObject *self, PyObject *args)
+static PyObject *_syncrng_rand(PyObject *self, PyObject *args)
{
- uint32_t i, value, numints;
- uint64_t *localstate;
+ int i;
+ uint32_t rand;
+ uint64_t *localstate = malloc(sizeof(uint64_t) * 4);
PyObject *listObj;
- PyObject *intObj;
+ PyObject *dblObj;
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(uint32_t)*5);
- numints = PyList_Size(listObj);
- for (i=0; i<numints; i++) {
- intObj = PyList_GetItem(listObj, i);
- value = (uint32_t) PyLong_AsLong(intObj);
- localstate[i] = value;
+ for (i=0; i<4; i++) {
+ dblObj = PyList_GetItem(listObj, i);
+ localstate[i] = (uint64_t) PyFloat_AS_DOUBLE(dblObj);
}
- uint32_t rand = lfsr113(&localstate);
- localstate[4] = rand;
+ rand = lfsr113(&localstate);
- PyObject *pystate = Py_BuildValue("[k, k, k, k, k]",
- localstate[0], localstate[1], localstate[2],
- localstate[3], rand);
+ PyObject *pystate = Py_BuildValue("[d, d, d, d, d]",
+ (double) localstate[0],
+ (double) localstate[1],
+ (double) localstate[2],
+ (double) localstate[3],
+ (double) rand);
free(localstate);
return pystate;
}
static PyMethodDef SyncRNGMethods[] = {
- {"seed", syncrng_seed, METH_VARARGS,
+ {"seed", _syncrng_seed, METH_VARARGS,
"Seed the RNG."},
- {"rand", syncrng_rand, METH_VARARGS,
+ {"rand", _syncrng_rand, METH_VARARGS,
"Generate a single random integer using SyncRNG."},
{NULL, NULL, 0, NULL}
};
@@ -174,7 +180,7 @@ static PyMethodDef SyncRNGMethods[] = {
#if PY_MAJOR_VERSION >= 3
static struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT,
- "syncrng",
+ "_syncrng",
"Python interface to SyncRNG",
-1,
SyncRNGMethods,
@@ -194,7 +200,7 @@ moduleinit(void)
#if PY_MAJOR_VERSION >= 3
m = PyModule_Create(&moduledef);
#else
- m = Py_InitModule3("syncrng", SyncRNGMethods,
+ m = Py_InitModule3("_syncrng", SyncRNGMethods,
"Python interface to SyncRNG");
#endif
@@ -203,13 +209,13 @@ moduleinit(void)
#if PY_MAJOR_VERSION >= 3
PyMODINIT_FUNC
-PyInit_syncrng(void)
+PyInit__syncrng(void)
{
return moduleinit();
}
#else
PyMODINIT_FUNC
-initsyncrng(void)
+init_syncrng(void)
{
moduleinit();
}
diff --git a/test/first_1000_seed_0.txt b/tests/first_1000_seed_0.txt
index 50aef45..50aef45 100644
--- a/test/first_1000_seed_0.txt
+++ b/tests/first_1000_seed_0.txt
diff --git a/test/run_tests.sh b/tests/run_tests.sh
index 4c7c5da..4c7c5da 100755
--- a/test/run_tests.sh
+++ b/tests/run_tests.sh
diff --git a/test/test.R b/tests/test.R
index 0937f52..0937f52 100644
--- a/test/test.R
+++ b/tests/test.R
diff --git a/test/test.py b/tests/test.py
index 1d7ec21..58b504d 100644
--- a/test/test.py
+++ b/tests/test.py
@@ -1,12 +1,12 @@
+# -*- coding: utf-8 -*-
-from __future__ import division
-
+import os
import unittest
from SyncRNG import SyncRNG
-class SyncRNGTestCase(unittest.TestCase):
+class SyncRNGTestCase(unittest.TestCase):
def test_randi(self):
s = SyncRNG(seed=123456)
self.assertEqual(s.randi(), 959852049)
@@ -17,11 +17,11 @@ class SyncRNGTestCase(unittest.TestCase):
def test_rand(self):
s = SyncRNG(seed=123456)
- self.assertAlmostEqual(s.rand(), 959852049/pow(2, 32))
- self.assertAlmostEqual(s.rand(), 2314333085/pow(2, 32))
- self.assertAlmostEqual(s.rand(), 2255782734/pow(2, 32))
- self.assertAlmostEqual(s.rand(), 2921461239/pow(2, 32))
- self.assertAlmostEqual(s.rand(), 1024197102/pow(2, 32))
+ self.assertAlmostEqual(s.rand(), 959852049 / pow(2, 32))
+ self.assertAlmostEqual(s.rand(), 2314333085 / pow(2, 32))
+ self.assertAlmostEqual(s.rand(), 2255782734 / pow(2, 32))
+ self.assertAlmostEqual(s.rand(), 2921461239 / pow(2, 32))
+ self.assertAlmostEqual(s.rand(), 1024197102 / pow(2, 32))
def test_randbelow(self):
s = SyncRNG(seed=123456)
@@ -39,10 +39,15 @@ class SyncRNGTestCase(unittest.TestCase):
def test_first_1000(self):
s = SyncRNG(seed=0)
- with open("./test/first_1000_seed_0.txt", "r") as fid:
+
+ here = os.path.dirname(__file__)
+ test_file = os.path.join(here, "first_1000_seed_0.txt")
+
+ with open(test_file, "r") as fid:
for line in fid:
exp = int(line.strip())
self.assertTrue(exp == s.randi())
-if __name__ == '__main__':
+
+if __name__ == "__main__":
unittest.main()