Skip to content

Commit

Permalink
Merge branch 'main' of github.com:Qiskit/qiskit-terra into EA/bye-bye…
Browse files Browse the repository at this point in the history
…-parsed-literal
  • Loading branch information
Eric-Arellano committed Oct 7, 2024
2 parents 43abd3a + e41828f commit b759191
Show file tree
Hide file tree
Showing 35 changed files with 930 additions and 188 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ jobs:
lcov --add-tracefile python.info --add-tracefile rust.info --output-file coveralls.info
- name: Coveralls
uses: coverallsapp/github-action@master
uses: coverallsapp/github-action@v2.3.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: coveralls.info
19 changes: 11 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ license = "Apache-2.0"
# Each crate can add on specific features freely as it inherits.
[workspace.dependencies]
bytemuck = "1.18"
indexmap.version = "2.5.0"
indexmap.version = "2.6.0"
hashbrown.version = "0.14.5"
num-bigint = "0.4"
num-complex = "0.4"
Expand Down
2 changes: 1 addition & 1 deletion crates/accelerate/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ itertools.workspace = true
qiskit-circuit.workspace = true
thiserror.workspace = true
ndarray_einsum_beta = "0.7"
once_cell = "1.20.1"
once_cell = "1.20.2"

[dependencies.smallvec]
workspace = true
Expand Down
7 changes: 6 additions & 1 deletion crates/accelerate/src/circuit_library/quantum_volume.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
// copyright notice, and modified files need to carry a notice indicating
// that they have been altered from the originals.

use pyo3::intern;
use pyo3::prelude::*;
use pyo3::types::PyDict;

use crate::getenv_use_multiple_threads;
use faer_ext::{IntoFaerComplex, IntoNdarrayComplex};
Expand Down Expand Up @@ -110,6 +112,8 @@ pub fn quantum_volume(
let num_unitaries = width * depth;
let mut permutation: Vec<Qubit> = (0..num_qubits).map(Qubit).collect();

let kwargs = PyDict::new_bound(py);
kwargs.set_item(intern!(py, "num_qubits"), 2)?;
let mut build_instruction = |(unitary_index, unitary_array): (usize, Array2<Complex64>),
rng: &mut Pcg64Mcg|
-> PyResult<Instruction> {
Expand All @@ -118,9 +122,10 @@ pub fn quantum_volume(
permutation.shuffle(rng);
}
let unitary = unitary_array.into_pyarray_bound(py);

let unitary_gate = UNITARY_GATE
.get_bound(py)
.call1((unitary.clone(), py.None(), false))?;
.call((unitary.clone(), py.None(), false), Some(&kwargs))?;
let instruction = PyInstruction {
qubits: 2,
clbits: 0,
Expand Down
9 changes: 7 additions & 2 deletions crates/accelerate/src/split_2q_unitaries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
// copyright notice, and modified files need to carry a notice indicating
// that they have been altered from the originals.

use pyo3::intern;
use pyo3::prelude::*;
use pyo3::types::PyDict;
use rustworkx_core::petgraph::stable_graph::NodeIndex;

use qiskit_circuit::circuit_instruction::OperationFromPython;
Expand All @@ -27,6 +29,7 @@ pub fn split_2q_unitaries(
requested_fidelity: f64,
) -> PyResult<()> {
let nodes: Vec<NodeIndex> = dag.op_nodes(false).collect();

for node in nodes {
if let NodeType::Operation(inst) = &dag.dag()[node] {
let qubits = dag.get_qargs(inst.qubits).to_vec();
Expand All @@ -45,12 +48,14 @@ pub fn split_2q_unitaries(
if matches!(decomp.specialization, Specialization::IdEquiv) {
let k1r_arr = decomp.K1r(py);
let k1l_arr = decomp.K1l(py);
let kwargs = PyDict::new_bound(py);
kwargs.set_item(intern!(py, "num_qubits"), 1)?;
let k1r_gate = UNITARY_GATE
.get_bound(py)
.call1((k1r_arr, py.None(), false))?;
.call((k1r_arr, py.None(), false), Some(&kwargs))?;
let k1l_gate = UNITARY_GATE
.get_bound(py)
.call1((k1l_arr, py.None(), false))?;
.call((k1l_arr, py.None(), false), Some(&kwargs))?;
let insert_fn = |edge: &Wire| -> PyResult<OperationFromPython> {
if let Wire::Qubit(qubit) = edge {
if *qubit == qubits[0] {
Expand Down
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(())
}
12 changes: 10 additions & 2 deletions crates/circuit/src/circuit_instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -637,15 +637,23 @@ impl<'py> FromPyObject<'py> for OperationFromPython {
let extract_extra = || -> PyResult<_> {
let unit = {
// We accept Python-space `None` or `"dt"` as both meaning the default `"dt"`.
let raw_unit = ob.getattr(intern!(py, "unit"))?;
let raw_unit = ob.getattr(intern!(py, "_unit"))?;
(!(raw_unit.is_none()
|| raw_unit.eq(ExtraInstructionAttributes::default_unit(py))?))
.then(|| raw_unit.extract::<String>())
.transpose()?
};
// Delay uses the `duration` attr as an alias for param[0] which isn't deprecated
// while all other instructions have deprecated `duration` so we want to access
// the inner _duration to avoid the deprecation warning's run time overhead.
let duration = if ob.getattr(intern!(py, "name"))?.extract::<String>()? != "delay" {
ob.getattr(intern!(py, "_duration"))?.extract()?
} else {
ob.getattr(intern!(py, "duration"))?.extract()?
};
Ok(ExtraInstructionAttributes::new(
ob.getattr(intern!(py, "label"))?.extract()?,
ob.getattr(intern!(py, "duration"))?.extract()?,
duration,
unit,
ob.getattr(intern!(py, "condition"))?.extract()?,
))
Expand Down
Loading

0 comments on commit b759191

Please sign in to comment.