Skip to content

Commit da61465

Browse files
authored
Deprecate DAGNode.sort_key attribute (#13744)
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. This attribute is removed in Qiskit 2.0.0 in #13736
1 parent 02e1476 commit da61465

File tree

4 files changed

+100
-8
lines changed

4 files changed

+100
-8
lines changed

crates/circuit/src/dag_node.rs

+59-5
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use std::cell::OnceCell;
1515
use std::hash::Hasher;
1616

1717
use crate::circuit_instruction::{CircuitInstruction, OperationFromPython};
18-
use crate::imports::QUANTUM_CIRCUIT;
18+
use crate::imports::{QUANTUM_CIRCUIT, WARNINGS_WARN};
1919
use crate::operations::{Operation, Param};
2020
use crate::TupleLikeArg;
2121

@@ -24,7 +24,7 @@ use approx::relative_eq;
2424
use rustworkx_core::petgraph::stable_graph::NodeIndex;
2525

2626
use numpy::IntoPyArray;
27-
use pyo3::exceptions::PyValueError;
27+
use pyo3::exceptions::{PyDeprecationWarning, PyValueError};
2828
use pyo3::prelude::*;
2929
use pyo3::types::{PyString, PyTuple};
3030
use pyo3::{intern, IntoPy, PyObject, PyResult, ToPyObject};
@@ -111,7 +111,6 @@ impl DAGNode {
111111
#[pyclass(module = "qiskit._accelerate.circuit", extends=DAGNode)]
112112
pub struct DAGOpNode {
113113
pub instruction: CircuitInstruction,
114-
#[pyo3(get)]
115114
pub sort_key: PyObject,
116115
}
117116

@@ -152,6 +151,25 @@ impl DAGOpNode {
152151
)
153152
}
154153

154+
#[getter]
155+
fn sort_key(&self, py: Python) -> PyResult<PyObject> {
156+
WARNINGS_WARN.get_bound(py).call1((
157+
intern!(
158+
py,
159+
concat!(
160+
"The sort_key attribute of DAGOpNode has been deprecated ",
161+
"and will be removed in the Qiskit 2.0.0 release. ",
162+
"Instead you can create this by looking at the `qargs` and ",
163+
"`cargs` attributes to create an equivalent string for a ",
164+
"given DAGOpNode instance.",
165+
)
166+
),
167+
py.get_type_bound::<PyDeprecationWarning>(),
168+
1,
169+
))?;
170+
Ok(self.sort_key.clone_ref(py))
171+
}
172+
155173
fn __hash__(slf: PyRef<'_, Self>) -> PyResult<u64> {
156174
let super_ = slf.as_ref();
157175
let mut hasher = AHasher::default();
@@ -462,7 +480,6 @@ impl DAGOpNode {
462480
pub struct DAGInNode {
463481
#[pyo3(get)]
464482
pub wire: PyObject,
465-
#[pyo3(get)]
466483
sort_key: PyObject,
467484
}
468485

@@ -491,6 +508,25 @@ impl DAGInNode {
491508
))
492509
}
493510

511+
#[getter]
512+
fn sort_key(&self, py: Python) -> PyResult<PyObject> {
513+
WARNINGS_WARN.get_bound(py).call1((
514+
intern!(
515+
py,
516+
concat!(
517+
"The sort_key attribute of DAGInNode has been deprecated ",
518+
"and will be removed in the Qiskit 2.0.0 release. ",
519+
"Instead you can create this by looking at the `wire` ",
520+
"attribute to create an equivalent string for a given ",
521+
"DAGInNode instance."
522+
)
523+
),
524+
py.get_type_bound::<PyDeprecationWarning>(),
525+
1,
526+
))?;
527+
Ok(self.sort_key.clone_ref(py))
528+
}
529+
494530
fn __reduce__(slf: PyRef<Self>, py: Python) -> PyObject {
495531
let state = (slf.as_ref().node.map(|node| node.index()), &slf.sort_key);
496532
(py.get_type_bound::<Self>(), (&slf.wire,), state).into_py(py)
@@ -535,7 +571,6 @@ impl DAGInNode {
535571
pub struct DAGOutNode {
536572
#[pyo3(get)]
537573
pub wire: PyObject,
538-
#[pyo3(get)]
539574
sort_key: PyObject,
540575
}
541576

@@ -564,6 +599,25 @@ impl DAGOutNode {
564599
))
565600
}
566601

602+
#[getter]
603+
fn sort_key(&self, py: Python) -> PyResult<PyObject> {
604+
WARNINGS_WARN.get_bound(py).call1((
605+
intern!(
606+
py,
607+
concat!(
608+
"The sort_key attribute of DAGOutNode has been deprecated ",
609+
"and will be removed in the Qiskit 2.0.0 release. ",
610+
"Instead you can create this by looking at the `wire` ",
611+
"attribute to create an equivalent string for a given ",
612+
"DAGInNode instance."
613+
)
614+
),
615+
py.get_type_bound::<PyDeprecationWarning>(),
616+
1,
617+
))?;
618+
Ok(self.sort_key.clone_ref(py))
619+
}
620+
567621
fn __reduce__(slf: PyRef<Self>, py: Python) -> PyObject {
568622
let state = (slf.as_ref().node.map(|node| node.index()), &slf.sort_key);
569623
(py.get_type_bound::<Self>(), (&slf.wire,), state).into_py(py)

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
@@ -330,7 +338,14 @@ def star_preroute(self, dag, blocks, processing_order):
330338
}
331339

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

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

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
deprecations_transpiler:
3+
- |
4+
The :attr:`.DAGOpNode.sort_key`, :attr:`.DAGOutNode.sort_key`, and
5+
:attr:`.DAGInNode.sort_key` attributes have been deprecated and will be
6+
removed in the Qiskit 2.0.0 release. This attribute was originally used
7+
as a lexicographical key for topological sorting nodes in
8+
a :class:`.DAGCircuit`. However, the key is no longer used for this
9+
as the sorting is done internally in Rust code now. If you're using this
10+
attribute for anything you can recreate the key from the other attributes
11+
of a node. For example, you can use a function like::
12+
13+
def get_sort_key(node: DAGNode):
14+
if isinstance(node, (DAGInNode, DAGOutNode)):
15+
return str(node.wire)
16+
return ",".join(
17+
f"{dag.find_bit(q).index:04d}" for q in itertools.chain(node.qargs, node.cargs)
18+
)
19+
20+
which will generate a string like the sort key does.

0 commit comments

Comments
 (0)