Skip to content

Raise not-implemented exception on numpy fallback #1201

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Nov 16, 2022
5 changes: 5 additions & 0 deletions dpnp/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,8 @@
'''
Explicitly use SYCL shared memory parameter in DPCtl array constructor for creation functions
'''

__DPNP_RAISE_EXCEPION_ON_NUMPY_FALLBACK__ = int(os.getenv('DPNP_RAISE_EXCEPION_ON_NUMPY_FALLBACK', 1))
'''
Trigger non-implemented exception when DPNP fallbacks on NumPy implementation
'''
8 changes: 8 additions & 0 deletions dpnp/dpnp_utils/dpnp_algo_utils.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,14 @@ def call_origin(function, *args, **kwargs):
Call fallback function for unsupported cases
"""

allow_fallback = kwargs.pop("allow_fallback", False)

if not allow_fallback and config.__DPNP_RAISE_EXCEPION_ON_NUMPY_FALLBACK__ == 1:
raise NotImplementedError(f"Requested funtion={function.__name__} with args={args} and kwargs={kwargs} "
"isn't currently supported and would fall back on NumPy implementation. "
"Define enviroment variable `DPNP_RAISE_EXCEPION_ON_NUMPY_FALLBACK` to `0` "
"if the fall back is required to be supported without rasing an exception.")

dpnp_inplace = kwargs.pop("dpnp_inplace", False)
sycl_queue = kwargs.pop("sycl_queue", None)
# print(f"DPNP call_origin(): Fallback called. \n\t function={function}, \n\t args={args}, \n\t kwargs={kwargs}, \n\t dpnp_inplace={dpnp_inplace}")
Expand Down
2 changes: 1 addition & 1 deletion dpnp/random/dpnp_iface_random.py
Original file line number Diff line number Diff line change
Expand Up @@ -1314,7 +1314,7 @@ def seed(seed=None):
dpnp_rng_srand(seed)

# always reseed numpy engine also
return call_origin(numpy.random.seed, seed)
return call_origin(numpy.random.seed, seed, allow_fallback=True)


def standard_cauchy(size=None):
Expand Down
62 changes: 52 additions & 10 deletions dpnp/random/dpnp_random_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,45 @@ def __init__(self, seed=None, device=None, sycl_queue=None):
self._def_float_type = dpnp.float64

self._random_state = MT19937(self._seed, self._sycl_queue)
self._fallback_random_state = call_origin(numpy.random.RandomState, seed)
self._fallback_random_state = call_origin(numpy.random.RandomState, seed, allow_fallback=True)


def _is_finite_scalar(self, x):
"""
Test a scalar for finiteness (not infinity and not Not a Number).

Parameters
-----------
x : input value for test, must be a scalar.

Returns
-------
True where ``x`` is not positive infinity, negative infinity, or NaN;
false otherwise.
"""

# TODO: replace with dpnp.isfinite() once function is available in DPNP,
# but for now use direct numpy calls without call_origin() wrapper, since data is a scalar
return numpy.isfinite(x)


def _is_signbit_scalar(self, x):
"""
Test a scalar if sign bit is set for it (less than zero).

Parameters
-----------
x : input value for test, must be a scalar.

Returns
-------
True where sign bit is set for ``x`` (that is ``x`` is less than zero);
false otherwise.
"""

# TODO: replace with dpnp.signbit() once function is available in DPNP,
# but for now use direct numpy calls without call_origin() wrapper, since data is a scalar
return numpy.signbit(x)


def get_state(self):
Expand Down Expand Up @@ -125,13 +163,14 @@ def normal(self, loc=0.0, scale=1.0, size=None, dtype=None, usm_type="device"):
else:
min_double = numpy.finfo('double').min
max_double = numpy.finfo('double').max
if (loc >= max_double or loc <= min_double) and dpnp.isfinite(loc):

if (loc >= max_double or loc <= min_double) and self._is_finite_scalar(loc):
raise OverflowError(f"Range of loc={loc} exceeds valid bounds")

if (scale >= max_double) and dpnp.isfinite(scale):
if (scale >= max_double) and self._is_finite_scalar(scale):
raise OverflowError(f"Range of scale={scale} exceeds valid bounds")
# # scale = -0.0 is cosidered as negative
elif scale < 0 or scale == 0 and numpy.signbit(scale):
# scale = -0.0 is cosidered as negative
elif scale < 0 or scale == 0 and self._is_signbit_scalar(scale):
raise ValueError(f"scale={scale}, but must be non-negative.")

if dtype is None:
Expand Down Expand Up @@ -198,7 +237,8 @@ def randint(self, low, high=None, size=None, dtype=int, usm_type="device"):
Limitations
-----------
Parameters ``low`` and ``high`` are supported only as scalar.
Parameter ``dtype`` is supported only as `int`.
Parameter ``dtype`` is supported only as :obj:`dpnp.int32` or `int`,
but `int` value is considered to be exactly equivalent to :obj:`dpnp.int32`.
Otherwise, :obj:`numpy.random.randint(low, high, size, dtype)` samples are drawn.

Examples
Expand Down Expand Up @@ -230,9 +270,10 @@ def randint(self, low, high=None, size=None, dtype=int, usm_type="device"):

min_int = numpy.iinfo('int32').min
max_int = numpy.iinfo('int32').max
if not dpnp.isfinite(low) or low > max_int or low < min_int:

if not self._is_finite_scalar(low) or low > max_int or low < min_int:
raise OverflowError(f"Range of low={low} exceeds valid bounds")
elif not dpnp.isfinite(high) or high > max_int or high < min_int:
elif not self._is_finite_scalar(high) or high > max_int or high < min_int:
raise OverflowError(f"Range of high={high} exceeds valid bounds")

low = int(low)
Expand Down Expand Up @@ -400,9 +441,10 @@ def uniform(self, low=0.0, high=1.0, size=None, dtype=None, usm_type="device"):
else:
min_double = numpy.finfo('double').min
max_double = numpy.finfo('double').max
if not dpnp.isfinite(low) or low >= max_double or low <= min_double:

if not self._is_finite_scalar(low) or low >= max_double or low <= min_double:
raise OverflowError(f"Range of low={low} exceeds valid bounds")
elif not dpnp.isfinite(high) or high >= max_double or high <= min_double:
elif not self._is_finite_scalar(high) or high >= max_double or high <= min_double:
raise OverflowError(f"Range of high={high} exceeds valid bounds")

if low > high:
Expand Down
4 changes: 4 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,7 @@ def pytest_collection_modifyitems(config, items):
# exact match of the test name with items from excluded_list
if test_name == item_tbl_str:
item.add_marker(skip_mark)

@pytest.fixture
def allow_fall_back_on_numpy(monkeypatch):
monkeypatch.setattr(dpnp.config, '__DPNP_RAISE_EXCEPION_ON_NUMPY_FALLBACK__', 0)
2 changes: 2 additions & 0 deletions tests/test_amin_amax.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ def _get_min_max_input(type, shape):
return a.reshape(shape)


@pytest.mark.usefixtures("allow_fall_back_on_numpy")
@pytest.mark.parametrize("type",
[numpy.float64, numpy.float32, numpy.int64, numpy.int32],
ids=['float64', 'float32', 'int64', 'int32'])
Expand All @@ -87,6 +88,7 @@ def test_amax(type, shape):
numpy.testing.assert_array_equal(dpnp_res, np_res)


@pytest.mark.usefixtures("allow_fall_back_on_numpy")
@pytest.mark.parametrize("type",
[numpy.float64, numpy.float32, numpy.int64, numpy.int32],
ids=['float64', 'float32', 'int64', 'int32'])
Expand Down
3 changes: 3 additions & 0 deletions tests/test_arithmetic.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import unittest
import pytest

from tests.third_party.cupy import testing

Expand All @@ -21,12 +22,14 @@ def test_modf_part2(self, xp, dtype):

return c

@pytest.mark.usefixtures("allow_fall_back_on_numpy")
@testing.for_float_dtypes()
@testing.numpy_cupy_allclose()
def test_nanprod(self, xp, dtype):
a = xp.array([-2.5, -1.5, xp.nan, 10.5, 1.5, xp.nan], dtype=dtype)
return xp.nanprod(a)

@pytest.mark.usefixtures("allow_fall_back_on_numpy")
@testing.for_float_dtypes()
@testing.numpy_cupy_allclose()
def test_nansum(self, xp, dtype):
Expand Down
7 changes: 7 additions & 0 deletions tests/test_arraycreation.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def test_eye(N, M, k, dtype):
numpy.testing.assert_array_equal(expected, result)


@pytest.mark.usefixtures("allow_fall_back_on_numpy")
@pytest.mark.parametrize("type",
[numpy.float64, numpy.float32, numpy.int64, numpy.int32],
ids=['float64', 'float32', 'int64', 'int32'])
Expand All @@ -93,6 +94,7 @@ def test_frombuffer(type):
numpy.testing.assert_array_equal(dpnp_res, np_res)


@pytest.mark.usefixtures("allow_fall_back_on_numpy")
@pytest.mark.parametrize("type",
[numpy.float64, numpy.float32, numpy.int64, numpy.int32],
ids=['float64', 'float32', 'int64', 'int32'])
Expand All @@ -109,6 +111,7 @@ def test_fromfile(type):
numpy.testing.assert_array_equal(dpnp_res, np_res)


@pytest.mark.usefixtures("allow_fall_back_on_numpy")
@pytest.mark.parametrize("type",
[numpy.float64, numpy.float32, numpy.int64, numpy.int32],
ids=['float64', 'float32', 'int64', 'int32'])
Expand All @@ -124,6 +127,7 @@ def func(x, y):
numpy.testing.assert_array_equal(dpnp_res, np_res)


@pytest.mark.usefixtures("allow_fall_back_on_numpy")
@pytest.mark.parametrize("type",
[numpy.float64, numpy.float32, numpy.int64, numpy.int32],
ids=['float64', 'float32', 'int64', 'int32'])
Expand All @@ -136,6 +140,7 @@ def test_fromiter(type):
numpy.testing.assert_array_equal(dpnp_res, np_res)


@pytest.mark.usefixtures("allow_fall_back_on_numpy")
@pytest.mark.parametrize("type",
[numpy.float64, numpy.float32, numpy.int64, numpy.int32],
ids=['float64', 'float32', 'int64', 'int32'])
Expand All @@ -148,6 +153,7 @@ def test_fromstring(type):
numpy.testing.assert_array_equal(dpnp_res, np_res)


@pytest.mark.usefixtures("allow_fall_back_on_numpy")
@pytest.mark.parametrize("type",
[numpy.float64, numpy.float32, numpy.int64, numpy.int32],
ids=['float64', 'float32', 'int64', 'int32'])
Expand Down Expand Up @@ -183,6 +189,7 @@ def test_identity(n, type):
numpy.testing.assert_array_equal(expected, result)


@pytest.mark.usefixtures("allow_fall_back_on_numpy")
@pytest.mark.parametrize("type",
[numpy.float64, numpy.float32, numpy.int64, numpy.int32],
ids=['float64', 'float32', 'int64', 'int32'])
Expand Down
11 changes: 11 additions & 0 deletions tests/test_arraymanipulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import numpy


@pytest.mark.usefixtures("allow_fall_back_on_numpy")
@pytest.mark.parametrize("dtype",
[numpy.float64, numpy.float32, numpy.int64, numpy.int32],
ids=["float64", "float32", "int64", "int32"])
Expand All @@ -30,6 +31,7 @@ def test_asfarray2(dtype, data):
numpy.testing.assert_array_equal(result, expected)


@pytest.mark.usefixtures("allow_fall_back_on_numpy")
class TestConcatenate:
def test_returns_copy(self):
a = dpnp.array(numpy.eye(3))
Expand Down Expand Up @@ -91,23 +93,27 @@ class TestHstack:
def test_non_iterable(self):
numpy.testing.assert_raises(TypeError, dpnp.hstack, 1)

@pytest.mark.usefixtures("allow_fall_back_on_numpy")
def test_empty_input(self):
numpy.testing.assert_raises(ValueError, dpnp.hstack, ())

@pytest.mark.usefixtures("allow_fall_back_on_numpy")
def test_0D_array(self):
b = dpnp.array(2)
a = dpnp.array(1)
res = dpnp.hstack([a, b])
desired = dpnp.array([1, 2])
numpy.testing.assert_array_equal(res, desired)

@pytest.mark.usefixtures("allow_fall_back_on_numpy")
def test_1D_array(self):
a = dpnp.array([1])
b = dpnp.array([2])
res = dpnp.hstack([a, b])
desired = dpnp.array([1, 2])
numpy.testing.assert_array_equal(res, desired)

@pytest.mark.usefixtures("allow_fall_back_on_numpy")
def test_2D_array(self):
a = dpnp.array([[1], [2]])
b = dpnp.array([[1], [2]])
Expand All @@ -126,30 +132,35 @@ class TestVstack:
def test_non_iterable(self):
numpy.testing.assert_raises(TypeError, dpnp.vstack, 1)

@pytest.mark.usefixtures("allow_fall_back_on_numpy")
def test_empty_input(self):
numpy.testing.assert_raises(ValueError, dpnp.vstack, ())

@pytest.mark.usefixtures("allow_fall_back_on_numpy")
def test_0D_array(self):
a = dpnp.array(1)
b = dpnp.array(2)
res = dpnp.vstack([a, b])
desired = dpnp.array([[1], [2]])
numpy.testing.assert_array_equal(res, desired)

@pytest.mark.usefixtures("allow_fall_back_on_numpy")
def test_1D_array(self):
a = dpnp.array([1])
b = dpnp.array([2])
res = dpnp.vstack([a, b])
desired = dpnp.array([[1], [2]])
numpy.testing.assert_array_equal(res, desired)

@pytest.mark.usefixtures("allow_fall_back_on_numpy")
def test_2D_array(self):
a = dpnp.array([[1], [2]])
b = dpnp.array([[1], [2]])
res = dpnp.vstack([a, b])
desired = dpnp.array([[1], [2], [1], [2]])
numpy.testing.assert_array_equal(res, desired)

@pytest.mark.usefixtures("allow_fall_back_on_numpy")
def test_2D_array2(self):
a = dpnp.array([1, 2])
b = dpnp.array([1, 2])
Expand Down
6 changes: 6 additions & 0 deletions tests/test_bitwise.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,26 @@ def _test_binary_int(self, name, lhs, rhs, dtype):

numpy.testing.assert_array_equal(result, expected)

@pytest.mark.usefixtures("allow_fall_back_on_numpy")
def test_bitwise_and(self, lhs, rhs, dtype):
self._test_binary_int('bitwise_and', lhs, rhs, dtype)

@pytest.mark.usefixtures("allow_fall_back_on_numpy")
def test_bitwise_or(self, lhs, rhs, dtype):
self._test_binary_int('bitwise_or', lhs, rhs, dtype)

@pytest.mark.usefixtures("allow_fall_back_on_numpy")
def test_bitwise_xor(self, lhs, rhs, dtype):
self._test_binary_int('bitwise_xor', lhs, rhs, dtype)

@pytest.mark.usefixtures("allow_fall_back_on_numpy")
def test_invert(self, lhs, rhs, dtype):
self._test_unary_int('invert', lhs, dtype)

@pytest.mark.usefixtures("allow_fall_back_on_numpy")
def test_left_shift(self, lhs, rhs, dtype):
self._test_binary_int('left_shift', lhs, rhs, dtype)

@pytest.mark.usefixtures("allow_fall_back_on_numpy")
def test_right_shift(self, lhs, rhs, dtype):
self._test_binary_int('right_shift', lhs, rhs, dtype)
4 changes: 4 additions & 0 deletions tests/test_histograms.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def setup(self):
def teardown(self):
pass

@pytest.mark.usefixtures("allow_fall_back_on_numpy")
def test_simple(self):
n = 100
v = dpnp.random.rand(n)
Expand All @@ -24,6 +25,7 @@ def test_simple(self):
(a, b) = dpnp.histogram(numpy.linspace(0, 10, 100))
numpy.testing.assert_array_equal(a, 10)

@pytest.mark.usefixtures("allow_fall_back_on_numpy")
def test_one_bin(self):
# Ticket 632
hist, edges = dpnp.histogram([1, 2, 3, 4], [1, 2])
Expand Down Expand Up @@ -66,6 +68,8 @@ def test_density(self):
[1, 2, 3, 4], [0.5, 1.5, numpy.inf], density=True)
numpy.testing.assert_equal(counts, [.25, 0])


@pytest.mark.usefixtures("allow_fall_back_on_numpy")
def test_arr_weights_mismatch(self):
a = dpnp.arange(10) + .5
w = dpnp.arange(11) + .5
Expand Down
Loading