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

Fix handling of multiple available target ops in UnitarySynthesis #7795

Merged
merged 15 commits into from
Mar 30, 2022

Conversation

mtreinish
Copy link
Member

Summary

We recently added support for working natively with a Target to the
UnitarySynthesis pass in #7775. However, in that PR we took a naive
approach to dealing with the KAK gate for decomposition which would
result in the pass not correctly generating an output circuit.
Previously, it would just pick the first KAK gate that was present in
the target whether or not it was the best choice available. There was
also a bug in #7775 where it would only look at the cx error rate
instead of the error rate for the selected kak gate. This bug could
result in cases of the output of the unitary synthesis would be
considered invalid by later passes because it didn't properly respect
the constraints of the backend. This commit fixes these issues by
instead considering all combinations of 1q and kak gate in the target
and picking the combination with the lowest error rate available on the
selected qubits. Then the unit testing for the pass is expanded to cover
the use of the target to ensure we're testing the full path with a
target specified.

Details and comments

We recently added support for working natively with a Target to the
UnitarySynthesis pass in Qiskit#7775. However, in that PR we took a naive
approach to dealing with the KAK gate for decomposition which would
result in the pass not correctly generating an output circuit.
Previously, it would just pick the first KAK gate that was present in
the target whether or not it was the best choice available. There was
also a bug in Qiskit#7775 where it would only look at the cx error rate
instead of the error rate for the selected kak gate. This bug could
result in cases of the output of the unitary synthesis would be
considered invalid by later passes because it didn't properly respect
the constraints of the backend. This commit fixes these issues by
instead considering all combinations of 1q and kak gate in the target
and picking the combination with the lowest error rate available on the
selected qubits. Then the unit testing for the pass is expanded to cover
the use of the target to ensure we're testing the full path with a
target specified.
@mtreinish mtreinish added this to the 0.20 milestone Mar 21, 2022
@mtreinish mtreinish requested a review from a team as a code owner March 21, 2022 16:10
@coveralls
Copy link

coveralls commented Mar 21, 2022

Pull Request Test Coverage Report for Build 2067085606

  • 88 of 96 (91.67%) changed or added relevant lines in 1 file are covered.
  • 3 unchanged lines in 1 file lost coverage.
  • Overall coverage increased (+0.005%) to 83.754%

Changes Missing Coverage Covered Lines Changed/Added Lines %
qiskit/transpiler/passes/synthesis/unitary_synthesis.py 88 96 91.67%
Files with Coverage Reduction New Missed Lines %
qiskit/pulse/library/waveform.py 3 89.36%
Totals Coverage Status
Change from base Build 2067083675: 0.005%
Covered Lines: 53225
Relevant Lines: 63549

💛 - Coveralls

The test assertions in the new tests were previously flawed and would
evaluate as True if there were no gates for the in the circuit we
were checking. This commit fixes the tests so we're actually
asserting things correctly.
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'm not 100% sure I understood why various bits were necessary, so I might have missed things / said things that don't make much sense.

This commit reorganizes the internal helper functions to split the
target and non-target paths to be completely separate. Then the target
path adds a cache for the 2q decomposer to use for the specified qubits
so we don't have to do the lookup more than once on subsequent runs.
@mtreinish mtreinish requested a review from jakelishman March 29, 2022 16:52
@mtreinish mtreinish removed their assignment Mar 29, 2022
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.

Pretty much looks solid to me - it's certainly easier to read with things split out like this a bit more.

Comment on lines +411 to +414
def __init__(self):
super().__init__()
self._decomposer_cache = {}

Copy link
Member

Choose a reason for hiding this comment

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

For another PR: I guess we should document what the lifetime of an instance of a plugin class is for the interface - the implementation is that it lives for the lifetime of a pass of the outer UnitarySynthesis, which is one run over the DAG, but we should be clear about it so others can cache like this if they want to. I also wonder if we should consider re-organising the interface such that the free-form configuration dictionary gets passed to the __init__, not the run (or both).

Copy link
Member Author

Choose a reason for hiding this comment

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

Well it could be more than one run over a dag, it depends specifically how the pass manager is constructed and used. For example, with transpile() it could be used multiple times on each iteration of the optimization loop (either in opt level 3 or if the translation method is set to synthesis). This means the plugin instance for that synthesis pass will be called multiple times depending on how many iterations the optimization loop needs. (which is why I added the cache because it could make a noticeable difference in those cases).

That being said I agree, we might need to revisit how we manage the flow of data between the pass and plugins. When we originally designed it plugins were intended to be more of a black box stateless function, you give it a unitary and it gives you back a dagcircuit. But in practice so far I think all the plugins (now with this pr, previously default was stateless) have some level of state and/or caching they do out of band. We should open an issue on it to figure out how to evolve the interface to support this.

Copy link
Member

Choose a reason for hiding this comment

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

Opened #7843 to track this.

ajavadia
ajavadia previously approved these changes Mar 30, 2022
Comment on lines +46 to +48
"rxx": RXXGate(pi / 2),
"ecr": ECRGate(),
"rzx": RZXGate(pi / 4), # typically pi/6 is also available
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 hope to fix these magic angle values with Target? It would require to not rely on string names but rather whatever RZX(theta) instruction that the backend has available.

Copy link
Member Author

Choose a reason for hiding this comment

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

I opened an issue about this in #7797 we need to do an overhaul of how we look for gates in the transpiler. I have PR open on start this with: #7807 which gives us a boolean check if a operation class and parameter value are supported by a target, but we also need a lookup method to get the name from a class and parameter value too. Once those are in place then we'll need to update all the places which are doing things like this to instead check the target and use that instead (which will require a good chunk of work I suspect).

Comment on lines 686 to 690
backend = FakeMumbaiV2()
synth_pass = UnitarySynthesis(target=backend.target)
tqc = synth_pass(circ)
tqc_index = {qubit: index for index, qubit in enumerate(tqc.qubits)}
self.assertGreaterEqual(len(tqc.get_instructions("rzx")), 1)
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 FakeMumbaiV2 has both RZX(pi/4) and RZX(pi/6)... do both of them get used here or because we have hardcoded pi/4 in the KAK_GATE_NAMES, only pi/4 gets used?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think they actually both get used but I think it's actually a bug in the XXDecomposer though, because we never tell it anything about which specific angles are available it just assumes that pi/6 is available.

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'm happy with this from my side. I'll not tag as automerge in case Ali wants to look at it again after the changes made in response to his review.

@mergify mergify bot merged commit 25be8a2 into Qiskit:main Mar 30, 2022
@mtreinish mtreinish deleted the add-unitary-synth-tests branch March 31, 2022 11:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants