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

Commit a08618d

Browse files
authored
Add truncation option (#913)
* fix apply_twoside_gate in base_mps.py to use QR if no truncation is requested * fix indentation * fiux test * add D and max_truncation_err arguments to position to allow optional MPS truncation * add D and max_truncation_err arguments to position to allow optional MPS truncation * minor bug fixes, tests added * fix ncon call
1 parent 6ac507a commit a08618d

3 files changed

Lines changed: 168 additions & 64 deletions

File tree

tensornetwork/matrixproductstates/base_mps.py

Lines changed: 151 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@
1919
from functools import partial
2020
from tensornetwork.backends.decorators import jit
2121
import warnings
22-
from tensornetwork.ncon_interface import ncon
2322
from tensornetwork.backend_contextmanager import get_default_backend
2423
from tensornetwork.backends.abstract_backend import AbstractBackend
2524
from typing import Any, List, Optional, Text, Type, Union, Dict, Sequence
25+
import tensornetwork.ncon_interface as ncon
26+
2627
Tensor = Any
2728

2829

@@ -82,10 +83,10 @@ def __init__(self,
8283
else:
8384
self.backend = backend_factory.get_backend(backend)
8485

86+
8587
# the dtype is deduced from the tensor object.
8688
self.tensors = [self.backend.convert_to_tensor(t) for t in tensors]
87-
if not all(
88-
[self.tensors[0].dtype == tensor.dtype for tensor in self.tensors]):
89+
if not all(t.dtype == self.tensors[0].dtype for t in self.tensors):
8990
raise TypeError('not all dtypes in BaseMPS.tensors are the same')
9091

9192
self.connector_matrix = connector_matrix
@@ -94,20 +95,30 @@ def __init__(self,
9495
########################################################################
9596
########## define functions for jitted operations ##########
9697
########################################################################
97-
@partial(jit, backend=self.backend, static_argnums=(1,))
98-
def svd(tensor, max_singular_values=None):
99-
return self.backend.svd(tensor=tensor, pivot_axis=2,
100-
max_singular_values=max_singular_values)
98+
@partial(jit, backend=self.backend, static_argnums=(1, 2, 3))
99+
def svd(tensor,
100+
pivot_axis=2,
101+
max_singular_values=None,
102+
max_truncation_error=None):
103+
return self.backend.svd(
104+
tensor=tensor,
105+
pivot_axis=pivot_axis,
106+
max_singular_values=max_singular_values,
107+
max_truncation_error=max_truncation_error,
108+
relative=True)
109+
101110
self.svd = svd
102111

103112
@partial(jit, backend=self.backend)
104113
def qr(tensor):
105114
return self.backend.qr(tensor, 2)
115+
106116
self.qr = qr
107117

108118
@partial(jit, backend=self.backend)
109119
def rq(tensor):
110120
return self.backend.rq(tensor, 1)
121+
111122
self.rq = rq
112123

113124
self.norm = self.backend.jit(self.backend.norm)
@@ -116,22 +127,27 @@ def rq(tensor):
116127
########################################################################
117128

118129
def left_transfer_operator(self, A, l, Abar):
119-
return ncon([A, l, Abar], [[1, 2, -1], [1, 3], [3, 2, -2]],
120-
backend=self.backend.name)
130+
return ncon.ncon([A, l, Abar], [[1, 2, -1], [1, 3], [3, 2, -2]],
131+
backend=self.backend.name)
121132

122133
def right_transfer_operator(self, B, r, Bbar):
123-
return ncon([B, r, Bbar], [[-1, 2, 1], [1, 3], [-2, 2, 3]],
124-
backend=self.backend.name)
134+
return ncon.ncon([B, r, Bbar], [[-1, 2, 1], [1, 3], [-2, 2, 3]],
135+
backend=self.backend.name)
125136

126137
def __len__(self) -> int:
127138
return len(self.tensors)
128139

129-
def position(self, site: int, normalize: Optional[bool] = True) -> np.number:
140+
def position(self, site: int, normalize: Optional[bool] = True,
141+
D: Optional[int] = None,
142+
max_truncation_err: Optional[float] = None) -> np.number:
130143
"""Shift `center_position` to `site`.
131144
132145
Args:
133146
site: The site to which FiniteMPS.center_position should be shifted
134147
normalize: If `True`, normalize matrices when shifting.
148+
D: If not `None`, truncate the MPS bond dimensions to `D`.
149+
max_truncation_err: if not `None`, truncate each bond dimension,
150+
but keeping the truncation error below `max_truncation_err`.
135151
Returns:
136152
`Tensor`: The norm of the tensor at `FiniteMPS.center_position`
137153
Raises:
@@ -141,27 +157,40 @@ def position(self, site: int, normalize: Optional[bool] = True) -> np.number:
141157
raise ValueError(
142158
"BaseMPS.center_position is `None`, cannot shift `center_position`."
143159
"Reset `center_position` manually or use `canonicalize`")
144-
160+
if max_truncation_err is not None and max_truncation_err >= 1.0:
161+
raise ValueError("max_truncation_err should be 0 <= max_truncation_er"
162+
f" < 1, found max_truncation_err = {max_truncation_err}")
145163
#`site` has to be between 0 and len(mps) - 1
146164
if site >= len(self.tensors) or site < 0:
147165
raise ValueError('site = {} not between values'
148166
' 0 < site < N = {}'.format(site, len(self)))
167+
168+
149169
#nothing to do
150170
if site == self.center_position:
151171
Z = self.norm(self.tensors[self.center_position])
152172
if normalize:
153173
self.tensors[self.center_position] /= Z
154174
return Z
155175

156-
#shift center_position to the right using QR decomposition
176+
#shift center_position to the right using QR or SV decomposition
157177
if site > self.center_position:
158178
n = self.center_position
159179
for n in range(self.center_position, site):
160-
Q, R = self.qr(self.tensors[n])
161-
self.tensors[n] = Q
162-
self.tensors[n + 1] = ncon([R, self.tensors[n + 1]],
163-
[[-1, 1], [1, -2, -3]],
164-
backend=self.backend.name)
180+
use_svd = (D is not None and D < self.bond_dimension(n + 1)
181+
) or max_truncation_err is not None
182+
if not use_svd:
183+
isometry, rest = self.qr(self.tensors[n])
184+
else:
185+
isometry, S, V, _ = self.svd(self.tensors[n], 2, D,
186+
max_truncation_err)
187+
rest = ncon.ncon([self.backend.diagflat(S), V], [[-1, 1], [1, -2]],
188+
backend=self.backend)
189+
190+
self.tensors[n] = isometry
191+
self.tensors[n + 1] = ncon.ncon([rest, self.tensors[n + 1]],
192+
[[-1, 1], [1, -2, -3]],
193+
backend=self.backend.name)
165194
Z = self.norm(self.tensors[n + 1])
166195
# for an mps with > O(10) sites one needs to normalize to avoid
167196
# over or underflow errors; this takes care of the normalization
@@ -170,18 +199,26 @@ def position(self, site: int, normalize: Optional[bool] = True) -> np.number:
170199

171200
self.center_position = site
172201

173-
#shift center_position to the left using RQ decomposition
202+
#shift center_position to the left using RQ or SV decomposition
174203
else:
175204
for n in reversed(range(site + 1, self.center_position + 1)):
176-
177-
R, Q = self.rq(self.tensors[n])
205+
use_svd = (D is not None and D < self.bond_dimension(n)
206+
) or max_truncation_err is not None
207+
if not use_svd:
208+
rest, isometry = self.rq(self.tensors[n])
209+
else:
210+
U, S, isometry, _ = self.svd(self.tensors[n], 1, D,
211+
max_truncation_err)
212+
rest = ncon.ncon([U, self.backend.diagflat(S)], [[-1, 1], [1, -2]],
213+
backend=self.backend)
214+
215+
self.tensors[n] = isometry #a right-isometric tensor of rank 3
216+
self.tensors[n - 1] = ncon.ncon([self.tensors[n - 1], rest],
217+
[[-1, -2, 1], [1, -3]],
218+
backend=self.backend.name)
219+
Z = self.norm(self.tensors[n - 1])
178220
# for an mps with > O(10) sites one needs to normalize to avoid
179221
# over or underflow errors; this takes care of the normalization
180-
self.tensors[n] = Q #Q is a right-isometric tensor of rank 3
181-
self.tensors[n - 1] = ncon([self.tensors[n - 1], R],
182-
[[-1, -2, 1], [1, -3]],
183-
backend=self.backend.name)
184-
Z = self.norm(self.tensors[n - 1])
185222
if normalize:
186223
self.tensors[n - 1] /= Z
187224

@@ -191,15 +228,23 @@ def position(self, site: int, normalize: Optional[bool] = True) -> np.number:
191228

192229
@property
193230
def dtype(self) -> Type[np.number]:
194-
if not all(
195-
[self.tensors[0].dtype == tensor.dtype for tensor in self.tensors]):
231+
if not all(t.dtype == self.tensors[0].dtype for t in self.tensors):
196232
raise TypeError('not all dtype in BaseMPS.tensors are the same')
197233

198234
return self.tensors[0].dtype
199235

200236
def save(self, path: str):
201237
raise NotImplementedError()
202238

239+
def bond_dimension(self, bond) -> List:
240+
"""The bond dimension of `bond`"""
241+
if bond > len(self):
242+
raise IndexError(f"bond {bond} out of bounds for"
243+
f" an MPS of length {len(self)}")
244+
if bond < len(self):
245+
return self.tensors[bond].shape[0]
246+
return self.tensors[bond].shape[2]
247+
203248
@property
204249
def bond_dimensions(self) -> List:
205250
"""A list of bond dimensions of `BaseMPS`"""
@@ -439,7 +484,9 @@ def apply_two_site_gate(self,
439484
site1: int,
440485
site2: int,
441486
max_singular_values: Optional[int] = None,
442-
max_truncation_err: Optional[float] = None) -> Tensor:
487+
max_truncation_err: Optional[float] = None,
488+
center_position: Optional[int] = None,
489+
relative: bool = False) -> Tensor:
443490
"""Apply a two-site gate to an MPS. This routine will in general destroy
444491
any canonical form of the state. If a canonical form is needed, the user
445492
can restore it using `FiniteMPS.position`.
@@ -450,6 +497,15 @@ def apply_two_site_gate(self,
450497
site2: The second site where the gate acts.
451498
max_singular_values: The maximum number of singular values to keep.
452499
max_truncation_err: The maximum allowed truncation error.
500+
center_position: An optional value to choose the MPS tensor at
501+
`center_position` to be isometric after the application of the gate.
502+
Defaults to `site1`. If the MPS is canonical (i.e.
503+
`BaseMPS.center_position != None`), and if the orthogonality center
504+
coincides with either `site1` or `site2`, the orthogonality center will
505+
be shifted to `center_position` (`site1` by default). If the
506+
orthogonality center does not coincide with `(site1, site2)` then
507+
`MPS.center_position` is set to `None`.
508+
relative: Multiply `max_truncation_err` with the largest singular value.
453509
454510
Returns:
455511
`Tensor`: A scalar tensor containing the truncated weight of the
@@ -473,6 +529,10 @@ def apply_two_site_gate(self,
473529
"neighbor gates are currently"
474530
"supported".format(site2, site1))
475531

532+
if center_position is not None and center_position not in (site1, site2):
533+
raise ValueError(f"center_position = {center_position} not "
534+
f"in {(site1, site2)} ")
535+
476536
if (max_singular_values or
477537
max_truncation_err) and self.center_position not in (site1, site2):
478538
raise ValueError(
@@ -481,28 +541,59 @@ def apply_two_site_gate(self,
481541
'is applied at the center position of the MPS'.format(
482542
self.center_position, site1, site2))
483543

484-
gate_node = Node(gate, backend=self.backend)
485-
node1 = Node(self.tensors[site1], backend=self.backend)
486-
node2 = Node(self.tensors[site2], backend=self.backend)
487-
node1[2] ^ node2[0]
488-
gate_node[2] ^ node1[1]
489-
gate_node[3] ^ node2[1]
490-
left_edges = [node1[0], gate_node[0]]
491-
right_edges = [gate_node[1], node2[2]]
492-
result = node1 @ node2 @ gate_node
493-
U, S, V, tw = split_node_full_svd(
494-
result,
495-
left_edges=left_edges,
496-
right_edges=right_edges,
497-
max_singular_values=max_singular_values,
498-
max_truncation_err=max_truncation_err,
499-
left_name=node1.name,
500-
right_name=node2.name)
501-
V.reorder_edges([S[1]] + right_edges)
502-
left_edges = left_edges + [S[1]]
503-
res = contract_between(U, S, name=U.name).reorder_edges(left_edges)
504-
self.tensors[site1] = res.tensor
505-
self.tensors[site2] = V.tensor
544+
use_svd = (max_truncation_err is not None) or (max_singular_values
545+
is not None)
546+
gate = self.backend.convert_to_tensor(gate)
547+
tensor = ncon.ncon([self.tensors[site1], self.tensors[site2], gate],
548+
[[-1, 1, 2], [2, 3, -4], [-2, -3, 1, 3]],
549+
backend=self.backend)
550+
551+
def set_center_position(site):
552+
if self.center_position is not None:
553+
if self.center_position in (site1, site2):
554+
assert site in (site1, site2)
555+
self.center_position = site
556+
else:
557+
self.center_position = None
558+
559+
if center_position is None:
560+
center_position = site1
561+
562+
if use_svd:
563+
U, S, V, tw = self.backend.svd(
564+
tensor,
565+
pivot_axis=2,
566+
max_singular_values=max_singular_values,
567+
max_truncation_error=max_truncation_err,
568+
relative=relative)
569+
if center_position == site2:
570+
left_tensor = U
571+
right_tensor = ncon.ncon([self.backend.diagflat(S), V],
572+
[[-1, 1], [1, -2, -3]],
573+
backend=self.backend)
574+
set_center_position(site2)
575+
else:
576+
left_tensor = ncon.ncon([U, self.backend.diagflat(S)],
577+
[[-1, -2, 1], [1, -3]],
578+
backend=self.backend)
579+
right_tensor = V
580+
set_center_position(site1)
581+
582+
else:
583+
tw = self.backend.zeros(1, dtype=self.dtype)
584+
if center_position == site2:
585+
R, Q = self.backend.rq(tensor, pivot_axis=2)
586+
left_tensor = R
587+
right_tensor = Q
588+
set_center_position(site2)
589+
else:
590+
Q, R = self.backend.qr(tensor, pivot_axis=2)
591+
left_tensor = Q
592+
right_tensor = R
593+
set_center_position(site1)
594+
595+
self.tensors[site1] = left_tensor
596+
self.tensors[site2] = right_tensor
506597
return tw
507598

508599
def apply_one_site_gate(self, gate: Tensor, site: int) -> None:
@@ -519,9 +610,9 @@ def apply_one_site_gate(self, gate: Tensor, site: int) -> None:
519610
if site < 0 or site >= len(self):
520611
raise ValueError('site = {} is not between 0 <= site < N={}'.format(
521612
site, len(self)))
522-
self.tensors[site] = ncon([gate, self.tensors[site]],
523-
[[-2, 1], [-1, 1, -3]],
524-
backend=self.backend.name)
613+
self.tensors[site] = ncon.ncon([gate, self.tensors[site]],
614+
[[-2, 1], [-1, 1, -3]],
615+
backend=self.backend.name)
525616

526617
def check_orthonormality(self, which: Text, site: int) -> Tensor:
527618
"""Check orthonormality of tensor at site `site`.
@@ -555,8 +646,8 @@ def check_orthonormality(self, which: Text, site: int) -> Tensor:
555646
M=self.backend.sparse_shape(result)[1],
556647
dtype=self.dtype)
557648
return self.backend.sqrt(
558-
ncon([tmp, self.backend.conj(tmp)], [[1, 2], [1, 2]],
559-
backend=self.backend))
649+
ncon.ncon([tmp, self.backend.conj(tmp)], [[1, 2], [1, 2]],
650+
backend=self.backend))
560651

561652
# pylint: disable=inconsistent-return-statements
562653
def check_canonical(self) -> Any:
@@ -601,9 +692,9 @@ def get_tensor(self, site: int) -> Tensor:
601692
'index `site` has to be larger than 0 (found `site`={}).'.format(
602693
site))
603694
if (site == len(self) - 1) and (self.connector_matrix is not None):
604-
return ncon([self.tensors[site], self.connector_matrix],
605-
[[-1, -2, 1], [1, -3]],
606-
backend=self.backend.name)
695+
return ncon.ncon([self.tensors[site], self.connector_matrix],
696+
[[-1, -2, 1], [1, -3]],
697+
backend=self.backend.name)
607698
return self.tensors[site]
608699

609700
def canonicalize(self, *args, **kwargs) -> np.number:

tensornetwork/matrixproductstates/base_mps_test.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,12 @@ def test_position_raises_error(backend):
187187
" `None`, cannot shift `center_position`."
188188
"Reset `center_position` manually or use `canonicalize`"):
189189
mps.position(1)
190+
mps = BaseMPS(tensors, center_position=0, backend=backend)
191+
with pytest.raises(
192+
ValueError,
193+
match="max_truncation_err"):
194+
mps.position(1, max_truncation_err=1.1)
195+
190196

191197

192198
def test_position_no_normalization(backend):
@@ -233,6 +239,15 @@ def test_position_no_shift_no_normalization(backend):
233239
Z = mps.position(int(N / 2), normalize=False)
234240
np.testing.assert_allclose(Z, 5.656854)
235241

242+
def test_position_truncation(backend):
243+
D, d, N = 10, 2, 10
244+
tensors = [np.ones((1, d, D))] + [np.ones((D, d, D)) for _ in range(N - 2)
245+
] + [np.ones((D, d, 1))]
246+
mps = BaseMPS(tensors, center_position=0, backend=backend)
247+
mps.position(N-1)
248+
mps.position(0, D=5)
249+
assert np.all(np.array(mps.bond_dimensions) <= 5)
250+
236251

237252
def test_different_dtypes_raises_error():
238253
D, d = 4, 2

0 commit comments

Comments
 (0)