-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Update Split2QUnitaries to handle SWAP unitary gates #13531
Conversation
One or more of the following people are relevant to this code:
|
Pull Request Test Coverage Report for Build 13158018594Warning: 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
💛 - Coveralls |
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.
Thanks for doing this, from a quick glance the code looks good (but I only quickly skimmed it on my phone, I'll do a deeper reviewer later). But I had some quick comments on the tests to start.
def test_2q_swap_with_1_qubit_gates(self): | ||
"""Test that a 2q unitary matching a swap gate with 1-qubit gates before and after is correctly processed.""" | ||
qc = QuantumCircuit(2) | ||
qc.h(0) | ||
qc.x(1) | ||
qc.swap(0,1) | ||
qc.sx(0) | ||
qc.sdg(1) | ||
qc.global_phase += 1.2345 | ||
qc_split = QuantumCircuit(2) | ||
qc_split.append(UnitaryGate(Operator(qc)), [0, 1]) | ||
|
||
pm = PassManager() | ||
pm.append(Collect2qBlocks()) | ||
pm.append(ConsolidateBlocks()) | ||
pm.append(Split2QUnitaries()) | ||
res = pm.run(qc_split) | ||
res_op = Operator.from_circuit(res, final_layout=res.layout.final_layout) |
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.
Can you add a test that has this in it as part of a larger circuit that runs through the full transpilation pipeline? I want to make sure that we validating that we're tracking and composing the final permutations correctly.
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.
Done. Also added a smaller test which does non-trivial swapping (with swap gates removed using the elide permutation pass) before the unitary test which surfaced a possible bug in the layout composition phase. We need to discuss whether in this phase we should compose on the existing layout or (as was the case before my change) on its inverse.
self.assertTrue(expected_op.equiv(res_op)) | ||
self.assertTrue( | ||
matrix_equal(expected_op.data, res_op.data, ignore_phase=False) | ||
) |
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.
Can we add an assertion here that swap
has been removed from the output?
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 self.assertNotIn("swap", res.count_ops())
but I don't think it's enough - there's also the possibility that we didn't split the unitary at all (note that it's also not being checked in the tests relating to the original split unitary code).
Any idea how to easily verify that the unitary was removed? Note that just using count_ops
is tricky since we have a unitary both before (as the 2-qubit unitary) and after (as two individual 1-qubit unitaries). I can compare the count itself (1 before vs 2 after) but it feel less robust than it should.
If I recall correctly, we don't run the |
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.
Thanks for your contribution. I have a few minor comments.
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.
Overall this looks excellent thanks for doing this, this looks about ready now. I just have a few small comments and questions inline. The biggest one I think is over how/when you're thinking we should integrate this new feature into the preset pass managers. It's fine if you'd like to do that in a separate follow-up PR to keep this PR to a single logical change (that's probably preferable actually). I just wanted to understand your thinking on that.
params: (!new_op.params.is_empty()).then(|| Box::new(new_op.params)), | ||
extra_attrs: new_op.extra_attrs, | ||
#[cfg(feature = "cache_pygates")] | ||
py_op: std::sync::OnceLock::new(), |
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.
You do have the py op available for caching here since you are generating this via python. But in this case it doesn't actually matter too much because this is always going to be a PyGate
and if the cache is None
the python object reference will just be taken from that.
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.
So, do you think something needs to change here?
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 think this is fine as is. This was just more my idle musings.
|
||
(new_dag, qubit_mapping) = result | ||
input_qubit_mapping = {qubit: index for index, qubit in enumerate(dag.qubits)} | ||
self.property_set["original_layout"] = Layout(input_qubit_mapping) |
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.
Does this behave correctly if we run elide permutations first? I realize this is just a straight copy of what elide permutations is doing, but I'm wondering if we need to check that we're not doing multiple permutations before applying the layout or do some other storing if we run these together.
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.
It should work - in theory. in split_2q_unitaries.py
we have
if current_layout := self.property_set["virtual_permutation_layout"]:
self.property_set["virtual_permutation_layout"] = new_layout.compose(
current_layout, dag.qubits
)
And compose
works, if I recall correctly. However, this is not covered by the current test suite, and that's one of the things I wanted to ask about - should the "joint" test be placed in TestSplit2QUnitaries
or perhaps a different location? What flows should we check, other than adding Split2QUnitaries
before and after ElidePermutations
?
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.
You can put it in TestSplit2QUnitaries
that's fine if the intent is to validate the Split2QUnitaries
logic is correct that's fine. But having the coverage in general is more important than where it's located specifically.
releasenotes/notes/update-split-2q-unitaries-with-swap-557a1252e3208257.yaml
Outdated
Show resolved
Hide resolved
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 think the PR looks good now, thanks for all the work on it. I'll hold off on adding it to the merge queue if you want to push the extra test case in this PR. If you'd rather add it to a follow up PR though that's fine too since I think we have indirect coverage of it now that we're running with the new option in the preset pass manager. Having a direct test of the combination of two permutation passes back to back would be good to have direct validation though.
Thanks Matthew. I'll try adding something useful tomorrow and see if I succeed. |
This also reworks the logic of the function to be consistent with the new structure for UnitaryGate defined in rust.
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 pushed up a fix for the merge conflicts caused by using the rust native UnitaryGate in the pass. As part of that process I caught a bug in the indexing in the pass. So I'm going to merge this without that one test. I'll open a tracking issue to add the test coverage so you can push a new PR when you have the test ready.
* Update Split2QUnitaries to handle SWAP unitaries as well * Linting, additional test, bugfix in layout composition * Docstring updates * Add a flag for toggling swap split * Changes according to PR review * Fix indexing in pass --------- Co-authored-by: Matthew Treinish <mtreinish@kortar.org>
Summary
Updates the
Split2QUnitaries
transpiler pass to handle 2-qubit unitary gates which can be decomposed as aswap
gate and 1-qubit gates.Closes #13476
Details and comments
This PR extends the
Split2QUnitaries
in the following manner:Split2QUnitaries
performs, it checks whether any unitary encountered is a SWAP gate.Split2QUnitaries
) and instead of adding SWAP gates, performs a virtual swap by dynamically changing the qubit layout (affecting subsequent gates and the final layout) similar to what theElidePermutations
does.Since step 2 requires generating a new DAG (as opposed to what
Split2QUnitaries
does to identity-equivalent unitary gates), it is not performed during the initial pass, only if SWAP gates were detected.