8000 Merge pull request #14954 from mattip/test-extending-cffi · numpy/numpy@3fcf144 · GitHub
[go: up one dir, main page]

Skip to content

Commit 3fcf144

Browse files
authored
Merge pull request #14954 from mattip/test-extending-cffi
TST. API: test using distributions.h via cffi
2 parents d379088 + e0519fe commit 3fcf144

File tree

8 files changed

+125
-6
lines changed

8 files changed

+125
-6
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Extending via CFFI
2+
------------------
3+
4+
.. literalinclude:: ../../../../../numpy/random/_examples/cffi/extending.py
5+
:language: python

doc/source/reference/random/extending.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,25 @@ RNG structure.
4949
These functions along with a minimal setup file are included in the
5050
`examples` folder, ``numpy.random.examples``.
5151

52+
CFFI
53+
====
54+
55+
CFFI can be used to directly access the functions in
56+
``include/numpy/random/distributions.h``. Some "massaging" of the header
57+
file is required:
58+
59+
.. literalinclude:: ../../../../numpy/random/_examples/cffi/extending.py
60+
:language: python
61+
:end-before: dlopen
62+
63+
Once the header is parsed by ``ffi.cdef``, the functions can be accessed
64+
directly from the ``_generator`` shared object, using the `BitGenerator.cffi` interface.
65+
66+
.. literalinclude:: ../../../../numpy/random/_examples/cffi/extending.py
67+
:language: python
68+
:start-after: dlopen
69+
70+
5271
New Basic RNGs
5372
==============
5473
`~Generator` can be used with other user-provided BitGenerators. The simplest
@@ -85,3 +104,4 @@ Examples
85104
Numba <examples/numba>
86105
CFFI + Numba <examples/numba_cffi>
87106
Cython <examples/cython/index>
107+
CFFI <examples/cffi>

numpy/core/include/numpy/random/distributions.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
#define RAND_INT_MAX INT64_MAX
2525
#endif
2626

27-
#ifdef DLL_EXPORT
27+
#ifdef _MSC_VER
2828
#define DECLDIR __declspec(dllexport)
2929
#else
3030
#define DECLDIR extern
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"""
2+
Use cffi to access the underlying C functions from distributions.h
3+
"""
4+
import os
5+
import numpy as np
6+
import cffi
7+
ffi = cffi.FFI()
8+
9+
inc_dir = os.path.join(np.get_include(), 'numpy')
10+
11+
# Basic numpy types
12+
ffi.cdef('''
13+ typedef intptr_t npy_intp;
14+
typedef unsigned char npy_bool;
15+
16+
''')
17+
18+
with open(os.path.join(inc_dir, 'random', 'bitgen.h')) as fid:
19+
s = []
20+
for line in fid:
21+
# massage the include file
22+
if line.strip().startswith('#'):
23+
continue
24+
s.append(line)
25+
ffi.cdef('\n'.join(s))
26+
27+
with open(os.path.join(inc_dir, 'random', 'distributions.h')) as fid:
28+
s = []
29+
in_skip = 0
30+
for line in fid:
31+
# massage the include file
32+
if line.strip().startswith('#'):
33+
continue
34+
35+
# skip any inlined function definition
36+
# which starts with 'static NPY_INLINE xxx(...) {'
37+
# and ends with a closing '}'
38+
if line.strip().startswith('static NPY_INLINE'):
39+
in_skip += line.count('{')
40+
continue
41+
elif in_skip > 0:
42+
in_skip += line.count('{')
43+
in_skip -= line.count('}')
44+
continue
45+
46+
# replace defines with their value or remove them
47+
line = line.replace('DECLDIR', '')
48+
line = line.replace('NPY_INLINE', '')
49+
line = line.replace('RAND_INT_TYPE', 'int64_t')
50+
s.append(line)
51+
ffi.cdef('\n'.join(s))
52+
53+
lib = ffi.dlopen(np.random._generator.__file__)
54+
55+
# Compare the distributions.h random_standard_normal_fill to
56+
# Generator.standard_random
57+
bit_gen = np.random.PCG64()
58+
rng = np.random.Generator(bit_gen)
59+
state = bit_gen.state
60+
61+
interface = rng.bit_generator.cffi
62+
n = 100
63+
vals_cffi = ffi.new('double[%d]' % n)
64+
lib.random_standard_normal_fill(interface.bit_generator, n, vals_cffi)
65+
66+
# reset the state
67+
bit_gen.state = state
68+
69+
vals = rng.standard_normal(n)
70+
71+
for i in range(n):
72+
assert vals[i] == vals_cffi[i]

numpy/random/setup.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,8 @@ def generate_libraries(ext, build_dir):
7878
libraries=EXTRA_LIBRARIES,
7979
extra_compile_args=EXTRA_COMPILE_ARGS,
8080
extra_link_args=EXTRA_LINK_ARGS,
81-
depends=['_%s.pyx' % gen, 'bit_generator.pyx',
82-
'bit_generator.pxd'],
81+
depends=['_%s.pyx' % gen, '_bit_generator.pyx',
82+
'_bit_generator.pxd'],
8383
define_macros=_defs,
8484
)
8585
for gen in ['_common', '_bit_generator']:
@@ -112,7 +112,7 @@ def generate_libraries(ext, build_dir):
112112
depends=['%s.pyx' % gen],
113113
define_macros=defs,
114114
)
115-
config.add_data_files('_bounded_inteters.pxd')
115+
config.add_data_files('_bounded_integers.pxd')
116116
config.add_extension('mtrand',
117117
sources=['mtrand.c',
118118
'src/legacy/legacy-distributions.c',

numpy/random/tests/test_direct.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
from os.path import join
3+
import sys
34

45
import numpy as np
56
from numpy.testing import (assert_equal, assert_allclose, assert_array_equal,
@@ -26,6 +27,12 @@
2627
except ImportError:
2728
MISSING_CTYPES = False
2829

30+
if sys.flags.optimize > 1:
31+
# no docstrings present to inspect when PYTHONOPTIMIZE/Py_OptimizeFlag > 1
32+
# cffi cannot succeed
33+
MISSING_CFFI = True
34+
35+
2936
pwd = os.path.dirname(os.path.abspath(__file__))
3037

3138

numpy/random/tests/test_extending.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,21 @@
22
import pytest
33
import warnings
44

5+
try:
6+
import cffi
7+
except ImportError:
8+
cffi = None
9+
10+
if sys.flags.optimize > 1:
11+
# no docstrings present to inspect when PYTHONOPTIMIZE/Py_OptimizeFlag > 1
12+
# cffi cannot succeed
13+
cffi = None
14+
515
try:
616
with warnings.catch_warnings(record=True) as w:
717
# numba issue gh-4733
818
warnings.filterwarnings('always', '', DeprecationWarning)
919
import numba
10-
import cffi
1120
except ImportError:
1221
numba = None
1322

@@ -32,7 +41,11 @@ def test_cython():
3241
sys.argv = argv
3342
os.chdir(curdir)
3443

35-
@pytest.mark.skipif(numba is None, reason="requires numba")
44+
@pytest.mark.skipif(numba is None or cffi is None,
45+
reason="requires numba and cffi")
3646
def test_numba():
3747
from numpy.random._examples.numba import extending
3848

49+
@pytest.mark.skipif(cffi is None, reason="requires cffi")
50+
def test_cffi():
51+
from numpy.random._examples.cffi import extending

test_requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ pytest-cov==2.8.1
55
pickle5; python_version == '3.7'
66
pickle5; python_version == '3.6' and platform_python_implementation != 'PyPy'
77
nose
8+
# for numpy.random.test.test_extending
9+
cffi

0 commit comments

Comments
 (0)
0