Skip to content

Commit fba57cc

Browse files
committed
Use toposort instead of topological_op_nodes for DAG reconstruction
This commit switches the topological sort function we use in transpiler passes when reconstructing a dag from scratch. In several passes where we typically replace or remove a large numbers of gates we iterate over the input dag in topological order and construct a copy of it making the alterations as we go. Right now when we do this we rely on `DAGCircuit::topological_op_nodes` which makes sense because it's or built-in method for iterating over a dag's op node in topological ordering. Internally this uses rustworkx's lexicographical topological sort function with our custom sort function that maintains our desired tie breaker using the bits of a node. However, since Qiskit#14762 where we're asserting structural equality in passes we don't need to use that sort anymore for these reconstruction cases. We just need a consistent topological sort. In optimizing Qiskit#13419 one thing that showed in profiles was that for very large circuits the overhead of the lexicographical topological sort for the iteration. The toposort function in petgraph is lower overhead because it doesn't need to work about the lexicographical tie breaker. By switching to use this instead we can reduce the overhead of the final sort in all these passes. In asv benchmarking this commit speeds up transpiler benchmarks are 2-5% faster (although without asv flagging it as significant).
1 parent e8180ed commit fba57cc

4 files changed

Lines changed: 30 additions & 16 deletions

File tree

crates/transpiler/src/passes/basis_translator/mod.rs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use errors::BasisTranslatorError;
2020
use hashbrown::{HashMap, HashSet};
2121
use indexmap::{IndexMap, IndexSet};
2222
use pyo3::prelude::*;
23+
use rustworkx_core::petgraph::algo::toposort;
2324

2425
mod basis_search;
2526
mod compose_transforms;
@@ -36,7 +37,7 @@ use qiskit_circuit::parameter::symbol_expr::SymbolExpr;
3637
use qiskit_circuit::parameter::symbol_expr::Value;
3738
use qiskit_circuit::{BlocksMode, Clbit, PhysicalQubit, Qubit, VarsMode};
3839
use qiskit_circuit::{
39-
dag_circuit::DAGCircuit,
40+
dag_circuit::{DAGCircuit, NodeType},
4041
operations::{Operation, OperationRef, PauliBased, PyOperationTypes, PythonOperation},
4142
};
4243
use smallvec::SmallVec;
@@ -343,8 +344,10 @@ fn apply_translation(
343344
)
344345
})?;
345346
let mut out_dag_builder = out_dag.into_builder();
346-
for node in dag.topological_op_nodes(false) {
347-
let node_obj = dag[node].unwrap_operation();
347+
for node in toposort(dag.dag(), None).unwrap() {
348+
let NodeType::Operation(ref node_obj) = dag.dag()[node] else {
349+
continue;
350+
};
348351
let node_qarg = dag.get_qargs(node_obj.qubits);
349352
let node_carg = dag.get_cargs(node_obj.clbits);
350353
let qubit_set: AhashIndexSet<Qubit> = AhashIndexSet::from_iter(node_qarg.iter().copied());
@@ -497,8 +500,10 @@ fn replace_node(
497500
});
498501
}
499502
if params_view.is_empty() {
500-
for inner_index in target_dag.topological_op_nodes(false) {
501-
let inner_node = &target_dag[inner_index].unwrap_operation();
503+
for inner_index in toposort(target_dag.dag(), None).unwrap() {
504+
let NodeType::Operation(ref inner_node) = target_dag[inner_index] else {
505+
continue;
506+
};
502507
let old_qargs = dag.qargs_interner().get(node.qubits);
503508
let old_cargs = dag.cargs_interner().get(node.clbits);
504509
let new_qubits: Vec<Qubit> = target_dag
@@ -574,8 +579,10 @@ fn replace_node(
574579
_ => None,
575580
}),
576581
);
577-
for inner_index in target_dag.topological_op_nodes(false) {
578-
let inner_node = &target_dag[inner_index].unwrap_operation();
582+
for inner_index in toposort(target_dag.dag(), None).unwrap() {
583+
let NodeType::Operation(ref inner_node) = target_dag[inner_index] else {
584+
continue;
585+
};
579586
let old_qargs = dag.qargs_interner().get(node.qubits);
580587
let old_cargs = dag.cargs_interner().get(node.clbits);
581588
let new_qubits: Vec<Qubit> = target_dag

crates/transpiler/src/passes/disjoint_layout.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,15 @@ use pyo3::prelude::*;
2020
use pyo3::types::PyList;
2121
use rustworkx_core::connectivity::connected_components;
2222
use rustworkx_core::petgraph::EdgeType;
23+
use rustworkx_core::petgraph::algo::toposort;
2324
use rustworkx_core::petgraph::prelude::*;
2425
use rustworkx_core::petgraph::visit::{IntoEdgeReferences, IntoNodeReferences, NodeFiltered};
2526
use uuid::Uuid;
2627

2728
use crate::TranspilerError;
2829
use crate::target::{Qargs, Target};
2930
use qiskit_circuit::bit::ShareableQubit;
30-
use qiskit_circuit::dag_circuit::DAGCircuit;
31+
use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType};
3132
use qiskit_circuit::operations::{Operation, OperationRef, StandardInstruction};
3233
use qiskit_circuit::packed_instruction::PackedOperation;
3334
use qiskit_circuit::{
@@ -474,8 +475,10 @@ fn separate_dag(dag: &mut DAGCircuit) -> PyResult<Vec<DAGCircuit>> {
474475
new_dag.set_global_phase_f64(0.);
475476
let old_qubits = dag.qubits();
476477
let mut block_map = BlockMapper::new();
477-
for index in dag.topological_op_nodes(false) {
478-
let node = dag[index].unwrap_operation();
478+
for index in toposort(dag.dag(), None).unwrap() {
479+
let NodeType::Operation(ref node) = dag.dag()[index] else {
480+
continue;
481+
};
479482
let qargs: HashSet<Qubit> = dag.get_qargs(node.qubits).iter().copied().collect();
480483
if dag_qubits.is_superset(&qargs) {
481484
let qargs = dag.get_qargs(node.qubits);

crates/transpiler/src/passes/split_2q_unitaries.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const PI4: f64 = PI / 4.;
1515
use nalgebra::Matrix2;
1616
use num_complex::Complex64;
1717
use pyo3::prelude::*;
18+
use rustworkx_core::petgraph::algo::toposort;
1819
use rustworkx_core::petgraph::stable_graph::NodeIndex;
1920
use smallvec::{SmallVec, smallvec};
2021

@@ -101,9 +102,9 @@ pub fn run_split_2q_unitaries(
101102
let mut mapping: Vec<usize> = (0..dag.num_qubits()).collect();
102103
let new_dag = dag.copy_empty_like(VarsMode::Alike, BlocksMode::Keep)?;
103104
let mut new_dag = new_dag.into_builder();
104-
for node in dag.topological_op_nodes(false) {
105-
let NodeType::Operation(inst) = &dag.dag()[node] else {
106-
unreachable!("Op nodes contain a non-operation");
105+
for node in toposort(dag.dag(), None).unwrap() {
106+
let NodeType::Operation(ref inst) = dag.dag()[node] else {
107+
continue;
107108
};
108109
if let OperationRef::Unitary(unitary_gate) = inst.op.view() {
109110
if unitary_gate.num_qubits() == 2 {

crates/transpiler/src/passes/unitary_synthesis/mod.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@ use std::hash;
2222
use numpy::PyReadonlyArray2;
2323
use pyo3::prelude::*;
2424
use pyo3::{intern, wrap_pyfunction};
25+
use rustworkx_core::petgraph::algo::toposort;
2526

2627
use self::decomposers::{Decomposer2q, DecomposerCache, Direction2q, FlipDirection};
2728
use crate::QiskitError;
2829
use crate::target::Target;
2930
use qiskit_circuit::bit::QuantumRegister;
30-
use qiskit_circuit::dag_circuit::{DAGCircuit, DAGCircuitBuilder};
31+
use qiskit_circuit::dag_circuit::{DAGCircuit, DAGCircuitBuilder, NodeType};
3132
use qiskit_circuit::instruction::Parameters;
3233
use qiskit_circuit::operations::{
3334
Operation, OperationRef, Param, PyOperationTypes, PythonOperation, StandardGate,
@@ -336,8 +337,10 @@ pub fn run_unitary_synthesis(
336337
let mut out = dag
337338
.copy_empty_like(VarsMode::Alike, BlocksMode::Drop)?
338339
.into_builder();
339-
for node in dag.topological_op_nodes(false) {
340-
let inst = dag[node].unwrap_operation();
340+
for node in toposort(dag.dag(), None).unwrap() {
341+
let NodeType::Operation(ref inst) = dag.dag()[node] else {
342+
continue;
343+
};
341344
let Some(cf) = dag.try_view_control_flow(inst) else {
342345
// Handle regular instructions - this path is where we end up most of the time.
343346
if !synthesize_onto(&mut out, state, inst)? {

0 commit comments

Comments
 (0)