-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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
1 parent
660541a
commit 8b6e47e
Showing
16 changed files
with
709 additions
and
123 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
184 changes: 184 additions & 0 deletions
184
crates/accelerate/src/synthesis/multi_controlled/mcmt.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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), | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.