def position(self, site: int, normalize: Optional[bool] = True) -> np.number: """ Shift FiniteMPS.center_position to `site`. Args: site: The site to which FiniteMPS.center_position should be shifted normalize: If `True`, normalize matrices when shifting. Returns: Tensor: The norm of the tensor at FiniteMPS.center_position """ #`site` has to be between 0 and len(mps) - 1 if site >= len(self.nodes) or site < 0: raise ValueError('site = {} not between values' ' 0 < site < N = {}'.format(site, len(self))) #nothing to do if site == self.center_position: return self.backend.norm(self.nodes[self.center_position].tensor) #shift center_position to the right using QR decomposition if site > self.center_position: n = self.center_position for n in range(self.center_position, site): Q, R = split_node_qr( self.nodes[n], left_edges=[self.nodes[n][0], self.nodes[n][1]], right_edges=[self.nodes[n][2]], left_name=self.nodes[n].name) self.nodes[n] = Q #Q is a left-isometric tensor of rank 3 self.nodes[n + 1] = contract(R[1], name=self.nodes[n + 1].name) Z = norm(self.nodes[n + 1]) # for an mps with > O(10) sites one needs to normalize to avoid # over or underflow errors; this takes care of the normalization if normalize: self.nodes[n + 1].tensor /= Z self.center_position = site #shift center_position to the left using RQ decomposition elif site < self.center_position: for n in reversed(range(site + 1, self.center_position + 1)): R, Q = split_node_rq( self.nodes[n], left_edges=[self.nodes[n][0]], right_edges=[self.nodes[n][1], self.nodes[n][2]], right_name=self.nodes[n].name) # for an mps with > O(10) sites one needs to normalize to avoid # over or underflow errors; this takes care of the normalization self.nodes[n] = Q #Q is a right-isometric tensor of rank 3 self.nodes[n - 1] = contract(R[0], name=self.nodes[n - 1].name) Z = norm(self.nodes[n - 1]) if normalize: self.nodes[n - 1].tensor /= Z self.center_position = site #return the norm of the last R tensor (useful for checks) return Z
def split_node_rq( self, node: network_components.BaseNode, left_edges: List[network_components.Edge], right_edges: List[network_components.Edge], left_name: Optional[Text] = None, right_name: Optional[Text] = None, edge_name: Optional[Text] = None, ) -> Tuple[network_components.BaseNode, network_components.BaseNode]: """Split a `Node` using RQ (reversed QR) decomposition Let M be the matrix created by flattening left_edges and right_edges into 2 axes. Let :math:`QR = M^*` be the QR Decomposition of :math:`M^*`. This will split the network into 2 nodes. The left node's tensor will be :math:`R^*` (a lower triangular matrix) and the right node's tensor will be :math:`Q^*` (an orthonormal matrix) Args: node: The node you want to split. left_edges: The edges you want connected to the new left node. right_edges: The edges you want connected to the new right node. left_name: The name of the new left node. If `None`, a name will be generated automatically. right_name: The name of the new right node. If `None`, a name will be generated automatically. edge_name: The name of the new `Edge` connecting the new left and right node. If `None`, a name will be generated automatically. Returns: A tuple containing: left_node: A new node created that connects to all of the `left_edges`. Its underlying tensor is :math:`Q` right_node: A new node created that connects to all of the `right_edges`. Its underlying tensor is :math:`R` """ r, q = network_operations.split_node_rq(node, left_edges, right_edges, left_name, right_name, edge_name) left_node = self.add_node(r) right_node = self.add_node(q) self.nodes_set.remove(node) node.disable() return left_node, right_node
def apply_two_site_gate( self, gate: Tensor, site1: int, site2: int, max_singular_values: Optional[int] = None, max_truncation_err: Optional[float] = None, new_center_position: Optional[int] = None) -> Tensor: """Apply a two-site gate to an MPS. This routine will in general destroy any canonical form of the state. If a canonical form is needed, the user can restore it using `FiniteMPS.position`. Args: gate: A two-body gate. site1: The first site where the gate acts. site2: The second site where the gate acts. max_singular_values: The maximum number of singular values to keep. max_truncation_err: The maximum allowed truncation error. new_center_position: The new orthogonality center. Returns: `Tensor`: A scalar tensor containing the truncated weight of the truncation. """ if len(gate.shape) != 4: raise ValueError('rank of gate is {} but has to be 4'.format( len(gate.shape))) if site1 < 0 or site1 >= len(self) - 1: raise ValueError( 'site1 = {} is not between 0 <= site < N - 1 = {}'.format( site1, len(self))) if site2 < 1 or site2 >= len(self): raise ValueError( 'site2 = {} is not between 1 <= site < N = {}'.format( site2, len(self))) if site2 <= site1: raise ValueError( 'site2 = {} has to be larger than site1 = {}'.format( site2, site1)) if site2 != site1 + 1: raise ValueError( 'Found site2 ={}, site1={}. Only nearest neighbor gates are currently ' 'supported'.format(site2, site1)) if ((self.center_position is None) and (new_center_position is not None)): #temporary fix raise ValueError( 'Cannot change orthogonality center if previously non canonical' ) if (max_singular_values or max_truncation_err): if ((self.center_position is not None) and (self.center_position not in (site1, site2))): raise ValueError( 'center_position = {}, but gate is applied at sites {}, {}. ' 'Truncation should only be done if the gate ' 'is applied at the center position of the MPS'.format( self.center_position, site1, site2)) gate_node = Node(gate, backend=self.backend) node1 = Node(self.tensors[site1], backend=self.backend) node2 = Node(self.tensors[site2], backend=self.backend) node1[2] ^ node2[0] gate_node[2] ^ node1[1] gate_node[3] ^ node2[1] left_edges = [node1[0], gate_node[0]] right_edges = [gate_node[1], node2[2]] result = node1 @ node2 @ gate_node U, S, V, tw = split_node_full_svd( result, left_edges=left_edges, right_edges=right_edges, max_singular_values=max_singular_values, max_truncation_err=max_truncation_err, left_name=node1.name, right_name=node2.name) V.reorder_edges([S[1]] + right_edges) left_edges = left_edges + [S[1]] res = contract_between(U, S, name=U.name).reorder_edges(left_edges) self.tensors[site1] = res.tensor self.tensors[site2] = V.tensor if (new_center_position): self.position(site=new_center_position, normalize=False) else: self.position(site=self.center_position, normalize=False) return tw else: gate_node = Node(gate, backend=self.backend) node1 = Node(self.tensors[site1], backend=self.backend) node2 = Node(self.tensors[site2], backend=self.backend) node1[2] ^ node2[0] gate_node[2] ^ node1[1] gate_node[3] ^ node2[1] left_edges = [node1[0], gate_node[0]] right_edges = [gate_node[1], node2[2]] result = node1 @ node2 @ gate_node R, Q = split_node_rq(result, left_edges=left_edges, right_edges=right_edges, left_name=node1.name, right_name=node2.name) self.tensors[site1] = R.tensor self.tensors[site2] = Q.tensor tw = self.backend.convert_to_tensor(0) if (self.center_position is not None): if (self.center_position in (site1, site2)): if (new_center_position is not None): self.position(site=new_center_position, normalize=False) else: self.position(site=self.center_position, normalize=False) else: self.center_position = None else: self.center_position = None return tw