Videre
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
from os.path import dirname
|
||||
import unittest
|
||||
from unittest.suite import TestSuite
|
||||
|
||||
from numba.testing import load_testsuite
|
||||
|
||||
def load_tests(loader, tests, pattern):
|
||||
suite = TestSuite()
|
||||
suite.addTests(load_testsuite(loader, dirname(__file__)))
|
||||
return suite
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,76 @@
|
||||
import numba as nb
|
||||
|
||||
|
||||
#
|
||||
# UFunc
|
||||
#
|
||||
|
||||
def direct_ufunc_cache_usecase(**kwargs):
|
||||
@nb.vectorize(["intp(intp)", "float64(float64)"], cache=True, **kwargs)
|
||||
def ufunc(inp):
|
||||
return inp * 2
|
||||
|
||||
return ufunc
|
||||
|
||||
|
||||
def indirect_ufunc_cache_usecase(**kwargs):
|
||||
@nb.njit(cache=True)
|
||||
def indirect_ufunc_core(inp):
|
||||
return inp * 3
|
||||
|
||||
@nb.vectorize(["intp(intp)", "float64(float64)", "complex64(complex64)"],
|
||||
**kwargs)
|
||||
def ufunc(inp):
|
||||
return indirect_ufunc_core(inp)
|
||||
|
||||
return ufunc
|
||||
|
||||
|
||||
#
|
||||
# DUFunc
|
||||
#
|
||||
|
||||
def direct_dufunc_cache_usecase(**kwargs):
|
||||
@nb.vectorize(cache=True, **kwargs)
|
||||
def ufunc(inp):
|
||||
return inp * 2
|
||||
|
||||
return ufunc
|
||||
|
||||
|
||||
def indirect_dufunc_cache_usecase(**kwargs):
|
||||
@nb.njit(cache=True)
|
||||
def indirect_ufunc_core(inp):
|
||||
return inp * 3
|
||||
|
||||
@nb.vectorize(**kwargs)
|
||||
def ufunc(inp):
|
||||
return indirect_ufunc_core(inp)
|
||||
|
||||
return ufunc
|
||||
|
||||
|
||||
#
|
||||
# GUFunc
|
||||
#
|
||||
|
||||
def direct_gufunc_cache_usecase(**kwargs):
|
||||
@nb.guvectorize(["(intp, intp[:])", "(float64, float64[:])"],
|
||||
"()->()", cache=True, **kwargs)
|
||||
def gufunc(inp, out):
|
||||
out[0] = inp * 2
|
||||
|
||||
return gufunc
|
||||
|
||||
|
||||
def indirect_gufunc_cache_usecase(**kwargs):
|
||||
@nb.njit(cache=True)
|
||||
def core(x):
|
||||
return x * 3
|
||||
|
||||
@nb.guvectorize(["(intp, intp[:])", "(float64, float64[:])",
|
||||
"(complex64, complex64[:])"], "()->()", **kwargs)
|
||||
def gufunc(inp, out):
|
||||
out[0] = core(inp)
|
||||
|
||||
return gufunc
|
||||
@@ -0,0 +1,228 @@
|
||||
import sys
|
||||
import os.path
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
import numpy as np
|
||||
|
||||
from numba.tests.support import capture_cache_log
|
||||
from numba.tests.test_caching import BaseCacheTest
|
||||
from numba.core import config
|
||||
import unittest
|
||||
|
||||
|
||||
class UfuncCacheTest(BaseCacheTest):
|
||||
"""
|
||||
Since the cache stats is not exposed by ufunc, we test by looking at the
|
||||
cache debug log.
|
||||
"""
|
||||
_numba_parallel_test_ = False
|
||||
|
||||
here = os.path.dirname(__file__)
|
||||
usecases_file = os.path.join(here, "cache_usecases.py")
|
||||
modname = "ufunc_caching_test_fodder"
|
||||
|
||||
regex_data_saved = re.compile(r'\[cache\] data saved to')
|
||||
regex_index_saved = re.compile(r'\[cache\] index saved to')
|
||||
|
||||
regex_data_loaded = re.compile(r'\[cache\] data loaded from')
|
||||
regex_index_loaded = re.compile(r'\[cache\] index loaded from')
|
||||
|
||||
def check_cache_saved(self, cachelog, count):
|
||||
"""
|
||||
Check number of cache-save were issued
|
||||
"""
|
||||
data_saved = self.regex_data_saved.findall(cachelog)
|
||||
index_saved = self.regex_index_saved.findall(cachelog)
|
||||
self.assertEqual(len(data_saved), count)
|
||||
self.assertEqual(len(index_saved), count)
|
||||
|
||||
def check_cache_loaded(self, cachelog, count):
|
||||
"""
|
||||
Check number of cache-load were issued
|
||||
"""
|
||||
data_loaded = self.regex_data_loaded.findall(cachelog)
|
||||
index_loaded = self.regex_index_loaded.findall(cachelog)
|
||||
self.assertEqual(len(data_loaded), count)
|
||||
self.assertEqual(len(index_loaded), count)
|
||||
|
||||
def check_ufunc_cache(self, usecase_name, n_overloads, **kwargs):
|
||||
"""
|
||||
Check number of cache load/save.
|
||||
There should be one per overloaded version.
|
||||
"""
|
||||
mod = self.import_module()
|
||||
usecase = getattr(mod, usecase_name)
|
||||
# New cache entry saved
|
||||
with capture_cache_log() as out:
|
||||
new_ufunc = usecase(**kwargs)
|
||||
cachelog = out.getvalue()
|
||||
self.check_cache_saved(cachelog, count=n_overloads)
|
||||
|
||||
# Use cached version
|
||||
with capture_cache_log() as out:
|
||||
cached_ufunc = usecase(**kwargs)
|
||||
cachelog = out.getvalue()
|
||||
self.check_cache_loaded(cachelog, count=n_overloads)
|
||||
|
||||
return new_ufunc, cached_ufunc
|
||||
|
||||
|
||||
class TestUfuncCacheTest(UfuncCacheTest):
|
||||
|
||||
def test_direct_ufunc_cache(self, **kwargs):
|
||||
new_ufunc, cached_ufunc = self.check_ufunc_cache(
|
||||
"direct_ufunc_cache_usecase", n_overloads=2, **kwargs)
|
||||
# Test the cached and original versions
|
||||
inp = np.random.random(10).astype(np.float64)
|
||||
np.testing.assert_equal(new_ufunc(inp), cached_ufunc(inp))
|
||||
inp = np.arange(10, dtype=np.intp)
|
||||
np.testing.assert_equal(new_ufunc(inp), cached_ufunc(inp))
|
||||
|
||||
def test_direct_ufunc_cache_objmode(self):
|
||||
self.test_direct_ufunc_cache(forceobj=True)
|
||||
|
||||
def test_direct_ufunc_cache_parallel(self):
|
||||
self.test_direct_ufunc_cache(target='parallel')
|
||||
|
||||
def test_indirect_ufunc_cache(self, **kwargs):
|
||||
new_ufunc, cached_ufunc = self.check_ufunc_cache(
|
||||
"indirect_ufunc_cache_usecase", n_overloads=3, **kwargs)
|
||||
# Test the cached and original versions
|
||||
inp = np.random.random(10).astype(np.float64)
|
||||
np.testing.assert_equal(new_ufunc(inp), cached_ufunc(inp))
|
||||
inp = np.arange(10, dtype=np.intp)
|
||||
np.testing.assert_equal(new_ufunc(inp), cached_ufunc(inp))
|
||||
|
||||
def test_indirect_ufunc_cache_parallel(self):
|
||||
self.test_indirect_ufunc_cache(target='parallel')
|
||||
|
||||
|
||||
class TestDUfuncCacheTest(UfuncCacheTest):
|
||||
# Note: DUFunc doesn't support parallel target yet
|
||||
|
||||
def check_dufunc_usecase(self, usecase_name):
|
||||
mod = self.import_module()
|
||||
usecase = getattr(mod, usecase_name)
|
||||
# Create dufunc
|
||||
with capture_cache_log() as out:
|
||||
ufunc = usecase()
|
||||
self.check_cache_saved(out.getvalue(), count=0)
|
||||
# Compile & cache
|
||||
with capture_cache_log() as out:
|
||||
ufunc(np.arange(10))
|
||||
self.check_cache_saved(out.getvalue(), count=1)
|
||||
self.check_cache_loaded(out.getvalue(), count=0)
|
||||
# Use cached
|
||||
with capture_cache_log() as out:
|
||||
ufunc = usecase()
|
||||
ufunc(np.arange(10))
|
||||
self.check_cache_loaded(out.getvalue(), count=1)
|
||||
|
||||
def test_direct_dufunc_cache(self):
|
||||
# We don't test for objmode because DUfunc don't support it.
|
||||
self.check_dufunc_usecase('direct_dufunc_cache_usecase')
|
||||
|
||||
def test_indirect_dufunc_cache(self):
|
||||
self.check_dufunc_usecase('indirect_dufunc_cache_usecase')
|
||||
|
||||
|
||||
def _fix_raw_path(rstr):
|
||||
if config.IS_WIN32:
|
||||
rstr = rstr.replace(r'/', r'\\\\')
|
||||
return rstr
|
||||
|
||||
|
||||
class TestGUfuncCacheTest(UfuncCacheTest):
|
||||
|
||||
def test_filename_prefix(self):
|
||||
mod = self.import_module()
|
||||
usecase = getattr(mod, "direct_gufunc_cache_usecase")
|
||||
with capture_cache_log() as out:
|
||||
usecase()
|
||||
cachelog = out.getvalue()
|
||||
# find number filename with "guf-" prefix
|
||||
fmt1 = _fix_raw_path(r'/__pycache__/guf-{}')
|
||||
prefixed = re.findall(fmt1.format(self.modname), cachelog)
|
||||
fmt2 = _fix_raw_path(r'/__pycache__/{}')
|
||||
normal = re.findall(fmt2.format(self.modname), cachelog)
|
||||
# expecting 2 overloads
|
||||
self.assertGreater(len(normal), 2)
|
||||
# expecting equal number of wrappers and overloads cache entries
|
||||
self.assertEqual(len(normal), len(prefixed))
|
||||
|
||||
def test_direct_gufunc_cache(self, **kwargs):
|
||||
# 2 cache entry for the 2 overloads
|
||||
# and 2 cache entry for the gufunc wrapper
|
||||
new_ufunc, cached_ufunc = self.check_ufunc_cache(
|
||||
"direct_gufunc_cache_usecase", n_overloads=2 + 2, **kwargs)
|
||||
# Test the cached and original versions
|
||||
inp = np.random.random(10).astype(np.float64)
|
||||
np.testing.assert_equal(new_ufunc(inp), cached_ufunc(inp))
|
||||
inp = np.arange(10, dtype=np.intp)
|
||||
np.testing.assert_equal(new_ufunc(inp), cached_ufunc(inp))
|
||||
|
||||
def test_direct_gufunc_cache_objmode(self):
|
||||
self.test_direct_gufunc_cache(forceobj=True)
|
||||
|
||||
def test_direct_gufunc_cache_parallel(self):
|
||||
self.test_direct_gufunc_cache(target='parallel')
|
||||
|
||||
def test_indirect_gufunc_cache(self, **kwargs):
|
||||
# 3 cache entry for the 3 overloads
|
||||
# and no cache entry for the gufunc wrapper
|
||||
new_ufunc, cached_ufunc = self.check_ufunc_cache(
|
||||
"indirect_gufunc_cache_usecase", n_overloads=3, **kwargs)
|
||||
# Test the cached and original versions
|
||||
inp = np.random.random(10).astype(np.float64)
|
||||
np.testing.assert_equal(new_ufunc(inp), cached_ufunc(inp))
|
||||
inp = np.arange(10, dtype=np.intp)
|
||||
np.testing.assert_equal(new_ufunc(inp), cached_ufunc(inp))
|
||||
|
||||
def test_indirect_gufunc_cache_parallel(self, **kwargs):
|
||||
self.test_indirect_gufunc_cache(target='parallel')
|
||||
|
||||
|
||||
class TestCacheSpecificIssue(UfuncCacheTest):
|
||||
|
||||
def run_in_separate_process(self, runcode):
|
||||
# Based on the same name util function in test_dispatcher but modified
|
||||
# to allow user to define what to run.
|
||||
code = """if 1:
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, %(tempdir)r)
|
||||
mod = __import__(%(modname)r)
|
||||
mod.%(runcode)s
|
||||
""" % dict(tempdir=self.tempdir, modname=self.modname,
|
||||
runcode=runcode)
|
||||
|
||||
popen = subprocess.Popen([sys.executable, "-c", code],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
out, err = popen.communicate()
|
||||
if popen.returncode != 0:
|
||||
raise AssertionError("process failed with code %s: stderr follows"
|
||||
"\n%s\n" % (popen.returncode, err.decode()))
|
||||
|
||||
#
|
||||
# The following test issue #2198 that loading cached (g)ufunc first
|
||||
# bypasses some target context initialization.
|
||||
#
|
||||
|
||||
def test_first_load_cached_ufunc(self):
|
||||
# ensure function is cached
|
||||
self.run_in_separate_process('direct_ufunc_cache_usecase()')
|
||||
# use the cached function
|
||||
# this will fail if the target context is not init'ed
|
||||
self.run_in_separate_process('direct_ufunc_cache_usecase()')
|
||||
|
||||
def test_first_load_cached_gufunc(self):
|
||||
# ensure function is cached
|
||||
self.run_in_separate_process('direct_gufunc_cache_usecase()')
|
||||
# use the cached function
|
||||
# this will fail out if the target context is not init'ed
|
||||
self.run_in_separate_process('direct_gufunc_cache_usecase()')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,174 @@
|
||||
import contextlib
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
|
||||
from numba import vectorize, guvectorize
|
||||
|
||||
from numba.tests.support import (TestCase, CheckWarningsMixin,
|
||||
skip_macos_fenv_errors)
|
||||
import unittest
|
||||
|
||||
|
||||
def sqrt(val):
|
||||
if val < 0.0:
|
||||
raise ValueError('Value must be positive')
|
||||
return val ** 0.5
|
||||
|
||||
|
||||
def gufunc_foo(inp, n, out):
|
||||
for i in range(inp.shape[0]):
|
||||
if inp[i] < 0:
|
||||
raise ValueError('Value must be positive')
|
||||
out[i] = inp[i] * n[0]
|
||||
|
||||
def truediv(a, b):
|
||||
return a / b
|
||||
|
||||
def floordiv(a, b):
|
||||
return a // b
|
||||
|
||||
def remainder(a, b):
|
||||
return a % b
|
||||
|
||||
def power(a, b):
|
||||
return a ** b
|
||||
|
||||
|
||||
class TestExceptions(TestCase):
|
||||
"""
|
||||
Test raising exceptions inside ufuncs.
|
||||
"""
|
||||
|
||||
def check_ufunc_raise(self, **vectorize_args):
|
||||
f = vectorize(['float64(float64)'], **vectorize_args)(sqrt)
|
||||
arr = np.array([1, 4, -2, 9, -1, 16], dtype=np.float64)
|
||||
out = np.zeros_like(arr)
|
||||
with self.assertRaises(ValueError) as cm:
|
||||
f(arr, out)
|
||||
self.assertIn('Value must be positive', str(cm.exception))
|
||||
# All values were computed except for the ones giving an error
|
||||
self.assertEqual(list(out), [1, 2, 0, 3, 0, 4])
|
||||
|
||||
def test_ufunc_raise(self):
|
||||
self.check_ufunc_raise(nopython=True)
|
||||
|
||||
def test_ufunc_raise_objmode(self):
|
||||
self.check_ufunc_raise(forceobj=True)
|
||||
|
||||
def check_gufunc_raise(self, **vectorize_args):
|
||||
f = guvectorize(['int32[:], int32[:], int32[:]'], '(n),()->(n)',
|
||||
**vectorize_args)(gufunc_foo)
|
||||
arr = np.array([1, 2, -3, 4], dtype=np.int32)
|
||||
out = np.zeros_like(arr)
|
||||
with self.assertRaises(ValueError) as cm:
|
||||
f(arr, 2, out)
|
||||
# The gufunc bailed out after the error
|
||||
self.assertEqual(list(out), [2, 4, 0, 0])
|
||||
|
||||
def test_gufunc_raise(self):
|
||||
self.check_gufunc_raise(nopython=True)
|
||||
|
||||
def test_gufunc_raise_objmode(self):
|
||||
self.check_gufunc_raise(forceobj=True)
|
||||
|
||||
class TestFloatingPointExceptions(TestCase, CheckWarningsMixin):
|
||||
"""
|
||||
Test floating-point exceptions inside ufuncs.
|
||||
|
||||
Note the warnings emitted by Numpy reflect IEEE-754 semantics.
|
||||
"""
|
||||
|
||||
def check_truediv_real(self, dtype):
|
||||
"""
|
||||
Test 1 / 0 and 0 / 0.
|
||||
"""
|
||||
f = vectorize(nopython=True)(truediv)
|
||||
a = np.array([5., 6., 0., 8.], dtype=dtype)
|
||||
b = np.array([1., 0., 0., 4.], dtype=dtype)
|
||||
expected = np.array([5., float('inf'), float('nan'), 2.])
|
||||
with self.check_warnings(["divide by zero encountered",
|
||||
"invalid value encountered"]):
|
||||
res = f(a, b)
|
||||
self.assertPreciseEqual(res, expected)
|
||||
|
||||
def test_truediv_float(self):
|
||||
self.check_truediv_real(np.float64)
|
||||
|
||||
def test_truediv_integer(self):
|
||||
self.check_truediv_real(np.int32)
|
||||
|
||||
def check_divmod_float(self, pyfunc, values, messages):
|
||||
"""
|
||||
Test 1 // 0 and 0 // 0.
|
||||
"""
|
||||
f = vectorize(nopython=True)(pyfunc)
|
||||
a = np.array([5., 6., 0., 9.])
|
||||
b = np.array([1., 0., 0., 4.])
|
||||
expected = np.array(values)
|
||||
with self.check_warnings(messages):
|
||||
res = f(a, b)
|
||||
self.assertPreciseEqual(res, expected)
|
||||
|
||||
def test_floordiv_float(self):
|
||||
self.check_divmod_float(floordiv,
|
||||
[5.0, float('inf'), float('nan'), 2.0],
|
||||
["divide by zero encountered",
|
||||
"invalid value encountered"])
|
||||
|
||||
@skip_macos_fenv_errors
|
||||
def test_remainder_float(self):
|
||||
self.check_divmod_float(remainder,
|
||||
[0.0, float('nan'), float('nan'), 1.0],
|
||||
["invalid value encountered"])
|
||||
|
||||
def check_divmod_int(self, pyfunc, values):
|
||||
"""
|
||||
Test 1 % 0 and 0 % 0.
|
||||
"""
|
||||
f = vectorize(nopython=True)(pyfunc)
|
||||
a = np.array([5, 6, 0, 9])
|
||||
b = np.array([1, 0, 0, 4])
|
||||
expected = np.array(values)
|
||||
# No warnings raised because LLVM makes it difficult
|
||||
with self.check_warnings([]):
|
||||
res = f(a, b)
|
||||
self.assertPreciseEqual(res, expected)
|
||||
|
||||
def test_floordiv_int(self):
|
||||
self.check_divmod_int(floordiv, [5, 0, 0, 2])
|
||||
|
||||
def test_remainder_int(self):
|
||||
self.check_divmod_int(remainder, [0, 0, 0, 1])
|
||||
|
||||
def test_power_float(self):
|
||||
"""
|
||||
Test 0 ** -1 and 2 ** <big number>.
|
||||
"""
|
||||
f = vectorize(nopython=True)(power)
|
||||
a = np.array([5., 0., 2., 8.])
|
||||
b = np.array([1., -1., 1e20, 4.])
|
||||
expected = np.array([5., float('inf'), float('inf'), 4096.])
|
||||
with self.check_warnings(["divide by zero encountered",
|
||||
"overflow encountered"]):
|
||||
res = f(a, b)
|
||||
self.assertPreciseEqual(res, expected)
|
||||
|
||||
def test_power_integer(self):
|
||||
"""
|
||||
Test 0 ** -1.
|
||||
Note 2 ** <big number> returns an undefined value (depending
|
||||
on the algorithm).
|
||||
"""
|
||||
dtype = np.int64
|
||||
f = vectorize(["int64(int64, int64)"], nopython=True)(power)
|
||||
a = np.array([5, 0, 6], dtype=dtype)
|
||||
b = np.array([1, -1, 2], dtype=dtype)
|
||||
expected = np.array([5, -2**63, 36], dtype=dtype)
|
||||
with self.check_warnings([]):
|
||||
res = f(a, b)
|
||||
self.assertPreciseEqual(res, expected)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,870 @@
|
||||
import unittest
|
||||
import pickle
|
||||
|
||||
import numpy as np
|
||||
|
||||
from numba import void, float32, float64, int32, int64, jit, guvectorize
|
||||
from numba.core.errors import TypingError
|
||||
from numba.np.ufunc import GUVectorize
|
||||
from numba.tests.support import TestCase, MemoryLeakMixin
|
||||
|
||||
|
||||
def matmulcore(A, B, C):
|
||||
"""docstring for matmulcore"""
|
||||
m, n = A.shape
|
||||
n, p = B.shape
|
||||
for i in range(m):
|
||||
for j in range(p):
|
||||
C[i, j] = 0
|
||||
for k in range(n):
|
||||
C[i, j] += A[i, k] * B[k, j]
|
||||
|
||||
|
||||
def axpy(a, x, y, out):
|
||||
out[0] = a * x + y
|
||||
|
||||
|
||||
class TestGUFunc(MemoryLeakMixin, TestCase):
|
||||
target = 'cpu'
|
||||
|
||||
def check_matmul_gufunc(self, gufunc):
|
||||
matrix_ct = 1001
|
||||
A = np.arange(matrix_ct * 2 * 4, dtype=np.float32).reshape(matrix_ct, 2, 4)
|
||||
B = np.arange(matrix_ct * 4 * 5, dtype=np.float32).reshape(matrix_ct, 4, 5)
|
||||
|
||||
C = gufunc(A, B)
|
||||
Gold = np.matmul(A, B)
|
||||
|
||||
np.testing.assert_allclose(C, Gold, rtol=1e-5, atol=1e-8)
|
||||
|
||||
def test_gufunc(self):
|
||||
gufunc = GUVectorize(matmulcore, '(m,n),(n,p)->(m,p)',
|
||||
target=self.target)
|
||||
gufunc.add((float32[:, :], float32[:, :], float32[:, :]))
|
||||
gufunc = gufunc.build_ufunc()
|
||||
|
||||
self.check_matmul_gufunc(gufunc)
|
||||
|
||||
def test_guvectorize_decor(self):
|
||||
gufunc = guvectorize([void(float32[:,:], float32[:,:], float32[:,:])],
|
||||
'(m,n),(n,p)->(m,p)',
|
||||
target=self.target)(matmulcore)
|
||||
|
||||
self.check_matmul_gufunc(gufunc)
|
||||
|
||||
def test_ufunc_like(self):
|
||||
# Test problem that the stride of "scalar" gufunc argument not properly
|
||||
# handled when the actual argument is an array,
|
||||
# causing the same value (first value) being repeated.
|
||||
gufunc = GUVectorize(axpy, '(), (), () -> ()', target=self.target)
|
||||
gufunc.add('(intp, intp, intp, intp[:])')
|
||||
gufunc = gufunc.build_ufunc()
|
||||
|
||||
x = np.arange(10, dtype=np.intp)
|
||||
out = gufunc(x, x, x)
|
||||
|
||||
np.testing.assert_equal(out, x * x + x)
|
||||
|
||||
def test_axis(self):
|
||||
# issue https://github.com/numba/numba/issues/6773
|
||||
@guvectorize(["f8[:],f8[:]"], "(n)->(n)")
|
||||
def my_cumsum(x, res):
|
||||
acc = 0
|
||||
for i in range(x.shape[0]):
|
||||
acc += x[i]
|
||||
res[i] = acc
|
||||
|
||||
x = np.ones((20, 30))
|
||||
# Check regular call
|
||||
y = my_cumsum(x, axis=0)
|
||||
expected = np.cumsum(x, axis=0)
|
||||
np.testing.assert_equal(y, expected)
|
||||
# Check "out" kw
|
||||
out_kw = np.zeros_like(y)
|
||||
my_cumsum(x, out=out_kw, axis=0)
|
||||
np.testing.assert_equal(out_kw, expected)
|
||||
|
||||
def test_docstring(self):
|
||||
@guvectorize([(int64[:], int64, int64[:])], '(n),()->(n)')
|
||||
def gufunc(x, y, res):
|
||||
"docstring for gufunc"
|
||||
for i in range(x.shape[0]):
|
||||
res[i] = x[i] + y
|
||||
|
||||
self.assertEqual("numba.tests.npyufunc.test_gufunc", gufunc.__module__)
|
||||
self.assertEqual("gufunc", gufunc.__name__)
|
||||
self.assertEqual("TestGUFunc.test_docstring.<locals>.gufunc", gufunc.__qualname__)
|
||||
self.assertEqual("docstring for gufunc", gufunc.__doc__)
|
||||
|
||||
|
||||
class TestMultipleOutputs(MemoryLeakMixin, TestCase):
|
||||
target = 'cpu'
|
||||
|
||||
def test_multiple_outputs_same_type_passed_in(self):
|
||||
@guvectorize('(x)->(x),(x)',
|
||||
target=self.target)
|
||||
def copy(A, B, C):
|
||||
for i in range(B.size):
|
||||
B[i] = A[i]
|
||||
C[i] = A[i]
|
||||
|
||||
A = np.arange(10, dtype=np.float32) + 1
|
||||
B = np.zeros_like(A)
|
||||
C = np.zeros_like(A)
|
||||
copy(A, B, C)
|
||||
np.testing.assert_allclose(A, B)
|
||||
np.testing.assert_allclose(A, C)
|
||||
|
||||
def test_multiple_outputs_distinct_values(self):
|
||||
|
||||
@guvectorize('(x)->(x),(x)',
|
||||
target=self.target)
|
||||
def copy_and_double(A, B, C):
|
||||
for i in range(B.size):
|
||||
B[i] = A[i]
|
||||
C[i] = A[i] * 2
|
||||
|
||||
A = np.arange(10, dtype=np.float32) + 1
|
||||
B = np.zeros_like(A)
|
||||
C = np.zeros_like(A)
|
||||
copy_and_double(A, B, C)
|
||||
np.testing.assert_allclose(A, B)
|
||||
np.testing.assert_allclose(A * 2, C)
|
||||
|
||||
def test_multiple_output_dtypes(self):
|
||||
|
||||
@guvectorize('(x)->(x),(x)',
|
||||
target=self.target)
|
||||
def copy_and_multiply(A, B, C):
|
||||
for i in range(B.size):
|
||||
B[i] = A[i]
|
||||
C[i] = A[i] * 1.5
|
||||
|
||||
A = np.arange(10, dtype=np.int32) + 1
|
||||
B = np.zeros_like(A)
|
||||
C = np.zeros_like(A, dtype=np.float64)
|
||||
copy_and_multiply(A, B, C)
|
||||
np.testing.assert_allclose(A, B)
|
||||
np.testing.assert_allclose(A * np.float64(1.5), C)
|
||||
|
||||
def test_incorrect_number_of_pos_args(self):
|
||||
@guvectorize('(m),(m)->(m),(m)', target=self.target)
|
||||
def f(x, y, z, w):
|
||||
pass
|
||||
|
||||
arr = np.arange(5, dtype=np.int32)
|
||||
|
||||
# Inputs only, too few
|
||||
msg = "Too few arguments for function 'f'"
|
||||
with self.assertRaises(TypeError) as te:
|
||||
f(arr)
|
||||
self.assertIn(msg, str(te.exception))
|
||||
|
||||
# Inputs and outputs, too many
|
||||
with self.assertRaises(TypeError) as te:
|
||||
f(arr, arr, arr, arr, arr)
|
||||
self.assertIn(msg, str(te.exception))
|
||||
|
||||
|
||||
class TestGUFuncParallel(TestGUFunc):
|
||||
_numba_parallel_test_ = False
|
||||
target = 'parallel'
|
||||
|
||||
|
||||
class TestDynamicGUFunc(MemoryLeakMixin, TestCase):
|
||||
target = 'cpu'
|
||||
|
||||
def test_dynamic_matmul(self):
|
||||
|
||||
def check_matmul_gufunc(gufunc, A, B, C):
|
||||
Gold = np.matmul(A, B)
|
||||
gufunc(A, B, C)
|
||||
np.testing.assert_allclose(C, Gold, rtol=1e-5, atol=1e-8)
|
||||
|
||||
gufunc = GUVectorize(matmulcore, '(m,n),(n,p)->(m,p)',
|
||||
target=self.target, is_dynamic=True)
|
||||
matrix_ct = 10
|
||||
Ai64 = np.arange(matrix_ct * 2 * 4, dtype=np.int64).reshape(matrix_ct, 2, 4)
|
||||
Bi64 = np.arange(matrix_ct * 4 * 5, dtype=np.int64).reshape(matrix_ct, 4, 5)
|
||||
Ci64 = np.arange(matrix_ct * 2 * 5, dtype=np.int64).reshape(matrix_ct, 2, 5)
|
||||
check_matmul_gufunc(gufunc, Ai64, Bi64, Ci64)
|
||||
|
||||
A = np.arange(matrix_ct * 2 * 4, dtype=np.float32).reshape(matrix_ct, 2, 4)
|
||||
B = np.arange(matrix_ct * 4 * 5, dtype=np.float32).reshape(matrix_ct, 4, 5)
|
||||
C = np.arange(matrix_ct * 2 * 5, dtype=np.float32).reshape(matrix_ct, 2, 5)
|
||||
check_matmul_gufunc(gufunc, A, B, C) # trigger compilation
|
||||
|
||||
self.assertEqual(len(gufunc.types), 2) # ensure two versions of gufunc
|
||||
|
||||
|
||||
def test_dynamic_ufunc_like(self):
|
||||
|
||||
def check_ufunc_output(gufunc, x):
|
||||
out = np.zeros(10, dtype=x.dtype)
|
||||
out_kw = np.zeros(10, dtype=x.dtype)
|
||||
gufunc(x, x, x, out)
|
||||
gufunc(x, x, x, out=out_kw)
|
||||
golden = x * x + x
|
||||
np.testing.assert_equal(out, golden)
|
||||
np.testing.assert_equal(out_kw, golden)
|
||||
|
||||
# Test problem that the stride of "scalar" gufunc argument not properly
|
||||
# handled when the actual argument is an array,
|
||||
# causing the same value (first value) being repeated.
|
||||
gufunc = GUVectorize(axpy, '(), (), () -> ()', target=self.target,
|
||||
is_dynamic=True)
|
||||
x = np.arange(10, dtype=np.intp)
|
||||
check_ufunc_output(gufunc, x)
|
||||
|
||||
|
||||
def test_dynamic_scalar_output(self):
|
||||
"""
|
||||
Note that scalar output is a 0-dimension array that acts as
|
||||
a pointer to the output location.
|
||||
"""
|
||||
|
||||
@guvectorize('(n)->()', target=self.target, nopython=True)
|
||||
def sum_row(inp, out):
|
||||
tmp = 0.
|
||||
for i in range(inp.shape[0]):
|
||||
tmp += inp[i]
|
||||
out[()] = tmp
|
||||
|
||||
# inp is (10000, 3)
|
||||
# out is (10000)
|
||||
# The outer (leftmost) dimension must match or numpy broadcasting is performed.
|
||||
|
||||
self.assertTrue(sum_row.is_dynamic)
|
||||
inp = np.arange(30000, dtype=np.int32).reshape(10000, 3)
|
||||
out = np.zeros(10000, dtype=np.int32)
|
||||
sum_row(inp, out)
|
||||
|
||||
# verify result
|
||||
for i in range(inp.shape[0]):
|
||||
self.assertEqual(out[i], inp[i].sum())
|
||||
|
||||
msg = "Too few arguments for function 'sum_row'."
|
||||
with self.assertRaisesRegex(TypeError, msg):
|
||||
sum_row(inp)
|
||||
|
||||
def test_axis(self):
|
||||
# issue https://github.com/numba/numba/issues/6773
|
||||
@guvectorize("(n)->(n)")
|
||||
def my_cumsum(x, res):
|
||||
acc = 0
|
||||
for i in range(x.shape[0]):
|
||||
acc += x[i]
|
||||
res[i] = acc
|
||||
|
||||
x = np.ones((20, 30))
|
||||
expected = np.cumsum(x, axis=0)
|
||||
# Check regular call
|
||||
y = np.zeros_like(expected)
|
||||
my_cumsum(x, y, axis=0)
|
||||
np.testing.assert_equal(y, expected)
|
||||
# Check "out" kw
|
||||
out_kw = np.zeros_like(y)
|
||||
my_cumsum(x, out=out_kw, axis=0)
|
||||
np.testing.assert_equal(out_kw, expected)
|
||||
|
||||
def test_gufunc_attributes(self):
|
||||
@guvectorize("(n)->(n)")
|
||||
def gufunc(x, res):
|
||||
acc = 0
|
||||
for i in range(x.shape[0]):
|
||||
acc += x[i]
|
||||
res[i] = acc
|
||||
|
||||
# ensure gufunc exports attributes
|
||||
attrs = ['signature', 'accumulate', 'at', 'outer', 'reduce', 'reduceat']
|
||||
for attr in attrs:
|
||||
contains = hasattr(gufunc, attr)
|
||||
self.assertTrue(contains, 'dynamic gufunc not exporting "%s"' % (attr,))
|
||||
|
||||
a = np.array([1, 2, 3, 4])
|
||||
res = np.array([0, 0, 0, 0])
|
||||
gufunc(a, res) # trigger compilation
|
||||
self.assertPreciseEqual(res, np.array([1, 3, 6, 10]))
|
||||
|
||||
# other attributes are not callable from a gufunc with signature
|
||||
# see: https://github.com/numba/numba/issues/2794
|
||||
# note: this is a limitation in NumPy source code!
|
||||
self.assertEqual(gufunc.signature, "(n)->(n)")
|
||||
|
||||
with self.assertRaises(RuntimeError) as raises:
|
||||
gufunc.accumulate(a)
|
||||
self.assertEqual(str(raises.exception), "Reduction not defined on ufunc with signature")
|
||||
|
||||
with self.assertRaises(RuntimeError) as raises:
|
||||
gufunc.reduce(a)
|
||||
self.assertEqual(str(raises.exception), "Reduction not defined on ufunc with signature")
|
||||
|
||||
with self.assertRaises(RuntimeError) as raises:
|
||||
gufunc.reduceat(a, [0, 2])
|
||||
self.assertEqual(str(raises.exception), "Reduction not defined on ufunc with signature")
|
||||
|
||||
with self.assertRaises(TypeError) as raises:
|
||||
gufunc.outer(a, a)
|
||||
self.assertEqual(str(raises.exception), "method outer is not allowed in ufunc with non-trivial signature")
|
||||
|
||||
def test_gufunc_attributes2(self):
|
||||
@guvectorize('(),()->()')
|
||||
def add(x, y, res):
|
||||
res[0] = x + y
|
||||
|
||||
# add signature "(),() -> ()" is evaluated to None
|
||||
self.assertIsNone(add.signature)
|
||||
|
||||
a = np.array([1, 2, 3, 4])
|
||||
b = np.array([4, 3, 2, 1])
|
||||
res = np.array([0, 0, 0, 0])
|
||||
add(a, b, res) # trigger compilation
|
||||
self.assertPreciseEqual(res, np.array([5, 5, 5, 5]))
|
||||
|
||||
# now test other attributes
|
||||
self.assertIsNone(add.signature)
|
||||
self.assertEqual(add.reduce(a), 10)
|
||||
self.assertPreciseEqual(add.accumulate(a), np.array([1, 3, 6, 10]))
|
||||
self.assertPreciseEqual(add.outer([0, 1], [1, 2]), np.array([[1, 2], [2, 3]]))
|
||||
self.assertPreciseEqual(add.reduceat(a, [0, 2]), np.array([3, 7]))
|
||||
|
||||
x = np.array([1, 2, 3, 4])
|
||||
y = np.array([1, 2])
|
||||
add.at(x, [0, 1], y)
|
||||
self.assertPreciseEqual(x, np.array([2, 4, 3, 4]))
|
||||
|
||||
|
||||
class TestGUVectorizeScalar(MemoryLeakMixin, TestCase):
|
||||
"""
|
||||
Nothing keeps user from out-of-bound memory access
|
||||
"""
|
||||
target = 'cpu'
|
||||
|
||||
def test_scalar_output(self):
|
||||
"""
|
||||
Note that scalar output is a 0-dimension array that acts as
|
||||
a pointer to the output location.
|
||||
"""
|
||||
|
||||
@guvectorize(['void(int32[:], int32[:])'], '(n)->()',
|
||||
target=self.target, nopython=True)
|
||||
def sum_row(inp, out):
|
||||
tmp = 0.
|
||||
for i in range(inp.shape[0]):
|
||||
tmp += inp[i]
|
||||
out[()] = tmp
|
||||
|
||||
# inp is (10000, 3)
|
||||
# out is (10000)
|
||||
# The outer (leftmost) dimension must match or numpy broadcasting is performed.
|
||||
|
||||
inp = np.arange(30000, dtype=np.int32).reshape(10000, 3)
|
||||
out = sum_row(inp)
|
||||
|
||||
# verify result
|
||||
for i in range(inp.shape[0]):
|
||||
self.assertEqual(out[i], inp[i].sum())
|
||||
|
||||
def test_scalar_input(self):
|
||||
|
||||
@guvectorize(['int32[:], int32[:], int32[:]'], '(n),()->(n)',
|
||||
target=self.target, nopython=True)
|
||||
def foo(inp, n, out):
|
||||
for i in range(inp.shape[0]):
|
||||
out[i] = inp[i] * n[0]
|
||||
|
||||
inp = np.arange(3 * 10, dtype=np.int32).reshape(10, 3)
|
||||
# out = np.empty_like(inp)
|
||||
out = foo(inp, 2)
|
||||
|
||||
# verify result
|
||||
self.assertPreciseEqual(inp * 2, out)
|
||||
|
||||
def test_scalar_input_core_type(self):
|
||||
def pyfunc(inp, n, out):
|
||||
for i in range(inp.size):
|
||||
out[i] = n * (inp[i] + 1)
|
||||
|
||||
my_gufunc = guvectorize(['int32[:], int32, int32[:]'],
|
||||
'(n),()->(n)',
|
||||
target=self.target)(pyfunc)
|
||||
|
||||
# test single core loop execution
|
||||
arr = np.arange(10).astype(np.int32)
|
||||
got = my_gufunc(arr, 2)
|
||||
|
||||
expected = np.zeros_like(got)
|
||||
pyfunc(arr, 2, expected)
|
||||
|
||||
np.testing.assert_equal(got, expected)
|
||||
|
||||
# test multiple core loop execution
|
||||
arr = np.arange(20).astype(np.int32).reshape(10, 2)
|
||||
got = my_gufunc(arr, 2)
|
||||
|
||||
expected = np.zeros_like(got)
|
||||
for ax in range(expected.shape[0]):
|
||||
pyfunc(arr[ax], 2, expected[ax])
|
||||
|
||||
np.testing.assert_equal(got, expected)
|
||||
|
||||
def test_scalar_input_core_type_error(self):
|
||||
with self.assertRaises(TypeError) as raises:
|
||||
@guvectorize(['int32[:], int32, int32[:]'], '(n),(n)->(n)',
|
||||
target=self.target)
|
||||
def pyfunc(a, b, c):
|
||||
pass
|
||||
self.assertEqual("scalar type int32 given for non scalar argument #2",
|
||||
str(raises.exception))
|
||||
|
||||
def test_ndim_mismatch(self):
|
||||
with self.assertRaises(TypeError) as raises:
|
||||
@guvectorize(['int32[:], int32[:]'], '(m,n)->(n)',
|
||||
target=self.target)
|
||||
def pyfunc(a, b):
|
||||
pass
|
||||
self.assertEqual("type and shape signature mismatch for arg #1",
|
||||
str(raises.exception))
|
||||
|
||||
|
||||
class TestGUVectorizeScalarParallel(TestGUVectorizeScalar):
|
||||
_numba_parallel_test_ = False
|
||||
target = 'parallel'
|
||||
|
||||
|
||||
class TestGUVectorizePickling(MemoryLeakMixin, TestCase):
|
||||
def test_pickle_gufunc_non_dyanmic(self):
|
||||
"""Non-dynamic gufunc.
|
||||
"""
|
||||
@guvectorize(["f8,f8[:]"], "()->()")
|
||||
def double(x, out):
|
||||
out[:] = x * 2
|
||||
|
||||
# pickle
|
||||
ser = pickle.dumps(double)
|
||||
cloned = pickle.loads(ser)
|
||||
|
||||
# attributes carried over
|
||||
self.assertEqual(cloned._frozen, double._frozen)
|
||||
self.assertEqual(cloned.identity, double.identity)
|
||||
self.assertEqual(cloned.is_dynamic, double.is_dynamic)
|
||||
self.assertEqual(cloned.gufunc_builder._sigs,
|
||||
double.gufunc_builder._sigs)
|
||||
# expected value of attributes
|
||||
self.assertTrue(cloned._frozen)
|
||||
|
||||
cloned.disable_compile()
|
||||
self.assertTrue(cloned._frozen)
|
||||
|
||||
# scalar version
|
||||
self.assertPreciseEqual(double(0.5), cloned(0.5))
|
||||
# array version
|
||||
arr = np.arange(10)
|
||||
self.assertPreciseEqual(double(arr), cloned(arr))
|
||||
|
||||
def test_pickle_gufunc_dyanmic_null_init(self):
|
||||
"""Dynamic gufunc w/o prepopulating before pickling.
|
||||
"""
|
||||
@guvectorize("()->()", identity=1)
|
||||
def double(x, out):
|
||||
out[:] = x * 2
|
||||
|
||||
# pickle
|
||||
ser = pickle.dumps(double)
|
||||
cloned = pickle.loads(ser)
|
||||
|
||||
# attributes carried over
|
||||
self.assertEqual(cloned._frozen, double._frozen)
|
||||
self.assertEqual(cloned.identity, double.identity)
|
||||
self.assertEqual(cloned.is_dynamic, double.is_dynamic)
|
||||
self.assertEqual(cloned.gufunc_builder._sigs,
|
||||
double.gufunc_builder._sigs)
|
||||
# expected value of attributes
|
||||
self.assertFalse(cloned._frozen)
|
||||
|
||||
# scalar version
|
||||
expect = np.zeros(1)
|
||||
got = np.zeros(1)
|
||||
double(0.5, out=expect)
|
||||
cloned(0.5, out=got)
|
||||
self.assertPreciseEqual(expect, got)
|
||||
# array version
|
||||
arr = np.arange(10)
|
||||
expect = np.zeros_like(arr)
|
||||
got = np.zeros_like(arr)
|
||||
double(arr, out=expect)
|
||||
cloned(arr, out=got)
|
||||
self.assertPreciseEqual(expect, got)
|
||||
|
||||
def test_pickle_gufunc_dynamic_initialized(self):
|
||||
"""Dynamic gufunc prepopulated before pickling.
|
||||
|
||||
Once unpickled, we disable compilation to verify that the gufunc
|
||||
compilation state is carried over.
|
||||
"""
|
||||
@guvectorize("()->()", identity=1)
|
||||
def double(x, out):
|
||||
out[:] = x * 2
|
||||
|
||||
# prepopulate scalar
|
||||
expect = np.zeros(1)
|
||||
got = np.zeros(1)
|
||||
double(0.5, out=expect)
|
||||
# prepopulate array
|
||||
arr = np.arange(10)
|
||||
expect = np.zeros_like(arr)
|
||||
got = np.zeros_like(arr)
|
||||
double(arr, out=expect)
|
||||
|
||||
# pickle
|
||||
ser = pickle.dumps(double)
|
||||
cloned = pickle.loads(ser)
|
||||
|
||||
# attributes carried over
|
||||
self.assertEqual(cloned._frozen, double._frozen)
|
||||
self.assertEqual(cloned.identity, double.identity)
|
||||
self.assertEqual(cloned.is_dynamic, double.is_dynamic)
|
||||
self.assertEqual(cloned.gufunc_builder._sigs,
|
||||
double.gufunc_builder._sigs)
|
||||
# expected value of attributes
|
||||
self.assertFalse(cloned._frozen)
|
||||
|
||||
# disable compilation
|
||||
cloned.disable_compile()
|
||||
self.assertTrue(cloned._frozen)
|
||||
# scalar version
|
||||
expect = np.zeros(1)
|
||||
got = np.zeros(1)
|
||||
double(0.5, out=expect)
|
||||
cloned(0.5, out=got)
|
||||
self.assertPreciseEqual(expect, got)
|
||||
# array version
|
||||
expect = np.zeros_like(arr)
|
||||
got = np.zeros_like(arr)
|
||||
double(arr, out=expect)
|
||||
cloned(arr, out=got)
|
||||
self.assertPreciseEqual(expect, got)
|
||||
|
||||
|
||||
class TestGUVectorizeJit(MemoryLeakMixin, TestCase):
|
||||
target = 'cpu'
|
||||
|
||||
def check_add_gufunc(self, gufunc):
|
||||
@jit(nopython=True)
|
||||
def jit_add(x, y, res):
|
||||
gufunc(x, y, res)
|
||||
|
||||
x = np.arange(40, dtype='i8').reshape(4, 2, 5)
|
||||
y = np.int32(100)
|
||||
res = np.zeros_like(x)
|
||||
jit_add(x, y, res)
|
||||
self.assertPreciseEqual(res, x + y)
|
||||
|
||||
def test_add_static(self):
|
||||
@guvectorize('int64[:], int64, int64[:]', '(n),()->(n)',
|
||||
target=self.target)
|
||||
def add(x, y, res):
|
||||
for i in range(x.shape[0]):
|
||||
res[i] = x[i] + y
|
||||
|
||||
self.check_add_gufunc(add)
|
||||
|
||||
def test_add_static_cast_args(self):
|
||||
# cast the second argument from i32 -> i64
|
||||
@guvectorize('int64[:], int64, int64[:]', '(n),()->(n)',
|
||||
target=self.target)
|
||||
def add(x, y, res):
|
||||
for i in range(x.shape[0]):
|
||||
res[i] = x[i] + y
|
||||
|
||||
self.check_add_gufunc(add)
|
||||
|
||||
def test_add_dynamic(self):
|
||||
@guvectorize('(n),()->(n)', target=self.target)
|
||||
def add(x, y, res):
|
||||
for i in range(x.shape[0]):
|
||||
res[i] = x[i] + y
|
||||
|
||||
self.check_add_gufunc(add)
|
||||
|
||||
@unittest.expectedFailure
|
||||
def test_object_mode(self):
|
||||
@guvectorize('(n),()->(n)', target=self.target, forceobj=True)
|
||||
def add(x, y, res):
|
||||
for i in range(x.shape[0]):
|
||||
res[i] = x[i] + y
|
||||
|
||||
self.check_add_gufunc(add)
|
||||
|
||||
def check_matmul(self, jit_func):
|
||||
matrix_ct = 1001
|
||||
A = np.arange(matrix_ct * 2 * 4, dtype=np.float32).reshape(matrix_ct, 2, 4)
|
||||
B = np.arange(matrix_ct * 4 * 5, dtype=np.float32).reshape(matrix_ct, 4, 5)
|
||||
C = np.arange(matrix_ct * 2 * 5, dtype=np.float32).reshape(matrix_ct, 2, 5)
|
||||
|
||||
jit_func(A, B, C)
|
||||
Gold = np.matmul(A, B)
|
||||
|
||||
np.testing.assert_allclose(C, Gold, rtol=1e-5, atol=1e-8)
|
||||
|
||||
def test_njit_matmul_call(self):
|
||||
|
||||
gufunc = guvectorize('(m,n),(n,p)->(m,p)',
|
||||
target=self.target)(matmulcore)
|
||||
|
||||
@jit(nopython=True)
|
||||
def matmul_jit(A, B, C):
|
||||
return gufunc(A, B, C)
|
||||
|
||||
self.check_matmul(matmul_jit)
|
||||
|
||||
def test_axpy(self):
|
||||
gufunc = GUVectorize(axpy, '(),(),() -> ()', target=self.target,
|
||||
is_dynamic=True)
|
||||
|
||||
@jit(nopython=True)
|
||||
def axpy_jit(a, x, y, out):
|
||||
gufunc(a, x, y, out)
|
||||
|
||||
x = np.arange(10, dtype=np.intp)
|
||||
out = np.zeros_like(x)
|
||||
axpy_jit(x, x, x, out)
|
||||
self.assertPreciseEqual(out, x * x + x)
|
||||
|
||||
def test_output_scalar(self):
|
||||
|
||||
@guvectorize('(n),(m) -> ()')
|
||||
def gufunc(x, y, res):
|
||||
res[0] = x.sum() + y.sum()
|
||||
|
||||
@jit(nopython=True)
|
||||
def jit_func(x, y, res):
|
||||
gufunc(x, y, res)
|
||||
|
||||
x = np.arange(40, dtype='i8').reshape(4, 10)
|
||||
y = np.arange(20, dtype='i8')
|
||||
res = np.zeros(4, dtype='i8')
|
||||
jit_func(x, y, res)
|
||||
expected = np.zeros_like(res)
|
||||
gufunc(x, y, expected)
|
||||
self.assertPreciseEqual(res, expected)
|
||||
|
||||
def test_input_scalar(self):
|
||||
|
||||
@guvectorize('() -> ()')
|
||||
def gufunc(x, res):
|
||||
res[0] = x + 100
|
||||
|
||||
@jit(nopython=True)
|
||||
def jit_func(x, res):
|
||||
gufunc(x, res)
|
||||
|
||||
x = np.arange(40, dtype='i8').reshape(5, 2, 4)
|
||||
res = np.zeros_like(x)
|
||||
jit_func(x, res)
|
||||
expected = np.zeros_like(res)
|
||||
gufunc(x, expected)
|
||||
self.assertPreciseEqual(res, expected)
|
||||
|
||||
def test_gufunc_ndim_mismatch(self):
|
||||
signature = "(n, m), (n, n, n) -> (m), (n, n)"
|
||||
@guvectorize(signature)
|
||||
def bar(x, y, res, out):
|
||||
res[0] = 123
|
||||
out[0] = 456
|
||||
|
||||
@jit(nopython=True)
|
||||
def foo(x, y, res, out):
|
||||
bar(x, y, res, out)
|
||||
|
||||
N, M = 2, 3
|
||||
x = np.arange(N**2).reshape(N, N)
|
||||
y = np.arange(N**3).reshape(N, N, N)
|
||||
res = np.arange(M)
|
||||
out = np.arange(N**2).reshape(N, N)
|
||||
|
||||
# calling with a 1d array should result in an error
|
||||
with self.assertRaises(TypingError) as raises:
|
||||
x_ = np.arange(N * N)
|
||||
foo(x_, y, res, out)
|
||||
msg = ('bar: Input operand 0 does not have enough dimensions (has '
|
||||
f'1, gufunc core with signature {signature} requires 2)')
|
||||
self.assertIn(msg, str(raises.exception))
|
||||
|
||||
with self.assertRaises(TypingError) as raises:
|
||||
y_ = np.arange(N * N).reshape(N, N)
|
||||
foo(x, y_, res, out)
|
||||
msg = ('bar: Input operand 1 does not have enough dimensions (has '
|
||||
f'2, gufunc core with signature {signature} requires 3)')
|
||||
self.assertIn(msg, str(raises.exception))
|
||||
|
||||
with self.assertRaises(TypingError) as raises:
|
||||
res_ = np.array(3)
|
||||
foo(x, y, res_, out)
|
||||
msg = ('bar: Output operand 0 does not have enough dimensions (has '
|
||||
f'0, gufunc core with signature {signature} requires 1)')
|
||||
self.assertIn(msg, str(raises.exception))
|
||||
|
||||
with self.assertRaises(TypingError) as raises:
|
||||
out_ = np.arange(N)
|
||||
foo(x, y, res, out_)
|
||||
msg = ('bar: Output operand 1 does not have enough dimensions (has '
|
||||
f'1, gufunc core with signature {signature} requires 2)')
|
||||
self.assertIn(msg, str(raises.exception))
|
||||
|
||||
def test_mismatch_inner_dimensions(self):
|
||||
@guvectorize('(n),(n) -> ()')
|
||||
def bar(x, y, res):
|
||||
res[0] = 123
|
||||
|
||||
@jit(nopython=True)
|
||||
def foo(x, y, res):
|
||||
bar(x, y, res)
|
||||
|
||||
N = 2
|
||||
M = 3
|
||||
x = np.empty((5, 3, N))
|
||||
y = np.empty((M,))
|
||||
res = np.zeros((5, 3))
|
||||
|
||||
# ensure that NumPy raises an exception
|
||||
with self.assertRaises(ValueError) as np_raises:
|
||||
bar(x, y, res)
|
||||
msg = ('Input operand 1 has a mismatch in its core dimension 0, with '
|
||||
'gufunc signature (n),(n) -> () (size 3 is different from 2)')
|
||||
self.assertIn(msg, str(np_raises.exception))
|
||||
|
||||
with self.assertRaises(ValueError) as raises:
|
||||
foo(x, y, res)
|
||||
msg = ('Operand has a mismatch in one of its core dimensions')
|
||||
self.assertIn(msg, str(raises.exception))
|
||||
|
||||
def test_mismatch_inner_dimensions_input_output(self):
|
||||
@guvectorize('(n),(m) -> (n)')
|
||||
def bar(x, y, res):
|
||||
res[0] = 123
|
||||
|
||||
@jit(nopython=True)
|
||||
def foo(x, y, res):
|
||||
bar(x, y, res)
|
||||
|
||||
N = 2
|
||||
M = 3
|
||||
x = np.empty((5, 3, N))
|
||||
y = np.empty((M,))
|
||||
res = np.zeros((5, 3))
|
||||
|
||||
# ensure that NumPy raises an exception
|
||||
with self.assertRaises(ValueError) as np_raises:
|
||||
bar(x, y, res)
|
||||
msg = ('Output operand 0 has a mismatch in its core dimension 0, with '
|
||||
'gufunc signature (n),(m) -> (n) (size 3 is different from 2)')
|
||||
self.assertIn(msg, str(np_raises.exception))
|
||||
|
||||
with self.assertRaises(ValueError) as raises:
|
||||
foo(x, y, res)
|
||||
msg = ('Operand has a mismatch in one of its core dimensions')
|
||||
self.assertIn(msg, str(raises.exception))
|
||||
|
||||
def test_mismatch_inner_dimensions_output(self):
|
||||
@guvectorize('(n),(m) -> (m),(m)')
|
||||
def bar(x, y, res, out):
|
||||
res[0] = 123
|
||||
out[0] = 456
|
||||
|
||||
@jit(nopython=True)
|
||||
def foo(x, y, res, out):
|
||||
bar(x, y, res, out)
|
||||
|
||||
N = 2
|
||||
M = 3
|
||||
x = np.empty((N,))
|
||||
y = np.empty((M,))
|
||||
res = np.zeros((N,))
|
||||
out = np.zeros((M,))
|
||||
|
||||
# ensure that NumPy raises an exception
|
||||
with self.assertRaises(ValueError) as np_raises:
|
||||
bar(x, y, res, out)
|
||||
msg = ('Output operand 0 has a mismatch in its core dimension 0, with '
|
||||
'gufunc signature (n),(m) -> (m),(m) (size 2 is different from 3)')
|
||||
self.assertIn(msg, str(np_raises.exception))
|
||||
|
||||
with self.assertRaises(ValueError) as raises:
|
||||
foo(x, y, res, out)
|
||||
msg = ('Operand has a mismatch in one of its core dimensions')
|
||||
self.assertIn(msg, str(raises.exception))
|
||||
|
||||
def test_mismatch_loop_shape(self):
|
||||
@guvectorize('(n),(n) -> ()')
|
||||
def bar(x, y, res):
|
||||
res[0] = 123
|
||||
|
||||
@jit(nopython=True)
|
||||
def foo(x, y, res):
|
||||
bar(x, y, res)
|
||||
|
||||
N = 2
|
||||
x = np.empty((1, 5, 3, N,))
|
||||
y = np.empty((5, 3, N,))
|
||||
res = np.zeros((5, 3))
|
||||
|
||||
with self.assertRaises(ValueError) as raises:
|
||||
foo(x, y, res)
|
||||
msg = ('Loop and array shapes are incompatible')
|
||||
self.assertIn(msg, str(raises.exception))
|
||||
|
||||
def test_mismatch_loop_shape_2(self):
|
||||
@guvectorize('(n),(n) -> (), (n)')
|
||||
def gufunc(x, y, res, out):
|
||||
res[0] = x.sum()
|
||||
for i in range(x.shape[0]):
|
||||
out[i] += x[i] + y.sum()
|
||||
|
||||
@jit
|
||||
def jit_func(x, y, res, out):
|
||||
gufunc(x, y, res, out)
|
||||
|
||||
N = 2
|
||||
|
||||
x = np.arange(4*N).reshape((4, N))
|
||||
y = np.arange(N)
|
||||
res = np.empty((3,))
|
||||
out = np.zeros((3, N))
|
||||
|
||||
# ensure that NumPy raises an exception
|
||||
with self.assertRaises(ValueError) as np_raises:
|
||||
gufunc(x, y, res, out)
|
||||
msg = ('operands could not be broadcast together with remapped shapes '
|
||||
'[original->remapped]: (4,2)->(4,newaxis) (2,)->() '
|
||||
'(3,)->(3,newaxis) (3,2)->(3,2) and requested shape (2)')
|
||||
self.assertIn(msg, str(np_raises.exception))
|
||||
|
||||
with self.assertRaises(ValueError) as raises:
|
||||
jit_func(x, y, res, out)
|
||||
msg = ('Loop and array shapes are incompatible')
|
||||
self.assertIn(msg, str(raises.exception))
|
||||
|
||||
def test_issue_10287(self):
|
||||
@guvectorize([(float64[:], int64, float64[:])], "(n),()->(n)")
|
||||
def guve(x, n, res):
|
||||
pass
|
||||
|
||||
|
||||
@jit
|
||||
def njit_guve(x, n):
|
||||
res = np.zeros_like(x)
|
||||
guve(x, n, res)
|
||||
return res
|
||||
|
||||
|
||||
rng = np.random.default_rng(69)
|
||||
|
||||
for _ in range(20000):
|
||||
x = rng.random(65)
|
||||
y = np.repeat(x[None], 130, axis=0)
|
||||
njit_guve(y, 5)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,38 @@
|
||||
from numba.np.ufunc.parallel import get_thread_count
|
||||
from os import environ as env
|
||||
from numba.core import config
|
||||
import unittest
|
||||
|
||||
|
||||
class TestParallelEnvVariable(unittest.TestCase):
|
||||
"""
|
||||
Tests environment variables related to the underlying "parallel"
|
||||
functions for npyufuncs.
|
||||
"""
|
||||
|
||||
_numba_parallel_test_ = False
|
||||
|
||||
def test_num_threads_variable(self):
|
||||
"""
|
||||
Tests the NUMBA_NUM_THREADS env variable behaves as expected.
|
||||
"""
|
||||
key = 'NUMBA_NUM_THREADS'
|
||||
current = str(getattr(env, key, config.NUMBA_NUM_THREADS))
|
||||
threads = "3154"
|
||||
env[key] = threads
|
||||
try:
|
||||
config.reload_config()
|
||||
except RuntimeError as e:
|
||||
# This test should fail if threads have already been launched
|
||||
self.assertIn("Cannot set NUMBA_NUM_THREADS", e.args[0])
|
||||
else:
|
||||
self.assertEqual(threads, str(get_thread_count()))
|
||||
self.assertEqual(threads, str(config.NUMBA_NUM_THREADS))
|
||||
finally:
|
||||
# reset the env variable/set to default. Should not fail even if
|
||||
# threads are launched because the value is the same.
|
||||
env[key] = current
|
||||
config.reload_config()
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,44 @@
|
||||
"""
|
||||
There was a deadlock problem when work count is smaller than number of threads.
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
from numba import float32, float64, int32, uint32
|
||||
from numba.np.ufunc import Vectorize
|
||||
import unittest
|
||||
|
||||
|
||||
def vector_add(a, b):
|
||||
return a + b
|
||||
|
||||
|
||||
class TestParallelLowWorkCount(unittest.TestCase):
|
||||
|
||||
_numba_parallel_test_ = False
|
||||
|
||||
def test_low_workcount(self):
|
||||
# build parallel native code ufunc
|
||||
pv = Vectorize(vector_add, target='parallel')
|
||||
for ty in (int32, uint32, float32, float64):
|
||||
pv.add(ty(ty, ty))
|
||||
para_ufunc = pv.build_ufunc()
|
||||
|
||||
# build python ufunc
|
||||
np_ufunc = np.vectorize(vector_add)
|
||||
|
||||
# test it out
|
||||
def test(ty):
|
||||
data = np.arange(1).astype(ty) # just one item
|
||||
result = para_ufunc(data, data)
|
||||
gold = np_ufunc(data, data)
|
||||
np.testing.assert_allclose(gold, result)
|
||||
|
||||
test(np.double)
|
||||
test(np.float32)
|
||||
test(np.int32)
|
||||
test(np.uint32)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,130 @@
|
||||
import time
|
||||
import ctypes
|
||||
|
||||
import numpy as np
|
||||
|
||||
from numba.tests.support import captured_stdout, skip_if_freethreading
|
||||
from numba import vectorize, guvectorize
|
||||
import unittest
|
||||
|
||||
|
||||
class TestParUfuncIssues(unittest.TestCase):
|
||||
|
||||
_numba_parallel_test_ = False
|
||||
|
||||
def test_thread_response(self):
|
||||
"""
|
||||
Related to #89.
|
||||
This does not test #89 but tests the fix for it.
|
||||
We want to make sure the worker threads can be used multiple times
|
||||
and with different time gap between each execution.
|
||||
"""
|
||||
|
||||
@vectorize('float64(float64, float64)', target='parallel')
|
||||
def fnv(a, b):
|
||||
return a + b
|
||||
|
||||
sleep_time = 1 # 1 second
|
||||
while sleep_time > 0.00001: # 10us
|
||||
time.sleep(sleep_time)
|
||||
a = b = np.arange(10**5)
|
||||
np.testing.assert_equal(a + b, fnv(a, b))
|
||||
# Reduce sleep time
|
||||
sleep_time /= 2
|
||||
|
||||
@skip_if_freethreading
|
||||
def test_gil_reacquire_deadlock(self):
|
||||
"""
|
||||
Testing issue #1998 due to GIL reacquiring
|
||||
"""
|
||||
# make a ctypes callback that requires the GIL
|
||||
proto = ctypes.CFUNCTYPE(None, ctypes.c_int32)
|
||||
characters = 'abcdefghij'
|
||||
|
||||
def bar(x):
|
||||
print(characters[x])
|
||||
|
||||
cbar = proto(bar)
|
||||
|
||||
# our unit under test
|
||||
@vectorize(['int32(int32)'], target='parallel', nopython=True)
|
||||
def foo(x):
|
||||
print(x % 10) # this reacquires the GIL
|
||||
cbar(x % 10) # this reacquires the GIL
|
||||
return x * 2
|
||||
|
||||
# Numpy ufunc has a heuristic to determine whether to release the GIL
|
||||
# during execution. Small input size (10) seems to not release the GIL.
|
||||
# Large input size (1000) seems to release the GIL.
|
||||
for nelem in [1, 10, 100, 1000]:
|
||||
# inputs
|
||||
a = np.arange(nelem, dtype=np.int32)
|
||||
acopy = a.copy()
|
||||
# run and capture stdout
|
||||
with captured_stdout() as buf:
|
||||
got = foo(a)
|
||||
stdout = buf.getvalue()
|
||||
buf.close()
|
||||
# process outputs from print
|
||||
got_output = sorted(map(lambda x: x.strip(), stdout.splitlines()))
|
||||
# build expected output
|
||||
expected_output = [str(x % 10) for x in range(nelem)]
|
||||
expected_output += [characters[x % 10] for x in range(nelem)]
|
||||
expected_output = sorted(expected_output)
|
||||
# verify
|
||||
self.assertEqual(got_output, expected_output)
|
||||
np.testing.assert_equal(got, 2 * acopy)
|
||||
|
||||
|
||||
|
||||
class TestParGUfuncIssues(unittest.TestCase):
|
||||
|
||||
_numba_parallel_test_ = False
|
||||
|
||||
@skip_if_freethreading
|
||||
def test_gil_reacquire_deadlock(self):
|
||||
"""
|
||||
Testing similar issue to #1998 due to GIL reacquiring for Gufunc
|
||||
"""
|
||||
# make a ctypes callback that requires the GIL
|
||||
proto = ctypes.CFUNCTYPE(None, ctypes.c_int32)
|
||||
characters = 'abcdefghij'
|
||||
|
||||
def bar(x):
|
||||
print(characters[x])
|
||||
|
||||
cbar = proto(bar)
|
||||
|
||||
# our unit under test
|
||||
@guvectorize(['(int32, int32[:])'], "()->()",
|
||||
target='parallel', nopython=True)
|
||||
def foo(x, out):
|
||||
print(x % 10) # this reacquires the GIL
|
||||
cbar(x % 10) # this reacquires the GIL
|
||||
out[0] = x * 2
|
||||
|
||||
# Numpy ufunc has a heuristic to determine whether to release the GIL
|
||||
# during execution. Small input size (10) seems to not release the GIL.
|
||||
# Large input size (1000) seems to release the GIL.
|
||||
for nelem in [1, 10, 100, 1000]:
|
||||
# inputs
|
||||
a = np.arange(nelem, dtype=np.int32)
|
||||
acopy = a.copy()
|
||||
# run and capture stdout
|
||||
with captured_stdout() as buf:
|
||||
got = foo(a)
|
||||
stdout = buf.getvalue()
|
||||
buf.close()
|
||||
# process outputs from print
|
||||
got_output = sorted(map(lambda x: x.strip(), stdout.splitlines()))
|
||||
# build expected output
|
||||
expected_output = [str(x % 10) for x in range(nelem)]
|
||||
expected_output += [characters[x % 10] for x in range(nelem)]
|
||||
expected_output = sorted(expected_output)
|
||||
# verify
|
||||
self.assertEqual(got_output, expected_output)
|
||||
np.testing.assert_equal(got, 2 * acopy)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,173 @@
|
||||
import numpy as np
|
||||
|
||||
from numba import float32, jit, njit
|
||||
from numba.np.ufunc import Vectorize
|
||||
from numba.core.errors import TypingError
|
||||
from numba.tests.support import TestCase
|
||||
import unittest
|
||||
|
||||
|
||||
dtype = np.float32
|
||||
a = np.arange(80, dtype=dtype).reshape(8, 10)
|
||||
b = a.copy()
|
||||
c = a.copy(order='F')
|
||||
d = np.arange(16 * 20, dtype=dtype).reshape(16, 20)[::2, ::2]
|
||||
|
||||
|
||||
def add(a, b):
|
||||
return a + b
|
||||
|
||||
|
||||
def add_multiple_args(a, b, c, d):
|
||||
return a + b + c + d
|
||||
|
||||
|
||||
def gufunc_add(a, b):
|
||||
result = 0.0
|
||||
for i in range(a.shape[0]):
|
||||
result += a[i] * b[i]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def ufunc_reduce(ufunc, arg):
|
||||
for i in range(arg.ndim):
|
||||
arg = ufunc.reduce(arg)
|
||||
return arg
|
||||
|
||||
|
||||
vectorizers = [
|
||||
Vectorize,
|
||||
# ParallelVectorize,
|
||||
# StreamVectorize,
|
||||
# CudaVectorize,
|
||||
# GUFuncVectorize,
|
||||
]
|
||||
|
||||
|
||||
class TestUFuncs(TestCase):
|
||||
|
||||
def _test_ufunc_attributes(self, cls, a, b, *args):
|
||||
"Test ufunc attributes"
|
||||
vectorizer = cls(add, *args)
|
||||
vectorizer.add(float32(float32, float32))
|
||||
ufunc = vectorizer.build_ufunc()
|
||||
|
||||
info = (cls, a.ndim)
|
||||
self.assertPreciseEqual(ufunc(a, b), a + b, msg=info)
|
||||
self.assertPreciseEqual(ufunc_reduce(ufunc, a), np.sum(a), msg=info)
|
||||
self.assertPreciseEqual(ufunc.accumulate(a), np.add.accumulate(a),
|
||||
msg=info)
|
||||
self.assertPreciseEqual(ufunc.outer(a, b), np.add.outer(a, b), msg=info)
|
||||
|
||||
def _test_broadcasting(self, cls, a, b, c, d):
|
||||
"Test multiple args"
|
||||
vectorizer = cls(add_multiple_args)
|
||||
vectorizer.add(float32(float32, float32, float32, float32))
|
||||
ufunc = vectorizer.build_ufunc()
|
||||
|
||||
info = (cls, a.shape)
|
||||
self.assertPreciseEqual(ufunc(a, b, c, d), a + b + c + d, msg=info)
|
||||
|
||||
def test_ufunc_attributes(self):
|
||||
for v in vectorizers: # 1D
|
||||
self._test_ufunc_attributes(v, a[0], b[0])
|
||||
for v in vectorizers: # 2D
|
||||
self._test_ufunc_attributes(v, a, b)
|
||||
for v in vectorizers: # 3D
|
||||
self._test_ufunc_attributes(v, a[:, np.newaxis, :],
|
||||
b[np.newaxis, :, :])
|
||||
|
||||
def test_broadcasting(self):
|
||||
for v in vectorizers: # 1D
|
||||
self._test_broadcasting(v, a[0], b[0], c[0], d[0])
|
||||
for v in vectorizers: # 2D
|
||||
self._test_broadcasting(v, a, b, c, d)
|
||||
for v in vectorizers: # 3D
|
||||
self._test_broadcasting(v, a[:, np.newaxis, :], b[np.newaxis, :, :],
|
||||
c[:, np.newaxis, :], d[np.newaxis, :, :])
|
||||
|
||||
def test_implicit_broadcasting(self):
|
||||
for v in vectorizers:
|
||||
vectorizer = v(add)
|
||||
vectorizer.add(float32(float32, float32))
|
||||
ufunc = vectorizer.build_ufunc()
|
||||
|
||||
broadcasting_b = b[np.newaxis, :, np.newaxis, np.newaxis, :]
|
||||
self.assertPreciseEqual(ufunc(a, broadcasting_b),
|
||||
a + broadcasting_b)
|
||||
|
||||
def test_ufunc_exception_on_write_to_readonly(self):
|
||||
z = np.ones(10)
|
||||
z.flags.writeable = False # flip write bit
|
||||
|
||||
tests = []
|
||||
expect = "ufunc 'sin' called with an explicit output that is read-only"
|
||||
tests.append((jit(nopython=True), TypingError, expect))
|
||||
tests.append((jit(forceobj=True), ValueError,
|
||||
"output array is read-only"))
|
||||
|
||||
for dec, exc, msg in tests:
|
||||
def test(x):
|
||||
a = np.ones(x.shape, x.dtype) # do not copy RO attribute from x
|
||||
np.sin(a, x)
|
||||
|
||||
with self.assertRaises(exc) as raises:
|
||||
dec(test)(z)
|
||||
|
||||
self.assertIn(msg, str(raises.exception))
|
||||
|
||||
def test_optional_type_handling(self):
|
||||
# Tests ufunc compilation with Optional type
|
||||
|
||||
@njit
|
||||
def inner(x, y):
|
||||
if y > 2:
|
||||
z = None
|
||||
else:
|
||||
z = np.ones(4)
|
||||
return np.add(x, z)
|
||||
|
||||
# This causes `z` to be np.ones(4) at runtime, success
|
||||
self.assertPreciseEqual(inner(np.arange(4), 1),
|
||||
np.arange(1, 5).astype(np.float64))
|
||||
|
||||
with self.assertRaises(TypeError) as raises:
|
||||
# This causes `z` to be None at runtime, TypeError raised on the
|
||||
# type cast of the Optional.
|
||||
inner(np.arange(4), 3)
|
||||
|
||||
msg = "expected array(float64, 1d, C), got None"
|
||||
self.assertIn(msg, str(raises.exception))
|
||||
|
||||
|
||||
class TestUFuncsMisc(TestCase):
|
||||
# Test for miscellaneous ufunc issues
|
||||
|
||||
def test_exp2(self):
|
||||
# See issue #8898, and TargetLibraryInfo based fix in #9336
|
||||
@njit
|
||||
def foo(x):
|
||||
return np.exp2(x)
|
||||
|
||||
for ty in (np.int8, np.uint16):
|
||||
x = ty(2)
|
||||
expected = foo.py_func(x)
|
||||
got = foo(x)
|
||||
self.assertPreciseEqual(expected, got)
|
||||
|
||||
def test_log2(self):
|
||||
# See issue #8898, and TargetLibraryInfo based fix in #9336
|
||||
@njit
|
||||
def foo(x):
|
||||
return np.log2(x)
|
||||
|
||||
for ty in (np.int8, np.uint16):
|
||||
x = ty(2)
|
||||
expected = foo.py_func(x)
|
||||
got = foo(x)
|
||||
self.assertPreciseEqual(expected, got)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,471 @@
|
||||
import pickle
|
||||
import unittest
|
||||
|
||||
import numpy as np
|
||||
from numpy.testing import assert_array_equal
|
||||
|
||||
from numba.np.ufunc.ufuncbuilder import GUFuncBuilder
|
||||
from numba import vectorize, guvectorize
|
||||
from numba.np.ufunc import PyUFunc_One
|
||||
from numba.np.ufunc.dufunc import DUFunc as UFuncBuilder
|
||||
from numba.tests.support import tag, TestCase
|
||||
from numba.core import config
|
||||
|
||||
|
||||
class TestUfuncBuilding(TestCase):
|
||||
|
||||
def test_basic_ufunc(self):
|
||||
from numba.tests.npyufunc.ufuncbuilding_usecases import add
|
||||
ufb = UFuncBuilder(add)
|
||||
cres = ufb.add("int32(int32, int32)")
|
||||
self.assertFalse(cres.objectmode)
|
||||
cres = ufb.add("int64(int64, int64)")
|
||||
self.assertFalse(cres.objectmode)
|
||||
ufunc = ufb.build_ufunc()
|
||||
|
||||
def check(a):
|
||||
b = ufunc(a, a)
|
||||
self.assertPreciseEqual(a + a, b)
|
||||
self.assertEqual(b.dtype, a.dtype)
|
||||
|
||||
a = np.arange(12, dtype='int32')
|
||||
check(a)
|
||||
# Non-contiguous dimension
|
||||
a = a[::2]
|
||||
check(a)
|
||||
a = a.reshape((2, 3))
|
||||
check(a)
|
||||
|
||||
# Metadata
|
||||
self.assertEqual(ufunc.__name__, "add")
|
||||
self.assertIn("An addition", ufunc.__doc__)
|
||||
|
||||
def test_ufunc_struct(self):
|
||||
from numba.tests.npyufunc.ufuncbuilding_usecases import add
|
||||
ufb = UFuncBuilder(add)
|
||||
cres = ufb.add("complex64(complex64, complex64)")
|
||||
self.assertFalse(cres.objectmode)
|
||||
ufunc = ufb.build_ufunc()
|
||||
|
||||
def check(a):
|
||||
b = ufunc(a, a)
|
||||
self.assertPreciseEqual(a + a, b)
|
||||
self.assertEqual(b.dtype, a.dtype)
|
||||
|
||||
a = np.arange(12, dtype='complex64') + 1j
|
||||
check(a)
|
||||
# Non-contiguous dimension
|
||||
a = a[::2]
|
||||
check(a)
|
||||
a = a.reshape((2, 3))
|
||||
check(a)
|
||||
|
||||
def test_ufunc_forceobj(self):
|
||||
from numba.tests.npyufunc.ufuncbuilding_usecases import add
|
||||
ufb = UFuncBuilder(add, targetoptions={'forceobj': True})
|
||||
cres = ufb.add("int32(int32, int32)")
|
||||
self.assertTrue(cres.objectmode)
|
||||
ufunc = ufb.build_ufunc()
|
||||
|
||||
a = np.arange(10, dtype='int32')
|
||||
b = ufunc(a, a)
|
||||
self.assertPreciseEqual(a + a, b)
|
||||
|
||||
def test_nested_call(self):
|
||||
"""
|
||||
Check nested call to an implicitly-typed ufunc.
|
||||
"""
|
||||
from numba.tests.npyufunc.ufuncbuilding_usecases import outer
|
||||
builder = UFuncBuilder(outer,
|
||||
targetoptions={'nopython': True})
|
||||
builder.add("(int64, int64)")
|
||||
ufunc = builder.build_ufunc()
|
||||
self.assertEqual(ufunc(-1, 3), 2)
|
||||
|
||||
def test_nested_call_explicit(self):
|
||||
"""
|
||||
Check nested call to an explicitly-typed ufunc.
|
||||
"""
|
||||
from numba.tests.npyufunc.ufuncbuilding_usecases import outer_explicit
|
||||
builder = UFuncBuilder(outer_explicit,
|
||||
targetoptions={'nopython': True})
|
||||
builder.add("(int64, int64)")
|
||||
ufunc = builder.build_ufunc()
|
||||
self.assertEqual(ufunc(-1, 3), 2)
|
||||
|
||||
|
||||
class TestUfuncBuildingJitDisabled(TestUfuncBuilding):
|
||||
|
||||
def setUp(self):
|
||||
self.old_disable_jit = config.DISABLE_JIT
|
||||
config.DISABLE_JIT = False
|
||||
|
||||
def tearDown(self):
|
||||
config.DISABLE_JIT = self.old_disable_jit
|
||||
|
||||
|
||||
class TestGUfuncBuilding(TestCase):
|
||||
|
||||
def test_basic_gufunc(self):
|
||||
from numba.tests.npyufunc.ufuncbuilding_usecases import guadd
|
||||
gufb = GUFuncBuilder(guadd, "(x, y),(x, y)->(x, y)")
|
||||
cres = gufb.add("void(int32[:,:], int32[:,:], int32[:,:])")
|
||||
self.assertFalse(cres.objectmode)
|
||||
ufunc = gufb.build_ufunc()
|
||||
|
||||
a = np.arange(10, dtype="int32").reshape(2, 5)
|
||||
b = ufunc(a, a)
|
||||
|
||||
self.assertPreciseEqual(a + a, b)
|
||||
self.assertEqual(b.dtype, np.dtype('int32'))
|
||||
|
||||
# Metadata
|
||||
self.assertEqual(ufunc.__name__, "guadd")
|
||||
self.assertIn("A generalized addition", ufunc.__doc__)
|
||||
|
||||
def test_gufunc_struct(self):
|
||||
from numba.tests.npyufunc.ufuncbuilding_usecases import guadd
|
||||
gufb = GUFuncBuilder(guadd, "(x, y),(x, y)->(x, y)")
|
||||
cres = gufb.add("void(complex64[:,:], complex64[:,:], complex64[:,:])")
|
||||
self.assertFalse(cres.objectmode)
|
||||
ufunc = gufb.build_ufunc()
|
||||
|
||||
a = np.arange(10, dtype="complex64").reshape(2, 5) + 1j
|
||||
b = ufunc(a, a)
|
||||
|
||||
self.assertPreciseEqual(a + a, b)
|
||||
|
||||
def test_gufunc_struct_forceobj(self):
|
||||
from numba.tests.npyufunc.ufuncbuilding_usecases import guadd
|
||||
gufb = GUFuncBuilder(guadd, "(x, y),(x, y)->(x, y)",
|
||||
targetoptions=dict(forceobj=True))
|
||||
cres = gufb.add("void(complex64[:,:], complex64[:,:], complex64[:,"
|
||||
":])")
|
||||
self.assertTrue(cres.objectmode)
|
||||
ufunc = gufb.build_ufunc()
|
||||
|
||||
a = np.arange(10, dtype="complex64").reshape(2, 5) + 1j
|
||||
b = ufunc(a, a)
|
||||
|
||||
self.assertPreciseEqual(a + a, b)
|
||||
|
||||
|
||||
class TestGUfuncBuildingJitDisabled(TestGUfuncBuilding):
|
||||
|
||||
def setUp(self):
|
||||
self.old_disable_jit = config.DISABLE_JIT
|
||||
config.DISABLE_JIT = False
|
||||
|
||||
def tearDown(self):
|
||||
config.DISABLE_JIT = self.old_disable_jit
|
||||
|
||||
|
||||
class TestVectorizeDecor(TestCase):
|
||||
|
||||
_supported_identities = [0, 1, None, "reorderable"]
|
||||
|
||||
def test_vectorize(self):
|
||||
from numba.tests.npyufunc.ufuncbuilding_usecases import add
|
||||
ufunc = vectorize(['int32(int32, int32)'])(add)
|
||||
a = np.arange(10, dtype='int32')
|
||||
b = ufunc(a, a)
|
||||
self.assertPreciseEqual(a + a, b)
|
||||
|
||||
def test_vectorize_objmode(self):
|
||||
from numba.tests.npyufunc.ufuncbuilding_usecases import add
|
||||
ufunc = vectorize(['int32(int32, int32)'], forceobj=True)(add)
|
||||
a = np.arange(10, dtype='int32')
|
||||
b = ufunc(a, a)
|
||||
self.assertPreciseEqual(a + a, b)
|
||||
|
||||
def test_vectorize_bool_return(self):
|
||||
from numba.tests.npyufunc.ufuncbuilding_usecases import equals
|
||||
ufunc = vectorize(['bool_(int32, int32)'])(equals)
|
||||
a = np.arange(10, dtype='int32')
|
||||
r = ufunc(a,a)
|
||||
self.assertPreciseEqual(r, np.ones(r.shape, dtype=np.bool_))
|
||||
|
||||
def test_vectorize_identity(self):
|
||||
from numba.tests.npyufunc.ufuncbuilding_usecases import add
|
||||
sig = 'int32(int32, int32)'
|
||||
for identity in self._supported_identities:
|
||||
ufunc = vectorize([sig], identity=identity)(add)
|
||||
expected = None if identity == 'reorderable' else identity
|
||||
self.assertEqual(ufunc.identity, expected)
|
||||
# Default value is None
|
||||
ufunc = vectorize([sig])(add)
|
||||
self.assertIs(ufunc.identity, None)
|
||||
# Invalid values
|
||||
with self.assertRaises(ValueError):
|
||||
vectorize([sig], identity='none')(add)
|
||||
with self.assertRaises(ValueError):
|
||||
vectorize([sig], identity=2)(add)
|
||||
|
||||
def test_vectorize_no_args(self):
|
||||
from numba.tests.npyufunc.ufuncbuilding_usecases import add
|
||||
a = np.linspace(0,1,10)
|
||||
b = np.linspace(1,2,10)
|
||||
ufunc = vectorize(add)
|
||||
self.assertPreciseEqual(ufunc(a,b), a + b)
|
||||
ufunc2 = vectorize(add)
|
||||
c = np.empty(10)
|
||||
ufunc2(a, b, c)
|
||||
self.assertPreciseEqual(c, a + b)
|
||||
|
||||
def test_vectorize_only_kws(self):
|
||||
from numba.tests.npyufunc.ufuncbuilding_usecases import mul
|
||||
a = np.linspace(0,1,10)
|
||||
b = np.linspace(1,2,10)
|
||||
ufunc = vectorize(identity=PyUFunc_One, nopython=True)(mul)
|
||||
self.assertPreciseEqual(ufunc(a,b), a * b)
|
||||
|
||||
def test_vectorize_output_kwarg(self):
|
||||
"""
|
||||
Passing the output array as a keyword argument (issue #1867).
|
||||
"""
|
||||
def check(ufunc):
|
||||
a = np.arange(10, 16, dtype='int32')
|
||||
out = np.zeros_like(a)
|
||||
got = ufunc(a, a, out=out)
|
||||
self.assertIs(got, out)
|
||||
self.assertPreciseEqual(out, a + a)
|
||||
with self.assertRaises(TypeError):
|
||||
ufunc(a, a, zzz=out)
|
||||
|
||||
# With explicit sigs
|
||||
from numba.tests.npyufunc.ufuncbuilding_usecases import add
|
||||
ufunc = vectorize(['int32(int32, int32)'], nopython=True)(add)
|
||||
check(ufunc)
|
||||
# With implicit sig
|
||||
ufunc = vectorize(nopython=True)(add)
|
||||
check(ufunc) # compiling
|
||||
check(ufunc) # after compiling
|
||||
|
||||
def test_guvectorize(self):
|
||||
from numba.tests.npyufunc.ufuncbuilding_usecases import guadd
|
||||
ufunc = guvectorize(['(int32[:,:], int32[:,:], int32[:,:])'],
|
||||
"(x,y),(x,y)->(x,y)")(guadd)
|
||||
a = np.arange(10, dtype='int32').reshape(2, 5)
|
||||
b = ufunc(a, a)
|
||||
self.assertPreciseEqual(a + a, b)
|
||||
|
||||
def test_guvectorize_no_output(self):
|
||||
from numba.tests.npyufunc.ufuncbuilding_usecases import guadd
|
||||
ufunc = guvectorize(['(int32[:,:], int32[:,:], int32[:,:])'],
|
||||
"(x,y),(x,y),(x,y)")(guadd)
|
||||
a = np.arange(10, dtype='int32').reshape(2, 5)
|
||||
out = np.zeros_like(a)
|
||||
ufunc(a, a, out)
|
||||
self.assertPreciseEqual(a + a, out)
|
||||
|
||||
def test_guvectorize_objectmode(self):
|
||||
from numba.tests.npyufunc.ufuncbuilding_usecases import guadd_obj
|
||||
ufunc = guvectorize(['(int32[:,:], int32[:,:], int32[:,:])'],
|
||||
"(x,y),(x,y)->(x,y)", forceobj=True)(guadd_obj)
|
||||
a = np.arange(10, dtype='int32').reshape(2, 5)
|
||||
b = ufunc(a, a)
|
||||
self.assertPreciseEqual(a + a, b)
|
||||
|
||||
def test_guvectorize_scalar_objectmode(self):
|
||||
"""
|
||||
Test passing of scalars to object mode gufuncs.
|
||||
"""
|
||||
from numba.tests.npyufunc.ufuncbuilding_usecases import guadd_scalar_obj
|
||||
ufunc = guvectorize(['(int32[:,:], int32, int32[:,:])'],
|
||||
"(x,y),()->(x,y)", forceobj=True)(guadd_scalar_obj)
|
||||
a = np.arange(10, dtype='int32').reshape(2, 5)
|
||||
b = ufunc(a, 3)
|
||||
self.assertPreciseEqual(a + 3, b)
|
||||
|
||||
def test_guvectorize_error_in_objectmode(self):
|
||||
from numba.tests.npyufunc.ufuncbuilding_usecases import guerror, \
|
||||
MyException
|
||||
ufunc = guvectorize(['(int32[:,:], int32[:,:], int32[:,:])'],
|
||||
"(x,y),(x,y)->(x,y)", forceobj=True)(guerror)
|
||||
a = np.arange(10, dtype='int32').reshape(2, 5)
|
||||
with self.assertRaises(MyException):
|
||||
ufunc(a, a)
|
||||
|
||||
def test_guvectorize_identity(self):
|
||||
from numba.tests.npyufunc.ufuncbuilding_usecases import add, guadd
|
||||
args = (['(int32[:,:], int32[:,:], int32[:,:])'], "(x,y),(x,y)->(x,y)")
|
||||
for identity in self._supported_identities:
|
||||
ufunc = guvectorize(*args, identity=identity)(guadd)
|
||||
expected = None if identity == 'reorderable' else identity
|
||||
self.assertEqual(ufunc.identity, expected)
|
||||
# Default value is None
|
||||
ufunc = guvectorize(*args)(guadd)
|
||||
self.assertIs(ufunc.identity, None)
|
||||
# Invalid values
|
||||
with self.assertRaises(ValueError):
|
||||
guvectorize(*args, identity='none')(add)
|
||||
with self.assertRaises(ValueError):
|
||||
guvectorize(*args, identity=2)(add)
|
||||
|
||||
def test_guvectorize_invalid_layout(self):
|
||||
from numba.tests.npyufunc.ufuncbuilding_usecases import guadd
|
||||
sigs = ['(int32[:,:], int32[:,:], int32[:,:])']
|
||||
# Syntax error
|
||||
with self.assertRaises(ValueError) as raises:
|
||||
guvectorize(sigs, ")-:")(guadd)
|
||||
self.assertIn("bad token in signature", str(raises.exception))
|
||||
# Output shape can't be inferred from inputs
|
||||
with self.assertRaises(NameError) as raises:
|
||||
guvectorize(sigs, "(x,y),(x,y)->(x,z,v)")(guadd)
|
||||
self.assertEqual(str(raises.exception),
|
||||
"undefined output symbols: v,z")
|
||||
# Arrow but no outputs
|
||||
with self.assertRaises(ValueError) as raises:
|
||||
guvectorize(sigs, "(x,y),(x,y),(x,y)->")(guadd)
|
||||
# (error message depends on Numpy version)
|
||||
|
||||
|
||||
class NEP13Array:
|
||||
"""https://numpy.org/neps/nep-0013-ufunc-overrides.html"""
|
||||
def __init__(self, array):
|
||||
self.array = array
|
||||
|
||||
def __array__(self):
|
||||
return self.array
|
||||
|
||||
def tolist(self):
|
||||
return self.array.tolist()
|
||||
|
||||
def __array_ufunc__(self, ufunc, method, *args, **kwargs):
|
||||
if method != "__call__":
|
||||
return NotImplemented
|
||||
|
||||
return NEP13Array(ufunc(*[np.asarray(x) for x in args], **kwargs))
|
||||
|
||||
|
||||
class FakeDaskArray:
|
||||
"""This class defines both the NEP13 protocol and the dask collection protocol
|
||||
(https://docs.dask.org/en/stable/custom-collections.html). This is a stand-in for
|
||||
dask array, dask dataframe, and for any wrapper around them (e.g. xarray or pint).
|
||||
"""
|
||||
|
||||
def __init__(self, array):
|
||||
self.array = array
|
||||
|
||||
def __array_ufunc__(self, ufunc, method, *args, **kwargs):
|
||||
if method != "__call__":
|
||||
return NotImplemented
|
||||
|
||||
# Simulate sending the ufunc over the network and applying it on a remote worker
|
||||
ufunc = pickle.loads(pickle.dumps(ufunc))
|
||||
args = [x.array if isinstance(x, FakeDaskArray) else x for x in args]
|
||||
return FakeDaskArray(ufunc(*args, **kwargs))
|
||||
|
||||
def _dask_method(self, *args, **kwargs):
|
||||
raise AssertionError("called potentially expensive method")
|
||||
|
||||
__array__ = _dask_method
|
||||
__dask_graph__ = _dask_method
|
||||
__dask_keys__ = _dask_method
|
||||
__dask_optimize__ = _dask_method
|
||||
__dask_postcompute__ = _dask_method
|
||||
__dask_postpersist__ = _dask_method
|
||||
__dask_scheduler__ = _dask_method
|
||||
__dask_tokenize__ = _dask_method
|
||||
compute = _dask_method
|
||||
persist = _dask_method
|
||||
visualize = _dask_method
|
||||
|
||||
|
||||
class TestNEP13WithoutSignature(TestCase):
|
||||
|
||||
def test_all(self):
|
||||
|
||||
# note: no signatures specified
|
||||
@vectorize(nopython=True)
|
||||
def new_ufunc(hundreds, tens, ones):
|
||||
return 100*hundreds + 10*tens + ones
|
||||
|
||||
# give it integers
|
||||
a = np.array([1, 2, 3], dtype=np.int64)
|
||||
b = np.array([4, 5, 6], dtype=np.int64)
|
||||
c = np.array([7, 8, 9], dtype=np.int64)
|
||||
|
||||
all_np = new_ufunc(a, b, c)
|
||||
self.assertIsInstance(all_np, np.ndarray)
|
||||
self.assertEqual(all_np.tolist(), [147, 258, 369])
|
||||
|
||||
nep13_1 = new_ufunc(NEP13Array(a), b, c)
|
||||
self.assertIsInstance(nep13_1, NEP13Array)
|
||||
self.assertEqual(nep13_1.tolist(), [147, 258, 369])
|
||||
|
||||
nep13_2 = new_ufunc(a, NEP13Array(b), c)
|
||||
self.assertIsInstance(nep13_2, NEP13Array)
|
||||
self.assertEqual(nep13_2.tolist(), [147, 258, 369])
|
||||
|
||||
nep13_3 = new_ufunc(a, b, NEP13Array(c))
|
||||
self.assertIsInstance(nep13_3, NEP13Array)
|
||||
self.assertEqual(nep13_3.tolist(), [147, 258, 369])
|
||||
|
||||
# give it floats
|
||||
a = np.array([1.1, 2.2, 3.3], dtype=np.float64)
|
||||
b = np.array([4.4, 5.5, 6.6], dtype=np.float64)
|
||||
c = np.array([7.7, 8.8, 9.9], dtype=np.float64)
|
||||
|
||||
all_np = new_ufunc(a, b, c)
|
||||
self.assertIsInstance(all_np, np.ndarray)
|
||||
self.assertEqual(all_np.tolist(), [161.7, 283.8, 405.9])
|
||||
|
||||
nep13_1 = new_ufunc(NEP13Array(a), b, c)
|
||||
self.assertIsInstance(nep13_1, NEP13Array)
|
||||
self.assertEqual(nep13_1.tolist(), [161.7, 283.8, 405.9])
|
||||
|
||||
nep13_2 = new_ufunc(a, NEP13Array(b), c)
|
||||
self.assertIsInstance(nep13_2, NEP13Array)
|
||||
self.assertEqual(nep13_2.tolist(), [161.7, 283.8, 405.9])
|
||||
|
||||
nep13_3 = new_ufunc(a, b, NEP13Array(c))
|
||||
self.assertIsInstance(nep13_3, NEP13Array)
|
||||
self.assertEqual(nep13_3.tolist(), [161.7, 283.8, 405.9])
|
||||
|
||||
|
||||
class TestDask(unittest.TestCase):
|
||||
"""Test that numba ufuncs are compatible with dask collections and wrappers around
|
||||
dask (e.g. xarray or pint) and that they can be serialized, sent over the network,
|
||||
deserialized on a different host and applied remotely.
|
||||
"""
|
||||
|
||||
def test_dask_array(self):
|
||||
a = FakeDaskArray(np.arange(4, dtype=np.float64))
|
||||
expect = np.arange(4, dtype=np.float64) * 2
|
||||
|
||||
@vectorize(["f8(f8)"])
|
||||
def double_static_vectorize(x):
|
||||
return x * 2
|
||||
|
||||
@vectorize()
|
||||
def double_dynamic_vectorize(x):
|
||||
return x * 2
|
||||
|
||||
@guvectorize(["f8,f8[:]"], "()->()")
|
||||
def double_guvectorize(x, out):
|
||||
out[:] = x * 2
|
||||
|
||||
for func in (
|
||||
double_static_vectorize,
|
||||
double_dynamic_vectorize,
|
||||
double_guvectorize,
|
||||
):
|
||||
with self.subTest(func):
|
||||
b = func(a)
|
||||
assert isinstance(b, FakeDaskArray)
|
||||
assert_array_equal(b.array, expect)
|
||||
|
||||
|
||||
class TestVectorizeDecorJitDisabled(TestVectorizeDecor):
|
||||
|
||||
def setUp(self):
|
||||
self.old_disable_jit = config.DISABLE_JIT
|
||||
config.DISABLE_JIT = False
|
||||
|
||||
def tearDown(self):
|
||||
config.DISABLE_JIT = self.old_disable_jit
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,122 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function, absolute_import, division
|
||||
|
||||
import unittest
|
||||
|
||||
import numpy as np
|
||||
from numba import guvectorize
|
||||
from numba.tests.support import TestCase
|
||||
|
||||
|
||||
def py_replace_2nd(x_t, y_1):
|
||||
for t in range(0, x_t.shape[0], 2):
|
||||
x_t[t] = y_1[0]
|
||||
|
||||
|
||||
def py_update_3(x0_t, x1_t, x2_t, y_1):
|
||||
for t in range(0, x0_t.shape[0]):
|
||||
x0_t[t] = y_1[0]
|
||||
x1_t[t] = 2 * y_1[0]
|
||||
x2_t[t] = 3 * y_1[0]
|
||||
|
||||
|
||||
class TestUpdateInplace(TestCase):
|
||||
|
||||
def _run_test_for_gufunc(self, gufunc, py_func, expect_f4_to_pass=True,
|
||||
z=2):
|
||||
for dtype, expect_to_pass in [('f8', True), ('f4', expect_f4_to_pass)]:
|
||||
inputs = [np.zeros(10, dtype) for _ in range(gufunc.nin - 1)]
|
||||
ex_inputs = [x_t.copy() for x_t in inputs]
|
||||
|
||||
gufunc(*inputs, z)
|
||||
py_func(*ex_inputs, np.array([z]))
|
||||
|
||||
for i, (x_t, ex_x_t) in enumerate(zip(inputs, ex_inputs)):
|
||||
if expect_to_pass:
|
||||
np.testing.assert_equal(x_t, ex_x_t, err_msg='input %s' % i)
|
||||
else:
|
||||
self.assertFalse((x_t == ex_x_t).all(), msg='input %s' % i)
|
||||
|
||||
def test_update_inplace(self):
|
||||
# test without writable_args
|
||||
gufunc = guvectorize(['void(f8[:], f8[:])'], '(t),()',
|
||||
nopython=True)(py_replace_2nd)
|
||||
self._run_test_for_gufunc(gufunc, py_replace_2nd,
|
||||
expect_f4_to_pass=False)
|
||||
|
||||
# test with writable_args
|
||||
gufunc = guvectorize(['void(f8[:], f8[:])'], '(t),()',
|
||||
nopython=True, writable_args=(0,))(py_replace_2nd)
|
||||
self._run_test_for_gufunc(gufunc, py_replace_2nd)
|
||||
|
||||
# test with writable_args as strings
|
||||
gufunc = guvectorize(['void(f8[:], f8[:])'], '(t),()', nopython=True,
|
||||
writable_args=('x_t',))(py_replace_2nd)
|
||||
self._run_test_for_gufunc(gufunc, py_replace_2nd)
|
||||
|
||||
def test_update_inplace_with_cache(self):
|
||||
# test with writable_args
|
||||
gufunc = guvectorize(['void(f8[:], f8[:])'], '(t),()',
|
||||
nopython=True, writable_args=(0,),
|
||||
cache=True)(py_replace_2nd)
|
||||
# 2nd time it is loaded from cache
|
||||
gufunc = guvectorize(['void(f8[:], f8[:])'], '(t),()',
|
||||
nopython=True, writable_args=(0,),
|
||||
cache=True)(py_replace_2nd)
|
||||
self._run_test_for_gufunc(gufunc, py_replace_2nd)
|
||||
|
||||
def test_update_inplace_parallel(self):
|
||||
# test with writable_args
|
||||
gufunc = guvectorize(['void(f8[:], f8[:])'], '(t),()',
|
||||
nopython=True, writable_args=(0,),
|
||||
target='parallel')(py_replace_2nd)
|
||||
self._run_test_for_gufunc(gufunc, py_replace_2nd)
|
||||
|
||||
def test_update_inplace_3(self):
|
||||
# test without writable_args
|
||||
gufunc = guvectorize(['void(f8[:], f8[:], f8[:], f8[:])'],
|
||||
'(t),(t),(t),()',
|
||||
nopython=True)(py_update_3)
|
||||
self._run_test_for_gufunc(gufunc, py_update_3, expect_f4_to_pass=False)
|
||||
|
||||
# test with writable_args
|
||||
gufunc = guvectorize(['void(f8[:], f8[:], f8[:], f8[:])'],
|
||||
'(t),(t),(t),()', nopython=True,
|
||||
writable_args=(0, 1, 2))(py_update_3)
|
||||
self._run_test_for_gufunc(gufunc, py_update_3)
|
||||
|
||||
# test with writable_args as mix of strings and ints
|
||||
gufunc = guvectorize(['void(f8[:], f8[:], f8[:], f8[:])'],
|
||||
'(t),(t),(t),()', nopython=True,
|
||||
writable_args=('x0_t', 'x1_t', 2))(py_update_3)
|
||||
self._run_test_for_gufunc(gufunc, py_update_3)
|
||||
|
||||
def test_exceptions(self):
|
||||
# check that len(writable_args) <= nin
|
||||
with self.assertRaises(ValueError):
|
||||
guvectorize(['void(f8[:], f8[:])'], '(t),()', nopython=True,
|
||||
writable_args=(0, 1, 2, 5))(py_replace_2nd)
|
||||
|
||||
# check that all values in writable_args are between 0 and nin
|
||||
with self.assertRaises(ValueError):
|
||||
guvectorize(['void(f8[:], f8[:])'], '(t),()',
|
||||
nopython=True, writable_args=(5,))(py_replace_2nd)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
guvectorize(['void(f8[:], f8[:])'], '(t),()',
|
||||
nopython=True, writable_args=(-1,))(py_replace_2nd)
|
||||
|
||||
# check that exception is raised when passing non-existing argument name
|
||||
with self.assertRaises(RuntimeError):
|
||||
guvectorize(['void(f8[:], f8[:])'], '(t),()',
|
||||
nopython=True, writable_args=('z_t',))(py_replace_2nd)
|
||||
|
||||
# writable_args are not supported for target='cuda'
|
||||
with self.assertRaises(TypeError):
|
||||
guvectorize(['void(f8[:], f8[:])'], '(t),()',
|
||||
nopython=True, writable_args=(0,),
|
||||
target='cuda')(py_replace_2nd)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,151 @@
|
||||
import math
|
||||
|
||||
import numpy as np
|
||||
|
||||
from numba import int32, uint32, float32, float64, jit, vectorize
|
||||
from numba.tests.support import tag, CheckWarningsMixin
|
||||
import unittest
|
||||
|
||||
|
||||
pi = math.pi
|
||||
|
||||
|
||||
def sinc(x):
|
||||
if x == 0.0:
|
||||
return 1.0
|
||||
else:
|
||||
return math.sin(x * pi) / (pi * x)
|
||||
|
||||
def scaled_sinc(x, scale):
|
||||
if x == 0.0:
|
||||
return scale
|
||||
else:
|
||||
return scale * (math.sin(x * pi) / (pi * x))
|
||||
|
||||
def vector_add(a, b):
|
||||
return a + b
|
||||
|
||||
|
||||
class BaseVectorizeDecor(object):
|
||||
target = None
|
||||
wrapper = None
|
||||
funcs = {
|
||||
'func1': sinc,
|
||||
'func2': scaled_sinc,
|
||||
'func3': vector_add,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _run_and_compare(cls, func, sig, A, *args, **kwargs):
|
||||
if cls.wrapper is not None:
|
||||
func = cls.wrapper(func)
|
||||
numba_func = vectorize(sig, target=cls.target)(func)
|
||||
numpy_func = np.vectorize(func)
|
||||
result = numba_func(A, *args)
|
||||
gold = numpy_func(A, *args)
|
||||
np.testing.assert_allclose(result, gold, **kwargs)
|
||||
|
||||
def test_1(self):
|
||||
sig = ['float64(float64)', 'float32(float32)']
|
||||
func = self.funcs['func1']
|
||||
A = np.arange(100, dtype=np.float64)
|
||||
self._run_and_compare(func, sig, A)
|
||||
|
||||
def test_2(self):
|
||||
sig = [float64(float64), float32(float32)]
|
||||
func = self.funcs['func1']
|
||||
A = np.arange(100, dtype=np.float64)
|
||||
self._run_and_compare(func, sig, A)
|
||||
|
||||
def test_3(self):
|
||||
sig = ['float64(float64, uint32)']
|
||||
func = self.funcs['func2']
|
||||
A = np.arange(100, dtype=np.float64)
|
||||
scale = np.uint32(3)
|
||||
self._run_and_compare(func, sig, A, scale, atol=1e-8)
|
||||
|
||||
def test_4(self):
|
||||
sig = [
|
||||
int32(int32, int32),
|
||||
uint32(uint32, uint32),
|
||||
float32(float32, float32),
|
||||
float64(float64, float64),
|
||||
]
|
||||
func = self.funcs['func3']
|
||||
A = np.arange(100, dtype=np.float64)
|
||||
self._run_and_compare(func, sig, A, A)
|
||||
A = A.astype(np.float32)
|
||||
self._run_and_compare(func, sig, A, A)
|
||||
A = A.astype(np.int32)
|
||||
self._run_and_compare(func, sig, A, A)
|
||||
A = A.astype(np.uint32)
|
||||
self._run_and_compare(func, sig, A, A)
|
||||
|
||||
|
||||
class TestCPUVectorizeDecor(unittest.TestCase, BaseVectorizeDecor):
|
||||
target = 'cpu'
|
||||
|
||||
|
||||
class TestParallelVectorizeDecor(unittest.TestCase, BaseVectorizeDecor):
|
||||
_numba_parallel_test_ = False
|
||||
target = 'parallel'
|
||||
|
||||
|
||||
class TestCPUVectorizeJitted(unittest.TestCase, BaseVectorizeDecor):
|
||||
target = 'cpu'
|
||||
wrapper = jit(nopython=True)
|
||||
|
||||
|
||||
class BaseVectorizeNopythonArg(unittest.TestCase, CheckWarningsMixin):
|
||||
"""
|
||||
Test passing the nopython argument to the vectorize decorator.
|
||||
"""
|
||||
def _test_target_nopython(self, target, warnings, with_sig=True):
|
||||
a = np.array([2.0], dtype=np.float32)
|
||||
b = np.array([3.0], dtype=np.float32)
|
||||
sig = [float32(float32, float32)]
|
||||
args = with_sig and [sig] or []
|
||||
with self.check_warnings(warnings):
|
||||
f = vectorize(*args, target=target, nopython=True)(vector_add)
|
||||
f(a, b)
|
||||
|
||||
class TestVectorizeNopythonArg(BaseVectorizeNopythonArg):
|
||||
def test_target_cpu_nopython(self):
|
||||
self._test_target_nopython('cpu', [])
|
||||
|
||||
def test_target_cpu_nopython_no_sig(self):
|
||||
self._test_target_nopython('cpu', [], False)
|
||||
|
||||
def test_target_parallel_nopython(self):
|
||||
self._test_target_nopython('parallel', [])
|
||||
|
||||
|
||||
class BaseVectorizeUnrecognizedArg(unittest.TestCase, CheckWarningsMixin):
|
||||
"""
|
||||
Test passing an unrecognized argument to the vectorize decorator.
|
||||
"""
|
||||
def _test_target_unrecognized_arg(self, target, with_sig=True):
|
||||
a = np.array([2.0], dtype=np.float32)
|
||||
b = np.array([3.0], dtype=np.float32)
|
||||
sig = [float32(float32, float32)]
|
||||
args = with_sig and [sig] or []
|
||||
with self.assertRaises(KeyError) as raises:
|
||||
f = vectorize(*args, target=target, nonexistent=2)(vector_add)
|
||||
f(a, b)
|
||||
self.assertIn("Unrecognized options", str(raises.exception))
|
||||
|
||||
class TestVectorizeUnrecognizedArg(BaseVectorizeUnrecognizedArg):
|
||||
def test_target_cpu_unrecognized_arg(self):
|
||||
self._test_target_unrecognized_arg('cpu')
|
||||
|
||||
def test_target_cpu_unrecognized_arg_no_sig(self):
|
||||
self._test_target_unrecognized_arg('cpu', False)
|
||||
|
||||
def test_target_parallel_unrecognized_arg(self):
|
||||
self._test_target_unrecognized_arg('parallel')
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,69 @@
|
||||
from numba import vectorize
|
||||
|
||||
|
||||
def add(a, b):
|
||||
"""An addition"""
|
||||
return a + b
|
||||
|
||||
|
||||
def equals(a, b):
|
||||
return a == b
|
||||
|
||||
|
||||
def mul(a, b):
|
||||
"""A multiplication"""
|
||||
return a * b
|
||||
|
||||
|
||||
def guadd(a, b, c):
|
||||
"""A generalized addition"""
|
||||
x, y = c.shape
|
||||
for i in range(x):
|
||||
for j in range(y):
|
||||
c[i, j] = a[i, j] + b[i, j]
|
||||
|
||||
|
||||
@vectorize(nopython=True)
|
||||
def inner(a, b):
|
||||
return a + b
|
||||
|
||||
|
||||
@vectorize(["int64(int64, int64)"], nopython=True)
|
||||
def inner_explicit(a, b):
|
||||
return a + b
|
||||
|
||||
|
||||
def outer(a, b):
|
||||
return inner(a, b)
|
||||
|
||||
|
||||
def outer_explicit(a, b):
|
||||
return inner_explicit(a, b)
|
||||
|
||||
|
||||
class Dummy:
|
||||
pass
|
||||
|
||||
|
||||
def guadd_obj(a, b, c):
|
||||
Dummy() # to force object mode
|
||||
x, y = c.shape
|
||||
for i in range(x):
|
||||
for j in range(y):
|
||||
c[i, j] = a[i, j] + b[i, j]
|
||||
|
||||
|
||||
def guadd_scalar_obj(a, b, c):
|
||||
Dummy() # to force object mode
|
||||
x, y = c.shape
|
||||
for i in range(x):
|
||||
for j in range(y):
|
||||
c[i, j] = a[i, j] + b
|
||||
|
||||
|
||||
class MyException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def guerror(a, b, c):
|
||||
raise MyException
|
||||
Reference in New Issue
Block a user