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 f19713820c [SYSTEMDS-3456] MatrixBlock Equals
f19713820c is described below

commit f19713820c46606fd4d77869cb2a0577c9ae5326
Author: baunsgaard <[email protected]>
AuthorDate: Wed Oct 26 14:09:43 2022 +0200

    [SYSTEMDS-3456] MatrixBlock Equals
    
    This commit contains a basic library for comparing equality of matrices.
    I have designed it to support an very small epsilon per default, but if
    wanted this epsilon can be increased.
    There is a specialization contained for Dense Matrices, but specialization
    for sparse matrices is missing, and instead fall back to a generic
    quickGetValue implementation.
    
    The implementation supports comparing Compressed - Sparse - Dense matrices,
    but is only efficiently implemented for Dense.
    
    Closes #1712
---
 .../org/apache/sysds/runtime/data/DenseBlock.java  |  42 +++
 .../sysds/runtime/matrix/data/LibMatrixEquals.java | 205 +++++++++++++++
 .../sysds/runtime/matrix/data/MatrixBlock.java     |  28 +-
 .../sysds/test/component/matrix/EqualsTest.java    | 291 +++++++++++++++++++++
 4 files changed, 564 insertions(+), 2 deletions(-)

diff --git a/src/main/java/org/apache/sysds/runtime/data/DenseBlock.java 
b/src/main/java/org/apache/sysds/runtime/data/DenseBlock.java
index 0b7158cf81..bd0a888b2f 100644
--- a/src/main/java/org/apache/sysds/runtime/data/DenseBlock.java
+++ b/src/main/java/org/apache/sysds/runtime/data/DenseBlock.java
@@ -663,6 +663,48 @@ public abstract class DenseBlock implements Serializable
                return false;
        }
        
+       @Override
+       public boolean equals(Object o) {
+               if(o instanceof DenseBlock)
+                       return equals((DenseBlock) o, Double.MIN_NORMAL * 1024);
+               return false;
+       }
+
+       /**
+        * Verify if the values in this dense block is equivalent to that dense 
block, not taking into account the dimensions
+        * of the contained values. Note in some cases one or the other block 
is allocated bigger than the other, so the
+        * values compared is only the values of the smaller block.
+        * 
+        * @param o   Other block
+        * @param eps Epsilon allowed
+        * @return If the blocs are equivalent.
+        */
+       public boolean equals(DenseBlock o, double eps) {
+               if(isContiguous() && o.isContiguous())
+                       return contiguousEquals(o, eps);
+               return genericEquals(o, eps);
+       }
+
+       private boolean contiguousEquals(DenseBlock o, double eps) {
+               final double[] va = values(0);
+               final double[] vb = o.values(0);
+               final int len = Math.min(va.length, vb.length);
+               for(int i = 0; i < len; i++)
+                       if(Math.abs(va[i] - vb[i]) > eps)
+                               return false;
+               return true;
+       }
+
+       private boolean genericEquals(DenseBlock o, double eps) {
+               final int nRows = getDim(0);
+               final int nCols = getDim(1);
+               for(int i = 0; i < nRows; i++)
+                       for(int j = 0; j < nCols; j++)
+                               if(Math.abs(get(i, j) - o.get(i, j)) > eps)
+                                       return false;
+               return true;
+       }
+
        @Override
        public String toString() {
                StringBuilder sb = new StringBuilder();
diff --git 
a/src/main/java/org/apache/sysds/runtime/matrix/data/LibMatrixEquals.java 
b/src/main/java/org/apache/sysds/runtime/matrix/data/LibMatrixEquals.java
new file mode 100644
index 0000000000..1862ff8a4e
--- /dev/null
+++ b/src/main/java/org/apache/sysds/runtime/matrix/data/LibMatrixEquals.java
@@ -0,0 +1,205 @@
+/*
+ * 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.
+ */
+
+package org.apache.sysds.runtime.matrix.data;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * 
+ * <p>
+ * Equals library for MatrixBLocks:
+ * </p>
+ * 
+ * <p>
+ * The implementations adhere to the properties of equals of:
+ * </p>
+ * 
+ * <ul>
+ * <li>Reflective</li>
+ * <li>Symmetric</li>
+ * <li>Transitive</li>
+ * <li>Consistent</li>
+ * </ul>
+ * 
+ */
+public class LibMatrixEquals {
+
+       /** Logger for Equals library */
+       private static final Log LOG = 
LogFactory.getLog(LibMatrixEquals.class.getName());
+
+       /** first block */
+       private final MatrixBlock a;
+       /** second block */
+       private final MatrixBlock b;
+       /** Epsilon */
+       private final double eps;
+
+       /**
+        * Private instance of a comparison, constructed to reduce the 
arguments to method calls.
+        * 
+        * @param a first block
+        * @param b second block
+        */
+       private LibMatrixEquals(MatrixBlock a, MatrixBlock b) {
+               this.a = a;
+               this.b = b;
+               this.eps = Double.MIN_VALUE * 1024;
+       }
+
+       /**
+        * Private instance of a comparison, constructed to reduce the 
arguments to method calls.
+        * 
+        * @param a   first block
+        * @param b   second block
+        * @param eps epsilon allowed
+        */
+       private LibMatrixEquals(MatrixBlock a, MatrixBlock b, double eps) {
+               this.a = a;
+               this.b = b;
+               this.eps = eps;
+       }
+
+       /**
+        * <p>
+        * Analyze if the two matrix blocks are equivalent, this functions even 
if the underlying allocation and data
+        * structure varies.
+        * </p>
+        * 
+        * <p>
+        * The implementations adhere to the properties of equals of:
+        * </p>
+        * 
+        * <ul>
+        * <li>Reflective</li>
+        * <li>Symmetric</li>
+        * <li>Transitive</li>
+        * <li>Consistent</li>
+        * </ul>
+        * 
+        * @param a Matrix Block a to compare
+        * @param b Matrix Block b to compare
+        * @return If the block are equivalent.
+        */
+       public static boolean equals(MatrixBlock a, MatrixBlock b) {
+               // Same object
+               if(a == b)
+                       return true;
+               return new LibMatrixEquals(a, b).exec();
+       }
+
+       /**
+        * <p>
+        * Analyze if the two matrix blocks are equivalent, this functions even 
if the underlying allocation and data
+        * structure varies.
+        * </p>
+        * 
+        * <p>
+        * The implementations adhere to the properties of equals of:
+        * </p>
+        * 
+        * <ul>
+        * <li>Reflective</li>
+        * <li>Symmetric</li>
+        * <li>Transitive</li>
+        * <li>Consistent</li>
+        * </ul>
+        * 
+        * @param a   Matrix Block a to compare
+        * @param b   Matrix Block b to compare
+        * @param eps Epsilon to allow between values
+        * @return If the block are equivalent.
+        */
+       public static boolean equals(MatrixBlock a, MatrixBlock b, double eps) {
+               // Same object
+               if(a == b)
+                       return true;
+               return new LibMatrixEquals(a, b, eps).exec();
+       }
+
+       /**
+        * Execute the comparison
+        * 
+        * @return if the blocks are equivalent
+        */
+       private boolean exec() {
+               if(isMetadataDifferent())
+                       return false;
+               Boolean empty = isEmpty();
+               if(empty != null)
+                       return empty;
+
+               if(a.denseBlock != null && b.denseBlock != null)
+                       return a.denseBlock.equals(b.denseBlock, eps);
+               return genericEquals();
+       }
+
+       /**
+        * Compare metadata, and return true if metadata is different
+        * 
+        * @param a MatrixBlock a
+        * @param b MatrixBlock b
+        * @return If the metadata was comparable
+        */
+       private boolean isMetadataDifferent() {
+               boolean diff = false;
+
+               diff |= a.getNumRows() != b.getNumRows();
+               diff |= a.getNumColumns() != b.getNumColumns();
+               final long nnzA = a.getNonZeros();
+               final long nnzB = b.getNonZeros();
+               diff |= nnzA != -1 && nnzB != -1 && nnzA != nnzB;
+
+               return diff;
+       }
+
+       /**
+        * Empty metadata check. to verify if the content is empty and such.
+        * 
+        * @return Boolean that is not null if something was found otherwise 
null.
+        */
+       private Boolean isEmpty() {
+               final boolean emptyA = a.isEmpty();
+               final boolean emptyB = b.isEmpty();
+               // empty cases!
+               if(emptyA != emptyB)
+                       return false;
+               else if(emptyA)
+                       return true;
+               return null;
+       }
+
+       /**
+        * Generic implementation to cover all cases. But it is slow in most.
+        * 
+        * @return if the matrices are equivalent.
+        */
+       private boolean genericEquals() {
+               LOG.warn("Using generic equals, potential optimizations are 
possible");
+               final int rows = a.getNumRows();
+               final int cols = a.getNumColumns();
+
+               for(int i = 0; i < rows; i++)
+                       for(int j = 0; j < cols; j++)
+                               if(Math.abs(a.quickGetValue(i, j) - 
b.quickGetValue(i, j)) > eps)
+                                       return false;
+               return true;
+       }
+}
diff --git 
a/src/main/java/org/apache/sysds/runtime/matrix/data/MatrixBlock.java 
b/src/main/java/org/apache/sysds/runtime/matrix/data/MatrixBlock.java
index 07c8f3031f..7da4acae46 100644
--- a/src/main/java/org/apache/sysds/runtime/matrix/data/MatrixBlock.java
+++ b/src/main/java/org/apache/sysds/runtime/matrix/data/MatrixBlock.java
@@ -5867,9 +5867,33 @@ public class MatrixBlock extends MatrixValue implements 
CacheBlock, Externalizab
 
        @Override
        public final boolean equals(Object arg0) {
-               throw new RuntimeException("equals should never be called for 
matrix blocks.");
+               if(arg0 instanceof MatrixBlock)
+                       return LibMatrixEquals.equals(this, (MatrixBlock) arg0);
+               return false;
        }
-       
+
+       /**
+        * Analyze if the matrixBlocks are equivalent, the comparsion supports 
if the differnet sides are differently
+        * allocated such as sparse and dense.
+        * 
+        * <p>
+        * The implementations adhere to the properties of equals of:
+        * </p>
+        * 
+        * <ul>
+        * <li>Reflective</li>
+        * <li>Symmetric</li>
+        * <li>Transitive</li>
+        * <li>Consistent</li>
+        * </ul>
+        * 
+        * @param arg0 MatrixBlock to compare
+        * @return If the matrices are equivalent
+        */
+       public final boolean equals(MatrixBlock arg0) {
+               return LibMatrixEquals.equals(this, arg0);
+       }
+
        @Override
        public final int hashCode() {
                throw new RuntimeException("HashCode should never be called for 
matrix blocks.");
diff --git 
a/src/test/java/org/apache/sysds/test/component/matrix/EqualsTest.java 
b/src/test/java/org/apache/sysds/test/component/matrix/EqualsTest.java
new file mode 100644
index 0000000000..36fe9914da
--- /dev/null
+++ b/src/test/java/org/apache/sysds/test/component/matrix/EqualsTest.java
@@ -0,0 +1,291 @@
+/*
+ * 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.
+ */
+
+package org.apache.sysds.test.component.matrix;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.sysds.runtime.matrix.data.LibMatrixEquals;
+import org.apache.sysds.runtime.matrix.data.MatrixBlock;
+import org.apache.sysds.test.TestUtils;
+import org.junit.Test;
+
+public class EqualsTest {
+
+       @Test
+       public void sameObject() {
+               MatrixBlock mb = new MatrixBlock();
+               assertTrue(mb.equals(mb));
+       }
+
+       @Test
+       public void sameObject2() {
+               MatrixBlock mb = new MatrixBlock(10, 10, -2.0);
+               assertTrue(mb.equals(mb));
+       }
+
+       @Test
+       public void sameObjectInterface() {
+               MatrixBlock mb = new MatrixBlock(10, 10, -2.0);
+               assertTrue(LibMatrixEquals.equals(mb, mb));
+       }
+
+       @Test
+       public void sameObjectInterface2() {
+               MatrixBlock mb = new MatrixBlock(10, 10, -2.0);
+               assertTrue(LibMatrixEquals.equals(mb, mb, 1.0));
+       }
+
+       @Test
+       public void sameIfConstructedEqual_Empty() {
+               MatrixBlock mb1 = new MatrixBlock();
+               MatrixBlock mb2 = new MatrixBlock();
+               assertTrue(mb1.equals(mb2));
+               assertTrue(mb2.equals(mb1));
+       }
+
+       @Test
+       public void sameIfConstructedEqual_CastToObject() {
+               MatrixBlock mb1 = new MatrixBlock();
+               MatrixBlock mb2 = new MatrixBlock();
+               assertTrue(mb1.equals((Object) mb2));
+               assertTrue(mb2.equals((Object) mb1));
+       }
+
+       @Test
+       public void oneIsEmpty() {
+               MatrixBlock empty = new MatrixBlock(10, 10, 0.0);
+               MatrixBlock full = new MatrixBlock(10, 10, 1.0);
+
+               assertFalse(empty.equals(full));
+               assertFalse(full.equals(empty));
+       }
+
+       @Test
+       public void diffRows() {
+
+               MatrixBlock empty = new MatrixBlock(11, 10, 0.0);
+               MatrixBlock empty2 = new MatrixBlock(10, 10, 0.0);
+
+               assertFalse(empty.equals(empty2));
+               assertFalse(empty2.equals(empty));
+       }
+
+       @Test
+       public void diffCols() {
+
+               MatrixBlock empty = new MatrixBlock(10, 10, 0.0);
+               MatrixBlock empty2 = new MatrixBlock(10, 11, 0.0);
+
+               assertFalse(empty.equals(empty2));
+               assertFalse(empty2.equals(empty));
+       }
+
+       @Test
+       public void diffRowsAndCols() {
+
+               MatrixBlock empty = new MatrixBlock(13, 10, 0.0);
+               MatrixBlock empty2 = new MatrixBlock(10, 11, 0.0);
+
+               assertFalse(empty.equals(empty2));
+               assertFalse(empty2.equals(empty));
+       }
+
+       @Test
+       public void diffNNZ() {
+
+               MatrixBlock m1 = new MatrixBlock(10, 10, 0.0);
+               MatrixBlock m2 = new MatrixBlock(10, 10, 0.0);
+
+               m1.setValue(1, 1, 3);
+               m1.setValue(1, 2, 3);
+
+               m2.setValue(1, 1, 3);
+               m2.setValue(1, 2, 3);
+               m2.setValue(1, 3, 3);
+
+               assertFalse(m1.equals(m2));
+               assertFalse(m2.equals(m1));
+       }
+
+       @Test
+       public void unknownNNZ_m1() {
+
+               MatrixBlock m1 = new MatrixBlock(10, 10, 0.0);
+               MatrixBlock m2 = new MatrixBlock(10, 10, 0.0);
+
+               m1.setValue(1, 1, 3);
+               m1.setValue(1, 2, 3);
+
+               m1.setNonZeros(-1);
+
+               m2.setValue(1, 1, 3);
+               m2.setValue(1, 2, 3);
+               m2.setValue(1, 3, 3);
+
+               assertFalse(m1.equals(m2));
+               assertFalse(m2.equals(m1));
+       }
+
+       @Test
+       public void unknownNNZ_m2() {
+
+               MatrixBlock m1 = new MatrixBlock(10, 10, 0.0);
+               MatrixBlock m2 = new MatrixBlock(10, 10, 0.0);
+
+               m1.setValue(1, 1, 3);
+               m1.setValue(1, 2, 3);
+
+               m2.setValue(1, 1, 3);
+               m2.setValue(1, 2, 3);
+               m2.setValue(1, 3, 3);
+
+               m2.setNonZeros(-1);
+
+               assertFalse(m1.equals(m2));
+               assertFalse(m2.equals(m1));
+       }
+
+       @Test
+       public void unknownNNZboth() {
+
+               MatrixBlock m1 = new MatrixBlock(10, 10, 0.0);
+               MatrixBlock m2 = new MatrixBlock(10, 10, 0.0);
+
+               m1.setValue(1, 1, 3);
+               m1.setValue(1, 2, 3);
+
+               m1.setNonZeros(-1);
+
+               m2.setValue(1, 1, 3);
+               m2.setValue(1, 2, 3);
+               m2.setValue(1, 3, 3);
+
+               m2.setNonZeros(-1);
+
+               assertFalse(m1.equals(m2));
+               assertFalse(m2.equals(m1));
+       }
+
+       @Test
+       public void unknownNNZEmptyBoth() {
+
+               MatrixBlock m1 = new MatrixBlock(10, 10, 0.0);
+               MatrixBlock m2 = new MatrixBlock(10, 10, 0.0);
+
+               m1.setNonZeros(-1);
+               m2.setNonZeros(-1);
+
+               assertTrue(m1.equals(m2));
+               assertTrue(m2.equals(m1));
+       }
+
+       @Test
+       public void unknownNNZEmptyOne() {
+
+               MatrixBlock m1 = new MatrixBlock(10, 10, 0.0);
+               MatrixBlock m2 = new MatrixBlock(10, 10, 0.0);
+
+               m1.setNonZeros(-1);
+
+               assertTrue(m1.equals(m2));
+               assertTrue(m2.equals(m1));
+       }
+
+       @Test
+       public void unknownNNZEmptyOneFull() {
+
+               MatrixBlock m1 = new MatrixBlock(10, 10, 0.0);
+               MatrixBlock m2 = new MatrixBlock(10, 10, 1.0);
+
+               m2.setNonZeros(-1);
+
+               assertFalse(m1.equals(m2));
+               assertFalse(m2.equals(m1));
+       }
+
+       @Test
+       public void equivalentWithVeryVerySmallEps() {
+               MatrixBlock m1 = new MatrixBlock(10, 10, 1.0 + Double.MIN_VALUE 
* 10);
+               MatrixBlock m2 = new MatrixBlock(10, 10, 1.0);
+               assertTrue(m1.equals(m2));
+               assertTrue(m2.equals(m1));
+       }
+
+       @Test
+       public void equivalentWithEpsSet() {
+               MatrixBlock m1 = new MatrixBlock(10, 10, 1.0 + 1.0);
+               MatrixBlock m2 = new MatrixBlock(10, 10, 1.0);
+               assertTrue(LibMatrixEquals.equals(m1, m2, 1.0));
+               assertTrue(LibMatrixEquals.equals(m2, m1, 1.0));
+       }
+
+       @Test
+       public void notEquivalentWithEpsSet() {
+               MatrixBlock m1 = new MatrixBlock(10, 10, 1.0 + 1.0);
+               MatrixBlock m2 = new MatrixBlock(10, 10, 1.0);
+               assertFalse(LibMatrixEquals.equals(m1, m2, 0.9999));
+               assertFalse(LibMatrixEquals.equals(m2, m1, 0.9999));
+       }
+
+       @Test
+       public void testSparse() {
+
+               MatrixBlock m1 = TestUtils.generateTestMatrixBlock(100, 1000, 
0, 100, 0.05, 231);
+               MatrixBlock m2 = TestUtils.generateTestMatrixBlock(100, 1000, 
0, 100, 0.05, 231);
+               assertTrue(LibMatrixEquals.equals(m1, m2, 0.00000001));
+               assertTrue(LibMatrixEquals.equals(m2, m1, 0.00000001));
+       }
+
+       @Test
+       public void testSparseOneForcedDense() {
+
+               MatrixBlock m1 = TestUtils.generateTestMatrixBlock(100, 100, 0, 
100, 0.05, 231);
+               MatrixBlock m2 = TestUtils.generateTestMatrixBlock(100, 100, 0, 
100, 0.05, 231);
+               m1.sparseToDense();
+               assertTrue(LibMatrixEquals.equals(m1, m2, 0.00000001));
+               assertTrue(LibMatrixEquals.equals(m2, m1, 0.00000001));
+       }
+
+       @Test
+       public void testSparseOneForcedDenseNotEquivalent() {
+
+               MatrixBlock m1 = TestUtils.generateTestMatrixBlock(100, 100, 0, 
100, 0.05, 231);
+               MatrixBlock m2 = TestUtils.generateTestMatrixBlock(100, 100, 0, 
100, 0.05, 231);
+
+               m1.getSparseBlock().get(13).values()[2] = 1324;
+               m2.sparseToDense();
+
+               assertFalse(LibMatrixEquals.equals(m1, m2, 0.00000001));
+               assertFalse(LibMatrixEquals.equals(m2, m1, 0.00000001));
+       }
+
+       @Test
+       public void testSparseNotEquivalent() {
+
+               MatrixBlock m1 = TestUtils.generateTestMatrixBlock(100, 1000, 
0, 100, 0.05, 231);
+               MatrixBlock m2 = TestUtils.generateTestMatrixBlock(100, 1000, 
0, 100, 0.05, 231);
+
+               m1.getSparseBlock().get(13).values()[2] = 1324;
+
+               assertFalse(LibMatrixEquals.equals(m1, m2, 0.00000001));
+               assertFalse(LibMatrixEquals.equals(m2, m1, 0.00000001));
+       }
+}

Reply via email to