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

Add a converter for QASM 2.0 to 3.0 #243

Merged
merged 8 commits into from
Jun 2, 2023

Conversation

TheGupta2012
Copy link
Member

@TheGupta2012 TheGupta2012 commented May 27, 2023

tags : unitaryhack interface compatibility

Changes:

Adds a new function convert_to_qasm_3 in qbraid/interface/qbraid_qasm/tools.py

Details or comments:

Fixes #223
According to the OPENQASM specification, many constructs of v2.0 are compatible with v3.0. The changes are as follows -

  1. Register declarations have been changed from qreg q[1]; to qubit[1] q; and creg c[1]; to bit[1] c;
  2. Opaque operations are no longer parsed in v3.0
  3. Measure operations are changed from measure q[i] -> c[j]; to c[j] = measure q[i];. This also supports multi-qubit measurement (given the register sizes are the same)
  4. Besides these, the CX gate is no longer a primitive operation. It is implemented using the ctrl @ construct and its definition is readily supplied in the standard header stdgates.inc. This is equivalent to the qelib1.inc header present in v2.0

Some helper functions are used in code to change different constructs from v2.0 to v3.0. I have also included a semantic check using qiskit before converting to v3.0.

One concern I have is about the keywords of v3.0 being used as identifiers in semantically valid v2.0 programs. Does that check need to be incorporated at this level?

Moreover, unit tests for each construct are added

Checklist before merge

  • More unit tests?
  • Misc

General

  • I have read the CONTRIBUTING doc.

  • ALL new features must include unit test to the test directory, codecov will check for any missing test code exist.

  • Ensure that all the test passes.

@TheGupta2012
Copy link
Member Author

I am not sure about the test fail as the unit test failure was present in files unaffected by this commit.

@ryanhill1
Copy link
Member

I am not sure about the test fail as the unit test failure was present in files unaffected by this commit.

@TheGupta2012 Yes, seems like a token is missing which would just be a GitHub actions permissions error. I'll take a look and lyk what I find! Should be an easy fix

@ryanhill1
Copy link
Member

@TheGupta2012 Ok, try syncing with latest commit to main and see if that fixes it. If not can try one other approach

@TheGupta2012
Copy link
Member Author

@TheGupta2012 Ok, try syncing with latest commit to main and see if that fixes it. If not can try one other approach

Hey, thanks @ryanhill1! Seems like that was indeed the issue.

Copy link
Member

@ryanhill1 ryanhill1 left a comment

Choose a reason for hiding this comment

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

Your progress looks great so far! It seems like your code covers all of the conversions for gates from stdgates.inc. As the next step, we'd also like to support conversions of gates from qelib1.inc. Unlike in qasm2, you won't just be able to declare these gates with an "includes" statement. In qasm3, you will have to dynamically create a "custom" gate that implements each one, based on it's definition. Instead of hard-coding each case, you can maybe try to incorporate the above linked file, and read-in the gate definitions as needed as you parse through and convert the qasm2 string. Or, you take your own approach!

Below is some code that you can use to test your solution. To run it, you will have to first install the experimental qiskit qasm3 library:

pip install 'qiskit-terra[qasm3-import]'
from qiskit.qasm3 import loads

from qbraid.interface import circuits_allclose, random_circuit
from qbraid.interface.qbraid_qasm.tools import convert_to_qasm_3

for _ in range(100):
    circuit = random_circuit("qiskit")
    qasm2_str = circuit.qasm()
    qasm3_str = convert_to_qasm_3(qasm2_str)
    circuit_test = loads(qasm3_str)
    assert circuits_allclose(circuit, circuit_test)

@TheGupta2012
Copy link
Member Author

TheGupta2012 commented May 30, 2023

Hi @ryanhill1 , I incorporated your suggestion by building a .qasm file with the QASM 3.0 definitions of the left-out gates from qelib1.inc file. These definitions are added to the QASM 3.0 string before parsing the QASM 2.0 string.

I figured that since these are indeed present in the qelib1.inc, having the definitions inserted beforehand instead of dynamically adding the gate definitions would be more straightforward and require lesser code.

I didn't modify the stdgates.inc file and added it as a lib file as (I think) it is a standard file given out by openqasm community. The gates specifically supported by the quantum experience header are thus added at runtime if they were ever required. This can also be extended to different packages and their standard header definitions.

I am currently experiencing a couple of bugs though.

First, is the fact that expressions containing constants and identifiers are not being parsed correctly by the qiskit.qasm3.loads function, specifically this line. I have raised an issue in the repository and am waiting for a response.

Second, is the failure of some of the random tests which I am yet to inspect more concretely.

@TheGupta2012 TheGupta2012 requested a review from ryanhill1 May 30, 2023 07:57
@ryanhill1
Copy link
Member

ryanhill1 commented May 30, 2023

@TheGupta2012 Awesome, these updates are looking great so far! It seems like the qiskit team has already gotten back to you on the issue you raised: Qiskit/qiskit-qasm3-import#11 (comment). If they're slow pushing the changes on their end, maybe you can separate out the unit tests for the effected gates, and add a @pytest.skip decorator so that we still have the tests for later, but for now know that they require revisions from qiskit to run.

In terms of other random tests errors, if they continue to impact your progress, let me know and I can try to help debug.

@TheGupta2012
Copy link
Member Author

TheGupta2012 commented May 31, 2023

Hi @ryanhill1, I discovered one more bug in terra 😺. Its related to the u0 gate conversion.

Besides that, I have added the unit tests for custom gates and the decorated tests pertaining to the active bugs.

An average of 80% of random tests are passing with some errors in qasm2 parsing.

I saw that although the circuit structures (of the two circuits being compared) looked the same, the names of the gates may be the culprit for circuit inequality. Do you think converting circuits to unitary matrices and then comparing them would help?

If you had time, could you also look at the failures? This was a script that I was using for the same -

import os

import pytest
from qiskit.qasm3 import loads
from qbraid.interface import circuits_allclose, random_circuit
from qbraid.interface.qbraid_qasm.tools import convert_to_qasm_3

correct = 0
excepts = 0
i = 0
for _ in range(100):
    try:
        circuit = random_circuit("qiskit")
        qasm2_str = circuit.qasm()
        qasm3_str = convert_to_qasm_3(qasm2_str)
        circuit_test = loads(qasm3_str)
        eq = circuits_allclose(circuit, circuit_test)
        if eq:
            correct += 1
        else:
            print(f"Failed test {i}")
            print(circuit.draw())
            print(circuit_test.draw())
            i += 1
    except Exception as e:
        print(e)
        print("Qasm 2 string : ")
        for line in qasm2_str.splitlines():
            print(line)
        print()
        excepts += 1
print("Exceptions :", excepts)
print("Correct circuits : ", correct)
print("Incorrect : ", 100 - correct)

@ryanhill1
Copy link
Member

@TheGupta2012 That's a great improvement on the random tests accuracy, good work! As for the remaining inequalities, if you look at the source code for the circuits_allclose function, you'll notice that all it's doing is calculating and comparing the unitaries of both circuits, so the gate naming shouldn't matter.

To verify, you could use the built-in qiskit methods to explicitly compare the unitaries as well:

import numpy as np
from qiskit.quantum_info import Operator

circuit_unitary = Operator(circuit).data
circuit_test_unitary = Operator(circuit_test).data
np.allclose(circuit_unitary, circuit_test_unitary)

although, this may actually lower the accuracy, because it does not account for global phase differences between the two circuits, while the circuits_allclose function allows up to a 1e-7 tolerance.

Have you noticed any pattern as far as the types of circuits / gates that are failing? Another good test could be to restrict to random_circuit("qiskit", num_qubits=1, depth=1) and see if it is failing for the same gate(s) every time. If it is passing all random tests for that setting, then you can move to num_qubits=2, and see if all two qubit gates are passing, etc. Having a better idea of which gate specifically are failing may help diagnose the problem.

@TheGupta2012
Copy link
Member Author

Thanks for the suggestion @ryanhill1 ! I see ~ 95% of tests pass when testing for single qubit gates and depth = 1.
All the failed tests correspond to the u gate, as well as any gate utilizing its definition. The source for this may indeed be the global phase as the native U gate for qasm3 differs by a global phase from the qasm2 implementation.

I have raised an issue regarding this problem on the repository and I hope it gets resolved soon.

@ryanhill1
Copy link
Member

@TheGupta2012 Ok, that sounds good. Same as before, if they're slow to make the change, then we can just note the inconsistency and skip the impacted test cases.

@TheGupta2012
Copy link
Member Author

Hi @ryanhill1 , the randomized test is now passing after excluding the problematic gates which we were discussing above. I have also added a note in the docstring of the test to reference the discussion about how the problematic gate list was generated.

Let me know if any changes are required!

Copy link
Member

@ryanhill1 ryanhill1 left a comment

Choose a reason for hiding this comment

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

These changes look great! Just two final very small, nit-picky updates, and then everything should be good to go. Thanks for your hard work and diligence on this issue 🎉

@ryanhill1 ryanhill1 merged commit 7477b6e into qBraid:main Jun 2, 2023
@TheGupta2012
Copy link
Member Author

TheGupta2012 commented Jun 2, 2023

Hi @ryanhill1 , so sorry for that CI failure, turns out I forgot to update the requirements.txt file with the qiskit_qasm3_import. Should I open another PR for that or you'll bump the requirements?

@ryanhill1
Copy link
Member

@TheGupta2012 No problem, small fix I got it!

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.

OpenQASM 2.0 --> OpenQASM 3.0 conversion function
2 participants