-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
Use retworkx for substitute_node_with_dag #6302
Conversation
This commit leverage the substitute_node_with_subgraph method being added Qiskit/rustworkx#312 for the dagcircuit method substitute_node_with_dag.
I ran some simple benchmarks with this script: import time
import statistics
from qiskit.circuit.random import random_circuit
from qiskit.test.mock import FakeMontreal
from qiskit import transpile
backend = FakeMontreal()
qc = random_circuit(27, 100, measure=True, seed=42)
transpile(qc, backend)
times = []
for i in range(10):
start = time.time()
transpile(qc, backend)
stop = time.time()
times.append(stop - start)
print(statistics.mean(times)) On main without this PR this returned 7.581698060035706 seconds and with this PR it returned 6.584884548187256 seconds. Then jumping it up to |
Now that retworkx 0.10.x has been released this is unblocked |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code and tests all look correct to me, so I'm happy enough to approve this. I did leave a couple of questions that are about my understanding of the DAGCircuit
, though.
pred = self._multi_graph.find_predecessors_by_edge( | ||
node._node_id, lambda edge, wire=self_wire: edge == wire | ||
)[0] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To check, here you're just using the second argument of lambda edge, wire=self_wire: edge == wire
purely for the normal safe scoping within the lambda, right? e.g. it's only ever intended to be called with one argument, like self_wire.__eq__
but with conversion of NotImplemented
to errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I did this solely for scoping. IIRC when I wrote this originally I didn't do this and lint or something complained. The filter_fn
argument only gets passed a single arg which is the weight/data payload object for each edge: https://qiskit.org/documentation/retworkx/dev/stubs/retworkx.PyDiGraph.find_predecessors_by_edge.html#retworkx.PyDiGraph.find_predecessors_by_edge
qiskit/dagcircuit/dagcircuit.py
Outdated
# Iterate over nodes of input_circuit and update wires | ||
for old_node_index in node_map: | ||
# update node attributes | ||
new_node_index = node_map[old_node_index] | ||
old_node = in_dag._multi_graph[old_node_index] | ||
condition = self._map_condition(wire_map, old_node.op.condition, self.cregs.values()) | ||
m_qargs = [wire_map.get(x, x) for x in old_node.qargs] | ||
m_cargs = [wire_map.get(x, x) for x in old_node.cargs] | ||
new_node = DAGOpNode(old_node.op, qargs=m_qargs, cargs=m_cargs) | ||
new_node._node_id = new_node_index | ||
new_node.op.condition = condition | ||
self._multi_graph[new_node_index] = new_node |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks correct, but I feel like I'm potentially missing something big in the implementation of DAGCircuit
; I would have expected that the call self._multi_graph.substitute_node_with_subgraph
would have replaced the nodes already. Or is this bit just about updating the references to Bit
objects in the nodes afterwards?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this is just house keeping for the Bit
objects in each DAG node object.
graph.substitute_node_with_subgraph(node, other, edge_map_fn)
copies the nodes and edges from other
into graph
, deletes node
in graph
and wires things into and out of node as described by edge_map_fn
. But the nodes in other still have the same weight/data payload from before the migration. So this section is just about updating the qargs and cargs in the nodes to be the bits from graph instead of the bits in other.
Also, I think making this "Changelog: None" is doing yourself and the others on retworkx a bit of a disservice - it's a significant performance improvement, and it wouldn't be a bad thing to mention it in the notes! |
Heh, I wasn't sure exactly how to describe it in the release notes or even which changelog label would apply as it's not really a user facing change. I'd gladly advertise it in either place but wasn't sure how to word it so it was meaningful for users reading the release documentation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few suggestions on comments/readability, but otherwise, looks good to me.
Co-authored-by: Kevin Krsulich <kevin@krsulich.net>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added a reno mentioning the performance benefits so Matthew doesn't have to toot his own horn - I've stuck it in "features", since I think performance is one of our features.
Sticking my approval on it so it can be merged if someone else is happy with it, but feel free to review the reno/suggest its removal.
Summary
This commit leverage the substitute_node_with_subgraph method being
added Qiskit/rustworkx#312 for the dagcircuit method
substitute_node_with_dag.
Details and comments
TODO:
substitute_node_with_dag
, prior to this sympy takes up ~35-50% of the profile and it's too difficult to see differences from anything else)