Repository: Theano/Theano

Issue:
Make equal_computations more efficient
This is a follow up to #2769. During review, a few points have been suggested to improve the function equal_computations() but they were not implemented since the PR was still an improvement over the previous version of the implementation and the additional points could be used as an introductory ticket for for the CCW and Scan. The improvements that were suggested (and can still be viewed in the original PR) are :
- [x] Pass `common` as an argument to `compare_nodes()`
- [x] Also have a cache for pairs of "not common" nodes 
- [x] Check if the outputs are already in `common`



Discussion:

Resolving patch:
diff --git a/theano/scan_module/scan_utils.py b/theano/scan_module/scan_utils.py
--- a/theano/scan_module/scan_utils.py
+++ b/theano/scan_module/scan_utils.py
@@ -420,6 +420,7 @@ def equal_computations(xs, ys, in_xs=None, in_ys=None):
             return False
 
     common = set(zip(in_xs, in_ys))
+    different = set()
     for dx, dy in izip(xs, ys):
         # We checked above that both dx and dy have an owner or not
         if not dx.owner:
@@ -434,7 +435,7 @@ def equal_computations(xs, ys, in_xs=None, in_ys=None):
 
     # Explore the two graphs, in parallel, depth first, comparing the nodes
     # along the way for equality.
-    def compare_nodes(nd_x, nd_y):
+    def compare_nodes(nd_x, nd_y, common, different):
         ''' Compare two nodes to determine if they perform equal computation.
         This is done by comparing the ops, the number of inputs, outputs and
         by ensuring that the inputs themselves are the result of equal
@@ -451,6 +452,16 @@ def compare_nodes(nd_x, nd_y):
         elif len(nd_x.outputs) != len(nd_y.outputs):
             return False
         else:
+            all_in_common=True
+            for dx, dy in izip(nd_x.outputs, nd_y.outputs):
+                if (dx, dy) in different:
+                    return False
+                if (dx, dy) not in common:
+                    all_in_common = False
+
+            if all_in_common:
+                return True
+
             # Compare the individual inputs for equality
             for dx, dy in izip(nd_x.inputs, nd_y.inputs):
                 if (dx, dy) not in common:
@@ -461,8 +472,9 @@ def compare_nodes(nd_x, nd_y):
                         dx.owner.outputs.index(dx) ==
                         dy.owner.outputs.index(dy)):
 
-                        nodes_equal = compare_nodes(dx.owner, dy.owner)
+                        nodes_equal = compare_nodes(dx.owner, dy.owner, common, different)
                         if not nodes_equal:
+                            different.add((dx, dy))
                             return False
 
                     # If both variables don't have an owner, then they are
@@ -493,7 +505,7 @@ def compare_nodes(nd_x, nd_y):
         if xs[i].owner:
             # The case where pairs of x[i]s and y[i]s don't both have an owner
             # have already been adressed.
-            is_equal = compare_nodes(xs[i].owner, ys[i].owner)
+            is_equal = compare_nodes(xs[i].owner, ys[i].owner, common, different)
             if not is_equal:
                 return False
 


Test patch: