This is an automated email from the ASF dual-hosted git repository.

ssiddiqi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/systemds.git


The following commit(s) were added to refs/heads/master by this push:
     new 2ae0f81  [SYSTEMDS-2976] Builtin for Stable Marriage Algorithm Closes 
#1279.
2ae0f81 is described below

commit 2ae0f818d9fc5092408df6ff3fe75539a5117e40
Author: Atefeh Asayesh <[email protected]>
AuthorDate: Wed May 19 11:06:26 2021 +0200

    [SYSTEMDS-2976] Builtin for Stable Marriage Algorithm
    Closes #1279.
---
 scripts/builtin/stableMarriage.dml                 | 154 +++++++++++++++++++++
 .../java/org/apache/sysds/common/Builtins.java     |   5 +-
 .../builtin/BuiltinStableMarriageTest.java         | 119 ++++++++++++++++
 .../scripts/functions/builtin/stablemarriage.dml   |  27 ++++
 4 files changed, 303 insertions(+), 2 deletions(-)

diff --git a/scripts/builtin/stableMarriage.dml 
b/scripts/builtin/stableMarriage.dml
new file mode 100644
index 0000000..e6293cd
--- /dev/null
+++ b/scripts/builtin/stableMarriage.dml
@@ -0,0 +1,154 @@
+#-------------------------------------------------------------
+## 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.
+#
+#-------------------------------------------------------------
+# THIS SCRIPT COMPUTES A SOLUTION FOR THE STABLE MARRIAGE PROBLEM
+#
+# INPUT PARAMETERS:
+# 
--------------------------------------------------------------------------------------------
+# NAME                  TYPE       DEFAULT      MEANING
+# 
--------------------------------------------------------------------------------------------
+# P                     Matrix     ---          proposer matrix P.
+#                                               It must be a square matrix 
with no zeros.
+#
+# A                     Matrix     ---          acceptor matrix A.
+#                                               It must be a square matrix 
with no zeros.
+#
+# ordered               Boolean    TRUE         If true, P and A are assumed 
to be ordered,
+#                                               i.e. the leftmost value in a 
row is the most preferred partner's index.
+#                                               i.e. the leftmost value in a 
row in P is the preference value for the acceptor with
+#                                               index 1 and vice-versa (higher 
is better).
+# OUTPUT PARAMETERS:
+# 
--------------------------------------------------------------------------------------------
+# NAME                  TYPE       DEFAULT      MEANING
+# 
--------------------------------------------------------------------------------------------
+#result_matrix          Matrix     ---          Result Matrix
+#                                               If cell [i,j] is non-zero, it 
means that acceptor i has matched with proposer j.
+#                                               Further, if cell [i,j] is 
non-zero, it holds the preference value that led to the match.
+#
+#
+# Proposers.mtx:
+# 2.0,1.0,3.0
+# 1.0,2.0,3.0
+# 1.0,3.0,2.0
+#
+# Since ordered=TRUE, this means that proposer 1 (row 1) likes acceptor 2 the 
most, followed by acceptor 1 and acceptor 3.
+# If ordered=FALSE, this would mean that proposer 1 (row 1) likes acceptor 3 
the most (since the value at [1,3] is the row max),
+# followed by acceptor 1 (2.0 preference value) and acceptor 2 (1.0 preference 
value).
+#
+# Acceptors.mtx:
+# 3.0,1.0,2.0
+# 2.0,1.0,3.0
+# 3.0,2.0,1.0
+#
+# Since ordered=TRUE, this means that acceptor 1 (row 1) likes proposer 3 the 
most, followed by proposer 1 and proposer 2.
+# If ordered=FALSE, this would mean that acceptor 1 (row 1) likes proposer 1 
the most (since the value at [1,1] is the row max),
+# followed by proposer 3 (2.0 preference value) and proposer 2 (1.0 preference 
value).
+#
+# Output.mtx (assuming ordered=TRUE):
+# 0.0,0.0,3.0
+# 0.0,3.0,0.0
+# 1.0,0.0,0.0
+#
+# Acceptor 1 has matched with proposer 3 (since [1,3] is non-zero) at a 
preference level of 3.0.
+# Acceptor 2 has matched with proposer 2 (since [2,2] is non-zero) at a 
preference level of 3.0.
+# Acceptor 3 has matched with proposer 1 (since [3,1] is non-zero) at a 
preference level of 1.0.
+# 
--------------------------------------------------------------------------------------------
+
+m_stableMarriage = function(Matrix[Double] P, Matrix[Double] A, Boolean 
ordered = TRUE, Boolean verbose = FALSE)
+  return (Matrix[Double] result_matrix)
+{
+  # variable names follow publication
+
+  print("STARTING STABLE MARRIAGE");
+  assert(nrow(A) == nrow(P));
+  assert(ncol(A) == ncol(P));
+
+  if(nrow(P) != ncol(P) | nrow(A) != ncol(A))
+    stop("StableMarriage error: Wrong Input! Both P and A must be square.")
+
+  n = nrow(P)
+  # Let S be the identity matrix
+  S = diag(matrix(1.0, rows=n, cols=1))
+  result_matrix = matrix(0.0, rows=n, cols=n)
+  # Pre-processing
+  if(!ordered) {
+    # If unordered, we need to order P
+    ordered_P = matrix(0.0, rows=n, cols=n)
+    transposed_P = t(P)
+
+    parfor(i in 1:n)
+      ordered_P[,i] = order(target=transposed_P, by=i, decreasing=TRUE, 
index.return=TRUE)
+    P = t(ordered_P)
+  }
+  else {
+    # If ordered, we need to unorder A
+    unordered_A = matrix(0.0, rows=n, cols=n)
+    # Since cells can be converted to unordered indices independently, we can 
nest parfor loops.
+    parfor(i in 1:n) {
+      parfor(j in 1:n, check=0) 
+        unordered_A[i, as.scalar(A[i, j])] = n - j + 1
+    }
+    A = unordered_A
+  }
+
+  proposer_pointers = matrix(1.0, rows=n, cols=1)
+
+  while(sum(S) > 0) {
+    stripped_preferences = S %*% P
+    mask_matrix = matrix(0.0, rows=n, cols=n)
+    for(i in 1:n) {
+      max_proposal = as.scalar(stripped_preferences[i, 
as.scalar(proposer_pointers[i])])
+      if(max_proposal != 0) {
+        proposer_pointers[i] = as.scalar(proposer_pointers[i]) + 1
+        mask_matrix[max_proposal, i] = 1
+      }
+    }
+    # make Hadamard Product
+    Propose_round_results = mask_matrix * A
+    best_proposers_vector = rowIndexMax(Propose_round_results)
+    prev_best_proposers = rowIndexMax(result_matrix)
+
+    for(i in 1:n, check=0) {
+      new_best_proposer_index = as.scalar(best_proposers_vector[i])
+      new_best = as.scalar(Propose_round_results[i, new_best_proposer_index])
+
+      if(new_best > 0) {
+        prev_best_proposer_index = as.scalar(prev_best_proposers[i])
+        prev_best = as.scalar(result_matrix[i, prev_best_proposer_index])
+
+        if (new_best > prev_best)
+        {
+          # Proposal is better than current fiance
+          result_matrix[i, prev_best_proposer_index] = 0
+          result_matrix[i, new_best_proposer_index] = new_best
+
+          #Disable freshly married man to search for a new woman in the next 
round
+          S[new_best_proposer_index, new_best_proposer_index] = 0
+
+          # If a fiance existed, dump him/her
+          if(prev_best > 0)
+            S[prev_best_proposer_index, prev_best_proposer_index] = 1
+        }
+      }
+    }
+  }
+  
+  if(verbose)
+    print("Result: \n"+toString(result_matrix))
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sysds/common/Builtins.java 
b/src/main/java/org/apache/sysds/common/Builtins.java
index 27a0d73..00bd95d 100644
--- a/src/main/java/org/apache/sysds/common/Builtins.java
+++ b/src/main/java/org/apache/sysds/common/Builtins.java
@@ -227,6 +227,7 @@ public enum Builtins {
        SOLVE("solve", false),
        SPLIT("split", true),
        SPLIT_BALANCED("splitBalanced", true),
+       STABLE_MARRIAGE("stableMarriage", true),
        STATSNA("statsNA", true),
        SQRT("sqrt", false),
        SUM("sum", false),
@@ -377,6 +378,6 @@ public enum Builtins {
 
        public static String getInternalFName(String name, DataType dt) {
                return !contains(name, true, false) ? name : // private builtin
-                       (dt.isMatrix() ? "m_" : "s_") + name;    // public 
builtin
+                               (dt.isMatrix() ? "m_" : "s_") + name;    // 
public builtin
        }
-}
+}
\ No newline at end of file
diff --git 
a/src/test/java/org/apache/sysds/test/functions/builtin/BuiltinStableMarriageTest.java
 
b/src/test/java/org/apache/sysds/test/functions/builtin/BuiltinStableMarriageTest.java
new file mode 100644
index 0000000..f8ed8a4
--- /dev/null
+++ 
b/src/test/java/org/apache/sysds/test/functions/builtin/BuiltinStableMarriageTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.functions.builtin;
+
+import org.apache.sysds.common.Types;
+import org.apache.sysds.lops.LopProperties.ExecType;
+import org.apache.sysds.runtime.matrix.data.MatrixValue;
+import org.apache.sysds.test.AutomatedTestBase;
+import org.apache.sysds.test.TestConfiguration;
+import org.apache.sysds.test.TestUtils;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class BuiltinStableMarriageTest extends AutomatedTestBase {
+
+
+       private final static String TEST_NAME = "stablemarriage";
+       private final static String TEST_DIR = "functions/builtin/";
+       private static final String TEST_CLASS_DIR = TEST_DIR + 
BuiltinStableMarriageTest.class.getSimpleName() + "/";
+
+       private final static double eps = 0.0001;
+
+
+
+       @Override
+       public void setUp() {
+               addTestConfiguration(TEST_NAME,new 
TestConfiguration(TEST_CLASS_DIR, TEST_NAME,new String[]{"SM"}));
+       }
+
+       @Test
+       public void testStableMarriage1() {
+               double[][] P = {{2, 1, 3}, {1, 2, 3}, {1, 3, 2}};
+               double[][] A = {{3, 1, 2}, {2, 1, 3}, {3, 2, 1}};
+               // this is an expected matrix
+               double[][] EM = { {0, 0, 3}, {0, 3, 0}, {1, 0, 0}};
+               runtestStableMarriage(P, A, EM, "TRUE", ExecType.CP);
+       }
+
+       @Test
+       public void testStableMarriage2() {
+               double[][] P = {{4, 3, 2, 1}, {2, 4, 3, 1}, {1, 4, 3, 2}, {4, 
1, 2, 3}};
+               double[][] A = {{2, 3, 4, 1}, {2, 3, 4, 1}, {4, 3, 1, 2}, {2, 
1, 4, 3}};
+               // this is an expected matrix
+               double[][] EM = {{0, 0, 3, 0}, {0, 4, 0, 0}, {0, 0, 0, 4}, {3, 
0, 0, 0}};
+               runtestStableMarriage(P, A, EM, "TRUE", ExecType.CP);
+       }
+
+       @Test
+       public void testStableMarriage3() {
+               double[][] P = {{4, 3, 2, 1}, {2, 4, 3, 1}, {1, 4, 3, 2}, {4, 
1, 2, 3}};
+               double[][] A = {{2, 3, 4, 1}, {2, 3, 4, 1}, {4, 3, 1, 2}, {2, 
1, 4, 3}};
+               // this is an expected matrix
+               double[][] EM = {{2, 0, 0, 0}, {0, 0, 4, 0}, {0, 3, 0, 0}, {0, 
0, 0, 3}};
+               runtestStableMarriage(P, A, EM, "FALSE", ExecType.CP);
+       }
+
+
+       @Test
+       public void testStableMarriageSP() {
+               double[][] P = {{4, 3, 2, 1}, {2, 4, 3, 1}, {1, 4, 3, 2}, {4, 
1, 2, 3}};
+               double[][] A = {{2, 3, 4, 1}, {2, 3, 4, 1}, {4, 3, 1, 2}, {2, 
1, 4, 3}};
+               // this is an expected matrix
+               double[][] EM = {{0, 0, 3, 0}, {0, 4, 0, 0}, {0, 0, 0, 4}, {3, 
0, 0, 0}};
+               runtestStableMarriage(P, A, EM, "TRUE", ExecType.SPARK);
+       }
+
+
+       private void runtestStableMarriage(double[][] P, double[][] A, 
double[][] EM, String ordered, ExecType instType) {
+               Types.ExecMode platformOld = setExecMode(instType);
+
+               try
+               {
+                       loadTestConfiguration(getTestConfiguration(TEST_NAME));
+                       String HOME = SCRIPT_DIR + TEST_DIR;
+                       fullDMLScriptName = HOME + TEST_NAME + ".dml";
+                       List<String> proArgs = new ArrayList<>();
+                       proArgs.add("-args");
+                       proArgs.add(input("P"));
+                       proArgs.add(input("A"));
+                       proArgs.add(ordered);
+                       proArgs.add(output("SM"));
+
+                       programArgs = proArgs.toArray(new String[0]);
+
+                       writeInputMatrixWithMTD("P", P, true);
+                       writeInputMatrixWithMTD("A", A, true);
+
+                       runTest(true, EXCEPTION_NOT_EXPECTED, null, -1);
+
+                       //compare expected results
+                       HashMap<MatrixValue.CellIndex, Double> matrixU = 
readDMLMatrixFromOutputDir("SM");
+                       double[][] OUT = 
TestUtils.convertHashMapToDoubleArray(matrixU);
+                       TestUtils.compareMatrices(EM, OUT, eps);
+               }
+               finally {
+                       rtplatform = platformOld;
+               }
+       }
+}
diff --git a/src/test/scripts/functions/builtin/stablemarriage.dml 
b/src/test/scripts/functions/builtin/stablemarriage.dml
new file mode 100644
index 0000000..c8d50b2
--- /dev/null
+++ b/src/test/scripts/functions/builtin/stablemarriage.dml
@@ -0,0 +1,27 @@
+#-------------------------------------------------------------
+#
+# 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.
+#
+#-------------------------------------------------------------
+
+# this dml file will call the stableMarriage built-in functions inside 
scripts/builtin
+P = read($1)
+A = read($2)
+ordered = as.logical($3)
+output = stableMarriage(P = P, A = A, ordered = ordered, verbose = TRUE)
+write(output, $4)
\ No newline at end of file

Reply via email to