Skip to content

Commit fe6a129

Browse files
authored
Merge branch 'master' into dpctl-setting-shape-by-scalar
2 parents f1de8c0 + cb31797 commit fe6a129

File tree

7 files changed

+235
-38
lines changed

7 files changed

+235
-38
lines changed

dpnp/dpnp_iface_mathematical.py

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@
110110
"mod",
111111
"modf",
112112
"multiply",
113+
"nan_to_num",
113114
"negative",
114115
"nextafter",
115116
"positive",
@@ -130,6 +131,13 @@
130131
]
131132

132133

134+
def _get_max_min(dtype):
135+
"""Get the maximum and minimum representable values for an inexact dtype."""
136+
137+
f = dpnp.finfo(dtype)
138+
return f.max, f.min
139+
140+
133141
def _get_reduction_res_dt(a, dtype, _out):
134142
"""Get a data type used by dpctl for result array in reduction function."""
135143

@@ -2353,6 +2361,141 @@ def modf(x1, **kwargs):
23532361
)
23542362

23552363

2364+
def nan_to_num(x, copy=True, nan=0.0, posinf=None, neginf=None):
2365+
"""
2366+
Replace ``NaN`` with zero and infinity with large finite numbers (default
2367+
behaviour) or with the numbers defined by the user using the `nan`,
2368+
`posinf` and/or `neginf` keywords.
2369+
2370+
If `x` is inexact, ``NaN`` is replaced by zero or by the user defined value
2371+
in `nan` keyword, infinity is replaced by the largest finite floating point
2372+
values representable by ``x.dtype`` or by the user defined value in
2373+
`posinf` keyword and -infinity is replaced by the most negative finite
2374+
floating point values representable by ``x.dtype`` or by the user defined
2375+
value in `neginf` keyword.
2376+
2377+
For complex dtypes, the above is applied to each of the real and
2378+
imaginary components of `x` separately.
2379+
2380+
If `x` is not inexact, then no replacements are made.
2381+
2382+
For full documentation refer to :obj:`numpy.nan_to_num`.
2383+
2384+
Parameters
2385+
----------
2386+
x : {dpnp.ndarray, usm_ndarray}
2387+
Input data.
2388+
copy : bool, optional
2389+
Whether to create a copy of `x` (``True``) or to replace values
2390+
in-place (``False``). The in-place operation only occurs if casting to
2391+
an array does not require a copy.
2392+
nan : {int, float, bool}, optional
2393+
Value to be used to fill ``NaN`` values.
2394+
Default: ``0.0``.
2395+
posinf : {int, float, bool, None}, optional
2396+
Value to be used to fill positive infinity values. If no value is
2397+
passed then positive infinity values will be replaced with a very
2398+
large number.
2399+
Default: ``None``.
2400+
neginf : {int, float, bool, None} optional
2401+
Value to be used to fill negative infinity values. If no value is
2402+
passed then negative infinity values will be replaced with a very
2403+
small (or negative) number.
2404+
Default: ``None``.
2405+
2406+
Returns
2407+
-------
2408+
out : dpnp.ndarray
2409+
`x`, with the non-finite values replaced. If `copy` is ``False``, this
2410+
may be `x` itself.
2411+
2412+
See Also
2413+
--------
2414+
:obj:`dpnp.isinf` : Shows which elements are positive or negative infinity.
2415+
:obj:`dpnp.isneginf` : Shows which elements are negative infinity.
2416+
:obj:`dpnp.isposinf` : Shows which elements are positive infinity.
2417+
:obj:`dpnp.isnan` : Shows which elements are Not a Number (NaN).
2418+
:obj:`dpnp.isfinite` : Shows which elements are finite
2419+
(not NaN, not infinity)
2420+
2421+
Examples
2422+
--------
2423+
>>> import dpnp as np
2424+
>>> np.nan_to_num(np.array(np.inf))
2425+
array(1.79769313e+308)
2426+
>>> np.nan_to_num(np.array(-np.inf))
2427+
array(-1.79769313e+308)
2428+
>>> np.nan_to_num(np.array(np.nan))
2429+
array(0.)
2430+
>>> x = np.array([np.inf, -np.inf, np.nan, -128, 128])
2431+
>>> np.nan_to_num(x)
2432+
array([ 1.79769313e+308, -1.79769313e+308, 0.00000000e+000,
2433+
-1.28000000e+002, 1.28000000e+002])
2434+
>>> np.nan_to_num(x, nan=-9999, posinf=33333333, neginf=33333333)
2435+
array([ 3.3333333e+07, 3.3333333e+07, -9.9990000e+03, -1.2800000e+02,
2436+
1.2800000e+02])
2437+
>>> y = np.array([complex(np.inf, np.nan), np.nan, complex(np.nan, np.inf)])
2438+
>>> np.nan_to_num(y)
2439+
array([1.79769313e+308 +0.00000000e+000j, # may vary
2440+
0.00000000e+000 +0.00000000e+000j,
2441+
0.00000000e+000 +1.79769313e+308j])
2442+
>>> np.nan_to_num(y, nan=111111, posinf=222222)
2443+
array([222222.+111111.j, 111111. +0.j, 111111.+222222.j])
2444+
2445+
"""
2446+
2447+
dpnp.check_supported_arrays_type(x)
2448+
2449+
# Python boolean is a subtype of an integer
2450+
# so additional check for bool is not needed.
2451+
if not isinstance(nan, (int, float)):
2452+
raise TypeError(
2453+
"nan must be a scalar of an integer, float, bool, "
2454+
f"but got {type(nan)}"
2455+
)
2456+
2457+
out = dpnp.empty_like(x) if copy else x
2458+
x_type = x.dtype.type
2459+
2460+
if not issubclass(x_type, dpnp.inexact):
2461+
return x
2462+
2463+
parts = (
2464+
(x.real, x.imag) if issubclass(x_type, dpnp.complexfloating) else (x,)
2465+
)
2466+
parts_out = (
2467+
(out.real, out.imag)
2468+
if issubclass(x_type, dpnp.complexfloating)
2469+
else (out,)
2470+
)
2471+
max_f, min_f = _get_max_min(x.real.dtype)
2472+
if posinf is not None:
2473+
if not isinstance(posinf, (int, float)):
2474+
raise TypeError(
2475+
"posinf must be a scalar of an integer, float, bool, "
2476+
f"or be None, but got {type(posinf)}"
2477+
)
2478+
max_f = posinf
2479+
if neginf is not None:
2480+
if not isinstance(neginf, (int, float)):
2481+
raise TypeError(
2482+
"neginf must be a scalar of an integer, float, bool, "
2483+
f"or be None, but got {type(neginf)}"
2484+
)
2485+
min_f = neginf
2486+
2487+
for part, part_out in zip(parts, parts_out):
2488+
nan_mask = dpnp.isnan(part)
2489+
posinf_mask = dpnp.isposinf(part)
2490+
neginf_mask = dpnp.isneginf(part)
2491+
2492+
part = dpnp.where(nan_mask, nan, part, out=part_out)
2493+
part = dpnp.where(posinf_mask, max_f, part, out=part_out)
2494+
part = dpnp.where(neginf_mask, min_f, part, out=part_out)
2495+
2496+
return out
2497+
2498+
23562499
_NEGATIVE_DOCSTRING = """
23572500
Computes the numerical negative for each element `x_i` of input array `x`.
23582501

tests/skipped_tests.tbl

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -206,22 +206,6 @@ tests/third_party/cupy/manipulation_tests/test_dims.py::TestInvalidBroadcast_par
206206
tests/third_party/cupy/manipulation_tests/test_dims.py::TestInvalidBroadcast_param_2_{shapes=[(3, 2), (3, 4)]}::test_invalid_broadcast
207207
tests/third_party/cupy/manipulation_tests/test_dims.py::TestInvalidBroadcast_param_3_{shapes=[(0,), (2,)]}::test_invalid_broadcast
208208

209-
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num
210-
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num_negative
211-
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num_for_old_numpy
212-
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num_negative_for_old_numpy
213-
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num_inf
214-
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num_nan
215-
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num_inf_nan
216-
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num_nan_arg
217-
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num_inf_arg
218-
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num_broadcast[nan]
219-
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num_broadcast[posinf]
220-
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num_broadcast[neginf]
221-
222-
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num_scalar_nan
223-
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num_copy
224-
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num_inplace
225209
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_real_if_close_real_dtypes
226210
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_real_if_close_with_tol_real_dtypes
227211
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_real_if_close_true

tests/skipped_tests_gpu.tbl

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -260,22 +260,6 @@ tests/third_party/cupy/manipulation_tests/test_dims.py::TestInvalidBroadcast_par
260260
tests/third_party/cupy/manipulation_tests/test_dims.py::TestInvalidBroadcast_param_2_{shapes=[(3, 2), (3, 4)]}::test_invalid_broadcast
261261
tests/third_party/cupy/manipulation_tests/test_dims.py::TestInvalidBroadcast_param_3_{shapes=[(0,), (2,)]}::test_invalid_broadcast
262262

263-
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num
264-
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num_negative
265-
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num_for_old_numpy
266-
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num_negative_for_old_numpy
267-
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num_inf
268-
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num_nan
269-
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num_inf_nan
270-
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num_nan_arg
271-
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num_inf_arg
272-
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num_broadcast[nan]
273-
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num_broadcast[posinf]
274-
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num_broadcast[neginf]
275-
276-
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num_scalar_nan
277-
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num_copy
278-
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num_inplace
279263
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_real_if_close_real_dtypes
280264
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_real_if_close_with_tol_real_dtypes
281265
tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_real_if_close_true

tests/test_mathematical.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1116,6 +1116,66 @@ def test_subtract(self, dtype, lhs, rhs):
11161116
self._test_mathematical("subtract", dtype, lhs, rhs, check_type=False)
11171117

11181118

1119+
class TestNanToNum:
1120+
@pytest.mark.parametrize("dtype", get_all_dtypes(no_none=True))
1121+
@pytest.mark.parametrize("shape", [(3,), (2, 3), (3, 2, 2)])
1122+
def test_nan_to_num(self, dtype, shape):
1123+
a = numpy.random.randn(*shape).astype(dtype)
1124+
if not dpnp.issubdtype(dtype, dpnp.integer):
1125+
a.flat[1] = numpy.nan
1126+
a_dp = dpnp.array(a)
1127+
1128+
result = dpnp.nan_to_num(a_dp)
1129+
expected = numpy.nan_to_num(a)
1130+
assert_allclose(result, expected)
1131+
1132+
@pytest.mark.parametrize(
1133+
"data", [[], [numpy.nan], [numpy.inf], [-numpy.inf]]
1134+
)
1135+
@pytest.mark.parametrize("dtype", get_float_complex_dtypes())
1136+
def test_empty_and_single_value_arrays(self, data, dtype):
1137+
a = numpy.array(data, dtype)
1138+
ia = dpnp.array(a)
1139+
1140+
result = dpnp.nan_to_num(ia)
1141+
expected = numpy.nan_to_num(a)
1142+
assert_allclose(result, expected)
1143+
1144+
def test_boolean_array(self):
1145+
a = numpy.array([True, False, numpy.nan], dtype=bool)
1146+
ia = dpnp.array(a)
1147+
1148+
result = dpnp.nan_to_num(ia)
1149+
expected = numpy.nan_to_num(a)
1150+
assert_allclose(result, expected)
1151+
1152+
def test_errors(self):
1153+
ia = dpnp.array([0, 1, dpnp.nan, dpnp.inf, -dpnp.inf])
1154+
1155+
# unsupported type `a`
1156+
a_np = dpnp.asnumpy(ia)
1157+
assert_raises(TypeError, dpnp.nan_to_num, a_np)
1158+
1159+
# unsupported type `nan`
1160+
i_nan = dpnp.array(1)
1161+
assert_raises(TypeError, dpnp.nan_to_num, ia, nan=i_nan)
1162+
1163+
# unsupported type `posinf`
1164+
i_posinf = dpnp.array(1)
1165+
assert_raises(TypeError, dpnp.nan_to_num, ia, posinf=i_posinf)
1166+
1167+
# unsupported type `neginf`
1168+
i_neginf = dpnp.array(1)
1169+
assert_raises(TypeError, dpnp.nan_to_num, ia, neginf=i_neginf)
1170+
1171+
@pytest.mark.parametrize("kwarg", ["nan", "posinf", "neginf"])
1172+
@pytest.mark.parametrize("value", [1 - 0j, [1, 2], (1,)])
1173+
def test_errors_diff_types(self, kwarg, value):
1174+
ia = dpnp.array([0, 1, dpnp.nan, dpnp.inf, -dpnp.inf])
1175+
with pytest.raises(TypeError):
1176+
dpnp.nan_to_num(ia, **{kwarg: value})
1177+
1178+
11191179
class TestNextafter:
11201180
@pytest.mark.parametrize("dt", get_float_dtypes())
11211181
@pytest.mark.parametrize(

tests/test_sycl_queue.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2336,3 +2336,17 @@ def test_astype(device_x, device_y):
23362336
sycl_queue = dpctl.SyclQueue(device_y)
23372337
y = dpnp.astype(x, dtype="f4", device=sycl_queue)
23382338
assert_sycl_queue_equal(y.sycl_queue, sycl_queue)
2339+
2340+
2341+
@pytest.mark.parametrize("copy", [True, False], ids=["True", "False"])
2342+
@pytest.mark.parametrize(
2343+
"device",
2344+
valid_devices,
2345+
ids=[device.filter_string for device in valid_devices],
2346+
)
2347+
def test_nan_to_num(copy, device):
2348+
a = dpnp.array([-dpnp.nan, -1, 0, 1, dpnp.nan], device=device)
2349+
result = dpnp.nan_to_num(a, copy=copy)
2350+
2351+
assert_sycl_queue_equal(result.sycl_queue, a.sycl_queue)
2352+
assert copy == (result is not a)

tests/test_usm_type.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1361,3 +1361,13 @@ def test_histogram_bin_edges(usm_type_v, usm_type_w):
13611361
assert v.usm_type == usm_type_v
13621362
assert w.usm_type == usm_type_w
13631363
assert edges.usm_type == du.get_coerced_usm_type([usm_type_v, usm_type_w])
1364+
1365+
1366+
@pytest.mark.parametrize("copy", [True, False], ids=["True", "False"])
1367+
@pytest.mark.parametrize("usm_type_a", list_of_usm_types, ids=list_of_usm_types)
1368+
def test_nan_to_num(copy, usm_type_a):
1369+
a = dp.array([-dp.nan, -1, 0, 1, dp.nan], usm_type=usm_type_a)
1370+
result = dp.nan_to_num(a, copy=copy)
1371+
1372+
assert result.usm_type == usm_type_a
1373+
assert copy == (result is not a)

tests/third_party/cupy/math_tests/test_misc.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ def test_nan_to_num_inf(self):
245245
def test_nan_to_num_nan(self):
246246
self.check_unary_nan("nan_to_num")
247247

248+
@pytest.mark.skip(reason="Scalar input is not supported")
248249
@testing.numpy_cupy_allclose(atol=1e-5)
249250
def test_nan_to_num_scalar_nan(self, xp):
250251
return xp.nan_to_num(xp.nan)
@@ -260,26 +261,27 @@ def test_nan_to_num_inf_arg(self):
260261

261262
@testing.numpy_cupy_array_equal()
262263
def test_nan_to_num_copy(self, xp):
263-
x = xp.asarray([0, 1, xp.nan, 4], dtype=xp.float64)
264+
x = xp.asarray([0, 1, xp.nan, 4], dtype=cupy.default_float_type())
264265
y = xp.nan_to_num(x, copy=True)
265266
assert x is not y
266267
return y
267268

268269
@testing.numpy_cupy_array_equal()
269270
def test_nan_to_num_inplace(self, xp):
270-
x = xp.asarray([0, 1, xp.nan, 4], dtype=xp.float64)
271+
x = xp.asarray([0, 1, xp.nan, 4], dtype=cupy.default_float_type())
271272
y = xp.nan_to_num(x, copy=False)
272273
assert x is y
273274
return y
274275

276+
@pytest.mark.skip(reason="nan, posinf, neginf as array are not supported")
275277
@pytest.mark.parametrize("kwarg", ["nan", "posinf", "neginf"])
276278
def test_nan_to_num_broadcast(self, kwarg):
277279
for xp in (numpy, cupy):
278-
x = xp.asarray([0, 1, xp.nan, 4], dtype=xp.float64)
279-
y = xp.zeros((2, 4), dtype=xp.float64)
280-
with pytest.raises(ValueError):
280+
x = xp.asarray([0, 1, xp.nan, 4], dtype=cupy.default_float_type())
281+
y = xp.zeros((2, 4), dtype=cupy.default_float_type())
282+
with pytest.raises(TypeError):
281283
xp.nan_to_num(x, **{kwarg: y})
282-
with pytest.raises(ValueError):
284+
with pytest.raises(TypeError):
283285
xp.nan_to_num(0.0, **{kwarg: y})
284286

285287
@testing.for_all_dtypes(no_bool=True, no_complex=True)

0 commit comments

Comments
 (0)