Skip to content

Commit

Permalink
Restrict Split2QUnitaries to run on UnitaryGate (#13095)
Browse files Browse the repository at this point in the history
  • Loading branch information
Cryoris authored Sep 5, 2024
1 parent 8606f05 commit 9898979
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 38 deletions.
34 changes: 15 additions & 19 deletions qiskit/transpiler/passes/optimization/split_2q_unitaries.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Splits each two-qubit gate in the `dag` into two single-qubit gates, if possible without error."""
from typing import Optional

from qiskit.transpiler.basepasses import TransformationPass
from qiskit.circuit.quantumcircuitdata import CircuitInstruction
Expand All @@ -21,34 +21,30 @@


class Split2QUnitaries(TransformationPass):
"""Attempt to splits two-qubit gates in a :class:`.DAGCircuit` into two single-qubit gates
"""Attempt to splits two-qubit unitaries in a :class:`.DAGCircuit` into two single-qubit gates.
This pass will analyze all the two qubit gates in the circuit and analyze the gate's unitary
matrix to determine if the gate is actually a product of 2 single qubit gates. In these
cases the 2q gate can be simplified into two single qubit gates and this pass will
perform this optimization and will replace the two qubit gate with two single qubit
:class:`.UnitaryGate`.
This pass will analyze all :class:`.UnitaryGate` instances and determine whether the
matrix is actually a product of 2 single qubit gates. In these cases the 2q gate can be
simplified into two single qubit gates and this pass will perform this optimization and will
replace the two qubit gate with two single qubit :class:`.UnitaryGate`.
"""

def __init__(self, fidelity: Optional[float] = 1.0 - 1e-16):
"""Split2QUnitaries initializer.
def __init__(self, fidelity: float = 1.0 - 1e-16):
"""
Args:
fidelity (float): Allowed tolerance for splitting two-qubit unitaries and gate decompositions
fidelity: Allowed tolerance for splitting two-qubit unitaries and gate decompositions.
"""
super().__init__()
self.requested_fidelity = fidelity

def run(self, dag: DAGCircuit):
def run(self, dag: DAGCircuit) -> DAGCircuit:
"""Run the Split2QUnitaries pass on `dag`."""

for node in dag.topological_op_nodes():
# skip operations without two-qubits and for which we can not determine a potential 1q split
if (
len(node.cargs) > 0
or len(node.qargs) != 2
or node.matrix is None
or node.is_parameterized()
):
# We only attempt to split UnitaryGate objects, but this could be extended in future
# -- however we need to ensure that we can compile the resulting single-qubit unitaries
# to the supported basis gate set.
if not (len(node.qargs) == 2 and node.op.name == "unitary"):
continue

decomp = TwoQubitWeylDecomposition(node.matrix, fidelity=self.requested_fidelity)
Expand Down
8 changes: 8 additions & 0 deletions releasenotes/notes/restrict-split2q-d51d840cc7a7a482.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
fixes:
- |
Fixed an edge case when transpiling a circuit with ``optimization_level`` 2 or 3 with an
incomplete 1-qubit basis gate set on a circuit containing 2-qubit gates, that can be
implemented as a product of single qubit gates. This bug is resolved by restricting
:class:`.Split2QUnitaries` to consider only :class:`.UnitaryGate` objects.
Fixed `#12970 <https://github.com/Qiskit/qiskit/issues/12970>`__.
44 changes: 25 additions & 19 deletions test/python/transpiler/test_split_2q_unitaries.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import numpy as np

from qiskit import QuantumCircuit, QuantumRegister, transpile
from qiskit.circuit.library import UnitaryGate, XGate, ZGate, HGate
from qiskit.circuit.library import UnitaryGate, XGate, ZGate, HGate, CPhaseGate
from qiskit.circuit import Parameter, CircuitInstruction, Gate
from qiskit.providers.fake_provider import GenericBackendV2
from qiskit.quantum_info import Operator
Expand Down Expand Up @@ -160,15 +160,11 @@ def test_no_split(self):
def test_almost_identity(self):
"""Test that the pass handles QFT correctly."""
qc = QuantumCircuit(2)
qc.cp(pi * 2 ** -(26), 0, 1)
qc.unitary(CPhaseGate(pi * 2 ** -(26)).to_matrix(), [0, 1])
pm = PassManager()
pm.append(Collect2qBlocks())
pm.append(ConsolidateBlocks())
pm.append(Split2QUnitaries(fidelity=1.0 - 1e-9))
qc_split = pm.run(qc)
pm = PassManager()
pm.append(Collect2qBlocks())
pm.append(ConsolidateBlocks())
pm.append(Split2QUnitaries())
qc_split2 = pm.run(qc)
self.assertEqual(qc_split.num_nonlocal_gates(), 0)
Expand Down Expand Up @@ -211,19 +207,6 @@ def test_single_q_gates(self):
self.assertTrue(CircuitInstruction(ZGate(), qubits=[qr[1]], clbits=[]) in qc.data)
self.assertTrue(CircuitInstruction(HGate(), qubits=[qr[2]], clbits=[]) in qc.data)

def test_split_qft(self):
"""Test that the pass handles QFT correctly."""
qc = QuantumCircuit(100)
qc.h(0)
for i in range(qc.num_qubits - 2, 0, -1):
qc.cp(pi * 2 ** -(qc.num_qubits - 1 - i), qc.num_qubits - 1, i)
pm = PassManager()
pm.append(Collect2qBlocks())
pm.append(ConsolidateBlocks())
pm.append(Split2QUnitaries())
qc_split = pm.run(qc)
self.assertEqual(26, qc_split.num_nonlocal_gates())

def test_gate_no_array(self):
"""
Test that the pass doesn't fail when the circuit contains a custom gate
Expand Down Expand Up @@ -260,3 +243,26 @@ def mygate(self, qubit1, qubit2):
self.assertTrue(
matrix_equal(Operator(qc).data, Operator(qc_split).data, ignore_phase=False)
)

def test_nosplit_custom(self):
"""Test a single custom gate is not split, even if it is a product.
That is because we cannot guarantee the split gate is valid in a given basis.
Regression test for #12970.
"""

class MyGate(Gate):
"""A custom gate that could be split."""

def __init__(self):
super().__init__("mygate", 2, [])

def to_matrix(self):
return np.eye(4, dtype=complex)

qc = QuantumCircuit(2)
qc.append(MyGate(), [0, 1])

no_split = Split2QUnitaries()(qc)

self.assertDictEqual({"mygate": 1}, no_split.count_ops())

0 comments on commit 9898979

Please sign in to comment.