Exemple #1
0
    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
Exemple #2
0
    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
Exemple #3
0
    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