import gc from itertools import product import numpy as np from numpy.polynomial import polynomial as poly from numpy.polynomial import polyutils as pu from numba import jit, njit from numba.tests.support import (TestCase, needs_lapack, EnableNRTStatsMixin, MemoryLeakMixin) from numba.core.errors import TypingError def roots_fn(p): return np.roots(p) def polyadd(c1,c2): return poly.polyadd(c1,c2) def polysub(c1,c2): return poly.polysub(c1,c2) def polymul(c1,c2): return poly.polymul(c1,c2) def trimseq(seq): return pu.trimseq(seq) def polyasseries1(a): res = pu.as_series(a) return res def polyasseries2(a, trim): res = pu.as_series(a, trim) return res def polydiv(c1, c2): res = poly.polydiv(c1, c2) return res def polyval2(x, c): res = poly.polyval(x, c) return res def polyval3T(x, c): res = poly.polyval(x, c, True) return res def polyval3F(x, c): res = poly.polyval(x, c, False) return res def polyint(c, m=1): res = poly.polyint(c, m) return res class TestPolynomialBase(EnableNRTStatsMixin, TestCase): """ Provides setUp and common data/error modes for testing polynomial functions. """ # supported dtypes dtypes = (np.float64, np.float32, np.complex128, np.complex64) def setUp(self): # Collect leftovers from previous test cases before checking for leaks gc.collect() super(TestPolynomialBase, self).setUp() def assert_error(self, cfunc, args, msg, err=ValueError): with self.assertRaises(err) as raises: cfunc(*args) self.assertIn(msg, str(raises.exception)) def assert_1d_input(self, cfunc, args): msg = "Input must be a 1d array." self.assert_error(cfunc, args, msg) class TestPoly1D(TestPolynomialBase): def assert_no_domain_change(self, name, cfunc, args): msg = name + "() argument must not cause a domain change." self.assert_error(cfunc, args, msg) @needs_lapack def test_roots(self): cfunc = jit(nopython=True)(roots_fn) default_resolution = np.finfo(np.float64).resolution def check(a, **kwargs): expected = roots_fn(a, **kwargs) got = cfunc(a, **kwargs) # eigen decomposition used so type specific impl # will be used in numba whereas a wide type impl # will be used in numpy, so compare using a more # fuzzy comparator if a.dtype in self.dtypes: resolution = np.finfo(a.dtype).resolution else: # this is for integer types when roots() will cast to float64 resolution = default_resolution np.testing.assert_allclose( expected, got, rtol=10 * resolution, atol=100 * resolution # zeros tend to be fuzzy ) # Ensure proper resource management with self.assertNoNRTLeak(): cfunc(a, **kwargs) # test vectors in real space # contrived examples to trip branches r_vectors = ( np.array([1]), np.array([1, 3, 2]), np.array([0, 0, 0]), np.array([1, 6, 11, 6]), np.array([0, 0, 0, 1, 3, 2]), np.array([1, 1, 0, 0, 0]), np.array([0, 0, 1, 0, 0, 0]) ) # test loop real space for v, dtype in \ product(r_vectors, [np.int32, np.int64] + list(self.dtypes)): a = v.astype(dtype) check(a) c_vectors = ( np.array([1 + 1j]), np.array([1, 3 + 1j, 2]), np.array([0, 0 + 0j, 0]), np.array([1, 6 + 1j, 11, 6]), np.array([0, 0, 0, 1 + 1j, 3, 2]), np.array([1 + 1j, 1, 0, 0, 0]), np.array([0, 0, 1 + 1j, 0, 0, 0]) ) # test loop complex space for v, dtype in product(c_vectors, self.dtypes[2:]): a = v.astype(dtype) check(a) # check input with dimension > 1 raises self.assert_1d_input(cfunc, (np.arange(4.).reshape(2, 2),)) # check real input with complex roots raises x = np.array([7., 2., 0., 1.]) self.assert_no_domain_change("eigvals", cfunc, (x,)) # but works fine if type conv to complex first cfunc(x.astype(np.complex128)) class TestPolynomial(MemoryLeakMixin, TestCase): # # tests for Polyutils functions # def test_trimseq_basic(self): pyfunc = trimseq cfunc = njit(trimseq) def inputs(): for i in range(5): yield np.array([1] + [0] * i) for coefs in inputs(): self.assertPreciseEqual(pyfunc(coefs), cfunc(coefs)) def test_trimseq_exception(self): cfunc = njit(trimseq) self.disable_leak_check() with self.assertRaises(TypingError) as raises: cfunc("abc") self.assertIn('The argument "seq" must be array-like', str(raises.exception)) with self.assertRaises(TypingError) as e: cfunc(np.arange(10).reshape(5, 2)) self.assertIn('Coefficient array is not 1-d', str(e.exception)) with self.assertRaises(TypingError) as e: cfunc((1, 2, 3, 0)) self.assertIn('Unsupported type UniTuple(int64, 4) for argument "seq"', str(e.exception)) def test_pu_as_series_basic(self): pyfunc1 = polyasseries1 cfunc1 = njit(polyasseries1) pyfunc2 = polyasseries2 cfunc2 = njit(polyasseries2) def inputs(): yield np.arange(4) yield np.arange(6).reshape((2,3)) yield (1, np.arange(3), np.arange(2, dtype=np.float32)) yield ([1, 2, 3, 4, 0], [1, 2, 3]) yield ((0, 0, 1e-3, 0, 1e-5, 0, 0), (1, 2, 3, 4, 5, 6, 7)) yield ((0, 0, 1e-3, 0, 1e-5, 0, 0), (1j, 2, 3j, 4j, 5, 6j, 7)) yield (2, [1.1, 0.]) yield ([1, 2, 3, 0], ) yield ((1, 2, 3, 0), ) yield (np.array([1, 2, 3, 0]), ) yield [np.array([1, 2, 3, 0]), np.array([1, 2, 3, 0])] yield [np.array([1,2,3]), ] for input in inputs(): self.assertPreciseEqual(pyfunc1(input), cfunc1(input)) self.assertPreciseEqual(pyfunc2(input, False), cfunc2(input, False)) self.assertPreciseEqual(pyfunc2(input, True), cfunc2(input, True)) def test_pu_as_series_exception(self): cfunc1 = njit(polyasseries1) cfunc2 = njit(polyasseries2) self.disable_leak_check() with self.assertRaises(TypingError) as raises: cfunc1("abc") self.assertIn('The argument "alist" must be array-like', str(raises.exception)) with self.assertRaises(TypingError) as raises: cfunc2("abc", True) self.assertIn('The argument "alist" must be array-like', str(raises.exception)) with self.assertRaises(TypingError) as raises: cfunc2(np.arange(4), "abc") self.assertIn('The argument "trim" must be boolean', str(raises.exception)) with self.assertRaises(TypingError) as raises: cfunc1(([1, 2, 3], np.arange(16).reshape(4,4))) self.assertIn('Coefficient array is not 1-d', str(raises.exception)) with self.assertRaises(TypingError) as raises: cfunc1(np.arange(8).reshape((2, 2, 2))) self.assertIn('Coefficient array is not 1-d', str(raises.exception)) with self.assertRaises(TypingError) as raises: cfunc1([np.array([[1,2,3],[1,2,3]]), ]) self.assertIn('Coefficient array is not 1-d', str(raises.exception)) with self.assertRaises(ValueError) as raises: cfunc1(np.array([[]], dtype=np.float64)) self.assertIn('Coefficient array is empty', str(raises.exception)) with self.assertRaises(ValueError) as raises: cfunc1(([1, 2, 3], np.array([], dtype=np.float64), np.array([1,2,1]))) self.assertIn('Coefficient array is empty', str(raises.exception)) def _test_polyarithm_basic(self, pyfunc, ignore_sign_on_zero=False): # test suite containing tests for polyadd, polysub, polymul, polydiv cfunc = njit(pyfunc) def inputs(): # basic, taken from https://github.com/numpy/numpy/blob/48a8277855849be094a5979c48d9f5f1778ee4de/numpy/polynomial/tests/test_polynomial.py#L58-L123 # noqa: E501 for i in range(5): for j in range(5): p1 = np.array([0] * i + [1]) p2 = np.array([0] * j + [1]) yield p1, p2 # test lists, tuples, scalars yield [1, 2, 3], [1, 2, 3] yield [1, 2, 3], (1, 2, 3) yield (1, 2, 3), [1, 2, 3] yield [1, 2, 3], 3 yield 3, (1, 2, 3) # test different dtypes yield np.array([1, 2, 3]), np.array([1.0, 2.0, 3.0]) yield np.array([1j, 2j, 3j]), np.array([1.0, 2.0, 3.0]) yield np.array([1, 2, 3]), np.array([1j, 2j, 3j]) yield (1, 2, 3), 3.0 yield (1, 2, 3), 3j yield (1, 1e-3, 3), (1, 2, 3) for p1, p2 in inputs(): self.assertPreciseEqual(pyfunc(p1,p2), cfunc(p1,p2), ignore_sign_on_zero=ignore_sign_on_zero) def _test_polyarithm_exception(self, pyfunc): # test suite containing tests for polyadd, polysub, polymul, polydiv cfunc = njit(pyfunc) self.disable_leak_check() with self.assertRaises(TypingError) as raises: cfunc("abc", np.array([1,2,3])) self.assertIn('The argument "c1" must be array-like', str(raises.exception)) with self.assertRaises(TypingError) as raises: cfunc(np.array([1,2,3]), "abc") self.assertIn('The argument "c2" must be array-like', str(raises.exception)) with self.assertRaises(TypingError) as e: cfunc(np.arange(10).reshape(5, 2), np.array([1, 2, 3])) self.assertIn('Coefficient array is not 1-d', str(e.exception)) with self.assertRaises(TypingError) as e: cfunc(np.array([1, 2, 3]), np.arange(10).reshape(5, 2)) self.assertIn('Coefficient array is not 1-d', str(e.exception)) def test_polyadd_basic(self): self._test_polyarithm_basic(polyadd) def test_polyadd_exception(self): self._test_polyarithm_exception(polyadd) def test_polysub_basic(self): self._test_polyarithm_basic(polysub, ignore_sign_on_zero=True) def test_polysub_exception(self): self._test_polyarithm_exception(polysub) def test_polymul_basic(self): self._test_polyarithm_basic(polymul) def test_polymul_exception(self): self._test_polyarithm_exception(polymul) def test_poly_polydiv_basic(self): pyfunc = polydiv cfunc = njit(polydiv) self._test_polyarithm_basic(polydiv) def inputs(): # Based on https://github.com/numpy/numpy/blob/160c16f055d4d2fce072004e286d8075b31955cd/numpy/polynomial/tests/test_polynomial.py#L99-L114 # noqa: E501 # check scalar division yield [2], [2] yield [2, 2], [2] # check rest. for i in range(5): for j in range(5): ci = [0] * i + [1, 2] cj = [0] * j + [1, 2] tgt = poly.polyadd(ci, cj) yield tgt, ci yield np.array([1,0,0,0,0,0,-1]), np.array([1,0,0,-1]) for c1, c2 in inputs(): self.assertPreciseEqual(pyfunc(c1, c2), cfunc(c1, c2)) def test_poly_polydiv_exception(self): self._test_polyarithm_exception(polydiv) cfunc = njit(polydiv) # Based on https://github.com/numpy/numpy/blob/160c16f055d4d2fce072004e286d8075b31955cd/numpy/polynomial/tests/test_polynomial.py#L97 # noqa: E501 # check zero division with self.assertRaises(ZeroDivisionError) as _: cfunc([1], [0]) def test_poly_polyval_basic(self): pyfunc2 = polyval2 cfunc2 = njit(polyval2) pyfunc3T = polyval3T cfunc3T = njit(polyval3T) pyfunc3F = polyval3F cfunc3F = njit(polyval3F) def inputs(): # Based on https://github.com/numpy/numpy/blob/160c16f055d4d2fce072004e286d8075b31955cd/numpy/polynomial/tests/test_polynomial.py#L137-L157 # noqa: E501 # check empty input yield np.array([], dtype=np.float64), [1] yield 1, [1,2,3] yield np.arange(4).reshape(2,2), [1,2,3] # check normal input for i in range(5): yield np.linspace(-1, 1), [0] * i + [1] yield np.linspace(-1, 1), [0, -1, 0, 1] # check that shape is preserved for i in range(3): dims = [2] * i x = np.zeros(dims) yield x, [1] yield x, [1, 0] yield x, [1, 0, 0] # Check that behaviour corresponds to tensor = False yield np.array([1, 2]), np.arange(4).reshape(2,2) yield [1, 2], np.arange(4).reshape(2,2) for x, c in inputs(): self.assertPreciseEqual(pyfunc2(x, c), cfunc2(x, c)) # test tensor argument self.assertPreciseEqual(pyfunc3T(x, c), cfunc3T(x, c)) self.assertPreciseEqual(pyfunc3F(x, c), cfunc3F(x, c)) def test_poly_polyval_exception(self): cfunc2 = njit(polyval2) cfunc3T = njit(polyval3T) cfunc3F = njit(polyval3F) self.disable_leak_check() with self.assertRaises(TypingError) as raises: cfunc2(3, "abc") self.assertIn('The argument "c" must be array-like', str(raises.exception)) with self.assertRaises(TypingError) as raises: cfunc2("abc", 3) self.assertIn('The argument "x" must be array-like', str(raises.exception)) with self.assertRaises(TypingError) as raises: cfunc2("abc", "def") self.assertIn('The argument "x" must be array-like', str(raises.exception)) with self.assertRaises(TypingError) as raises: cfunc3T(3, "abc") self.assertIn('The argument "c" must be array-like', str(raises.exception)) with self.assertRaises(TypingError) as raises: cfunc3T("abc", 3) self.assertIn('The argument "x" must be array-like', str(raises.exception)) @njit def polyval3(x, c, tensor): res = poly.polyval(x, c, tensor) return res with self.assertRaises(TypingError) as raises: polyval3(3, 3, "abc") self.assertIn('The argument "tensor" must be boolean', str(raises.exception)) with self.assertRaises(TypingError) as raises: cfunc3F("abc", "def") self.assertIn('The argument "x" must be array-like', str(raises.exception)) def test_poly_polyint_basic(self): pyfunc = polyint cfunc = njit(polyint) # basic self.assertPreciseEqual(pyfunc([1,2,3]), cfunc([1,2,3])) # Based on https://github.com/numpy/numpy/blob/160c16f055d4d2fce072004e286d8075b31955cd/numpy/polynomial/tests/test_polynomial.py#L314-L381 # noqa: E501 # test integration of zero polynomial for i in range(2, 5): self.assertPreciseEqual(pyfunc([0], m=i), cfunc([0], m=i)) # check single integration with integration constant for i in range(5): pol = [0] * i + [1] self.assertPreciseEqual(pyfunc(pol, m=1), pyfunc(pol, m=1)) # check multiple integrations with default k for i in range(5): for j in range(2, 5): pol = [0] * i + [1] self.assertPreciseEqual(pyfunc(pol, m=j), cfunc(pol, m=j)) # test multidimensional arrays c2 = np.array([[0,1], [0,2]]) self.assertPreciseEqual(pyfunc(c2), cfunc(c2)) c3 = np.arange(8).reshape((2,2,2)) self.assertPreciseEqual(pyfunc(c3), cfunc(c3)) def test_poly_polyint_exception(self): cfunc = njit(polyint) self.disable_leak_check() with self.assertRaises(TypingError) as raises: cfunc("abc") self.assertIn('The argument "c" must be array-like', str(raises.exception)) with self.assertRaises(TypingError) as raises: cfunc(np.array([1,2,3]), "abc") self.assertIn('The argument "m" must be an integer', str(raises.exception)) with self.assertRaises(TypingError) as raises: cfunc(['a', 'b', 'c'], 1) self.assertIn('Input dtype must be scalar.', str(raises.exception)) with self.assertRaises(TypingError) as raises: cfunc(('a', 'b', 'c'), 1) self.assertIn('Input dtype must be scalar.', str(raises.exception)) # # tests for Polynomial class # def test_Polynomial_constructor(self): def pyfunc3(c, dom, win): p = poly.Polynomial(c, dom, win) return p cfunc3 = njit(pyfunc3) def pyfunc1(c): p = poly.Polynomial(c) return p cfunc1 = njit(pyfunc1) list1 = (np.array([0, 1]), np.array([0., 1.])) list2 = (np.array([0, 1]), np.array([0., 1.])) list3 = (np.array([0, 1]), np.array([0., 1.])) for c in list1: for dom in list2: for win in list3: p1 = pyfunc3(c, dom, win) p2 = cfunc3(c, dom, win) q1 = pyfunc1(c) q2 = cfunc1(c) self.assertPreciseEqual(p1, p2) self.assertPreciseEqual(p1.coef, p2.coef) self.assertPreciseEqual(p1.domain, p2.domain) self.assertPreciseEqual(p1.window, p2.window) self.assertPreciseEqual(q1.coef, q2.coef) self.assertPreciseEqual(q1.domain, q2.domain) self.assertPreciseEqual(q1.window, q2.window) def test_Polynomial_exeption(self): def pyfunc3(c, dom, win): p = poly.Polynomial(c, dom, win) return p cfunc3 = njit(pyfunc3) self.disable_leak_check() input2 = np.array([1, 2]) input3 = np.array([1, 2, 3]) input2D = np.arange(4).reshape((2, 2)) with self.assertRaises(ValueError) as raises: cfunc3(input2, input3, input2) self.assertIn("Domain has wrong number of elements.", str(raises.exception)) with self.assertRaises(ValueError) as raises: cfunc3(input2, input2, input3) self.assertIn("Window has wrong number of elements.", str(raises.exception)) with self.assertRaises(TypingError) as raises: cfunc3(input2D, input2, input2) self.assertIn("Coefficient array is not 1-d", str(raises.exception))