1919from functools import partial
2020from tensornetwork .backends .decorators import jit
2121import warnings
22- from tensornetwork .ncon_interface import ncon
2322from tensornetwork .backend_contextmanager import get_default_backend
2423from tensornetwork .backends .abstract_backend import AbstractBackend
2524from typing import Any , List , Optional , Text , Type , Union , Dict , Sequence
25+ import tensornetwork .ncon_interface as ncon
26+
2627Tensor = 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 :
0 commit comments