Skip to content

Add DAGDependencyV2 and Converters #11705

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

Merged
merged 43 commits into from
Apr 16, 2024
Merged

Conversation

enavarro51
Copy link
Contributor

@enavarro51 enavarro51 commented Feb 1, 2024

Summary

Add new DAGDependencyV2 and Converters

Details and comments

This PR is a replacement for #11310. In that PR, the new DAGDependencyV2 was created and the associated template matching code was modified to use it. This PR only creates the dag and the 2 converters required. The following changes were made,

  • There is a new DAGDependencyV2 which no longer uses DAGDepNode, but instead uses the existing DAGOpNode without modification. The makes for a cleaner and much reduced memory profile for the nodes.
  • Additional methods from DAGCircuit, such as find_bit were added to the DAGDependencyV2 and other methods were modified to more closely match the 2 dags.
  • The information stored previously in the node attributes would now be accessed in the template matching code through calls to the rustworkx functions once the template matching code is refactored.
  • A terminology change was made in DAGDependencyV2. direct_successors is now successors and successors is now descendants. Similarly for predecessors and ancestors. This mirrors the usage in rustworkx.

Doing some local tests on an 8000 gate circuit and converting the circuit to a dag dependency, V2 used ~300MB of memory and V1 used ~2.5GB of memory.

The next phase will be to refactor the template matching code to use V2 and to optimize the matching.

edit (@jakelishman):

Warning

Just to highlight to anyone else reading this, again: all this is deliberately not public, and it's all so not public (undocumented, and has explicitly private names) that we're not even marking it with the "experimental" API. This is for near-term experimentation and there's a good chance it may even be replaced entirely with a pure-Rust implementation before being made public.

@enavarro51 enavarro51 requested a review from a team as a code owner February 1, 2024 15:09
@qiskit-bot
Copy link
Collaborator

One or more of the the following people are requested to review this:

  • @Qiskit/terra-core

@coveralls
Copy link

coveralls commented Feb 1, 2024

Pull Request Test Coverage Report for Build 8705828497

Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

Details

  • 204 of 281 (72.6%) changed or added relevant lines in 4 files are covered.
  • 84 unchanged lines in 7 files lost coverage.
  • Overall coverage decreased (-0.1%) to 89.262%

Changes Missing Coverage Covered Lines Changed/Added Lines %
qiskit/visualization/dag_visualization.py 0 3 0.0%
qiskit/dagcircuit/dagdependency_v2.py 170 244 69.67%
Files with Coverage Reduction New Missed Lines %
qiskit/synthesis/two_qubit/xx_decompose/circuits.py 1 92.86%
qiskit/transpiler/passes/routing/sabre_swap.py 2 96.82%
qiskit/synthesis/two_qubit/xx_decompose/decomposer.py 2 90.63%
crates/qasm2/src/lex.rs 5 92.37%
crates/qasm2/src/parse.rs 12 96.23%
qiskit/quantum_info/operators/symplectic/pauli.py 29 84.64%
qiskit/transpiler/passes/synthesis/unitary_synthesis.py 33 89.02%
Totals Coverage Status
Change from base Build 8614257652: -0.1%
Covered Lines: 60353
Relevant Lines: 67613

💛 - Coveralls

Copy link
Member

@alexanderivrii alexanderivrii left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am very excited about this new DAG-dependency interface because it's significantly closer to the DAGCircuit interface and in principle allows to unify the two even further. Ideally I would like the two interfaces to be almost fully identical (modulo the comment below) and I have left several inline comments to that extent. In particular, I am suggesting to remove or to adapt all methods that don't have a close DAGCircuit analogue (or add such methods to DAGCircuit as well). (This does not apply to internal methods prefixed with _).

But even right now this allows to write transpiler passes that can work with either circuit representation.

Other than that, we want release notes and a few more tests exploring various DAGDependencyV2 functionality (possibly many of these can be adapted from DAGDependency tests).

The promised comment is that DAG-dependency is conceptually different from a DAG-circuit and some operations on DAG-dependency have to be implemented considerably differently. For instance, we don't yet have a DAG-dependency analogue of substitute_node_with_dag because adding new nodes to DAG dependency generally involves performing commutativity checks and recursively examining descendants and ancestors of the existing nodes.

Comment on lines 407 to 415
def get_node(self, node_id):
"""
Args:
node_id (int): label of considered node.

Returns:
node: corresponding to the label.
"""
return self._multi_graph.get_node_data(node_id)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am slightly confused: DAGCircuit does not have this function nor ever checks _multi_graph.get_node_data. Maybe we don't need these functions, given your changes to unifying the predecessor/successor interface? I.e. this looks like a point where the APIs for DAGCircuit and DAGDependencyV2 slightly deviate, and I am wondering if we can avoid this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is needed in template matching so not important there, but also in _update_edges since that method is written using node indices instead of node object ids. I did a first try to convert this method to using object ids, but could not come up with an efficient way to do it. So for now I would leave this and address it later.

The use of node indices could also be a problem if nodes are inserted or removed and then iterated over.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving this in until I rewrite _update_edges to not use indices. Also used in some of the tests, which will need to be redone later.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. What about making get_node into an internal function (i.e. just renaming it to _get_node), so that we will able to freely change/remove it later?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, let's rewrite _update_edges in a follow-up PR, not now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed on _get_node and redo _update_edges later.

Comment on lines 446 to 448
def remove_op_node(self, node):
"""Remove an operation node n.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a little bit worried about this function in the context of DAG dependency. Though (after some thinking) I do believe that this method of removing nodes and reconnecting predecessors to successors is safe: we will not be able to incorrectly infer from dag dependency that two nodes commute when in fact they do not. It might not be compete: the dag dependency may say that two nodes do not commute when in fact they do. For example, suppose that we have a chain of nodes A -> B -> C. By definition, A and B do not commute, and B and C do not commute (but this says nothing about whether A and C commute). If we remove B and retain edges, we will end up with A -> C, which might be over-conservative if A and C do commute. Note that it's not enough to check the commutativity of A and C and remove the edge if they do commute, since we should also recursively check if C commutes with predecessors of A, and A commutes with successors of C. So at the minimum let's update the docstring of this function. Hmm, when would we ever want to remove nodes from a DAG?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment for replace_block_with_op addresses the question of commutativity of the output, which is relevant here as well. remove_op_node is used extensively in the transpiler passes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, replace_block_with_op has the same flavor: the resulting DAG-dependency may hide some commutativity relations. So at some point we may want to add a function to reconstruct DAGDependency from scratch.

Comment on lines 577 to 578
target_dag.comm_checker = CommutationChecker()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do this instead: target_dag.comm_checker = self.comm_checker?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok.

Comment on lines 17 to 21
from test import QiskitTestCase

from qiskit.converters.dagdependency_to_circuit import dagdependency_to_circuit
from qiskit.converters.circuit_to_dagdependency import circuit_to_dagdependency
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
from test import QiskitTestCase # pylint: disable=wrong-import-order
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't say that I fully understand why PR #10998 replaced all lines of the form

from test import QiskitTestCase

to

from test import QiskitTestCase  # pylint: disable=wrong-import-order

but it may be best to keep the change here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why either, but can leave as is.

Comment on lines 24 to 25
class TestCircuitToDagCanonical(QiskitTestCase):
"""Test QuantumCircuit to DAGDependency."""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the name of the class be TestCircuitToDagDependency (not that it really matters).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok.

Copy link
Member

@alexanderivrii alexanderivrii left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the quick updates. I have added a few more minor inline suggestions, with the only other thing being the release notes :).

if key is None:
key = _key

return iter(rx.lexicographical_topological_sort(self._multi_graph, key=_key))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return iter(rx.lexicographical_topological_sort(self._multi_graph, key=_key))
return iter(rx.lexicographical_topological_sort(self._multi_graph, key=key))

I just stumbled upon this typo while working on something else. :-)

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.

For others' reference: this DAGDependencyV2 is exploratory work that we're doing with Ed (@enavarro51) about reworking the DAGDependency into a usable and much faster form for template matching, and potentially using it more throughout other parts of the transpiler pipeline.

We're looking to merge it into the main Qiskit as a fully private object, mostly to support longer-term working on the project with Ed as an external contributor while working with us. There's a chance that the Python implementation never gets made fully public, but that instead we make a fully Rust version of this DAGDependencyV2 (with Python wrappers) and use that in things like template matching and commutation analysis. There are follow-up PRs to this that will do things like update the _update_wires function and the core data structure a little to include the requirement to track a leaves set when modifying the graph (in order to reduce the computational complexity of appends to the graph), but it's still in the largely exploratory phases at the moment.

Ed: I'm largely fine to merge this as-is, except for:

  • let's remove the public release note
  • we probably want to fix the typo Sebastian noticed

For the bits Sasha was talking about with remove_op_node (and really any removals): the best situation is probably just to remove those functions from this PR - if they're broken, there's no real reason to include them in even the exploratory merge. I've no issue with removing all the functions that exist that we don't immediately have a use for in the exploratory stuff - we can add useful things as/when we need them. If anything, it's probably best to start with less than more, just so we don't forget to remove the cruft later.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove the release note, to keep it very much seeming internal.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will remove the release note. As to other functions, I'll remove the remove_op_node and remove_all_ops_named functions. My question is, what does "all the functions that exist that we don't immediately have a use for" mean? There seems to be a difference of opinion on what that includes.

enavarro51 and others added 5 commits April 9, 2024 15:07
These `quantum_successors` etc have no meaning in `DAGDependency` (and
V2) since the edges don't carry the data that the `DAGCircuit` functions
check for; `DAGDependency` is in part a deliberate erasure of this data.
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.

Thanks for this Ed. I've removed the quantum/classical successor/predecessor methods because they were checking edge data that completely doesn't exist on DAGDependency. I'm not super convinced that replace_block_with_op is completely right yet, but I think that might be the same as in DAGDependency and I don't want to erase it out from under Sasha.

Just to highlight to anyone else reading this, again: all this is deliberately not public, and it's all so not public (undocumented, and has explicitly private names) that we're not even marking it with the "experimental" API. This is for near-term experimentation and there's a good chance it may even be replaced entirely with a pure-Rust implementation before being made public.

@jakelishman jakelishman enabled auto-merge April 16, 2024 12:47
@jakelishman jakelishman added the Changelog: None Do not include in changelog label Apr 16, 2024
@jakelishman jakelishman added this pull request to the merge queue Apr 16, 2024
Merged via the queue into Qiskit:main with commit 6cae4cf Apr 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: None Do not include in changelog
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants