Skip to content

Commit

Permalink
Merge branch 'Qiskit:main' into mlapson/deprecate_cu1
Browse files Browse the repository at this point in the history
  • Loading branch information
melechlapson authored Sep 9, 2024
2 parents 1627c82 + 2ef371a commit 01a5126
Show file tree
Hide file tree
Showing 30 changed files with 1,209 additions and 455 deletions.
8 changes: 4 additions & 4 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 @@ -14,7 +14,7 @@ license = "Apache-2.0"
#
# Each crate can add on specific features freely as it inherits.
[workspace.dependencies]
bytemuck = "1.17"
bytemuck = "1.18"
indexmap.version = "2.5.0"
hashbrown.version = "0.14.5"
num-bigint = "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 @@ -20,7 +20,7 @@ num-traits = "0.2"
num-complex.workspace = true
rustworkx-core.workspace = true
num-bigint.workspace = true
faer = "0.19.2"
faer = "0.19.3"
itertools.workspace = true
qiskit-circuit.workspace = true
thiserror.workspace = true
Expand Down
98 changes: 98 additions & 0 deletions crates/accelerate/src/check_map.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// 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 hashbrown::HashSet;
use pyo3::intern;
use pyo3::prelude::*;
use pyo3::wrap_pyfunction;

use qiskit_circuit::circuit_data::CircuitData;
use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType};
use qiskit_circuit::imports::CIRCUIT_TO_DAG;
use qiskit_circuit::operations::{Operation, OperationRef};
use qiskit_circuit::Qubit;

fn recurse<'py>(
py: Python<'py>,
dag: &'py DAGCircuit,
edge_set: &'py HashSet<[u32; 2]>,
wire_map: Option<&'py [Qubit]>,
) -> PyResult<Option<(String, [u32; 2])>> {
let check_qubits = |qubits: &[Qubit]| -> bool {
match wire_map {
Some(wire_map) => {
let mapped_bits = [
wire_map[qubits[0].0 as usize],
wire_map[qubits[1].0 as usize],
];
edge_set.contains(&[mapped_bits[0].into(), mapped_bits[1].into()])
}
None => edge_set.contains(&[qubits[0].into(), qubits[1].into()]),
}
};
for node in dag.op_nodes(false) {
if let NodeType::Operation(inst) = &dag.dag[node] {
let qubits = dag.get_qargs(inst.qubits);
if inst.op.control_flow() {
if let OperationRef::Instruction(py_inst) = inst.op.view() {
let raw_blocks = py_inst.instruction.getattr(py, "blocks")?;
let circuit_to_dag = CIRCUIT_TO_DAG.get_bound(py);
for raw_block in raw_blocks.bind(py).iter().unwrap() {
let block_obj = raw_block?;
let block = block_obj
.getattr(intern!(py, "_data"))?
.downcast::<CircuitData>()?
.borrow();
let new_dag: DAGCircuit =
circuit_to_dag.call1((block_obj.clone(),))?.extract()?;
let wire_map = (0..block.num_qubits())
.map(|inner| {
let outer = qubits[inner];
match wire_map {
Some(wire_map) => wire_map[outer.0 as usize],
None => outer,
}
})
.collect::<Vec<_>>();
let res = recurse(py, &new_dag, edge_set, Some(&wire_map))?;
if res.is_some() {
return Ok(res);
}
}
}
} else if qubits.len() == 2
&& (dag.calibrations_empty() || !dag.has_calibration_for_index(py, node)?)
&& !check_qubits(qubits)
{
return Ok(Some((
inst.op.name().to_string(),
[qubits[0].0, qubits[1].0],
)));
}
}
}
Ok(None)
}

#[pyfunction]
pub fn check_map(
py: Python,
dag: &DAGCircuit,
edge_set: HashSet<[u32; 2]>,
) -> PyResult<Option<(String, [u32; 2])>> {
recurse(py, dag, &edge_set, None)
}

pub fn check_map_mod(m: &Bound<PyModule>) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(check_map))?;
Ok(())
}
63 changes: 63 additions & 0 deletions crates/accelerate/src/filter_op_nodes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// 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 pyo3::wrap_pyfunction;

use qiskit_circuit::dag_circuit::DAGCircuit;
use qiskit_circuit::packed_instruction::PackedInstruction;
use rustworkx_core::petgraph::stable_graph::NodeIndex;

#[pyfunction]
#[pyo3(name = "filter_op_nodes")]
pub fn py_filter_op_nodes(
py: Python,
dag: &mut DAGCircuit,
predicate: &Bound<PyAny>,
) -> PyResult<()> {
let callable = |node: NodeIndex| -> PyResult<bool> {
let dag_op_node = dag.get_node(py, node)?;
predicate.call1((dag_op_node,))?.extract()
};
let mut remove_nodes: Vec<NodeIndex> = Vec::new();
for node in dag.op_nodes(true) {
if !callable(node)? {
remove_nodes.push(node);
}
}
for node in remove_nodes {
dag.remove_op_node(node);
}
Ok(())
}

/// Remove any nodes that have the provided label set
///
/// Args:
/// dag (DAGCircuit): The dag circuit to filter the ops from
/// label (str): The label to filter nodes on
#[pyfunction]
pub fn filter_labeled_op(dag: &mut DAGCircuit, label: String) {
let predicate = |node: &PackedInstruction| -> bool {
match node.label() {
Some(inst_label) => inst_label != label,
None => false,
}
};
dag.filter_op_nodes(predicate);
}

pub fn filter_op_nodes_mod(m: &Bound<PyModule>) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(py_filter_op_nodes))?;
m.add_wrapped(wrap_pyfunction!(filter_labeled_op))?;
Ok(())
}
150 changes: 150 additions & 0 deletions crates/accelerate/src/gate_direction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// 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 crate::nlayout::PhysicalQubit;
use crate::target_transpiler::Target;
use hashbrown::HashSet;
use pyo3::prelude::*;
use qiskit_circuit::imports;
use qiskit_circuit::operations::OperationRef;
use qiskit_circuit::{
dag_circuit::{DAGCircuit, NodeType},
operations::Operation,
packed_instruction::PackedInstruction,
Qubit,
};
use smallvec::smallvec;

/// Check if the two-qubit gates follow the right direction with respect to the coupling map.
///
/// Args:
/// dag: the DAGCircuit to analyze
///
/// coupling_edges: set of edge pairs representing a directed coupling map, against which gate directionality is checked
///
/// Returns:
/// true iff all two-qubit gates comply with the coupling constraints
#[pyfunction]
#[pyo3(name = "check_gate_direction_coupling")]
fn py_check_with_coupling_map(
py: Python,
dag: &DAGCircuit,
coupling_edges: HashSet<[Qubit; 2]>,
) -> PyResult<bool> {
let coupling_map_check =
|_: &PackedInstruction, op_args: &[Qubit]| -> bool { coupling_edges.contains(op_args) };

check_gate_direction(py, dag, &coupling_map_check, None)
}

/// Check if the two-qubit gates follow the right direction with respect to instructions supported in the given target.
///
/// Args:
/// dag: the DAGCircuit to analyze
///
/// target: the Target against which gate directionality compliance is checked
///
/// Returns:
/// true iff all two-qubit gates comply with the target's coupling constraints
#[pyfunction]
#[pyo3(name = "check_gate_direction_target")]
fn py_check_with_target(py: Python, dag: &DAGCircuit, target: &Target) -> PyResult<bool> {
let target_check = |inst: &PackedInstruction, op_args: &[Qubit]| -> bool {
let qargs = smallvec![
PhysicalQubit::new(op_args[0].0),
PhysicalQubit::new(op_args[1].0)
];

target.instruction_supported(inst.op.name(), Some(&qargs))
};

check_gate_direction(py, dag, &target_check, None)
}

// The main routine for checking gate directionality.
//
// gate_complies: a function returning true iff the two-qubit gate direction complies with directionality constraints
//
// qubit_mapping: used for mapping the index of a given qubit within an instruction qargs vector to the corresponding qubit index of the
// original DAGCircuit the pass was called with. This mapping is required since control flow blocks are represented by nested DAGCircuit
// objects whose instruction qubit indices are relative to the parent DAGCircuit they reside in, thus when we recurse into nested DAGs, we need
// to carry the mapping context relative to the original DAG.
// When qubit_mapping is None, the identity mapping is assumed
fn check_gate_direction<T>(
py: Python,
dag: &DAGCircuit,
gate_complies: &T,
qubit_mapping: Option<&[Qubit]>,
) -> PyResult<bool>
where
T: Fn(&PackedInstruction, &[Qubit]) -> bool,
{
for node in dag.op_nodes(false) {
let NodeType::Operation(packed_inst) = &dag.dag[node] else {
panic!("PackedInstruction is expected");
};

let inst_qargs = dag.get_qargs(packed_inst.qubits);

if let OperationRef::Instruction(py_inst) = packed_inst.op.view() {
if py_inst.control_flow() {
let circuit_to_dag = imports::CIRCUIT_TO_DAG.get_bound(py); // TODO: Take out of the recursion
let py_inst = py_inst.instruction.bind(py);

for block in py_inst.getattr("blocks")?.iter()? {
let inner_dag: DAGCircuit = circuit_to_dag.call1((block?,))?.extract()?;

let block_ok = if let Some(mapping) = qubit_mapping {
let mapping = inst_qargs // Create a temp mapping for the recursive call
.iter()
.map(|q| mapping[q.0 as usize])
.collect::<Vec<Qubit>>();

check_gate_direction(py, &inner_dag, gate_complies, Some(&mapping))?
} else {
check_gate_direction(py, &inner_dag, gate_complies, Some(inst_qargs))?
};

if !block_ok {
return Ok(false);
}
}
continue;
}
}

if inst_qargs.len() == 2
&& !match qubit_mapping {
// Check gate direction based either on a given custom mapping or the identity mapping
Some(mapping) => gate_complies(
packed_inst,
&[
mapping[inst_qargs[0].0 as usize],
mapping[inst_qargs[1].0 as usize],
],
),
None => gate_complies(packed_inst, inst_qargs),
}
{
return Ok(false);
}
}

Ok(true)
}

#[pymodule]
pub fn gate_direction(m: &Bound<PyModule>) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(py_check_with_coupling_map))?;
m.add_wrapped(wrap_pyfunction!(py_check_with_target))?;
Ok(())
}
Loading

0 comments on commit 01a5126

Please sign in to comment.