We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Einsum support?
Should we extend our API to support einsum equations? It could potentially make connecting nodes much less verbose. However, I question whether anyone would want to use `tn.einsum` over say `np.einsum`. Perhaps we could support only doing the left side of the equation?

--- 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/__init__.py`
Content:
```
1 from tensornetwork.network_components import (AbstractNode, CopyNode, Edge,
2                                               Node, NodeCollection)
3 from tensornetwork.network_operations import (
4     check_connected, check_correct, contract_trace_edges, copy, get_all_edges,
5     get_all_nodes, get_neighbors, get_subgraph_dangling, reachable,
6     reduced_density, remove_node, replicate_nodes, split_node,
7     split_node_full_svd, split_node_qr, split_node_rq, switch_backend)
8 
9 from tensornetwork.tensor import Tensor
10 from tensornetwork.linalg.initialization import (
11     eye,
12     ones,
13     randn,
14     random_uniform,
15     zeros
16     )
17 
18 from tensornetwork.linalg.linalg import norm, qr, svd
19 
20 #pylint: disable=redefined-builtin
21 from tensornetwork.linalg.operations import (
22     tensordot,
23     reshape,
24     transpose,
25     take_slice,
26     shape,
27     sqrt,
28     outer,
29     einsum,
30     conj,
31     hconj,
32     sin,
33     cos,
34     exp,
35     log,
36     diagonal,
37     diagflat,
38     trace,
39     sign,
40     abs,
41     kron,
42     pivot
43     )
44 
45 from tensornetwork.backends.decorators import jit
46 
47 from tensornetwork.network_components import (
48     contract, contract_between, contract_copy_node, contract_parallel,
49     flatten_all_edges, flatten_edges, flatten_edges_between,
50     get_all_nondangling, get_all_dangling, get_parallel_edges, get_shared_edges,
51     outer_product, outer_product_final_nodes, slice_edge, split_edge)
52 from tensornetwork.backends.abstract_backend import AbstractBackend
53 from tensornetwork.network_components import connect, disconnect
54 from tensornetwork.ncon_interface import ncon
55 from tensornetwork.version import __version__
56 from tensornetwork.visualization.graphviz import to_graphviz
57 from tensornetwork import contractors
58 from tensornetwork.utils import load_nodes, save_nodes
59 from tensornetwork.matrixproductstates.infinite_mps import InfiniteMPS
60 from tensornetwork.matrixproductstates.finite_mps import FiniteMPS
61 from tensornetwork.matrixproductstates.dmrg import FiniteDMRG
62 from tensornetwork.matrixproductstates.mpo import FiniteTFI, FiniteXXZ
63 from tensornetwork.backend_contextmanager import DefaultBackend
64 from tensornetwork.backend_contextmanager import set_default_backend
65 from tensornetwork import block_sparse
66 from tensornetwork.block_sparse.blocksparsetensor import BlockSparseTensor
67 from tensornetwork.block_sparse.blocksparsetensor import ChargeArray
68 from tensornetwork.block_sparse.index import Index
69 from tensornetwork.block_sparse.charge import U1Charge, BaseCharge, Z2Charge
70 from tensornetwork.block_sparse.charge import ZNCharge
71
```

Path: `tensornetwork/utils.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 
15 import h5py
16 from tensornetwork.component_factory import get_component
17 from tensornetwork.network_components import Edge, AbstractNode
18 from tensornetwork.network_operations import reachable, get_all_edges
19 from typing import List, Union, BinaryIO
20 import numpy as np
21 string_type = h5py.special_dtype(vlen=str)
22 
23 
24 def save_nodes(nodes: List[AbstractNode], path: Union[str, BinaryIO]) -> None:
25   """Save an iterable of nodes into hdf5 format.
26 
27   Args:
28     nodes: An iterable of connected nodes. All nodes have to connect within
29       `nodes`.
30     path: path to file where network is saved.
31   """
32   if reachable(nodes) > set(nodes):
33     raise ValueError(
34         "Some nodes in `nodes` are connected to nodes not contained in `nodes`."
35         " Saving not possible.")
36   if len(set(nodes)) < len(list(nodes)):
37     raise ValueError(
38         'Some nodes in `nodes` appear more than once. This is not supported')
39   #we need to iterate twice and order matters
40   edges = list(get_all_edges(nodes))
41   nodes = list(nodes)
42 
43   old_edge_names = {n: edge.name for n, edge in enumerate(edges)}
44   old_node_names = {n: node.name for n, node in enumerate(nodes)}
45 
46   #generate unique names for nodes and edges
47   #for saving them
48   for n, node in enumerate(nodes):
49     node.set_name('node{}'.format(n))
50 
51   for e, edge in enumerate(edges):
52     edge.set_name('edge{}'.format(e))
53 
54   with h5py.File(path, 'w') as net_file:
55     nodes_group = net_file.create_group('nodes')
56     node_names_group = net_file.create_group('node_names')
57     node_names_group.create_dataset(
58         'names',
59         dtype=string_type,
60         data=np.array(list(old_node_names.values()), dtype=object))
61 
62     edges_group = net_file.create_group('edges')
63     edge_names_group = net_file.create_group('edge_names')
64     edge_names_group.create_dataset(
65         'names',
66         dtype=string_type,
67         data=np.array(list(old_edge_names.values()), dtype=object))
68 
69     for n, node in enumerate(nodes):
70       node_group = nodes_group.create_group(node.name)
71       node._save_node(node_group)
72       for edge in node.edges:
73         if edge.node1 == node and edge in edges:
74           edge_group = edges_group.create_group(edge.name)
75           edge._save_edge(edge_group)
76           edges.remove(edge)
77 
78   #name edges and nodes back  to their original names
79   for n, node in enumerate(nodes):
80     nodes[n].set_name(old_node_names[n])
81 
82   for n, edge in enumerate(edges):
83     edges[n].set_name(old_edge_names[n])
84 
85 
86 def load_nodes(path: str) -> List[AbstractNode]:
87   """Load a set of nodes from disk.
88 
89   Args:
90     path: path to file where network is saved.
91   Returns:
92     An iterable of `Node` objects
93   """
94   nodes_list = []
95   edges_list = []
96   with h5py.File(path, 'r') as net_file:
97     nodes = list(net_file["nodes"].keys())
98     node_names = {
99         'node{}'.format(n): v
100         for n, v in enumerate(net_file["node_names"]['names'][()])
101     }
102 
103     edge_names = {
104         'edge{}'.format(n): v
105         for n, v in enumerate(net_file["edge_names"]['names'][()])
106     }
107     edges = list(net_file["edges"].keys())
108     for node_name in nodes:
109       node_data = net_file["nodes/" + node_name]
110       node_type = get_component(node_data['type'][()])
111       nodes_list.append(node_type._load_node(node_data=node_data))
112     nodes_dict = {node.name: node for node in nodes_list}
113     for edge in edges:
114       edge_data = net_file["edges/" + edge]
115       edges_list.append(Edge._load_edge(edge_data, nodes_dict))
116 
117   for edge in edges_list:
118     edge.set_name(edge_names[edge.name])
119   for node in nodes_list:
120     node.set_name(node_names[node.name])
121 
122   return nodes_list
123
```
--- 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/__init__.py b/tensornetwork/__init__.py
--- a/tensornetwork/__init__.py
+++ b/tensornetwork/__init__.py
@@ -55,7 +55,7 @@
 from tensornetwork.version import __version__
 from tensornetwork.visualization.graphviz import to_graphviz
 from tensornetwork import contractors
-from tensornetwork.utils import load_nodes, save_nodes
+from tensornetwork.utils import load_nodes, save_nodes, from_topology
 from tensornetwork.matrixproductstates.infinite_mps import InfiniteMPS
 from tensornetwork.matrixproductstates.finite_mps import FiniteMPS
 from tensornetwork.matrixproductstates.dmrg import FiniteDMRG
diff --git a/tensornetwork/utils.py b/tensornetwork/utils.py
--- a/tensornetwork/utils.py
+++ b/tensornetwork/utils.py
@@ -14,7 +14,7 @@
 
 import h5py
 from tensornetwork.component_factory import get_component
-from tensornetwork.network_components import Edge, AbstractNode
+from tensornetwork.network_components import Edge, AbstractNode, Node
 from tensornetwork.network_operations import reachable, get_all_edges
 from typing import List, Union, BinaryIO
 import numpy as np
@@ -120,3 +120,37 @@
     node.set_name(node_names[node.name])
 
   return nodes_list
+
+def from_topology(topology, tensors, backend=None):
+  """Create and connect new `tn.Node`s by the given einsum-like topology.
+  
+  Example:
+    ```
+    a, b, c = tn.from_topology("xy,yz,zx", [a, b, c])
+    ```
+  Args:
+    topology: A string that defines the topology. Should be like
+      the left side of an einsum expression.
+    tensors: The tensors needed to create the nodes.
+
+  Returns:
+    A list of Nodes.
+  """
+  edge_dict = {}
+  nodes = []
+  split_list = topology.split(",")
+  if len(split_list) != len(tensors):
+    raise ValueError("topology and number of tensors is mismatched")
+  for local_axes, tensor in zip(split_list, tensors):
+    local_axes_list = list(local_axes)
+    if len(local_axes_list) != len(tensor.shape):
+      raise ValueError(f"{local_axes} does not match shape {tensor.shape}")
+    new_node = Node(tensor, axis_names=local_axes_list, backend=backend)
+    for c in local_axes:
+      if c in edge_dict:
+        edge_dict[c] = edge_dict[c] ^ new_node[c]
+      else:
+        edge_dict[c] = new_node[c]
+    nodes.append(new_node)
+  return nodes     
+

{"golden_diff": "diff --git a/tensornetwork/__init__.py b/tensornetwork/__init__.py\n--- a/tensornetwork/__init__.py\n+++ b/tensornetwork/__init__.py\n@@ -55,7 +55,7 @@\n from tensornetwork.version import __version__\n from tensornetwork.visualization.graphviz import to_graphviz\n from tensornetwork import contractors\n-from tensornetwork.utils import load_nodes, save_nodes\n+from tensornetwork.utils import load_nodes, save_nodes, from_topology\n from tensornetwork.matrixproductstates.infinite_mps import InfiniteMPS\n from tensornetwork.matrixproductstates.finite_mps import FiniteMPS\n from tensornetwork.matrixproductstates.dmrg import FiniteDMRG\ndiff --git a/tensornetwork/utils.py b/tensornetwork/utils.py\n--- a/tensornetwork/utils.py\n+++ b/tensornetwork/utils.py\n@@ -14,7 +14,7 @@\n \n import h5py\n from tensornetwork.component_factory import get_component\n-from tensornetwork.network_components import Edge, AbstractNode\n+from tensornetwork.network_components import Edge, AbstractNode, Node\n from tensornetwork.network_operations import reachable, get_all_edges\n from typing import List, Union, BinaryIO\n import numpy as np\n@@ -120,3 +120,37 @@\n     node.set_name(node_names[node.name])\n \n   return nodes_list\n+\n+def from_topology(topology, tensors, backend=None):\n+  \"\"\"Create and connect new `tn.Node`s by the given einsum-like topology.\n+  \n+  Example:\n+    ```\n+    a, b, c = tn.from_topology(\"xy,yz,zx\", [a, b, c])\n+    ```\n+  Args:\n+    topology: A string that defines the topology. Should be like\n+      the left side of an einsum expression.\n+    tensors: The tensors needed to create the nodes.\n+\n+  Returns:\n+    A list of Nodes.\n+  \"\"\"\n+  edge_dict = {}\n+  nodes = []\n+  split_list = topology.split(\",\")\n+  if len(split_list) != len(tensors):\n+    raise ValueError(\"topology and number of tensors is mismatched\")\n+  for local_axes, tensor in zip(split_list, tensors):\n+    local_axes_list = list(local_axes)\n+    if len(local_axes_list) != len(tensor.shape):\n+      raise ValueError(f\"{local_axes} does not match shape {tensor.shape}\")\n+    new_node = Node(tensor, axis_names=local_axes_list, backend=backend)\n+    for c in local_axes:\n+      if c in edge_dict:\n+        edge_dict[c] = edge_dict[c] ^ new_node[c]\n+      else:\n+        edge_dict[c] = new_node[c]\n+    nodes.append(new_node)\n+  return nodes     \n+\n", "issue": "Einsum support?\nShould we extend our API to support einsum equations? It could potentially make connecting nodes much less verbose. However, I question whether anyone would want to use `tn.einsum` over say `np.einsum`. Perhaps we could support only doing the left side of the equation?\n", "before_files": [{"content": "from tensornetwork.network_components import (AbstractNode, CopyNode, Edge,\n                                              Node, NodeCollection)\nfrom tensornetwork.network_operations import (\n    check_connected, check_correct, contract_trace_edges, copy, get_all_edges,\n    get_all_nodes, get_neighbors, get_subgraph_dangling, reachable,\n    reduced_density, remove_node, replicate_nodes, split_node,\n    split_node_full_svd, split_node_qr, split_node_rq, switch_backend)\n\nfrom tensornetwork.tensor import Tensor\nfrom tensornetwork.linalg.initialization import (\n    eye,\n    ones,\n    randn,\n    random_uniform,\n    zeros\n    )\n\nfrom tensornetwork.linalg.linalg import norm, qr, svd\n\n#pylint: disable=redefined-builtin\nfrom tensornetwork.linalg.operations import (\n    tensordot,\n    reshape,\n    transpose,\n    take_slice,\n    shape,\n    sqrt,\n    outer,\n    einsum,\n    conj,\n    hconj,\n    sin,\n    cos,\n    exp,\n    log,\n    diagonal,\n    diagflat,\n    trace,\n    sign,\n    abs,\n    kron,\n    pivot\n    )\n\nfrom tensornetwork.backends.decorators import jit\n\nfrom tensornetwork.network_components import (\n    contract, contract_between, contract_copy_node, contract_parallel,\n    flatten_all_edges, flatten_edges, flatten_edges_between,\n    get_all_nondangling, get_all_dangling, get_parallel_edges, get_shared_edges,\n    outer_product, outer_product_final_nodes, slice_edge, split_edge)\nfrom tensornetwork.backends.abstract_backend import AbstractBackend\nfrom tensornetwork.network_components import connect, disconnect\nfrom tensornetwork.ncon_interface import ncon\nfrom tensornetwork.version import __version__\nfrom tensornetwork.visualization.graphviz import to_graphviz\nfrom tensornetwork import contractors\nfrom tensornetwork.utils import load_nodes, save_nodes\nfrom tensornetwork.matrixproductstates.infinite_mps import InfiniteMPS\nfrom tensornetwork.matrixproductstates.finite_mps import FiniteMPS\nfrom tensornetwork.matrixproductstates.dmrg import FiniteDMRG\nfrom tensornetwork.matrixproductstates.mpo import FiniteTFI, FiniteXXZ\nfrom tensornetwork.backend_contextmanager import DefaultBackend\nfrom tensornetwork.backend_contextmanager import set_default_backend\nfrom tensornetwork import block_sparse\nfrom tensornetwork.block_sparse.blocksparsetensor import BlockSparseTensor\nfrom tensornetwork.block_sparse.blocksparsetensor import ChargeArray\nfrom tensornetwork.block_sparse.index import Index\nfrom tensornetwork.block_sparse.charge import U1Charge, BaseCharge, Z2Charge\nfrom tensornetwork.block_sparse.charge import ZNCharge\n", "path": "tensornetwork/__init__.py"}, {"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\nimport h5py\nfrom tensornetwork.component_factory import get_component\nfrom tensornetwork.network_components import Edge, AbstractNode\nfrom tensornetwork.network_operations import reachable, get_all_edges\nfrom typing import List, Union, BinaryIO\nimport numpy as np\nstring_type = h5py.special_dtype(vlen=str)\n\n\ndef save_nodes(nodes: List[AbstractNode], path: Union[str, BinaryIO]) -> None:\n  \"\"\"Save an iterable of nodes into hdf5 format.\n\n  Args:\n    nodes: An iterable of connected nodes. All nodes have to connect within\n      `nodes`.\n    path: path to file where network is saved.\n  \"\"\"\n  if reachable(nodes) > set(nodes):\n    raise ValueError(\n        \"Some nodes in `nodes` are connected to nodes not contained in `nodes`.\"\n        \" Saving not possible.\")\n  if len(set(nodes)) < len(list(nodes)):\n    raise ValueError(\n        'Some nodes in `nodes` appear more than once. This is not supported')\n  #we need to iterate twice and order matters\n  edges = list(get_all_edges(nodes))\n  nodes = list(nodes)\n\n  old_edge_names = {n: edge.name for n, edge in enumerate(edges)}\n  old_node_names = {n: node.name for n, node in enumerate(nodes)}\n\n  #generate unique names for nodes and edges\n  #for saving them\n  for n, node in enumerate(nodes):\n    node.set_name('node{}'.format(n))\n\n  for e, edge in enumerate(edges):\n    edge.set_name('edge{}'.format(e))\n\n  with h5py.File(path, 'w') as net_file:\n    nodes_group = net_file.create_group('nodes')\n    node_names_group = net_file.create_group('node_names')\n    node_names_group.create_dataset(\n        'names',\n        dtype=string_type,\n        data=np.array(list(old_node_names.values()), dtype=object))\n\n    edges_group = net_file.create_group('edges')\n    edge_names_group = net_file.create_group('edge_names')\n    edge_names_group.create_dataset(\n        'names',\n        dtype=string_type,\n        data=np.array(list(old_edge_names.values()), dtype=object))\n\n    for n, node in enumerate(nodes):\n      node_group = nodes_group.create_group(node.name)\n      node._save_node(node_group)\n      for edge in node.edges:\n        if edge.node1 == node and edge in edges:\n          edge_group = edges_group.create_group(edge.name)\n          edge._save_edge(edge_group)\n          edges.remove(edge)\n\n  #name edges and nodes back  to their original names\n  for n, node in enumerate(nodes):\n    nodes[n].set_name(old_node_names[n])\n\n  for n, edge in enumerate(edges):\n    edges[n].set_name(old_edge_names[n])\n\n\ndef load_nodes(path: str) -> List[AbstractNode]:\n  \"\"\"Load a set of nodes from disk.\n\n  Args:\n    path: path to file where network is saved.\n  Returns:\n    An iterable of `Node` objects\n  \"\"\"\n  nodes_list = []\n  edges_list = []\n  with h5py.File(path, 'r') as net_file:\n    nodes = list(net_file[\"nodes\"].keys())\n    node_names = {\n        'node{}'.format(n): v\n        for n, v in enumerate(net_file[\"node_names\"]['names'][()])\n    }\n\n    edge_names = {\n        'edge{}'.format(n): v\n        for n, v in enumerate(net_file[\"edge_names\"]['names'][()])\n    }\n    edges = list(net_file[\"edges\"].keys())\n    for node_name in nodes:\n      node_data = net_file[\"nodes/\" + node_name]\n      node_type = get_component(node_data['type'][()])\n      nodes_list.append(node_type._load_node(node_data=node_data))\n    nodes_dict = {node.name: node for node in nodes_list}\n    for edge in edges:\n      edge_data = net_file[\"edges/\" + edge]\n      edges_list.append(Edge._load_edge(edge_data, nodes_dict))\n\n  for edge in edges_list:\n    edge.set_name(edge_names[edge.name])\n  for node in nodes_list:\n    node.set_name(node_names[node.name])\n\n  return nodes_list\n", "path": "tensornetwork/utils.py"}], "after_files": [{"content": "from tensornetwork.network_components import (AbstractNode, CopyNode, Edge,\n                                              Node, NodeCollection)\nfrom tensornetwork.network_operations import (\n    check_connected, check_correct, contract_trace_edges, copy, get_all_edges,\n    get_all_nodes, get_neighbors, get_subgraph_dangling, reachable,\n    reduced_density, remove_node, replicate_nodes, split_node,\n    split_node_full_svd, split_node_qr, split_node_rq, switch_backend)\n\nfrom tensornetwork.tensor import Tensor\nfrom tensornetwork.linalg.initialization import (\n    eye,\n    ones,\n    randn,\n    random_uniform,\n    zeros\n    )\n\nfrom tensornetwork.linalg.linalg import norm, qr, svd\n\n#pylint: disable=redefined-builtin\nfrom tensornetwork.linalg.operations import (\n    tensordot,\n    reshape,\n    transpose,\n    take_slice,\n    shape,\n    sqrt,\n    outer,\n    einsum,\n    conj,\n    hconj,\n    sin,\n    cos,\n    exp,\n    log,\n    diagonal,\n    diagflat,\n    trace,\n    sign,\n    abs,\n    kron,\n    pivot\n    )\n\nfrom tensornetwork.backends.decorators import jit\n\nfrom tensornetwork.network_components import (\n    contract, contract_between, contract_copy_node, contract_parallel,\n    flatten_all_edges, flatten_edges, flatten_edges_between,\n    get_all_nondangling, get_all_dangling, get_parallel_edges, get_shared_edges,\n    outer_product, outer_product_final_nodes, slice_edge, split_edge)\nfrom tensornetwork.backends.abstract_backend import AbstractBackend\nfrom tensornetwork.network_components import connect, disconnect\nfrom tensornetwork.ncon_interface import ncon\nfrom tensornetwork.version import __version__\nfrom tensornetwork.visualization.graphviz import to_graphviz\nfrom tensornetwork import contractors\nfrom tensornetwork.utils import load_nodes, save_nodes, from_topology\nfrom tensornetwork.matrixproductstates.infinite_mps import InfiniteMPS\nfrom tensornetwork.matrixproductstates.finite_mps import FiniteMPS\nfrom tensornetwork.matrixproductstates.dmrg import FiniteDMRG\nfrom tensornetwork.matrixproductstates.mpo import FiniteTFI, FiniteXXZ\nfrom tensornetwork.backend_contextmanager import DefaultBackend\nfrom tensornetwork.backend_contextmanager import set_default_backend\nfrom tensornetwork import block_sparse\nfrom tensornetwork.block_sparse.blocksparsetensor import BlockSparseTensor\nfrom tensornetwork.block_sparse.blocksparsetensor import ChargeArray\nfrom tensornetwork.block_sparse.index import Index\nfrom tensornetwork.block_sparse.charge import U1Charge, BaseCharge, Z2Charge\nfrom tensornetwork.block_sparse.charge import ZNCharge\n", "path": "tensornetwork/__init__.py"}, {"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\nimport h5py\nfrom tensornetwork.component_factory import get_component\nfrom tensornetwork.network_components import Edge, AbstractNode, Node\nfrom tensornetwork.network_operations import reachable, get_all_edges\nfrom typing import List, Union, BinaryIO\nimport numpy as np\nstring_type = h5py.special_dtype(vlen=str)\n\n\ndef save_nodes(nodes: List[AbstractNode], path: Union[str, BinaryIO]) -> None:\n  \"\"\"Save an iterable of nodes into hdf5 format.\n\n  Args:\n    nodes: An iterable of connected nodes. All nodes have to connect within\n      `nodes`.\n    path: path to file where network is saved.\n  \"\"\"\n  if reachable(nodes) > set(nodes):\n    raise ValueError(\n        \"Some nodes in `nodes` are connected to nodes not contained in `nodes`.\"\n        \" Saving not possible.\")\n  if len(set(nodes)) < len(list(nodes)):\n    raise ValueError(\n        'Some nodes in `nodes` appear more than once. This is not supported')\n  #we need to iterate twice and order matters\n  edges = list(get_all_edges(nodes))\n  nodes = list(nodes)\n\n  old_edge_names = {n: edge.name for n, edge in enumerate(edges)}\n  old_node_names = {n: node.name for n, node in enumerate(nodes)}\n\n  #generate unique names for nodes and edges\n  #for saving them\n  for n, node in enumerate(nodes):\n    node.set_name('node{}'.format(n))\n\n  for e, edge in enumerate(edges):\n    edge.set_name('edge{}'.format(e))\n\n  with h5py.File(path, 'w') as net_file:\n    nodes_group = net_file.create_group('nodes')\n    node_names_group = net_file.create_group('node_names')\n    node_names_group.create_dataset(\n        'names',\n        dtype=string_type,\n        data=np.array(list(old_node_names.values()), dtype=object))\n\n    edges_group = net_file.create_group('edges')\n    edge_names_group = net_file.create_group('edge_names')\n    edge_names_group.create_dataset(\n        'names',\n        dtype=string_type,\n        data=np.array(list(old_edge_names.values()), dtype=object))\n\n    for n, node in enumerate(nodes):\n      node_group = nodes_group.create_group(node.name)\n      node._save_node(node_group)\n      for edge in node.edges:\n        if edge.node1 == node and edge in edges:\n          edge_group = edges_group.create_group(edge.name)\n          edge._save_edge(edge_group)\n          edges.remove(edge)\n\n  #name edges and nodes back  to their original names\n  for n, node in enumerate(nodes):\n    nodes[n].set_name(old_node_names[n])\n\n  for n, edge in enumerate(edges):\n    edges[n].set_name(old_edge_names[n])\n\n\ndef load_nodes(path: str) -> List[AbstractNode]:\n  \"\"\"Load a set of nodes from disk.\n\n  Args:\n    path: path to file where network is saved.\n  Returns:\n    An iterable of `Node` objects\n  \"\"\"\n  nodes_list = []\n  edges_list = []\n  with h5py.File(path, 'r') as net_file:\n    nodes = list(net_file[\"nodes\"].keys())\n    node_names = {\n        'node{}'.format(n): v\n        for n, v in enumerate(net_file[\"node_names\"]['names'][()])\n    }\n\n    edge_names = {\n        'edge{}'.format(n): v\n        for n, v in enumerate(net_file[\"edge_names\"]['names'][()])\n    }\n    edges = list(net_file[\"edges\"].keys())\n    for node_name in nodes:\n      node_data = net_file[\"nodes/\" + node_name]\n      node_type = get_component(node_data['type'][()])\n      nodes_list.append(node_type._load_node(node_data=node_data))\n    nodes_dict = {node.name: node for node in nodes_list}\n    for edge in edges:\n      edge_data = net_file[\"edges/\" + edge]\n      edges_list.append(Edge._load_edge(edge_data, nodes_dict))\n\n  for edge in edges_list:\n    edge.set_name(edge_names[edge.name])\n  for node in nodes_list:\n    node.set_name(node_names[node.name])\n\n  return nodes_list\n\ndef from_topology(topology, tensors, backend=None):\n  \"\"\"Create and connect new `tn.Node`s by the given einsum-like topology.\n  \n  Example:\n    ```\n    a, b, c = tn.from_topology(\"xy,yz,zx\", [a, b, c])\n    ```\n  Args:\n    topology: A string that defines the topology. Should be like\n      the left side of an einsum expression.\n    tensors: The tensors needed to create the nodes.\n\n  Returns:\n    A list of Nodes.\n  \"\"\"\n  edge_dict = {}\n  nodes = []\n  split_list = topology.split(\",\")\n  if len(split_list) != len(tensors):\n    raise ValueError(\"topology and number of tensors is mismatched\")\n  for local_axes, tensor in zip(split_list, tensors):\n    local_axes_list = list(local_axes)\n    if len(local_axes_list) != len(tensor.shape):\n      raise ValueError(f\"{local_axes} does not match shape {tensor.shape}\")\n    new_node = Node(tensor, axis_names=local_axes_list, backend=backend)\n    for c in local_axes:\n      if c in edge_dict:\n        edge_dict[c] = edge_dict[c] ^ new_node[c]\n      else:\n        edge_dict[c] = new_node[c]\n    nodes.append(new_node)\n  return nodes     \n      \n", "path": "tensornetwork/utils.py"}]}