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

[WIP] Add 2q fractional gates to the UnitarySynthesis tranpiler pass #13568

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

ShellyGarion
Copy link
Member

Summary

close #13320

With the introduction of new 2q-fractional gates, such as RZZGate as part of the QPU, we add them to the UnitarySynthesis transpiler pass.
https://www.ibm.com/quantum/blog/fractional-gates

Details and comments

@ShellyGarion ShellyGarion added the mod: transpiler Issues and PRs related to Transpiler label Dec 16, 2024
@ShellyGarion ShellyGarion added this to the 2.0.0 milestone Dec 16, 2024
@ShellyGarion ShellyGarion requested a review from ElePT December 16, 2024 11:06
@ShellyGarion ShellyGarion requested review from alexanderivrii and a team as code owners December 16, 2024 11:06
@qiskit-bot
Copy link
Collaborator

One or more of the following people are relevant to this code:

  • @Qiskit/terra-core
  • @levbishop

@ElePT ElePT self-assigned this Dec 16, 2024
@coveralls
Copy link

coveralls commented Dec 16, 2024

Pull Request Test Coverage Report for Build 12455380316

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

  • 24 of 24 (100.0%) changed or added relevant lines in 3 files are covered.
  • 24 unchanged lines in 6 files lost coverage.
  • Overall coverage increased (+0.007%) to 88.968%

Files with Coverage Reduction New Missed Lines %
qiskit/circuit/library/quantum_volume.py 1 93.18%
crates/accelerate/src/unitary_synthesis.rs 1 92.2%
crates/qasm2/src/expr.rs 1 94.02%
crates/qasm2/src/lex.rs 4 91.98%
crates/qasm2/src/parse.rs 6 97.62%
qiskit/quantum_info/states/stabilizerstate.py 11 93.89%
Totals Coverage Status
Change from base Build 12296775949: 0.007%
Covered Lines: 79447
Relevant Lines: 89298

💛 - Coveralls

@@ -107,7 +129,12 @@ def _choose_kak_gate(basis_gates):
"""Choose the first available 2q gate to use in the KAK decomposition."""
Copy link
Contributor

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]

Copy link
Contributor

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).

Copy link
Member Author

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.

Copy link
Contributor

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).

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 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).

Copy link
Contributor

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

done in eeff4cd

Copy link
Contributor

@ElePT ElePT left a 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."""
Copy link
Contributor

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).

Comment on lines 293 to 295
def __call__(
self, unitary: Operator | np.ndarray, approximate=False, use_dag=False, atol=DEFAULT_ATOL
) -> QuantumCircuit:
Copy link
Contributor

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?

Copy link
Member Author

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)

Copy link
Contributor

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:

Suggested change
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.

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 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)

Comment on lines 181 to 191
# 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)
Copy link
Contributor

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?

Copy link
Member Author

@ShellyGarion ShellyGarion Dec 17, 2024

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)?

Copy link
Member Author

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.

Copy link
Contributor

@ElePT ElePT Dec 18, 2024

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).

Copy link
Contributor

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?

Copy link
Contributor

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!

Copy link
Member Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
mod: transpiler Issues and PRs related to Transpiler
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add TwoQubitControlledUDecomposer to the UnitarySynthesis tranpiler pass
5 participants