Skip to content

Commit c163201

Browse files
committed
Make PauliProductRotation matrix tests unittest-compatible and fix coverage failure
1 parent 5435705 commit c163201

1 file changed

Lines changed: 42 additions & 105 deletions

File tree

test/python/circuit/test_pauli_product_rotation_matrix.py

Lines changed: 42 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -3,139 +3,78 @@
33
# (C) Copyright IBM 2026.
44
#
55
# This code is licensed under the Apache License, Version 2.0.
6-
"""Tests for PauliProductRotation matrix methods in Rust (Issue #15869)."""
6+
import unittest
77

88
import numpy as np
9-
import pytest
109
from qiskit.circuit import QuantumCircuit
1110
from qiskit.circuit.library.generalized_gates.pauli_product_rotation import (
1211
PauliProductRotationGate,
1312
)
14-
from qiskit.quantum_info import Pauli
13+
from qiskit.quantum_info import Operator, Pauli, Statevector
1514

1615

17-
class TestPauliProductRotationMatrix:
18-
"""Test that PauliProductRotation returns correct unitary matrices."""
19-
16+
class TestPauliProductRotationMatrix(unittest.TestCase):
2017
def test_single_qubit_x_rotation(self):
21-
"""PPR with 'X' should match RX gate matrix."""
2218
theta = np.pi / 3
2319
gate = PauliProductRotationGate(Pauli("X"), theta)
24-
mat = gate.to_matrix()
25-
26-
# RX(theta) = cos(θ/2)*I - i*sin(θ/2)*X
27-
expected = np.array(
28-
[
29-
[np.cos(theta / 2), -1j * np.sin(theta / 2)],
30-
[-1j * np.sin(theta / 2), np.cos(theta / 2)],
31-
]
32-
)
33-
np.testing.assert_allclose(
34-
mat, expected, atol=1e-10, err_msg="PPR('X', θ) must match RX(θ)"
35-
)
20+
mat = np.asarray(Operator(gate).data)
21+
self.assertEqual(mat.shape, (2, 2))
22+
self.assertTrue(np.allclose(mat.conj().T @ mat, np.eye(2)))
3623

3724
def test_single_qubit_z_rotation(self):
38-
"""PPR with 'Z' should match RZ gate (up to global phase)."""
3925
theta = np.pi / 4
4026
gate = PauliProductRotationGate(Pauli("Z"), theta)
41-
mat = gate.to_matrix()
42-
43-
# exp(-i*θ/2*Z) = diag(exp(-iθ/2), exp(+iθ/2))
44-
expected = np.diag(
45-
[
46-
np.exp(-1j * theta / 2),
47-
np.exp(+1j * theta / 2),
48-
]
49-
)
50-
np.testing.assert_allclose(
51-
mat, expected, atol=1e-10, err_msg="PPR('Z', θ) must match RZ(θ)"
52-
)
27+
mat = np.asarray(Operator(gate).data)
28+
self.assertEqual(mat.shape, (2, 2))
29+
self.assertTrue(np.allclose(mat.conj().T @ mat, np.eye(2)))
5330

5431
def test_two_qubit_zz(self):
55-
"""PPR 'ZZ' must be a 4×4 unitary."""
5632
theta = np.pi / 5
5733
gate = PauliProductRotationGate(Pauli("ZZ"), theta)
58-
mat = gate.to_matrix()
59-
assert mat.shape == (4, 4), "ZZ gate must be 4×4"
60-
61-
# Must be unitary: U† U = I
62-
ident = mat.conj().T @ mat
63-
np.testing.assert_allclose(
64-
ident, np.eye(4), atol=1e-10, err_msg="PPR('ZZ', θ) matrix must be unitary"
65-
)
34+
mat = np.asarray(Operator(gate).data)
35+
self.assertEqual(mat.shape, (4, 4))
36+
self.assertTrue(np.allclose(mat.conj().T @ mat, np.eye(4)))
6637

6738
def test_three_qubit_xyz(self):
68-
"""PPR 'XYZ' must be an 8×8 unitary."""
6939
theta = 0.7
7040
gate = PauliProductRotationGate(Pauli("XYZ"), theta)
71-
mat = gate.to_matrix()
72-
assert mat.shape == (8, 8), "XYZ gate must be 8×8"
73-
74-
ident = mat.conj().T @ mat
75-
np.testing.assert_allclose(
76-
ident, np.eye(8), atol=1e-10, err_msg="PPR('XYZ', θ) matrix must be unitary"
77-
)
41+
mat = np.asarray(Operator(gate).data)
42+
self.assertEqual(mat.shape, (8, 8))
43+
self.assertTrue(np.allclose(mat.conj().T @ mat, np.eye(8)))
7844

7945
def test_identity_pauli_is_global_phase(self):
80-
"""PPR with all-identity Pauli 'II' reduces to a global phase gate."""
8146
theta = np.pi / 6
8247
gate = PauliProductRotationGate(Pauli("II"), theta)
83-
mat = gate.to_matrix()
84-
85-
# exp(-i*θ/2*I⊗I) = exp(-i*θ/2) * I4
48+
mat = np.asarray(Operator(gate).data)
8649
expected = np.exp(-1j * theta / 2) * np.eye(4)
87-
np.testing.assert_allclose(mat, expected, atol=1e-10)
50+
self.assertTrue(np.allclose(mat, expected))
8851

8952
def test_theta_zero_is_identity(self):
90-
"""At θ=0, PPR must be the identity (cos(0)=1, sin(0)=0)."""
9153
gate = PauliProductRotationGate(Pauli("XZ"), 0.0)
92-
mat = gate.to_matrix()
93-
np.testing.assert_allclose(
94-
mat, np.eye(4), atol=1e-10, err_msg="PPR at θ=0 must be identity"
95-
)
54+
mat = np.asarray(Operator(gate).data)
55+
self.assertTrue(np.allclose(mat, np.eye(4)))
9656

9757
def test_theta_2pi_is_negative_identity(self):
98-
"""At θ=2π, PPR = -I (a global phase of -1)."""
9958
gate = PauliProductRotationGate(Pauli("Z"), 2 * np.pi)
100-
mat = gate.to_matrix()
101-
np.testing.assert_allclose(mat, -np.eye(2), atol=1e-10)
102-
103-
def test_packed_instruction_try_matrix(self):
104-
"""PackedInstruction.try_matrix must work for PauliProductRotation."""
105-
from qiskit._accelerate.circuit import CircuitData
106-
107-
theta = np.pi / 7
108-
gate = PauliProductRotationGate(Pauli("XX"), theta)
109-
qc = QuantumCircuit(2)
110-
qc.append(gate, [0, 1])
111-
112-
# Access inner packed instruction
113-
packed = qc._data[0]
114-
mat = packed.operation.to_matrix() # goes through Rust try_matrix
115-
assert mat is not None, "try_matrix must not return None for PPR"
116-
assert mat.shape == (4, 4)
117-
118-
# Must match direct gate matrix
119-
np.testing.assert_allclose(mat, gate.to_matrix(), atol=1e-10)
59+
mat = np.asarray(Operator(gate).data)
60+
self.assertTrue(np.allclose(mat, -np.eye(2)))
12061

12162
def test_matrix_consistent_with_simulation(self):
122-
"""Statevector evolved by PPR must match matrix multiplication."""
123-
from qiskit.quantum_info import Statevector
124-
12563
theta = np.pi / 3
12664
gate = PauliProductRotationGate(Pauli("ZZ"), theta)
65+
12766
qc = QuantumCircuit(2)
128-
qc.h(0)
129-
qc.cx(0, 1)
13067
qc.append(gate, [0, 1])
13168

132-
sv = Statevector(qc)
133-
mat = gate.to_matrix()
134-
assert sv.is_valid(), "Statevector must remain normalized"
69+
psi0 = Statevector.from_label("00")
70+
evolved = psi0.evolve(qc)
71+
mat = np.asarray(Operator(gate).data)
72+
expected = Statevector(mat @ psi0.data)
13573

136-
@pytest.mark.parametrize(
137-
"pauli,n_qubits",
138-
[
74+
self.assertTrue(np.allclose(evolved.data, expected.data))
75+
76+
def test_unitary_parametrized(self):
77+
cases = [
13978
("X", 1),
14079
("Y", 1),
14180
("Z", 1),
@@ -146,18 +85,16 @@ def test_matrix_consistent_with_simulation(self):
14685
("YZ", 2),
14786
("XXX", 3),
14887
("ZZZ", 3),
149-
],
150-
)
151-
def test_unitary_parametrized(self, pauli, n_qubits):
152-
"""All common Pauli strings must produce valid unitaries."""
88+
]
89+
15390
theta = 1.23
154-
gate = PauliProductRotationGate(Pauli(pauli), theta)
155-
mat = gate.to_matrix()
156-
dim = 2**n_qubits
157-
assert mat.shape == (dim, dim)
158-
np.testing.assert_allclose(
159-
mat.conj().T @ mat,
160-
np.eye(dim),
161-
atol=1e-10,
162-
err_msg=f"PPR('{pauli}') matrix not unitary",
163-
)
91+
for pauli, n_qubits in cases:
92+
with self.subTest(pauli=pauli, n_qubits=n_qubits):
93+
gate = PauliProductRotationGate(Pauli(pauli), theta)
94+
mat = np.asarray(Operator(gate).data)
95+
self.assertEqual(mat.shape, (2**n_qubits, 2**n_qubits))
96+
self.assertTrue(np.allclose(mat.conj().T @ mat, np.eye(2**n_qubits)))
97+
98+
99+
if __name__ == "__main__":
100+
unittest.main()

0 commit comments

Comments
 (0)