Skip to content

Commit 5029894

Browse files
Commit2Cosmosawni
andauthored
[Issue #1187] Add nan_to_num function initial attempt (#1247)
* initial attempt, working with wrong types * not compiling; mx.float16 and mx.bfloat16 tests added * fix nan to num * nit --------- Co-authored-by: Awni Hannun <[email protected]>
1 parent baf9fa5 commit 5029894

File tree

5 files changed

+93
-1
lines changed

5 files changed

+93
-1
lines changed

docs/src/python/ops.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ Operations
106106
minimum
107107
moveaxis
108108
multiply
109+
nan_to_num
109110
negative
110111
not_equal
111112
ones

mlx/ops.cpp

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
// Copyright © 2023-2024 Apple Inc.
2-
32
#include <algorithm>
43
#include <climits>
54
#include <cmath>
@@ -1344,6 +1343,40 @@ array where(
13441343
inputs);
13451344
}
13461345

1346+
array nan_to_num(
1347+
const array& a,
1348+
float nan /* = 0.0f */,
1349+
const std::optional<float>& posinf_ /* = std::nullopt */,
1350+
const std::optional<float>& neginf_ /* = std::nullopt */,
1351+
StreamOrDevice s /* = {} */) {
1352+
Dtype dtype = a.dtype();
1353+
if (!issubdtype(dtype, inexact)) {
1354+
return a;
1355+
}
1356+
1357+
auto type_to_max = [](const auto& dtype) -> float {
1358+
if (dtype == float32) {
1359+
return std::numeric_limits<float>::max();
1360+
} else if (dtype == bfloat16) {
1361+
return std::numeric_limits<bfloat16_t>::max();
1362+
} else if (dtype == float16) {
1363+
return std::numeric_limits<float16_t>::max();
1364+
} else {
1365+
std::ostringstream msg;
1366+
msg << "[nan_to_num] Does not yet support given type: " << dtype << ".";
1367+
throw std::invalid_argument(msg.str());
1368+
}
1369+
};
1370+
1371+
float posinf = posinf_ ? *posinf_ : type_to_max(dtype);
1372+
float neginf = neginf_ ? *neginf_ : -type_to_max(dtype);
1373+
1374+
auto out = where(isnan(a, s), array(nan, dtype), a, s);
1375+
out = where(isposinf(a, s), array(posinf, dtype), out, s);
1376+
out = where(isneginf(a, s), array(neginf, dtype), out, s);
1377+
return out;
1378+
}
1379+
13471380
array allclose(
13481381
const array& a,
13491382
const array& b,

mlx/ops.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,14 @@ array where(
406406
const array& y,
407407
StreamOrDevice s = {});
408408

409+
/** Replace NaN and infinities with finite numbers. */
410+
array nan_to_num(
411+
const array& a,
412+
float nan = 0.0f,
413+
const std::optional<float>& posinf = std::nullopt,
414+
const std::optional<float>& neginf = std::nullopt,
415+
StreamOrDevice s = {});
416+
409417
/** True if all elements in the array are true (or non-zero). **/
410418
array all(const array& a, bool keepdims, StreamOrDevice s = {});
411419
inline array all(const array& a, StreamOrDevice s = {}) {

python/src/ops.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3595,6 +3595,39 @@ void init_ops(nb::module_& m) {
35953595
array: The output containing elements selected from
35963596
``x`` and ``y``.
35973597
)pbdoc");
3598+
m.def(
3599+
"nan_to_num",
3600+
[](const ScalarOrArray& a,
3601+
float nan,
3602+
std::optional<float>& posinf,
3603+
std::optional<float>& neginf,
3604+
StreamOrDevice s) {
3605+
return nan_to_num(to_array(a), nan, posinf, neginf, s);
3606+
},
3607+
nb::arg(),
3608+
"nan"_a = 0.0f,
3609+
"posinf"_a = nb::none(),
3610+
"neginf"_a = nb::none(),
3611+
nb::kw_only(),
3612+
"stream"_a = nb::none(),
3613+
nb::sig(
3614+
"def nan_to_num(a: Union[scalar, array], nan: float = 0, posinf: Optional[float] = None, neginf: Optional[float] = None, *, stream: Union[None, Stream, Device] = None) -> array"),
3615+
R"pbdoc(
3616+
Replace NaN and Inf values with finite numbers.
3617+
3618+
Args:
3619+
a (array): Input array
3620+
nan (float, optional): Value to replace NaN with. Default: ``0``.
3621+
posinf (float, optional): Value to replace positive infinities
3622+
with. If ``None``, defaults to largest finite value for the
3623+
given data type. Default: ``None``.
3624+
neginf (float, optional): Value to replace negative infinities
3625+
with. If ``None``, defaults to the negative of the largest
3626+
finite value for the given data type. Default: ``None``.
3627+
3628+
Returns:
3629+
array: Output array with NaN and Inf replaced.
3630+
)pbdoc");
35983631
m.def(
35993632
"round",
36003633
[](const ScalarOrArray& a, int decimals, StreamOrDevice s) {

python/tests/test_ops.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1653,6 +1653,23 @@ def test_where(self):
16531653
np.where,
16541654
)
16551655

1656+
def test_nan_to_num(self):
1657+
a = mx.array([6, float("inf"), 2, 0])
1658+
out_mx = mx.nan_to_num(a)
1659+
out_np = np.nan_to_num(a)
1660+
self.assertTrue(np.allclose(out_mx, out_np))
1661+
1662+
for t in [mx.float32, mx.float16]:
1663+
a = mx.array([float("inf"), 6.9, float("nan"), float("-inf")])
1664+
out_mx = mx.nan_to_num(a)
1665+
out_np = np.nan_to_num(a)
1666+
self.assertTrue(np.allclose(out_mx, out_np))
1667+
1668+
a = mx.array([float("inf"), 6.9, float("nan"), float("-inf")]).astype(t)
1669+
out_np = np.nan_to_num(a, nan=0.0, posinf=1000, neginf=-1000)
1670+
out_mx = mx.nan_to_num(a, nan=0.0, posinf=1000, neginf=-1000)
1671+
self.assertTrue(np.allclose(out_mx, out_np))
1672+
16561673
def test_as_strided(self):
16571674
x_npy = np.random.randn(128).astype(np.float32)
16581675
x_mlx = mx.array(x_npy)

0 commit comments

Comments
 (0)