Esempio n. 1
0
    def test_are_identical_pauli_words(self):
        """Tests for determining if two Pauli words have the same ``wires`` and ``name`` attributes."""

        pauli_word_1 = PauliX(0) @ PauliY(1)
        pauli_word_2 = PauliY(1) @ PauliX(0)
        pauli_word_3 = Tensor(PauliX(0), PauliY(1))
        pauli_word_4 = PauliX(1) @ PauliZ(2)

        assert are_identical_pauli_words(pauli_word_1, pauli_word_2)
        assert are_identical_pauli_words(pauli_word_1, pauli_word_3)
        assert not are_identical_pauli_words(pauli_word_1, pauli_word_4)
        assert not are_identical_pauli_words(pauli_word_3, pauli_word_4)
Esempio n. 2
0
    def test_binary_to_pauli_with_wire_map(self, vec, op):
        """Test conversion of Pauli in binary vector representation to operator form when
        ``wire_map`` is specified."""

        wire_map = {"alice": 0, "bob": 1, "ancilla": 2}

        assert are_identical_pauli_words(
            binary_to_pauli(vec, wire_map=wire_map), op)
Esempio n. 3
0
def pauli_mult(pauli_1, pauli_2, wire_map=None):
    """Multiply two Pauli words together and return the product as a Pauli word.

    Two Pauli operations can be multiplied together by taking the additive
    OR of their binary symplectic representations.

    Args:
        pauli_1 (.Operation): A Pauli word.
        pauli_2 (.Operation): A Pauli word to multiply with the first one.
        wire_map (dict[Union[str, int], int]): dictionary containing all wire labels used in the Pauli
            word as keys, and unique integer labels as their values. If no wire map is
            provided, the map will be constructed from the set of wires acted on
            by the input Pauli words.

    Returns:
        .Operation: The product of pauli_1 and pauli_2 as a Pauli word
        (ignoring the global phase).

    **Example**

    This function enables multiplication of Pauli group elements at the level of
    Pauli words, rather than matrices. For example,

    >>> from pennylane.grouping import pauli_mult
    >>> pauli_1 = qml.PauliX(0) @ qml.PauliZ(1)
    >>> pauli_2 = qml.PauliY(0) @ qml.PauliZ(1)
    >>> product = pauli_mult(pauli_1, pauli_2)
    >>> print(product)
    PauliZ(wires=[0])
    """

    if wire_map is None:
        wire_map = _wire_map_from_pauli_pair(pauli_1, pauli_2)

    # Check if pauli_1 and pauli_2 are the same; if so, the result is the Identity
    if are_identical_pauli_words(pauli_1, pauli_2):
        first_wire = list(pauli_1.wires)[0]
        return Identity(first_wire)

    # Compute binary symplectic representations
    pauli_1_binary = pauli_to_binary(pauli_1, wire_map=wire_map)
    pauli_2_binary = pauli_to_binary(pauli_2, wire_map=wire_map)

    bin_symp_1 = np.array([int(x) for x in pauli_1_binary])
    bin_symp_2 = np.array([int(x) for x in pauli_2_binary])

    # Shorthand for bitwise XOR of numpy arrays
    pauli_product = bin_symp_1 ^ bin_symp_2

    return binary_to_pauli(pauli_product, wire_map=wire_map)
Esempio n. 4
0
    def test_are_identical_pauli_words_non_pauli_word_catch(
            self, non_pauli_word):
        """Tests TypeError raise for when non-Pauli word Pennylane operators/operations are given
        as input to are_identical_pauli_words."""

        with pytest.raises(TypeError):
            are_identical_pauli_words(non_pauli_word, PauliZ(0) @ PauliZ(1))

        with pytest.raises(TypeError):
            are_identical_pauli_words(non_pauli_word, PauliZ(0) @ PauliZ(1))

        with pytest.raises(TypeError):
            are_identical_pauli_words(
                PauliX("a") @ Identity("b"), non_pauli_word)

        with pytest.raises(TypeError):
            are_identical_pauli_words(non_pauli_word, non_pauli_word)
Esempio n. 5
0
    def test_diagonalize_qwc_pauli_words(self, qwc_grouping, qwc_sol_tuple):
        """Tests for validating diagonalize_qwc_pauli_words solutions."""

        qwc_rot, diag_qwc_grouping = diagonalize_qwc_pauli_words(qwc_grouping)
        qwc_rot_sol, diag_qwc_grouping_sol = qwc_sol_tuple

        assert all([
            self.are_identical_rotation_gates(qwc_rot[i], qwc_rot_sol[i])
            for i in range(len(qwc_rot))
        ])
        assert all([
            are_identical_pauli_words(diag_qwc_grouping[i],
                                      diag_qwc_grouping_sol[i])
            for i in range(len(diag_qwc_grouping))
        ])
Esempio n. 6
0
    def test_commuting_partitioning(self, observables, com_partitions_sol):

        com_partitions = group_observables(observables, grouping_type="commuting")

        # assert the correct number of partitions:
        n_partitions = len(com_partitions_sol)
        assert len(com_partitions) == n_partitions
        # assert each partition is of the correct length:
        assert all(
            [len(com_partitions[i]) == len(com_partitions_sol[i]) for i in range(n_partitions)]
        )
        # assert each partition contains the same Pauli terms as the solution partition:
        for i, partition in enumerate(com_partitions):
            for j, pauli in enumerate(partition):
                assert are_identical_pauli_words(pauli, com_partitions_sol[i][j])
    def test_optimize_measurements_qwc_generic_case(
            self, observables, diagonalized_groupings_sol):
        """Generic test cases without coefficients."""

        diagonalized_groupings = optimize_measurements(
            observables, grouping="qwc", colouring_method="rlf")[1]

        # assert the correct number of partitions:
        n_partitions = len(diagonalized_groupings_sol)
        assert len(diagonalized_groupings) == n_partitions
        # assert each partition is of the correct length:
        assert all([
            len(diagonalized_groupings[i]) == len(
                diagonalized_groupings_sol[i]) for i in range(n_partitions)
        ])
        # assert each partition contains the same Pauli terms as the solution partition:
        for i, partition in enumerate(diagonalized_groupings):
            for j, pauli in enumerate(partition):
                assert are_identical_pauli_words(
                    pauli, diagonalized_groupings_sol[i][j])
Esempio n. 8
0
def diagonalize_qwc_pauli_words(qwc_grouping):
    """Diagonalizes a list of mutually qubit-wise commutative Pauli words.

    Args:
        qwc_grouping (list[Observable]): a list of observables containing mutually
            qubit-wise commutative Pauli words

    Returns:
        tuple:

            * list[Operation]: an instance of the qwc_rotation template which
              diagonalizes the qubit-wise commuting grouping
            * list[Observable]: list of Pauli string observables diagonal in
              the computational basis

    Raises:
        ValueError: if any 2 elements in the input QWC grouping are not qubit-wise commutative

    **Example**

    >>> qwc_group = [qml.PauliX(0) @ qml.PauliZ(1),
                     qml.PauliX(0) @ qml.PauliY(3),
                     qml.PauliZ(1) @ qml.PauliY(3)]
    >>> diagonalize_qwc_pauli_words(qwc_group)
    ([RY(-1.5707963267948966, wires=[0]), RX(1.5707963267948966, wires=[3])],
     [Tensor(PauliZ(wires=[0]), PauliZ(wires=[1])),
     Tensor(PauliZ(wires=[0]), PauliZ(wires=[3])),
     Tensor(PauliZ(wires=[1]), PauliZ(wires=[3]))])
    """
    m_paulis = len(qwc_grouping)
    all_wires = Wires.all_wires(
        [pauli_word.wires for pauli_word in qwc_grouping])
    wire_map = {label: ind for ind, label in enumerate(all_wires)}
    for i in range(m_paulis):
        for j in range(i + 1, m_paulis):
            if not is_qwc(
                    pauli_to_binary(qwc_grouping[i], wire_map=wire_map),
                    pauli_to_binary(qwc_grouping[j], wire_map=wire_map),
            ):
                raise ValueError(
                    "{} and {} are not qubit-wise commuting.".format(
                        qwc_grouping[i], qwc_grouping[j]))

    pauli_operators = []
    diag_terms = []

    paulis_with_identity = (qml.PauliX, qml.PauliY, qml.PauliZ, qml.Identity)
    for term in qwc_grouping:
        diag_terms.append(diagonalize_pauli_word(term))
        if isinstance(term, Tensor):
            for sigma in term.obs:
                if sigma.name != "Identity":
                    if not any([
                            are_identical_pauli_words(sigma, existing_pauli)
                            for existing_pauli in pauli_operators
                    ]):
                        pauli_operators.append(sigma)
        elif isinstance(term, paulis_with_identity):
            sigma = term
            if sigma.name != "Identity":
                if not any([
                        are_identical_pauli_words(sigma, existing_pauli)
                        for existing_pauli in pauli_operators
                ]):
                    pauli_operators.append(sigma)

    unitary = qwc_rotation(pauli_operators)

    return unitary, diag_terms
Esempio n. 9
0
    def test_binary_to_pauli_no_wire_map(self, vec, op):
        """Test conversion of Pauli in binary vector representation to operator form when no
        ``wire_map`` is specified."""

        assert are_identical_pauli_words(binary_to_pauli(vec), op)
Esempio n. 10
0
def group_observables(observables,
                      coefficients=None,
                      grouping_type="qwc",
                      method="rlf"):
    """Partitions a list of observables (Pauli operations and tensor products thereof) into
    groupings according to a binary relation (qubit-wise commuting, fully commuting, or
    anticommuting).

    Partitions are found by 1) mapping the list of observables to a graph where vertices represent
    observables and edges encode the binary relation, then 2) solving minimum clique cover for the
    graph using graph-coloring heuristic algorithms.

    **Example usage:**

    >>> observables = [qml.PauliY(0), qml.PauliX(0) @ qml.PauliX(1), qml.PauliZ(1)]
    >>> coefficients = [1.43, 4.21, 0.97]
    >>> obs_groupings, coeffs_groupings = group_observables(
                                                            observables,
                                                            coefficients,
                                                            'anticommuting',
                                                            'lf')
    >>> obs_groupings
    [[Tensor(PauliZ(wires=[1])),
      Tensor(PauliX(wires=[0]), PauliX(wires=[1]))],
     [Tensor(PauliY(wires=[0]))]]
    >>> coeffs_groupings
    [[0.97, 4.21], [1.43]]

    Args:
        observables (list[Observable]): a list of Pauli word `Observable` instances (Pauli
            operation instances and Tensor instances thereof)

    Keyword args:
        coefficients (list[scalar]): A list of scalar coefficients. If not specified,
            output `partitioned_coeffs` is not returned.
        grouping_type (str): The type of binary relation between Pauli words. Can be 'qwc',
            'commuting', or 'anticommuting'.
        method (str): the graph coloring heuristic to use in solving minimum clique cover, which
            can be 'lf' (Largest First) or 'rlf' (Recursive Largest First)

    Returns:
       partitioned_paulis (list[list[Observable]]): A list of the obtained groupings. Each grouping
            is itself a list of Pauli word `Observable` instances.
       partitioned_coeffs (list[list[scalar]]): A list of coefficient groupings. Each coefficient
           grouping is itself a list of the grouping's corresponding coefficients. This is only
           output if coefficients are specified.

    Raises:
        IndexError: if the input list of coefficients is not of the same length as the input list
            of Pauli words
    """

    if coefficients is not None:
        if len(coefficients) != len(observables):
            raise IndexError(
                "The coefficients list must be the same length as the observables list."
            )

    pauli_grouping = PauliGroupingStrategy(observables,
                                           grouping_type=grouping_type,
                                           graph_colourer=method)
    partitioned_paulis = pauli_grouping.colour_pauli_graph()

    if coefficients is None:
        return partitioned_paulis

    partitioned_coeffs = [[0] * len(g) for g in partitioned_paulis]

    for i, partition in enumerate(partitioned_paulis):
        for j, pauli_word in enumerate(partition):
            for observable in observables:
                if are_identical_pauli_words(pauli_word, observable):
                    partitioned_coeffs[i][j] = coefficients[observables.index(
                        observable)]
                    break

    return partitioned_paulis, partitioned_coeffs
Esempio n. 11
0
    def test_diagonalize_pauli_word(self, pauli_word, diag_pauli_word):
        """Tests `diagonalize_pauli_word` returns the correct diagonal Pauli word in computational
        basis for a given Pauli word."""

        assert are_identical_pauli_words(diagonalize_pauli_word(pauli_word),
                                         diag_pauli_word)
Esempio n. 12
0
def group_observables(observables, coefficients=None, grouping_type="qwc", method="rlf"):
    """Partitions a list of observables (Pauli operations and tensor products thereof) into
    groupings according to a binary relation (qubit-wise commuting, fully-commuting, or
    anticommuting).

    Partitions are found by 1) mapping the list of observables to a graph where vertices represent
    observables and edges encode the binary relation, then 2) solving minimum clique cover for the
    graph using graph-colouring heuristic algorithms.

    Args:
        observables (list[Observable]): a list of Pauli word ``Observable`` instances (Pauli
            operation instances and :class:`~.Tensor` instances thereof)
        coefficients (tensor_like): A tensor or list of coefficients. If not specified,
            output ``partitioned_coeffs`` is not returned.
        grouping_type (str): The type of binary relation between Pauli words.
            Can be ``'qwc'``, ``'commuting'``, or ``'anticommuting'``.
        method (str): the graph coloring heuristic to use in solving minimum clique cover, which
            can be ``'lf'`` (Largest First) or ``'rlf'`` (Recursive Largest First)

    Returns:
       tuple:

           * list[list[Observable]]: A list of the obtained groupings. Each grouping
             is itself a list of Pauli word ``Observable`` instances.
           * list[tensor_like]: A list of coefficient groupings. Each coefficient
             grouping is itself a tensor or list of the grouping's corresponding coefficients. This is only
             returned if coefficients are specified.

    Raises:
        IndexError: if the input list of coefficients is not of the same length as the input list
            of Pauli words

    **Example**

    >>> obs = [qml.PauliY(0), qml.PauliX(0) @ qml.PauliX(1), qml.PauliZ(1)]
    >>> coeffs = [1.43, 4.21, 0.97]
    >>> obs_groupings, coeffs_groupings = group_observables(obs, coeffs, 'anticommuting', 'lf')
    >>> obs_groupings
    [[PauliZ(wires=[1]), PauliX(wires=[0]) @ PauliX(wires=[1])],
     [PauliY(wires=[0])]]
    >>> coeffs_groupings
    [[0.97, 4.21], [1.43]]
    """

    if coefficients is not None:
        if qml.math.shape(coefficients)[0] != len(observables):
            raise IndexError(
                "The coefficients list must be the same length as the observables list."
            )

    pauli_grouping = PauliGroupingStrategy(
        observables, grouping_type=grouping_type, graph_colourer=method
    )
    partitioned_paulis = pauli_grouping.colour_pauli_graph()

    if coefficients is None:
        return partitioned_paulis

    partitioned_coeffs = [
        qml.math.cast_like([0] * len(g), coefficients) for g in partitioned_paulis
    ]

    observables = copy(observables)
    # we cannot delete elements from the coefficients tensor, so we
    # use a proxy list memorising the indices for this logic
    coeff_indices = list(range(qml.math.shape(coefficients)[0]))
    for i, partition in enumerate(partitioned_paulis):
        indices = []
        for pauli_word in partition:
            # find index of this pauli word in remaining original observables,
            for observable in observables:
                if are_identical_pauli_words(pauli_word, observable):
                    ind = observables.index(observable)
                    indices.append(coeff_indices[ind])
                    observables.pop(ind)
                    coeff_indices.pop(ind)
                    break

        # add a tensor of coefficients to the grouped coefficients
        partitioned_coeffs[i] = qml.math.take(coefficients, indices, axis=0)

    # make sure the output is of the same format as the input
    # for these two frequent cases
    if isinstance(coefficients, list):
        partitioned_coeffs = [list(p) for p in partitioned_coeffs]

    return partitioned_paulis, partitioned_coeffs