Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fully port GatesInBasis to Rust. #13034

Merged
merged 4 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
kevinhartman marked this conversation as resolved.
Show resolved Hide resolved
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()),
)
}));

Comment on lines +76 to +84
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this mapping here mainly to reduce tha cost of re-building Qubits or PhysicalQubit instances from each other? Not that it needs any correction I just wanted to understand if this was the case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The purpose is actually to map qubits within control flow blocks from the indexing scheme used within the current block back to the indexing of the root circuit.

For example, if the root circuit has 5 qubits and has an if-else operation that uses qubits 3 and 4, then the inner circuits corresponding to the if and else blocks of that operation will have 2 qubits, indexed 0 and 1. To get from 0 and 1 each inner block's indexing scheme to 3 and 4 in the root circuit's indexing scheme, we use this map.

// 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 @@ -26,6 +26,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!"),
kevinhartman marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

#[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 @@ -39,6 +39,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 @@ -57,6 +57,7 @@
sys.modules["qiskit._accelerate.convert_2q_block_matrix"] = _accelerate.convert_2q_block_matrix
sys.modules["qiskit._accelerate.dense_layout"] = _accelerate.dense_layout
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
Loading