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
25 changes: 17 additions & 8 deletions dpnp/random/dpnp_random_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ 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 get_state(self):
Expand Down Expand Up @@ -125,12 +125,15 @@ 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):

# TODO: switch to dpnp.isfinite() and dpnp.signbit() once functions are available,
# but for now use direct numpy calls without call_origin() wrapper, since data is a scalar
if (loc >= max_double or loc <= min_double) and numpy.isfinite(loc):
raise OverflowError(f"Range of loc={loc} exceeds valid bounds")

if (scale >= max_double) and dpnp.isfinite(scale):
if (scale >= max_double) and numpy.isfinite(scale):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps factor out uses of numpy.isfinite into your own routine which can be modified as needed.

If scale always remains a Python or NumPy scalar, than not compute-follows-data violation occurs, but if scale can be an array, then your modular routine can be modified to apply appropriate validation function.

Copy link
Contributor Author

@antonwolfy antonwolfy Nov 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added internal functions _is_finite_scalar() and _is_finite_scalar() to dpnp_random_state.py with TODO comment inside to replace with dpnp functionality once availbale.

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

Expand Down Expand Up @@ -230,9 +233,12 @@ 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:

# TODO: switch to dpnp.isfinite() once function is available,
# but for now use direct numpy calls without call_origin() wrapper, since data is a scalar
if not numpy.isfinite(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 numpy.isfinite(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 +406,12 @@ 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:

# TODO: switch to dpnp.isfinite() once function is available,
# but for now use direct numpy calls without call_origin() wrapper, since data is a scalar
if not numpy.isfinite(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 numpy.isfinite(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
9 changes: 9 additions & 0 deletions tests/test_indexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import numpy


@pytest.mark.usefixtures("allow_fall_back_on_numpy")
def test_choose():
a = numpy.r_[:4]
ia = dpnp.array(a)
Expand Down Expand Up @@ -109,6 +110,7 @@ def test_nonzero(array):
numpy.testing.assert_array_equal(expected, result)


@pytest.mark.usefixtures("allow_fall_back_on_numpy")
@pytest.mark.parametrize("vals",
[[100, 200],
(100, 200)],
Expand Down Expand Up @@ -138,6 +140,7 @@ def test_place1(arr, mask, vals):
numpy.testing.assert_array_equal(a, ia)


@pytest.mark.usefixtures("allow_fall_back_on_numpy")
@pytest.mark.parametrize("vals",
[[100, 200],
[100, 200, 300, 400, 500, 600],
Expand All @@ -161,6 +164,7 @@ def test_place2(arr, mask, vals):
numpy.testing.assert_array_equal(a, ia)


@pytest.mark.usefixtures("allow_fall_back_on_numpy")
@pytest.mark.parametrize("vals",
[[100, 200],
[100, 200, 300, 400, 500, 600],
Expand Down Expand Up @@ -243,6 +247,7 @@ def test_put3():
numpy.testing.assert_array_equal(a, ia)


@pytest.mark.usefixtures("allow_fall_back_on_numpy")
def test_put_along_axis_val_int():
a = numpy.arange(16).reshape(4, 4)
ai = dpnp.array(a)
Expand All @@ -254,6 +259,7 @@ def test_put_along_axis_val_int():
numpy.testing.assert_array_equal(a, ai)


@pytest.mark.usefixtures("allow_fall_back_on_numpy")
def test_put_along_axis1():
a = numpy.arange(64).reshape(4, 4, 4)
ai = dpnp.array(a)
Expand All @@ -265,6 +271,7 @@ def test_put_along_axis1():
numpy.testing.assert_array_equal(a, ai)


@pytest.mark.usefixtures("allow_fall_back_on_numpy")
def test_put_along_axis2():
a = numpy.arange(64).reshape(4, 4, 4)
ai = dpnp.array(a)
Expand Down Expand Up @@ -411,6 +418,7 @@ def test_take(array, indices, array_type, indices_type):
numpy.testing.assert_array_equal(expected, result)


@pytest.mark.usefixtures("allow_fall_back_on_numpy")
def test_take_along_axis():
a = numpy.arange(16).reshape(4, 4)
ai = dpnp.array(a)
Expand All @@ -422,6 +430,7 @@ def test_take_along_axis():
numpy.testing.assert_array_equal(expected, result)


@pytest.mark.usefixtures("allow_fall_back_on_numpy")
def test_take_along_axis1():
a = numpy.arange(64).reshape(4, 4, 4)
ai = dpnp.array(a)
Expand Down
Loading