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()

Reply via email to