Skip to content

Commit

Permalink
Fully port GatesInBasis to Rust. (#13034)
Browse files Browse the repository at this point in the history
* Fully port gates_in_basis to Rust.

* Revert visibility to no longer public.

* Add docstring for unwrap_operation.

* Remove unused py token.
  • Loading branch information
kevinhartman authored Sep 26, 2024
1 parent a0c3026 commit 8ccbc8d
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 31 deletions.
117 changes: 117 additions & 0 deletions crates/accelerate/src/gates_in_basis.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// 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::prelude::*;
use qiskit_circuit::circuit_data::CircuitData;
use smallvec::SmallVec;

use crate::nlayout::PhysicalQubit;
use crate::target_transpiler::Target;
use qiskit_circuit::dag_circuit::DAGCircuit;
use qiskit_circuit::operations::Operation;
use qiskit_circuit::packed_instruction::PackedInstruction;
use qiskit_circuit::Qubit;

#[pyfunction]
fn any_gate_missing_from_target(dag: &DAGCircuit, target: &Target) -> PyResult<bool> {
#[inline]
fn is_universal(gate: &PackedInstruction) -> bool {
matches!(gate.op.name(), "barrier" | "store")
}

fn visit_gate(
target: &Target,
gate: &PackedInstruction,
qargs: &[Qubit],
wire_map: &HashMap<Qubit, PhysicalQubit>,
) -> PyResult<bool> {
let qargs_mapped = SmallVec::from_iter(qargs.iter().map(|q| wire_map[q]));
if !target.instruction_supported(gate.op.name(), Some(&qargs_mapped)) {
return Ok(true);
}

if gate.op.control_flow() {
for block in gate.op.blocks() {
let block_qubits = (0..block.num_qubits()).map(|i| Qubit(i.try_into().unwrap()));
let inner_wire_map = qargs
.iter()
.zip(block_qubits)
.map(|(outer, inner)| (inner, wire_map[outer]))
.collect();
if visit_circuit(target, &block, &inner_wire_map)? {
return Ok(true);
}
}
}
Ok(false)
}

fn visit_circuit(
target: &Target,
circuit: &CircuitData,
wire_map: &HashMap<Qubit, PhysicalQubit>,
) -> PyResult<bool> {
for gate in circuit.iter() {
if is_universal(gate) {
continue;
}
let qargs = circuit.qargs_interner().get(gate.qubits);
if visit_gate(target, gate, qargs, wire_map)? {
return Ok(true);
}
}
Ok(false)
}

// In the outer DAG, virtual and physical bits are the same thing.
let wire_map: HashMap<Qubit, PhysicalQubit> =
HashMap::from_iter((0..dag.num_qubits()).map(|i| {
(
Qubit(i.try_into().unwrap()),
PhysicalQubit::new(i.try_into().unwrap()),
)
}));

// Process the DAG.
for gate in dag.op_nodes(true) {
let gate = dag.dag()[gate].unwrap_operation();
if is_universal(gate) {
continue;
}
let qargs = dag.qargs_interner().get(gate.qubits);
if visit_gate(target, gate, qargs, &wire_map)? {
return Ok(true);
}
}
Ok(false)
}

#[pyfunction]
fn any_gate_missing_from_basis(
py: Python,
dag: &DAGCircuit,
basis: HashSet<String>,
) -> PyResult<bool> {
for (gate, _) in dag.count_ops(py, true)? {
if !basis.contains(gate.as_str()) {
return Ok(true);
}
}
Ok(false)
}

pub fn gates_in_basis(m: &Bound<PyModule>) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(any_gate_missing_from_target))?;
m.add_wrapped(wrap_pyfunction!(any_gate_missing_from_basis))?;
Ok(())
}
1 change: 1 addition & 0 deletions crates/accelerate/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub mod error_map;
pub mod euler_one_qubit_decomposer;
pub mod filter_op_nodes;
pub mod gate_direction;
pub mod gates_in_basis;
pub mod inverse_cancellation;
pub mod isometry;
pub mod nlayout;
Expand Down
13 changes: 13 additions & 0 deletions crates/circuit/src/dag_circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,19 @@ pub enum NodeType {
Operation(PackedInstruction),
}

impl NodeType {
/// Unwraps this node as an operation and returns a reference to
/// the contained [PackedInstruction].
///
/// Panics if this is not an operation node.
pub fn unwrap_operation(&self) -> &PackedInstruction {
match self {
NodeType::Operation(instr) => instr,
_ => panic!("Node is not an operation!"),
}
}
}

#[derive(Clone, Debug)]
pub enum Wire {
Qubit(Qubit),
Expand Down
2 changes: 1 addition & 1 deletion crates/circuit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ mod dot_utils;
mod error;
pub mod gate_matrix;
pub mod imports;
mod interner;
pub mod interner;
pub mod operations;
pub mod packed_instruction;
pub mod parameter_table;
Expand Down
1 change: 1 addition & 0 deletions crates/pyext/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ fn _accelerate(m: &Bound<PyModule>) -> PyResult<()> {
add_submodule(m, ::qiskit_accelerate::euler_one_qubit_decomposer::euler_one_qubit_decomposer, "euler_one_qubit_decomposer")?;
add_submodule(m, ::qiskit_accelerate::filter_op_nodes::filter_op_nodes_mod, "filter_op_nodes")?;
add_submodule(m, ::qiskit_accelerate::gate_direction::gate_direction, "gate_direction")?;
add_submodule(m, ::qiskit_accelerate::gates_in_basis::gates_in_basis, "gates_in_basis")?;
add_submodule(m, ::qiskit_accelerate::inverse_cancellation::inverse_cancellation_mod, "inverse_cancellation")?;
add_submodule(m, ::qiskit_accelerate::isometry::isometry, "isometry")?;
add_submodule(m, ::qiskit_accelerate::nlayout::nlayout, "nlayout")?;
Expand Down
1 change: 1 addition & 0 deletions qiskit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
sys.modules["qiskit._accelerate.dense_layout"] = _accelerate.dense_layout
sys.modules["qiskit._accelerate.equivalence"] = _accelerate.equivalence
sys.modules["qiskit._accelerate.error_map"] = _accelerate.error_map
sys.modules["qiskit._accelerate.gates_in_basis"] = _accelerate.gates_in_basis
sys.modules["qiskit._accelerate.isometry"] = _accelerate.isometry
sys.modules["qiskit._accelerate.uc_gate"] = _accelerate.uc_gate
sys.modules["qiskit._accelerate.euler_one_qubit_decomposer"] = (
Expand Down
37 changes: 7 additions & 30 deletions qiskit/transpiler/passes/utils/gates_basis.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@

"""Check if all gates in the DAGCircuit are in the specified basis gates."""

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

from qiskit._accelerate.gates_in_basis import (
any_gate_missing_from_basis,
any_gate_missing_from_target,
)


class GatesInBasis(AnalysisPass):
"""Check if all gates in a DAG are in a given set of gates"""
Expand All @@ -40,35 +44,8 @@ def run(self, dag):
if self._basis_gates is None and self._target is None:
self.property_set["all_gates_in_basis"] = True
return
gates_out_of_basis = False
if self._target is not None:

def _visit_target(dag, wire_map):
for gate in dag.op_nodes():
# Barrier and store are assumed universal and supported by all backends
if gate.name in ("barrier", "store"):
continue
if not self._target.instruction_supported(
gate.name, tuple(wire_map[bit] for bit in gate.qargs)
):
return True
# Control-flow ops still need to be supported, so don't skip them in the
# previous checks.
if gate.is_control_flow():
for block in gate.op.blocks:
inner_wire_map = {
inner: wire_map[outer]
for outer, inner in zip(gate.qargs, block.qubits)
}
if _visit_target(circuit_to_dag(block), inner_wire_map):
return True
return False

qubit_map = {qubit: index for index, qubit in enumerate(dag.qubits)}
gates_out_of_basis = _visit_target(dag, qubit_map)
gates_out_of_basis = any_gate_missing_from_target(dag, self._target)
else:
for gate in dag.count_ops(recurse=True):
if gate not in self._basis_gates:
gates_out_of_basis = True
break
gates_out_of_basis = any_gate_missing_from_basis(dag, self._basis_gates)
self.property_set["all_gates_in_basis"] = not gates_out_of_basis

0 comments on commit 8ccbc8d

Please sign in to comment.