Skip to content

TST. API: test using distributions.h via cffi #14954

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Nov 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions doc/source/reference/random/examples/cffi.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Extending via CFFI
------------------

.. literalinclude:: ../../../../../numpy/random/_examples/cffi/extending.py
:language: python
20 changes: 20 additions & 0 deletions doc/source/reference/random/extending.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,25 @@ RNG structure.
These functions along with a minimal setup file are included in the
`examples` folder, ``numpy.random.examples``.

CFFI
====

CFFI can be used to directly access the functions in
``include/numpy/random/distributions.h``. Some "massaging" of the header
file is required:

.. literalinclude:: ../../../../numpy/random/_examples/cffi/extending.py
:language: python
:end-before: dlopen

Once the header is parsed by ``ffi.cdef``, the functions can be accessed
directly from the ``_generator`` shared object, using the `BitGenerator.cffi` interface.

.. literalinclude:: ../../../../numpy/random/_examples/cffi/extending.py
:language: python
:start-after: dlopen


New Basic RNGs
==============
`~Generator` can be used with other user-provided BitGenerators. The simplest
Expand Down Expand Up @@ -85,3 +104,4 @@ Examples
Numba <examples/numba>
CFFI + Numba <examples/numba_cffi>
Cython <examples/cython/index>
CFFI <examples/cffi>
2 changes: 1 addition & 1 deletion numpy/core/include/numpy/random/distributions.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
#define RAND_INT_MAX INT64_MAX
#endif

#ifdef DLL_EXPORT
#ifdef _MSC_VER
#define DECLDIR __declspec(dllexport)
#else
#define DECLDIR extern
Expand Down
72 changes: 72 additions & 0 deletions numpy/random/_examples/cffi/extending.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""
Use cffi to access the underlying C functions from distributions.h
"""
import os
import numpy as np
import cffi
ffi = cffi.FFI()

inc_dir = os.path.join(np.get_include(), 'numpy')

# Basic numpy types
ffi.cdef('''
typedef intptr_t npy_intp;
typedef unsigned char npy_bool;

''')

with open(os.path.join(inc_dir, 'random', 'bitgen.h')) as fid:
s = []
for line in fid:
# massage the include file
if line.strip().startswith('#'):
continue
s.append(line)
ffi.cdef('\n'.join(s))

with open(os.path.join(inc_dir, 'random', 'distributions.h')) as fid:
s = []
in_skip = 0
for line in fid:
# massage the include file
if line.strip().startswith('#'):
continue

# skip any inlined function definition
# which starts with 'static NPY_INLINE xxx(...) {'
# and ends with a closing '}'
if line.strip().startswith('static NPY_INLINE'):
in_skip += line.count('{')
continue
elif in_skip > 0:
in_skip += line.count('{')
in_skip -= line.count('}')
continue

# replace defines with their value or remove them
line = line.replace('DECLDIR', '')
line = line.replace('NPY_INLINE', '')
line = line.replace('RAND_INT_TYPE', 'int64_t')
s.append(line)
ffi.cdef('\n'.join(s))

lib = ffi.dlopen(np.random._generator.__file__)

# Compare the distributions.h random_standard_normal_fill to
# Generator.standard_random
bit_gen = np.random.PCG64()
rng = np.random.Generator(bit_gen)
state = bit_gen.state

interface = rng.bit_generator.cffi
n = 100
vals_cffi = ffi.new('double[%d]' % n)
lib.random_standard_normal_fill(interface.bit_generator, n, vals_cffi)

# reset the state
bit_gen.state = state

vals = rng.standard_normal(n)

for i in range(n):
assert vals[i] == vals_cffi[i]
6 changes: 3 additions & 3 deletions numpy/random/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ def generate_libraries(ext, build_dir):
libraries=EXTRA_LIBRARIES,
extra_compile_args=EXTRA_COMPILE_ARGS,
extra_link_args=EXTRA_LINK_ARGS,
depends=['_%s.pyx' % gen, 'bit_generator.pyx',
'bit_generator.pxd'],
depends=['_%s.pyx' % gen, '_bit_generator.pyx',
'_bit_generator.pxd'],
define_macros=_defs,
)
for gen in ['_common', '_bit_generator']:
Expand Down Expand Up @@ -112,7 +112,7 @@ def generate_libraries(ext, build_dir):
depends=['%s.pyx' % gen],
define_macros=defs,
)
config.add_data_files('_bounded_inteters.pxd')
config.add_data_files('_bounded_integers.pxd')
config.add_extension('mtrand',
sources=['mtrand.c',
'src/legacy/legacy-distributions.c',
Expand Down
7 changes: 7 additions & 0 deletions numpy/random/tests/test_direct.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
from os.path import join
import sys

import numpy as np
from numpy.testing import (assert_equal, assert_allclose, assert_array_equal,
Expand All @@ -26,6 +27,12 @@
except ImportError:
MISSING_CTYPES = False

if sys.flags.optimize > 1:
# no docstrings present to inspect when PYTHONOPTIMIZE/Py_OptimizeFlag > 1
# cffi cannot succeed
MISSING_CFFI = True


pwd = os.path.dirname(os.path.abspath(__file__))


Expand Down
17 changes: 15 additions & 2 deletions numpy/random/tests/test_extending.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,21 @@
import pytest
import warnings

try:
import cffi
except ImportError:
cffi = None

if sys.flags.optimize > 1:
# no docstrings present to inspect when PYTHONOPTIMIZE/Py_OptimizeFlag > 1
# cffi cannot succeed
cffi = None

try:
with warnings.catch_warnings(record=True) as w:
# numba issue gh-4733
warnings.filterwarnings('always', '', DeprecationWarning)
import numba
import cffi
except ImportError:
numba = None

Expand All @@ -26,7 +35,11 @@ def test_cython():
sys.argv = argv
os.chdir(curdir)

@pytest.mark.skipif(numba is None, reason="requires numba")
@pytest.mark.skipif(numba is None or cffi is None,
reason="requires numba and cffi")
def test_numba():
from numpy.random._examples.numba import extending

@pytest.mark.skipif(cffi is None, reason="requires cffi")
def test_cffi():
from numpy.random._examples.cffi import extending
2 changes: 2 additions & 0 deletions test_requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ pytest-cov==2.8.1
pickle5; python_version == '3.7'
pickle5; python_version == '3.6' and platform_python_implementation != 'PyPy'
nose
# for numpy.random.test.test_extending
cffi