1+ # This code is part of Qiskit.
2+ #
3+ # (C) Copyright IBM 2026.
4+ #
5+ # This code is licensed under the Apache License, Version 2.0.
6+ """Tests for PauliProductRotation matrix methods in Rust (Issue #15869)."""
7+
8+ import numpy as np
9+ import pytest
10+ from qiskit .circuit import QuantumCircuit
11+ from qiskit .circuit .library .generalized_gates .pauli_product_rotation import (
12+ PauliProductRotationGate ,
13+ )
14+ from qiskit .quantum_info import Pauli
15+
16+ class TestPauliProductRotationMatrix :
17+ """Test that PauliProductRotation returns correct unitary matrices."""
18+
19+ def test_single_qubit_x_rotation (self ):
20+ """PPR with 'X' should match RX gate matrix."""
21+ theta = np .pi / 3
22+ gate = PauliProductRotationGate (Pauli ("X" ), theta )
23+ mat = gate .to_matrix ()
24+
25+ # RX(theta) = cos(θ/2)*I - i*sin(θ/2)*X
26+ expected = np .array ([
27+ [np .cos (theta / 2 ), - 1j * np .sin (theta / 2 )],
28+ [- 1j * np .sin (theta / 2 ), np .cos (theta / 2 )],
29+ ])
30+ np .testing .assert_allclose (mat , expected , atol = 1e-10 ,
31+ err_msg = "PPR('X', θ) must match RX(θ)" )
32+
33+ def test_single_qubit_z_rotation (self ):
34+ """PPR with 'Z' should match RZ gate (up to global phase)."""
35+ theta = np .pi / 4
36+ gate = PauliProductRotationGate (Pauli ("Z" ),theta )
37+ mat = gate .to_matrix ()
38+
39+ # exp(-i*θ/2*Z) = diag(exp(-iθ/2), exp(+iθ/2))
40+ expected = np .diag ([
41+ np .exp (- 1j * theta / 2 ),
42+ np .exp (+ 1j * theta / 2 ),
43+ ])
44+ np .testing .assert_allclose (mat , expected , atol = 1e-10 ,
45+ err_msg = "PPR('Z', θ) must match RZ(θ)" )
46+
47+ def test_two_qubit_zz (self ):
48+ """PPR 'ZZ' must be a 4×4 unitary."""
49+ theta = np .pi / 5
50+ gate = PauliProductRotationGate (Pauli ("ZZ" ),theta )
51+ mat = gate .to_matrix ()
52+ assert mat .shape == (4 , 4 ), "ZZ gate must be 4×4"
53+
54+ # Must be unitary: U† U = I
55+ ident = mat .conj ().T @ mat
56+ np .testing .assert_allclose (ident , np .eye (4 ), atol = 1e-10 ,
57+ err_msg = "PPR('ZZ', θ) matrix must be unitary" )
58+
59+ def test_three_qubit_xyz (self ):
60+ """PPR 'XYZ' must be an 8×8 unitary."""
61+ theta = 0.7
62+ gate = PauliProductRotationGate (Pauli ("XYZ" ),theta )
63+ mat = gate .to_matrix ()
64+ assert mat .shape == (8 , 8 ), "XYZ gate must be 8×8"
65+
66+ ident = mat .conj ().T @ mat
67+ np .testing .assert_allclose (ident , np .eye (8 ), atol = 1e-10 ,
68+ err_msg = "PPR('XYZ', θ) matrix must be unitary" )
69+
70+ def test_identity_pauli_is_global_phase (self ):
71+ """PPR with all-identity Pauli 'II' reduces to a global phase gate."""
72+ theta = np .pi / 6
73+ gate = PauliProductRotationGate (Pauli ("II" ),theta )
74+ mat = gate .to_matrix ()
75+
76+ # exp(-i*θ/2*I⊗I) = exp(-i*θ/2) * I4
77+ expected = np .exp (- 1j * theta / 2 ) * np .eye (4 )
78+ np .testing .assert_allclose (mat , expected , atol = 1e-10 )
79+
80+ def test_theta_zero_is_identity (self ):
81+ """At θ=0, PPR must be the identity (cos(0)=1, sin(0)=0)."""
82+ gate = PauliProductRotationGate (Pauli ("XZ" ),0.0 )
83+ mat = gate .to_matrix ()
84+ np .testing .assert_allclose (mat , np .eye (4 ), atol = 1e-10 ,
85+ err_msg = "PPR at θ=0 must be identity" )
86+
87+ def test_theta_2pi_is_negative_identity (self ):
88+ """At θ=2π, PPR = -I (a global phase of -1)."""
89+ gate = PauliProductRotationGate (Pauli ("Z" ),2 * np .pi )
90+ mat = gate .to_matrix ()
91+ np .testing .assert_allclose (mat , - np .eye (2 ), atol = 1e-10 )
92+
93+ def test_packed_instruction_try_matrix (self ):
94+ """PackedInstruction.try_matrix must work for PauliProductRotation."""
95+ from qiskit ._accelerate .circuit import CircuitData
96+
97+ theta = np .pi / 7
98+ gate = PauliProductRotationGate (Pauli ("XX" ),theta )
99+ qc = QuantumCircuit (2 )
100+ qc .append (gate , [0 , 1 ])
101+
102+ # Access inner packed instruction
103+ packed = qc ._data [0 ]
104+ mat = packed .operation .to_matrix () # goes through Rust try_matrix
105+ assert mat is not None , "try_matrix must not return None for PPR"
106+ assert mat .shape == (4 , 4 )
107+
108+ # Must match direct gate matrix
109+ np .testing .assert_allclose (mat , gate .to_matrix (), atol = 1e-10 )
110+
111+ def test_matrix_consistent_with_simulation (self ):
112+ """Statevector evolved by PPR must match matrix multiplication."""
113+ from qiskit .quantum_info import Statevector
114+
115+ theta = np .pi / 3
116+ gate = PauliProductRotationGate (Pauli ("ZZ" ),theta )
117+ qc = QuantumCircuit (2 )
118+ qc .h (0 )
119+ qc .cx (0 , 1 )
120+ qc .append (gate , [0 , 1 ])
121+
122+ sv = Statevector (qc )
123+ mat = gate .to_matrix ()
124+ assert sv .is_valid (), "Statevector must remain normalized"
125+
126+ @pytest .mark .parametrize ("pauli,n_qubits" , [
127+ ("X" , 1 ), ("Y" , 1 ), ("Z" , 1 ),
128+ ("XX" , 2 ), ("YY" , 2 ), ("ZZ" , 2 ), ("XY" , 2 ), ("YZ" , 2 ),
129+ ("XXX" , 3 ), ("ZZZ" , 3 ),
130+ ])
131+ def test_unitary_parametrized (self , pauli , n_qubits ):
132+ """All common Pauli strings must produce valid unitaries."""
133+ theta = 1.23
134+ gate = PauliProductRotationGate (Pauli (pauli ), theta )
135+ mat = gate .to_matrix ()
136+ dim = 2 ** n_qubits
137+ assert mat .shape == (dim , dim )
138+ np .testing .assert_allclose (
139+ mat .conj ().T @ mat , np .eye (dim ), atol = 1e-10 ,
140+ err_msg = f"PPR('{ pauli } ') matrix not unitary"
141+ )
0 commit comments