Skip to content

Commit

Permalink
Fully port CheckMap to Rust
Browse files Browse the repository at this point in the history
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 Qiskit#13001 is complete).

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

Closes Qiskit#12251
Part of Qiskit#12208
  • Loading branch information
mtreinish committed Aug 24, 2024
1 parent 2c0aad5 commit 41fe453
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 31 deletions.
93 changes: 93 additions & 0 deletions crates/accelerate/src/check_map.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// 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::{HashMap, 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 HashMap<Qubit, 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]], wire_map[&qubits[1]]];
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_qubits(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"))?
.extract::<CircuitData>()?;
let new_dag: DAGCircuit =
circuit_to_dag.call1((block_obj.clone(),))?.extract()?;
let wire_map = (0..block.num_qubits())
.map(|x| Qubit(x as u32))
.zip(qubits)
.map(|(inner, outer)| match wire_map {
Some(wire_map) => (inner, wire_map[outer]),
None => (inner, *outer),
})
.collect();
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 convert_2q_block_matrix;
pub mod dense_layout;
pub mod edge_collections;
Expand Down
17 changes: 9 additions & 8 deletions crates/pyext/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@
use pyo3::prelude::*;

use qiskit_accelerate::{
convert_2q_block_matrix::convert_2q_block_matrix, dense_layout::dense_layout,
error_map::error_map, euler_one_qubit_decomposer::euler_one_qubit_decomposer,
isometry::isometry, nlayout::nlayout, optimize_1q_gates::optimize_1q_gates,
pauli_exp_val::pauli_expval, 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, target_transpiler::target,
two_qubit_decompose::two_qubit_decompose, uc_gate::uc_gate, utils::utils,
vf2_layout::vf2_layout,
check_map::check_map_mod, convert_2q_block_matrix::convert_2q_block_matrix,
dense_layout::dense_layout, error_map::error_map,
euler_one_qubit_decomposer::euler_one_qubit_decomposer, isometry::isometry, nlayout::nlayout,
optimize_1q_gates::optimize_1q_gates, pauli_exp_val::pauli_expval, 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,
target_transpiler::target, two_qubit_decompose::two_qubit_decompose, uc_gate::uc_gate,
utils::utils, vf2_layout::vf2_layout,
};

#[inline(always)]
Expand All @@ -39,6 +39,7 @@ fn _accelerate(m: &Bound<PyModule>) -> PyResult<()> {
add_submodule(m, qiskit_circuit::circuit, "circuit")?;
add_submodule(m, qiskit_qasm2::qasm2, "qasm2")?;
add_submodule(m, qiskit_qasm3::qasm3, "qasm3")?;
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 @@ -86,6 +86,7 @@
sys.modules["qiskit._accelerate.synthesis.linear"] = _accelerate.synthesis.linear
sys.modules["qiskit._accelerate.synthesis.clifford"] = _accelerate.synthesis.clifford
sys.modules["qiskit._accelerate.synthesis.linear_phase"] = _accelerate.synthesis.linear_phase
sys.modules["qiskit._accelerate.check_map"] = _accelerate.check_map

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 41fe453

Please sign in to comment.