Skip to content

Commit e80db9f

Browse files
authored
Enable try_matrix & friends for PPR and PPM (#16011)
* Enable `try_matrix` & friends for PPR and PPM Previously, the `matrix` method was added but not actually used in the codebase. This commit also fixes the commutation checker for PPR with another matrix-based gate, which previously failed since it couldn't extract a matrix from PPR. * Review comments - Feature, not bugfix reno - Add SK test - Add PPM matrix test (which always is False) * Add `try_staticNq` The nalgebra methods are automatically implement if the static methods exist
1 parent 9fa17a3 commit e80db9f

8 files changed

Lines changed: 71 additions & 1 deletion

File tree

crates/circuit/src/instruction.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ pub trait Instruction {
164164
OperationRef::StandardGate(g) => g.matrix(self.params_view()),
165165
OperationRef::Gate(g) => g.matrix(),
166166
OperationRef::Unitary(u) => u.matrix(),
167+
OperationRef::PauliProductRotation(ppr) => ppr.matrix(),
167168
_ => None,
168169
}
169170
}

crates/circuit/src/operations.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1740,6 +1740,29 @@ impl PauliProductRotation {
17401740
}
17411741
Some(out)
17421742
}
1743+
1744+
pub fn matrix_as_static_1q(&self) -> Option<[[Complex64; 2]; 2]> {
1745+
if self.num_qubits() == 1 {
1746+
let arr = self.matrix()?;
1747+
Some([[arr[(0, 0)], arr[(0, 1)]], [arr[(1, 0)], arr[(1, 1)]]])
1748+
} else {
1749+
None
1750+
}
1751+
}
1752+
1753+
pub fn matrix_as_static_2q(&self) -> Option<[[Complex64; 4]; 4]> {
1754+
if self.num_qubits() == 2 {
1755+
let arr = self.matrix()?;
1756+
Some([
1757+
[arr[[0, 0]], arr[[0, 1]], arr[[0, 2]], arr[[0, 3]]],
1758+
[arr[[1, 0]], arr[[1, 1]], arr[[1, 2]], arr[[1, 3]]],
1759+
[arr[[2, 0]], arr[[2, 1]], arr[[2, 2]], arr[[2, 3]]],
1760+
[arr[[3, 0]], arr[[3, 1]], arr[[3, 2]], arr[[3, 3]]],
1761+
])
1762+
} else {
1763+
None
1764+
}
1765+
}
17431766
}
17441767

17451768
impl PartialEq for PauliProductRotation {

crates/circuit/src/packed_instruction.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -954,6 +954,7 @@ impl PackedInstruction {
954954
OperationRef::StandardGate(g) => g.matrix(self.params_view()),
955955
OperationRef::Gate(g) => g.matrix(),
956956
OperationRef::Unitary(u) => u.matrix(),
957+
OperationRef::PauliProductRotation(ppr) => ppr.matrix(),
957958
_ => None,
958959
}
959960
}
@@ -966,6 +967,7 @@ impl PackedInstruction {
966967
match self.op.view() {
967968
OperationRef::StandardGate(g) => g.matrix(self.params_view()).map(CowArray::from),
968969
OperationRef::Gate(g) => g.matrix().map(CowArray::from),
970+
OperationRef::PauliProductRotation(ppr) => ppr.matrix().map(CowArray::from),
969971
OperationRef::Unitary(u) => Some(CowArray::from(u.matrix_view())),
970972
_ => None,
971973
}
@@ -979,6 +981,7 @@ impl PackedInstruction {
979981
standard.matrix_as_static_1q(self.params_view())
980982
}
981983
OperationRef::Gate(gate) => gate.matrix_as_static_1q(),
984+
OperationRef::PauliProductRotation(ppr) => ppr.matrix_as_static_1q(),
982985
OperationRef::Unitary(unitary) => unitary.matrix_as_static_1q(),
983986
_ => None,
984987
}
@@ -1003,6 +1006,7 @@ impl PackedInstruction {
10031006
standard.matrix_as_static_2q(self.params_view())
10041007
}
10051008
OperationRef::Gate(gate) => gate.matrix_as_static_2q(),
1009+
OperationRef::PauliProductRotation(ppr) => ppr.matrix_as_static_2q(),
10061010
OperationRef::Unitary(unitary) => unitary.matrix_as_static_2q(),
10071011
_ => None,
10081012
}

crates/synthesis/src/discrete_basis/solovay_kitaev.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ impl SolovayKitaevSynthesis {
119119
let matrix_nalgebra: Matrix2<Complex64> = Matrix2::from_fn(|i, j| matrix[(i, j)]);
120120
self.synthesize_matrix(&matrix_nalgebra, recursion_degree)
121121
}
122+
OperationRef::PauliProductRotation(ppr) => {
123+
let matrix = ppr.matrix().expect("PPR should have a matrix defined");
124+
let matrix_nalgebra: Matrix2<Complex64> = Matrix2::from_fn(|i, j| matrix[(i, j)]);
125+
self.synthesize_matrix(&matrix_nalgebra, recursion_degree)
126+
}
122127
OperationRef::Gate(gate) => {
123128
let matrix = gate.matrix();
124129
match matrix {

crates/transpiler/src/commutation_checker.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,7 @@ pub fn try_matrix_with_definition(
775775
) -> Option<Array2<Complex64>> {
776776
match operation {
777777
OperationRef::StandardGate(gate) => gate.matrix(params),
778+
OperationRef::PauliProductRotation(ppr) => ppr.matrix(),
778779
OperationRef::Unitary(unitary) => unitary.matrix(),
779780
OperationRef::Gate(gate) => Python::attach(|py| -> Option<_> {
780781
if let Some(matrix) = gate.matrix() {
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
features_transpiler:
3+
- |
4+
Enable matrix-based checks for :class:`.PauliProductRotationGate` in the
5+
:class:`.CommutationChecker`. Previously, these checks always returned
6+
``False``.

test/python/circuit/test_commutation_checker.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,21 @@ def test_pauli_and_standard_gate(self, pauli_type):
649649
)
650650
self.assertEqual(expected, commutes)
651651

652+
@data("evolution", "pauli", "rotation", "measure")
653+
def test_pauli_based_with_matrix(self, pauli_type):
654+
"""Test commutation with a matrix-based gate."""
655+
z_pauli = build_pauli_gate("Z", pauli_type)
656+
clbit = [0] if pauli_type == "measure" else []
657+
z_unitary = UnitaryGate(ZGate().to_matrix())
658+
x_unitary = UnitaryGate(XGate().to_matrix())
659+
660+
with self.subTest(other="x_unitary"):
661+
self.assertFalse(scc.commute(z_pauli, [0], clbit, x_unitary, [0], []))
662+
663+
expect = pauli_type != "measure" # False for measure, else True
664+
with self.subTest(other="z_unitary"):
665+
self.assertEqual(expect, scc.commute(z_pauli, [0], clbit, z_unitary, [0], []))
666+
652667

653668
def build_pauli_gate(pauli_string: str, gate_type: str) -> Gate:
654669
"""Build a Pauli-based gate off a Pauli string.

test/python/transpiler/test_solovay_kitaev.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,11 @@
3333
RXGate,
3434
RYGate,
3535
RZGate,
36+
PauliProductRotationGate,
3637
)
3738
from qiskit.circuit import QuantumRegister
3839
from qiskit.converters import circuit_to_dag, dag_to_circuit
39-
from qiskit.quantum_info import Operator
40+
from qiskit.quantum_info import Operator, Pauli
4041
from qiskit.synthesis.discrete_basis.generate_basis_approximations import (
4142
generate_basic_approximations,
4243
)
@@ -383,6 +384,20 @@ def test_y_gate(self):
383384
diff = _trace_distance(circuit, transpiled)
384385
self.assertLess(diff, 1e-6)
385386

387+
def test_ppr(self):
388+
"""Test a circuit with a Pauli-product rotation."""
389+
ppr = PauliProductRotationGate(Pauli("X"), 0.2)
390+
circuit = QuantumCircuit(1)
391+
circuit.append(ppr, [0])
392+
393+
transpiled = self.default_sk(circuit)
394+
with self.subTest(msg="test gate set"):
395+
self.assertEqual(set(transpiled.count_ops()), {"h", "t", "tdg"})
396+
397+
with self.subTest(msg="test approximation"):
398+
diff = _trace_distance(circuit, transpiled)
399+
self.assertLess(diff, 1e-5)
400+
386401
@data(["unitary"], ["rz"])
387402
def test_sk_synth_gates_to_basis(self, synth_gates):
388403
"""Verify two qubit unitaries are synthesized to match basis gates."""

0 commit comments

Comments
 (0)