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

Add separable_circuits method to DAGCircuit class #9471

Merged
merged 29 commits into from
Jul 19, 2023

Conversation

caleb-johnson
Copy link
Contributor

@caleb-johnson caleb-johnson commented Jan 26, 2023

Summary

There is currently no easy way to retrieve all of the disconnected subsets of qubits in a given circuit, even though this information is easily accessible in the graph representation of a quantum circuit.

Create an instance method, DAGCircuit.separable_circuits, that returns a list of DAGCircuits -- one for each set of disconnected qubits in the quantum circuit.

Closes #9459

@caleb-johnson caleb-johnson requested a review from a team as a code owner January 26, 2023 20:37
@qiskit-bot
Copy link
Collaborator

Thank you for opening a new pull request.

Before your PR can be merged it will first need to pass continuous integration tests and be reviewed. Sometimes the review process can be slow, so please be patient.

While you're waiting, please feel free to review other open PRs. While only a subset of people are authorized to approve pull requests for merging, everyone is encouraged to review open pull requests. Doing reviews helps reduce the burden on the core team and helps make the project's code better for everyone.

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

  • @Qiskit/terra-core

@CLAassistant
Copy link

CLAassistant commented Jan 26, 2023

CLA assistant check
All committers have signed the CLA.

@caleb-johnson caleb-johnson changed the title Add decompose_dag method to DAGCircuit class WIP: Add decompose_dag method to DAGCircuit class Jan 26, 2023
@caleb-johnson caleb-johnson marked this pull request as draft January 26, 2023 20:39
@coveralls
Copy link

coveralls commented Jan 26, 2023

Pull Request Test Coverage Report for Build 5592777414

  • 25 of 25 (100.0%) changed or added relevant lines in 1 file are covered.
  • 28 unchanged lines in 3 files lost coverage.
  • Overall coverage decreased (-0.02%) to 86.025%

Files with Coverage Reduction New Missed Lines %
qiskit/transpiler/passes/synthesis/unitary_synthesis.py 1 90.33%
crates/qasm2/src/lex.rs 3 91.39%
crates/qasm2/src/parse.rs 24 95.71%
Totals Coverage Status
Change from base Build 5592160564: -0.02%
Covered Lines: 72400
Relevant Lines: 84162

💛 - Coveralls

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 is definitely an interesting function, and I'm sure it'll be useful, especially as hardware develops that has sparse links between denser separate chips like IBM Heron. I had a couple of significant design questions in the comments, some about the mathematics and some about the intended usage - I think there might be some hidden complexity here.

I get this is WIP, but later on, don't forget we'll need tests and a release note.

qiskit/dagcircuit/dagcircuit.py Outdated Show resolved Hide resolved
qiskit/dagcircuit/dagcircuit.py Outdated Show resolved Hide resolved
qiskit/dagcircuit/dagcircuit.py Outdated Show resolved Hide resolved
@caleb-johnson caleb-johnson changed the title WIP: Add decompose_dag method to DAGCircuit class Add decompose_dag method to DAGCircuit class Feb 1, 2023
@caleb-johnson caleb-johnson marked this pull request as ready for review February 1, 2023 21:48
@caleb-johnson caleb-johnson changed the title Add decompose_dag method to DAGCircuit class Add separable_circuits method to DAGCircuit class Feb 1, 2023
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.

Thanks @caleb-johnson , this looks good so far! A few comments below.

qiskit/dagcircuit/dagcircuit.py Outdated Show resolved Hide resolved
qiskit/dagcircuit/dagcircuit.py Outdated Show resolved Hide resolved
qiskit/dagcircuit/dagcircuit.py Outdated Show resolved Hide resolved
qiskit/dagcircuit/dagcircuit.py Show resolved Hide resolved
@mtreinish mtreinish added the Changelog: New Feature Include in the "Added" section of the changelog label Jul 11, 2023
@mtreinish mtreinish added this to the 0.25.0 milestone Jul 11, 2023
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 Caleb, and sorry for the slow review. The actual function itself looks like it's in good shape to me. Please could we add a test of the global_phase behaviour, since it's a defined part of the interface, and make sure that some tests of remove_idle_wires include testing explicit forms, especially with regard to the special handling that unconnected clbits get?

For a little bit further docs: could you comment somewhere in the docstring what the behaviour is for the separable DAGs with regard to clbits / classical registers, and make sure we're testing that?

qiskit/dagcircuit/dagcircuit.py Outdated Show resolved Hide resolved
qiskit/dagcircuit/dagcircuit.py Outdated Show resolved Hide resolved
test/python/dagcircuit/test_dagcircuit.py Outdated Show resolved Hide resolved
Comment on lines 1254 to 1283
# Test circuit ordering with measurements
dag = DAGCircuit()
qreg = QuantumRegister(3, "q")
creg = ClassicalRegister(1, "c")
dag.add_qreg(qreg)
dag.add_creg(creg)
dag.apply_operation_back(HGate(), [qreg[0]], [])
dag.apply_operation_back(XGate(), [qreg[0]], [])
dag.apply_operation_back(XGate(), [qreg[1]], [])
dag.apply_operation_back(HGate(), [qreg[1]], [])
dag.apply_operation_back(YGate(), [qreg[2]], [])
dag.apply_operation_back(HGate(), [qreg[2]], [])
dag.apply_operation_back(Measure(), [qreg[0]], [creg[0]])

compare1 = QuantumCircuit(3, 1)
compare1.h(0)
compare1.x(0)
compare1.measure(0, 0)
compare2 = QuantumCircuit(3, 1)
compare2.x(1)
compare2.h(1)
compare3 = QuantumCircuit(3, 1)
compare3.y(2)
compare3.h(2)

dags = dag.separable_circuits()

self.assertEqual(dag_to_circuit(dags[0]), compare1)
self.assertEqual(dag_to_circuit(dags[1]), compare2)
self.assertEqual(dag_to_circuit(dags[2]), compare3)
Copy link
Member

Choose a reason for hiding this comment

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

I think this one introduces a completely disjoint change, right? It would be good to put that in a separate test case, so they both run, even if one fails.

Also, it'd be better to construct the compare1, compare2 and compare3 elements as a list of DAGCircuit, and then compare the two lists directly - that way we get more information on how many / which circuits fail, if there's a failure. I'm also a bit nervous that this test requires that the output has a particular order, but I don't think rustworkx.weakly_connect_components will guarantee that for us? It might be ok to leave for now, but if there's an easy way to make the test resistant to re-orderings of the list, that would be good.

Copy link
Member

@garrison garrison Jul 17, 2023

Choose a reason for hiding this comment

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

I don't think rustworkx.weakly_connect_components will guarantee that for us?

I asked about rustworkx.connected_components on Qiskit Slack, and this is what Matthew said:

There isn't an explicit guarantee to the order, mostly because I don't think we want to commit to it being stable in case we want to change the internal implementation of the algorithm in the future.

In that case, it was easy to make the operation deterministic by calling sort or the result. Something similar may be possible here.

Copy link
Member

Choose a reason for hiding this comment

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

That's a good idea, thanks Jim. I think we could find a way to choose the "first" busy qubit in each returned DAG, and sort the final list based on that qubit's index into the parent DAG. Given the nature of the function, that should be a unique sort key, so should deterministically order the list.

Just to be clear: I think we only need to bother with that in the test; I don't think we need to commit to any particular order in the method itself, just like weakly_connected_components doesn't.

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 looks great now Caleb, thanks. I'd still like to put in the output sorting/normalisation into the test if possible - let me know if you're working on it, or I can add it - but beyond that, this is ready to go.

@caleb-johnson
Copy link
Contributor Author

This looks great now Caleb, thanks. I'd still like to put in the output sorting/normalisation into the test if possible - let me know if you're working on it, or I can add it - but beyond that, this is ready to go.

Thanks a lot for the help getting this in. Let me know if you'd like any last minute tweaks

Comment on lines 1359 to 1363
def _min_active_qubit(self, d):
"""Return the minimum index of all active qubits."""
circ = dag_to_circuit(d)
return min({circ.find_bit(inst.qubits[i]).index for inst in circ for i in range(len(inst.qubits))})

Copy link
Member

@jakelishman jakelishman Jul 18, 2023

Choose a reason for hiding this comment

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

I don't think this is quite correct for the case of remove_idle_qubits=True, because it's going to take indices from the cut-down DAGs rather than the big dags, so the indices won't be the same. I think you'll want to get

def sort_key(indices: dict[Qubit, int]):
    def sorter(dag):
        try:
            first_op = next(dag.topological_op_nodes())
        except StopIteration:
            return -1
        return min(indices[q] for q in first_op.qargs)
    return sorter

def test_separable_circuits(self):
    big_dag = ...
    indices = {bit: i for i, bit in enumerate(big_dag)}
    components = [...]
    test = sorted(big_dag.separable_circuits(), key=sort_key(indices))
    expected = sorted(components, key=sort_key(indices))
    self.assertEqual(test, expected)

(or something like this - I just wrote that cold into GitHub)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks again, this worked as-is :)

jakelishman
jakelishman previously approved these changes Jul 18, 2023
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 looks great Caleb, thanks for sticking with it, for the quick updates, and for indulging my paranoia about test outputs changing from underneath us!

jakelishman
jakelishman previously approved these changes Jul 18, 2023
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 just quickly fixed the type-hint resolution problem in the test - no big deal.

@caleb-johnson
Copy link
Contributor Author

This looks great Caleb, thanks for sticking with it, for the quick updates, and for indulging my paranoia about test outputs changing from underneath us!

I was a bit sloppy here picking this back up after so much time. Thanks to you for sticking it out as well, hah :D

jakelishman
jakelishman previously approved these changes Jul 18, 2023
@jakelishman
Copy link
Member

(Don't worry, but there's no need to press "update branch" in 95% of cases when there's no merge conflict - the automerge queue will automatically do it when this PR joins the queue.)

auto-merge was automatically disabled July 19, 2023 00:44

Head branch was pushed to by a user without write access

@jakelishman jakelishman added this pull request to the merge queue Jul 19, 2023
Merged via the queue into Qiskit:main with commit 665f1dd Jul 19, 2023
13 checks passed
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
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Create a class method in DAGCircuit which returns its disconnected components
8 participants