예제 #1
0
    def tensor_product(self, other: "QuOperator") -> "QuOperator":
        """Tensor product with another operator.

    Given two operators `A` and `B`, produces a new operator `AB` representing
    `A` ⊗ `B`. The `out_edges` (`in_edges`) of `AB` is simply the
    concatenation of the `out_edges` (`in_edges`) of `A.copy()` with that of
    `B.copy()`:

    `new_out_edges = [*out_edges_A_copy, *out_edges_B_copy]`
    `new_in_edges = [*in_edges_A_copy, *in_edges_B_copy]`

    Args:
      other: The other operator (`B`).
    Returns:
      The result (`AB`).
    """
        nodes_dict1, edges_dict1 = copy(self.nodes, False)
        nodes_dict2, edges_dict2 = copy(other.nodes, False)

        in_edges = ([edges_dict1[e] for e in self.in_edges] +
                    [edges_dict2[e] for e in other.in_edges])
        out_edges = ([edges_dict1[e] for e in self.out_edges] +
                     [edges_dict2[e] for e in other.out_edges])
        ref_nodes = ([n for _, n in nodes_dict1.items()] +
                     [n for _, n in nodes_dict2.items()])
        ignore_edges = ([edges_dict1[e] for e in self.ignore_edges] +
                        [edges_dict2[e] for e in other.ignore_edges])

        return quantum_constructor(out_edges, in_edges, ref_nodes,
                                   ignore_edges)
예제 #2
0
    def __matmul__(self, other: "QuOperator") -> "QuOperator":
        """The action of this operator on another.

    Given `QuOperator`s `A` and `B`, produces a new `QuOperator` for `A @ B`,
    where `A @ B` means: "the action of A, as a linear operator, on B".

    Under the hood, this produces copies of the tensor networks defining `A`
    and `B` and then connects the copies by hooking up the `in_edges` of
    `A.copy()` to the `out_edges` of `B.copy()`.
    """
        check_spaces(self.in_edges, other.out_edges)

        # Copy all nodes involved in the two operators.
        # We must do this separately for self and other, in case self and other
        # are defined via the same network components (e.g. if self === other).
        nodes_dict1, edges_dict1 = copy(self.nodes, False)
        nodes_dict2, edges_dict2 = copy(other.nodes, False)

        # connect edges to create network for the result
        for (e1, e2) in zip(self.in_edges, other.out_edges):
            _ = edges_dict1[e1] ^ edges_dict2[e2]

        in_edges = [edges_dict2[e] for e in other.in_edges]
        out_edges = [edges_dict1[e] for e in self.out_edges]
        ref_nodes = ([n for _, n in nodes_dict1.items()] +
                     [n for _, n in nodes_dict2.items()])
        ignore_edges = ([edges_dict1[e] for e in self.ignore_edges] +
                        [edges_dict2[e] for e in other.ignore_edges])

        return quantum_constructor(out_edges, in_edges, ref_nodes,
                                   ignore_edges)
예제 #3
0
    def adjoint(self) -> "QuOperator":
        """The adjoint of the operator.

    This creates a new `QuOperator` with complex-conjugate copies of all
    tensors in the network and with the input and output edges switched.
    """
        nodes_dict, edge_dict = copy(self.nodes, True)
        out_edges = [edge_dict[e] for e in self.in_edges]
        in_edges = [edge_dict[e] for e in self.out_edges]
        ref_nodes = [nodes_dict[n] for n in self.ref_nodes]
        ignore_edges = [edge_dict[e] for e in self.ignore_edges]
        return quantum_constructor(out_edges, in_edges, ref_nodes,
                                   ignore_edges)
예제 #4
0
    def partial_trace(
            self, subsystems_to_trace_out: Collection[int]) -> "QuOperator":
        """The partial trace of the operator.

    Subsystems to trace out are supplied as indices, so that dangling edges
    are connected to eachother as:
      `out_edges[i] ^ in_edges[i] for i in subsystems_to_trace_out`

    This does not modify the original network. The original ordering of the
    remaining subsystems is maintained.

    Args:
      subsystems_to_trace_out: Indices of subsystems to trace out.
    Returns:
      A new QuOperator or QuScalar representing the result.
    """
        out_edges_trace = [self.out_edges[i] for i in subsystems_to_trace_out]
        in_edges_trace = [self.in_edges[i] for i in subsystems_to_trace_out]

        check_spaces(in_edges_trace, out_edges_trace)

        nodes_dict, edge_dict = copy(self.nodes, False)
        for (e1, e2) in zip(out_edges_trace, in_edges_trace):
            edge_dict[e1] = edge_dict[e1] ^ edge_dict[e2]

        # get leftover edges in the original order
        out_edges_trace = set(out_edges_trace)
        in_edges_trace = set(in_edges_trace)
        out_edges = [
            edge_dict[e] for e in self.out_edges if e not in out_edges_trace
        ]
        in_edges = [
            edge_dict[e] for e in self.in_edges if e not in in_edges_trace
        ]
        ref_nodes = [n for _, n in nodes_dict.items()]
        ignore_edges = [edge_dict[e] for e in self.ignore_edges]

        return quantum_constructor(out_edges, in_edges, ref_nodes,
                                   ignore_edges)