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 converting Rxx and similar Qiskit gates #2579

Merged
merged 11 commits into from
Nov 25, 2024

Conversation

gluonhiggs
Copy link
Contributor

@gluonhiggs gluonhiggs commented Nov 17, 2024

Description

Resolves issue #2578

Basically, the error happens technically due to the fact that rzz is not recognized by QASM.
In general, this can also happen to other gates that are pre-built in any framework but not existing in QASM.
So, the idea is that we should decompose() some parts of the argument circuit such that these gate are more recognizable to QASM, and then decompose() one more time to obtain the decomposition by the basis gates.

My original thought was:

Basically, the error happens technically due to the fact that rzz is not recognized by QASM.
In general, this can also happen to other gates that are pre-built in any framework but not existing in QASM.
So, the idea is that we should decompose() the argument circuit to semi-basic gate using QFT such that these gate are recognizable to QASM, and then decompose() one more time to obtain the decomposition by the basis gates. Turns out it was not working even passing all tests.


License

  • I license this contribution under the terms of the GNU GPL, version 3 and grant Unitary Fund the right to provide additional permissions as described in section 7 of the GNU GPL, version 3.

Before opening the PR, please ensure you have completed the following where appropriate.

  • I added unit tests for new code.
  • I used type hints in function signatures.
  • I used Google-style docstrings for functions.
  • I updated the documentation where relevant. no doc updated.
  • Added myself / the copyright holder to the AUTHORS file

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Hello @gluonhiggs, thank you for submitting a PR to Mitiq! We will respond as soon as possible, and if you have any questions in the meantime, you can ask us on the Unitary Fund Discord.

Copy link

codecov bot commented Nov 17, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 98.72%. Comparing base (edeede6) to head (828fdf2).
Report is 2 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #2579   +/-   ##
=======================================
  Coverage   98.72%   98.72%           
=======================================
  Files          92       92           
  Lines        4168     4169    +1     
=======================================
+ Hits         4115     4116    +1     
  Misses         53       53           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.


🚨 Try these New Features:

@@ -255,6 +255,9 @@ def from_qiskit(circuit: qiskit.QuantumCircuit) -> cirq.Circuit:
# Try to decompose circuit before running
# This is necessary for converting qiskit circuits with
# custom packaged gates, e.g., QFT gates
circuit = circuit.decompose(
gates_to_decompose=["u1", "u2", "u3", "cx", "rx", "ry"]
Copy link
Member

Choose a reason for hiding this comment

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

First, thanks @gluonhiggs for the quick fix!
I am not sure about this line where you are decomposing simple one-qubit gates such as "cx", "rx", "ry". Those gates are supported by Mitiq/Cirq and we risk to over-decompose the circuit.

I understand that tests are passing, but this change may unnecessarily increase the depth of the output circuit and this is a problem for the efficiency of the quantum computation. For example, if you input a short qiskit circuit made by a single "rzz" gate and many "cx", "rx", "ry" , Mitiq would decompose all gates producing an extremely long output circuit.

I don't know if it works, but I would suggest,it after the existing line circuit=circuit.decompose(),
to add something like this:

circuit = circuit.decompose(gates_to_decompose=["rzz", "rzx", "rxx"], reps=10)

In this way, Mitiq will first apply a general first decomposition step as usual.
Afterwards, if there are still "rzz", "rzx", "rxx" gates left in the circuit, it will simplify only those specific gates.

What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

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

For issues such as #2578 and #558, we should just find the maximum set of gates that are supported by both Qiskit's QASM2 serializer and Cirq's QASM2 parser, and transpile to that set. @andreamari wdyt?

Copy link
Contributor Author

@gluonhiggs gluonhiggs Nov 18, 2024

Choose a reason for hiding this comment

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

@cosenal I thought about that and tried to transpile not to the maximum set of the common gates, but to the set of the gates that can be used to construct the others, i.e., ["u1", "u2", "u3", "cx", "rx", "ry", "rz"], then decompose(). I also thought that this would be efficient because transpile comes with optimization_method. However, the same error still happens. I even tried with a broader set, e.g., ["u3", "u2", "u1", "cx", "id", "x", "y", "z", "h", "s", "t", "rx", "ry", "rz"], but nothing has changed. Generally, I think it would be hard to determine the set because there will be more n-qubit gates, right?
@andreamari If we choose the set gate to decompose like ["rzz", "rzx", "rxx"], I'm wonder if we also have to consider other combinations of x, y, and z here like ryy, ryz, and so on? Because I tried to replace rzz by ryy in the circuit (in the case ["rzz", "rzx", "rxx"]), there will be an error.

test_qc1 = QuantumCircuit(2)
test_qc1.rx(0.1, 0) 
test_qc1.ryy(0.1, 0, 1)
print(test_qc1)
print(convert_to_mitiq(test_qc1))

And i think over decompose will happen if we add more rii gates (where i in {x, y, z})?

Copy link
Contributor

Choose a reason for hiding this comment

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

What I am proposing is something like

circuit = qiskit.transpile(
    circuit,
    basis_gates=QASM2_GATE_SET,
    optimization_level=0,       
)
 mitiq_circuit = from_qasm(qasm2.dumps(circuit))

without the decompose(), because that would bring the gate set to another set that may not be supported by QASM serializer/parser.

I tried with QASM2_GATE_SET = ["u1", "u2", "u3", "cx", "rx", "ry", "rz"] and it seems to work fine. I don't think that's the maximal set though. Having the maximal QASM-supported set there is the best we can do. And something smaller than the maximal will be sub-optimal when it comes to the size of the output circuit (as Andrea pointed out.)

Copy link
Contributor Author

@gluonhiggs gluonhiggs Nov 19, 2024

Choose a reason for hiding this comment

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

Yeah. It works. Let me have some tests. @andreamari what do you think? I think we can increase the level of optimization or expose the option to the interface.
Update 1: @cosenal i have tried different variants of this approach, but some tests can't be passed.
Update 2: Because transpile is for transforming a circuit to satisfy specific constraints including hardware, I don't think we should use transpile here. To the best of my knowledge, decompose is just a "pure" break of circuits to some basis gates, and to optimize the decomposition (which is generally a difficult problem, even though we have algorithms to do that for some circuits), we either should choose a good set gates_to_decompose globally optimal (what we got so far is "u1", "u2", "u3", "cx", "rx", "ry"), or we suggest a change on qiskit repo. I'd love to hear your thoughts on this.

Copy link
Contributor Author

@gluonhiggs gluonhiggs Nov 20, 2024

Choose a reason for hiding this comment

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

@andreamari I can distinguish gates_to_decompose and basis_gates. Perhaps, the way I expressed my thoughts was not clear at some point. The problem here is for some reasons, when I tried to decompose the "bad gates" (different lists of rii with this reference), the error similar to #2578 (the original issue) still happens.
@cosenal Here is the short test summary from pytest:

================================================================================= short test summary info =================================================================================
FAILED mitiq/interface/mitiq_qiskit/tests/test_conversions_qiskit.py::test_convert_with_qft - AssertionError: assert False
FAILED mitiq/zne/scaling/tests/test_folding.py::test_convert_qiskit_to_mitiq_circuit_conversion_error - Failed: DID NOT RAISE <class 'mitiq.interface.conversions.CircuitConversionError'>
FAILED mitiq/zne/scaling/tests/test_folding.py::test_folding_circuit_conversion_error_qiskit[fold_gates_at_random] - Failed: DID NOT RAISE <class 'mitiq.interface.conversions.CircuitConversionError'>
FAILED mitiq/zne/scaling/tests/test_folding.py::test_folding_circuit_conversion_error_qiskit[fold_global] - Failed: DID NOT RAISE <class 'mitiq.interface.conversions.CircuitConversionError'>

Should I paste the whole log here or move the discussion to discord?

Here are the tests failed:
Solution 1:
mitiq/interface/mitiq_qiskit/tests/test_conversions_qiskit.py::test_convert_with_qft
mitiq/zne/tests/test_zne.py::test_execute_zne_on_qiskit_circuit_with_QFT

Solution 2:
mitiq/interface/mitiq_qiskit/tests/test_conversions_qiskit.py::test_convert_with_qft
mitiq/zne/scaling/tests/test_folding.py::test_convert_qiskit_to_mitiq_circuit_conversion_error
mitiq/zne/scaling/tests/test_folding.py::test_folding_circuit_conversion_error_qiskit[fold_gates_at_random]
mitiq/zne/scaling/tests/test_folding.py::test_folding_circuit_conversion_error_qiskit[fold_global]
The error log is too lengthy that I can't even paste to discord.

Copy link
Member

Choose a reason for hiding this comment

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

@andreamari Why would we need repeated decompose() if we add all gates we don't like in gates_to_decompose?

I think you are right, if all bad gates are in gates_to_decompose you probably don't need to also keep the existing line circuit.decompose() (without options).

However I think you would still need the reps=... option, since it ensures that potential bad gates that may be created during the decomposition process are further decomposed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

FYI: I have updated the code and it seems to work fine thanks to your suggestion @andreamari
The story is:
I fell in the 2nd approach that used transpile to transform the circuits to some pre-declared basis_gates. I had tried dozens of combinations of the supported gates to form basis_gates. In the end, I realized that a very good choice of basis_gates passing almost all test cases but still can cause transpile to fail these tests

mitiq/zne/scaling/tests/test_folding.py::test_convert_qiskit_to_mitiq_circuit_conversion_error
mitiq/zne/scaling/tests/test_folding.py::test_folding_circuit_conversion_error_qiskit[fold_gates_at_random]
mitiq/zne/scaling/tests/test_folding.py::test_folding_circuit_conversion_error_qiskit[fold_global]

because transpile is too strong that can transform any circuit of both supported and unsupported gates to a circuit constructed from basis_gates. In the context of mitiq, to my understanding, we do not implement the noise mitigation for unsupported gates (or custom gates in the failed test) directly. Therefore, we expect that convert_to_mitiq will raise error in case there are custom gates, instead of transforming the circuit normally. So, this is too much. We should not use transpile here.

Back to the 1st approach, when these tests are failed

mitiq/interface/mitiq_qiskit/tests/test_conversions_qiskit.py::test_convert_with_qft
mitiq/zne/tests/test_zne.py::test_execute_zne_on_qiskit_circuit_with_QFT

so I tried to add "QFT" to the gates_to_decompose and it's worked since then. Previously, instead of using "QFT", I used "qft". That's why it didn't work.

Copy link
Contributor

Choose a reason for hiding this comment

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

@andreamari

since it ensures that potential bad gates that may be created during the decomposition process

I would be surprised if gates from the gates_to_decompose reappear during decomposition. Something we can ask to Qiskit folks.

Copy link
Contributor

Choose a reason for hiding this comment

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

@gluonhiggs if we go with the approach I see now in the PR (good that it works), we could do the decomposition directly instead of doing it as a fallback when the conversion fails.

@gluonhiggs gluonhiggs force-pushed the resolve-issue2578 branch 2 times, most recently from 6ab8365 to 4d07521 Compare November 20, 2024 04:24
@cosenal cosenal changed the title Resolve issue2578 Fix converting Rxx and similar Qiskit gates Nov 20, 2024
circuit = circuit.decompose()
gates_to_decompose = ["rxx", "rzz", "rzx", "ryy", "QFT"]
circuit = circuit.decompose(
gates_to_decompose=gates_to_decompose, reps=10
Copy link
Contributor

Choose a reason for hiding this comment

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

from what I understand of decompose, we shouldn't need reps=10 here, see discussion with @andreamari in the other comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes. I agree!

try:
mitiq_circuit = from_qasm(qasm2.dumps(circuit))
except QasmException:
# Try to decompose circuit before running
# This is necessary for converting qiskit circuits with
# custom packaged gates, e.g., QFT gates
circuit = circuit.decompose()
gates_to_decompose = ["rxx", "rzz", "rzx", "ryy", "QFT"]
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: this should be capitalized as we want to treat it as a constant

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have updated the code. Thank you!

Copy link
Contributor

@cosenal cosenal left a comment

Choose a reason for hiding this comment

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

LGTM. Thanks for iterating on it!

@cosenal cosenal requested a review from andreamari November 24, 2024 15:10
Copy link
Member

@andreamari andreamari left a comment

Choose a reason for hiding this comment

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

One could consider adding to GATES_TO_DECOMPOSE also the gates mentioned in #1073.
But maybe this would go too much beyond the scope of this PR, that is fixing #2578.

So, LGTM!
And thanks everyone for your help!

@cosenal cosenal merged commit be6a76c into unitaryfund:main Nov 25, 2024
16 checks passed
@purva-thakre purva-thakre added this to the 0.42.0 milestone Nov 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants