This is an automated email from the ASF dual-hosted git repository.
baunsgaard pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/systemds.git
The following commit(s) were added to refs/heads/main by this push:
new 49d00d2829 [SYSTEMDS-3751, SYSTEMDS-3752, SYSTEMDS-3753] Python API
missing builtin isNA, isNaN, isInf
49d00d2829 is described below
commit 49d00d2829e073b073c80802847e660288d1477d
Author: e-strauss <[email protected]>
AuthorDate: Tue Sep 3 22:13:30 2024 +0200
[SYSTEMDS-3751, SYSTEMDS-3752, SYSTEMDS-3753] Python API missing builtin
isNA, isNaN, isInf
Closes #2090
---
src/main/python/systemds/operator/nodes/matrix.py | 23 ++++
src/main/python/systemds/operator/nodes/scalar.py | 23 ++++
src/main/python/tests/matrix/test_is_special.py | 123 ++++++++++++++++++++++
3 files changed, 169 insertions(+)
diff --git a/src/main/python/systemds/operator/nodes/matrix.py
b/src/main/python/systemds/operator/nodes/matrix.py
index 4e010bf088..3f02daa343 100644
--- a/src/main/python/systemds/operator/nodes/matrix.py
+++ b/src/main/python/systemds/operator/nodes/matrix.py
@@ -549,6 +549,29 @@ class Matrix(OperationNode):
"""
return Scalar(self.sds_context, 'toString', [self], kwargs,
output_type=OutputType.STRING)
+ def isNA(self) -> 'Matrix':
+ """ Computes a boolean indicator matrix of the same shape as the
input, indicating where NA (not available)
+ values are located. Currently, NA is only capturing NaN values.
+
+ :return: the OperationNode representing this operation
+ """
+ return Matrix(self.sds_context, 'isNA', [self])
+
+ def isNaN(self) -> 'Matrix':
+ """ Computes a boolean indicator matrix of the same shape as the
input, indicating where NaN (not a number)
+ values are located.
+
+ :return: the OperationNode representing this operation
+ """
+ return Matrix(self.sds_context, 'isNaN', [self])
+
+ def isInf(self) -> 'Matrix':
+ """ Computes a boolean indicator matrix of the same shape as the
input, indicating where Inf (positive or
+ negative infinity) values are located.
+ :return: the OperationNode representing this operation
+ """
+ return Matrix(self.sds_context, 'isInf', [self])
+
def rev(self) -> 'Matrix':
""" Reverses the rows
diff --git a/src/main/python/systemds/operator/nodes/scalar.py
b/src/main/python/systemds/operator/nodes/scalar.py
index ad29e3f13b..7a369b2625 100644
--- a/src/main/python/systemds/operator/nodes/scalar.py
+++ b/src/main/python/systemds/operator/nodes/scalar.py
@@ -268,5 +268,28 @@ class Scalar(OperationNode):
"""
return Scalar(self.sds_context, 'toString', [self],
named_input_nodes=kwargs, output_type=OutputType.STRING)
+ def isNA(self) -> 'Scalar':
+ """ Computes a boolean indicator matrix of the same shape as the
input, indicating where NA (not available)
+ values are located. Currently, NA is only capturing NaN values.
+
+ :return: the OperationNode representing this operation
+ """
+ return Scalar(self.sds_context, 'isNA', [self])
+
+ def isNaN(self) -> 'Scalar':
+ """ Computes a boolean indicator matrix of the same shape as the
input, indicating where NaN (not a number)
+ values are located.
+
+ :return: the OperationNode representing this operation
+ """
+ return Scalar(self.sds_context, 'isNaN', [self])
+
+ def isInf(self) -> 'Scalar':
+ """ Computes a boolean indicator matrix of the same shape as the
input, indicating where Inf (positive or
+ negative infinity) values are located.
+ :return: the OperationNode representing this operation
+ """
+ return Scalar(self.sds_context, 'isInf', [self])
+
def __str__(self):
return "ScalarNode"
diff --git a/src/main/python/tests/matrix/test_is_special.py
b/src/main/python/tests/matrix/test_is_special.py
new file mode 100644
index 0000000000..e04e61ddbf
--- /dev/null
+++ b/src/main/python/tests/matrix/test_is_special.py
@@ -0,0 +1,123 @@
+# -------------------------------------------------------------
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+# -------------------------------------------------------------
+
+import unittest
+import numpy as np
+from systemds.context import SystemDSContext
+
+np.random.seed(7)
+m1 = np.array(
+ [
+ [float("nan"), 2, 3, float("nan")],
+ [5, float("nan"), 7, 8],
+ [9, 10, float("nan"), 12],
+ [float("nan"), 14, 15, float("nan")],
+ ]
+)
+
+m2 = np.array(
+ [
+ [float("inf"), 2, 3, float("-inf")],
+ [5, float("inf"), 7, 8],
+ [9, 10, float("-inf"), 12],
+ [float("inf"), 14, 15, float("-inf")],
+ ]
+)
+
+dim = 100
+m3 = np.random.random((dim * dim))
+sel = np.random.randint(6, size=dim * dim)
+m3[sel == 0] = float("nan")
+m3[sel == 1] = float("inf")
+m3[sel == 2] = float("-inf")
+m3 = m3.reshape((dim, dim))
+
+
+class TestIS_SPECIAL(unittest.TestCase):
+ def setUp(self):
+ self.sds = SystemDSContext()
+
+ def tearDown(self):
+ self.sds.close()
+
+ def test_na_basic(self):
+ sds_input = self.sds.from_numpy(m1)
+ sds_result = sds_input.isNA().compute()
+ np_result = np.isnan(m1)
+ assert np.allclose(sds_result, np_result)
+
+ def test_nan_basic(self):
+ sds_input = self.sds.from_numpy(m1)
+ sds_result = sds_input.isNaN().compute()
+ np_result = np.isnan(m1)
+ assert np.allclose(sds_result, np_result)
+
+ def test_inf_basic(self):
+ sds_input = self.sds.from_numpy(m2)
+ sds_result = sds_input.isInf().compute()
+ np_result = np.isinf(m2)
+ assert np.allclose(sds_result, np_result)
+
+ def test_na_random(self):
+ sds_input = self.sds.from_numpy(m3)
+ sds_result = sds_input.isNA().compute()
+ np_result = np.isnan(m3)
+ assert np.allclose(sds_result, np_result)
+
+ def test_nan_random(self):
+ sds_input = self.sds.from_numpy(m3)
+ sds_result = sds_input.isNaN().compute()
+ np_result = np.isnan(m3)
+ assert np.allclose(sds_result, np_result)
+
+ def test_inf_random(self):
+ sds_input = self.sds.from_numpy(m3)
+ sds_result = sds_input.isInf().compute()
+ np_result = np.isinf(m3)
+ assert np.allclose(sds_result, np_result)
+
+ def test_na_scalar1(self):
+ self.assertTrue(self.sds.scalar(float("nan")).isNA() == 1)
+
+ def test_na_scalar2(self):
+ self.assertTrue(self.sds.scalar(1.0).isNA() == 0)
+
+ def test_nan_scalar1(self):
+ self.assertTrue(self.sds.scalar(float("nan")).isNaN() == 1)
+
+ def test_nan_scalar2(self):
+ self.assertTrue(self.sds.scalar(1.0).isNaN() == 0)
+
+ def test_inf_scalar1(self):
+ self.assertTrue(self.sds.scalar(float("nan")).isInf() == 0)
+
+ def test_inf_scalar2(self):
+ self.assertTrue(self.sds.scalar(1.0).isInf() == 0)
+
+ def test_inf_scalar3(self):
+ self.assertTrue(self.sds.scalar(float("inf")).isInf() == 1)
+
+ def test_inf_scalar4(self):
+ self.assertTrue(self.sds.scalar(float("-inf")).isInf() == 1)
+
+
+if __name__ == "__main__":
+ unittest.main()