-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
[WIP] Add 2q fractional gates to the UnitarySynthesis
tranpiler pass
#13568
base: main
Are you sure you want to change the base?
Conversation
One or more of the following people are relevant to this code:
|
Pull Request Test Coverage Report for Build 12455380316Warning: 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 |
@@ -107,7 +129,12 @@ def _choose_kak_gate(basis_gates): | |||
"""Choose the first available 2q gate to use in the KAK decomposition.""" |
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.
Question: It seems we are not choosing the element according to some order, but using set pop
, which is random, leading to nondeterministic behavior. Can this be avoided (or already avoided and I'm missing something)? e.g.
kak_gate = sorted([KAK_GATE_PARAM_NAMES[kak_gate_param] for kak_gate_params in kak_gates_params])[0]
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.
This is a good point @gadial, I had always assumed that this randomness was a reflection of the fact that there can be several equally valid gates. And if that's case, I wonder if we should favor the one that comes first in alphabetical order? At the same time, I am not sure how conscious some of these decisions were. There are other parts of the code (the parts that were migrated to Rust) where we actually rely on insertion order when there are several valid decompositions, so at that point I guess it's as arbitrary as sorting alphabetically. It might be also worth noting that these cases don't come up often in real scenarios because we rarely see a basis set with more than one 2q gate (although it can definitely happen).
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 the comment. I'll look into it.
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.
Thank you @ElePT. My approach is usually to prefer the arbitrary to the nondeterminisitic, since nondeterministic behavior makes testing more difficult (since it's harder to reproduce problems) but also less resilient (since we are only testing the conditions generated nondeterminisically on the current testing machines, whereas a future version of Python might behave differently).
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 agree with @gadial's comment. If the user provides several 2q KAK gates, this "pop" just randomly chooses one of them. We would probably want a more deterministic behavior (perhaps favoring more "commonly" used 2q gates).
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 that sounds reasonable, it helps with benchmarking and bug-catching.
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 in eeff4cd
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.
Hi @ShellyGarion! thanks for opening the PR, I left a couple of preliminary comments. I hope they make sense :)
@@ -107,7 +129,12 @@ def _choose_kak_gate(basis_gates): | |||
"""Choose the first available 2q gate to use in the KAK decomposition.""" |
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.
This is a good point @gadial, I had always assumed that this randomness was a reflection of the fact that there can be several equally valid gates. And if that's case, I wonder if we should favor the one that comes first in alphabetical order? At the same time, I am not sure how conscious some of these decisions were. There are other parts of the code (the parts that were migrated to Rust) where we actually rely on insertion order when there are several valid decompositions, so at that point I guess it's as arbitrary as sorting alphabetically. It might be also worth noting that these cases don't come up often in real scenarios because we rarely see a basis set with more than one 2q gate (although it can definitely happen).
def __call__( | ||
self, unitary: Operator | np.ndarray, approximate=False, use_dag=False, atol=DEFAULT_ATOL | ||
) -> QuantumCircuit: |
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 wonder what's the motivation for this change. Are approximate
or use_dag
used in the code? Could it be an oversight?
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.
when calling the decomposer2q
later in the code it assumes to have both parameters, see e.g
synth_circ = decomposer2q(su4_mat, approximate=approximate, use_dag=True) |
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 see. I keep forgetting that we still have python code for the target aware case (this is kept in case someone uses a non-default plugin). In that case I think we should respect the keyword-only argument:
def __call__( | |
self, unitary: Operator | np.ndarray, approximate=False, use_dag=False, atol=DEFAULT_ATOL | |
) -> QuantumCircuit: | |
def __call__( | |
self, unitary: Operator | np.ndarray, approximate=False, use_dag=False, *, atol=DEFAULT_ATOL | |
) -> QuantumCircuit: |
I also have realized that the TwoQubitControlledUDecomposer
is not documented. If we are going to add it to the default pipeline, I believe we should, as right now it's not part of the public interface. So we should probably make sure to add it to the API docs with the other decomposers and add a release note listing the class as a new feature.
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 plan to add TwoQubitControlledUDecomposer
to the API docs following this PR (as I may need to update the API as part of adding it to the transpiler pass)
# if isinstance(kak_gate, RZXGate): | ||
# backup_optimizer = TwoQubitBasisDecomposer( | ||
# CXGate(), | ||
# basis_fidelity=basis_fidelity, | ||
# euler_basis=euler_basis, | ||
# pulse_optimize=pulse_optimize, | ||
# ) | ||
# decomposer2q = XXDecomposer(euler_basis=euler_basis, backup_optimizer=backup_optimizer) | ||
|
||
if kak_gate in KAK_GATE_PARAM_NAMES.values(): | ||
decomposer2q = TwoQubitControlledUDecomposer(kak_gate) |
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.
Double checking our discussion from earlier in the week: for the target-aware code we discussed adding a third check (apart from is_controlled
or is_supercontrolled
) to differentiate cases for the 3 available decomposers: TwoQubitDecomposer
, XXDecomposer
, and TwoQubitControlledUDecomposer
. However, for the Python code (just basis gates), it looks like we are simply substituting the XXDecomposer
for the TwoQubitControlledUDecomposer
. Is this the plan?
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.
In the case of basis gates, I'm not sure if we can have 3 options or only 2 (this is why this code is only commented out and not removed yet).
If a user provides the basis gates [rzz, rz, ry]
how can we tell if they want an RZZGate
with an arbitrary angle or an RZZGate
with a fixed angle (and if so - which fixed value)?
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.
Another comment: I ran the tests in qiskit/test/python/transpiler/test_unitary_synthesis.py
(main branch), and couldn't find any test that goes into the following "if" statement (lines 181-188 above), so I wonder if the XXDecomposer
here has ever been used.
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 am not surprised, the coverage of these tests is not good because a lot of the tests are in the synthesis folder and not testing the transpilation pipeline, but calling the synthesis methods directly. Maybe this is a good opportunity to do some test refactoring before merging this PR. During the Rust migration I also noticed there weren't any tests for the UnitarySynthesis
class that used the QSD method with a target and had to add a couple of them. I think it would make sense to ensure that we have a couple of tests per decomposer, and that there are sufficient tests for the target (HW-aware) path (this I could do in a separate PR because I didn't have time to look more into it during the Rust migration).
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.
If a user provides the basis gates [rzz, rz, ry] how can we tell if they want an RZZGate with an arbitrary angle or an RZZGate with a fixed angle?
I think this is up to us to decide and document the behavior change. I am not sure how many users actually rely or expect the fixed angle?
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.
FYI, I started the test cleanup with this PR: #13582, that is not much but I think can be helpful for future development. Let me know if you have any thoughts on it!
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.
Another question is what to expect when given a parametrized gate in the target? see e.g this test.
Currently, we replace the parametrized gates by gates with a constant angle, without even checking if this angle is actually calibrated in the target:
def _replace_parameterized_gate(op): |
Again, I could not find any test that checks the XXDecomposer
with a given target, namely the tests don't go into here.
Summary
close #13320
With the introduction of new 2q-fractional gates, such as
RZZGate
as part of the QPU, we add them to theUnitarySynthesis
transpiler pass.https://www.ibm.com/quantum/blog/fractional-gates
Details and comments