Skip to content

Commit 8d29a79

Browse files
Cryorisalexanderivrii
authored andcommitted
MCMTGate & plugins (Qiskit#13150)
* MCMT in Rust & Plugin * test plugins * more docs * add support for ``ctrl_state`` and fix tests * Sasha's review comments * fix cyclic imports * add sphinx doc for mcmt_vchain * review comments & reno * Update releasenotes/notes/mcmt-gate-a201d516f05c7d56.yaml Co-authored-by: Alexander Ivrii <[email protected]> * Fix plugin name * Update from_packed_operations usage * add missing MCMTGate to docs --------- Co-authored-by: Alexander Ivrii <[email protected]>
1 parent 83a5104 commit 8d29a79

File tree

16 files changed

+709
-123
lines changed

16 files changed

+709
-123
lines changed

crates/accelerate/src/synthesis/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
mod clifford;
1414
pub mod linear;
1515
pub mod linear_phase;
16+
mod multi_controlled;
1617
mod permutation;
1718

1819
use pyo3::prelude::*;
@@ -34,5 +35,9 @@ pub fn synthesis(m: &Bound<PyModule>) -> PyResult<()> {
3435
clifford::clifford(&clifford_mod)?;
3536
m.add_submodule(&clifford_mod)?;
3637

38+
let mc_mod = PyModule::new_bound(m.py(), "multi_controlled")?;
39+
multi_controlled::multi_controlled(&mc_mod)?;
40+
m.add_submodule(&mc_mod)?;
41+
3742
Ok(())
3843
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
// This code is part of Qiskit.
2+
//
3+
// (C) Copyright IBM 2024
4+
//
5+
// This code is licensed under the Apache License, Version 2.0. You may
6+
// obtain a copy of this license in the LICENSE.txt file in the root directory
7+
// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8+
//
9+
// Any modifications or derivative works of this code must retain this
10+
// copyright notice, and modified files need to carry a notice indicating
11+
// that they have been altered from the originals.
12+
13+
use pyo3::prelude::*;
14+
use qiskit_circuit::circuit_data::CircuitData;
15+
use qiskit_circuit::circuit_instruction::OperationFromPython;
16+
use qiskit_circuit::operations::{Param, StandardGate};
17+
use qiskit_circuit::packed_instruction::PackedOperation;
18+
use qiskit_circuit::{Clbit, Qubit};
19+
use smallvec::{smallvec, SmallVec};
20+
21+
use crate::QiskitError;
22+
23+
/// A Toffoli chain, implementing a multi-control condition on all controls using
24+
/// ``controls.len() - 1`` auxiliary qubits.
25+
///
26+
/// For example, for 4 controls we require 3 auxiliaries and create the circuit
27+
///
28+
/// control_0: ──■──────────────
29+
/// │
30+
/// control_1: ──■──────────────
31+
/// │
32+
/// control_2: ──┼────■─────────
33+
/// │ │
34+
/// control_3: ──┼────┼────■────
35+
/// ┌─┴─┐ │ │
36+
/// aux_0: ┤ X ├──■────┼────
37+
/// └───┘┌─┴─┐ │
38+
/// aux_1: ─────┤ X ├──■────
39+
/// └───┘┌─┴─┐ "master control" qubit: controlling on this
40+
/// aux_2: ──────────┤ X ├── <-- implements a controlled operation on all qubits
41+
/// └───┘ in the "control" register
42+
fn ccx_chain<'a>(
43+
controls: &'a [usize],
44+
auxiliaries: &'a [usize],
45+
) -> impl DoubleEndedIterator<
46+
Item = PyResult<(
47+
PackedOperation,
48+
SmallVec<[Param; 3]>,
49+
Vec<Qubit>,
50+
Vec<Clbit>,
51+
)>,
52+
> + 'a {
53+
let n = controls.len() - 1; // number of chain elements
54+
std::iter::once((controls[0], controls[1], auxiliaries[0]))
55+
.chain((0..n - 1).map(|i| (controls[i + 2], auxiliaries[i], auxiliaries[i + 1])))
56+
.map(|(ctrl1, ctrl2, target)| {
57+
Ok((
58+
StandardGate::CCXGate.into(),
59+
smallvec![],
60+
vec![
61+
Qubit(ctrl1 as u32),
62+
Qubit(ctrl2 as u32),
63+
Qubit(target as u32),
64+
],
65+
vec![],
66+
))
67+
})
68+
}
69+
70+
/// Implement multi-control, multi-target of a single-qubit gate using a V-chain with
71+
/// (num_ctrl_qubits - 1) auxiliary qubits.
72+
/// ``controlled_gate`` here must already be the controlled operation, e.g. if we
73+
/// call MCMT of X, then it must be a CX gate. This is because I currently don't know how to
74+
/// nicely map the single-qubit gate to it's controlled version.
75+
///
76+
/// For example, 4 controls and 2 target qubits for the Hadamard gate, generates
77+
///
78+
/// q_0: ──■──────────────────────────────────■──
79+
/// │ │
80+
/// q_1: ──■──────────────────────────────────■──
81+
/// │ │
82+
/// q_2: ──┼────■────────────────────────■────┼──
83+
/// │ │ │ │
84+
/// q_3: ──┼────┼────■──────────────■────┼────┼──
85+
/// │ │ │ ┌───┐ │ │ │
86+
/// q_4: ──┼────┼────┼──┤ H ├───────┼────┼────┼──
87+
/// │ │ │ └─┬─┘┌───┐ │ │ │
88+
/// q_5: ──┼────┼────┼────┼──┤ H ├──┼────┼────┼──
89+
/// ┌─┴─┐ │ │ │ └─┬─┘ │ │ ┌─┴─┐
90+
/// q_6: ┤ X ├──■────┼────┼────┼────┼────■──┤ X ├
91+
/// └───┘┌─┴─┐ │ │ │ │ ┌─┴─┐└───┘
92+
/// q_7: ─────┤ X ├──■────┼────┼────■──┤ X ├─────
93+
/// └───┘┌─┴─┐ │ │ ┌─┴─┐└───┘
94+
/// q_8: ──────────┤ X ├──■────■──┤ X ├──────────
95+
/// └───┘ └───┘
96+
///
97+
#[pyfunction]
98+
#[pyo3(signature = (controlled_gate, num_ctrl_qubits, num_target_qubits, control_state=None))]
99+
pub fn mcmt_v_chain(
100+
py: Python,
101+
controlled_gate: OperationFromPython,
102+
num_ctrl_qubits: usize,
103+
num_target_qubits: usize,
104+
control_state: Option<usize>,
105+
) -> PyResult<CircuitData> {
106+
if num_ctrl_qubits < 1 {
107+
return Err(QiskitError::new_err("Need at least 1 control qubit."));
108+
}
109+
110+
let packed_controlled_gate = controlled_gate.operation;
111+
let num_qubits = if num_ctrl_qubits > 1 {
112+
2 * num_ctrl_qubits - 1 + num_target_qubits
113+
} else {
114+
1 + num_target_qubits // we can have 1 control and multiple targets
115+
};
116+
117+
let control_state = control_state.unwrap_or(usize::pow(2, num_ctrl_qubits as u32) - 1);
118+
119+
// First, we handle bitflips in case of open controls.
120+
let flip_control_state = (0..num_ctrl_qubits)
121+
.filter(|index| control_state & (1 << index) == 0)
122+
.map(|index| {
123+
Ok((
124+
PackedOperation::from_standard(StandardGate::XGate),
125+
smallvec![] as SmallVec<[Param; 3]>,
126+
vec![Qubit(index as u32)],
127+
vec![] as Vec<Clbit>,
128+
))
129+
});
130+
131+
// Then, we create the operations that apply the controlled base gate.
132+
// That's because we only add the V-chain of CCX gates, if the number of controls
133+
// is larger than 1, otherwise we're already done here.
134+
let master_control = if num_ctrl_qubits > 1 {
135+
num_qubits - 1
136+
} else {
137+
0
138+
};
139+
let targets = (0..num_target_qubits).map(|i| {
140+
Ok((
141+
packed_controlled_gate.clone(),
142+
smallvec![] as SmallVec<[Param; 3]>,
143+
vec![
144+
Qubit(master_control as u32),
145+
Qubit((num_ctrl_qubits + i) as u32),
146+
],
147+
vec![] as Vec<Clbit>,
148+
))
149+
});
150+
151+
// Finally we add the V-chain (or return in case of 1 control).
152+
if num_ctrl_qubits == 1 {
153+
CircuitData::from_packed_operations(
154+
py,
155+
num_qubits as u32,
156+
0,
157+
flip_control_state
158+
.clone()
159+
.chain(targets)
160+
.chain(flip_control_state),
161+
Param::Float(0.0),
162+
)
163+
} else {
164+
// If the number of controls is larger than 1, and we need to apply the V-chain,
165+
// create it here and sandwich the targets in-between.
166+
let controls: Vec<usize> = (0..num_ctrl_qubits).collect();
167+
let auxiliaries: Vec<usize> = (num_ctrl_qubits + num_target_qubits..num_qubits).collect();
168+
let down_chain = ccx_chain(&controls, &auxiliaries);
169+
let up_chain = ccx_chain(&controls, &auxiliaries).rev();
170+
171+
CircuitData::from_packed_operations(
172+
py,
173+
num_qubits as u32,
174+
0,
175+
flip_control_state
176+
.clone()
177+
.chain(down_chain)
178+
.chain(targets)
179+
.chain(up_chain)
180+
.chain(flip_control_state),
181+
Param::Float(0.0),
182+
)
183+
}
184+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// This code is part of Qiskit.
2+
//
3+
// (C) Copyright IBM 2024
4+
//
5+
// This code is licensed under the Apache License, Version 2.0. You may
6+
// obtain a copy of this license in the LICENSE.txt file in the root directory
7+
// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8+
//
9+
// Any modifications or derivative works of this code must retain this
10+
// copyright notice, and modified files need to carry a notice indicating
11+
// that they have been altered from the originals.
12+
13+
use pyo3::prelude::*;
14+
15+
mod mcmt;
16+
17+
pub fn multi_controlled(m: &Bound<PyModule>) -> PyResult<()> {
18+
m.add_function(wrap_pyfunction!(mcmt::mcmt_v_chain, m)?)?;
19+
Ok(())
20+
}

pyproject.toml

+3
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ sk = "qiskit.transpiler.passes.synthesis.solovay_kitaev_synthesis:SolovayKitaevS
8989
"mcx.gray_code" = "qiskit.transpiler.passes.synthesis.hls_plugins:MCXSynthesisGrayCode"
9090
"mcx.noaux_v24" = "qiskit.transpiler.passes.synthesis.hls_plugins:MCXSynthesisNoAuxV24"
9191
"mcx.default" = "qiskit.transpiler.passes.synthesis.hls_plugins:MCXSynthesisDefault"
92+
"mcmt.default" = "qiskit.transpiler.passes.synthesis.hls_plugins:MCMTSynthesisDefault"
93+
"mcmt.noaux" = "qiskit.transpiler.passes.synthesis.hls_plugins:MCMTSynthesisNoAux"
94+
"mcmt.vchain" = "qiskit.transpiler.passes.synthesis.hls_plugins:MCMTSynthesisVChain"
9295
"permutation.default" = "qiskit.transpiler.passes.synthesis.hls_plugins:BasicSynthesisPermutation"
9396
"permutation.kms" = "qiskit.transpiler.passes.synthesis.hls_plugins:KMSSynthesisPermutation"
9497
"permutation.basic" = "qiskit.transpiler.passes.synthesis.hls_plugins:BasicSynthesisPermutation"

qiskit/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@
9595
sys.modules["qiskit._accelerate.commutation_analysis"] = _accelerate.commutation_analysis
9696
sys.modules["qiskit._accelerate.commutation_cancellation"] = _accelerate.commutation_cancellation
9797
sys.modules["qiskit._accelerate.synthesis.linear_phase"] = _accelerate.synthesis.linear_phase
98+
sys.modules["qiskit._accelerate.synthesis.multi_controlled"] = (
99+
_accelerate.synthesis.multi_controlled
100+
)
98101
sys.modules["qiskit._accelerate.split_2q_unitaries"] = _accelerate.split_2q_unitaries
99102
sys.modules["qiskit._accelerate.gate_direction"] = _accelerate.gate_direction
100103
sys.modules["qiskit._accelerate.inverse_cancellation"] = _accelerate.inverse_cancellation

qiskit/circuit/library/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@
181181
GRX
182182
GRY
183183
GRZ
184+
MCMTGate
184185
MCPhaseGate
185186
MCXGate
186187
MCXGrayCode
@@ -501,6 +502,7 @@
501502
Permutation,
502503
PermutationGate,
503504
GMS,
505+
MCMTGate,
504506
MSGate,
505507
GR,
506508
GRX,

qiskit/circuit/library/generalized_gates/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from .diagonal import Diagonal, DiagonalGate
1616
from .permutation import Permutation, PermutationGate
17-
from .mcmt import MCMT, MCMTVChain
17+
from .mcmt import MCMT, MCMTVChain, MCMTGate
1818
from .gms import GMS, MSGate
1919
from .gr import GR, GRX, GRY, GRZ
2020
from .pauli import PauliGate

0 commit comments

Comments
 (0)