def test_binary_repr_custom_wire_map(self): """Tests that the ``binary_repr`` method sets a custom wire map correctly.""" observables = [Identity("alice"), Identity("bob"), Identity("charlie")] grouping_instance = PauliGroupingStrategy(observables, "anticommuting") n_qubits = 3 wire_map = {"alice": 1, "bob": 0, "charlie": 2} _ = grouping_instance.binary_repr(n_qubits, wire_map) assert grouping_instance._wire_map == wire_map
def test_one_qubit_pauli_group_valid_float_input(self): """Test that the single-qubit Pauli group is constructed correctly when a float that represents an integer is passed.""" expected_pg_1 = [Identity(0), PauliZ(0), PauliX(0), PauliY(0)] pg_1 = list(pauli_group(1.0)) assert all([expected.compare(obtained) for expected, obtained in zip(expected_pg_1, pg_1)])
def test_one_qubit_pauli_group_integer_wire_map(self): """Test that the single-qubit Pauli group is constructed correctly with a wire labeled by an integer.""" expected_pg_1 = [Identity(0), PauliZ(0), PauliX(0), PauliY(0)] pg_1 = list(pauli_group(1)) assert all([expected.compare(obtained) for expected, obtained in zip(expected_pg_1, pg_1)])
def test_group_observables_exception(self): """Tests that the ``group_observables`` function raises an exception if the lengths of coefficients and observables do not agree.""" observables = [Identity(0), PauliX(1)] coefficients = [0.5] with pytest.raises(IndexError, match="must be the same length"): group_observables(observables, coefficients)
def test_two_qubit_pauli_group(self): """Test that the two-qubit Pauli group is constructed correctly.""" # With no wire map; ordering is based on construction from binary representation wire_map = {"a": 0, "b": 1} expected_pg_2 = [ Identity("a"), PauliZ("b"), PauliZ("a"), PauliZ("a") @ PauliZ("b"), PauliX("b"), PauliY("b"), PauliZ("a") @ PauliX("b"), PauliZ("a") @ PauliY("b"), PauliX("a"), PauliX("a") @ PauliZ("b"), PauliY("a"), PauliY("a") @ PauliZ("b"), PauliX("a") @ PauliX("b"), PauliX("a") @ PauliY("b"), PauliY("a") @ PauliX("b"), PauliY("a") @ PauliY("b"), ] pg_2 = list(pauli_group(2, wire_map=wire_map)) assert all([expected.compare(obtained) for expected, obtained in zip(expected_pg_2, pg_2)])
def test_is_qwc(self): """Determining if two Pauli words are qubit-wise commuting.""" n_qubits = 3 wire_map = {0: 0, "a": 1, "b": 2} p1_vec = pauli_to_binary(PauliX(0) @ PauliY("a"), wire_map=wire_map) p2_vec = pauli_to_binary(PauliX(0) @ Identity("a") @ PauliX("b"), wire_map=wire_map) p3_vec = pauli_to_binary(PauliX(0) @ PauliZ("a") @ Identity("b"), wire_map=wire_map) identity = pauli_to_binary(Identity("a") @ Identity(0), wire_map=wire_map) assert is_qwc(p1_vec, p2_vec) assert not is_qwc(p1_vec, p3_vec) assert is_qwc(p2_vec, p3_vec) assert (is_qwc(p1_vec, identity) == is_qwc(p2_vec, identity) == is_qwc( p3_vec, identity) == is_qwc(identity, identity) == True)
def test_observables_to_binary_matrix(self): """Test conversion of list of Pauli word operators to representation as a binary matrix.""" observables = [Identity(1), PauliX(1), PauliZ(0) @ PauliZ(1)] binary_observables = np.array([[0.0, 1.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 1.0], [0.0, 0.0, 1.0]]).T assert (observables_to_binary_matrix(observables) == binary_observables ).all()
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.""" assert pytest.raises(TypeError, are_identical_pauli_words, (non_pauli_word, PauliZ(0) @ PauliZ(1))) assert pytest.raises(TypeError, are_identical_pauli_words, (PauliX("a") @ Identity("b"), non_pauli_word)) assert pytest.raises(TypeError, are_identical_pauli_words, (non_pauli_word, non_pauli_word))
def test_observables_to_binary_matrix_n_qubits_arg(self): """Tests if ValueError is raised when specified n_qubits is not large enough to support the number of distinct wire labels in input observables.""" observables = [ Identity(1) @ PauliZ("a"), PauliX(1), PauliZ(0) @ PauliZ(2) ] n_qubits_invalid = 3 assert pytest.raises(ValueError, observables_to_binary_matrix, observables, n_qubits_invalid)
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)
def test_one_qubit_pauli_group_string_wire_map(self): """Test that the single-qubit Pauli group is constructed correctly with a wire labeled by a string.""" wire_map = {"qubit": 0} expected_pg_1_wires = [ Identity("qubit"), PauliZ("qubit"), PauliX("qubit"), PauliY("qubit"), ] pg_1_wires = list(pauli_group(1, wire_map=wire_map)) assert all( [ expected.compare(obtained) for expected, obtained in zip(expected_pg_1_wires, pg_1_wires) ] )
import numpy as np from pennylane import Identity, PauliX, PauliY, PauliZ, Hadamard, Hermitian, U3 from pennylane.operation import Tensor from pennylane.wires import Wires from pennylane.grouping.utils import ( is_pauli_word, are_identical_pauli_words, pauli_to_binary, binary_to_pauli, is_qwc, observables_to_binary_matrix, qwc_complement_adj_matrix, ) non_pauli_words = [ PauliX(0) @ Hadamard(1) @ Identity(2), Hadamard("a"), U3(0.1, 1, 1, wires="a"), Hermitian(np.array([[3.2, 1.1 + 0.6j], [1.1 - 0.6j, 3.2]]), wires="a") @ PauliX("b"), ] class TestGroupingUtils: """Basic usage and edge-case tests for the measurement optimization utility functions.""" ops_to_vecs_explicit_wires = [ (PauliX(0) @ PauliY(1) @ PauliZ(2), np.array([1, 1, 0, 0, 1, 1])), (PauliZ(0) @ PauliY(2), np.array([0, 1, 1, 1])), (PauliY(1) @ PauliX(2), np.array([1, 1, 1, 0])), (Identity(0), np.zeros(2)),
class TestOptimizeMeasurements: """Tests for the `optimize_measurements` function.""" observables_diagonalized = [ ( [PauliY(0), PauliX(0) @ PauliX(1), PauliZ(1)], [ [PauliZ(wires=[0]) @ PauliZ(wires=[1])], [PauliZ(wires=[0]), PauliZ(wires=[1])], ], ), ( [ Identity(0), PauliX(1) @ PauliY(2), PauliY(3) @ PauliX(1) @ PauliZ(2), PauliY(4) @ PauliZ(1), PauliZ(2), ], [ [Identity(wires=[0]), PauliZ(wires=[1]) @ PauliZ(wires=[2])], [ PauliZ(wires=[1]) @ PauliZ(wires=[2]) @ PauliZ(wires=[3]), PauliZ(wires=[2]), ], [PauliZ(wires=[1]) @ PauliZ(wires=[4])], ], ), ( [ PauliX("a"), PauliX("a") @ PauliY(1), PauliZ("b") @ PauliY("c") @ Identity("a"), PauliZ("a") @ PauliZ("b"), ], [ [ PauliZ(wires=["b"]) @ PauliZ(wires=["c"]), PauliZ(wires=["a"]) @ PauliZ(wires=["b"]), ], [PauliZ(wires=["a"]), PauliZ(wires=["a"]) @ PauliZ(wires=[1])], ], ), ] @pytest.mark.parametrize("observables,diagonalized_groupings_sol", observables_diagonalized) 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]) def test_optimize_measurements_qwc_generic_case_with_coefficients(self): """Tests if coefficients are properly re-structured.""" observables = [PauliY(0), PauliX(0) @ PauliX(1), PauliZ(1)] coefficients = [1.43, 4.21, 0.97] diagonalized_groupings_sol = [ [PauliZ(wires=[0]) @ PauliZ(wires=[1])], [PauliZ(wires=[0]), PauliZ(wires=[1])], ] grouped_coeffs_sol = [[4.21], [1.43, 0.97]] grouped_coeffs = optimize_measurements(observables, coefficients, grouping="qwc", colouring_method="rlf")[2] assert len(grouped_coeffs) == len(grouped_coeffs) assert all(grouped_coeffs[i] == grouped_coeffs_sol[i] for i in range(len(grouped_coeffs_sol))) def test_optimize_measurements_not_implemented_catch(self): """Tests that NotImplementedError is raised for methods other than 'qwc'.""" observables = [PauliY(0), PauliX(0) @ PauliX(1), PauliZ(1)] grouping = "anticommuting" assert pytest.raises(NotImplementedError, optimize_measurements, observables, grouping=grouping)
class TestMeasurementTransformations: """Tests for the functions involved in obtaining post-rotations necessary in the measurement optimization schemes implemented in :mod:`grouping`.""" def are_identical_rotation_gates(self, gate_1, gate_2, param_tol=1e-6): """Checks whether the two input gates are identical up to a certain threshold in their parameters. Arguments: gate_1 (Union[RX, RY, RZ]): the first single-qubit rotation gate gate_2 (Union[RX, RY, RZ]): the second single-qubit rotation gate Keyword arguments: param_tol (float): the absolute tolerance for considering whether two gates parameter values are the same Returns: bool: whether the input rotation gates are identical up to the parameter tolerance """ return (gate_1.wires == gate_2.wires and np.allclose( gate_1.parameters, gate_2.parameters, atol=param_tol, rtol=0) and gate_1.name == gate_2.name) qwc_rotation_io = [ ([PauliX(0), PauliZ(1), PauliZ(3)], [RY(-np.pi / 2, wires=[0])]), ([Identity(0), PauliZ(1)], []), ( [PauliX(2), PauliY(0), Identity(1)], [RY(-np.pi / 2, wires=[2]), RX(np.pi / 2, wires=[0])], ), ( [PauliZ("a"), PauliX("b"), PauliY(0)], [RY(-np.pi / 2, wires=["b"]), RX(np.pi / 2, wires=[0])], ), ] @pytest.mark.parametrize("pauli_ops,qwc_rot_sol", qwc_rotation_io) def test_qwc_rotation(self, pauli_ops, qwc_rot_sol): """Tests that the correct single-qubit post-rotation gates are obtained for the input list of Pauli operators.""" qwc_rot = qwc_rotation(pauli_ops) assert all([ self.are_identical_rotation_gates(qwc_rot[i], qwc_rot_sol[i]) for i in range(len(qwc_rot)) ]) invalid_qwc_rotation_inputs = [ [PauliX(0), PauliY(1), Hadamard(2)], [PauliX(0) @ PauliY(1), PauliZ(1), Identity(2)], [RX(1, wires="a"), PauliX("b")], ] @pytest.mark.parametrize("bad_input", invalid_qwc_rotation_inputs) def test_invalid_qwc_rotation_input_catch(self, bad_input): """Verifies that a TypeError is raised when the input to qwc_rotations is not a list of single Pauli operators.""" assert pytest.raises(TypeError, qwc_rotation, bad_input) diagonalized_paulis = [ (Identity("a"), Identity("a")), (PauliX(1) @ Identity(2) @ PauliZ("b"), PauliZ(1) @ PauliZ("b")), (PauliZ(1) @ PauliZ(2), PauliZ(1) @ PauliZ(2)), (PauliX("a") @ PauliY("b") @ PauliY("c"), PauliZ("a") @ PauliZ("b") @ PauliZ("c")), ] @pytest.mark.parametrize("pauli_word, diag_pauli_word", diagonalized_paulis) 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) non_pauli_words = [ PauliX(0) @ Hadamard(1) @ Identity(2), Hadamard("a"), U3(0.1, 1, 1, wires="a"), Hermitian(np.array([[3.2, 1.1 + 0.6j], [1.1 - 0.6j, 3.2]]), wires="a") @ PauliX("b"), ] @pytest.mark.parametrize("non_pauli_word", non_pauli_words) def test_diagonalize_pauli_word_catch_non_pauli_word(self, non_pauli_word): assert pytest.raises(TypeError, diagonalize_pauli_word, non_pauli_word) qwc_diagonalization_io = [ ( [PauliX(0) @ PauliY(1), PauliX(0) @ PauliZ(2)], ( [RY(-np.pi / 2, wires=[0]), RX(np.pi / 2, wires=[1])], [ PauliZ(wires=[0]) @ PauliZ(wires=[1]), PauliZ(wires=[0]) @ PauliZ(wires=[2]) ], ), ), ( [ PauliX(2) @ Identity(0), PauliY(1), PauliZ(0) @ PauliY(1), PauliX(2) @ PauliY(1) ], ( [RY(-np.pi / 2, wires=[2]), RX(np.pi / 2, wires=[1])], [ PauliZ(wires=[2]), PauliZ(wires=[1]), PauliZ(wires=[0]) @ PauliZ(wires=[1]), PauliZ(wires=[2]) @ PauliZ(wires=[1]), ], ), ), ( [ PauliZ("a") @ PauliY("b") @ PauliZ("c"), PauliY("b") @ PauliZ("d") ], ( [RX(np.pi / 2, wires=["b"])], [ PauliZ(wires=["a"]) @ PauliZ(wires=["b"]) @ PauliZ(wires=["c"]), PauliZ(wires=["b"]) @ PauliZ(wires=["d"]), ], ), ), ([PauliX("a")], ([RY(-np.pi / 2, wires=["a"])], [PauliZ(wires=["a"])])), ] @pytest.mark.parametrize("qwc_grouping,qwc_sol_tuple", qwc_diagonalization_io) 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)) ]) not_qwc_groupings = [ [PauliX("a"), PauliY("a")], [ PauliZ(0) @ Identity(1), PauliZ(0) @ PauliZ(1), PauliX(0) @ Identity(1) ], [PauliX("a") @ PauliX(0), PauliZ(0) @ PauliZ("a")], ] @pytest.mark.parametrize("not_qwc_grouping", not_qwc_groupings) def test_diagonalize_qwc_pauli_words_catch_when_not_qwc( self, not_qwc_grouping): """Test for ValueError raise when diagonalize_qwc_pauli_words is not given a list of qubit-wise commuting Pauli words.""" assert pytest.raises(ValueError, diagonalize_qwc_pauli_words, not_qwc_grouping)
class TestPauliGroup: """Testing for Pauli group construction and manipulation functions.""" def test_pauli_group_size(self): """Test that the size of the returned Pauli group is correct.""" for n_qubits in range(1, 5): pg = list(pauli_group(n_qubits)) assert len(pg) == 4 ** n_qubits def test_pauli_group_invalid_input(self): """Test that invalid inputs to the Pauli group are handled correctly.""" with pytest.raises(TypeError, match="Must specify an integer number"): pauli_group("3") with pytest.raises(ValueError, match="Number of qubits must be at least 1"): pauli_group(-1) def test_one_qubit_pauli_group_integer_wire_map(self): """Test that the single-qubit Pauli group is constructed correctly with a wire labeled by an integer.""" expected_pg_1 = [Identity(0), PauliZ(0), PauliX(0), PauliY(0)] pg_1 = list(pauli_group(1)) assert all([expected.compare(obtained) for expected, obtained in zip(expected_pg_1, pg_1)]) def test_one_qubit_pauli_group_valid_float_input(self): """Test that the single-qubit Pauli group is constructed correctly when a float that represents an integer is passed.""" expected_pg_1 = [Identity(0), PauliZ(0), PauliX(0), PauliY(0)] pg_1 = list(pauli_group(1.0)) assert all([expected.compare(obtained) for expected, obtained in zip(expected_pg_1, pg_1)]) def test_one_qubit_pauli_group_string_wire_map(self): """Test that the single-qubit Pauli group is constructed correctly with a wire labeled by a string.""" wire_map = {"qubit": 0} expected_pg_1_wires = [ Identity("qubit"), PauliZ("qubit"), PauliX("qubit"), PauliY("qubit"), ] pg_1_wires = list(pauli_group(1, wire_map=wire_map)) assert all( [ expected.compare(obtained) for expected, obtained in zip(expected_pg_1_wires, pg_1_wires) ] ) def test_two_qubit_pauli_group(self): """Test that the two-qubit Pauli group is constructed correctly.""" # With no wire map; ordering is based on construction from binary representation wire_map = {"a": 0, "b": 1} expected_pg_2 = [ Identity("a"), PauliZ("b"), PauliZ("a"), PauliZ("a") @ PauliZ("b"), PauliX("b"), PauliY("b"), PauliZ("a") @ PauliX("b"), PauliZ("a") @ PauliY("b"), PauliX("a"), PauliX("a") @ PauliZ("b"), PauliY("a"), PauliY("a") @ PauliZ("b"), PauliX("a") @ PauliX("b"), PauliX("a") @ PauliY("b"), PauliY("a") @ PauliX("b"), PauliY("a") @ PauliY("b"), ] pg_2 = list(pauli_group(2, wire_map=wire_map)) assert all([expected.compare(obtained) for expected, obtained in zip(expected_pg_2, pg_2)]) @pytest.mark.parametrize( "pauli_word_1,pauli_word_2,wire_map,expected_product", [ (PauliX(0), Identity(0), {0: 0}, PauliX(0)), (PauliZ(0), PauliY(0), {0: 0}, PauliX(0)), (PauliZ(0), PauliZ(0), {0: 0}, Identity(0)), (Identity("a"), Identity("b"), None, Identity("a")), (PauliZ("b") @ PauliY("a"), PauliZ("b") @ PauliY("a"), None, Identity("b")), ( PauliZ("b") @ PauliY("a"), PauliZ("b") @ PauliY("a"), {"b": 0, "a": 1}, Identity("b"), ), ( PauliZ(0) @ PauliY(1), PauliX(0) @ PauliZ(1), {0: 0, 1: 1}, PauliY(0) @ PauliX(1), ), (PauliZ(0) @ PauliY(1), PauliX(1) @ PauliY(0), {0: 0, 1: 1}, PauliX(0) @ PauliZ(1)), ( PauliZ(0) @ PauliY(3) @ PauliZ(1), PauliX(1) @ PauliX(2) @ PauliY(0), None, PauliX(0) @ PauliY(1) @ PauliX(2) @ PauliY(3), ), ( PauliX(0) @ PauliX(2), PauliX(0) @ PauliZ(2), None, PauliY(2), ), (PauliZ("a"), PauliX("b"), {"a": 0, "b": 1}, PauliZ("a") @ PauliX("b")), ( PauliZ("a"), PauliX("e"), {"a": 0, "b": 1, "c": 2, "d": 3, "e": 4}, PauliZ("a") @ PauliX("e"), ), (PauliZ("a"), PauliY("e"), None, PauliZ("a") @ PauliY("e")), ( PauliZ(0) @ PauliZ(2) @ PauliZ(4), PauliZ(0) @ PauliY(1) @ PauliX(3), None, PauliZ(2) @ PauliY(1) @ PauliZ(4) @ PauliX(3), ), ( PauliZ(0) @ PauliZ(4) @ PauliZ(2), PauliZ(0) @ PauliY(1) @ PauliX(3), {0: 0, 2: 1, 4: 2, 1: 3, 3: 4}, PauliZ(2) @ PauliY(1) @ PauliZ(4) @ PauliX(3), ), ( PauliZ(0) @ PauliY(3) @ PauliZ(1), PauliX(1) @ PauliX(2) @ PauliY(0), None, PauliX(0) @ PauliY(3) @ PauliY(1) @ PauliX(2), ), ], ) def test_pauli_mult(self, pauli_word_1, pauli_word_2, wire_map, expected_product): """Test that Pauli words are multiplied together correctly.""" obtained_product = pauli_mult(pauli_word_1, pauli_word_2, wire_map=wire_map) assert obtained_product.compare(expected_product) @pytest.mark.parametrize( "pauli_word_1,pauli_word_2,wire_map,expected_phase", [ (PauliX(0), Identity(0), {0: 0}, 1), (PauliZ(0), PauliY(0), {0: 0}, -1j), (PauliZ(0), PauliZ(0), {0: 0}, 1), (Identity("a"), Identity("b"), None, 1), (PauliZ("b") @ PauliY("a"), PauliZ("b") @ PauliY("a"), None, 1), (PauliZ(0), PauliY("b"), None, 1), ( PauliZ("a") @ PauliY("b"), PauliX("a") @ PauliZ("b"), {"a": 0, "b": 1}, -1, ), (PauliX(0) @ PauliX(2), PauliX(0) @ PauliZ(2), None, -1j), ( PauliX(0) @ PauliY(1) @ PauliZ(2), PauliY(0) @ PauliY(1), {0: 0, 1: 1, 2: 2}, 1j, ), (PauliZ(0) @ PauliY(1), PauliX(1) @ PauliY(0), {0: 0, 1: 1}, -1), (PauliZ(0) @ PauliY(1), PauliX(1) @ PauliY(0), {1: 0, 0: 1}, -1), (PauliZ(0) @ PauliY(3) @ PauliZ(1), PauliX(1) @ PauliX(2) @ PauliY(0), None, 1), ], ) def test_pauli_mult_with_phase(self, pauli_word_1, pauli_word_2, wire_map, expected_phase): """Test that multiplication including phases works as expected.""" _, obtained_phase = pauli_mult_with_phase(pauli_word_1, pauli_word_2, wire_map=wire_map) assert obtained_phase == expected_phase
def string_to_pauli_word(pauli_string, wire_map=None): """Convert a string in terms of ``'I'``, ``'X'``, ``'Y'``, and ``'Z'`` into a Pauli word for the given wire map. Args: pauli_string (str): A string of characters consisting of ``'I'``, ``'X'``, ``'Y'``, and ``'Z'`` indicating a Pauli word. 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 Returns: .Observable: The Pauli word representing of ``pauli_string`` on the wires enumerated in the wire map. **Example** >>> wire_map = {'a' : 0, 'b' : 1, 'c' : 2} >>> string_to_pauli_word('XIY', wire_map=wire_map) PauliX(wires=['a']) @ PauliY(wires=['c']) """ character_map = {"I": Identity, "X": PauliX, "Y": PauliY, "Z": PauliZ} if not isinstance(pauli_string, str): raise TypeError( f"Input to string_to_pauli_word must be string, obtained {pauli_string}" ) # String can only consist of I, X, Y, Z if any(char not in character_map.keys() for char in pauli_string): raise ValueError( "Invalid characters encountered in string_to_pauli_word " f"string {pauli_string}. Permitted characters are 'I', 'X', 'Y', and 'Z'" ) # If no wire map is provided, construct one using integers based on the length of the string if wire_map is None: wire_map = {x: x for x in range(len(pauli_string))} if len(pauli_string) != len(wire_map): raise ValueError( "Wire map and pauli_string must have the same length to convert " "from string to Pauli word.") # Special case: all-identity Pauli if pauli_string == "I" * len(wire_map): first_wire = list(wire_map.keys())[0] return Identity(wire_map[first_wire]) pauli_word = None for wire_name, wire_idx in wire_map.items(): pauli_char = pauli_string[wire_idx] # Don't care about the identity if pauli_char == "I": continue if pauli_word is not None: pauli_word = pauli_word @ character_map[pauli_char](wire_name) else: pauli_word = character_map[pauli_char](wire_name) return pauli_word
class TestOptimizeMeasurements: """Tests for the ``optimize_measurements`` function.""" observables_diagonalized = [ ( [PauliY(0), PauliX(0) @ PauliX(1), PauliZ(1)], [ [PauliZ(wires=[0]) @ PauliZ(wires=[1])], [PauliZ(wires=[0]), PauliZ(wires=[1])], ], ), ( [ Identity(0), PauliX(1) @ PauliY(2), PauliY(3) @ PauliX(1) @ PauliZ(2), PauliY(4) @ PauliZ(1), PauliZ(2), ], [ [Identity(wires=[0]), PauliZ(wires=[1]) @ PauliZ(wires=[2])], [ PauliZ(wires=[1]) @ PauliZ(wires=[2]) @ PauliZ(wires=[3]), PauliZ(wires=[2]), ], [PauliZ(wires=[1]) @ PauliZ(wires=[4])], ], ), ( [ PauliX("a"), PauliX("a") @ PauliY(1), PauliZ("b") @ PauliY("c") @ Identity("a"), PauliZ("a") @ PauliZ("b"), ], [ [ PauliZ(wires=["b"]) @ PauliZ(wires=["c"]), PauliZ(wires=["a"]) @ PauliZ(wires=["b"]), ], [PauliZ(wires=["a"]), PauliZ(wires=["a"]) @ PauliZ(wires=[1])], ], ), ] @pytest.mark.parametrize("observables,diagonalized_groupings_sol", observables_diagonalized) 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]) def test_optimize_measurements_qwc_generic_case_with_coefficients(self): """Tests if coefficients are properly re-structured.""" observables = [PauliY(0), PauliX(0) @ PauliX(1), PauliZ(1)] coefficients = [1.43, 4.21, 0.97] diagonalized_groupings_sol = [ [PauliZ(wires=[0]) @ PauliZ(wires=[1])], [PauliZ(wires=[0]), PauliZ(wires=[1])], ] grouped_coeffs_sol = [[4.21], [1.43, 0.97]] grouped_coeffs = optimize_measurements(observables, coefficients, grouping="qwc", colouring_method="rlf")[2] assert len(grouped_coeffs) == len(grouped_coeffs) assert all(grouped_coeffs[i] == grouped_coeffs_sol[i] for i in range(len(grouped_coeffs_sol))) @pytest.mark.parametrize( "obs", [ [PauliZ(0), PauliZ(0)], [PauliX(0) @ PauliX(1), PauliX(0) @ PauliX(1)], [PauliX(0) @ PauliX(1), PauliX(1) @ PauliX(0)], ], ) def test_optimize_measurements_qwc_term_multiple_times(self, obs): """Tests if coefficients are properly re-structured even if the same terms appear multiple times. Although it should be fair to assume that the terms are unique in the Hamiltonian, making sure that grouping happens even with terms appearing multiple times can ensure that there is no unexpected behaviour. """ coefficients = [1.43, 4.21] diagonalized_groupings_sol = [obs] grouped_coeffs_sol = [[1.43, 4.21]] grouped_coeffs = optimize_measurements(obs, coefficients, grouping="qwc", colouring_method="rlf")[2] assert len(grouped_coeffs) == len(grouped_coeffs) assert all(grouped_coeffs[i] == grouped_coeffs_sol[i] for i in range(len(grouped_coeffs_sol))) def test_optimize_measurements_not_implemented_catch(self): """Tests that NotImplementedError is raised for methods other than ``'qwc'``.""" observables = [PauliY(0), PauliX(0) @ PauliX(1), PauliZ(1)] grouping = "anticommuting" with pytest.raises( NotImplementedError, match= "Measurement reduction by 'anticommuting' grouping not implemented", ): optimize_measurements(observables, grouping=grouping)
class TestGroupingUtils: """Basic usage and edge-case tests for the measurement optimization utility functions.""" ops_to_vecs_explicit_wires = [ (PauliX(0) @ PauliY(1) @ PauliZ(2), np.array([1, 1, 0, 0, 1, 1])), (PauliZ(0) @ PauliY(2), np.array([0, 1, 1, 1])), (PauliY(1) @ PauliX(2), np.array([1, 1, 1, 0])), (Identity(0), np.zeros(2)), ] @pytest.mark.parametrize("op,vec", ops_to_vecs_explicit_wires) def test_pauli_to_binary_no_wire_map(self, op, vec): """Test conversion of Pauli word from operator to binary vector representation when no ``wire_map`` is specified.""" assert (pauli_to_binary(op) == vec).all() ops_to_vecs_abstract_wires = [ (PauliX("a") @ PauliZ("b") @ Identity("c"), np.array([1, 0, 0, 0, 0, 1, 0, 0])), (PauliY(6) @ PauliZ("a") @ PauliZ("b"), np.array([0, 0, 0, 1, 1, 1, 0, 1])), (PauliX("b") @ PauliY("c"), np.array([0, 1, 1, 0, 0, 0, 1, 0])), (Identity("a") @ Identity(6), np.zeros(8)), ] @pytest.mark.parametrize("op,vec", ops_to_vecs_abstract_wires) def test_pauli_to_binary_with_wire_map(self, op, vec): """Test conversion of Pauli word from operator to binary vector representation if a ``wire_map`` is specified.""" wire_map = {"a": 0, "b": 1, "c": 2, 6: 3} assert (pauli_to_binary(op, wire_map=wire_map) == vec).all() vecs_to_ops_explicit_wires = [ (np.array([1, 0, 1, 0, 0, 1]), PauliX(0) @ PauliY(2)), (np.array([1, 1, 1, 1, 1, 1]), PauliY(0) @ PauliY(1) @ PauliY(2)), (np.array([1, 0, 1, 0, 1, 1]), PauliX(0) @ PauliZ(1) @ PauliY(2)), (np.zeros(6), Identity(0)), ] @pytest.mark.parametrize("non_pauli_word", non_pauli_words) def test_pauli_to_binary_non_pauli_word_catch(self, non_pauli_word): """Tests TypeError raise for when non Pauli-word Pennylane operations/operators are given as input to pauli_to_binary.""" assert pytest.raises(TypeError, pauli_to_binary, non_pauli_word) def test_pauli_to_binary_incompatable_wire_map_n_qubits(self): """Tests ValueError raise when n_qubits is not high enough to support the highest wire_map value.""" pauli_word = PauliX("a") @ PauliY("b") @ PauliZ("c") wire_map = {"a": 0, "b": 1, "c": 3} n_qubits = 3 assert pytest.raises(ValueError, pauli_to_binary, pauli_word, n_qubits, wire_map) @pytest.mark.parametrize("vec,op", vecs_to_ops_explicit_wires) 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) vecs_to_ops_abstract_wires = [ (np.array([1, 0, 1, 0, 0, 1]), PauliX("alice") @ PauliY("ancilla")), (np.array([1, 1, 1, 1, 1, 1]), PauliY("alice") @ PauliY("bob") @ PauliY("ancilla")), (np.array([1, 0, 1, 0, 1, 0]), PauliX("alice") @ PauliZ("bob") @ PauliX("ancilla")), (np.zeros(6), Identity("alice")), ] @pytest.mark.parametrize("vec,op", vecs_to_ops_abstract_wires) 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) binary_vecs_with_invalid_wire_maps = [ ([1, 0], { "a": 1 }), ([1, 1, 1, 0], { "a": 0 }), ([1, 0, 1, 0, 1, 1], { "a": 0, "b": 2, "c": 3 }), ([1, 0, 1, 0], { "a": 0, "b": 2 }), ] @pytest.mark.parametrize("binary_vec,wire_map", binary_vecs_with_invalid_wire_maps) def test_binary_to_pauli_invalid_wire_map(self, binary_vec, wire_map): """Tests ValueError raise when wire_map values are not integers 0 to N, for input 2N dimensional binary vector.""" assert pytest.raises(ValueError, binary_to_pauli, binary_vec, wire_map) not_binary_symplectic_vecs = [[1, 0, 1, 1, 0], [1], [2, 0, 0, 1], [0.1, 4.3, 2.0, 1.3]] @pytest.mark.parametrize("not_binary_symplectic", not_binary_symplectic_vecs) def test_binary_to_pauli_with_illegal_vectors(self, not_binary_symplectic): """Test ValueError raise for when non even-dimensional binary vectors are given to binary_to_pauli.""" assert pytest.raises(ValueError, binary_to_pauli, not_binary_symplectic) def test_observables_to_binary_matrix(self): """Test conversion of list of Pauli word operators to representation as a binary matrix.""" observables = [Identity(1), PauliX(1), PauliZ(0) @ PauliZ(1)] binary_observables = np.array([[0.0, 1.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 1.0], [0.0, 0.0, 1.0]]).T assert (observables_to_binary_matrix(observables) == binary_observables ).all() def test_observables_to_binary_matrix_n_qubits_arg(self): """Tests if ValueError is raised when specified n_qubits is not large enough to support the number of distinct wire labels in input observables.""" observables = [ Identity(1) @ PauliZ("a"), PauliX(1), PauliZ(0) @ PauliZ(2) ] n_qubits_invalid = 3 assert pytest.raises(ValueError, observables_to_binary_matrix, observables, n_qubits_invalid) def test_is_qwc(self): """Determining if two Pauli words are qubit-wise commuting.""" n_qubits = 3 wire_map = {0: 0, "a": 1, "b": 2} p1_vec = pauli_to_binary(PauliX(0) @ PauliY("a"), wire_map=wire_map) p2_vec = pauli_to_binary(PauliX(0) @ Identity("a") @ PauliX("b"), wire_map=wire_map) p3_vec = pauli_to_binary(PauliX(0) @ PauliZ("a") @ Identity("b"), wire_map=wire_map) identity = pauli_to_binary(Identity("a") @ Identity(0), wire_map=wire_map) assert is_qwc(p1_vec, p2_vec) assert not is_qwc(p1_vec, p3_vec) assert is_qwc(p2_vec, p3_vec) assert (is_qwc(p1_vec, identity) == is_qwc(p2_vec, identity) == is_qwc( p3_vec, identity) == is_qwc(identity, identity) == True) def test_is_qwc_not_equal_lengths(self): """Tests ValueError is raised when input Pauli vectors are not of equal length.""" pauli_vec_1 = [0, 1, 0, 1] pauli_vec_2 = [1, 1, 0, 1, 0, 1] assert pytest.raises(ValueError, is_qwc, pauli_vec_1, pauli_vec_2) def test_is_qwc_not_even_lengths(self): """Tests ValueError is raised when input Pauli vectors are not of even length.""" pauli_vec_1 = [1, 0, 1] pauli_vec_2 = [1, 1, 1] assert pytest.raises(ValueError, is_qwc, pauli_vec_1, pauli_vec_2) def test_is_qwc_not_binary_vectors(self): """Tests ValueError is raised when input Pauli vectors do not have binary components.""" pauli_vec_1 = [1, 3.2, 1, 1 + 2j] pauli_vec_2 = [1, 0, 0, 0] assert pytest.raises(ValueError, is_qwc, pauli_vec_1, pauli_vec_2) def test_is_pauli_word(self): """Test for determining whether input ``Observable`` instance is a Pauli word.""" observable_1 = PauliX(0) observable_2 = PauliZ(1) @ PauliX(2) @ PauliZ(4) observable_3 = PauliX(1) @ Hadamard(4) observable_4 = Hadamard(0) assert is_pauli_word(observable_1) assert is_pauli_word(observable_2) assert not is_pauli_word(observable_3) assert not is_pauli_word(observable_4) def test_are_identical_pauli_words(self): """Tests for determining if two Pauli words have the same ``wires`` and ``name`` attributes.""" pauli_word_1 = Tensor(PauliX(0)) pauli_word_2 = PauliX(0) assert are_identical_pauli_words(pauli_word_1, pauli_word_2) assert are_identical_pauli_words(pauli_word_2, pauli_word_1) 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) @pytest.mark.parametrize("non_pauli_word", non_pauli_words) 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) def test_qwc_complement_adj_matrix(self): """Tests that the ``qwc_complement_adj_matrix`` function returns the correct adjacency matrix.""" binary_observables = np.array([ [1.0, 0.0, 1.0, 0.0, 0.0, 1.0], [0.0, 1.0, 1.0, 1.0, 0.0, 1.0], [0.0, 0.0, 0.0, 1.0, 0.0, 0.0], ]) adj = qwc_complement_adj_matrix(binary_observables) expected = np.array([[0.0, 1.0, 1.0], [1.0, 0.0, 0.0], [1.0, 0.0, 0.0]]) assert np.all(adj == expected) binary_obs_list = list(binary_observables) adj = qwc_complement_adj_matrix(binary_obs_list) assert np.all(adj == expected) binary_obs_tuple = tuple(binary_observables) adj = qwc_complement_adj_matrix(binary_obs_tuple) assert np.all(adj == expected) def test_qwc_complement_adj_matrix_exception(self): """Tests that the ``qwc_complement_adj_matrix`` function raises an exception if the matrix is not binary.""" not_binary_observables = np.array([ [1.1, 0.5, 1.0, 0.0, 0.0, 1.0], [0.0, 1.3, 1.0, 1.0, 0.0, 1.0], [2.2, 0.0, 0.0, 1.0, 0.0, 0.0], ]) with pytest.raises(ValueError, match="Expected a binary array, instead got"): qwc_complement_adj_matrix(not_binary_observables)
grouping_instance = PauliGroupingStrategy(observables, "anticommuting") assert (grouping_instance.complement_adj_matrix_for_operator() == anticommuting_complement_adjacency_matrix).all() observables_list = [ [ PauliX(0) @ PauliZ(1), PauliY(2) @ PauliZ(1), PauliX(1), PauliY(0), PauliZ(1) @ PauliZ(2) ], [ Identity(1) @ Identity(0), PauliX(1) @ PauliY(0) @ Identity(2), PauliZ(2), Identity(0), PauliZ(2) @ Identity(0), PauliX(0) @ PauliX(1), ], [ PauliX("a") @ Identity("b"), PauliX("a") @ PauliZ("b"), PauliX("b") @ PauliZ("a"), PauliZ("a") @ PauliZ("b") @ PauliZ("c"), ], ] qwc_sols = [
def binary_to_pauli(binary_vector, wire_map=None): # pylint: disable=too-many-branches """Converts a binary vector of even dimension to an Observable instance. This functions follows the convention that the first half of binary vector components specify PauliX placements while the last half specify PauliZ placements. Args: binary_vector (Union[list, tuple, array]): binary vector of even dimension representing a unique Pauli word wire_map (dict): dictionary containing all wire labels used in the Pauli word as keys, and unique integer labels as their values Returns: Tensor: The Pauli word corresponding to the input binary vector. Note that if a zero vector is input, then the resulting Pauli word will be an :class:`~.Identity` instance. Raises: TypeError: if length of binary vector is not even, or if vector does not have strictly binary components **Example** If ``wire_map`` is unspecified, the Pauli operations follow the same enumerations as the vector components, i.e., the ``i`` and ``N+i`` components specify the Pauli operation on wire ``i``, >>> binary_to_pauli([0,1,1,0,1,0]) Tensor(PauliY(wires=[1]), PauliX(wires=[2])) An arbitrary labelling can be assigned by using ``wire_map``: >>> wire_map = {Wires('a'): 0, Wires('b'): 1, Wires('c'): 2} >>> binary_to_pauli([0,1,1,0,1,0], wire_map=wire_map) Tensor(PauliY(wires=['b']), PauliX(wires=['c'])) Note that the values of ``wire_map``, if specified, must be ``0,1,..., N``, where ``N`` is the dimension of the vector divided by two, i.e., ``list(wire_map.values())`` must be ``list(range(len(binary_vector)/2))``. """ if isinstance(binary_vector, (list, tuple)): binary_vector = np.asarray(binary_vector) if len(binary_vector) % 2 != 0: raise ValueError( "Length of binary_vector must be even, instead got vector of shape {}." .format(np.shape(binary_vector))) if not np.array_equal(binary_vector, binary_vector.astype(bool)): raise ValueError( "Input vector must have strictly binary components, instead got {}." .format(binary_vector)) n_qubits = len(binary_vector) // 2 if wire_map is not None: if set(wire_map.values()) != set(range(n_qubits)): raise ValueError( "The values of wire_map must be integers 0 to N, for 2N-dimensional binary vector." " Instead got wire_map values: {}".format(wire_map.values())) label_map = { explicit_index: wire_label for wire_label, explicit_index in wire_map.items() } else: label_map = {i: Wires(i) for i in range(n_qubits)} pauli_word = None for i in range(n_qubits): operation = None if binary_vector[i] == 1 and binary_vector[n_qubits + i] == 0: operation = PauliX(wires=label_map[i]) elif binary_vector[i] == 1 and binary_vector[n_qubits + i] == 1: operation = PauliY(wires=label_map[i]) elif binary_vector[i] == 0 and binary_vector[n_qubits + i] == 1: operation = PauliZ(wires=label_map[i]) if operation is not None: if pauli_word is None: pauli_word = operation else: pauli_word @= operation if pauli_word is None: return Identity(wires=list(label_map.values())[0]) return pauli_word
class TestPauliGroupingStrategy: """Tests for the PauliGroupingStrategy class""" def test_initialize_with_invalid_grouping(self): """Tests ValueError is raised if specified grouping_type is not recognized.""" observables = [PauliX(0) @ PauliY(2), PauliZ(2)] assert pytest.raises(ValueError, PauliGroupingStrategy, observables, grouping_type="invalid") def test_initialize_with_invalid_colourer(self): """Tests ValueError is raised if specified graph_colourer is not recognized.""" observables = [PauliX(0) @ PauliY(2), PauliZ(2)] assert pytest.raises(ValueError, PauliGroupingStrategy, observables, graph_colourer="invalid") def test_construct_qwc_complement_adj_matrix_for_operators(self): """Constructing the complement graph adjacency matrix for a list of Pauli words according to qubit-wise commutativity.""" observables = [PauliY(0), PauliZ(0) @ PauliZ(1), PauliY(0) @ PauliX(1)] qwc_complement_adjacency_matrix = np.array([[0, 1, 0], [1, 0, 1], [0, 1, 0]]) grouping_instance = PauliGroupingStrategy(observables, "qwc") assert (grouping_instance.complement_adj_matrix_for_operator() == qwc_complement_adjacency_matrix).all() def test_construct_commuting_complement_adj_matrix_for_operators(self): """Constructing the complement graph adjacency matrix for a list of Pauli words according to general commutativity.""" observables = [PauliY(0), PauliZ(0) @ PauliZ(1), PauliY(0) @ PauliX(1)] commuting_complement_adjacency_matrix = np.array([[0, 1, 0], [1, 0, 0], [0, 0, 0]]) grouping_instance = PauliGroupingStrategy(observables, "commuting") assert (grouping_instance.complement_adj_matrix_for_operator() == commuting_complement_adjacency_matrix).all() def test_construct_anticommuting_complement_adj_matrix_for_operators(self): """Constructing the complement graph adjacency matrix for a list of Pauli words according to anticommutativity.""" observables = [PauliY(0), PauliZ(0) @ PauliZ(1), PauliY(0) @ PauliX(1)] anticommuting_complement_adjacency_matrix = np.array([[0, 0, 1], [0, 0, 1], [1, 1, 0]]) grouping_instance = PauliGroupingStrategy(observables, "anticommuting") assert (grouping_instance.complement_adj_matrix_for_operator() == anticommuting_complement_adjacency_matrix).all() trivial_ops = [ [Identity(0), Identity(0), Identity(7)], [ Identity("a") @ Identity(1), Identity("b"), Identity("b") @ Identity("c") ], ] @pytest.mark.parametrize("observables", trivial_ops) def test_construct_complement_qwc_adj_matrix_for_trivial_operators( self, observables): """Constructing the complement of QWC graph's adjacency matrix for a list of identity operations and various symmetric binary relations""" qwc_complement_adjacency_matrix = np.array([[0, 0, 0], [0, 0, 0], [0, 0, 0]]) grouping_instance = PauliGroupingStrategy(observables, "qwc") assert (grouping_instance.complement_adj_matrix_for_operator() == qwc_complement_adjacency_matrix).all() @pytest.mark.parametrize("observables", trivial_ops) def test_construct_complement_commuting_adj_matrix_for_trivial_operators( self, observables): """Constructing the complement of commutativity graph's adjacency matrix for a list of identity operations and various symmetric binary relations""" commuting_complement_adjacency_matrix = np.array([[0, 0, 0], [0, 0, 0], [0, 0, 0]]) grouping_instance = PauliGroupingStrategy(observables, "commuting") assert (grouping_instance.complement_adj_matrix_for_operator() == commuting_complement_adjacency_matrix).all() @pytest.mark.parametrize("observables", trivial_ops) def test_construct_complement_anticommuting_adj_matrix_for_trivial_operators( self, observables): """Constructing the complement of anticommutativity graph's adjacency matrix for a list of identity operations and various symmetric binary relations""" anticommuting_complement_adjacency_matrix = np.array([[0, 1, 1], [1, 0, 1], [1, 1, 0]]) grouping_instance = PauliGroupingStrategy(observables, "anticommuting") assert (grouping_instance.complement_adj_matrix_for_operator() == anticommuting_complement_adjacency_matrix).all()
-0.23666489, -0.23666489, 0.17001485, 0.04491735, -0.04491735, -0.04491735, 0.04491735, 0.12222641, 0.16714376, 0.16714376, 0.12222641, 0.17570278, ]) h2_ops = [ Identity(wires=[0]), PauliZ(wires=[0]), PauliZ(wires=[1]), PauliZ(wires=[2]), PauliZ(wires=[3]), PauliZ(wires=[0]) @ PauliZ(wires=[1]), PauliY(wires=[0]) @ PauliX(wires=[1]) @ PauliX(wires=[2]) @ PauliY(wires=[3]), PauliY(wires=[0]) @ PauliY(wires=[1]) @ PauliX(wires=[2]) @ PauliX(wires=[3]), PauliX(wires=[0]) @ PauliX(wires=[1]) @ PauliY(wires=[2]) @ PauliY(wires=[3]), PauliX(wires=[0]) @ PauliY(wires=[1]) @ PauliY(wires=[2]) @ PauliX(wires=[3]), PauliZ(wires=[0]) @ PauliZ(wires=[2]), PauliZ(wires=[0]) @ PauliZ(wires=[3]),
class TestGroupingUtils: """Basic usage and edge-case tests for the measurement optimization utility functions.""" ops_to_vecs_explicit_wires = [ (PauliX(0) @ PauliY(1) @ PauliZ(2), np.array([1, 1, 0, 0, 1, 1])), (PauliZ(0) @ PauliY(2), np.array([0, 1, 1, 1])), (PauliY(1) @ PauliX(2), np.array([1, 1, 1, 0])), (Identity(0), np.zeros(2)), ] @pytest.mark.parametrize("op,vec", ops_to_vecs_explicit_wires) def test_pauli_to_binary_no_wire_map(self, op, vec): """Test conversion of Pauli word from operator to binary vector representation when no ``wire_map`` is specified.""" assert (pauli_to_binary(op) == vec).all() ops_to_vecs_abstract_wires = [ (PauliX("a") @ PauliZ("b") @ Identity("c"), np.array([1, 0, 0, 0, 0, 1, 0, 0])), (PauliY(6) @ PauliZ("a") @ PauliZ("b"), np.array([0, 0, 0, 1, 1, 1, 0, 1])), (PauliX("b") @ PauliY("c"), np.array([0, 1, 1, 0, 0, 0, 1, 0])), (Identity("a") @ Identity(6), np.zeros(8)), ] @pytest.mark.parametrize("op,vec", ops_to_vecs_abstract_wires) def test_pauli_to_binary_with_wire_map(self, op, vec): """Test conversion of Pauli word from operator to binary vector representation if a ``wire_map`` is specified.""" wire_map = {"a": 0, "b": 1, "c": 2, 6: 3} assert (pauli_to_binary(op, wire_map=wire_map) == vec).all() vecs_to_ops_explicit_wires = [ (np.array([1, 0, 1, 0, 0, 1]), PauliX(0) @ PauliY(2)), (np.array([1, 1, 1, 1, 1, 1]), PauliY(0) @ PauliY(1) @ PauliY(2)), (np.array([1, 0, 1, 0, 1, 1]), PauliX(0) @ PauliZ(1) @ PauliY(2)), (np.zeros(6), Identity(0)), ] @pytest.mark.parametrize("non_pauli_word", non_pauli_words) def test_pauli_to_binary_non_pauli_word_catch(self, non_pauli_word): """Tests TypeError raise for when non Pauli-word Pennylane operations/operators are given as input to pauli_to_binary.""" assert pytest.raises(TypeError, pauli_to_binary, non_pauli_word) def test_pauli_to_binary_incompatable_wire_map_n_qubits(self): """Tests ValueError raise when n_qubits is not high enough to support the highest wire_map value.""" pauli_word = PauliX("a") @ PauliY("b") @ PauliZ("c") wire_map = {"a": 0, "b": 1, "c": 3} n_qubits = 3 assert pytest.raises(ValueError, pauli_to_binary, pauli_word, n_qubits, wire_map) @pytest.mark.parametrize("vec,op", vecs_to_ops_explicit_wires) 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) vecs_to_ops_abstract_wires = [ (np.array([1, 0, 1, 0, 0, 1]), PauliX("alice") @ PauliY("ancilla")), (np.array([1, 1, 1, 1, 1, 1]), PauliY("alice") @ PauliY("bob") @ PauliY("ancilla")), (np.array([1, 0, 1, 0, 1, 0]), PauliX("alice") @ PauliZ("bob") @ PauliX("ancilla")), (np.zeros(6), Identity("alice")), ] @pytest.mark.parametrize("vec,op", vecs_to_ops_abstract_wires) 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) binary_vecs_with_invalid_wire_maps = [ ([1, 0], { "a": 1 }), ([1, 1, 1, 0], { "a": 0 }), ([1, 0, 1, 0, 1, 1], { "a": 0, "b": 2, "c": 3 }), ([1, 0, 1, 0], { "a": 0, "b": 2 }), ] @pytest.mark.parametrize("binary_vec,wire_map", binary_vecs_with_invalid_wire_maps) def test_binary_to_pauli_invalid_wire_map(self, binary_vec, wire_map): """Tests ValueError raise when wire_map values are not integers 0 to N, for input 2N dimensional binary vector.""" assert pytest.raises(ValueError, binary_to_pauli, binary_vec, wire_map) not_binary_symplectic_vecs = [[1, 0, 1, 1, 0], [1], [2, 0, 0, 1], [0.1, 4.3, 2.0, 1.3]] @pytest.mark.parametrize("not_binary_symplectic", not_binary_symplectic_vecs) def test_binary_to_pauli_with_illegal_vectors(self, not_binary_symplectic): """Test ValueError raise for when non even-dimensional binary vectors are given to binary_to_pauli.""" assert pytest.raises(ValueError, binary_to_pauli, not_binary_symplectic) def test_observables_to_binary_matrix(self): """Test conversion of list of Pauli word operators to representation as a binary matrix.""" observables = [Identity(1), PauliX(1), PauliZ(0) @ PauliZ(1)] binary_observables = np.array([[0.0, 1.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 1.0], [0.0, 0.0, 1.0]]).T assert (observables_to_binary_matrix(observables) == binary_observables ).all() def test_observables_to_binary_matrix_n_qubits_arg(self): """Tests if ValueError is raised when specified n_qubits is not large enough to support the number of distinct wire labels in input observables.""" observables = [ Identity(1) @ PauliZ("a"), PauliX(1), PauliZ(0) @ PauliZ(2) ] n_qubits_invalid = 3 assert pytest.raises(ValueError, observables_to_binary_matrix, observables, n_qubits_invalid) def test_is_qwc(self): """Determining if two Pauli words are qubit-wise commuting.""" n_qubits = 3 wire_map = {0: 0, "a": 1, "b": 2} p1_vec = pauli_to_binary(PauliX(0) @ PauliY("a"), wire_map=wire_map) p2_vec = pauli_to_binary(PauliX(0) @ Identity("a") @ PauliX("b"), wire_map=wire_map) p3_vec = pauli_to_binary(PauliX(0) @ PauliZ("a") @ Identity("b"), wire_map=wire_map) identity = pauli_to_binary(Identity("a") @ Identity(0), wire_map=wire_map) assert is_qwc(p1_vec, p2_vec) assert not is_qwc(p1_vec, p3_vec) assert is_qwc(p2_vec, p3_vec) assert (is_qwc(p1_vec, identity) == is_qwc(p2_vec, identity) == is_qwc( p3_vec, identity) == is_qwc(identity, identity) == True) def test_is_qwc_not_equal_lengths(self): """Tests ValueError is raised when input Pauli vectors are not of equal length.""" pauli_vec_1 = [0, 1, 0, 1] pauli_vec_2 = [1, 1, 0, 1, 0, 1] assert pytest.raises(ValueError, is_qwc, pauli_vec_1, pauli_vec_2) def test_is_qwc_not_even_lengths(self): """Tests ValueError is raised when input Pauli vectors are not of even length.""" pauli_vec_1 = [1, 0, 1] pauli_vec_2 = [1, 1, 1] assert pytest.raises(ValueError, is_qwc, pauli_vec_1, pauli_vec_2) def test_is_qwc_not_binary_vectors(self): """Tests ValueError is raised when input Pauli vectors do not have binary components.""" pauli_vec_1 = [1, 3.2, 1, 1 + 2j] pauli_vec_2 = [1, 0, 0, 0] assert pytest.raises(ValueError, is_qwc, pauli_vec_1, pauli_vec_2) def test_is_pauli_word(self): """Test for determining whether input ``Observable`` instance is a Pauli word.""" observable_1 = PauliX(0) observable_2 = PauliZ(1) @ PauliX(2) @ PauliZ(4) observable_3 = PauliX(1) @ Hadamard(4) observable_4 = Hadamard(0) assert is_pauli_word(observable_1) assert is_pauli_word(observable_2) assert not is_pauli_word(observable_3) assert not is_pauli_word(observable_4) def test_are_identical_pauli_words(self): """Tests for determining if two Pauli words have the same ``wires`` and ``name`` attributes.""" pauli_word_1 = Tensor(PauliX(0)) pauli_word_2 = PauliX(0) assert are_identical_pauli_words(pauli_word_1, pauli_word_2) assert are_identical_pauli_words(pauli_word_2, pauli_word_1) 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) @pytest.mark.parametrize("non_pauli_word", non_pauli_words) 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) def test_qwc_complement_adj_matrix(self): """Tests that the ``qwc_complement_adj_matrix`` function returns the correct adjacency matrix.""" binary_observables = np.array([ [1.0, 0.0, 1.0, 0.0, 0.0, 1.0], [0.0, 1.0, 1.0, 1.0, 0.0, 1.0], [0.0, 0.0, 0.0, 1.0, 0.0, 0.0], ]) adj = qwc_complement_adj_matrix(binary_observables) expected = np.array([[0.0, 1.0, 1.0], [1.0, 0.0, 0.0], [1.0, 0.0, 0.0]]) assert np.all(adj == expected) binary_obs_list = list(binary_observables) adj = qwc_complement_adj_matrix(binary_obs_list) assert np.all(adj == expected) binary_obs_tuple = tuple(binary_observables) adj = qwc_complement_adj_matrix(binary_obs_tuple) assert np.all(adj == expected) def test_qwc_complement_adj_matrix_exception(self): """Tests that the ``qwc_complement_adj_matrix`` function raises an exception if the matrix is not binary.""" not_binary_observables = np.array([ [1.1, 0.5, 1.0, 0.0, 0.0, 1.0], [0.0, 1.3, 1.0, 1.0, 0.0, 1.0], [2.2, 0.0, 0.0, 1.0, 0.0, 0.0], ]) with pytest.raises(ValueError, match="Expected a binary array, instead got"): qwc_complement_adj_matrix(not_binary_observables) @pytest.mark.parametrize( "pauli_word,wire_map,expected_string", [ (PauliX(0), { 0: 0 }, "X"), (Identity(0), { 0: 0 }, "I"), (PauliZ(0) @ PauliY(1), { 0: 0, 1: 1 }, "ZY"), (PauliX(1), { 0: 0, 1: 1 }, "IX"), (PauliX(1), None, "X"), (PauliX(1), { 1: 0, 0: 1 }, "XI"), (PauliZ("a") @ PauliY("b") @ PauliZ("d"), { "a": 0, "b": 1, "c": 2, "d": 3 }, "ZYIZ"), (PauliZ("a") @ PauliY("b") @ PauliZ("d"), None, "ZYZ"), (PauliX("a") @ PauliY("b") @ PauliZ("d"), { "d": 0, "c": 1, "b": 2, "a": 3 }, "ZIYX"), ], ) def test_pauli_word_to_string(self, pauli_word, wire_map, expected_string): """Test that Pauli words are correctly converted into strings.""" obtained_string = pauli_word_to_string(pauli_word, wire_map) assert obtained_string == expected_string @pytest.mark.parametrize("non_pauli_word", non_pauli_words) def test_pauli_word_to_string_invalid_input(self, non_pauli_word): """Ensure invalid inputs are handled properly when converting Pauli words to strings.""" with pytest.raises(TypeError): pauli_word_to_string(non_pauli_word) @pytest.mark.parametrize( "pauli_string,wire_map,expected_pauli", [ ("I", { "a": 0 }, Identity("a")), ("X", { 0: 0 }, PauliX(0)), ("XI", { 1: 0, 0: 1 }, PauliX(1)), ("II", { 0: 0, 1: 1 }, Identity(0)), ("ZYIZ", { "a": 0, "b": 1, "c": 2, "d": 3 }, PauliZ("a") @ PauliY("b") @ PauliZ("d")), ("ZYZ", None, PauliZ(0) @ PauliY(1) @ PauliZ(2)), ("ZIYX", { "d": 0, "c": 1, "b": 2, "a": 3 }, PauliZ("d") @ PauliY("b") @ PauliX("a")), ], ) def test_string_to_pauli_word(self, pauli_string, wire_map, expected_pauli): """Test that valid strings are correctly converted into Pauli words.""" obtained_pauli = string_to_pauli_word(pauli_string, wire_map) assert obtained_pauli.compare(expected_pauli) @pytest.mark.parametrize( "non_pauli_string,wire_map,error_type,error_message", [ (Identity("a"), None, TypeError, "must be string"), ("XAYZ", None, ValueError, "Invalid characters encountered"), ("XYYZ", { 0: 0, 1: 1, 2: 2 }, ValueError, "must have the same length"), ], ) def test_string_to_pauli_word_invalid_input(self, non_pauli_string, wire_map, error_type, error_message): """Ensure invalid inputs are handled properly when converting strings to Pauli words.""" with pytest.raises(error_type, match=error_message): string_to_pauli_word(non_pauli_string, wire_map) @pytest.mark.parametrize( "pauli_word,wire_map,expected_matrix", [ (PauliX(0), { 0: 0 }, PauliX.matrix), (Identity(0), { 0: 0 }, np.eye(2)), ( PauliZ(0) @ PauliY(1), { 0: 0, 1: 1 }, np.array([[0, -1j, 0, 0], [1j, 0, 0, 0], [0, 0, 0, 1j], [0, 0, -1j, 0]]), ), ( PauliY(1) @ PauliZ(0), { 0: 0, 1: 1 }, np.array([[0, -1j, 0, 0], [1j, 0, 0, 0], [0, 0, 0, 1j], [0, 0, -1j, 0]]), ), ( PauliY(1) @ PauliZ(0), { 1: 0, 0: 1 }, np.array([[0, 0, -1j, 0], [0, 0, 0, 1j], [1j, 0, 0, 0], [0, -1j, 0, 0]]), ), (Identity(0), { 0: 0, 1: 1 }, np.eye(4)), (PauliX(2), None, PauliX.matrix), ( PauliX(2), { 0: 0, 1: 1, 2: 2 }, np.array([ [0, 1, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 0, 1, 0], ]), ), ( PauliZ("a") @ PauliX(2), { "a": 0, 1: 1, 2: 2 }, np.array([ [0, 1, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, -1, 0, 0], [0, 0, 0, 0, -1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, -1], [0, 0, 0, 0, 0, 0, -1, 0], ]), ), ( PauliX(2) @ PauliZ("a"), { "a": 0, 1: 1, 2: 2 }, np.array([ [0, 1, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, -1, 0, 0], [0, 0, 0, 0, -1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, -1], [0, 0, 0, 0, 0, 0, -1, 0], ]), ), ], ) def test_pauli_word_to_matrix(self, pauli_word, wire_map, expected_matrix): """Test that Pauli words are correctly converted into matrices.""" obtained_matrix = pauli_word_to_matrix(pauli_word, wire_map) assert np.allclose(obtained_matrix, expected_matrix) @pytest.mark.parametrize("non_pauli_word", non_pauli_words) def test_pauli_word_to_matrix_invalid_input(self, non_pauli_word): """Ensure invalid inputs are handled properly when converting Pauli words to matrices.""" with pytest.raises(TypeError): pauli_word_to_matrix(non_pauli_word) @pytest.mark.parametrize( "pauli_word_1,pauli_word_2,wire_map,commute_status", [ (Identity(0), PauliZ(0), { 0: 0 }, True), (PauliY(0), PauliZ(0), { 0: 0 }, False), (PauliX(0), PauliX(1), { 0: 0, 1: 1 }, True), (PauliY("x"), PauliX("y"), None, True), ( PauliZ("a") @ PauliY("b") @ PauliZ("d"), PauliX("a") @ PauliZ("c") @ PauliY("d"), { "a": 0, "b": 1, "c": 2, "d": 3 }, True, ), ( PauliX("a") @ PauliY("b") @ PauliZ("d"), PauliX("a") @ PauliZ("c") @ PauliY("d"), { "a": 0, "b": 1, "c": 2, "d": 3 }, False, ), ], ) def test_is_commuting(self, pauli_word_1, pauli_word_2, wire_map, commute_status): """Test that (non)-commuting Pauli words are correctly identified.""" do_they_commute = is_commuting(pauli_word_1, pauli_word_2, wire_map=wire_map) assert do_they_commute == commute_status @pytest.mark.parametrize( "pauli_word_1,pauli_word_2", [(non_pauli_words[0], PauliX(0) @ PauliY(2)), (PauliX(0) @ PauliY(2), non_pauli_words[0])], ) def test_is_commuting_invalid_input(self, pauli_word_1, pauli_word_2): """Ensure invalid inputs are handled properly when determining commutativity.""" with pytest.raises(TypeError): is_commuting(pauli_word_1, pauli_word_2)