Skip to content

Commit

Permalink
Fully port CheckMap to Rust (#13030)
Browse files Browse the repository at this point in the history
* Fully port CheckMap to Rust

This commit migrates the entirety of the CheckMap analysis pass to Rust.
The pass operates solely in the rust domain and returns an
`Option<(String, [u32; 2])>` to Python which is used to set the two
property set fields appropriately. All the analysis of the dag is done
in Rust. There is still Python interaction required though because
control flow operations are only defined in Python. However the
interaction is minimal and only to get the circuits for control flow
blocks and converting them into DAGs (at least until #13001 is complete).

This commit is based on top of #12959 and will need to be rebased after
that merges.

Closes #12251
Part of #12208

* Use a Vec<Qubit> for wire_map instead of a HashMap

This commit switches to using a Vec<Qubit> for the internal wire_map
used to map control flow qubits. A HashMap was originally used because
in Python a dictionary is used. However, in the rust domain the inner
qubits are contiguous integers starting from 0 so a Vec can be used for
better performance in the case we have control flow.

* Update crates/accelerate/src/check_map.rs

Co-authored-by: Raynel Sanchez <[email protected]>

---------

Co-authored-by: Raynel Sanchez <[email protected]>
  • Loading branch information
mtreinish and raynelfss authored Sep 6, 2024
1 parent 3d4bab2 commit 41dc418
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 29 deletions.
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(())
}
1 change: 1 addition & 0 deletions crates/accelerate/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use std::env;

use pyo3::import_exception;

pub mod check_map;
pub mod circuit_library;
pub mod commutation_analysis;
pub mod commutation_checker;
Expand Down
13 changes: 7 additions & 6 deletions crates/pyext/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@
use pyo3::prelude::*;

use qiskit_accelerate::{
circuit_library::circuit_library, commutation_analysis::commutation_analysis,
commutation_checker::commutation_checker, convert_2q_block_matrix::convert_2q_block_matrix,
dense_layout::dense_layout, error_map::error_map,
euler_one_qubit_decomposer::euler_one_qubit_decomposer, filter_op_nodes::filter_op_nodes_mod,
isometry::isometry, nlayout::nlayout, optimize_1q_gates::optimize_1q_gates,
pauli_exp_val::pauli_expval,
check_map::check_map_mod, circuit_library::circuit_library,
commutation_analysis::commutation_analysis, commutation_checker::commutation_checker,
convert_2q_block_matrix::convert_2q_block_matrix, dense_layout::dense_layout,
error_map::error_map, euler_one_qubit_decomposer::euler_one_qubit_decomposer,
filter_op_nodes::filter_op_nodes_mod, isometry::isometry, nlayout::nlayout,
optimize_1q_gates::optimize_1q_gates, pauli_exp_val::pauli_expval,
remove_diagonal_gates_before_measure::remove_diagonal_gates_before_measure, results::results,
sabre::sabre, sampled_exp_val::sampled_exp_val, sparse_pauli_op::sparse_pauli_op,
star_prerouting::star_prerouting, stochastic_swap::stochastic_swap, synthesis::synthesis,
Expand All @@ -43,6 +43,7 @@ fn _accelerate(m: &Bound<PyModule>) -> PyResult<()> {
add_submodule(m, qiskit_qasm2::qasm2, "qasm2")?;
add_submodule(m, qiskit_qasm3::qasm3, "qasm3")?;
add_submodule(m, circuit_library, "circuit_library")?;
add_submodule(m, check_map_mod, "check_map")?;
add_submodule(m, convert_2q_block_matrix, "convert_2q_block_matrix")?;
add_submodule(m, dense_layout, "dense_layout")?;
add_submodule(m, error_map, "error_map")?;
Expand Down
1 change: 1 addition & 0 deletions qiskit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
sys.modules["qiskit._accelerate.commutation_checker"] = _accelerate.commutation_checker
sys.modules["qiskit._accelerate.commutation_analysis"] = _accelerate.commutation_analysis
sys.modules["qiskit._accelerate.synthesis.linear_phase"] = _accelerate.synthesis.linear_phase
sys.modules["qiskit._accelerate.check_map"] = _accelerate.check_map
sys.modules["qiskit._accelerate.filter_op_nodes"] = _accelerate.filter_op_nodes

from qiskit.exceptions import QiskitError, MissingOptionalLibraryError
Expand Down
33 changes: 10 additions & 23 deletions qiskit/transpiler/passes/utils/check_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@

from qiskit.transpiler.basepasses import AnalysisPass
from qiskit.transpiler.target import Target
from qiskit.converters import circuit_to_dag

from qiskit._accelerate import check_map


class CheckMap(AnalysisPass):
Expand Down Expand Up @@ -67,25 +68,11 @@ def run(self, dag):
if not self.qargs:
self.property_set[self.property_set_field] = True
return
wire_map = {bit: index for index, bit in enumerate(dag.qubits)}
self.property_set[self.property_set_field] = self._recurse(dag, wire_map)

def _recurse(self, dag, wire_map) -> bool:
for node in dag.op_nodes(include_directives=False):
if node.is_control_flow():
for block in node.op.blocks:
inner_wire_map = {
inner: wire_map[outer] for inner, outer in zip(block.qubits, node.qargs)
}
if not self._recurse(circuit_to_dag(block), inner_wire_map):
return False
elif (
len(node.qargs) == 2
and not dag.has_calibration_for(node)
and (wire_map[node.qargs[0]], wire_map[node.qargs[1]]) not in self.qargs
):
self.property_set["check_map_msg"] = (
f"{node.name}({wire_map[node.qargs[0]]}, {wire_map[node.qargs[1]]}) failed"
)
return False
return True
res = check_map.check_map(dag, self.qargs)
if res is None:
self.property_set[self.property_set_field] = True
return
self.property_set[self.property_set_field] = False
self.property_set["check_map_msg"] = (
f"{res[0]}({dag.qubits[res[1][0]]}, {dag.qubits[res[1][1]]}) failed"
)

0 comments on commit 41dc418

Please sign in to comment.