From 1476cd221a7f60f286cafb52b484b0364496b83b Mon Sep 17 00:00:00 2001 From: Mudit Pandey Date: Tue, 23 Apr 2024 15:06:02 -0400 Subject: [PATCH] Update condition for swapping iterators in `PauliWord._matmul` (#5301) **Context:** We found that the wire order in `Sum/Prod.terms` was being swapped for the first and second term in terms that were `Prod`s. This was due to the use of the `pauli_rep`. While building the pauli rep, we use `PauliWord._matmul` which swaps the order of `self` and `other` if `len(other) >= len(self)` so that we iterate through the larger `PauliWord`. Example: ```python H = qml.prod(X(0), X(1), X(2)) Sum_coeffs, Sum_ops = H.terms() ``` ```pycon >>> Sum_ops[0].wires, H.wires (, ) ``` **Description of the Change:** Changed the condition for swapping from `len(other) >= len(self)` to `len(other) > len(self)`. **Benefits:** More consistent `Sum/Prod.pauli_rep`, and `Sum/Prod.terms()` with the original operands. The above example now behaves as follows: ```python H = qml.prod(X(0), X(1), X(2)) Sum_coeffs, Sum_ops = H.terms() ``` ```pycon >>> Sum_ops[0].wires, H.wires (, ) ``` **Possible Drawbacks:** **Related GitHub Issues:** --------- Co-authored-by: qottmann --- pennylane/pauli/pauli_arithmetic.py | 2 +- tests/circuit_graph/test_qasm.py | 6 +++--- tests/ops/op_math/test_prod.py | 15 ++++++++++++--- tests/ops/op_math/test_sum.py | 18 ++++++++++++++++++ .../pauli/test_measurement_transformations.py | 2 +- tests/pauli/test_pauli_utils.py | 5 +---- .../test_approx_time_evolution.py | 4 ++-- tests/test_qaoa.py | 18 +++++++++--------- 8 files changed, 47 insertions(+), 23 deletions(-) diff --git a/pennylane/pauli/pauli_arithmetic.py b/pennylane/pauli/pauli_arithmetic.py index b861b15d813..418550bf3aa 100644 --- a/pennylane/pauli/pauli_arithmetic.py +++ b/pennylane/pauli/pauli_arithmetic.py @@ -226,7 +226,7 @@ def __hash__(self): def _matmul(self, other): """Private matrix multiplication that returns (pauli_word, coeff) tuple for more lightweight processing""" base, iterator, swapped = ( - (self, other, False) if len(self) > len(other) else (other, self, True) + (self, other, False) if len(self) >= len(other) else (other, self, True) ) result = copy(dict(base)) coeff = 1 diff --git a/tests/circuit_graph/test_qasm.py b/tests/circuit_graph/test_qasm.py index c3f2beaf5f7..9521b1c8bb9 100644 --- a/tests/circuit_graph/test_qasm.py +++ b/tests/circuit_graph/test_qasm.py @@ -117,9 +117,9 @@ def test_to_ApproxTimeEvolution(self): include "qelib1.inc"; qreg q[2]; creg c[2]; - cx q[0],q[1]; - rz(2.0) q[1]; - cx q[0],q[1]; + cx q[1],q[0]; + rz(2.0) q[0]; + cx q[1],q[0]; measure q[0] -> c[0]; measure q[1] -> c[1]; """ diff --git a/tests/ops/op_math/test_prod.py b/tests/ops/op_math/test_prod.py index d35823bd78d..cbdfa39e6f6 100644 --- a/tests/ops/op_math/test_prod.py +++ b/tests/ops/op_math/test_prod.py @@ -255,6 +255,15 @@ def test_terms_pauli_rep(self, op, coeffs_true, ops_true): assert coeffs == coeffs_true assert ops1 == ops_true + def test_terms_pauli_rep_wire_order(self): + """Test that the wire order of the terms is the same as the wire order of the original + operands when the Prod has a valid pauli_rep""" + H = qml.prod(X(0), X(1), X(2)) + _, H_ops = H.terms() + + assert len(H_ops) == 1 + assert H_ops[0].wires == H.wires + def test_batch_size(self): """Test that batch size returns the batch size of a base operation if it is batched.""" x = qml.numpy.array([1.0, 2.0, 3.0]) @@ -1035,8 +1044,8 @@ def test_pauli_rep_order(self): """ op = qml.prod(qml.PauliX(0), qml.PauliY(1), qml.PauliZ(2)) pw = list(op.pauli_rep.keys())[0] - assert list(pw.keys()) == [1, 0, 2] - assert list(pw.values()) == ["Y", "X", "Z"] + assert list(pw.keys()) == [0, 1, 2] + assert list(pw.values()) == ["X", "Y", "Z"] @pytest.mark.parametrize("op, rep", op_pauli_reps) def test_pauli_rep(self, op, rep): @@ -1119,7 +1128,7 @@ def test_simplify_method_product_of_sums(self): """Test the simplify method with a product of sums.""" prod_op = Prod(qml.PauliX(0) + qml.RX(1, 0), qml.PauliX(1) + qml.RX(1, 1), qml.Identity(3)) final_op = qml.sum( - Prod(qml.PauliX(1), qml.PauliX(0)), + Prod(qml.PauliX(0), qml.PauliX(1)), qml.PauliX(0) @ qml.RX(1, 1), qml.PauliX(1) @ qml.RX(1, 0), qml.RX(1, 0) @ qml.RX(1, 1), diff --git a/tests/ops/op_math/test_sum.py b/tests/ops/op_math/test_sum.py index f9fdd3ae12c..be266eab927 100644 --- a/tests/ops/op_math/test_sum.py +++ b/tests/ops/op_math/test_sum.py @@ -212,6 +212,24 @@ def test_terms_pauli_rep(self, op, coeffs_true, ops_true): assert coeffs == coeffs_true assert ops1 == ops_true + def test_terms_pauli_rep_wire_order(self): + """Test that the wire order of the terms is the same as the wire order of the original + operands when the Sum has a valid pauli_rep""" + w0, w1, w2, w3 = [0, 1, 2, 3] + coeffs = [0.5, -0.5] + + obs = [ + qml.X(w0) @ qml.Y(w1) @ qml.X(w2) @ qml.Z(w3), + qml.X(w0) @ qml.X(w1) @ qml.Y(w2) @ qml.Z(w3), + ] + + H = qml.dot(coeffs, obs) + _, H_ops = H.terms() + + assert all(o1.wires == o2.wires for o1, o2 in zip(obs, H_ops)) + assert H_ops[0] == qml.prod(qml.X(w0), qml.Y(w1), qml.X(w2), qml.Z(w3)) + assert H_ops[1] == qml.prod(qml.X(w0), qml.X(w1), qml.Y(w2), qml.Z(w3)) + coeffs_ = [1.0, 1.0, 1.0, 3.0, 4.0, 4.0, 5.0] h6 = qml.sum( qml.s_prod(2.0, qml.prod(qml.Hadamard(0), qml.PauliZ(10))), diff --git a/tests/pauli/test_measurement_transformations.py b/tests/pauli/test_measurement_transformations.py index cae360ebf0f..7526a5fc488 100644 --- a/tests/pauli/test_measurement_transformations.py +++ b/tests/pauli/test_measurement_transformations.py @@ -120,7 +120,7 @@ def test_diagonalize_pauli_word_catch_non_pauli_word(self, non_pauli_word): ( [PauliX(0) @ PauliY(1), PauliX(0) @ PauliZ(2)], ( - [RX(np.pi / 2, wires=[1]), RY(-np.pi / 2, wires=[0])], + [RY(-np.pi / 2, wires=[0]), RX(np.pi / 2, wires=[1])], [PauliZ(wires=[0]) @ PauliZ(wires=[1]), PauliZ(wires=[0]) @ PauliZ(wires=[2])], ), ), diff --git a/tests/pauli/test_pauli_utils.py b/tests/pauli/test_pauli_utils.py index 2f21637e06e..db66e0bff78 100644 --- a/tests/pauli/test_pauli_utils.py +++ b/tests/pauli/test_pauli_utils.py @@ -926,10 +926,7 @@ def test_diagonalize_pauli_word_catch_non_pauli_word(self, non_pauli_word): ( [PauliX(0) @ PauliY(1), PauliX(0) @ PauliZ(2)], ( - [ - RX(np.pi / 2, wires=[1]), - RY(-np.pi / 2, wires=[0]), - ], + [RY(-np.pi / 2, wires=[0]), RX(np.pi / 2, wires=[1])], [PauliZ(wires=[0]) @ PauliZ(wires=[1]), PauliZ(wires=[0]) @ PauliZ(wires=[2])], ), ), diff --git a/tests/templates/test_subroutines/test_approx_time_evolution.py b/tests/templates/test_subroutines/test_approx_time_evolution.py index 044f261ee1b..5924d3e7504 100644 --- a/tests/templates/test_subroutines/test_approx_time_evolution.py +++ b/tests/templates/test_subroutines/test_approx_time_evolution.py @@ -66,7 +66,7 @@ class TestDecomposition: ), ( 2, - qml.Hamiltonian([2, 0.5], [qml.PauliX("a"), qml.PauliZ("b") @ qml.PauliX("a")]), + qml.Hamiltonian([2, 0.5], [qml.PauliX("a"), qml.PauliX("a") @ qml.PauliZ("b")]), 2, [ qml.PauliRot(4.0, "X", wires=["a"]), @@ -87,7 +87,7 @@ class TestDecomposition: [2, 0.5, 0.5], [ qml.PauliX("a"), - qml.PauliZ(-15) @ qml.PauliX("a"), + qml.PauliX("a") @ qml.PauliZ(-15), qml.Identity(0) @ qml.PauliY(-15), ], ), diff --git a/tests/test_qaoa.py b/tests/test_qaoa.py index 591b5a5ce3f..32428406f4c 100644 --- a/tests/test_qaoa.py +++ b/tests/test_qaoa.py @@ -1166,12 +1166,12 @@ def make_mixer_layer_test_cases(): [ qaoa.xy_mixer(Graph([(0, 1), (1, 2), (2, 0)])), [ - qml.PauliRot(1.0, "XX", wires=[1, 0]), - qml.PauliRot(1.0, "YY", wires=[1, 0]), - qml.PauliRot(1.0, "XX", wires=[2, 0]), - qml.PauliRot(1.0, "YY", wires=[2, 0]), - qml.PauliRot(1.0, "XX", wires=[2, 1]), - qml.PauliRot(1.0, "YY", wires=[2, 1]), + qml.PauliRot(1.0, "XX", wires=[0, 1]), + qml.PauliRot(1.0, "YY", wires=[0, 1]), + qml.PauliRot(1.0, "XX", wires=[0, 2]), + qml.PauliRot(1.0, "YY", wires=[0, 2]), + qml.PauliRot(1.0, "XX", wires=[1, 2]), + qml.PauliRot(1.0, "YY", wires=[1, 2]), ], ], ] @@ -1186,9 +1186,9 @@ def make_cost_layer_test_cases(): [ qaoa.maxcut(Graph([(0, 1), (1, 2), (2, 0)]))[0], [ - qml.PauliRot(1.0, "ZZ", wires=[1, 0]), - qml.PauliRot(1.0, "ZZ", wires=[2, 0]), - qml.PauliRot(1.0, "ZZ", wires=[2, 1]), + qml.PauliRot(1.0, "ZZ", wires=[0, 1]), + qml.PauliRot(1.0, "ZZ", wires=[0, 2]), + qml.PauliRot(1.0, "ZZ", wires=[1, 2]), ], ], ]