Skip to content

Preliminary support for parsing OpenQASM 3.0 #6797

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

Merged
merged 6 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions cirq-core/cirq/contrib/qasm_import/_lexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,28 @@ class QasmLexer:
def __init__(self):
self.lex = lex.lex(object=self, debug=False)

literals = "{}[]();,+/*-^"
literals = "{}[]();,+/*-^="

reserved = {
'qubit': 'QUBIT',
'qreg': 'QREG',
'bit': 'BIT',
'creg': 'CREG',
'measure': 'MEASURE',
'if': 'IF',
'->': 'ARROW',
'==': 'EQ',
}

tokens = ['FORMAT_SPEC', 'NUMBER', 'NATURAL_NUMBER', 'QELIBINC', 'ID', 'PI'] + list(
reserved.values()
)
tokens = [
'FORMAT_SPEC',
'NUMBER',
'NATURAL_NUMBER',
'STDGATESINC',
'QELIBINC',
'ID',
'PI',
] + list(reserved.values())

def t_newline(self, t):
r"""\n+"""
Expand Down Expand Up @@ -83,14 +91,26 @@ def t_QELIBINC(self, t):
r"""include(\s+)"qelib1.inc";"""
return t

def t_STDGATESINC(self, t):
r"""include(\s+)"stdgates.inc";"""
return t

def t_QREG(self, t):
r"""qreg"""
return t

def t_QUBIT(self, t):
r"""qubit"""
return t

def t_CREG(self, t):
r"""creg"""
return t

def t_BIT(self, t):
r"""bit"""
return t

def t_MEASURE(self, t):
r"""measure"""
return t
Expand Down
43 changes: 35 additions & 8 deletions cirq-core/cirq/contrib/qasm_import/_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ def p_qasm_format_only(self, p):

def p_qasm_no_format_specified_error(self, p):
"""qasm : QELIBINC
| STDGATESINC
| circuit"""
if self.supported_format is False:
raise QasmException("Missing 'OPENQASM 2.0;' statement")
Expand All @@ -279,15 +280,21 @@ def p_qasm_include(self, p):
self.qelibinc = True
p[0] = Qasm(self.supported_format, self.qelibinc, self.qregs, self.cregs, self.circuit)

def p_qasm_include_stdgates(self, p):
"""qasm : qasm STDGATESINC"""
self.qelibinc = True
p[0] = Qasm(self.supported_format, self.qelibinc, self.qregs, self.cregs, self.circuit)

def p_qasm_circuit(self, p):
"""qasm : qasm circuit"""
p[0] = Qasm(self.supported_format, self.qelibinc, self.qregs, self.cregs, p[2])

def p_format(self, p):
"""format : FORMAT_SPEC"""
if p[1] != "2.0":
if p[1] not in ["2.0", "3.0"]:
raise QasmException(
f"Unsupported OpenQASM version: {p[1]}, only 2.0 is supported currently by Cirq"
f"Unsupported OpenQASM version: {p[1]}, "
"only 2.0 and 3.0 are supported currently by Cirq"
)

# circuit : new_reg circuit
Expand Down Expand Up @@ -315,13 +322,28 @@ def p_circuit_empty(self, p):

def p_new_reg(self, p):
"""new_reg : QREG ID '[' NATURAL_NUMBER ']' ';'
| CREG ID '[' NATURAL_NUMBER ']' ';'"""
name, length = p[2], p[4]
| QUBIT '[' NATURAL_NUMBER ']' ID ';'
Copy link
Collaborator

Choose a reason for hiding this comment

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

The OpenQASM 3 seems to allow scalar qubits defined as qubit some_name;.

Would it be feasible to handle it here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added scalar qubits (and bits)

| QUBIT ID ';'
| CREG ID '[' NATURAL_NUMBER ']' ';'
| BIT '[' NATURAL_NUMBER ']' ID ';'
| BIT ID ';'
"""
if p[1] == "qreg" or p[1] == "creg":
# QREG ID '[' NATURAL_NUMBER ']' ';'
name, length = p[2], p[4]
else:
if len(p) < 5:
# QUBIT ID ';'
name = p[2]
length = 1
else:
# QUBIT '[' NATURAL_NUMBER ']' ID ';'
name, length = p[5], p[3]
if name in self.qregs.keys() or name in self.cregs.keys():
raise QasmException(f"{name} is already defined at line {p.lineno(2)}")
if length == 0:
raise QasmException(f"Illegal, zero-length register '{name}' at line {p.lineno(4)}")
if p[1] == "qreg":
if p[1] == "qreg" or p[1] == "qubit":
self.qregs[name] = length
else:
self.cregs[name] = length
Expand Down Expand Up @@ -485,9 +507,14 @@ def p_classical_arg_bit(self, p):
# measurement : MEASURE qarg ARROW carg

def p_measurement(self, p):
"""measurement : MEASURE qarg ARROW carg ';'"""
qreg = p[2]
creg = p[4]
"""measurement : MEASURE qarg ARROW carg ';'
| carg '=' MEASURE qarg ';'"""
if p[1] == 'measure':
qreg = p[2]
creg = p[4]
else:
qreg = p[4]
creg = p[1]

if len(qreg) != len(creg):
raise QasmException(
Expand Down
48 changes: 48 additions & 0 deletions cirq-core/cirq/contrib/qasm_import/_parser_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1057,3 +1057,51 @@ def test_single_qubit_gates(qasm_gate: str, cirq_gate: cirq.Gate):

ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit)
assert parsed_qasm.qregs == {'q': 2}


def test_openqasm_3_0_qubits():
qasm = """OPENQASM 3.0;
include "stdgates.inc";
qubit[2] q;
bit[2] b;

x q[0];

b[0] = measure q[0];
"""
parser = QasmParser()

q0 = cirq.NamedQubit('q_0')

expected_circuit = Circuit([cirq.X.on(q0), cirq.measure(q0, key='b_0')])

parsed_qasm = parser.parse(qasm)

assert parsed_qasm.supportedFormat

ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit)
assert parsed_qasm.qregs == {'q': 2}


def test_openqasm_3_0_scalar_qubit():
qasm = """OPENQASM 3.0;
include "stdgates.inc";
qubit q;
bit b;

x q;

b = measure q;
"""
parser = QasmParser()

q0 = cirq.NamedQubit('q_0')

expected_circuit = Circuit([cirq.X.on(q0), cirq.measure(q0, key='b_0')])

parsed_qasm = parser.parse(qasm)

assert parsed_qasm.supportedFormat

ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit)
assert parsed_qasm.qregs == {'q': 1}