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

Use retworkx for substitute_node_with_dag #6302

Merged
merged 35 commits into from
Sep 7, 2021
Merged

Conversation

mtreinish
Copy link
Member

@mtreinish mtreinish commented Apr 26, 2021

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:

This commit leverage the substitute_node_with_subgraph method being
added Qiskit/rustworkx#312 for the dagcircuit method
substitute_node_with_dag.
@mtreinish mtreinish requested a review from a team as a code owner April 26, 2021 14:49
@mtreinish mtreinish added the on hold Can not fix yet label Apr 26, 2021
@mtreinish
Copy link
Member Author

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 optimization_level=3 on the transpile calls (but changing to range(3) in the interest of time) on main the script returned 285.9030789534251 seconds and with this PR it returned 189.92249496777853 seconds

@mtreinish mtreinish added Changelog: None Do not include in changelog and removed on hold Can not fix yet labels Aug 26, 2021
@mtreinish
Copy link
Member Author

Now that retworkx 0.10.x has been released this is unblocked

jakelishman
jakelishman previously approved these changes Aug 27, 2021
Copy link
Member

@jakelishman jakelishman left a 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.

Comment on lines +1022 to +1024
pred = self._multi_graph.find_predecessors_by_edge(
node._node_id, lambda edge, wire=self_wire: edge == wire
)[0]
Copy link
Member

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.

Copy link
Member Author

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 Show resolved Hide resolved
Comment on lines 1063 to 1074
# 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
Copy link
Member

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?

Copy link
Member Author

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.

@jakelishman
Copy link
Member

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!

@mtreinish
Copy link
Member Author

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.

Copy link
Member

@kdk kdk left a 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.

test/python/dagcircuit/test_dagcircuit.py Outdated Show resolved Hide resolved
test/python/dagcircuit/test_dagcircuit.py Outdated Show resolved Hide resolved
qiskit/dagcircuit/dagcircuit.py Show resolved Hide resolved
qiskit/dagcircuit/dagcircuit.py Outdated Show resolved Hide resolved
qiskit/dagcircuit/dagcircuit.py Outdated Show resolved Hide resolved
Co-authored-by: Kevin Krsulich <kevin@krsulich.net>
@mtreinish mtreinish requested review from kdk and jakelishman August 30, 2021 17:29
jakelishman
jakelishman previously approved these changes Aug 31, 2021
Copy link
Member

@jakelishman jakelishman left a 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.

@jakelishman jakelishman added Changelog: New Feature Include in the "Added" section of the changelog and removed Changelog: None Do not include in changelog labels Aug 31, 2021
@mergify mergify bot merged commit 1eb6681 into Qiskit:main Sep 7, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: New Feature Include in the "Added" section of the changelog performance
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants