Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 2q fractional gates to the ConsolidateBlocks transpiler pass #13884

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
26 changes: 20 additions & 6 deletions crates/accelerate/src/consolidate_blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ 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::circuit_instruction::ExtraInstructionAttributes;
use qiskit_circuit::dag_circuit::DAGCircuit;
Expand All @@ -28,12 +25,20 @@ 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};

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

fn is_supported(
target: Option<&Target>,
Expand Down Expand Up @@ -62,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 @@ -212,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
40 changes: 32 additions & 8 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,18 @@
"cz": CZGate(),
"iswap": iSwapGate(),
"ecr": ECRGate(),
"rxx": RXXGate(pi / 2),
# "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 +100,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
)
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[kak_param_gates.pop()]
)
else:
self.decomposer = None
Expand All @@ -109,7 +133,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
10 changes: 10 additions & 0 deletions test/python/transpiler/test_consolidate_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,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]))
Copy link
Member Author

@ShellyGarion ShellyGarion Feb 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that due to the TwoQubitControlledUDecomposer synthesis algorithm, the synthesized circuit is:

q_0: ──────────■──────────────────
     ┌───────┐ │ZZ(-0.3) ┌───────┐
q_1: ┤ Rx(π) ├─■─────────┤ Rx(π) ├
     └───────┘           └───────┘

and not just RZZGate(0.3)



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