This commit is contained in:
2026-04-10 15:06:59 +02:00
parent 3031b7153b
commit e5a4711004
7806 changed files with 1918528 additions and 335 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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