Skip to content
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