Skip to content
This repository was archived by the owner on Nov 7, 2024. It is now read-only.

Commit a159393

Browse files
katolikyanChase Roberts
andcommitted
Issue #339. with tn.DefaultBackend(backend): support (#434)
* A context manager support implementation for setting up a backend for Nodes. (Issue #339) * Stack-based backend context manager implementation * code styele fix * Added get_default_backend() function which returns top stack backend. Stack returns config.default_backend if there is nothing in stack. A little clean-up in test file. * - Moved `set_default_backend` to the `backend_contextmanager` - `default_backend` now is a property of `_DefaultBackendStack` - removed `config` imports as an unused file. - fixed some tests in `backend_contextmanager_test.py` * little code-style fix Co-authored-by: Chase Roberts <chaseriley@google.com>
1 parent aa3caec commit a159393

9 files changed

Lines changed: 108 additions & 28 deletions

tensornetwork/__init__.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,8 @@
1010
from tensornetwork.version import __version__
1111
from tensornetwork.visualization.graphviz import to_graphviz
1212
from tensornetwork import contractors
13-
from tensornetwork import config
1413
from typing import Text, Optional, Type, Union
1514
from tensornetwork.utils import load_nodes, save_nodes
1615
from tensornetwork.matrixproductstates.finite_mps import FiniteMPS
1716
from tensornetwork.matrixproductstates.infinite_mps import InfiniteMPS
18-
19-
20-
def set_default_backend(backend: Union[Text, BaseBackend]) -> None:
21-
config.default_backend = backend
17+
from tensornetwork.backend_contextmanager import DefaultBackend, set_default_backend
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from typing import Text, Union
2+
from tensornetwork.backends.base_backend import BaseBackend
3+
4+
class DefaultBackend():
5+
"""Context manager for setting up backend for nodes"""
6+
7+
def __init__(self, backend: Union[Text, BaseBackend]) -> None:
8+
if not isinstance(backend, (Text, BaseBackend)):
9+
raise ValueError("Item passed to DefaultBackend "
10+
"must be Text or BaseBackend")
11+
self.backend = backend
12+
13+
def __enter__(self):
14+
_default_backend_stack.stack.append(self)
15+
16+
def __exit__(self, exc_type, exc_val, exc_tb):
17+
_default_backend_stack.stack.pop()
18+
19+
class _DefaultBackendStack():
20+
"""A stack to keep track default backends context manager"""
21+
22+
def __init__(self):
23+
self.stack = []
24+
self.default_backend = "numpy"
25+
26+
def get_current_backend(self):
27+
return self.stack[-1].backend if self.stack else self.default_backend
28+
29+
_default_backend_stack = _DefaultBackendStack()
30+
31+
def get_default_backend():
32+
return _default_backend_stack.get_current_backend()
33+
34+
def set_default_backend(backend: Union[Text, BaseBackend]) -> None:
35+
if _default_backend_stack.stack:
36+
raise AssertionError("The default backend should not be changed "
37+
"inside the backend context manager")
38+
if not isinstance(backend, (Text, BaseBackend)):
39+
raise ValueError("Item passed to set_default_backend "
40+
"must be Text or BaseBackend")
41+
_default_backend_stack.default_backend = backend

tensornetwork/backends/backend_factory.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
from tensornetwork.backends.shell import shell_backend
2020
from tensornetwork.backends.pytorch import pytorch_backend
2121
from tensornetwork.backends import base_backend
22-
import tensornetwork.config as config_file
2322

2423
_BACKENDS = {
2524
"tensorflow": tensorflow_backend.TensorFlowBackend,

tensornetwork/config.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,3 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14-
15-
default_backend = "numpy"

tensornetwork/ncon_interface.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import warnings
1717
from typing import Any, Sequence, List, Optional, Union, Text, Tuple, Dict
1818
from tensornetwork import network_components
19-
from tensornetwork import config
19+
from tensornetwork.backend_contextmanager import get_default_backend
2020
from tensornetwork.backends import backend_factory
2121
Tensor = Any
2222

@@ -67,8 +67,8 @@ def ncon(tensors: Sequence[Union[network_components.BaseNode, Tensor]],
6767
structure.
6868
con_order: List of edge labels specifying the contraction order.
6969
out_order: List of edge labels specifying the output order.
70-
backend: String specifying the backend to use. Defaults to
71-
`tensornetwork.config.default_backend`.
70+
backend: String specifying the backend to use. Defaults to
71+
`tensornetwork.backend_contextmanager.get_default_backend`.
7272
7373
Returns:
7474
The result of the contraction. The result is returned as a `Node`
@@ -78,7 +78,7 @@ def ncon(tensors: Sequence[Union[network_components.BaseNode, Tensor]],
7878
if backend and (backend not in backend_factory._BACKENDS):
7979
raise ValueError("Backend '{}' does not exist".format(backend))
8080
if backend is None:
81-
backend = config.default_backend
81+
backend = get_default_backend()
8282

8383
are_nodes = [isinstance(t, network_components.BaseNode) for t in tensors]
8484
nodes = {t for t in tensors if isinstance(t, network_components.BaseNode)}

tensornetwork/network_components.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@
2121
import h5py
2222

2323
#pylint: disable=useless-import-alias
24-
import tensornetwork.config as config
2524
from tensornetwork import ops
2625
from tensornetwork.backends import backend_factory
2726
from tensornetwork.backends.base_backend import BaseBackend
27+
from tensornetwork.backend_contextmanager import get_default_backend
2828

2929
string_type = h5py.special_dtype(vlen=str)
3030
Tensor = Any
@@ -525,8 +525,8 @@ def __init__(self,
525525
"""Create a node.
526526
527527
Args:
528-
tensor: The concrete that is represented by this node, or a `BaseNode`
529-
object. If a tensor is passed, it can be
528+
tensor: The concrete that is represented by this node, or a `BaseNode`
529+
object. If a tensor is passed, it can be
530530
be either a numpy array or the tensor-type of the used backend.
531531
If a `BaseNode` is passed, the passed node has to have the same \
532532
backend as given by `backend`.
@@ -543,7 +543,7 @@ def __init__(self,
543543
backend = tensor.backend
544544
tensor = tensor.tensor
545545
if not backend:
546-
backend = config.default_backend
546+
backend = get_default_backend()
547547
if isinstance(backend, BaseBackend):
548548
backend_obj = backend
549549
else:
@@ -633,13 +633,13 @@ def __init__(self,
633633
backend: An optional backend for the node. If `None`, a default
634634
backend is used
635635
dtype: The dtype used to initialize a numpy-copy node.
636-
Note that this dtype has to be a numpy dtype, and it has to be
636+
Note that this dtype has to be a numpy dtype, and it has to be
637637
compatible with the dtype of the backend, e.g. for a tensorflow
638638
backend with a tf.Dtype=tf.floa32, `dtype` has to be `np.float32`.
639639
"""
640640

641641
if not backend:
642-
backend = config.default_backend
642+
backend = get_default_backend()
643643
backend_obj = backend_factory.get_backend(backend)
644644

645645
self.rank = rank
@@ -1092,14 +1092,14 @@ def disconnect(self,
10921092
edge2_name: Optional[Text] = None) -> Tuple["Edge", "Edge"]:
10931093
"""
10941094
Break an existing non-dangling edge.
1095-
This updates both Edge.node1 and Edge.node2 by removing the
1095+
This updates both Edge.node1 and Edge.node2 by removing the
10961096
connecting edge from `Edge.node1.edges` and `Edge.node2.edges`
10971097
and adding new dangling edges instead
10981098
Args:
10991099
edge1_name: A name for the new dangling edge at `self.node1`
11001100
edge2_name: A name for the new dangling edge at `self.node2`
11011101
Returns:
1102-
(new_edge1, new_edge2): The new `Edge` objects of
1102+
(new_edge1, new_edge2): The new `Edge` objects of
11031103
`self.node1` and `self.node2`
11041104
"""
11051105
if self.is_dangling():
@@ -1155,7 +1155,7 @@ def get_parallel_edges(edge: Edge) -> Set[Edge]:
11551155
edge: The given edge.
11561156
11571157
Returns:
1158-
A `set` of all of the edges parallel to the given edge
1158+
A `set` of all of the edges parallel to the given edge
11591159
(including the given edge).
11601160
"""
11611161
return get_shared_edges(edge.node1, edge.node2)
@@ -1389,8 +1389,8 @@ def split_edge(edge: Edge,
13891389
shape: Tuple[int, ...],
13901390
new_edge_names: Optional[List[Text]] = None) -> List[Edge]:
13911391
"""Split an `Edge` into multiple edges according to `shape`. Reshapes
1392-
the underlying tensors connected to the edge accordingly.
1393-
1392+
the underlying tensors connected to the edge accordingly.
1393+
13941394
This method acts as the inverse operation of flattening edges and
13951395
distinguishes between the following edge cases when adding new edges:
13961396
1) standard edge connecting two different nodes: reshape node dimensions
@@ -1772,7 +1772,7 @@ def disconnect(edge,
17721772
edge2_name: Optional[Text] = None) -> Tuple[Edge, Edge]:
17731773
"""
17741774
Break an existing non-dangling edge.
1775-
This updates both Edge.node1 and Edge.node2 by removing the
1775+
This updates both Edge.node1 and Edge.node2 by removing the
17761776
connecting edge from `Edge.node1.edges` and `Edge.node2.edges`
17771777
and adding new dangling edges instead
17781778
"""
@@ -1894,9 +1894,9 @@ def outer_product_final_nodes(nodes: Iterable[BaseNode],
18941894
edge_order: List[Edge]) -> BaseNode:
18951895
"""Get the outer product of `nodes`
18961896
1897-
For example, if there are 3 nodes remaining in `nodes` with
1897+
For example, if there are 3 nodes remaining in `nodes` with
18981898
shapes :math:`(2, 3)`, :math:`(4, 5, 6)`, and :math:`(7)`
1899-
respectively, the newly returned node will have shape
1899+
respectively, the newly returned node will have shape
19001900
:math:`(2, 3, 4, 5, 6, 7)`.
19011901
19021902
Args:

tensornetwork/network_operations.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import numpy as np
2020

2121
#pylint: disable=useless-import-alias
22-
import tensornetwork.config as config
2322
#pylint: disable=line-too-long
2423
from tensornetwork.network_components import BaseNode, Node, CopyNode, Edge, disconnect
2524
from tensornetwork.backends import backend_factory
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import tensornetwork as tn
2+
from tensornetwork.backend_contextmanager import _default_backend_stack
3+
import pytest
4+
import numpy as np
5+
6+
def test_contextmanager_simple():
7+
with tn.DefaultBackend("tensorflow"):
8+
a = tn.Node(np.ones((10,)))
9+
b = tn.Node(np.ones((10,)))
10+
assert a.backend.name == b.backend.name
11+
12+
def test_contextmanager_default_backend():
13+
tn.set_default_backend("pytorch")
14+
with tn.DefaultBackend("numpy"):
15+
assert _default_backend_stack.default_backend == "pytorch"
16+
17+
def test_contextmanager_interruption():
18+
tn.set_default_backend("pytorch")
19+
with pytest.raises(AssertionError):
20+
with tn.DefaultBackend("numpy"):
21+
tn.set_default_backend("tensorflow")
22+
23+
def test_contextmanager_nested():
24+
with tn.DefaultBackend("tensorflow"):
25+
a = tn.Node(np.ones((10,)))
26+
assert a.backend.name == "tensorflow"
27+
with tn.DefaultBackend("numpy"):
28+
b = tn.Node(np.ones((10,)))
29+
assert b.backend.name == "numpy"
30+
c = tn.Node(np.ones((10,)))
31+
assert c.backend.name == "tensorflow"
32+
d = tn.Node(np.ones((10,)))
33+
assert d.backend.name == "numpy"
34+
35+
def test_contextmanager_wrong_item():
36+
a = tn.Node(np.ones((10,)))
37+
with pytest.raises(ValueError):
38+
with tn.DefaultBackend(a): # pytype: disable=wrong-arg-types
39+
pass
40+
41+
def test_contextmanager_BaseBackend():
42+
tn.set_default_backend("pytorch")
43+
a = tn.Node(np.ones((10,)))
44+
with tn.DefaultBackend(a.backend):
45+
b = tn.Node(np.ones((10,)))
46+
assert b.backend.name == "pytorch"

tensornetwork/tests/tensornetwork_test.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515
import tensornetwork as tn
16+
from tensornetwork.backend_contextmanager import _default_backend_stack
1617
import pytest
1718
import numpy as np
1819
import tensorflow as tf
@@ -522,7 +523,7 @@ def test_set_node2(backend):
522523

523524
def test_set_default(backend):
524525
tn.set_default_backend(backend)
525-
assert tn.config.default_backend == backend
526+
assert _default_backend_stack.default_backend == backend
526527
a = tn.Node(np.eye(2))
527528
assert a.backend.name == backend
528529

0 commit comments

Comments
 (0)