Skip to content

Commit

Permalink
Add 2q fractional gates to the ConsolidateBlocks transpiler pass (#…
Browse files Browse the repository at this point in the history
…13884)

* add TwoQubitControlledUDecomposer to init file

* add kak parametrized gates

* add a test for parametrized gates

* add num_basis_gates_inner function to TwoQubitControlledUDecomoser class

* add TwoQubitControlledUDecomposer to consolidate_blocks function

* add TwoQubitControlledUDecomposer to ConsolidateBlocks pass

* add FromPyObject to enum

* replace _inner_decomposition by _inner_decomposer

* update rust code

* add self.gate_name to 2-qubit decmposer classes

* extend test_collect_rzz test

* add release notes

* update test_no_kak_gates_in_present_pm

* remove commented line

* add allow clippy

* do not pop
  • Loading branch information
ShellyGarion authored Mar 4, 2025
1 parent 091228c commit ec33b5f
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 20 deletions.
27 changes: 21 additions & 6 deletions crates/accelerate/src/consolidate_blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,28 @@ use num_complex::Complex64;
use numpy::PyReadonlyArray2;
use pyo3::intern;
use pyo3::prelude::*;
use rustworkx_core::petgraph::stable_graph::NodeIndex;
use smallvec::smallvec;

use qiskit_circuit::circuit_data::CircuitData;
use qiskit_circuit::dag_circuit::DAGCircuit;
use qiskit_circuit::gate_matrix::{ONE_QUBIT_IDENTITY, TWO_QUBIT_IDENTITY};
use qiskit_circuit::imports::{QI_OPERATOR, QUANTUM_CIRCUIT};
use qiskit_circuit::operations::{ArrayType, Operation, Param, UnitaryGate};
use qiskit_circuit::packed_instruction::PackedOperation;
use qiskit_circuit::Qubit;
use rustworkx_core::petgraph::stable_graph::NodeIndex;
use smallvec::smallvec;

use crate::convert_2q_block_matrix::{blocks_to_matrix, get_matrix_from_inst};
use crate::euler_one_qubit_decomposer::matmul_1q;
use crate::nlayout::PhysicalQubit;
use crate::target_transpiler::Target;
use crate::two_qubit_decompose::TwoQubitBasisDecomposer;
use crate::two_qubit_decompose::{TwoQubitBasisDecomposer, TwoQubitControlledUDecomposer};

#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, FromPyObject)]
pub enum DecomposerType {
TwoQubitBasis(TwoQubitBasisDecomposer),
TwoQubitControlledU(TwoQubitControlledUDecomposer),
}

fn is_supported(
target: Option<&Target>,
Expand Down Expand Up @@ -61,7 +67,7 @@ const MAX_2Q_DEPTH: usize = 20;
pub(crate) fn consolidate_blocks(
py: Python,
dag: &mut DAGCircuit,
decomposer: &TwoQubitBasisDecomposer,
decomposer: DecomposerType,
basis_gate_name: &str,
force_consolidate: bool,
target: Option<&Target>,
Expand Down Expand Up @@ -211,8 +217,17 @@ pub(crate) fn consolidate_blocks(
];
let matrix = blocks_to_matrix(py, dag, &block, block_index_map).ok();
if let Some(matrix) = matrix {
let num_basis_gates = match decomposer {
DecomposerType::TwoQubitBasis(ref decomp) => {
decomp.num_basis_gates_inner(matrix.view())
}
DecomposerType::TwoQubitControlledU(ref decomp) => {
decomp.num_basis_gates_inner(matrix.view())?
}
};

if force_consolidate
|| decomposer.num_basis_gates_inner(matrix.view()) < basis_count
|| num_basis_gates < basis_count
|| block.len() > MAX_2Q_DEPTH
|| (basis_gates.is_some() && outside_basis)
|| (target.is_some() && outside_basis)
Expand Down
10 changes: 10 additions & 0 deletions crates/accelerate/src/two_qubit_decompose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2497,6 +2497,16 @@ type InverseReturn = (Option<StandardGate>, SmallVec<[f64; 3]>, SmallVec<[u8; 2]
/// :math:`U \sim U_d(\alpha, 0, 0) \sim \text{Ctrl-U}`
/// gate that is locally equivalent to an :class:`.RXXGate`.
impl TwoQubitControlledUDecomposer {
/// Compute the number of basis gates needed for a given unitary
pub fn num_basis_gates_inner(&self, unitary: ArrayView2<Complex64>) -> PyResult<usize> {
let target_decomposed =
TwoQubitWeylDecomposition::new_inner(unitary, Some(DEFAULT_FIDELITY), None)?;
let num_basis_gates = (((target_decomposed.a).abs() > DEFAULT_ATOL) as usize)
+ (((target_decomposed.b).abs() > DEFAULT_ATOL) as usize)
+ (((target_decomposed.c).abs() > DEFAULT_ATOL) as usize);
Ok(num_basis_gates)
}

/// invert 2q gate sequence
fn invert_2q_gate(
&self,
Expand Down
1 change: 1 addition & 0 deletions qiskit/synthesis/two_qubit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@
TwoQubitBasisDecomposer,
two_qubit_cnot_decompose,
TwoQubitWeylDecomposition,
TwoQubitControlledUDecomposer,
)
10 changes: 6 additions & 4 deletions qiskit/synthesis/two_qubit/two_qubit_decompose.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,15 +288,16 @@ def __init__(self, rxx_equivalent_gate: Type[Gate], euler_basis: str = "ZXZ"):
QiskitError: If the gate is not locally equivalent to an :class:`.RXXGate`.
"""
if rxx_equivalent_gate._standard_gate is not None:
self._inner_decomposition = two_qubit_decompose.TwoQubitControlledUDecomposer(
self._inner_decomposer = two_qubit_decompose.TwoQubitControlledUDecomposer(
rxx_equivalent_gate._standard_gate, euler_basis
)
self.gate_name = rxx_equivalent_gate._standard_gate.name
else:
self._inner_decomposition = two_qubit_decompose.TwoQubitControlledUDecomposer(
self._inner_decomposer = two_qubit_decompose.TwoQubitControlledUDecomposer(
rxx_equivalent_gate, euler_basis
)
self.rxx_equivalent_gate = rxx_equivalent_gate
self.scale = self._inner_decomposition.scale
self.scale = self._inner_decomposer.scale
self.euler_basis = euler_basis

def __call__(
Expand All @@ -312,7 +313,7 @@ def __call__(
Note: atol is passed to OneQubitEulerDecomposer.
"""
circ_data = self._inner_decomposition(np.asarray(unitary, dtype=complex), atol)
circ_data = self._inner_decomposer(np.asarray(unitary, dtype=complex), atol)
return QuantumCircuit._from_circuit_data(circ_data, add_regs=True)


Expand Down Expand Up @@ -353,6 +354,7 @@ def __init__(
gate_name = "cx"
else:
gate_name = "USER_GATE"
self.gate_name = gate_name

self._inner_decomposer = two_qubit_decompose.TwoQubitBasisDecomposer(
gate_name,
Expand Down
41 changes: 32 additions & 9 deletions qiskit/transpiler/passes/optimization/consolidate_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,22 @@

"""Replace each block of consecutive gates by a single Unitary node."""
from __future__ import annotations
from math import pi

from qiskit.synthesis.two_qubit import TwoQubitBasisDecomposer
from qiskit.circuit.library.standard_gates import CXGate, CZGate, iSwapGate, ECRGate, RXXGate
from qiskit.synthesis.two_qubit import TwoQubitBasisDecomposer, TwoQubitControlledUDecomposer
from qiskit.circuit.library.standard_gates import (
CXGate,
CZGate,
iSwapGate,
ECRGate,
RXXGate,
RYYGate,
RZZGate,
RZXGate,
CRXGate,
CRYGate,
CRZGate,
CPhaseGate,
)

from qiskit.transpiler.basepasses import TransformationPass
from qiskit.transpiler.passmanager import PassManager
Expand All @@ -29,7 +41,17 @@
"cz": CZGate(),
"iswap": iSwapGate(),
"ecr": ECRGate(),
"rxx": RXXGate(pi / 2),
}

KAK_GATE_PARAM_NAMES = {
"rxx": RXXGate,
"rzz": RZZGate,
"ryy": RYYGate,
"rzx": RZXGate,
"cphase": CPhaseGate,
"crx": CRXGate,
"cry": CRYGate,
"crz": CRZGate,
}


Expand Down Expand Up @@ -77,13 +99,14 @@ def __init__(
self.decomposer = TwoQubitBasisDecomposer(kak_basis_gate)
elif basis_gates is not None:
kak_gates = KAK_GATE_NAMES.keys() & (basis_gates or [])
kak_param_gates = KAK_GATE_PARAM_NAMES.keys() & (basis_gates or [])
if kak_gates:
self.decomposer = TwoQubitBasisDecomposer(
KAK_GATE_NAMES[kak_gates.pop()], basis_fidelity=approximation_degree or 1.0
KAK_GATE_NAMES[list(kak_gates)[0]], basis_fidelity=approximation_degree or 1.0
)
elif "rzx" in basis_gates:
self.decomposer = TwoQubitBasisDecomposer(
CXGate(), basis_fidelity=approximation_degree or 1.0
elif kak_param_gates:
self.decomposer = TwoQubitControlledUDecomposer(
KAK_GATE_PARAM_NAMES[list(kak_param_gates)[0]]
)
else:
self.decomposer = None
Expand All @@ -109,7 +132,7 @@ def run(self, dag):
consolidate_blocks(
dag,
self.decomposer._inner_decomposer,
self.decomposer.gate.name,
self.decomposer.gate_name,
self.force_consolidate,
target=self.target,
basis_gates=self.basis_gates,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
features_transpiler:
- |
Added support for two-qubit fractional basis gates, such as :class:`.RZZGate`, to the
:class:`.ConsolidateBlocks` transpiler pass. The decomposition itself is done using the
:class:`.TwoQubitControlledUDecomposer`.
For example::
from qiskit import QuantumCircuit
from qiskit.transpiler import generate_preset_pass_manager
from qiskit.transpiler.passes import ConsolidateBlocks
qc = QuantumCircuit(2)
qc.rzz(0.1, 0, 1)
qc.rzz(0.2, 0, 1)
consolidate_pass = ConsolidateBlocks(basis_gates=["rz", "rzz", "sx", "x", "rx"])
block = consolidate_pass(qc) # consolidate the circuit into a single unitary block
block.draw(output='mpl')
pm = generate_preset_pass_manager(
optimization_level=2, basis_gates=["rz", "rzz", "sx", "x", "rx"]
)
tqc = pm.run(qc) # synthesizing the circuit into basis gates
tqc.draw(output='mpl')
16 changes: 15 additions & 1 deletion test/python/transpiler/test_consolidate_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,11 @@ def test_no_kak_gates_in_preset_pm(self, opt_level):
optimization_level=opt_level, basis_gates=["rz", "rzz", "sx", "x", "rx"]
)
tqc = pm.run(qc)
self.assertEqual(ref_tqc, tqc)
# it's enough to check that the number of 2-qubit gates does not change
count_rzz_ref = ref_tqc.count_ops()["rzz"]
count_rzz_tqc = tqc.count_ops()["rzz"]
self.assertEqual(Operator.from_circuit(qc), Operator.from_circuit(tqc))
self.assertEqual(count_rzz_ref, count_rzz_tqc)

def test_non_cx_basis_gate(self):
"""Test a non-cx kak gate is consolidated correctly."""
Expand Down Expand Up @@ -650,6 +654,16 @@ def test_non_cx_target(self):
self.assertEqual({"unitary": 1}, res.count_ops())
self.assertEqual(Operator.from_circuit(qc), Operator(res.data[0].operation.params[0]))

def test_collect_rzz(self):
"""Collect blocks with RZZ gates."""
qc = QuantumCircuit(2)
qc.rzz(0.1, 0, 1)
qc.rzz(0.2, 0, 1)
consolidate_pass = ConsolidateBlocks(basis_gates=["rzz", "rx", "rz"])
res = consolidate_pass(qc)
self.assertEqual({"unitary": 1}, res.count_ops())
self.assertEqual(Operator.from_circuit(qc), Operator(res.data[0].operation.params[0]))


if __name__ == "__main__":
unittest.main()

0 comments on commit ec33b5f

Please sign in to comment.