Skip to content

Commit d3fb389

Browse files
authored
Add 2q fractional gates to the ConsolidateBlocks transpiler pass (#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
1 parent 091228c commit d3fb389

File tree

7 files changed

+110
-20
lines changed

7 files changed

+110
-20
lines changed

crates/accelerate/src/consolidate_blocks.rs

+21-6
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,28 @@ use num_complex::Complex64;
1717
use numpy::PyReadonlyArray2;
1818
use pyo3::intern;
1919
use pyo3::prelude::*;
20-
use rustworkx_core::petgraph::stable_graph::NodeIndex;
21-
use smallvec::smallvec;
22-
2320
use qiskit_circuit::circuit_data::CircuitData;
2421
use qiskit_circuit::dag_circuit::DAGCircuit;
2522
use qiskit_circuit::gate_matrix::{ONE_QUBIT_IDENTITY, TWO_QUBIT_IDENTITY};
2623
use qiskit_circuit::imports::{QI_OPERATOR, QUANTUM_CIRCUIT};
2724
use qiskit_circuit::operations::{ArrayType, Operation, Param, UnitaryGate};
2825
use qiskit_circuit::packed_instruction::PackedOperation;
2926
use qiskit_circuit::Qubit;
27+
use rustworkx_core::petgraph::stable_graph::NodeIndex;
28+
use smallvec::smallvec;
3029

3130
use crate::convert_2q_block_matrix::{blocks_to_matrix, get_matrix_from_inst};
3231
use crate::euler_one_qubit_decomposer::matmul_1q;
3332
use crate::nlayout::PhysicalQubit;
3433
use crate::target_transpiler::Target;
35-
use crate::two_qubit_decompose::TwoQubitBasisDecomposer;
34+
use crate::two_qubit_decompose::{TwoQubitBasisDecomposer, TwoQubitControlledUDecomposer};
35+
36+
#[allow(clippy::large_enum_variant)]
37+
#[derive(Clone, Debug, FromPyObject)]
38+
pub enum DecomposerType {
39+
TwoQubitBasis(TwoQubitBasisDecomposer),
40+
TwoQubitControlledU(TwoQubitControlledUDecomposer),
41+
}
3642

3743
fn is_supported(
3844
target: Option<&Target>,
@@ -61,7 +67,7 @@ const MAX_2Q_DEPTH: usize = 20;
6167
pub(crate) fn consolidate_blocks(
6268
py: Python,
6369
dag: &mut DAGCircuit,
64-
decomposer: &TwoQubitBasisDecomposer,
70+
decomposer: DecomposerType,
6571
basis_gate_name: &str,
6672
force_consolidate: bool,
6773
target: Option<&Target>,
@@ -211,8 +217,17 @@ pub(crate) fn consolidate_blocks(
211217
];
212218
let matrix = blocks_to_matrix(py, dag, &block, block_index_map).ok();
213219
if let Some(matrix) = matrix {
220+
let num_basis_gates = match decomposer {
221+
DecomposerType::TwoQubitBasis(ref decomp) => {
222+
decomp.num_basis_gates_inner(matrix.view())
223+
}
224+
DecomposerType::TwoQubitControlledU(ref decomp) => {
225+
decomp.num_basis_gates_inner(matrix.view())?
226+
}
227+
};
228+
214229
if force_consolidate
215-
|| decomposer.num_basis_gates_inner(matrix.view()) < basis_count
230+
|| num_basis_gates < basis_count
216231
|| block.len() > MAX_2Q_DEPTH
217232
|| (basis_gates.is_some() && outside_basis)
218233
|| (target.is_some() && outside_basis)

crates/accelerate/src/two_qubit_decompose.rs

+10
Original file line numberDiff line numberDiff line change
@@ -2497,6 +2497,16 @@ type InverseReturn = (Option<StandardGate>, SmallVec<[f64; 3]>, SmallVec<[u8; 2]
24972497
/// :math:`U \sim U_d(\alpha, 0, 0) \sim \text{Ctrl-U}`
24982498
/// gate that is locally equivalent to an :class:`.RXXGate`.
24992499
impl TwoQubitControlledUDecomposer {
2500+
/// Compute the number of basis gates needed for a given unitary
2501+
pub fn num_basis_gates_inner(&self, unitary: ArrayView2<Complex64>) -> PyResult<usize> {
2502+
let target_decomposed =
2503+
TwoQubitWeylDecomposition::new_inner(unitary, Some(DEFAULT_FIDELITY), None)?;
2504+
let num_basis_gates = (((target_decomposed.a).abs() > DEFAULT_ATOL) as usize)
2505+
+ (((target_decomposed.b).abs() > DEFAULT_ATOL) as usize)
2506+
+ (((target_decomposed.c).abs() > DEFAULT_ATOL) as usize);
2507+
Ok(num_basis_gates)
2508+
}
2509+
25002510
/// invert 2q gate sequence
25012511
fn invert_2q_gate(
25022512
&self,

qiskit/synthesis/two_qubit/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@
1616
TwoQubitBasisDecomposer,
1717
two_qubit_cnot_decompose,
1818
TwoQubitWeylDecomposition,
19+
TwoQubitControlledUDecomposer,
1920
)

qiskit/synthesis/two_qubit/two_qubit_decompose.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -288,15 +288,16 @@ def __init__(self, rxx_equivalent_gate: Type[Gate], euler_basis: str = "ZXZ"):
288288
QiskitError: If the gate is not locally equivalent to an :class:`.RXXGate`.
289289
"""
290290
if rxx_equivalent_gate._standard_gate is not None:
291-
self._inner_decomposition = two_qubit_decompose.TwoQubitControlledUDecomposer(
291+
self._inner_decomposer = two_qubit_decompose.TwoQubitControlledUDecomposer(
292292
rxx_equivalent_gate._standard_gate, euler_basis
293293
)
294+
self.gate_name = rxx_equivalent_gate._standard_gate.name
294295
else:
295-
self._inner_decomposition = two_qubit_decompose.TwoQubitControlledUDecomposer(
296+
self._inner_decomposer = two_qubit_decompose.TwoQubitControlledUDecomposer(
296297
rxx_equivalent_gate, euler_basis
297298
)
298299
self.rxx_equivalent_gate = rxx_equivalent_gate
299-
self.scale = self._inner_decomposition.scale
300+
self.scale = self._inner_decomposer.scale
300301
self.euler_basis = euler_basis
301302

302303
def __call__(
@@ -312,7 +313,7 @@ def __call__(
312313
313314
Note: atol is passed to OneQubitEulerDecomposer.
314315
"""
315-
circ_data = self._inner_decomposition(np.asarray(unitary, dtype=complex), atol)
316+
circ_data = self._inner_decomposer(np.asarray(unitary, dtype=complex), atol)
316317
return QuantumCircuit._from_circuit_data(circ_data, add_regs=True)
317318

318319

@@ -353,6 +354,7 @@ def __init__(
353354
gate_name = "cx"
354355
else:
355356
gate_name = "USER_GATE"
357+
self.gate_name = gate_name
356358

357359
self._inner_decomposer = two_qubit_decompose.TwoQubitBasisDecomposer(
358360
gate_name,

qiskit/transpiler/passes/optimization/consolidate_blocks.py

+32-9
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,22 @@
1212

1313
"""Replace each block of consecutive gates by a single Unitary node."""
1414
from __future__ import annotations
15-
from math import pi
1615

17-
from qiskit.synthesis.two_qubit import TwoQubitBasisDecomposer
18-
from qiskit.circuit.library.standard_gates import CXGate, CZGate, iSwapGate, ECRGate, RXXGate
16+
from qiskit.synthesis.two_qubit import TwoQubitBasisDecomposer, TwoQubitControlledUDecomposer
17+
from qiskit.circuit.library.standard_gates import (
18+
CXGate,
19+
CZGate,
20+
iSwapGate,
21+
ECRGate,
22+
RXXGate,
23+
RYYGate,
24+
RZZGate,
25+
RZXGate,
26+
CRXGate,
27+
CRYGate,
28+
CRZGate,
29+
CPhaseGate,
30+
)
1931

2032
from qiskit.transpiler.basepasses import TransformationPass
2133
from qiskit.transpiler.passmanager import PassManager
@@ -29,7 +41,17 @@
2941
"cz": CZGate(),
3042
"iswap": iSwapGate(),
3143
"ecr": ECRGate(),
32-
"rxx": RXXGate(pi / 2),
44+
}
45+
46+
KAK_GATE_PARAM_NAMES = {
47+
"rxx": RXXGate,
48+
"rzz": RZZGate,
49+
"ryy": RYYGate,
50+
"rzx": RZXGate,
51+
"cphase": CPhaseGate,
52+
"crx": CRXGate,
53+
"cry": CRYGate,
54+
"crz": CRZGate,
3355
}
3456

3557

@@ -77,13 +99,14 @@ def __init__(
7799
self.decomposer = TwoQubitBasisDecomposer(kak_basis_gate)
78100
elif basis_gates is not None:
79101
kak_gates = KAK_GATE_NAMES.keys() & (basis_gates or [])
102+
kak_param_gates = KAK_GATE_PARAM_NAMES.keys() & (basis_gates or [])
80103
if kak_gates:
81104
self.decomposer = TwoQubitBasisDecomposer(
82-
KAK_GATE_NAMES[kak_gates.pop()], basis_fidelity=approximation_degree or 1.0
105+
KAK_GATE_NAMES[list(kak_gates)[0]], basis_fidelity=approximation_degree or 1.0
83106
)
84-
elif "rzx" in basis_gates:
85-
self.decomposer = TwoQubitBasisDecomposer(
86-
CXGate(), basis_fidelity=approximation_degree or 1.0
107+
elif kak_param_gates:
108+
self.decomposer = TwoQubitControlledUDecomposer(
109+
KAK_GATE_PARAM_NAMES[list(kak_param_gates)[0]]
87110
)
88111
else:
89112
self.decomposer = None
@@ -109,7 +132,7 @@ def run(self, dag):
109132
consolidate_blocks(
110133
dag,
111134
self.decomposer._inner_decomposer,
112-
self.decomposer.gate.name,
135+
self.decomposer.gate_name,
113136
self.force_consolidate,
114137
target=self.target,
115138
basis_gates=self.basis_gates,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
features_transpiler:
3+
- |
4+
Added support for two-qubit fractional basis gates, such as :class:`.RZZGate`, to the
5+
:class:`.ConsolidateBlocks` transpiler pass. The decomposition itself is done using the
6+
:class:`.TwoQubitControlledUDecomposer`.
7+
8+
For example::
9+
10+
from qiskit import QuantumCircuit
11+
from qiskit.transpiler import generate_preset_pass_manager
12+
from qiskit.transpiler.passes import ConsolidateBlocks
13+
14+
qc = QuantumCircuit(2)
15+
qc.rzz(0.1, 0, 1)
16+
qc.rzz(0.2, 0, 1)
17+
consolidate_pass = ConsolidateBlocks(basis_gates=["rz", "rzz", "sx", "x", "rx"])
18+
block = consolidate_pass(qc) # consolidate the circuit into a single unitary block
19+
block.draw(output='mpl')
20+
21+
pm = generate_preset_pass_manager(
22+
optimization_level=2, basis_gates=["rz", "rzz", "sx", "x", "rx"]
23+
)
24+
tqc = pm.run(qc) # synthesizing the circuit into basis gates
25+
tqc.draw(output='mpl')

test/python/transpiler/test_consolidate_blocks.py

+15-1
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,11 @@ def test_no_kak_gates_in_preset_pm(self, opt_level):
581581
optimization_level=opt_level, basis_gates=["rz", "rzz", "sx", "x", "rx"]
582582
)
583583
tqc = pm.run(qc)
584-
self.assertEqual(ref_tqc, tqc)
584+
# it's enough to check that the number of 2-qubit gates does not change
585+
count_rzz_ref = ref_tqc.count_ops()["rzz"]
586+
count_rzz_tqc = tqc.count_ops()["rzz"]
587+
self.assertEqual(Operator.from_circuit(qc), Operator.from_circuit(tqc))
588+
self.assertEqual(count_rzz_ref, count_rzz_tqc)
585589

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

657+
def test_collect_rzz(self):
658+
"""Collect blocks with RZZ gates."""
659+
qc = QuantumCircuit(2)
660+
qc.rzz(0.1, 0, 1)
661+
qc.rzz(0.2, 0, 1)
662+
consolidate_pass = ConsolidateBlocks(basis_gates=["rzz", "rx", "rz"])
663+
res = consolidate_pass(qc)
664+
self.assertEqual({"unitary": 1}, res.count_ops())
665+
self.assertEqual(Operator.from_circuit(qc), Operator(res.data[0].operation.params[0]))
666+
653667

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

0 commit comments

Comments
 (0)