ai-content-maker/.venv/Lib/site-packages/numba/tests/npyufunc/test_gufunc.py

550 lines
18 KiB
Python
Raw Normal View History

2024-05-03 04:18:51 +03:00
import unittest
import pickle
import numpy as np
from numba import void, float32, float64, int32, int64, jit, guvectorize
from numba.np.ufunc import GUVectorize
from numba.tests.support import tag, TestCase
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(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(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(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(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(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)
if __name__ == '__main__':
unittest.main()