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
88import numpy as np
9- import pytest
109from qiskit .circuit import QuantumCircuit
1110from 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