Skip to content

Commit

Permalink
MCMTGate & plugins (#13150)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
Cryoris and alexanderivrii authored Oct 4, 2024
1 parent 660541a commit 8b6e47e
Show file tree
Hide file tree
Showing 16 changed files with 709 additions and 123 deletions.
5 changes: 5 additions & 0 deletions crates/accelerate/src/synthesis/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
mod clifford;
pub mod linear;
pub mod linear_phase;
mod multi_controlled;
mod permutation;

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

let mc_mod = PyModule::new_bound(m.py(), "multi_controlled")?;
multi_controlled::multi_controlled(&mc_mod)?;
m.add_submodule(&mc_mod)?;

Ok(())
}
184 changes: 184 additions & 0 deletions crates/accelerate/src/synthesis/multi_controlled/mcmt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// This code is part of Qiskit.
//
// (C) Copyright IBM 2024
//
// This code is licensed under the Apache License, Version 2.0. You may
// obtain a copy of this license in the LICENSE.txt file in the root directory
// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
//
// 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.

use pyo3::prelude::*;
use qiskit_circuit::circuit_data::CircuitData;
use qiskit_circuit::circuit_instruction::OperationFromPython;
use qiskit_circuit::operations::{Param, StandardGate};
use qiskit_circuit::packed_instruction::PackedOperation;
use qiskit_circuit::{Clbit, Qubit};
use smallvec::{smallvec, SmallVec};

use crate::QiskitError;

/// A Toffoli chain, implementing a multi-control condition on all controls using
/// ``controls.len() - 1`` auxiliary qubits.
///
/// For example, for 4 controls we require 3 auxiliaries and create the circuit
///
/// control_0: ──■──────────────
/// │
/// control_1: ──■──────────────
/// │
/// control_2: ──┼────■─────────
/// │ │
/// control_3: ──┼────┼────■────
/// ┌─┴─┐ │ │
/// aux_0: ┤ X ├──■────┼────
/// └───┘┌─┴─┐ │
/// aux_1: ─────┤ X ├──■────
/// └───┘┌─┴─┐ "master control" qubit: controlling on this
/// aux_2: ──────────┤ X ├── <-- implements a controlled operation on all qubits
/// └───┘ in the "control" register
fn ccx_chain<'a>(
controls: &'a [usize],
auxiliaries: &'a [usize],
) -> impl DoubleEndedIterator<
Item = PyResult<(
PackedOperation,
SmallVec<[Param; 3]>,
Vec<Qubit>,
Vec<Clbit>,
)>,
> + 'a {
let n = controls.len() - 1; // number of chain elements
std::iter::once((controls[0], controls[1], auxiliaries[0]))
.chain((0..n - 1).map(|i| (controls[i + 2], auxiliaries[i], auxiliaries[i + 1])))
.map(|(ctrl1, ctrl2, target)| {
Ok((
StandardGate::CCXGate.into(),
smallvec![],
vec![
Qubit(ctrl1 as u32),
Qubit(ctrl2 as u32),
Qubit(target as u32),
],
vec![],
))
})
}

/// Implement multi-control, multi-target of a single-qubit gate using a V-chain with
/// (num_ctrl_qubits - 1) auxiliary qubits.
/// ``controlled_gate`` here must already be the controlled operation, e.g. if we
/// call MCMT of X, then it must be a CX gate. This is because I currently don't know how to
/// nicely map the single-qubit gate to it's controlled version.
///
/// For example, 4 controls and 2 target qubits for the Hadamard gate, generates
///
/// q_0: ──■──────────────────────────────────■──
/// │ │
/// q_1: ──■──────────────────────────────────■──
/// │ │
/// q_2: ──┼────■────────────────────────■────┼──
/// │ │ │ │
/// q_3: ──┼────┼────■──────────────■────┼────┼──
/// │ │ │ ┌───┐ │ │ │
/// q_4: ──┼────┼────┼──┤ H ├───────┼────┼────┼──
/// │ │ │ └─┬─┘┌───┐ │ │ │
/// q_5: ──┼────┼────┼────┼──┤ H ├──┼────┼────┼──
/// ┌─┴─┐ │ │ │ └─┬─┘ │ │ ┌─┴─┐
/// q_6: ┤ X ├──■────┼────┼────┼────┼────■──┤ X ├
/// └───┘┌─┴─┐ │ │ │ │ ┌─┴─┐└───┘
/// q_7: ─────┤ X ├──■────┼────┼────■──┤ X ├─────
/// └───┘┌─┴─┐ │ │ ┌─┴─┐└───┘
/// q_8: ──────────┤ X ├──■────■──┤ X ├──────────
/// └───┘ └───┘
///
#[pyfunction]
#[pyo3(signature = (controlled_gate, num_ctrl_qubits, num_target_qubits, control_state=None))]
pub fn mcmt_v_chain(
py: Python,
controlled_gate: OperationFromPython,
num_ctrl_qubits: usize,
num_target_qubits: usize,
control_state: Option<usize>,
) -> PyResult<CircuitData> {
if num_ctrl_qubits < 1 {
return Err(QiskitError::new_err("Need at least 1 control qubit."));
}

let packed_controlled_gate = controlled_gate.operation;
let num_qubits = if num_ctrl_qubits > 1 {
2 * num_ctrl_qubits - 1 + num_target_qubits
} else {
1 + num_target_qubits // we can have 1 control and multiple targets
};

let control_state = control_state.unwrap_or(usize::pow(2, num_ctrl_qubits as u32) - 1);

// First, we handle bitflips in case of open controls.
let flip_control_state = (0..num_ctrl_qubits)
.filter(|index| control_state & (1 << index) == 0)
.map(|index| {
Ok((
PackedOperation::from_standard(StandardGate::XGate),
smallvec![] as SmallVec<[Param; 3]>,
vec![Qubit(index as u32)],
vec![] as Vec<Clbit>,
))
});

// Then, we create the operations that apply the controlled base gate.
// That's because we only add the V-chain of CCX gates, if the number of controls
// is larger than 1, otherwise we're already done here.
let master_control = if num_ctrl_qubits > 1 {
num_qubits - 1
} else {
0
};
let targets = (0..num_target_qubits).map(|i| {
Ok((
packed_controlled_gate.clone(),
smallvec![] as SmallVec<[Param; 3]>,
vec![
Qubit(master_control as u32),
Qubit((num_ctrl_qubits + i) as u32),
],
vec![] as Vec<Clbit>,
))
});

// Finally we add the V-chain (or return in case of 1 control).
if num_ctrl_qubits == 1 {
CircuitData::from_packed_operations(
py,
num_qubits as u32,
0,
flip_control_state
.clone()
.chain(targets)
.chain(flip_control_state),
Param::Float(0.0),
)
} else {
// If the number of controls is larger than 1, and we need to apply the V-chain,
// create it here and sandwich the targets in-between.
let controls: Vec<usize> = (0..num_ctrl_qubits).collect();
let auxiliaries: Vec<usize> = (num_ctrl_qubits + num_target_qubits..num_qubits).collect();
let down_chain = ccx_chain(&controls, &auxiliaries);
let up_chain = ccx_chain(&controls, &auxiliaries).rev();

CircuitData::from_packed_operations(
py,
num_qubits as u32,
0,
flip_control_state
.clone()
.chain(down_chain)
.chain(targets)
.chain(up_chain)
.chain(flip_control_state),
Param::Float(0.0),
)
}
}
20 changes: 20 additions & 0 deletions crates/accelerate/src/synthesis/multi_controlled/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// This code is part of Qiskit.
//
// (C) Copyright IBM 2024
//
// This code is licensed under the Apache License, Version 2.0. You may
// obtain a copy of this license in the LICENSE.txt file in the root directory
// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
//
// 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.

use pyo3::prelude::*;

mod mcmt;

pub fn multi_controlled(m: &Bound<PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(mcmt::mcmt_v_chain, m)?)?;
Ok(())
}
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ sk = "qiskit.transpiler.passes.synthesis.solovay_kitaev_synthesis:SolovayKitaevS
"mcx.gray_code" = "qiskit.transpiler.passes.synthesis.hls_plugins:MCXSynthesisGrayCode"
"mcx.noaux_v24" = "qiskit.transpiler.passes.synthesis.hls_plugins:MCXSynthesisNoAuxV24"
"mcx.default" = "qiskit.transpiler.passes.synthesis.hls_plugins:MCXSynthesisDefault"
"mcmt.default" = "qiskit.transpiler.passes.synthesis.hls_plugins:MCMTSynthesisDefault"
"mcmt.noaux" = "qiskit.transpiler.passes.synthesis.hls_plugins:MCMTSynthesisNoAux"
"mcmt.vchain" = "qiskit.transpiler.passes.synthesis.hls_plugins:MCMTSynthesisVChain"
"permutation.default" = "qiskit.transpiler.passes.synthesis.hls_plugins:BasicSynthesisPermutation"
"permutation.kms" = "qiskit.transpiler.passes.synthesis.hls_plugins:KMSSynthesisPermutation"
"permutation.basic" = "qiskit.transpiler.passes.synthesis.hls_plugins:BasicSynthesisPermutation"
Expand Down
3 changes: 3 additions & 0 deletions qiskit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@
sys.modules["qiskit._accelerate.commutation_analysis"] = _accelerate.commutation_analysis
sys.modules["qiskit._accelerate.commutation_cancellation"] = _accelerate.commutation_cancellation
sys.modules["qiskit._accelerate.synthesis.linear_phase"] = _accelerate.synthesis.linear_phase
sys.modules["qiskit._accelerate.synthesis.multi_controlled"] = (
_accelerate.synthesis.multi_controlled
)
sys.modules["qiskit._accelerate.split_2q_unitaries"] = _accelerate.split_2q_unitaries
sys.modules["qiskit._accelerate.gate_direction"] = _accelerate.gate_direction
sys.modules["qiskit._accelerate.inverse_cancellation"] = _accelerate.inverse_cancellation
Expand Down
2 changes: 2 additions & 0 deletions qiskit/circuit/library/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@
GRX
GRY
GRZ
MCMTGate
MCPhaseGate
MCXGate
MCXGrayCode
Expand Down Expand Up @@ -501,6 +502,7 @@
Permutation,
PermutationGate,
GMS,
MCMTGate,
MSGate,
GR,
GRX,
Expand Down
2 changes: 1 addition & 1 deletion qiskit/circuit/library/generalized_gates/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from .diagonal import Diagonal, DiagonalGate
from .permutation import Permutation, PermutationGate
from .mcmt import MCMT, MCMTVChain
from .mcmt import MCMT, MCMTVChain, MCMTGate
from .gms import GMS, MSGate
from .gr import GR, GRX, GRY, GRZ
from .pauli import PauliGate
Expand Down
Loading

0 comments on commit 8b6e47e

Please sign in to comment.