We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Enable contractor contraction on disconnected graphs
When we are using tensor network to represent a tensor in the quantum physics, there is no rule that the network must be connected. for example, we can represent an exterior product from two non-connected nodes.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `tensornetwork/contractors/opt_einsum_paths/path_contractors.py`
Content:
```
1 # pylint: disable=cyclic-import
2 # Copyright 2019 The TensorNetwork Authors
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 #      http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 """Contractors based on `opt_einsum`'s path algorithms."""
16 
17 import functools
18 import opt_einsum
19 # pylint: disable=line-too-long
20 from tensornetwork.network_operations import check_connected, get_all_edges, get_subgraph_dangling
21 # pylint: disable=line-too-long
22 from tensornetwork.network_components import get_all_nondangling, contract_parallel
23 from tensornetwork.network_components import Edge, BaseNode
24 from tensornetwork.contractors.opt_einsum_paths import utils
25 from typing import Any, Optional, Sequence, Iterable
26 
27 #TODO (martin): add return types of functions back once TensorNetwork is gone
28 #               remove _base_network
29 #               _base_nodes -> base
30 
31 
32 def base(nodes: Iterable[BaseNode],
33          algorithm: utils.Algorithm,
34          output_edge_order: Optional[Sequence[Edge]] = None,
35          ignore_edge_order: bool = False) -> BaseNode:
36   """Base method for all `opt_einsum` contractors.
37 
38   Args:
39     nodes: A collection of connected nodes.
40     algorithm: `opt_einsum` contraction method to use.
41     output_edge_order: An optional list of edges. Edges of the
42       final node in `nodes_set`
43       are reordered into `output_edge_order`;
44       if final node has more than one edge,
45       `output_edge_order` must be pronvided.
46     ignore_edge_order: An option to ignore the output edge
47       order.
48 
49   Returns:
50     Final node after full contraction.
51   """
52   nodes_set = set(nodes)
53   check_connected(nodes_set)
54   edges = get_all_edges(nodes_set)
55   #output edge order has to be determinded before any contraction
56   #(edges are refreshed after contractions)
57 
58   if not ignore_edge_order:
59     if output_edge_order is None:
60       output_edge_order = list(get_subgraph_dangling(nodes))
61       if len(output_edge_order) > 1:
62         raise ValueError("The final node after contraction has more than "
63                          "one remaining edge. In this case `output_edge_order` "
64                          "has to be provided.")
65 
66     if set(output_edge_order) != get_subgraph_dangling(nodes):
67       raise ValueError(
68           "output edges are not equal to the remaining "
69           "non-contracted edges of the final node."
70       )
71 
72   for edge in edges:
73     if not edge.is_disabled:  #if its disabled we already contracted it
74       if edge.is_trace():
75         nodes_set.remove(edge.node1)
76         nodes_set.add(contract_parallel(edge))
77 
78   if len(nodes_set) == 1:
79     # There's nothing to contract.
80     if ignore_edge_order:
81       return list(nodes_set)[0]
82     return list(nodes_set)[0].reorder_edges(output_edge_order)
83 
84   # Then apply `opt_einsum`'s algorithm
85   path, nodes = utils.get_path(nodes_set, algorithm)
86   for a, b in path:
87     new_node = nodes[a] @ nodes[b]
88     nodes.append(new_node)
89     nodes = utils.multi_remove(nodes, [a, b])
90 
91   # if the final node has more than one edge,
92   # output_edge_order has to be specified
93   final_node = nodes[0]  # nodes were connected, we checked this
94   if not ignore_edge_order:
95     final_node.reorder_edges(output_edge_order)
96   return final_node
97 
98 
99 def optimal(
100     nodes: Iterable[BaseNode],
101     output_edge_order: Optional[Sequence[Edge]] = None,
102     memory_limit: Optional[int] = None,
103     ignore_edge_order: bool = False) -> BaseNode:
104   """Optimal contraction order via `opt_einsum`.
105 
106   This method will find the truly optimal contraction order via
107   `opt_einsum`'s depth first search algorithm. Since this search is
108   exhaustive, if your network is large (n>10), then the search may
109   take longer than just contracting in a suboptimal way.
110 
111   Args:
112     nodes: an iterable of Nodes
113     output_edge_order: An optional list of edges.
114       Edges of the final node in `nodes_set`
115       are reordered into `output_edge_order`;
116       if final node has more than one edge,
117       `output_edge_order` must be provided.
118     memory_limit: Maximum number of elements in an array during contractions.
119     ignore_edge_order: An option to ignore the output edge order.
120 
121   Returns:
122     The final node after full contraction.
123   """
124   alg = functools.partial(opt_einsum.paths.optimal, memory_limit=memory_limit)
125   return base(nodes, alg, output_edge_order, ignore_edge_order)
126 
127 
128 def branch(nodes: Iterable[BaseNode],
129            output_edge_order: Optional[Sequence[Edge]] = None,
130            memory_limit: Optional[int] = None,
131            nbranch: Optional[int] = None,
132            ignore_edge_order: bool = False) -> BaseNode:
133   """Branch contraction path via `opt_einsum`.
134 
135   This method uses the DFS approach of `optimal` while sorting potential
136   contractions based on a heuristic cost, in order to reduce time spent
137   in exploring paths which are unlikely to be optimal.
138   For more details:
139     https://optimized-einsum.readthedocs.io/en/latest/branching_path.html
140 
141   Args:
142     nodes: an iterable of Nodes
143     output_edge_order: An optional list of edges.
144       Edges of the final node in `nodes_set`
145        are reordered into `output_edge_order`;
146        if final node has more than one edge,
147        `output_edge_order` must be provided.
148     memory_limit: Maximum number of elements in an array during contractions.
149     nbranch: Number of best contractions to explore.
150       If None it explores all inner products starting with those that
151       have the best cost heuristic.
152     ignore_edge_order: An option to ignore the output edge order.
153 
154   Returns:
155     The final node after full contraction.
156   """
157   alg = functools.partial(
158       opt_einsum.paths.branch, memory_limit=memory_limit, nbranch=nbranch)
159   return base(nodes, alg, output_edge_order, ignore_edge_order)
160 
161 
162 def greedy(
163     nodes: Iterable[BaseNode],
164     output_edge_order: Optional[Sequence[Edge]] = None,
165     memory_limit: Optional[int] = None,
166     ignore_edge_order: bool = False) -> BaseNode:
167   """Greedy contraction path via `opt_einsum`.
168 
169   This provides a more efficient strategy than `optimal` for finding
170   contraction paths in large networks. First contracts pairs of tensors
171   by finding the pair with the lowest cost at each step. Then it performs
172   the outer products.
173   For more details:
174     https://optimized-einsum.readthedocs.io/en/latest/greedy_path.html
175 
176   Args:
177     nodes: an iterable of Nodes
178     output_edge_order: An optional list of edges.
179       Edges of the final node in `nodes_set`
180       are reordered into `output_edge_order`;
181       if final node has more than one edge,
182       `output_edge_order` must be provided.
183     memory_limit: Maximum number of elements in an array during contractions.
184     ignore_edge_order: An option to ignore the output edge order.
185 
186   Returns:
187     The final node after full contraction.
188   """
189   alg = functools.partial(opt_einsum.paths.greedy, memory_limit=memory_limit)
190   return base(nodes, alg, output_edge_order, ignore_edge_order)
191 
192 
193 # pylint: disable=too-many-return-statements
194 def auto(
195     nodes: BaseNode,
196     output_edge_order: Optional[Sequence[Edge]] = None,
197     memory_limit: Optional[int] = None,
198     ignore_edge_order: bool = False) -> BaseNode:
199   """Chooses one of the above algorithms according to network size.
200 
201   Default behavior is based on `opt_einsum`'s `auto` contractor.
202 
203   Args:
204     nodes: A collection of connected nodes.
205     output_edge_order: An optional list of edges.
206       Edges of the final node in `nodes_set`
207       are reordered into `output_edge_order`;
208       if final node has more than one edge,
209       `output_edge_order` must be provided.
210     memory_limit: Maximum number of elements in an array during contractions.
211     ignore_edge_order: An option to ignore the output edge order.
212 
213   Returns:
214     Final node after full contraction.
215   """
216 
217   n = len(list(nodes))  #pytype thing
218   _nodes = nodes
219   if n <= 0:
220     raise ValueError("Cannot contract empty tensor network.")
221   if n == 1:
222     if not ignore_edge_order:
223       if output_edge_order is None:
224         output_edge_order = list(
225             (get_all_edges(_nodes) - get_all_nondangling(_nodes)))
226         if len(output_edge_order) > 1:
227           raise ValueError("The final node after contraction has more than "
228                            "one dangling edge. In this case `output_edge_order` "
229                            "has to be provided.")
230 
231     edges = get_all_nondangling(_nodes)
232     if edges:
233       final_node = contract_parallel(edges.pop())
234     else:
235       final_node = list(_nodes)[0]
236     final_node.reorder_edges(output_edge_order)
237     if not ignore_edge_order:
238       final_node.reorder_edges(output_edge_order)
239     return final_node
240 
241   if n < 5:
242     return optimal(nodes, output_edge_order, memory_limit, ignore_edge_order)
243   if n < 7:
244     return branch(nodes, output_edge_order, memory_limit, ignore_edge_order)
245   if n < 9:
246     return branch(nodes, output_edge_order, memory_limit, nbranch=2, ignore_edge_order=ignore_edge_order)
247   if n < 15:
248     return branch(nodes, output_edge_order, nbranch=1, ignore_edge_order=ignore_edge_order)
249   return greedy(nodes, output_edge_order, memory_limit, ignore_edge_order)
250 
251 
252 def custom(
253     nodes: Iterable[BaseNode],
254     optimizer: Any,
255     output_edge_order: Sequence[Edge] = None,
256     memory_limit: Optional[int] = None,
257     ignore_edge_order: bool = False) -> BaseNode:
258   """Uses a custom path optimizer created by the user to calculate paths.
259 
260   The custom path optimizer should inherit `opt_einsum`'s `PathOptimizer`.
261   For more details:
262     https://optimized-einsum.readthedocs.io/en/latest/custom_paths.html
263 
264   Args:
265     nodes: an iterable of Nodes
266     output_edge_order: An optional list of edges.
267       Edges of the final node in `nodes_set`
268       are reordered into `output_edge_order`;
269       if final node has more than one edge,
270       output_edge_order` must be provided.
271     optimizer: A custom `opt_einsum.PathOptimizer` object.
272     memory_limit: Maximum number of elements in an array during contractions.
273     ignore_edge_order: An option to ignore the output edge order.
274 
275   Returns:
276     Final node after full contraction.
277   """
278   alg = functools.partial(optimizer, memory_limit=memory_limit)
279   return base(nodes, alg, output_edge_order, ignore_edge_order)
280
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/tensornetwork/contractors/opt_einsum_paths/path_contractors.py b/tensornetwork/contractors/opt_einsum_paths/path_contractors.py
--- a/tensornetwork/contractors/opt_einsum_paths/path_contractors.py
+++ b/tensornetwork/contractors/opt_einsum_paths/path_contractors.py
@@ -19,7 +19,7 @@
 # pylint: disable=line-too-long
 from tensornetwork.network_operations import check_connected, get_all_edges, get_subgraph_dangling
 # pylint: disable=line-too-long
-from tensornetwork.network_components import get_all_nondangling, contract_parallel
+from tensornetwork.network_components import get_all_nondangling, contract_parallel, contract_between
 from tensornetwork.network_components import Edge, BaseNode
 from tensornetwork.contractors.opt_einsum_paths import utils
 from typing import Any, Optional, Sequence, Iterable
@@ -50,7 +50,6 @@
     Final node after full contraction.
   """
   nodes_set = set(nodes)
-  check_connected(nodes_set)
   edges = get_all_edges(nodes_set)
   #output edge order has to be determinded before any contraction
   #(edges are refreshed after contractions)
@@ -84,7 +83,7 @@
   # Then apply `opt_einsum`'s algorithm
   path, nodes = utils.get_path(nodes_set, algorithm)
   for a, b in path:
-    new_node = nodes[a] @ nodes[b]
+    new_node = contract_between(nodes[a], nodes[b], allow_outer_product=True)
     nodes.append(new_node)
     nodes = utils.multi_remove(nodes, [a, b])
 
@@ -192,7 +191,7 @@
 
 # pylint: disable=too-many-return-statements
 def auto(
-    nodes: BaseNode,
+    nodes: Iterable[BaseNode],
     output_edge_order: Optional[Sequence[Edge]] = None,
     memory_limit: Optional[int] = None,
     ignore_edge_order: bool = False) -> BaseNode:

{"golden_diff": "diff --git a/tensornetwork/contractors/opt_einsum_paths/path_contractors.py b/tensornetwork/contractors/opt_einsum_paths/path_contractors.py\n--- a/tensornetwork/contractors/opt_einsum_paths/path_contractors.py\n+++ b/tensornetwork/contractors/opt_einsum_paths/path_contractors.py\n@@ -19,7 +19,7 @@\n # pylint: disable=line-too-long\n from tensornetwork.network_operations import check_connected, get_all_edges, get_subgraph_dangling\n # pylint: disable=line-too-long\n-from tensornetwork.network_components import get_all_nondangling, contract_parallel\n+from tensornetwork.network_components import get_all_nondangling, contract_parallel, contract_between\n from tensornetwork.network_components import Edge, BaseNode\n from tensornetwork.contractors.opt_einsum_paths import utils\n from typing import Any, Optional, Sequence, Iterable\n@@ -50,7 +50,6 @@\n     Final node after full contraction.\n   \"\"\"\n   nodes_set = set(nodes)\n-  check_connected(nodes_set)\n   edges = get_all_edges(nodes_set)\n   #output edge order has to be determinded before any contraction\n   #(edges are refreshed after contractions)\n@@ -84,7 +83,7 @@\n   # Then apply `opt_einsum`'s algorithm\n   path, nodes = utils.get_path(nodes_set, algorithm)\n   for a, b in path:\n-    new_node = nodes[a] @ nodes[b]\n+    new_node = contract_between(nodes[a], nodes[b], allow_outer_product=True)\n     nodes.append(new_node)\n     nodes = utils.multi_remove(nodes, [a, b])\n \n@@ -192,7 +191,7 @@\n \n # pylint: disable=too-many-return-statements\n def auto(\n-    nodes: BaseNode,\n+    nodes: Iterable[BaseNode],\n     output_edge_order: Optional[Sequence[Edge]] = None,\n     memory_limit: Optional[int] = None,\n     ignore_edge_order: bool = False) -> BaseNode:\n", "issue": "Enable contractor contraction on disconnected graphs\nWhen we are using tensor network to represent a tensor in the quantum physics, there is no rule that the network must be connected. for example, we can represent an exterior product from two non-connected nodes.\n", "before_files": [{"content": "# pylint: disable=cyclic-import\n# Copyright 2019 The TensorNetwork Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"Contractors based on `opt_einsum`'s path algorithms.\"\"\"\n\nimport functools\nimport opt_einsum\n# pylint: disable=line-too-long\nfrom tensornetwork.network_operations import check_connected, get_all_edges, get_subgraph_dangling\n# pylint: disable=line-too-long\nfrom tensornetwork.network_components import get_all_nondangling, contract_parallel\nfrom tensornetwork.network_components import Edge, BaseNode\nfrom tensornetwork.contractors.opt_einsum_paths import utils\nfrom typing import Any, Optional, Sequence, Iterable\n\n#TODO (martin): add return types of functions back once TensorNetwork is gone\n#               remove _base_network\n#               _base_nodes -> base\n\n\ndef base(nodes: Iterable[BaseNode],\n         algorithm: utils.Algorithm,\n         output_edge_order: Optional[Sequence[Edge]] = None,\n         ignore_edge_order: bool = False) -> BaseNode:\n  \"\"\"Base method for all `opt_einsum` contractors.\n\n  Args:\n    nodes: A collection of connected nodes.\n    algorithm: `opt_einsum` contraction method to use.\n    output_edge_order: An optional list of edges. Edges of the\n      final node in `nodes_set`\n      are reordered into `output_edge_order`;\n      if final node has more than one edge,\n      `output_edge_order` must be pronvided.\n    ignore_edge_order: An option to ignore the output edge\n      order.\n\n  Returns:\n    Final node after full contraction.\n  \"\"\"\n  nodes_set = set(nodes)\n  check_connected(nodes_set)\n  edges = get_all_edges(nodes_set)\n  #output edge order has to be determinded before any contraction\n  #(edges are refreshed after contractions)\n\n  if not ignore_edge_order:\n    if output_edge_order is None:\n      output_edge_order = list(get_subgraph_dangling(nodes))\n      if len(output_edge_order) > 1:\n        raise ValueError(\"The final node after contraction has more than \"\n                         \"one remaining edge. In this case `output_edge_order` \"\n                         \"has to be provided.\")\n\n    if set(output_edge_order) != get_subgraph_dangling(nodes):\n      raise ValueError(\n          \"output edges are not equal to the remaining \"\n          \"non-contracted edges of the final node.\"\n      )\n\n  for edge in edges:\n    if not edge.is_disabled:  #if its disabled we already contracted it\n      if edge.is_trace():\n        nodes_set.remove(edge.node1)\n        nodes_set.add(contract_parallel(edge))\n\n  if len(nodes_set) == 1:\n    # There's nothing to contract.\n    if ignore_edge_order:\n      return list(nodes_set)[0]\n    return list(nodes_set)[0].reorder_edges(output_edge_order)\n\n  # Then apply `opt_einsum`'s algorithm\n  path, nodes = utils.get_path(nodes_set, algorithm)\n  for a, b in path:\n    new_node = nodes[a] @ nodes[b]\n    nodes.append(new_node)\n    nodes = utils.multi_remove(nodes, [a, b])\n\n  # if the final node has more than one edge,\n  # output_edge_order has to be specified\n  final_node = nodes[0]  # nodes were connected, we checked this\n  if not ignore_edge_order:\n    final_node.reorder_edges(output_edge_order)\n  return final_node\n\n\ndef optimal(\n    nodes: Iterable[BaseNode],\n    output_edge_order: Optional[Sequence[Edge]] = None,\n    memory_limit: Optional[int] = None,\n    ignore_edge_order: bool = False) -> BaseNode:\n  \"\"\"Optimal contraction order via `opt_einsum`.\n\n  This method will find the truly optimal contraction order via\n  `opt_einsum`'s depth first search algorithm. Since this search is\n  exhaustive, if your network is large (n>10), then the search may\n  take longer than just contracting in a suboptimal way.\n\n  Args:\n    nodes: an iterable of Nodes\n    output_edge_order: An optional list of edges.\n      Edges of the final node in `nodes_set`\n      are reordered into `output_edge_order`;\n      if final node has more than one edge,\n      `output_edge_order` must be provided.\n    memory_limit: Maximum number of elements in an array during contractions.\n    ignore_edge_order: An option to ignore the output edge order.\n\n  Returns:\n    The final node after full contraction.\n  \"\"\"\n  alg = functools.partial(opt_einsum.paths.optimal, memory_limit=memory_limit)\n  return base(nodes, alg, output_edge_order, ignore_edge_order)\n\n\ndef branch(nodes: Iterable[BaseNode],\n           output_edge_order: Optional[Sequence[Edge]] = None,\n           memory_limit: Optional[int] = None,\n           nbranch: Optional[int] = None,\n           ignore_edge_order: bool = False) -> BaseNode:\n  \"\"\"Branch contraction path via `opt_einsum`.\n\n  This method uses the DFS approach of `optimal` while sorting potential\n  contractions based on a heuristic cost, in order to reduce time spent\n  in exploring paths which are unlikely to be optimal.\n  For more details:\n    https://optimized-einsum.readthedocs.io/en/latest/branching_path.html\n\n  Args:\n    nodes: an iterable of Nodes\n    output_edge_order: An optional list of edges.\n      Edges of the final node in `nodes_set`\n       are reordered into `output_edge_order`;\n       if final node has more than one edge,\n       `output_edge_order` must be provided.\n    memory_limit: Maximum number of elements in an array during contractions.\n    nbranch: Number of best contractions to explore.\n      If None it explores all inner products starting with those that\n      have the best cost heuristic.\n    ignore_edge_order: An option to ignore the output edge order.\n\n  Returns:\n    The final node after full contraction.\n  \"\"\"\n  alg = functools.partial(\n      opt_einsum.paths.branch, memory_limit=memory_limit, nbranch=nbranch)\n  return base(nodes, alg, output_edge_order, ignore_edge_order)\n\n\ndef greedy(\n    nodes: Iterable[BaseNode],\n    output_edge_order: Optional[Sequence[Edge]] = None,\n    memory_limit: Optional[int] = None,\n    ignore_edge_order: bool = False) -> BaseNode:\n  \"\"\"Greedy contraction path via `opt_einsum`.\n\n  This provides a more efficient strategy than `optimal` for finding\n  contraction paths in large networks. First contracts pairs of tensors\n  by finding the pair with the lowest cost at each step. Then it performs\n  the outer products.\n  For more details:\n    https://optimized-einsum.readthedocs.io/en/latest/greedy_path.html\n\n  Args:\n    nodes: an iterable of Nodes\n    output_edge_order: An optional list of edges.\n      Edges of the final node in `nodes_set`\n      are reordered into `output_edge_order`;\n      if final node has more than one edge,\n      `output_edge_order` must be provided.\n    memory_limit: Maximum number of elements in an array during contractions.\n    ignore_edge_order: An option to ignore the output edge order.\n\n  Returns:\n    The final node after full contraction.\n  \"\"\"\n  alg = functools.partial(opt_einsum.paths.greedy, memory_limit=memory_limit)\n  return base(nodes, alg, output_edge_order, ignore_edge_order)\n\n\n# pylint: disable=too-many-return-statements\ndef auto(\n    nodes: BaseNode,\n    output_edge_order: Optional[Sequence[Edge]] = None,\n    memory_limit: Optional[int] = None,\n    ignore_edge_order: bool = False) -> BaseNode:\n  \"\"\"Chooses one of the above algorithms according to network size.\n\n  Default behavior is based on `opt_einsum`'s `auto` contractor.\n\n  Args:\n    nodes: A collection of connected nodes.\n    output_edge_order: An optional list of edges.\n      Edges of the final node in `nodes_set`\n      are reordered into `output_edge_order`;\n      if final node has more than one edge,\n      `output_edge_order` must be provided.\n    memory_limit: Maximum number of elements in an array during contractions.\n    ignore_edge_order: An option to ignore the output edge order.\n\n  Returns:\n    Final node after full contraction.\n  \"\"\"\n\n  n = len(list(nodes))  #pytype thing\n  _nodes = nodes\n  if n <= 0:\n    raise ValueError(\"Cannot contract empty tensor network.\")\n  if n == 1:\n    if not ignore_edge_order:\n      if output_edge_order is None:\n        output_edge_order = list(\n            (get_all_edges(_nodes) - get_all_nondangling(_nodes)))\n        if len(output_edge_order) > 1:\n          raise ValueError(\"The final node after contraction has more than \"\n                           \"one dangling edge. In this case `output_edge_order` \"\n                           \"has to be provided.\")\n\n    edges = get_all_nondangling(_nodes)\n    if edges:\n      final_node = contract_parallel(edges.pop())\n    else:\n      final_node = list(_nodes)[0]\n    final_node.reorder_edges(output_edge_order)\n    if not ignore_edge_order:\n      final_node.reorder_edges(output_edge_order)\n    return final_node\n\n  if n < 5:\n    return optimal(nodes, output_edge_order, memory_limit, ignore_edge_order)\n  if n < 7:\n    return branch(nodes, output_edge_order, memory_limit, ignore_edge_order)\n  if n < 9:\n    return branch(nodes, output_edge_order, memory_limit, nbranch=2, ignore_edge_order=ignore_edge_order)\n  if n < 15:\n    return branch(nodes, output_edge_order, nbranch=1, ignore_edge_order=ignore_edge_order)\n  return greedy(nodes, output_edge_order, memory_limit, ignore_edge_order)\n\n\ndef custom(\n    nodes: Iterable[BaseNode],\n    optimizer: Any,\n    output_edge_order: Sequence[Edge] = None,\n    memory_limit: Optional[int] = None,\n    ignore_edge_order: bool = False) -> BaseNode:\n  \"\"\"Uses a custom path optimizer created by the user to calculate paths.\n\n  The custom path optimizer should inherit `opt_einsum`'s `PathOptimizer`.\n  For more details:\n    https://optimized-einsum.readthedocs.io/en/latest/custom_paths.html\n\n  Args:\n    nodes: an iterable of Nodes\n    output_edge_order: An optional list of edges.\n      Edges of the final node in `nodes_set`\n      are reordered into `output_edge_order`;\n      if final node has more than one edge,\n      output_edge_order` must be provided.\n    optimizer: A custom `opt_einsum.PathOptimizer` object.\n    memory_limit: Maximum number of elements in an array during contractions.\n    ignore_edge_order: An option to ignore the output edge order.\n\n  Returns:\n    Final node after full contraction.\n  \"\"\"\n  alg = functools.partial(optimizer, memory_limit=memory_limit)\n  return base(nodes, alg, output_edge_order, ignore_edge_order)\n", "path": "tensornetwork/contractors/opt_einsum_paths/path_contractors.py"}], "after_files": [{"content": "# pylint: disable=cyclic-import\n# Copyright 2019 The TensorNetwork Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"Contractors based on `opt_einsum`'s path algorithms.\"\"\"\n\nimport functools\nimport opt_einsum\n# pylint: disable=line-too-long\nfrom tensornetwork.network_operations import check_connected, get_all_edges, get_subgraph_dangling\n# pylint: disable=line-too-long\nfrom tensornetwork.network_components import get_all_nondangling, contract_parallel, contract_between\nfrom tensornetwork.network_components import Edge, BaseNode\nfrom tensornetwork.contractors.opt_einsum_paths import utils\nfrom typing import Any, Optional, Sequence, Iterable\n\n#TODO (martin): add return types of functions back once TensorNetwork is gone\n#               remove _base_network\n#               _base_nodes -> base\n\n\ndef base(nodes: Iterable[BaseNode],\n         algorithm: utils.Algorithm,\n         output_edge_order: Optional[Sequence[Edge]] = None,\n         ignore_edge_order: bool = False) -> BaseNode:\n  \"\"\"Base method for all `opt_einsum` contractors.\n\n  Args:\n    nodes: A collection of connected nodes.\n    algorithm: `opt_einsum` contraction method to use.\n    output_edge_order: An optional list of edges. Edges of the\n      final node in `nodes_set`\n      are reordered into `output_edge_order`;\n      if final node has more than one edge,\n      `output_edge_order` must be pronvided.\n    ignore_edge_order: An option to ignore the output edge\n      order.\n\n  Returns:\n    Final node after full contraction.\n  \"\"\"\n  nodes_set = set(nodes)\n  edges = get_all_edges(nodes_set)\n  #output edge order has to be determinded before any contraction\n  #(edges are refreshed after contractions)\n\n  if not ignore_edge_order:\n    if output_edge_order is None:\n      output_edge_order = list(get_subgraph_dangling(nodes))\n      if len(output_edge_order) > 1:\n        raise ValueError(\"The final node after contraction has more than \"\n                         \"one remaining edge. In this case `output_edge_order` \"\n                         \"has to be provided.\")\n\n    if set(output_edge_order) != get_subgraph_dangling(nodes):\n      raise ValueError(\n          \"output edges are not equal to the remaining \"\n          \"non-contracted edges of the final node.\"\n      )\n\n  for edge in edges:\n    if not edge.is_disabled:  #if its disabled we already contracted it\n      if edge.is_trace():\n        nodes_set.remove(edge.node1)\n        nodes_set.add(contract_parallel(edge))\n\n  if len(nodes_set) == 1:\n    # There's nothing to contract.\n    if ignore_edge_order:\n      return list(nodes_set)[0]\n    return list(nodes_set)[0].reorder_edges(output_edge_order)\n\n  # Then apply `opt_einsum`'s algorithm\n  path, nodes = utils.get_path(nodes_set, algorithm)\n  for a, b in path:\n    new_node = contract_between(nodes[a], nodes[b], allow_outer_product=True)\n    nodes.append(new_node)\n    nodes = utils.multi_remove(nodes, [a, b])\n\n  # if the final node has more than one edge,\n  # output_edge_order has to be specified\n  final_node = nodes[0]  # nodes were connected, we checked this\n  if not ignore_edge_order:\n    final_node.reorder_edges(output_edge_order)\n  return final_node\n\n\ndef optimal(\n    nodes: Iterable[BaseNode],\n    output_edge_order: Optional[Sequence[Edge]] = None,\n    memory_limit: Optional[int] = None,\n    ignore_edge_order: bool = False) -> BaseNode:\n  \"\"\"Optimal contraction order via `opt_einsum`.\n\n  This method will find the truly optimal contraction order via\n  `opt_einsum`'s depth first search algorithm. Since this search is\n  exhaustive, if your network is large (n>10), then the search may\n  take longer than just contracting in a suboptimal way.\n\n  Args:\n    nodes: an iterable of Nodes\n    output_edge_order: An optional list of edges.\n      Edges of the final node in `nodes_set`\n      are reordered into `output_edge_order`;\n      if final node has more than one edge,\n      `output_edge_order` must be provided.\n    memory_limit: Maximum number of elements in an array during contractions.\n    ignore_edge_order: An option to ignore the output edge order.\n\n  Returns:\n    The final node after full contraction.\n  \"\"\"\n  alg = functools.partial(opt_einsum.paths.optimal, memory_limit=memory_limit)\n  return base(nodes, alg, output_edge_order, ignore_edge_order)\n\n\ndef branch(nodes: Iterable[BaseNode],\n           output_edge_order: Optional[Sequence[Edge]] = None,\n           memory_limit: Optional[int] = None,\n           nbranch: Optional[int] = None,\n           ignore_edge_order: bool = False) -> BaseNode:\n  \"\"\"Branch contraction path via `opt_einsum`.\n\n  This method uses the DFS approach of `optimal` while sorting potential\n  contractions based on a heuristic cost, in order to reduce time spent\n  in exploring paths which are unlikely to be optimal.\n  For more details:\n    https://optimized-einsum.readthedocs.io/en/latest/branching_path.html\n\n  Args:\n    nodes: an iterable of Nodes\n    output_edge_order: An optional list of edges.\n      Edges of the final node in `nodes_set`\n       are reordered into `output_edge_order`;\n       if final node has more than one edge,\n       `output_edge_order` must be provided.\n    memory_limit: Maximum number of elements in an array during contractions.\n    nbranch: Number of best contractions to explore.\n      If None it explores all inner products starting with those that\n      have the best cost heuristic.\n    ignore_edge_order: An option to ignore the output edge order.\n\n  Returns:\n    The final node after full contraction.\n  \"\"\"\n  alg = functools.partial(\n      opt_einsum.paths.branch, memory_limit=memory_limit, nbranch=nbranch)\n  return base(nodes, alg, output_edge_order, ignore_edge_order)\n\n\ndef greedy(\n    nodes: Iterable[BaseNode],\n    output_edge_order: Optional[Sequence[Edge]] = None,\n    memory_limit: Optional[int] = None,\n    ignore_edge_order: bool = False) -> BaseNode:\n  \"\"\"Greedy contraction path via `opt_einsum`.\n\n  This provides a more efficient strategy than `optimal` for finding\n  contraction paths in large networks. First contracts pairs of tensors\n  by finding the pair with the lowest cost at each step. Then it performs\n  the outer products.\n  For more details:\n    https://optimized-einsum.readthedocs.io/en/latest/greedy_path.html\n\n  Args:\n    nodes: an iterable of Nodes\n    output_edge_order: An optional list of edges.\n      Edges of the final node in `nodes_set`\n      are reordered into `output_edge_order`;\n      if final node has more than one edge,\n      `output_edge_order` must be provided.\n    memory_limit: Maximum number of elements in an array during contractions.\n    ignore_edge_order: An option to ignore the output edge order.\n\n  Returns:\n    The final node after full contraction.\n  \"\"\"\n  alg = functools.partial(opt_einsum.paths.greedy, memory_limit=memory_limit)\n  return base(nodes, alg, output_edge_order, ignore_edge_order)\n\n\n# pylint: disable=too-many-return-statements\ndef auto(\n    nodes: Iterable[BaseNode],\n    output_edge_order: Optional[Sequence[Edge]] = None,\n    memory_limit: Optional[int] = None,\n    ignore_edge_order: bool = False) -> BaseNode:\n  \"\"\"Chooses one of the above algorithms according to network size.\n\n  Default behavior is based on `opt_einsum`'s `auto` contractor.\n\n  Args:\n    nodes: A collection of connected nodes.\n    output_edge_order: An optional list of edges.\n      Edges of the final node in `nodes_set`\n      are reordered into `output_edge_order`;\n      if final node has more than one edge,\n      `output_edge_order` must be provided.\n    memory_limit: Maximum number of elements in an array during contractions.\n    ignore_edge_order: An option to ignore the output edge order.\n\n  Returns:\n    Final node after full contraction.\n  \"\"\"\n\n  n = len(list(nodes))  #pytype thing\n  _nodes = nodes\n  if n <= 0:\n    raise ValueError(\"Cannot contract empty tensor network.\")\n  if n == 1:\n    if not ignore_edge_order:\n      if output_edge_order is None:\n        output_edge_order = list(\n            (get_all_edges(_nodes) - get_all_nondangling(_nodes)))\n        if len(output_edge_order) > 1:\n          raise ValueError(\"The final node after contraction has more than \"\n                           \"one dangling edge. In this case `output_edge_order` \"\n                           \"has to be provided.\")\n\n    edges = get_all_nondangling(_nodes)\n    if edges:\n      final_node = contract_parallel(edges.pop())\n    else:\n      final_node = list(_nodes)[0]\n    final_node.reorder_edges(output_edge_order)\n    if not ignore_edge_order:\n      final_node.reorder_edges(output_edge_order)\n    return final_node\n\n  if n < 5:\n    return optimal(nodes, output_edge_order, memory_limit, ignore_edge_order)\n  if n < 7:\n    return branch(nodes, output_edge_order, memory_limit, ignore_edge_order)\n  if n < 9:\n    return branch(nodes, output_edge_order, memory_limit, nbranch=2, ignore_edge_order=ignore_edge_order)\n  if n < 15:\n    return branch(nodes, output_edge_order, nbranch=1, ignore_edge_order=ignore_edge_order)\n  return greedy(nodes, output_edge_order, memory_limit, ignore_edge_order)\n\n\ndef custom(\n    nodes: Iterable[BaseNode],\n    optimizer: Any,\n    output_edge_order: Sequence[Edge] = None,\n    memory_limit: Optional[int] = None,\n    ignore_edge_order: bool = False) -> BaseNode:\n  \"\"\"Uses a custom path optimizer created by the user to calculate paths.\n\n  The custom path optimizer should inherit `opt_einsum`'s `PathOptimizer`.\n  For more details:\n    https://optimized-einsum.readthedocs.io/en/latest/custom_paths.html\n\n  Args:\n    nodes: an iterable of Nodes\n    output_edge_order: An optional list of edges.\n      Edges of the final node in `nodes_set`\n      are reordered into `output_edge_order`;\n      if final node has more than one edge,\n      output_edge_order` must be provided.\n    optimizer: A custom `opt_einsum.PathOptimizer` object.\n    memory_limit: Maximum number of elements in an array during contractions.\n    ignore_edge_order: An option to ignore the output edge order.\n\n  Returns:\n    Final node after full contraction.\n  \"\"\"\n  alg = functools.partial(optimizer, memory_limit=memory_limit)\n  return base(nodes, alg, output_edge_order, ignore_edge_order)\n", "path": "tensornetwork/contractors/opt_einsum_paths/path_contractors.py"}]}