Skip to content

Commit 75436a4

Browse files
authored
Remove sort_key attribute from DAGNode (#13736)
* Remove sort_key attribute from DAGNode Prior to having the DAGCircuit in rust the `sort_key` attribute was added as a cache to speed up the lexicographical topological sort. Prior to having this attribute the topological sort method would compute the sort key as a string on the fly which ended up being a large bottleneck in transpilation (see: #4040 for more details). However, since migrating the DAGCircuit implementation to Rust this sort_key attribute isn't needed anymore because we call the rustworkx-core function with a tuple of bit objects instead of a string. The `sort_key` atribute was left on in place for backwards compatibility (it shouldn't have been public, this was a mistake in #4040) and when we create a python DAGNode object that will be returned to Python the creation of the sort key is unnecessary overhead (it takes roughly 1% of profile time to format the sort_key string during transpilation). Since nothing in DAGCircuit is actually using it this commit removes it to save the CPU cycles creating the string on each dag creation. We will need to add a deprecation to 1.4.0 to mark this removal for 2.0.0 since this was missed in 1.3.0. * Simplify star pre-routing sort_key logic
1 parent cc7ac53 commit 75436a4

File tree

6 files changed

+62
-81
lines changed

6 files changed

+62
-81
lines changed

crates/accelerate/src/gate_direction.rs

-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ use hashbrown::HashSet;
1717
use pyo3::intern;
1818
use pyo3::prelude::*;
1919
use pyo3::types::PyTuple;
20-
use pyo3::IntoPyObjectExt;
2120
use qiskit_circuit::operations::OperationRef;
2221
use qiskit_circuit::packed_instruction::PackedOperation;
2322
use qiskit_circuit::{
@@ -399,7 +398,6 @@ fn has_calibration_for_op_node(
399398
#[cfg(feature = "cache_pygates")]
400399
py_op: packed_inst.py_op.clone(),
401400
},
402-
sort_key: "".into_py_any(py)?,
403401
},
404402
DAGNode { node: None },
405403
),

crates/circuit/src/dag_circuit.rs

+10-13
Original file line numberDiff line numberDiff line change
@@ -2674,9 +2674,8 @@ def _format(operand):
26742674
///
26752675
/// Args:
26762676
/// key (Callable): A callable which will take a DAGNode object and
2677-
/// return a string sort key. If not specified the
2678-
/// :attr:`~qiskit.dagcircuit.DAGNode.sort_key` attribute will be
2679-
/// used as the sort key for each node.
2677+
/// return a string sort key. If not specified the bit qargs and
2678+
/// cargs of a node will be used for sorting.
26802679
///
26812680
/// Returns:
26822681
/// generator(DAGOpNode, DAGInNode, or DAGOutNode): node in topological order
@@ -2710,9 +2709,8 @@ def _format(operand):
27102709
///
27112710
/// Args:
27122711
/// key (Callable): A callable which will take a DAGNode object and
2713-
/// return a string sort key. If not specified the
2714-
/// :attr:`~qiskit.dagcircuit.DAGNode.sort_key` attribute will be
2715-
/// used as the sort key for each node.
2712+
/// return a string sort key. If not specified the qargs and
2713+
/// cargs of a node will be used for sorting.
27162714
///
27172715
/// Returns:
27182716
/// generator(DAGOpNode): op node in topological order
@@ -5612,22 +5610,22 @@ impl DAGCircuit {
56125610
let dag_node = match weight {
56135611
NodeType::QubitIn(qubit) => Py::new(
56145612
py,
5615-
DAGInNode::new(py, id, self.qubits.get(*qubit).unwrap().clone_ref(py)),
5613+
DAGInNode::new(id, self.qubits.get(*qubit).unwrap().clone_ref(py)),
56165614
)?
56175615
.into_any(),
56185616
NodeType::QubitOut(qubit) => Py::new(
56195617
py,
5620-
DAGOutNode::new(py, id, self.qubits.get(*qubit).unwrap().clone_ref(py)),
5618+
DAGOutNode::new(id, self.qubits.get(*qubit).unwrap().clone_ref(py)),
56215619
)?
56225620
.into_any(),
56235621
NodeType::ClbitIn(clbit) => Py::new(
56245622
py,
5625-
DAGInNode::new(py, id, self.clbits.get(*clbit).unwrap().clone_ref(py)),
5623+
DAGInNode::new(id, self.clbits.get(*clbit).unwrap().clone_ref(py)),
56265624
)?
56275625
.into_any(),
56285626
NodeType::ClbitOut(clbit) => Py::new(
56295627
py,
5630-
DAGOutNode::new(py, id, self.clbits.get(*clbit).unwrap().clone_ref(py)),
5628+
DAGOutNode::new(id, self.clbits.get(*clbit).unwrap().clone_ref(py)),
56315629
)?
56325630
.into_any(),
56335631
NodeType::Operation(packed) => {
@@ -5646,7 +5644,6 @@ impl DAGCircuit {
56465644
#[cfg(feature = "cache_pygates")]
56475645
py_op: packed.py_op.clone(),
56485646
},
5649-
sort_key: format!("{:?}", self.sort_key(id)).into_py_any(py)?,
56505647
},
56515648
DAGNode { node: Some(id) },
56525649
),
@@ -5655,12 +5652,12 @@ impl DAGCircuit {
56555652
}
56565653
NodeType::VarIn(var) => Py::new(
56575654
py,
5658-
DAGInNode::new(py, id, self.vars.get(*var).unwrap().clone_ref(py)),
5655+
DAGInNode::new(id, self.vars.get(*var).unwrap().clone_ref(py)),
56595656
)?
56605657
.into_any(),
56615658
NodeType::VarOut(var) => Py::new(
56625659
py,
5663-
DAGOutNode::new(py, id, self.vars.get(*var).unwrap().clone_ref(py)),
5660+
DAGOutNode::new(id, self.vars.get(*var).unwrap().clone_ref(py)),
56645661
)?
56655662
.into_any(),
56665663
};

crates/circuit/src/dag_node.rs

+16-63
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,6 @@ impl DAGNode {
114114
#[pyclass(module = "qiskit._accelerate.circuit", extends=DAGNode)]
115115
pub struct DAGOpNode {
116116
pub instruction: CircuitInstruction,
117-
#[pyo3(get)]
118-
pub sort_key: PyObject,
119117
}
120118

121119
#[pymethods]
@@ -131,7 +129,6 @@ impl DAGOpNode {
131129
) -> PyResult<Py<Self>> {
132130
let py_op = op.extract::<OperationFromPython>()?;
133131
let qargs = qargs.map_or_else(|| PyTuple::empty(py), |q| q.value);
134-
let sort_key = qargs.str().unwrap().into();
135132
let cargs = cargs.map_or_else(|| PyTuple::empty(py), |c| c.value);
136133
let instruction = CircuitInstruction {
137134
operation: py_op.operation,
@@ -143,16 +140,7 @@ impl DAGOpNode {
143140
py_op: op.unbind().into(),
144141
};
145142

146-
Py::new(
147-
py,
148-
(
149-
DAGOpNode {
150-
instruction,
151-
sort_key,
152-
},
153-
DAGNode { node: None },
154-
),
155-
)
143+
Py::new(py, (DAGOpNode { instruction }, DAGNode { node: None }))
156144
}
157145

158146
fn __hash__(slf: PyRef<'_, Self>) -> PyResult<u64> {
@@ -239,7 +227,6 @@ impl DAGOpNode {
239227
mut instruction: CircuitInstruction,
240228
deepcopy: bool,
241229
) -> PyResult<PyObject> {
242-
let sort_key = instruction.qubits.bind(py).str().unwrap().into();
243230
if deepcopy {
244231
instruction.operation = instruction.operation.py_deepcopy(py, None)?;
245232
#[cfg(feature = "cache_pygates")]
@@ -248,15 +235,12 @@ impl DAGOpNode {
248235
}
249236
}
250237
let base = PyClassInitializer::from(DAGNode { node: None });
251-
let sub = base.add_subclass(DAGOpNode {
252-
instruction,
253-
sort_key,
254-
});
238+
let sub = base.add_subclass(DAGOpNode { instruction });
255239
Py::new(py, sub)?.into_py_any(py)
256240
}
257241

258242
fn __reduce__(slf: PyRef<Self>, py: Python) -> PyResult<PyObject> {
259-
let state = (slf.as_ref().node.map(|node| node.index()), &slf.sort_key);
243+
let state = slf.as_ref().node.map(|node| node.index());
260244
let temp = (
261245
slf.instruction.get_operation(py)?,
262246
&slf.instruction.qubits,
@@ -266,9 +250,8 @@ impl DAGOpNode {
266250
}
267251

268252
fn __setstate__(mut slf: PyRefMut<Self>, state: &Bound<PyAny>) -> PyResult<()> {
269-
let (index, sort_key): (Option<usize>, PyObject) = state.extract()?;
253+
let index: Option<usize> = state.extract()?;
270254
slf.as_mut().node = index.map(NodeIndex::new);
271-
slf.sort_key = sort_key;
272255
Ok(())
273256
}
274257

@@ -461,44 +444,29 @@ impl DAGOpNode {
461444
pub struct DAGInNode {
462445
#[pyo3(get)]
463446
pub wire: PyObject,
464-
#[pyo3(get)]
465-
sort_key: PyObject,
466447
}
467448

468449
impl DAGInNode {
469-
pub fn new(py: Python, node: NodeIndex, wire: PyObject) -> (Self, DAGNode) {
470-
(
471-
DAGInNode {
472-
wire,
473-
sort_key: intern!(py, "[]").clone().into(),
474-
},
475-
DAGNode { node: Some(node) },
476-
)
450+
pub fn new(node: NodeIndex, wire: PyObject) -> (Self, DAGNode) {
451+
(DAGInNode { wire }, DAGNode { node: Some(node) })
477452
}
478453
}
479454

480455
#[pymethods]
481456
impl DAGInNode {
482457
#[new]
483-
fn py_new(py: Python, wire: PyObject) -> PyResult<(Self, DAGNode)> {
484-
Ok((
485-
DAGInNode {
486-
wire,
487-
sort_key: intern!(py, "[]").clone().into(),
488-
},
489-
DAGNode { node: None },
490-
))
458+
fn py_new(wire: PyObject) -> PyResult<(Self, DAGNode)> {
459+
Ok((DAGInNode { wire }, DAGNode { node: None }))
491460
}
492461

493462
fn __reduce__<'py>(slf: PyRef<'py, Self>, py: Python<'py>) -> PyResult<Bound<'py, PyTuple>> {
494-
let state = (slf.as_ref().node.map(|node| node.index()), &slf.sort_key);
463+
let state = slf.as_ref().node.map(|node| node.index());
495464
(py.get_type::<Self>(), (&slf.wire,), state).into_pyobject(py)
496465
}
497466

498467
fn __setstate__(mut slf: PyRefMut<Self>, state: &Bound<PyAny>) -> PyResult<()> {
499-
let (index, sort_key): (Option<usize>, PyObject) = state.extract()?;
468+
let index: Option<usize> = state.extract()?;
500469
slf.as_mut().node = index.map(NodeIndex::new);
501-
slf.sort_key = sort_key;
502470
Ok(())
503471
}
504472

@@ -534,44 +502,29 @@ impl DAGInNode {
534502
pub struct DAGOutNode {
535503
#[pyo3(get)]
536504
pub wire: PyObject,
537-
#[pyo3(get)]
538-
sort_key: PyObject,
539505
}
540506

541507
impl DAGOutNode {
542-
pub fn new(py: Python, node: NodeIndex, wire: PyObject) -> (Self, DAGNode) {
543-
(
544-
DAGOutNode {
545-
wire,
546-
sort_key: intern!(py, "[]").clone().into(),
547-
},
548-
DAGNode { node: Some(node) },
549-
)
508+
pub fn new(node: NodeIndex, wire: PyObject) -> (Self, DAGNode) {
509+
(DAGOutNode { wire }, DAGNode { node: Some(node) })
550510
}
551511
}
552512

553513
#[pymethods]
554514
impl DAGOutNode {
555515
#[new]
556-
fn py_new(py: Python, wire: PyObject) -> PyResult<(Self, DAGNode)> {
557-
Ok((
558-
DAGOutNode {
559-
wire,
560-
sort_key: intern!(py, "[]").clone().into(),
561-
},
562-
DAGNode { node: None },
563-
))
516+
fn py_new(wire: PyObject) -> PyResult<(Self, DAGNode)> {
517+
Ok((DAGOutNode { wire }, DAGNode { node: None }))
564518
}
565519

566520
fn __reduce__(slf: PyRef<Self>, py: Python) -> PyResult<PyObject> {
567-
let state = (slf.as_ref().node.map(|node| node.index()), &slf.sort_key);
521+
let state = slf.as_ref().node.map(|node| node.index());
568522
(py.get_type::<Self>(), (&slf.wire,), state).into_py_any(py)
569523
}
570524

571525
fn __setstate__(mut slf: PyRefMut<Self>, state: &Bound<PyAny>) -> PyResult<()> {
572-
let (index, sort_key): (Option<usize>, PyObject) = state.extract()?;
526+
let index: Option<usize> = state.extract()?;
573527
slf.as_mut().node = index.map(NodeIndex::new);
574-
slf.sort_key = sort_key;
575528
Ok(())
576529
}
577530

qiskit/dagcircuit/dagdependency_v2.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"""_DAGDependencyV2 class for representing non-commutativity in a circuit.
1414
"""
1515

16+
import itertools
1617
import math
1718
from collections import OrderedDict, defaultdict, namedtuple
1819
from typing import Dict, List, Generator, Any
@@ -459,7 +460,9 @@ def topological_nodes(self, key=None) -> Generator[DAGOpNode, Any, Any]:
459460
"""
460461

461462
def _key(x):
462-
return x.sort_key
463+
return ",".join(
464+
f"{self.find_bit(q).index:04d}" for q in itertools.chain(x.qargs, x.cargs)
465+
)
463466

464467
if key is None:
465468
key = _key

qiskit/transpiler/passes/routing/star_prerouting.py

+17-2
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,20 @@
1111
# that they have been altered from the originals.
1212

1313
"""Search for star connectivity patterns and replace them with."""
14+
import itertools
1415
from typing import Iterable, Union, Optional, List, Tuple
1516
from math import floor, log10
1617

1718
from qiskit.circuit import SwitchCaseOp, Clbit, ClassicalRegister, Barrier
1819
from qiskit.circuit.controlflow import condition_resources, node_resources
19-
from qiskit.dagcircuit import DAGOpNode, DAGDepNode, DAGDependency, DAGCircuit
20+
from qiskit.dagcircuit import (
21+
DAGOpNode,
22+
DAGDepNode,
23+
DAGDependency,
24+
DAGCircuit,
25+
DAGOutNode,
26+
DAGInNode,
27+
)
2028
from qiskit.transpiler.basepasses import TransformationPass
2129
from qiskit.transpiler.layout import Layout
2230
from qiskit.transpiler.passes.routing.sabre_swap import _build_sabre_dag, _apply_sabre_result
@@ -331,7 +339,14 @@ def star_preroute(self, dag, blocks, processing_order):
331339
}
332340

333341
def tie_breaker_key(node):
334-
return processing_order_index_map.get(node, node.sort_key)
342+
processing_order = processing_order_index_map.get(node, None)
343+
if processing_order is not None:
344+
return processing_order
345+
if isinstance(node, (DAGInNode, DAGOutNode)):
346+
return str(node.wire)
347+
return ",".join(
348+
f"{dag.find_bit(q).index:04d}" for q in itertools.chain(node.qargs, node.cargs)
349+
)
335350

336351
rust_processing_order = _extract_nodes(dag.topological_op_nodes(key=tie_breaker_key), dag)
337352

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
upgrade_transpiler:
3+
- |
4+
Removed the deprecated ``DAGNode.sort_key`` attribute. This attribute was deprecated
5+
in the Qiskit 1.4.0 release. As the lexicographical topological sorting is done internally
6+
and rust and the sort key attribute was unused this was removed to remove the overhead
7+
from DAG node creation. If you were relying on the sort key you can reproduce it from
8+
a given node using something like::
9+
10+
def get_sort_key(node: DAGNode):
11+
if isinstance(node, (DAGInNode, DAGOutNode)):
12+
return str(node.wire)
13+
return ",".join(
14+
f"{dag.find_bit(q).index:04d}" for q in itertools.chain(node.qargs, node.cargs)
15+
)

0 commit comments

Comments
 (0)