diff options
| author | Gertjan van den Burg <gertjanvandenburg@gmail.com> | 2021-01-14 17:06:19 +0000 |
|---|---|---|
| committer | Gertjan van den Burg <gertjanvandenburg@gmail.com> | 2021-01-14 17:06:19 +0000 |
| commit | 9187fc1fd45203c2e3501a8b55236e9db71fc2ed (patch) | |
| tree | 572cffe74f5894f9cf1bd5168ee462cfc4afe9a9 | |
| parent | update python version with R version (diff) | |
| parent | cast to int explicitly (diff) | |
| download | SyncRNG-9187fc1fd45203c2e3501a8b55236e9db71fc2ed.tar.gz SyncRNG-9187fc1fd45203c2e3501a8b55236e9db71fc2ed.zip | |
Merge branch 'bugfix/python_error' into python
| -rw-r--r-- | Makefile | 83 | ||||
| -rw-r--r-- | SyncRNG/__init__.py | 29 | ||||
| -rw-r--r-- | SyncRNG/__version__.py | 5 | ||||
| -rw-r--r-- | setup.py | 109 | ||||
| -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-x | tests/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
@@ -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)) @@ -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() |
