We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
ncon throws an obscure error for a valid contraction order
Hey, I just noticed that the following code throws an obscure error:
```python 
d = 2
D1 = 4
D2= 5
a = tf.random_uniform(shape = [D1,D2,d,d])
b = tf.random_uniform(shape = [D1,d,D1])
c = tf.random_uniform(shape = [D2,d,D2])
f = tf.random_uniform(shape = [d,d,d,d])

tn.ncon([a, b, c, f], [[1,3,5,4], [1,2,-1], [3,4,-2], [5,-4,2,-3]])
```
The order above is not chosen smartly, but `ncon` should either throw an interpretable error, or do the contraction

--- 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/ncon_interface.py`
Content:
```
1 # Copyright 2019 The TensorNetwork Authors
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #      http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 """NCON interface to TensorNetwork."""
15 
16 from __future__ import absolute_import
17 from __future__ import division
18 from __future__ import print_function
19 from typing import Any, Sequence, List, Optional, Union, Text, Tuple, Dict, Any
20 from tensornetwork import network
21 from tensornetwork import network_components
22 
23 Tensor = Any
24 
25 
26 def ncon(tensors: Sequence[Tensor],
27          network_structure: Sequence[Sequence],
28          con_order: Optional[Sequence] = None,
29          out_order: Optional[Sequence] = None) -> Tensor:
30   r"""Contracts a list of tensors according to a tensor network specification.
31 
32     The network is provided as a list of lists, one for each
33     tensor, specifying labels for the edges connected to that tensor.
34 
35     If a contraction order `con_order` and an output order `out_order`
36     are both provided, the edge labels can be anything.
37     Otherwise (`con_order == None or out_order == None`), the edge labels 
38     must be nonzero integers and edges will be contracted in ascending order.
39     Negative integers denote the (dangling) indices of the output tensor,
40     which will be in descending order, e.g. [-1,-2,-3,...].
41 
42     For example, matrix multiplication:
43 
44     ```python
45     A = np.array([[1.0, 2.0], [3.0, 4.0]])
46     B = np.array([[1.0, 1.0], [0.0, 1.0]])
47     ncon([A,B], [(-1, 1), (1, -2)])
48     ```
49 
50     Matrix trace:
51 
52     ```python
53     A = np.array([[1.0, 2.0], [3.0, 4.0]])
54     ncon([A], [(1, 1)]) # 5.0
55     ```
56 
57     Note: The reason `0` is not allowed as an edge label without manually
58     specifying the contraction order is to maintain compatibility with the
59     [original NCON implementation](https://arxiv.org/abs/1402.0939). However,
60     the use of `0` in `con_order` to denote outer products is not (currently) 
61     supported in this implementation.
62 
63     Args:
64       tensors: List of `Tensor`s.
65       network_structure: List of lists specifying the tensor network
66           structure.
67       con_order: List of edge labels specifying the contraction order.
68       out_order: List of edge labels specifying the output order.
69 
70     Returns:
71       A `Tensor` resulting from the contraction of the tensor network.
72     """
73   tn, con_edges, out_edges = ncon_network(
74       tensors, network_structure, con_order=con_order, out_order=out_order)
75 
76   # Contract assuming all edges connecting a given pair of nodes are adjacent
77   # in con_order. If this is not the case, the contraction is sub-optimal
78   # so we throw an exception.
79   prev_nodes = []
80   while con_edges:
81     e = con_edges.pop(0)  # pop so that older nodes can be deallocated
82     nodes = e.get_nodes()
83 
84     nodes_set = set(nodes)
85     if nodes_set != set(prev_nodes):
86       if not nodes_set.issubset(tn.nodes_set):
87         # the node pair was already contracted
88         raise ValueError("Edge '{}' is not adjacent to other edges connecting "
89                          "'{}' and '{}' in the contraction order.".format(
90                              e, nodes[0], nodes[1]))
91       if not con_edges and len(tn.nodes_set) == 2:
92         # If this already produces the final output, order the edges 
93         # here to avoid transposes in some cases.
94         tn.contract_between(
95             *nodes,
96             name="con({},{})".format(*nodes),
97             output_edge_order=out_edges)
98       else:
99         tn.contract_between(*nodes, name="con({},{})".format(*nodes))
100       prev_nodes = nodes
101 
102   # TODO: More efficient ordering of products based on out_edges
103   res_node = tn.outer_product_final_nodes(out_edges)
104 
105   return res_node.tensor
106 
107 
108 def ncon_network(
109     tensors: Sequence[Tensor],
110     network_structure: Sequence[Sequence],
111     con_order: Optional[Sequence] = None,
112     out_order: Optional[Sequence] = None) -> Tuple[network.TensorNetwork, List[
113         network_components.Edge], List[network_components.Edge]]:
114   r"""Creates a TensorNetwork from a list of tensors according to `network`.
115 
116     The network is provided as a list of lists, one for each
117     tensor, specifying labels for the edges connected to that tensor.
118 
119     If a contraction order `con_order` and an output order `out_order`
120     are both provided, the edge labels can be anything.
121     Otherwise (`con_order == None or out_order == None`), the edge labels 
122     must be integers and edges will be contracted in ascending order.
123     Negative integers denote the (dangling) indices of the output tensor,
124     which will be in descending order, e.g. [-1,-2,-3,...].
125 
126     This is used internally by `ncon()`.
127 
128     Args:
129       tensors: List of `Tensor`s.
130       network_structure: List of lists specifying the tensor network.
131       con_order: List of edge labels specifying the contraction order.
132       out_order: List of edge labels specifying the output order.
133 
134     Returns:
135       net: `TensorNetwork` with the structure given by `network`.
136       con_edges: List of internal `Edge` objects in contraction order.
137       out_edges: List of dangling `Edge` objects in output order.
138   """
139   if len(tensors) != len(network_structure):
140     raise ValueError('len(tensors) != len(network_structure)')
141 
142   tn, edges = _build_network(tensors, network_structure)
143 
144   if con_order is None:
145     try:
146       con_order = sorted((k for k in edges if k >= 0))
147       if con_order and con_order[0] == 0:
148         raise ValueError("'0' is not a valid edge label when the "
149           "contraction order is not specified separately.")
150     except TypeError:
151       raise ValueError("Non-integer edge label(s): {}".format(
152           list(edges.keys())))
153   else:
154     if len(con_order) != len(set(con_order)):
155       raise ValueError("Duplicate labels in con_order: {}".format(con_order))
156 
157   if out_order is None:
158     try:
159       out_order = sorted((k for k in edges if k < 0), reverse=True)
160     except TypeError:
161       raise ValueError("Non-integer edge label(s): {}".format(
162           list(edges.keys())))
163   else:
164     if len(out_order) != len(set(out_order)):
165       raise ValueError("Duplicate labels in out_order: {}".format(out_order))
166 
167   try:
168     con_edges = [edges[k] for k in con_order]
169     out_edges = [edges[k] for k in out_order]
170   except KeyError as err:
171     raise ValueError("Order contained an unknown edge label: {}".format(
172         err.args[0]))
173 
174   if len(con_edges) + len(out_edges) != len(edges):
175     raise ValueError(
176         "Edges {} were not included in the contraction and output "
177         "ordering.".format(
178             list(set(edges.keys()) - set(con_order) - set(out_order))))
179 
180   for e in con_edges:
181     if e.is_dangling():
182       raise ValueError(
183           "Contraction edge {} appears only once in the network.".format(
184               str(e)))
185 
186   for e in out_edges:
187     if not e.is_dangling():
188       raise ValueError(
189           "Output edge {} appears more than once in the network.".format(
190               str(e)))
191 
192   return tn, con_edges, out_edges
193 
194 
195 def _build_network(
196     tensors: Sequence[Tensor], network_structure: Sequence[Sequence]
197 ) -> Tuple[network.TensorNetwork, Dict[Any, network_components.Edge]]:
198   tn = network.TensorNetwork()
199   nodes = []
200   edges = {}
201   for i, (tensor, edge_lbls) in enumerate(zip(tensors, network_structure)):
202     if len(tensor.shape) != len(edge_lbls):
203       raise ValueError(
204           "Incorrect number of edge labels specified tensor {}".format(i))
205 
206     node = tn.add_node(tensor, name="tensor_{}".format(i))
207     nodes.append(node)
208 
209     for (axis_num, edge_lbl) in enumerate(edge_lbls):
210       if edge_lbl not in edges:
211         e = node[axis_num]
212         e.set_name(str(edge_lbl))
213         edges[edge_lbl] = e
214       else:
215         # This will raise an error if the edges are not dangling.
216         e = tn.connect(edges[edge_lbl], node[axis_num], name=str(edge_lbl))
217         edges[edge_lbl] = e
218   return tn, edges
219
```
--- 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/ncon_interface.py b/tensornetwork/ncon_interface.py
--- a/tensornetwork/ncon_interface.py
+++ b/tensornetwork/ncon_interface.py
@@ -16,6 +16,7 @@
 from __future__ import absolute_import
 from __future__ import division
 from __future__ import print_function
+import warnings
 from typing import Any, Sequence, List, Optional, Union, Text, Tuple, Dict, Any
 from tensornetwork import network
 from tensornetwork import network_components
@@ -73,31 +74,46 @@
   tn, con_edges, out_edges = ncon_network(
       tensors, network_structure, con_order=con_order, out_order=out_order)
 
-  # Contract assuming all edges connecting a given pair of nodes are adjacent
-  # in con_order. If this is not the case, the contraction is sub-optimal
-  # so we throw an exception.
-  prev_nodes = []
+  # Reverse the list so we can pop from the end: O(1).
+  con_edges = con_edges[::-1]
   while con_edges:
-    e = con_edges.pop(0)  # pop so that older nodes can be deallocated
-    nodes = e.get_nodes()
-
-    nodes_set = set(nodes)
-    if nodes_set != set(prev_nodes):
-      if not nodes_set.issubset(tn.nodes_set):
-        # the node pair was already contracted
-        raise ValueError("Edge '{}' is not adjacent to other edges connecting "
-                         "'{}' and '{}' in the contraction order.".format(
-                             e, nodes[0], nodes[1]))
-      if not con_edges and len(tn.nodes_set) == 2:
-        # If this already produces the final output, order the edges 
-        # here to avoid transposes in some cases.
-        tn.contract_between(
-            *nodes,
-            name="con({},{})".format(*nodes),
-            output_edge_order=out_edges)
+    nodes_to_contract = con_edges[-1].get_nodes()
+    edges_to_contract = tn.get_shared_edges(*nodes_to_contract)
+
+    # Eat up all parallel edges that are adjacent in the ordering.
+    adjacent_parallel_edges = set()
+    for edge in reversed(con_edges):
+      if edge in edges_to_contract:
+        adjacent_parallel_edges.add(edge)
       else:
-        tn.contract_between(*nodes, name="con({},{})".format(*nodes))
-      prev_nodes = nodes
+        break
+    con_edges = con_edges[:-len(adjacent_parallel_edges)]
+
+    # In an optimal ordering, all edges connecting a given pair of nodes are
+    # adjacent in con_order. If this is not the case, warn the user.
+    leftovers = edges_to_contract - adjacent_parallel_edges
+    if leftovers:
+      warnings.warn(
+        "Suboptimal ordering detected. Edges {} are not adjacent in the "
+        "contraction order to edges {}, connecting nodes {}. Deviating from "
+        "the specified ordering!".format(
+          list(map(str, leftovers)),
+          list(map(str, adjacent_parallel_edges)),
+          list(map(str, nodes_to_contract)))
+        )
+      con_edges = [e for e in con_edges if e not in edges_to_contract]
+
+    if set(nodes_to_contract) == tn.nodes_set:
+      # If this already produces the final output, order the edges
+      # here to avoid transposes in some cases.
+      tn.contract_between(
+        *nodes_to_contract,
+        name="con({},{})".format(*nodes_to_contract),
+        output_edge_order=out_edges)
+    else:
+      tn.contract_between(
+        *nodes_to_contract,
+        name="con({},{})".format(*nodes_to_contract))
 
   # TODO: More efficient ordering of products based on out_edges
   res_node = tn.outer_product_final_nodes(out_edges)

{"golden_diff": "diff --git a/tensornetwork/ncon_interface.py b/tensornetwork/ncon_interface.py\n--- a/tensornetwork/ncon_interface.py\n+++ b/tensornetwork/ncon_interface.py\n@@ -16,6 +16,7 @@\n from __future__ import absolute_import\n from __future__ import division\n from __future__ import print_function\n+import warnings\n from typing import Any, Sequence, List, Optional, Union, Text, Tuple, Dict, Any\n from tensornetwork import network\n from tensornetwork import network_components\n@@ -73,31 +74,46 @@\n   tn, con_edges, out_edges = ncon_network(\n       tensors, network_structure, con_order=con_order, out_order=out_order)\n \n-  # Contract assuming all edges connecting a given pair of nodes are adjacent\n-  # in con_order. If this is not the case, the contraction is sub-optimal\n-  # so we throw an exception.\n-  prev_nodes = []\n+  # Reverse the list so we can pop from the end: O(1).\n+  con_edges = con_edges[::-1]\n   while con_edges:\n-    e = con_edges.pop(0)  # pop so that older nodes can be deallocated\n-    nodes = e.get_nodes()\n-\n-    nodes_set = set(nodes)\n-    if nodes_set != set(prev_nodes):\n-      if not nodes_set.issubset(tn.nodes_set):\n-        # the node pair was already contracted\n-        raise ValueError(\"Edge '{}' is not adjacent to other edges connecting \"\n-                         \"'{}' and '{}' in the contraction order.\".format(\n-                             e, nodes[0], nodes[1]))\n-      if not con_edges and len(tn.nodes_set) == 2:\n-        # If this already produces the final output, order the edges \n-        # here to avoid transposes in some cases.\n-        tn.contract_between(\n-            *nodes,\n-            name=\"con({},{})\".format(*nodes),\n-            output_edge_order=out_edges)\n+    nodes_to_contract = con_edges[-1].get_nodes()\n+    edges_to_contract = tn.get_shared_edges(*nodes_to_contract)\n+\n+    # Eat up all parallel edges that are adjacent in the ordering.\n+    adjacent_parallel_edges = set()\n+    for edge in reversed(con_edges):\n+      if edge in edges_to_contract:\n+        adjacent_parallel_edges.add(edge)\n       else:\n-        tn.contract_between(*nodes, name=\"con({},{})\".format(*nodes))\n-      prev_nodes = nodes\n+        break\n+    con_edges = con_edges[:-len(adjacent_parallel_edges)]\n+\n+    # In an optimal ordering, all edges connecting a given pair of nodes are\n+    # adjacent in con_order. If this is not the case, warn the user.\n+    leftovers = edges_to_contract - adjacent_parallel_edges\n+    if leftovers:\n+      warnings.warn(\n+        \"Suboptimal ordering detected. Edges {} are not adjacent in the \"\n+        \"contraction order to edges {}, connecting nodes {}. Deviating from \"\n+        \"the specified ordering!\".format(\n+          list(map(str, leftovers)),\n+          list(map(str, adjacent_parallel_edges)),\n+          list(map(str, nodes_to_contract)))\n+        )\n+      con_edges = [e for e in con_edges if e not in edges_to_contract]\n+\n+    if set(nodes_to_contract) == tn.nodes_set:\n+      # If this already produces the final output, order the edges\n+      # here to avoid transposes in some cases.\n+      tn.contract_between(\n+        *nodes_to_contract,\n+        name=\"con({},{})\".format(*nodes_to_contract),\n+        output_edge_order=out_edges)\n+    else:\n+      tn.contract_between(\n+        *nodes_to_contract,\n+        name=\"con({},{})\".format(*nodes_to_contract))\n \n   # TODO: More efficient ordering of products based on out_edges\n   res_node = tn.outer_product_final_nodes(out_edges)\n", "issue": "ncon throws an obscure error for a valid contraction order\nHey, I just noticed that the following code throws an obscure error:\r\n```python \r\nd = 2\r\nD1 = 4\r\nD2= 5\r\na = tf.random_uniform(shape = [D1,D2,d,d])\r\nb = tf.random_uniform(shape = [D1,d,D1])\r\nc = tf.random_uniform(shape = [D2,d,D2])\r\nf = tf.random_uniform(shape = [d,d,d,d])\r\n\r\ntn.ncon([a, b, c, f], [[1,3,5,4], [1,2,-1], [3,4,-2], [5,-4,2,-3]])\r\n```\r\nThe order above is not chosen smartly, but `ncon` should either throw an interpretable error, or do the contraction\n", "before_files": [{"content": "# 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\"\"\"NCON interface to TensorNetwork.\"\"\"\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nfrom typing import Any, Sequence, List, Optional, Union, Text, Tuple, Dict, Any\nfrom tensornetwork import network\nfrom tensornetwork import network_components\n\nTensor = Any\n\n\ndef ncon(tensors: Sequence[Tensor],\n         network_structure: Sequence[Sequence],\n         con_order: Optional[Sequence] = None,\n         out_order: Optional[Sequence] = None) -> Tensor:\n  r\"\"\"Contracts a list of tensors according to a tensor network specification.\n\n    The network is provided as a list of lists, one for each\n    tensor, specifying labels for the edges connected to that tensor.\n\n    If a contraction order `con_order` and an output order `out_order`\n    are both provided, the edge labels can be anything.\n    Otherwise (`con_order == None or out_order == None`), the edge labels \n    must be nonzero integers and edges will be contracted in ascending order.\n    Negative integers denote the (dangling) indices of the output tensor,\n    which will be in descending order, e.g. [-1,-2,-3,...].\n\n    For example, matrix multiplication:\n\n    ```python\n    A = np.array([[1.0, 2.0], [3.0, 4.0]])\n    B = np.array([[1.0, 1.0], [0.0, 1.0]])\n    ncon([A,B], [(-1, 1), (1, -2)])\n    ```\n\n    Matrix trace:\n\n    ```python\n    A = np.array([[1.0, 2.0], [3.0, 4.0]])\n    ncon([A], [(1, 1)]) # 5.0\n    ```\n\n    Note: The reason `0` is not allowed as an edge label without manually\n    specifying the contraction order is to maintain compatibility with the\n    [original NCON implementation](https://arxiv.org/abs/1402.0939). However,\n    the use of `0` in `con_order` to denote outer products is not (currently) \n    supported in this implementation.\n\n    Args:\n      tensors: List of `Tensor`s.\n      network_structure: List of lists specifying the tensor network\n          structure.\n      con_order: List of edge labels specifying the contraction order.\n      out_order: List of edge labels specifying the output order.\n\n    Returns:\n      A `Tensor` resulting from the contraction of the tensor network.\n    \"\"\"\n  tn, con_edges, out_edges = ncon_network(\n      tensors, network_structure, con_order=con_order, out_order=out_order)\n\n  # Contract assuming all edges connecting a given pair of nodes are adjacent\n  # in con_order. If this is not the case, the contraction is sub-optimal\n  # so we throw an exception.\n  prev_nodes = []\n  while con_edges:\n    e = con_edges.pop(0)  # pop so that older nodes can be deallocated\n    nodes = e.get_nodes()\n\n    nodes_set = set(nodes)\n    if nodes_set != set(prev_nodes):\n      if not nodes_set.issubset(tn.nodes_set):\n        # the node pair was already contracted\n        raise ValueError(\"Edge '{}' is not adjacent to other edges connecting \"\n                         \"'{}' and '{}' in the contraction order.\".format(\n                             e, nodes[0], nodes[1]))\n      if not con_edges and len(tn.nodes_set) == 2:\n        # If this already produces the final output, order the edges \n        # here to avoid transposes in some cases.\n        tn.contract_between(\n            *nodes,\n            name=\"con({},{})\".format(*nodes),\n            output_edge_order=out_edges)\n      else:\n        tn.contract_between(*nodes, name=\"con({},{})\".format(*nodes))\n      prev_nodes = nodes\n\n  # TODO: More efficient ordering of products based on out_edges\n  res_node = tn.outer_product_final_nodes(out_edges)\n\n  return res_node.tensor\n\n\ndef ncon_network(\n    tensors: Sequence[Tensor],\n    network_structure: Sequence[Sequence],\n    con_order: Optional[Sequence] = None,\n    out_order: Optional[Sequence] = None) -> Tuple[network.TensorNetwork, List[\n        network_components.Edge], List[network_components.Edge]]:\n  r\"\"\"Creates a TensorNetwork from a list of tensors according to `network`.\n\n    The network is provided as a list of lists, one for each\n    tensor, specifying labels for the edges connected to that tensor.\n\n    If a contraction order `con_order` and an output order `out_order`\n    are both provided, the edge labels can be anything.\n    Otherwise (`con_order == None or out_order == None`), the edge labels \n    must be integers and edges will be contracted in ascending order.\n    Negative integers denote the (dangling) indices of the output tensor,\n    which will be in descending order, e.g. [-1,-2,-3,...].\n\n    This is used internally by `ncon()`.\n\n    Args:\n      tensors: List of `Tensor`s.\n      network_structure: List of lists specifying the tensor network.\n      con_order: List of edge labels specifying the contraction order.\n      out_order: List of edge labels specifying the output order.\n\n    Returns:\n      net: `TensorNetwork` with the structure given by `network`.\n      con_edges: List of internal `Edge` objects in contraction order.\n      out_edges: List of dangling `Edge` objects in output order.\n  \"\"\"\n  if len(tensors) != len(network_structure):\n    raise ValueError('len(tensors) != len(network_structure)')\n\n  tn, edges = _build_network(tensors, network_structure)\n\n  if con_order is None:\n    try:\n      con_order = sorted((k for k in edges if k >= 0))\n      if con_order and con_order[0] == 0:\n        raise ValueError(\"'0' is not a valid edge label when the \"\n          \"contraction order is not specified separately.\")\n    except TypeError:\n      raise ValueError(\"Non-integer edge label(s): {}\".format(\n          list(edges.keys())))\n  else:\n    if len(con_order) != len(set(con_order)):\n      raise ValueError(\"Duplicate labels in con_order: {}\".format(con_order))\n\n  if out_order is None:\n    try:\n      out_order = sorted((k for k in edges if k < 0), reverse=True)\n    except TypeError:\n      raise ValueError(\"Non-integer edge label(s): {}\".format(\n          list(edges.keys())))\n  else:\n    if len(out_order) != len(set(out_order)):\n      raise ValueError(\"Duplicate labels in out_order: {}\".format(out_order))\n\n  try:\n    con_edges = [edges[k] for k in con_order]\n    out_edges = [edges[k] for k in out_order]\n  except KeyError as err:\n    raise ValueError(\"Order contained an unknown edge label: {}\".format(\n        err.args[0]))\n\n  if len(con_edges) + len(out_edges) != len(edges):\n    raise ValueError(\n        \"Edges {} were not included in the contraction and output \"\n        \"ordering.\".format(\n            list(set(edges.keys()) - set(con_order) - set(out_order))))\n\n  for e in con_edges:\n    if e.is_dangling():\n      raise ValueError(\n          \"Contraction edge {} appears only once in the network.\".format(\n              str(e)))\n\n  for e in out_edges:\n    if not e.is_dangling():\n      raise ValueError(\n          \"Output edge {} appears more than once in the network.\".format(\n              str(e)))\n\n  return tn, con_edges, out_edges\n\n\ndef _build_network(\n    tensors: Sequence[Tensor], network_structure: Sequence[Sequence]\n) -> Tuple[network.TensorNetwork, Dict[Any, network_components.Edge]]:\n  tn = network.TensorNetwork()\n  nodes = []\n  edges = {}\n  for i, (tensor, edge_lbls) in enumerate(zip(tensors, network_structure)):\n    if len(tensor.shape) != len(edge_lbls):\n      raise ValueError(\n          \"Incorrect number of edge labels specified tensor {}\".format(i))\n\n    node = tn.add_node(tensor, name=\"tensor_{}\".format(i))\n    nodes.append(node)\n\n    for (axis_num, edge_lbl) in enumerate(edge_lbls):\n      if edge_lbl not in edges:\n        e = node[axis_num]\n        e.set_name(str(edge_lbl))\n        edges[edge_lbl] = e\n      else:\n        # This will raise an error if the edges are not dangling.\n        e = tn.connect(edges[edge_lbl], node[axis_num], name=str(edge_lbl))\n        edges[edge_lbl] = e\n  return tn, edges\n", "path": "tensornetwork/ncon_interface.py"}], "after_files": [{"content": "# 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\"\"\"NCON interface to TensorNetwork.\"\"\"\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\nimport warnings\nfrom typing import Any, Sequence, List, Optional, Union, Text, Tuple, Dict, Any\nfrom tensornetwork import network\nfrom tensornetwork import network_components\n\nTensor = Any\n\n\ndef ncon(tensors: Sequence[Tensor],\n         network_structure: Sequence[Sequence],\n         con_order: Optional[Sequence] = None,\n         out_order: Optional[Sequence] = None) -> Tensor:\n  r\"\"\"Contracts a list of tensors according to a tensor network specification.\n\n    The network is provided as a list of lists, one for each\n    tensor, specifying labels for the edges connected to that tensor.\n\n    If a contraction order `con_order` and an output order `out_order`\n    are both provided, the edge labels can be anything.\n    Otherwise (`con_order == None or out_order == None`), the edge labels \n    must be nonzero integers and edges will be contracted in ascending order.\n    Negative integers denote the (dangling) indices of the output tensor,\n    which will be in descending order, e.g. [-1,-2,-3,...].\n\n    For example, matrix multiplication:\n\n    ```python\n    A = np.array([[1.0, 2.0], [3.0, 4.0]])\n    B = np.array([[1.0, 1.0], [0.0, 1.0]])\n    ncon([A,B], [(-1, 1), (1, -2)])\n    ```\n\n    Matrix trace:\n\n    ```python\n    A = np.array([[1.0, 2.0], [3.0, 4.0]])\n    ncon([A], [(1, 1)]) # 5.0\n    ```\n\n    Note: The reason `0` is not allowed as an edge label without manually\n    specifying the contraction order is to maintain compatibility with the\n    [original NCON implementation](https://arxiv.org/abs/1402.0939). However,\n    the use of `0` in `con_order` to denote outer products is not (currently) \n    supported in this implementation.\n\n    Args:\n      tensors: List of `Tensor`s.\n      network_structure: List of lists specifying the tensor network\n          structure.\n      con_order: List of edge labels specifying the contraction order.\n      out_order: List of edge labels specifying the output order.\n\n    Returns:\n      A `Tensor` resulting from the contraction of the tensor network.\n    \"\"\"\n  tn, con_edges, out_edges = ncon_network(\n      tensors, network_structure, con_order=con_order, out_order=out_order)\n\n  # Reverse the list so we can pop from the end: O(1).\n  con_edges = con_edges[::-1]\n  while con_edges:\n    nodes_to_contract = con_edges[-1].get_nodes()\n    edges_to_contract = tn.get_shared_edges(*nodes_to_contract)\n\n    # Eat up all parallel edges that are adjacent in the ordering.\n    adjacent_parallel_edges = set()\n    for edge in reversed(con_edges):\n      if edge in edges_to_contract:\n        adjacent_parallel_edges.add(edge)\n      else:\n        break\n    con_edges = con_edges[:-len(adjacent_parallel_edges)]\n\n    # In an optimal ordering, all edges connecting a given pair of nodes are\n    # adjacent in con_order. If this is not the case, warn the user.\n    leftovers = edges_to_contract - adjacent_parallel_edges\n    if leftovers:\n      warnings.warn(\n        \"Suboptimal ordering detected. Edges {} are not adjacent in the \"\n        \"contraction order to edges {}, connecting nodes {}. Deviating from \"\n        \"the specified ordering!\".format(\n          list(map(str, leftovers)),\n          list(map(str, adjacent_parallel_edges)),\n          list(map(str, nodes_to_contract)))\n        )\n      con_edges = [e for e in con_edges if e not in edges_to_contract]\n\n    if set(nodes_to_contract) == tn.nodes_set:\n      # If this already produces the final output, order the edges\n      # here to avoid transposes in some cases.\n      tn.contract_between(\n        *nodes_to_contract,\n        name=\"con({},{})\".format(*nodes_to_contract),\n        output_edge_order=out_edges)\n    else:\n      tn.contract_between(\n        *nodes_to_contract,\n        name=\"con({},{})\".format(*nodes_to_contract))\n\n  # TODO: More efficient ordering of products based on out_edges\n  res_node = tn.outer_product_final_nodes(out_edges)\n\n  return res_node.tensor\n\n\ndef ncon_network(\n    tensors: Sequence[Tensor],\n    network_structure: Sequence[Sequence],\n    con_order: Optional[Sequence] = None,\n    out_order: Optional[Sequence] = None) -> Tuple[network.TensorNetwork, List[\n        network_components.Edge], List[network_components.Edge]]:\n  r\"\"\"Creates a TensorNetwork from a list of tensors according to `network`.\n\n    The network is provided as a list of lists, one for each\n    tensor, specifying labels for the edges connected to that tensor.\n\n    If a contraction order `con_order` and an output order `out_order`\n    are both provided, the edge labels can be anything.\n    Otherwise (`con_order == None or out_order == None`), the edge labels \n    must be integers and edges will be contracted in ascending order.\n    Negative integers denote the (dangling) indices of the output tensor,\n    which will be in descending order, e.g. [-1,-2,-3,...].\n\n    This is used internally by `ncon()`.\n\n    Args:\n      tensors: List of `Tensor`s.\n      network_structure: List of lists specifying the tensor network.\n      con_order: List of edge labels specifying the contraction order.\n      out_order: List of edge labels specifying the output order.\n\n    Returns:\n      net: `TensorNetwork` with the structure given by `network`.\n      con_edges: List of internal `Edge` objects in contraction order.\n      out_edges: List of dangling `Edge` objects in output order.\n  \"\"\"\n  if len(tensors) != len(network_structure):\n    raise ValueError('len(tensors) != len(network_structure)')\n\n  tn, edges = _build_network(tensors, network_structure)\n\n  if con_order is None:\n    try:\n      con_order = sorted((k for k in edges if k >= 0))\n      if con_order and con_order[0] == 0:\n        raise ValueError(\"'0' is not a valid edge label when the \"\n          \"contraction order is not specified separately.\")\n    except TypeError:\n      raise ValueError(\"Non-integer edge label(s): {}\".format(\n          list(edges.keys())))\n  else:\n    if len(con_order) != len(set(con_order)):\n      raise ValueError(\"Duplicate labels in con_order: {}\".format(con_order))\n\n  if out_order is None:\n    try:\n      out_order = sorted((k for k in edges if k < 0), reverse=True)\n    except TypeError:\n      raise ValueError(\"Non-integer edge label(s): {}\".format(\n          list(edges.keys())))\n  else:\n    if len(out_order) != len(set(out_order)):\n      raise ValueError(\"Duplicate labels in out_order: {}\".format(out_order))\n\n  try:\n    con_edges = [edges[k] for k in con_order]\n    out_edges = [edges[k] for k in out_order]\n  except KeyError as err:\n    raise ValueError(\"Order contained an unknown edge label: {}\".format(\n        err.args[0]))\n\n  if len(con_edges) + len(out_edges) != len(edges):\n    raise ValueError(\n        \"Edges {} were not included in the contraction and output \"\n        \"ordering.\".format(\n            list(set(edges.keys()) - set(con_order) - set(out_order))))\n\n  for e in con_edges:\n    if e.is_dangling():\n      raise ValueError(\n          \"Contraction edge {} appears only once in the network.\".format(\n              str(e)))\n\n  for e in out_edges:\n    if not e.is_dangling():\n      raise ValueError(\n          \"Output edge {} appears more than once in the network.\".format(\n              str(e)))\n\n  return tn, con_edges, out_edges\n\n\ndef _build_network(\n    tensors: Sequence[Tensor], network_structure: Sequence[Sequence]\n) -> Tuple[network.TensorNetwork, Dict[Any, network_components.Edge]]:\n  tn = network.TensorNetwork()\n  nodes = []\n  edges = {}\n  for i, (tensor, edge_lbls) in enumerate(zip(tensors, network_structure)):\n    if len(tensor.shape) != len(edge_lbls):\n      raise ValueError(\n          \"Incorrect number of edge labels specified tensor {}\".format(i))\n\n    node = tn.add_node(tensor, name=\"tensor_{}\".format(i))\n    nodes.append(node)\n\n    for (axis_num, edge_lbl) in enumerate(edge_lbls):\n      if edge_lbl not in edges:\n        e = node[axis_num]\n        e.set_name(str(edge_lbl))\n        edges[edge_lbl] = e\n      else:\n        # This will raise an error if the edges are not dangling.\n        e = tn.connect(edges[edge_lbl], node[axis_num], name=str(edge_lbl))\n        edges[edge_lbl] = e\n  return tn, edges\n", "path": "tensornetwork/ncon_interface.py"}]}