Пример #1
0
    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)))
Пример #2
0
    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
Пример #3
0
    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)
Пример #4
0
 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)
Пример #5
0
    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
Пример #6
0
    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
Пример #7
0
    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