def check_orthonormality(self, which: Text, site: int) -> Tensor: """ Check orthonormality of tensor at site `site`. Args: which: * if `'l'` or `'left'`: check left orthogonality * if `'r`' or `'right'`: check right orthogonality site: The site of the tensor. Returns: scalar `Tensor`: The L2 norm of the deviation from identity. Raises: ValueError: If which is different from 'l','left', 'r' or 'right'. """ if which not in ('l', 'left', 'r', 'right'): raise ValueError( "Wrong value `which`={}. " "`which` as to be 'l','left', 'r' or 'right.".format(which)) n1 = self.nodes[site] n2 = conj(n1) if which in ('l', 'left'): n1[0] ^ n2[0] n1[1] ^ n2[1] elif which in ('r', 'right'): n1[2] ^ n2[2] n1[1] ^ n2[1] result = n1 @ n2 return self.backend.norm( abs(result.tensor - self.backend.eye( N=result.shape[0], M=result.shape[1], dtype=self.dtype)))
def measure_local_operator(self, ops: List[Union[BaseNode, Tensor]], sites: Sequence[int]) -> List: """ Measure the expectation value of local operators `ops` site `sites`. Args: ops: A list Tensors of rank 2; the local operators to be measured. sites: Sites where `ops` act. Returns: List: measurements :math:`\\langle` `ops[n]`:math:`\\rangle` for n in `sites` Raises: ValueError if `len(ops) != len(sites)` """ if not len(ops) == len(sites): raise ValueError( 'measure_1site_ops: len(ops) has to be len(sites)!') right_envs = self.right_envs(sites) left_envs = self.left_envs(sites) res = [] for n, site in enumerate(sites): O = Node(ops[n], backend=self.backend) R = right_envs[site] L = left_envs[site] A = Node(self.nodes[site], backend=self.backend) conj_A = conj(A) O[1] ^ A[1] O[0] ^ conj_A[1] R[0] ^ A[2] R[1] ^ conj_A[2] L[0] ^ A[0] L[1] ^ conj_A[0] result = L @ A @ O @ conj_A @ R res.append(result.tensor) return res
def apply_transfer_operator(self, site: int, direction: Union[Text, int], matrix: Union[BaseNode, Tensor]) -> BaseNode: """ Compute the action of the MPS transfer-operator at site `site`. Args: site: a site of the MPS direction: * if `1, 'l'` or `'left'`: compute the left-action of the MPS transfer-operator at `site` on the input `matrix`. * if `-1, 'r'` or `'right'`: compute the right-action of the MPS transfer-operator at `site` on the input `matrix` matrix: A rank-2 tensor or matrix. Returns: `Node`: The result of applying the MPS transfer-operator to `matrix` """ mat = Node(matrix, backend=self.backend) node = self.get_node(site) conj_node = conj(node) node[1] ^ conj_node[1] if direction in (1, 'l', 'left'): mat[0] ^ node[0] mat[1] ^ conj_node[0] edge_order = [node[2], conj_node[2]] elif direction in (-1, 'r', 'right'): mat[0] ^ node[2] mat[1] ^ conj_node[2] edge_order = [node[0], conj_node[0]] result = mat @ node @ conj_node return result.reorder_edges(edge_order)
def apply_transfer_operator(self, site: int, direction: Union[Text, int], matrix: Tensor) -> Tensor: """ Compute the action of the MPS transfer-operator at site `site`. Args: site (int): a site of the MPS direction (str or int): if 1, 'l' or 'left': compute the left-action of the MPS transfer-operator at `site` on the input `matrix` if -1, 'r' or 'right': compute the right-action of the MPS transfer-operator at `site` on the input `matrix` matrix (Tensor): A rank-2 tensor or matrix. Returns: Tensor: the result of applying the MPS transfer-operator to `matrix` """ mat = Node(matrix, backend=self.backend.name) node = Node(self.nodes[site], backend=self.backend.name) conj_node = conj(node) node[1] ^ conj_node[1] if direction in (1, 'l', 'left'): mat[0] ^ node[0] mat[1] ^ conj_node[0] edge_order = [node[2], conj_node[2]] elif direction in (-1, 'r', 'right'): mat[0] ^ node[2] mat[1] ^ conj_node[2] edge_order = [node[0], conj_node[0]] result = mat @ node @ conj_node return result.reorder_edges(edge_order)
def right_envs(self, sites: Sequence[int]) -> Dict: """Compute right reduced density matrices for site `sites. This returns a dict `right_envs` mapping sites (int) to Tensors. `right_envs[site]` is the right-reduced density matrix to the right of site `site`. Args: sites (list of int): A list of sites of the MPS. Returns: `dict` mapping `int` to `Tensor`: The right-reduced density matrices at each site in `sites`. """ sites = np.array(sites) if len(sites) == 0: return {} n1 = np.min(sites) #check if all elements of `sites` are within allowed range if not np.all(sites < len(self)): raise ValueError( 'all elements of `sites` have to be < N = {}'.format( len(self))) if not np.all(sites >= -1): raise ValueError('all elements of `sites` have to be >= -1') # right-reduced density matrices to the right of `center_position` # (including center_position) are all identities right_sites = sites[sites >= self.center_position] right_envs = {} for site in right_sites: right_envs[site] = Node(self.backend.eye( N=self.nodes[site].shape[2], dtype=self.dtype), backend=self.backend) # right reduced density matrices at sites < center_position # have to be calculated from a network contraction if n1 < self.center_position: nodes = {} conj_nodes = {} for site in reversed(range(n1 + 1, self.center_position + 1)): nodes[site] = Node(self.nodes[site], backend=self.backend) conj_nodes[site] = conj(self.nodes[site]) nodes[self.center_position][2] ^ conj_nodes[ self.center_position][2] nodes[self.center_position][1] ^ conj_nodes[ self.center_position][1] for site in reversed(range(n1 + 1, self.center_position)): nodes[site][2] ^ nodes[site + 1][0] conj_nodes[site][2] ^ conj_nodes[site + 1][0] nodes[site][1] ^ conj_nodes[site][1] edges = {site: node[0] for site, node in nodes.items()} conj_edges = {site: node[0] for site, node in conj_nodes.items()} right_env = contract_between(nodes[self.center_position], conj_nodes[self.center_position]) if self.center_position - 1 in sites: right_env.reorder_edges([ edges[self.center_position], conj_edges[self.center_position] ]) right_envs[self.center_position - 1] = right_env for site in reversed(range(n1 + 1, self.center_position)): right_env = contract_between(right_env, nodes[site]) right_env = contract_between(right_env, conj_nodes[site]) if site - 1 in sites: right_env.reorder_edges([edges[site], conj_edges[site]]) right_envs[site - 1] = right_env return right_envs
def left_envs(self, sites: Sequence[int]) -> Dict: """Compute left reduced density matrices for site `sites`. This returns a dict `left_envs` mapping sites (int) to Tensors. `left_envs[site]` is the left-reduced density matrix to the left of site `site`. Args: sites (list of int): A list of sites of the MPS. Returns: `dict` mapping `int` to `Tensor`: The left-reduced density matrices at each site in `sites`. """ sites = np.array(sites) #enable logical indexing if len(sites) == 0: return {} n2 = np.max(sites) #check if all elements of `sites` are within allowed range if not np.all(sites <= len(self)): raise ValueError( 'all elements of `sites` have to be <= N = {}'.format( len(self))) if not np.all(sites >= 0): raise ValueError('all elements of `sites` have to be positive') # left-reduced density matrices to the left of `center_position` # (including center_position) are all identities left_sites = sites[sites <= self.center_position] left_envs = {} for site in left_sites: left_envs[site] = Node(self.backend.eye( N=self.nodes[site].shape[0], dtype=self.dtype), backend=self.backend) # left reduced density matrices at sites > center_position # have to be calculated from a network contraction if n2 > self.center_position: nodes = {} conj_nodes = {} for site in range(self.center_position, n2): nodes[site] = Node(self.nodes[site], backend=self.backend) conj_nodes[site] = conj(self.nodes[site]) nodes[self.center_position][0] ^ conj_nodes[ self.center_position][0] nodes[self.center_position][1] ^ conj_nodes[ self.center_position][1] for site in range(self.center_position + 1, n2): nodes[site][0] ^ nodes[site - 1][2] conj_nodes[site][0] ^ conj_nodes[site - 1][2] nodes[site][1] ^ conj_nodes[site][1] edges = {site: node[2] for site, node in nodes.items()} conj_edges = {site: node[2] for site, node in conj_nodes.items()} left_env = contract_between(nodes[self.center_position], conj_nodes[self.center_position]) left_env.reorder_edges([ edges[self.center_position], conj_edges[self.center_position] ]) if self.center_position + 1 in sites: left_envs[self.center_position + 1] = left_env for site in range(self.center_position + 1, n2): left_env = contract_between(left_env, nodes[site]) left_env = contract_between(left_env, conj_nodes[site]) if site + 1 in sites: left_env.reorder_edges([edges[site], conj_edges[site]]) left_envs[site + 1] = left_env return left_envs
def measure_two_body_correlator(self, op1: Union[BaseNode, Tensor], op2: Union[BaseNode, Tensor], site1: int, sites2: Sequence[int]) -> List: """ Compute the correlator :math:`\\langle` `op1[site1], op2[s]`:math:`\\rangle` between `site1` and all sites `s` in `sites2`. if `s==site1`, `op2[s]` will be applied first Args: op1: Tensor of rank 2; the local operator at `site1` op2: List of tensors of rank 2; the local operators at `sites2`. site1: The site where `op1` acts sites2: Sites where operators `op2` act. Returns: List: Correlator :math:`\\langle` `op1[site1], op2[s]`:math:`\\rangle` for `s` :math:`\\in` `sites2`. Raises: ValueError if `site1` is out of range """ N = len(self) if site1 < 0: raise ValueError( "Site site1 out of range: {} not between 0 <= site < N = {}.". format(site1, N)) sites2 = np.array(sites2) #enable logical indexing # we break the computation into two parts: # first we get all correlators <op2(site2) op1(site1)> with site2 < site1 # then all correlators <op1(site1) op2(site2)> with site1 >= site1 # get all sites smaller than site1 left_sites = sorted(sites2[sites2 < site1]) # get all sites larger than site1 right_sites = sorted(sites2[sites2 > site1]) # compute all neccessary right reduced # density matrices in one go. This is # more efficient than calling right_envs # for each site individually if right_sites: right_sites_mod = list({n % N for n in right_sites}) rs = self.right_envs([site1] + right_sites_mod) c = [] if left_sites: left_sites_mod = list({n % N for n in left_sites}) ls = self.left_envs(left_sites_mod + [site1]) A = Node(self.nodes[site1], backend=self.backend) O1 = Node(op1, backend=self.backend) conj_A = conj(A) R = rs[site1] R[0] ^ A[2] R[1] ^ conj_A[2] A[1] ^ O1[1] conj_A[1] ^ O1[0] R = ((R @ A) @ O1) @ conj_A n1 = np.min(left_sites) # -- A-------- # | | # compute op1(site1) | # | | # -- A*------- # and evolve it to the left by contracting tensors at site2 < site1 # if site2 is in `sites2`, calculate the observable # # ---A--........-- A-------- # | | | | # | op2(site2) op1(site1)| # | | | | # ---A--........-- A*------- for n in range(site1 - 1, n1 - 1, -1): if n in left_sites: A = Node(self.nodes[n % N], backend=self.backend) conj_A = conj(A) O2 = Node(op2, backend=self.backend) L = ls[n % N] L[0] ^ A[0] L[1] ^ conj_A[0] O2[0] ^ conj_A[1] O2[1] ^ A[1] R[0] ^ A[2] R[1] ^ conj_A[2] res = (((L @ A) @ O2) @ conj_A) @ R c.append(res.tensor) if n > n1: R = self.apply_transfer_operator(n % N, 'right', R) c = list(reversed(c)) # compute <op2(site1)op1(site1)> if site1 in sites2: O1 = Node(op1, backend=self.backend) O2 = Node(op2, backend=self.backend) L = ls[site1] R = rs[site1] A = Node(self.nodes[site1], backend=self.backend) conj_A = conj(A) O1[1] ^ O2[0] L[0] ^ A[0] L[1] ^ conj_A[0] R[0] ^ A[2] R[1] ^ conj_A[2] A[1] ^ O2[1] conj_A[1] ^ O1[0] O = O1 @ O2 res = (((L @ A) @ O) @ conj_A) @ R c.append(res.tensor) # compute <op1(site1) op2(site2)> for site1 < site2 right_sites = sorted(sites2[sites2 > site1]) if right_sites: A = Node(self.nodes[site1], backend=self.backend) conj_A = conj(A) L = ls[site1] O1 = Node(op1, backend=self.backend) L[0] ^ A[0] L[1] ^ conj_A[0] A[1] ^ O1[1] conj_A[1] ^ O1[0] L = L @ A @ O1 @ conj_A n2 = np.max(right_sites) # -- A-- # | | # compute | op1(site1) # | | # -- A*-- # and evolve it to the right by contracting tensors at site2 > site1 # if site2 is in `sites2`, calculate the observable # # ---A--........-- A-------- # | | | | # | op1(site1) op2(site2)| # | | | | # ---A--........-- A*------- for n in range(site1 + 1, n2 + 1): if n in right_sites: R = rs[n % N] A = Node(self.nodes[n % N], backend=self.backend) conj_A = conj(A) O2 = Node(op2, backend=self.backend) A[0] ^ L[0] conj_A[0] ^ L[1] O2[0] ^ conj_A[1] O2[1] ^ A[1] R[0] ^ A[2] R[1] ^ conj_A[2] res = L @ A @ O2 @ conj_A @ R c.append(res.tensor) if n < n2: L = self.apply_transfer_operator(n % N, 'left', L) return c