From 1f63bd47bfb395d5aa44f6d454aa4d7b4afc60c4 Mon Sep 17 00:00:00 2001 From: beizha Date: Sat, 16 Sep 2023 17:40:54 +0800 Subject: [PATCH 01/15] Add unit test for parser --- setup.py | 1 + tests/quafu/qasm/parser_test.py | 1351 +++++++++++++++++++++++++++++++ 2 files changed, 1352 insertions(+) create mode 100644 tests/quafu/qasm/parser_test.py diff --git a/setup.py b/setup.py index 442772b..d968675 100644 --- a/setup.py +++ b/setup.py @@ -44,6 +44,7 @@ packages=find_packages(exclude=["test*"]), cmake_install_dir="quafu/simulators/", include_package_data=True, + package_data={"quafu":["qfasm/*.inc"]}, long_description=long_description, long_description_content_type="text/markdown", extras_require={"test": ["pytest"]}, diff --git a/tests/quafu/qasm/parser_test.py b/tests/quafu/qasm/parser_test.py new file mode 100644 index 0000000..a661385 --- /dev/null +++ b/tests/quafu/qasm/parser_test.py @@ -0,0 +1,1351 @@ +# (C) Copyright 2023 Beijing Academy of Quantum Information Sciences +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import enum +import math +import os +import pathlib +import random +import tempfile + +import pytest + +from quafu.circuits import QuantumCircuit +from quafu.qfasm.exceptions import LexerError, ParserError +from quafu.qfasm.qfasm_convertor import qasm_to_quafu + + +class T(enum.Enum): + OPENQASM = "OPENQASM" + BARRIER = "barrier" + CREG = "creg" + GATE = "gate" + IF = "if" + INCLUDE = "include" + MEASURE = "measure" + OPAQUE = "opaque" + QREG = "qreg" + RESET = "reset" + PI = "pi" + ASSIGN = "->" + MATCHES = "==" + SEMICOLON = ";" + COMMA = "," + LPAREN = "(" + RPAREN = ")" + LBRACKET = "[" + RBRACKET = "]" + LBRACE = "{" + RBRACE = "}" + ID = "q" + FLOAT = "0.125" + INTEGER = "1" + FILENAME = '"qelib1.inc"' + + +tokenset = frozenset(T) + + +class TestParser: + """ + Test for PLY parser + """ + + def compare_cir(self, qc1: QuantumCircuit, qc2: QuantumCircuit): + # compare reg and compare gates + assert len(qc1.qregs) == len(qc2.qregs) + for i in range(len(qc1.qregs)): + reg1 = qc1.qregs[i] + reg2 = qc2.qregs[i] + assert len(reg1.qubits) == len(reg2.qubits) + assert len(qc1.gates) == len(qc2.gates) + for i in range(len(qc1.gates)): + gate1 = qc1.gates[i] + gate2 = qc2.gates[i] + assert gate1.name == gate2.name + if hasattr(gate1, "pos"): + assert gate1.pos == gate2.pos + if hasattr(gate1, "paras") and gate1.paras is None: + assert gate2.paras is None + if hasattr(gate1, "paras") and gate1.paras != None: + assert gate2.paras is not None + assert math.isclose(gate1.paras ,gate2.paras) + + # ---------------------------------------- + # test for lexer + # ---------------------------------------- + def test_id_cannot_start_with_capital(self): + with pytest.raises(LexerError, match=r"Illegal character .*") as e: + token = "qreg Qcav[1];" + qasm_to_quafu(token) + + def test_unexpected_linebreak(self): + with pytest.raises(LexerError) as e: + token = 'include "qe\nlib1.inc";' + qasm_to_quafu(token) + + def test_include_miss_sigd(self): + with pytest.raises( + LexerError, match=r'Expecting ";" for INCLUDE at line.*' + ) as e: + token = 'include "qelib1.inc" qreg q[1];' + qasm_to_quafu(token) + + @pytest.mark.parametrize("filename", ["qelib1.inc", '"qelib1.inc', 'qelib1.inc"']) + def test_filename_miss_quote(self, filename): + with pytest.raises( + LexerError, match=r"Invalid include: need a quoted string as filename." + ) as e: + token = f"include {filename};" + qasm_to_quafu(token) + + def test_include_cannot_find_file(self): + with pytest.raises( + LexerError, match=r"Include file .* cannot be found, .*" + ) as e: + token = f'include "qe.inc";' + qasm_to_quafu(token) + + def test_single_equals_error(self): + with pytest.raises(LexerError, match=r"Illegal character =.*") as e: + qasm = f"if (a=2) U(0,0,0)q[0];" + qasm_to_quafu(qasm) + + def test_invalid_token(self): + with pytest.raises(LexerError, match=r"Illegal character.*") as e: + token = f"!" + qasm_to_quafu(token) + + def test_single_quote_filename(self): + with pytest.raises(LexerError, match=r"Illegal character '.*") as e: + token = f"include 'qe.inc';" + qasm_to_quafu(token) + + # ---------------------------------------- + # test for expression + # ---------------------------------------- + def test_exp_unary(self): + num1 = random.random() + qasm = f"qreg q[1]; U(0, 0, {num1})q[0];" + cir = qasm_to_quafu(openqasm=qasm) + assert cir.gates[0].name == "RZ" + assert cir.gates[0].paras == num1 + + def test_exp_unary_symbolic(self): + qasm = """ + gate test(a,b,c) q{ + U(a+b,-(a-c), a+c-b) q; + } + qreg q[1]; test(0.5, 1.0, 2.0)q[0]; + """ + cir = qasm_to_quafu(openqasm=qasm) + assert cir.gates[0].paras == 0.5 + 2.0 - 1.0 + assert cir.gates[1].paras == 0.5 + 1.0 + assert cir.gates[2].paras == -(0.5 - 2.0) + + def test_exp_add(self): + num1 = random.random() + qasm = f"qreg q[1]; U(0, 0, {num1} + 1 + 1)q[0];" + cir = qasm_to_quafu(openqasm=qasm) + assert cir.gates[0].name == "RZ" + assert cir.gates[0].paras == num1 + 1 + 1 + + def test_exp_sub(self): + num1 = random.random() + qasm = f"qreg q[1]; U(0, 0, 3 - {num1})q[0];" + cir = qasm_to_quafu(openqasm=qasm) + assert cir.gates[0].name == "RZ" + assert cir.gates[0].paras == 3 - num1 + + def test_exp_mul(self): + num1 = random.random() + qasm = f"qreg q[1]; U(0, 0, 2 * {num1})q[0];" + cir = qasm_to_quafu(openqasm=qasm) + assert cir.gates[0].name == "RZ" + assert cir.gates[0].paras == 2 * num1 + + def test_exp_div(self): + num1 = random.random() + qasm = f"qreg q[1]; U(0, 0, {num1}/2)q[0];" + cir = qasm_to_quafu(openqasm=qasm) + assert cir.gates[0].name == "RZ" + assert cir.gates[0].paras == num1 / 2 + + def test_exp_power(self): + qasm = f"qreg q[1]; U(0, 0, 2^3)q[0];" + cir = qasm_to_quafu(openqasm=qasm) + assert cir.gates[0].name == "RZ" + assert cir.gates[0].paras == 2**3 + + @pytest.mark.parametrize( + ["symbol", "op"], + [ + ("+", lambda a, b: a + b), + ("-", lambda a, b: a - b), + ("*", lambda a, b: a * b), + ("/", lambda a, b: a / b), + ("^", lambda a, b: a**b), + ], + ) + def test_exp_binary(self, symbol, op): + num1 = random.random() + num2 = random.random() + qasm = f"qreg q[1]; U(0, 0, {num1}{symbol}{num2})q[0];" + cir = qasm_to_quafu(openqasm=qasm) + assert cir.gates[0].name == "RZ" + assert cir.gates[0].paras == op(num1, num2) + + @pytest.mark.parametrize( + ["symbol", "op"], + [ + ("+", lambda a, b: a + b), + ("-", lambda a, b: a - b), + ("*", lambda a, b: a * b), + ("/", lambda a, b: a / b), + ("^", lambda a, b: a**b), + ], + ) + def test_exp_binary_symbol(self, symbol, op): + num1 = random.random() + num2 = random.random() + qasm = f""" + gate test(a,b,c) q {{ + U(0,0,a{symbol}(b{symbol}c)) q; + }} + qreg q[1]; + test({num2}, {num1}, {num2})q[0]; + """ + cir = qasm_to_quafu(openqasm=qasm) + assert cir.gates[0].name == "RZ" + assert cir.gates[0].paras == op(num2, op(num1, num2)) + + def test_exp_mix(self): + qasm = f"qreg q[1]; U(2+1*2-3, 1+3-2, 2/2*2)q[0]; U(3*(2+1),3-(2-1)*2,(2-1)+1)q[0];" + cir = qasm_to_quafu(openqasm=qasm) + assert cir.gates[0].name == "RZ" + assert cir.gates[0].paras == 2 / 2 * 2 + assert cir.gates[1].name == "RY" + assert cir.gates[1].paras == 2 + 1 * 2 - 3 + assert cir.gates[2].name == "RZ" + assert cir.gates[2].paras == 1 + 3 - 2 + assert cir.gates[3].name == "RZ" + assert cir.gates[3].paras == (2 - 1) + 1 + assert cir.gates[4].name == "RY" + assert cir.gates[4].paras == 3 * (2 + 1) + assert cir.gates[5].name == "RZ" + assert cir.gates[5].paras == 3 - (2 - 1) * 2 + + def test_exp_par(self): + qasm = f"qreg q[1]; U((1-2)+(3-2), -(2*2-2), 2*(2-3))q[0];" + cir = qasm_to_quafu(openqasm=qasm) + assert cir.gates[0].name == "RZ" + assert cir.gates[0].paras == 2 * (2 - 3) + assert cir.gates[1].name == "RY" + assert cir.gates[1].paras == (1 - 2) + (3 - 2) + assert cir.gates[2].name == "RZ" + assert cir.gates[2].paras == -(2 * 2 - 2) + + @pytest.mark.parametrize( + ["func", "mathop"], + [ + ("cos", math.cos), + ("exp", math.exp), + ("ln", math.log), + ("sin", math.sin), + ("sqrt", math.sqrt), + ("tan", math.tan), + ], + ) + def test_exp_func(self, func, mathop): + qasm = f"qreg q[1]; U({func}(0.5),{func}(1.0),{func}(pi)) q[0];" + cir = qasm_to_quafu(openqasm=qasm) + assert math.isclose(cir.gates[0].paras, mathop(math.pi)) + assert math.isclose(cir.gates[1].paras, mathop(0.5)) + assert math.isclose(cir.gates[2].paras, mathop(1.0)) + + @pytest.mark.parametrize( + ["func", "mathop"], + [ + ("cos", math.cos), + ("exp", math.exp), + ("ln", math.log), + ("sin", math.sin), + ("sqrt", math.sqrt), + ("tan", math.tan), + ], + ) + def test_exp_func_symbol(self, func, mathop): + num1 = random.random() + num2 = random.random() + num3 = random.random() + qasm = f""" + gate test(a,b,c) q{{ + U({func}(a),{func}(b),{func}(c)) q; + }} + qreg q[1]; + test({num1},{num2},{num3}) q[0]; + """ + cir = qasm_to_quafu(openqasm=qasm) + assert math.isclose(cir.gates[0].paras, mathop(num3)) + assert math.isclose(cir.gates[1].paras, mathop(num1)) + assert math.isclose(cir.gates[2].paras, mathop(num2)) + + def test_exp_precedence(self): + num1 = random.random() + num2 = random.random() + num3 = random.random() + expr = f"{num1} + 1.5 * -{num3} ^ 2 - {num2} / 0.5" + # expr1 = f"{num1} * -{num2} ^ 2" + # expected1 = num1* (-num2)**2 + expected = num1 + 1.5 * (-num3) ** 2 - num2 / 0.5 + qasm = f"qreg q[1]; U( 0, 0, {expr}) q[0];" + cir = qasm_to_quafu(openqasm=qasm) + assert math.isclose(cir.gates[0].paras , expected) + + def test_exp_sub_left(self): + qasm = f"qreg q[1]; U( 0 , 0 , 2.0-1.0-1.0 ) q[0];" + print(qasm) + cir = qasm_to_quafu(openqasm=qasm) + assert cir.gates[0].paras == 0 + assert cir.gates[1].paras == 0 + assert cir.gates[2].paras == 0 + + def test_exp_div_left(self): + qasm = f"qreg q[1]; U( 0 , 0 , 2.0/2.0/2.0 ) q[0];" + print(qasm) + cir = qasm_to_quafu(openqasm=qasm) + assert cir.gates[0].paras == 0.5 + assert cir.gates[1].paras == 0 + assert cir.gates[2].paras == 0 + + def test_exp_pow_right(self): + qasm = f"qreg q[1]; U( 0 , 0 , 2.0^3.0^2.0 ) q[0];" + print(qasm) + cir = qasm_to_quafu(openqasm=qasm) + assert cir.gates[0].paras == 512.0 + assert cir.gates[1].paras == 0 + assert cir.gates[2].paras == 0 + + def test_exp_div_zero(self): + with pytest.raises(ParserError, match=r"Divided by 0 at line.*") as e: + qasm = f"qreg q[1]; U( 0 , 0 , 1.0/0 ) q[0];" + qasm_to_quafu(openqasm=qasm) + with pytest.raises(ParserError, match=r"Divided by 0 at line.*") as e: + qasm = f"qreg q[1]; U( 0 , 0 , 1.0/(1.0-1.0) ) q[0];" + qasm_to_quafu(openqasm=qasm) + + def test_exp_ln_neg(self): + "It's send to numpy.log, so will get nan" + pass + + def test_exp_sqrt_neg(self): + "It's send to numpy.sqrt, so will get nan" + pass + + @pytest.mark.parametrize( + ["symbol", "op"], + [ + ("*", lambda a, b: a * b), + ("/", lambda a, b: a / b), + ("^", lambda a, b: a**b), + ], + ) + def test_exp_nonunary_operators_lack(self, symbol, op): + qasm = f"qreg q[1]; U( 0 , 0 , {symbol}1.0 ) q[0];" + with pytest.raises(ParserError, match=r"Expecting an ID, received.*") as e: + qasm_to_quafu(openqasm=qasm) + + @pytest.mark.parametrize( + ["symbol", "op"], + [ + ("+", lambda a, b: a + b), + ("-", lambda a, b: a - b), + ("*", lambda a, b: a * b), + ("/", lambda a, b: a / b), + ("^", lambda a, b: a**b), + ], + ) + def test_exp_missing_binary_operand(self, symbol, op): + qasm = f"qreg q[1]; U( 0 , 0 , 1.0{symbol} ) q[0];" + with pytest.raises(ParserError, match=r"Expecting an ID, received.*") as e: + qasm_to_quafu(openqasm=qasm) + + def test_exp_missing_op(self): + qasm = f"qreg q[1]; U( 0 , 0 , 1.0 2.0 ) q[0];" + with pytest.raises(ParserError, match=r"Expecting '\)' after '\('.*") as e: + qasm_to_quafu(openqasm=qasm) + + def test_exp_missing_premature_right_pare(self): + qasm = f"qreg q[1]; U( 0 , 0 , sin() ) q[0];" + with pytest.raises(ParserError, match=r"Expecting an ID, received.*") as e: + qasm_to_quafu(openqasm=qasm) + + # ---------------------------------------- + # test for parser + # ---------------------------------------- + def test_multi_format(self): + qasm = """ + OPENQASM + 2.0 + ; + gate // a comment here + test( // to split a gate declaration + theta + ) // But it's + q // still a + { h // void +q; // gate! +u2( +theta , + 1) q + ;} + qreg // a quantum reg + q + [2]; + test(0.1) q[0]; cx q[0], + q[1]; + creg c[2]; measure q->c;""" + + cir = qasm_to_quafu(openqasm=qasm) + expected_cir = QuantumCircuit(2) + expected_cir.h(0) + expected_cir.rz(0, 1) + expected_cir.ry(0, math.pi / 2) + expected_cir.rz(0, 0.1) + expected_cir.cx(0, 1) + expected_cir.measure([0, 1]) + self.compare_cir(cir, expected_cir) + + @pytest.mark.parametrize("num", ["0.11", "2.2", "0.44"]) + def test_float(self, num): + qasm = f"qreg q[1]; U(0, 0, {num})q[0];" + cir = qasm_to_quafu(openqasm=qasm) + assert cir.gates[0].name == "RZ" + assert cir.gates[0].paras == float(num) + + def test_id_cannot_start_with_num(self): + with pytest.raises(ParserError, match=r"Expecting an ID, received .*") as e: + token = "qreg 0cav[1];" + qasm_to_quafu(token) + + def test_openqasm_float(self): + with pytest.raises( + ParserError, match=r"Expecting FLOAT after OPENQASM, received .*" + ) as e: + token = "OPENQASM 3;" + qasm_to_quafu(token) + + def test_openqasm_version(self): + with pytest.raises( + ParserError, match=r"Only support OPENQASM 2.0 version.*" + ) as e: + token = "OPENQASM 3.0;" + qasm_to_quafu(token) + + def test_openqasm_miss_sigd(self): + with pytest.raises( + ParserError, match=r"Expecting ';' at end of OPENQASM statement.*" + ) as e: + token = "OPENQASM 2.0 qreg q[3];" + qasm_to_quafu(token) + + def test_unexpected_end_of_file(self): + with pytest.raises(ParserError, match=r"Error at end of file") as e: + token = "OPENQASM 2.0" + qasm_to_quafu(token) + + def test_allow_empty(self): + cir = qasm_to_quafu("") + self.compare_cir(cir, QuantumCircuit(0)) + + def test_comment(self): + qasm = """ + // It's a comment + OPENQASM 2.0; + qreg q[2]; + """ + cir = qasm_to_quafu(qasm) + self.compare_cir(cir, QuantumCircuit(2)) + + def test_register(self): + qasm = """ + OPENQASM 2.0; + qreg q[2]; + qreg c[1]; + """ + cir = qasm_to_quafu(qasm) + self.compare_cir(cir, QuantumCircuit(3)) + + def test_creg(self): + qasm = """ + OPENQASM 2.0; + creg q[2]; + creg c[1]; + """ + cir = qasm_to_quafu(qasm) + self.compare_cir(cir, QuantumCircuit(0)) + + def test_registers_after_gate(self): + qasm = "qreg a[2]; CX a[0], a[1]; qreg b[2]; CX b[0], b[1];" + cir = qasm_to_quafu(qasm) + # print(cir.gates) + qc = QuantumCircuit(4) + qc.cx(0, 1) + qc.cx(2, 3) + self.compare_cir(cir, qc) + + def test_builtin_single(self): + qasm = """ + qreg q[2]; + U(0, 0, 0) q[0]; + CX q[0], q[1]; + """ + cir = qasm_to_quafu(qasm) + qc = QuantumCircuit(2) + qc.rz(0, 0) + qc.ry(0, 0) + qc.rz(0, 0) + qc.cx(0, 1) + self.compare_cir(cir, qc) + + def test_builtin_broadcast(self): + qasm = """ + qreg q[2]; + U(0, 0, 0) q; + """ + cir = qasm_to_quafu(qasm) + qc = QuantumCircuit(2) + qc.rz(0, 0) + qc.ry(0, 0) + qc.rz(0, 0) + qc.rz(1, 0) + qc.ry(1, 0) + qc.rz(1, 0) + self.compare_cir(cir, qc) + + def test_builtin_broadcast_2q(self): + qasm = """ + qreg q[2]; + qreg r[2]; + CX q,r; + """ + cir = qasm_to_quafu(qasm) + qc = QuantumCircuit(4) + qc.cx(0, 2) + qc.cx(1, 3) + self.compare_cir(cir, qc) + + def test_call_defined_gate(self): + qasm = """ + gate test a,b { + CX a,b; + } + qreg q[2]; + test q[0], q[1]; + """ + cir = qasm_to_quafu(qasm) + qc = QuantumCircuit(2) + qc.cx(0, 1) + self.compare_cir(cir, qc) + + def test_parameterless_gates(self): + qasm = """ + qreg q[2]; + CX q[0], q[1]; + CX() q[1], q[0]; + """ + cir = qasm_to_quafu(qasm) + qc = QuantumCircuit(2) + qc.cx(0, 1) + qc.cx(1, 0) + self.compare_cir(cir, qc) + + def test_include_in_definition(self): + qasm = """ + include "qelib1.inc"; + qreg q[2]; + cx q[0], q[1]; + cx() q[1], q[0]; + """ + cir = qasm_to_quafu(qasm) + qc = QuantumCircuit(2) + qc.cx(0, 1) + qc.cx(1, 0) + self.compare_cir(cir, qc) + + def test_previous_defined_gate(self): + qasm = """ + include "qelib1.inc"; + gate bell a, b { + h a; + cx a, b; + } + gate second_bell a, b { + bell b, a; + } + qreg q[2]; + second_bell q[0], q[1]; + """ + cir = qasm_to_quafu(qasm) + qc = QuantumCircuit(2) + qc.h(1) + qc.cx(1, 0) + self.compare_cir(cir, qc) + + def test_qubitname_lookup_differently_to_gate(self): + qasm = """ + gate bell h, cx { + h h; + cx h, cx; + } + qreg q[2]; + bell q[0], q[1]; + """ + cir = qasm_to_quafu(qasm) + qc = QuantumCircuit(2) + qc.h(0) + qc.cx(0, 1) + self.compare_cir(cir, qc) + + def test_paramname_lookup_differently_to_gates(self): + qasm = """ + gate test(rz, ry) a { + rz(rz) a; + ry(ry) a; + } + qreg q[1]; + test(0.5, 1.0) q[0]; + """ + cir = qasm_to_quafu(qasm) + qc = QuantumCircuit(1) + qc.rz(0, 0.5) + qc.ry(0, 1.0) + self.compare_cir(cir, qc) + + def test_unused_parameter(self): + qasm = """ + gate test(rz, ry) a { + rz(1.0) a; + ry(2.0) a; + } + qreg q[1]; + test(0.5, 1.0) q[0]; + """ + cir = qasm_to_quafu(qasm) + qc = QuantumCircuit(1) + qc.rz(0, 1.0) + qc.ry(0, 2.0) + self.compare_cir(cir, qc) + + def test_qubit_barrier_in_definition(self): + qasm = """ + gate test a, b { + barrier a; + barrier b; + barrier a, b; + } + qreg q[2]; + test q[0], q[1]; + """ + cir = qasm_to_quafu(qasm) + qc = QuantumCircuit(2) + qc.barrier([0]) + qc.barrier([1]) + qc.barrier([0]) + qc.barrier([1]) + self.compare_cir(cir, qc) + + def test_double_call_gate(self): + qasm = """ + gate test(x, y) a { + rz(x) a; + ry(y) a; + } + qreg q[2]; + test(0.5, 1.0) q[0]; + test(1.0, 0.5) q[1]; + """ + cir = qasm_to_quafu(qasm) + qc = QuantumCircuit(2) + qc.rz(0, 0.5) + qc.ry(0, 1.0) + qc.rz(1, 1.0) + qc.ry(1, 0.5) + self.compare_cir(cir, qc) + + def test_muti_register(self): + qasm = """ + qreg q[2]; + qreg r[2]; + qreg m[3]; + """ + cir = qasm_to_quafu(qasm) + qc = QuantumCircuit(7) + self.compare_cir(cir, qc) + + def test_single_measure(self): + qasm = """ + qreg q[1]; + creg c[1]; + measure q[0] -> c[0]; + """ + cir = qasm_to_quafu(qasm) + qc = QuantumCircuit(1) + qc.measure([0]) + self.compare_cir(cir, qc) + + def test_muti_measure(self): + qasm = """ + qreg q[2]; + creg c[2]; + measure q -> c; + """ + cir = qasm_to_quafu(qasm) + qc = QuantumCircuit(2) + qc.measure([0, 1]) + self.compare_cir(cir, qc) + + def test_single_reset(self): + qasm = """ + qreg q[1]; + reset q[0]; + """ + cir = qasm_to_quafu(qasm) + qc = QuantumCircuit(1) + qc.reset([0]) + self.compare_cir(cir, qc) + + def test_muti_reset(self): + qasm = """ + qreg q[2]; + reset q; + """ + cir = qasm_to_quafu(qasm) + qc = QuantumCircuit(2) + qc.reset([0, 1]) + self.compare_cir(cir, qc) + + def test_include_all_gate(self): + qasm = """ + include "qelib1.inc"; + qreg q[3]; + u3(0.5, 0.25, 0.125) q[0]; + u2(0.5, 0.25) q[0]; + u1(0.5) q[0]; + cx q[0], q[1]; + id q[0]; + x q[0]; + y q[0]; + z q[0]; + h q[0]; + s q[0]; + sdg q[0]; + t q[0]; + tdg q[0]; + rx(0.5) q[0]; + ry(0.5) q[0]; + rz(0.5) q[0]; + cz q[0], q[1]; + cy q[0], q[1]; + """ + cir = qasm_to_quafu(qasm) + qc = QuantumCircuit(3) + qc.rz(0, 0.125) + qc.ry(0, 0.5) + qc.rz(0, 0.25) + qc.rz(0, 0.25) + qc.ry(0, math.pi / 2) + qc.rz(0, 0.5) + qc.rz(0, 0.5) + qc.ry(0, 0) + qc.rz(0, 0) + qc.cx(0, 1) + qc.id(0) + qc.x(0) + qc.y(0) + qc.z(0) + qc.h(0) + qc.s(0) + qc.sdg(0) + qc.t(0) + qc.tdg(0) + qc.rx(0, 0.5) + qc.ry(0, 0.5) + qc.rz(0, 0.5) + qc.cz(0, 1) + qc.cy(0, 1) + self.compare_cir(cir, qc) + + def test_include_define_version(self): + include = """ + OPENQASM 2.0; + qreg q[2]; + """ + tmp_dir = pathlib.Path(tempfile.mkdtemp()) + + with open(tmp_dir / "include.qasm", "w") as fp: + fp.write(include) + filepath = tmp_dir / "include.qasm" + qasm = f'OPENQASM 2.0;include "{filepath}";' + cir = qasm_to_quafu(qasm) + qc = QuantumCircuit(2) + self.compare_cir(cir, qc) + + def test_nested_include(self): + inner = "qreg c[2];" + tmp_dir = pathlib.Path(tempfile.mkdtemp()) + with open(tmp_dir / "inner.qasm", "w") as fp: + fp.write(inner) + filepath = tmp_dir / "inner.qasm" + outer = f""" + qreg q[2]; + include "{filepath}"; + """ + with open(tmp_dir / "outer.qasm", "w") as fp: + fp.write(outer) + filepath = tmp_dir / "outer.qasm" + qasm = f""" + OPENQASM 2.0; + include "{filepath}"; + """ + cir = qasm_to_quafu(qasm) + qc = QuantumCircuit(4) + self.compare_cir(cir, qc) + + def test_include_from_current_directory(self): + include = """ + qreg q[2]; + """ + tmp_dir = pathlib.Path(tempfile.mkdtemp()) + with open(tmp_dir / "include.qasm", "w") as fp: + fp.write(include) + qasm = """ + OPENQASM 2.0; + include "include.qasm"; + """ + prevdir = os.getcwd() + os.chdir(tmp_dir) + try: + cir = qasm_to_quafu(qasm) + qc = QuantumCircuit(2) + self.compare_cir(cir, qc) + finally: + os.chdir(prevdir) + + def test_override_cx(self): + qasm = """ + qreg q[2]; + CX q[0], q[1]; + """ + cir = qasm_to_quafu(qasm) + qc = QuantumCircuit(2) + qc.cx(0, 1) + self.compare_cir(cir, qc) + + def test_override_u3(self): + qasm = """ + qreg q[2]; + U(0.1,0.2,0.3) q[0] ; + """ + cir = qasm_to_quafu(qasm) + qc = QuantumCircuit(2) + qc.rz(0, 0.3) + qc.ry(0, 0.1) + qc.rz(0, 0.2) + self.compare_cir(cir, qc) + + def test_gate_without_body(self): + qasm = """ + qreg q[2]; + gate test( ) q { } + test q[0]; + """ + cir = qasm_to_quafu(qasm) + qc = QuantumCircuit(2) + self.compare_cir(cir, qc) + + def test_gate_params_without_body(self): + qasm = """ + qreg q[2]; + gate test(x,y) q { } + test(0.1,0.2) q[0]; + """ + cir = qasm_to_quafu(qasm) + qc = QuantumCircuit(2) + self.compare_cir(cir, qc) + + def test_qiskit_mathfunc(self): + qasm = """ + include "qelib1.inc"; + qreg q[1]; + rx(asin(0.3)) q[0]; + ry(acos(0.3)) q[0]; + rz(atan(0.3)) q[0]; + """ + cir = qasm_to_quafu(qasm) + qc = QuantumCircuit(1) + qc.rx(0, math.asin(0.3)) + qc.ry(0, math.acos(0.3)) + qc.rz(0, math.atan(0.3)) + self.compare_cir(cir, qc) + + def test_empty_statement(self): + qasm = """ + include "qelib1.inc"; + qreg q[1]; + ; + """ + cir = qasm_to_quafu(qasm) + qc = QuantumCircuit(1) + self.compare_cir(cir, qc) + + # parser error + def test_registers_size_positive(self): + qasm = "qreg a[0]; CX a[0], a[1]; qreg b[2]; CX b[0], b[1];" + with pytest.raises(ParserError, match=r"QREG size must be positive at line"): + qasm_to_quafu(qasm) + + def test_unexpected_endof_file(self): + with pytest.raises(ParserError, match=r"Error at end of file") as e: + token = "OPENQASM 2.0" + qasm_to_quafu(token) + + +def getbadtoken(*tokens): + return tokenset - set(tokens) + + +def getbadstatement(): + list_badstatement = [] + for statement, badtoken in [ + ( + "", + getbadtoken( + T.OPAQUE, + T.OPENQASM, + T.ID, + T.INCLUDE, + T.GATE, + T.QREG, + T.CREG, + T.IF, + T.RESET, + T.BARRIER, + T.MEASURE, + T.SEMICOLON, + ), + ), + ("OPENQASM", getbadtoken(T.FLOAT, T.INTEGER)), + ("OPENQASM 2.0", getbadtoken(T.SEMICOLON)), + ("include", getbadtoken(T.FILENAME)), + ('include "qelib1.inc"', getbadtoken(T.SEMICOLON)), + ("gate", getbadtoken(T.ID)), + ("gate test (", getbadtoken(T.ID, T.RPAREN)), + ("gate test (a", getbadtoken(T.COMMA, T.RPAREN)), + ("gate test (a,", getbadtoken(T.ID, T.RPAREN)), + ("gate test (a, b", getbadtoken(T.COMMA, T.RPAREN)), + ("gate test (a, b) q1", getbadtoken(T.COMMA, T.LBRACE)), + ("gate test (a, b) q1,", getbadtoken(T.ID, T.LBRACE)), + ("gate test (a, b) q1, q2", getbadtoken(T.COMMA, T.LBRACE)), + ("qreg", getbadtoken(T.ID)), + ("qreg reg", getbadtoken(T.LBRACKET)), + ("qreg reg[", getbadtoken(T.INTEGER)), + ("qreg reg[5", getbadtoken(T.RBRACKET)), + ("qreg reg[5]", getbadtoken(T.SEMICOLON)), + ("creg", getbadtoken(T.ID)), + ("creg reg", getbadtoken(T.LBRACKET)), + ("creg reg[", getbadtoken(T.INTEGER)), + ("creg reg[5", getbadtoken(T.RBRACKET)), + ("creg reg[5]", getbadtoken(T.SEMICOLON)), + ("CX", getbadtoken(T.LPAREN, T.ID, T.SEMICOLON)), + ("CX(", getbadtoken(T.PI, T.INTEGER, T.FLOAT, T.ID, T.LPAREN, T.RPAREN)), + ("CX()", getbadtoken(T.ID, T.SEMICOLON)), + ("CX q", getbadtoken(T.LBRACKET, T.COMMA, T.SEMICOLON)), + ("CX q[", getbadtoken(T.INTEGER)), + ("CX q[0", getbadtoken(T.RBRACKET)), + ("CX q[0]", getbadtoken(T.COMMA, T.SEMICOLON)), + ("CX q[0],", getbadtoken(T.ID, T.SEMICOLON)), + ("CX q[0], q", getbadtoken(T.LBRACKET, T.COMMA, T.SEMICOLON)), + ("measure", getbadtoken(T.ID)), + ("measure q", getbadtoken(T.LBRACKET, T.ASSIGN)), + ("measure q[", getbadtoken(T.INTEGER)), + ("measure q[0", getbadtoken(T.RBRACKET)), + ("measure q[0]", getbadtoken(T.ASSIGN)), + ("measure q[0] ->", getbadtoken(T.ID)), + ("measure q[0] -> c", getbadtoken(T.LBRACKET, T.SEMICOLON)), + ("measure q[0] -> c[", getbadtoken(T.INTEGER)), + ("measure q[0] -> c[0", getbadtoken(T.RBRACKET)), + ("measure q[0] -> c[0]", getbadtoken(T.SEMICOLON)), + ("reset", getbadtoken(T.ID)), + ("reset q", getbadtoken(T.LBRACKET, T.SEMICOLON)), + ("reset q[", getbadtoken(T.INTEGER)), + ("reset q[0", getbadtoken(T.RBRACKET)), + ("reset q[0]", getbadtoken(T.SEMICOLON)), + ("barrier", getbadtoken(T.ID, T.SEMICOLON)), + ("barrier q", getbadtoken(T.LBRACKET, T.COMMA, T.SEMICOLON)), + ("barrier q[", getbadtoken(T.INTEGER)), + ("barrier q[0", getbadtoken(T.RBRACKET)), + ("barrier q[0]", getbadtoken(T.COMMA, T.SEMICOLON)), + ("if", getbadtoken(T.LPAREN)), + ("if (", getbadtoken(T.ID)), + ("if (cond", getbadtoken(T.MATCHES)), + ("if (cond ==", getbadtoken(T.INTEGER)), + ("if (cond == 0", getbadtoken(T.RPAREN)), + ("if (cond == 0)", getbadtoken(T.ID, T.RESET, T.MEASURE)), + ]: + for bad_token in badtoken: + list_badstatement.append(statement + bad_token.value) + return list_badstatement + + +badstatement = getbadstatement() + + +class TestParser2: + @pytest.mark.parametrize("badstatement", badstatement) + def test_bad_token(self, badstatement): + qasm = f"qreg q[2]; creg c[2]; creg cond[1]; {badstatement}" + with pytest.raises(Exception) as e: + qasm_to_quafu(qasm) + + def test_qubit_reg_use_before_declaration(self): + qasm = f"U(1,1,1) q[1]; qreg q[2]; creg c[2]; creg cond[1]; " + with pytest.raises( + ParserError, match=r".*is undefined in qubit register.*" + ) as e: + qasm_to_quafu(qasm) + + def test_cbit_reg_use_before_declaration(self): + qasm = f"qreg q[2]; measure q[0] -> c[0];creg c[2]; creg cond[1]; " + with pytest.raises(ParserError, match=r".*is undefined .*") as e: + qasm_to_quafu(qasm) + + def test_qreg_already_defined(self): + qasm = f"qreg q[2]; qreg q[2]; creg cond[1]; " + with pytest.raises(ParserError, match=r"Duplicate declaration.*") as e: + qasm_to_quafu(qasm) + + def test_creg_already_defined(self): + qasm = f"qreg q[2]; creg q[2]; creg cond[1]; " + with pytest.raises(ParserError, match=r"Duplicate declaration.*") as e: + qasm_to_quafu(qasm) + + def test_gate_not_defined(self): + qasm = f"qreg q[2]; creg cond[1]; test q[0],q[1]; " + with pytest.raises(ParserError, match=r".*is undefined.*") as e: + qasm_to_quafu(qasm) + + def test_gate_cannot_use_before_define(self): + qasm = f"""qreg q[2]; + test q[0],q[1]; + gate test () q,r{{ + cx q,r; + }} + """ + with pytest.raises(ParserError, match=r".*is undefined.*") as e: + qasm_to_quafu(qasm) + + def test_cannot_access_gate_recursively(self): + qasm = """ + gate test a, b { + test a, b; + } + qreg q[2]; + test q[0], q[1]; + """ + with pytest.raises(ParserError, match=r".*is undefined.*") as e: + qasm_to_quafu(qasm) + + def test_local_qubit_cannot_be_acceessed_by_other_gate(self): + qasm = """ + gate test a, b { + test a, b; + } + gate test2 c, d { + test a, b; + } + qreg q[2]; + test q[0], q[1]; + """ + with pytest.raises(ParserError, match=r".*is undefined.*") as e: + qasm_to_quafu(qasm) + + def test_local_arg_cannot_be_acceessed_by_other_gate(self): + qasm = """ + gate test(x,y) a, b { + test a, b; + } + gate test2 c, d { + test(x) c, d; + } + qreg q[2]; + """ + with pytest.raises(ParserError, match=r".*is undefined.*") as e: + qasm_to_quafu(qasm) + + def test_gate_ins_cannot_use_global_qubit_directly(self): + qasm = """ + qreg q[2]; + gate test(x,y) a, b { + test q, b; + } + gate test2 c, d { + test(x) c, d; + } + """ + with pytest.raises(ParserError, match=r".*is undefined.*") as e: + qasm_to_quafu(qasm) + + def test_arg_not_defined_outside(self): + qasm = """ + qreg q[2]; + gate test(x,y) a, b { + test q, b; + } + U(x,0,0) q; + """ + with pytest.raises(ParserError, match=r".*is undefined.*") as e: + qasm_to_quafu(qasm) + + def test_qubit_not_defined_outside(self): + qasm = """ + gate my_gate(a) q {} + U(0, 0, 0) q; + """ + with pytest.raises(ParserError, match=r".*is undefined.*") as e: + qasm_to_quafu(qasm) + + def test_use_undeclared_reg_in_if(self): + qasm = """ + qreg q[1]; + if (c==0) U(0,0,0)q[0]; + """ + with pytest.raises(ParserError, match=r".*is undefined.*") as e: + qasm_to_quafu(qasm) + + @pytest.mark.parametrize( + "statement", + [ + "CX q[0], U;", + "measure U -> c[0];", + "measure q[0] -> U;", + "reset U;", + "barrier U;", + "if (U == 0) CX q[0], q[1];", + ], + ) + def test_use_gate_in_wrong_way(self, statement): + qasm = f""" + qreg q[2]; + {statement} + """ + with pytest.raises(ParserError, match=r".*is not declared as.*") as e: + qasm_to_quafu(qasm) + + @pytest.mark.parametrize( + "statement", + [ + "measure q[0] -> q[1];", + "if (q == 0) CX q[0], q[1];", + "q q[0], q[1];", + ], + ) + def test_use_qreg_in_wrong_way(self, statement): + qasm = f""" + qreg q[2]; + {statement} + """ + with pytest.raises(ParserError, match=r".*is not declared as.*") as e: + qasm_to_quafu(qasm) + + @pytest.mark.parametrize( + "statement", + [ + "CX q[0], c[1];", + "measure c[0] -> c[1];", + "reset c[0];", + "barrier c[0];", + "c q[0], q[1];", + ], + ) + def test_use_creg_in_wrong_way(self, statement): + qasm = f""" + qreg q[2]; + creg c[2]; + {statement} + """ + with pytest.raises(ParserError, match=r".*is not declared as.*") as e: + qasm_to_quafu(qasm) + + def test_use_arg_in_wrong_way(self): + qasm = "gate test(p) q { CX p, q; } qreg q[2]; test(1) q[0];" + with pytest.raises(ParserError, match=r".*is not declared as.*") as e: + qasm_to_quafu(qasm) + + def test_use_gate_qubit_in_wrong_way(self): + qasm = f""" + qreg q[2]; + creg c[2]; + gate test(p) q {{ U(q, q, q) q; }}; + """ + with pytest.raises(ParserError, match=r".*is not declared as.*") as e: + qasm_to_quafu(qasm) + + @pytest.mark.parametrize( + ["gate", "badq"], + [("h", 3), ("h", 2), ("CX", 4), ("CX", 1), ("CX", 3), ("ccx", 2), ("ccx", 4)], + ) + def test_qubit_inconsistent_num(self, gate, badq): + arguments = ", ".join(f"q[{i}]" for i in range(badq)) + qasm = f'include "qelib1.inc"; qreg q[5];\n{gate} {arguments};' + with pytest.raises(ParserError, match=r".*is inconsistent with.*") as e: + qasm_to_quafu(qasm) + + @pytest.mark.parametrize( + ["gate", "bada"], [("U", 2), ("U", 4), ("rx", 5), ("rx", 2), ("u3", 1)] + ) + def test_arg_inconsistent_num(self, gate, bada): + arguments = ", ".join(f"q[{i}]" for i in range(bada)) + qasm = f'include "qelib1.inc"; qreg q[5];\n{gate} {arguments};' + with pytest.raises(ParserError, match=r".*is inconsistent with.*") as e: + qasm_to_quafu(qasm) + + @pytest.mark.parametrize("statement", ["gate test {}", "gate test(a) {}"]) + def test_gate_must_op_atleast_onequbit(self, statement): + qasm = statement + with pytest.raises(ParserError, match=r"Expecting an ID.*") as e: + qasm_to_quafu(qasm) + + def test_cannot_subscript_qubit(self): + qasm = """ + gate my_gate a { + CX a[0], a[1]; + } + """ + with pytest.raises(ParserError, match=r"Expecting.*") as e: + qasm_to_quafu(qasm) + + def test_cannot_duplicate_parameters(self): + qasm = "gate my_gate(a, a) q {}" + with pytest.raises(ParserError, match=r"Duplicate.*") as e: + qasm_to_quafu(qasm) + + def test_cannot_dulpicate_qubits(self): + qasm = "gate my_gate a, a {}" + with pytest.raises(ParserError, match=r"Duplicate.*") as e: + qasm_to_quafu(qasm) + + def test_qubit_cannot_shadow_parameter(self): + qasm = "gate my_gate(a) a {}" + with pytest.raises(ParserError, match=r"Duplicate.*") as e: + qasm_to_quafu(qasm) + + @pytest.mark.parametrize( + "statement", + ["measure q -> c;", "reset q;", "if (c == 0) U(0, 0, 0) q;", "gate my_x q {}"], + ) + def test_definition_cannot_contain_nonunitary(self, statement): + qasm = f"OPENQASM 2.0; creg c[5]; gate my_gate q {{ {statement} }}" + with pytest.raises(ParserError, match=r"Expecting.*") as e: + qasm_to_quafu(qasm) + + @pytest.mark.parametrize("statement", ["gate U(a, b, c) q {}", "gate CX a, b {}"]) + def test_cannot_redefine_u_cx(self, statement): + qasm = statement + with pytest.raises(ParserError, match=r"Duplicate.*") as e: + qasm_to_quafu(qasm) + + @pytest.mark.parametrize( + "statement", + [ + "qreg q[2]; U(0, 0, 0) q[2];", + "qreg q[2]; creg c[2]; measure q[2] -> c[0];", + "qreg q[2]; creg c[2]; measure q[0] -> c[2];", + ], + ) + def test_out_of_range(self, statement): + qasm = statement + with pytest.raises(ParserError, match=r".*out of bounds.*") as e: + qasm_to_quafu(qasm) + + @pytest.mark.parametrize( + "statement", + [ + "CX q1[0], q1[0];", + "CX q1, q1[0];", + "CX q1[0], q1;", + "CX q1, q1;", + "ccx q1[0], q1[1], q1[0];", + "ccx q2, q1, q2[0];", + ], + ) + def test_duplicate_use_qubit(self, statement): + qasm = """ + include "qelib1.inc"; + qreg q1[3]; + qreg q2[3]; + qreg q3[3]; + """ + qasm += statement + with pytest.raises(ParserError, match=r".*as different.*") as e: + qasm_to_quafu(qasm) + + @pytest.mark.parametrize( + ["reg", "statement"], + [ + (("q1[1]", "q2[2]"), "CX q1, q2"), + (("q1[1]", "q2[2]"), "CX q2, q1"), + (("q1[3]", "q2[2]"), "CX q1, q2"), + (("q1[2]", "q2[3]", "q3[3]"), "ccx q1, q2, q3"), + (("q1[2]", "q2[3]", "q3[3]"), "ccx q2, q3, q1"), + (("q1[2]", "q2[3]", "q3[3]"), "ccx q3, q1, q2"), + (("q1[2]", "q2[3]", "q3[3]"), "ccx q1, q2[0], q3"), + (("q1[2]", "q2[3]", "q3[3]"), "ccx q2[0], q3, q1"), + (("q1[2]", "q2[3]", "q3[3]"), "ccx q3, q1, q2[0]"), + ], + ) + def test_inconsistent_num_of_qubit_broadcast(self, reg, statement): + qasm = 'include "qelib1.inc";\n' + "\n".join(f"qreg {reg};" for reg in reg) + qasm += statement + ";" + with pytest.raises(ParserError, match=r".*is inconsistent.*") as e: + qasm_to_quafu(qasm) + + @pytest.mark.parametrize( + ["reg", "statement"], + [ + ("qreg q[2]; creg c[2];", "q[0] -> c"), + ("qreg q[2]; creg c[2];", "q -> c[0]"), + ("qreg q[3]; creg c[2];", "q -> c[0]"), + ("qreg q[2]; creg c[3];", "q[0] -> c"), + ("qreg q[2]; creg c[3];", "q -> c"), + ], + ) + def test_inconsistent_measure_broadcast(self, reg, statement): + qasm = f"{reg}\nmeasure {statement};" + with pytest.raises(ParserError, match=r".*doesn't match.*") as e: + qasm_to_quafu(qasm) + + @pytest.mark.parametrize( + "statement", + [ + "gate my_gate(p0, p1,) q0, q1 {}", + "gate my_gate(p0, p1) q0, q1, {}", + 'include "qelib1.inc"; qreg q[2]; cu3(0.5, 0.25, 0.125,) q[0], q[1];', + 'include "qelib1.inc"; qreg q[1]; rx(sin(pi,)) q[0];', + ], + ) + def test_trailing_comma(self, statement): + qasm = statement + with pytest.raises(ParserError, match=r"Expecting*") as e: + qasm_to_quafu(qasm) From 78724091d7655ec43f9717c5d944e559adca94ee Mon Sep 17 00:00:00 2001 From: beizha Date: Sat, 16 Sep 2023 17:45:51 +0800 Subject: [PATCH 02/15] Ignore .idea and internal file generated by parser --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 39c4b1c..e172329 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,5 @@ MANIFEST.in *.pyd dev_*.py *temp.* +.idea +parsetab.py From ef24dfe81ca3ea527dc87e8e07c37188150584b4 Mon Sep 17 00:00:00 2001 From: beizha Date: Sat, 16 Sep 2023 18:31:10 +0800 Subject: [PATCH 03/15] Add support for parser --- quafu/qfasm/qfasm_utils.py | 156 +++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 quafu/qfasm/qfasm_utils.py diff --git a/quafu/qfasm/qfasm_utils.py b/quafu/qfasm/qfasm_utils.py new file mode 100644 index 0000000..69eb40b --- /dev/null +++ b/quafu/qfasm/qfasm_utils.py @@ -0,0 +1,156 @@ +# (C) Copyright 2023 Beijing Academy of Quantum Information Sciences +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import quafu.elements.element_gates as qeg + + +class Node: + """ + Node created in reduction. + Help for building QuantumCrcuit. + """ + + def __init__(self): + self.type = None + self.name = None + self.num = 0 + self.start = 0 + self.lineno = 0 + self.filename = None + + +class UnaryExpr(Node): + def __init__(self, type, node: Node): + self.type = type + self.children = [node] + + +class BinaryExpr(Node): + def __init__(self, type, left, right): + self.type = type + self.children = [left, right] + + +class Id(Node): + def __init__(self, name, lineno, filename): + self.name = name + self.lineno = lineno + self.filename = filename + self.num = 0 + + +class IndexedId(Node): + def __init__(self, node: Node, index): + self.num = index + self.name = node.name + self.lineno = node.lineno + self.filename = node.filename + + +class GateInstruction: + def __init__(self, node, qargs, cargs=None, cbits=None): + self.name = node.name + self.lineno = node.lineno + self.filename = node.filename + self.qargs = qargs + self.cargs = cargs + self.cbits = cbits + + +class IfInstruction: + def __init__(self, node, cbits, value: int, instruction): + self.name = node.name + self.lineno = node.lineno + self.filename = node.filename + self.cbits = cbits + self.value = value + self.instruction = instruction + + +class SymtabNode(Node): + def __init__(self, type, node, is_global=True, is_qubit=False): + # qreg creg gate arg qarg + self.type = type + self.name = node.name + # for reg + self.num = 0 + if isinstance(node, IndexedId): + self.num = node.num + self.start = 0 + # for debug + self.lineno = node.lineno + self.filename = node.filename + # for check + self.is_global = is_global + # for gate + self.is_qubit = is_qubit + self.qargs = [] + self.cargs = [] + self.instructions = [] + + def fill_gate(self, qargs, instructions=None, cargs=None): + self.qargs = qargs + self.instructions = instructions + if cargs: + self.cargs = cargs + + +from quafu.elements.quantum_element.quantum_element import ( + Measure, + Barrier, + Delay, + XYResonance, +) + +gate_classes = { + "x": qeg.XGate, + "y": qeg.YGate, + "z": qeg.ZGate, + "h": qeg.HGate, + "s": qeg.SGate, + "sdg": qeg.SdgGate, + "t": qeg.TGate, + "tdg": qeg.TdgGate, + "rx": qeg.RXGate, + "ry": qeg.RYGate, + "rz": qeg.RZGate, + "id": qeg.IdGate, + "sx": qeg.SXGate, + "sy": qeg.SYGate, + "w": qeg.WGate, + "sw": qeg.SWGate, + "p": qeg.PhaseGate, + "cx": qeg.CXGate, + "cnot": qeg.CXGate, + "cp": qeg.CPGate, + "swap": qeg.SwapGate, + "rxx": qeg.RXXGate, + "ryy": qeg.RYYGate, + "rzz": qeg.RZZGate, + "cy": qeg.CYGate, + "cz": qeg.CZGate, + "cs": qeg.CSGate, + "ct": qeg.CTGate, + "ccx": qeg.ToffoliGate, + "toffoli": qeg.ToffoliGate, + "cswap": qeg.FredkinGate, + "fredkin": qeg.FredkinGate, + "mcx": qeg.MCXGate, + "mcy": qeg.MCYGate, + "mcz": qeg.MCZGate, + "delay": Delay, + "barrier": Barrier, + "measure": Measure, + "xy": XYResonance, +} From c7035f7161b9933da1a95f8dcaa48679fe920a12 Mon Sep 17 00:00:00 2001 From: beizha Date: Sat, 16 Sep 2023 18:31:52 +0800 Subject: [PATCH 04/15] Add lexer based on parser --- quafu/qfasm/qfasm_lexer.py | 210 +++++++++++++++++++++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 quafu/qfasm/qfasm_lexer.py diff --git a/quafu/qfasm/qfasm_lexer.py b/quafu/qfasm/qfasm_lexer.py new file mode 100644 index 0000000..8d4ac48 --- /dev/null +++ b/quafu/qfasm/qfasm_lexer.py @@ -0,0 +1,210 @@ +# (C) Copyright 2023 Beijing Academy of Quantum Information Sciences +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Qfasm is modified OPENQASM 2.0. Currently it is mainly used to +# transfer circuit data to backends. Further it will +# support more instructions (classical or quantum) to enable +# interaction with quantum hardware + +import os +import ply.lex as lex +from .qfasm_utils import Id +from .exceptions import LexerError + + +class QfasmLexer(object): + def __init__(self, filename: str = None): + self.build(filename) + self.file_lexer_stack = [] + + def build(self, filename: str = None): + self.lexer = lex.lex(module=self) + self.lexer.filename = filename + self.lexer.lineno = 1 + if filename: + with open(filename, "r") as ifile: + self.data = ifile.read() + self.lexer.input(self.data) + + def push_lexer(self, filename): + self.file_lexer_stack.append(self.lexer) + self.build(filename=filename) + + def pop_lexer(self): + self.lexer = self.file_lexer_stack.pop() + + def input(self, data): + self.data = data + self.lexer.input(data) + # read qelib1.inc + qelib1 = os.path.join(os.path.dirname(__file__), "qelib1.inc") + self.push_lexer(qelib1) + + def token(self): + ret = self.lexer.token() + return ret + + literals = r'()[]{};<>,+-/*^"' + + reserved = { + "creg": "CREG", + "qreg": "QREG", + "pi": "PI", + "measure": "MEASURE", + "include": "INCLUDE", + "barrier": "BARRIER", + "gate": "GATE", + "opaque": "OPAQUE", + "reset": "RESET", + "if": "IF", + } + + tokens = [ + "FLOAT", + "INT", + "STRING", + "ASSIGN", + "MATCHES", + "ID", + "UNIT", + "CHANNEL", + "OPENQASM", + "NONE", + ] + list(reserved.values()) + + # dispose include file + def t_INCLUDE(self, _): + "include" + filename_token = self.lexer.token() + if filename_token is None: + raise LexerError("Expecting filename, received nothing.") + # print(filename_token.value) + if isinstance(filename_token.value, str): + filename = filename_token.value.strip('"') + if filename == "": + raise LexerError("Invalid include: need a quoted string as filename.") + else: + raise LexerError("Invalid include: need a quoted string as filename.") + + # just ignore, because we include it at first + if filename == "qelib1.inc": + semicolon_token = self.lexer.token() + if semicolon_token is None or semicolon_token.value != ";": + raise LexerError( + f'Expecting ";" for INCLUDE at line {semicolon_token.lineno}, in file {self.lexer.filename}' + ) + return self.lexer.token() + # if filename == 'qelib1.inc': + # filename = os.path.join(os.path.dirname(__file__), 'qelib1.inc') + + if not os.path.exists(filename): + # print(filename) + raise LexerError( + f"Include file {filename} cannot be found, at line {filename_token.lineno}, in file {self.lexer.filename}" + ) + + semicolon_token = self.lexer.token() + if semicolon_token is None or semicolon_token.value != ";": + raise LexerError( + f'Expecting ";" for INCLUDE at line {semicolon_token.lineno}, in file {self.lexer.filename}' + ) + + self.push_lexer(filename) + return self.lexer.token() + + def t_FLOAT(self, t): + r"(([1-9]\d*\.\d*)|(0\.\d*[1-9]\d*))" + t.value = float(t.value) + return t + + def t_INT(self, t): + r"\d+" + t.value = int(t.value) + return t + + def t_STRING(self, t): + r'"([^\\\"]|\\.)*"' + return t + + def t_ASSIGN(self, t): + r"->" + return t + + def t_MATCHES(self, t): + r"==" + return t + + def t_UNIT(self, t): + r"ns|us" + return t + + def t_CHANNEL(self, t): + r"XY|Z" + return t + + def t_OPENQASM(self, t): + r"OPENQASM" + return t + + def t_NONE(self, t): + r"None" + return t + + def t_CX_U(self, t): + "CX|U" + t.type = "ID" + t.value = Id(t.value, self.lexer.lineno, self.lexer.filename) + return t + + def t_ID(self, t): + r"[a-z][a-zA-Z0-9_]*" + t.type = self.reserved.get(t.value, "ID") + # all the reset | barrier | gate | include | measure | opaque + t.value = Id(t.value, self.lexer.lineno, self.lexer.filename) + return t + + def t_COMMENT(self, _): + r"//.*" + pass + + t_ignore = " \t\r" + + def t_error(self, t): + raise LexerError(f"Illegal character {t.value[0]}") + + def t_newline(self, t): + r"\n+" + t.lexer.lineno += len(t.value) + + def t_eof(self, _): + if len(self.file_lexer_stack) > 0: + self.pop_lexer() + return self.lexer.token() + return None + + def test_data(self, data): + self.lexer.input(data) + while True: + tok = self.lexer.token() + if not tok: + break + print(tok) + + def test_file(self): + print_file = open("lex.txt", "w+") + while True: + tok = self.lexer.token() + if not tok: + break + print(tok, file=print_file) From 77cf759156ab6552cc6bc0b6ee00a532877d1446 Mon Sep 17 00:00:00 2001 From: beizha Date: Sat, 16 Sep 2023 18:32:49 +0800 Subject: [PATCH 05/15] Fix bugs in qasm2_to_quafu_qc --- quafu/qfasm/qfasm_convertor.py | 202 +++++++-------------------------- 1 file changed, 41 insertions(+), 161 deletions(-) diff --git a/quafu/qfasm/qfasm_convertor.py b/quafu/qfasm/qfasm_convertor.py index be0e121..d7853d2 100644 --- a/quafu/qfasm/qfasm_convertor.py +++ b/quafu/qfasm/qfasm_convertor.py @@ -1,31 +1,36 @@ -from .qfasm_parser import QfasmParser, QregNode -from quafu.dagcircuits.circuit_dag import node_to_gate -from quafu.dagcircuits.instruction_node import InstructionNode -from quafu.circuits import QuantumCircuit, QuantumRegister - - -def qasm_to_circuit(qasm): - parser = QfasmParser() - nodes = parser.parse(qasm) - - n = 0 - gates = [] - measures = {} - for node in nodes: - if isinstance(node, QregNode): - n = node.n - if isinstance(node, InstructionNode): - if node.name == "measure": - for q, c in zip(node.pos.keys(), node.pos.values()): - measures[q] = c - else: - gates.append(node_to_gate(node)) - - q = QuantumCircuit(n) - q.gates = gates - q.openqasm = qasm - q.measures = measures - return q +# (C) Copyright 2023 Beijing Academy of Quantum Information Sciences +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from quafu.circuits.quantum_circuit import QuantumCircuit +from .qfasm_parser import QfasmParser + +def qasm_to_quafu(openqasm: str): + """ + Initialize pyquafu circuit from openqasm text, mainly by + utilizing regular expressions. This function is developed + not only to be backward compatible with pure quantum part of + OPENQASM2.0, but also to support provide foundation of more + powerful syntax parser. + """ + qparser = QfasmParser() + qc = qparser.parse(openqasm) + qc.openqasm = openqasm + if not qc.executable_on_backend: + print( + "Warning: At present, quafu's back-end real machine does not support dynamic circuits." + ) + return qc def qasm2_to_quafu_qc(qc: QuantumCircuit, openqasm: str): @@ -36,139 +41,14 @@ def qasm2_to_quafu_qc(qc: QuantumCircuit, openqasm: str): OPENQASM2.0, but also to support provide foundation of more powerful syntax parser. """ - from numpy import pi - import re - + qparser = QfasmParser() + newqc = qparser.parse(openqasm) qc.openqasm = openqasm - # lines = self.openqasm.strip("\n").splitlines(";") - lines = qc.openqasm.splitlines() - lines = [line for line in lines if line] - qc.gates = [] - qc.measures = {} - measured_qubits = [] - global_valid = True - for line in lines[2:]: - if line: - operations_qbs = line.split(" ", 1) - operations = operations_qbs[0] - if operations == "qreg": - qbs = operations_qbs[1] - num = int(re.findall("\d+", qbs)[0]) - reg = QuantumRegister(num) - qc.qregs.append(reg) - elif operations == "creg": - pass - elif operations == "measure": - qbs = operations_qbs[1] - indstr = re.findall("\d+", qbs) - inds = [int(indst) for indst in indstr] - mb = inds[0] - cb = inds[1] - qc.measures[mb] = cb - measured_qubits.append(mb) - else: - qbs = operations_qbs[1] - indstr = re.findall("\d+", qbs) - inds = [int(indst) for indst in indstr] - valid = True - for pos in inds: - if pos in measured_qubits: - valid = False - global_valid = False - break - - if valid: - if operations == "barrier": - qc.barrier(inds) - - else: - sp_op = operations.split("(") - gatename = sp_op[0] - if gatename == "delay": - paras = sp_op[1].strip("()") - duration = int(re.findall("\d+", paras)[0]) - unit = re.findall("[a-z]+", paras)[0] - qc.delay(inds[0], duration, unit) - elif gatename == "xy": - paras = sp_op[1].strip("()") - duration = int(re.findall("\d+", paras)[0]) - unit = re.findall("[a-z]+", paras)[0] - qc.xy(min(inds), max(inds), duration, unit) - else: - if len(sp_op) > 1: - paras = sp_op[1].strip("()") - parastr = paras.split(",") - paras = [ - eval(parai, {"pi": pi}) for parai in parastr - ] - - if gatename == "cx": - qc.cnot(inds[0], inds[1]) - elif gatename == "cy": - qc.cy(inds[0], inds[1]) - elif gatename == "cz": - qc.cz(inds[0], inds[1]) - elif gatename == "cp": - qc.cp(inds[0], inds[1], paras[0]) - elif gatename == "swap": - qc.swap(inds[0], inds[1]) - elif gatename == "rx": - qc.rx(inds[0], paras[0]) - elif gatename == "ry": - qc.ry(inds[0], paras[0]) - elif gatename == "rz": - qc.rz(inds[0], paras[0]) - elif gatename == "p": - qc.p(inds[0], paras[0]) - elif gatename == "x": - qc.x(inds[0]) - elif gatename == "y": - qc.y(inds[0]) - elif gatename == "z": - qc.z(inds[0]) - elif gatename == "h": - qc.h(inds[0]) - elif gatename == "id": - qc.id(inds[0]) - elif gatename == "s": - qc.s(inds[0]) - elif gatename == "sdg": - qc.sdg(inds[0]) - elif gatename == "t": - qc.t(inds[0]) - elif gatename == "tdg": - qc.tdg(inds[0]) - elif gatename == "sx": - qc.sx(inds[0]) - elif gatename == "ccx": - qc.toffoli(inds[0], inds[1], inds[2]) - elif gatename == "cswap": - qc.fredkin(inds[0], inds[1], inds[2]) - elif gatename == "u1": - qc.rz(inds[0], paras[0]) - elif gatename == "u2": - qc.rz(inds[0], paras[1]) - qc.ry(inds[0], pi / 2) - qc.rz(inds[0], paras[0]) - elif gatename == "u3": - qc.rz(inds[0], paras[2]) - qc.ry(inds[0], paras[0]) - qc.rz(inds[0], paras[1]) - elif gatename == "rxx": - qc.rxx(inds[0], inds[1], paras[0]) - elif gatename == "ryy": - qc.ryy(inds[0], inds[1], paras[0]) - elif gatename == "rzz": - qc.rzz(inds[0], inds[1], paras[0]) - else: - print( - "Warning: Operations %s may be not supported by QuantumCircuit class currently." - % gatename - ) - - if not qc.measures: - qc.measures = dict(zip(range(qc.num), range(qc.num))) - if not global_valid: + qc.gates = newqc.gates + qc.measures = newqc.measures + qc.qregs = newqc.qregs + qc.executable_on_backend = newqc.executable_on_backend + if not qc.executable_on_backend: print( - "Warning: All operations after measurement will be removed for executing on experiment" + "Warning: At present, quafu's back-end real machine does not support dynamic circuits." ) From 84d123033b452077ab7bf3d9874ab236029c150c Mon Sep 17 00:00:00 2001 From: beizha Date: Sat, 16 Sep 2023 18:34:05 +0800 Subject: [PATCH 06/15] Add qelib1.inc standard gate for parser --- quafu/qfasm/qelib1.inc | 269 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 269 insertions(+) create mode 100644 quafu/qfasm/qelib1.inc diff --git a/quafu/qfasm/qelib1.inc b/quafu/qfasm/qelib1.inc new file mode 100644 index 0000000..8f510a5 --- /dev/null +++ b/quafu/qfasm/qelib1.inc @@ -0,0 +1,269 @@ +// Quantum Experience (QE) Standard Header +// file: qelib1.inc + +// --- QE Hardware primitives --- + +// 3-parameter 2-pulse single qubit gate +gate u3(theta,phi,lambda) q +{ + U(theta,phi,lambda) q; +} +// 2-parameter 1-pulse single qubit gate +gate u2(phi,lambda) q { U(pi/2,phi,lambda) q; } +// 1-parameter 0-pulse single qubit gate +gate u1(lambda) q { U(0,0,lambda) q; } +// controlled-NOT +gate cx c,t { CX c,t; } +// idle gate (identity) +gate id a { U(0,0,0) a; } +// idle gate (identity) with length gamma*sqglen +gate u0(gamma) q { U(0,0,0) q; } + +// --- QE Standard Gates --- + +// generic single qubit gate +gate u(theta,phi,lambda) q { U(theta,phi,lambda) q; } +// phase gate +gate p(lambda) q { U(0,0,lambda) q; } +// Pauli gate: bit-flip +gate x a { u3(pi,0,pi) a; } +// Pauli gate: bit and phase flip +gate y a { u3(pi,pi/2,pi/2) a; } +// Pauli gate: phase flip +gate z a { u1(pi) a; } +// Clifford gate: Hadamard +gate h a { u2(0,pi) a; } +// Clifford gate: sqrt(Z) phase gate +gate s a { u1(pi/2) a; } +// Clifford gate: conjugate of sqrt(Z) +gate sdg a { u1(-pi/2) a; } +// C3 gate: sqrt(S) phase gate +gate t a { u1(pi/4) a; } +// C3 gate: conjugate of sqrt(S) +gate tdg a { u1(-pi/4) a; } + +// --- Standard rotations --- +// Rotation around X-axis +gate rx(theta) a { u3(theta, -pi/2,pi/2) a; } +// rotation around Y-axis +gate ry(theta) a { u3(theta,0,0) a; } +// rotation around Z axis +gate rz(phi) a { u1(phi) a; } + +// --- QE Standard User-Defined Gates --- + +// sqrt(X) +gate sx a { sdg a; h a; sdg a; } +// inverse sqrt(X) +gate sxdg a { s a; h a; s a; } +// controlled-Phase +gate cz a,b { h b; cx a,b; h b; } +// controlled-Y +gate cy a,b { sdg b; cx a,b; s b; } +// swap +gate swap a,b { cx a,b; cx b,a; cx a,b; } +// controlled-H +gate ch a,b { +h b; sdg b; +cx a,b; +h b; t b; +cx a,b; +t b; h b; s b; x b; s a; +} +// C3 gate: Toffoli +gate ccx a,b,c +{ + h c; + cx b,c; tdg c; + cx a,c; t c; + cx b,c; tdg c; + cx a,c; t b; t c; h c; + cx a,b; t a; tdg b; + cx a,b; +} +// cswap (Fredkin) +gate cswap a,b,c +{ + cx c,b; + ccx a,b,c; + cx c,b; +} +// controlled rx rotation +gate crx(lambda) a,b +{ + u1(pi/2) b; + cx a,b; + u3(-lambda/2,0,0) b; + cx a,b; + u3(lambda/2,-pi/2,0) b; +} +// controlled ry rotation +gate cry(lambda) a,b +{ + ry(lambda/2) b; + cx a,b; + ry(-lambda/2) b; + cx a,b; +} +// controlled rz rotation +gate crz(lambda) a,b +{ + rz(lambda/2) b; + cx a,b; + rz(-lambda/2) b; + cx a,b; +} +// controlled phase rotation +gate cu1(lambda) a,b +{ + u1(lambda/2) a; + cx a,b; + u1(-lambda/2) b; + cx a,b; + u1(lambda/2) b; +} +gate cp(lambda) a,b +{ + p(lambda/2) a; + cx a,b; + p(-lambda/2) b; + cx a,b; + p(lambda/2) b; +} +// controlled-U +gate cu3(theta,phi,lambda) c, t +{ + // implements controlled-U(theta,phi,lambda) with target t and control c + u1((lambda+phi)/2) c; + u1((lambda-phi)/2) t; + cx c,t; + u3(-theta/2,0,-(phi+lambda)/2) t; + cx c,t; + u3(theta/2,phi,0) t; +} +// controlled-sqrt(X) +gate csx a,b { h b; cu1(pi/2) a,b; h b; } +// controlled-U gate +gate cu(theta,phi,lambda,gamma) c, t +{ p(gamma) c; + p((lambda+phi)/2) c; + p((lambda-phi)/2) t; + cx c,t; + u(-theta/2,0,-(phi+lambda)/2) t; + cx c,t; + u(theta/2,phi,0) t; +} +// two-qubit XX rotation +gate rxx(theta) a,b +{ + u3(pi/2, theta, 0) a; + h b; + cx a,b; + u1(-theta) b; + cx a,b; + h b; + u2(-pi, pi-theta) a; +} +// two-qubit ZZ rotation +gate rzz(theta) a,b +{ + cx a,b; + u1(theta) b; + cx a,b; +} +// relative-phase CCX +gate rccx a,b,c +{ + u2(0,pi) c; + u1(pi/4) c; + cx b, c; + u1(-pi/4) c; + cx a, c; + u1(pi/4) c; + cx b, c; + u1(-pi/4) c; + u2(0,pi) c; +} +// relative-phase 3-controlled X gate +gate rc3x a,b,c,d +{ + u2(0,pi) d; + u1(pi/4) d; + cx c,d; + u1(-pi/4) d; + u2(0,pi) d; + cx a,d; + u1(pi/4) d; + cx b,d; + u1(-pi/4) d; + cx a,d; + u1(pi/4) d; + cx b,d; + u1(-pi/4) d; + u2(0,pi) d; + u1(pi/4) d; + cx c,d; + u1(-pi/4) d; + u2(0,pi) d; +} +// 3-controlled X gate +gate c3x a,b,c,d +{ + h d; + p(pi/8) a; + p(pi/8) b; + p(pi/8) c; + p(pi/8) d; + cx a, b; + p(-pi/8) b; + cx a, b; + cx b, c; + p(-pi/8) c; + cx a, c; + p(pi/8) c; + cx b, c; + p(-pi/8) c; + cx a, c; + cx c, d; + p(-pi/8) d; + cx b, d; + p(pi/8) d; + cx c, d; + p(-pi/8) d; + cx a, d; + p(pi/8) d; + cx c, d; + p(-pi/8) d; + cx b, d; + p(pi/8) d; + cx c, d; + p(-pi/8) d; + cx a, d; + h d; +} +// 3-controlled sqrt(X) gate, this equals the C3X gate where the CU1 rotations are -pi/8 not -pi/4 +gate c3sqrtx a,b,c,d +{ + h d; cu1(pi/8) a,d; h d; + cx a,b; + h d; cu1(-pi/8) b,d; h d; + cx a,b; + h d; cu1(pi/8) b,d; h d; + cx b,c; + h d; cu1(-pi/8) c,d; h d; + cx a,c; + h d; cu1(pi/8) c,d; h d; + cx b,c; + h d; cu1(-pi/8) c,d; h d; + cx a,c; + h d; cu1(pi/8) c,d; h d; +} +// 4-controlled X gate +gate c4x a,b,c,d,e +{ + h e; cu1(pi/2) d,e; h e; + c3x a,b,c,d; + h e; cu1(-pi/2) d,e; h e; + c3x a,b,c,d; + c3sqrtx a,b,c,e; +} From ec5e452ee98ff620f55b244cd65c40837af2e791 Mon Sep 17 00:00:00 2001 From: beizha Date: Sat, 16 Sep 2023 18:34:54 +0800 Subject: [PATCH 07/15] Add exception for parser --- quafu/qfasm/exceptions.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/quafu/qfasm/exceptions.py b/quafu/qfasm/exceptions.py index b855514..2606f52 100644 --- a/quafu/qfasm/exceptions.py +++ b/quafu/qfasm/exceptions.py @@ -1,3 +1,17 @@ +# (C) Copyright 2023 Beijing Academy of Quantum Information Sciences +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + from quafu.exceptions import QuafuError @@ -5,12 +19,17 @@ class QfasmError(QuafuError): """ Base class for errors raised by Qfasm. """ + pass -class QfasmSyntaxError(QfasmError): +class LexerError(Exception): + """Errors raised while lexer get tokens""" + pass -class QfasmSemanticError(QfasmError): +class ParserError(Exception): + """Errors raised while parser OPENQASM""" + pass From 5055b41042454969c50a0e5cb98d74a46bee9e1c Mon Sep 17 00:00:00 2001 From: beizha Date: Sat, 16 Sep 2023 18:35:18 +0800 Subject: [PATCH 08/15] Fix some bugs in parser --- quafu/qfasm/qfasm_parser.py | 1223 ++++++++++++++++++++++++++++++----- 1 file changed, 1078 insertions(+), 145 deletions(-) diff --git a/quafu/qfasm/qfasm_parser.py b/quafu/qfasm/qfasm_parser.py index 9f97974..f50c8ed 100644 --- a/quafu/qfasm/qfasm_parser.py +++ b/quafu/qfasm/qfasm_parser.py @@ -1,270 +1,1203 @@ -import ply.yacc as yacc +# (C) Copyright 2023 Beijing Academy of Quantum Information Sciences +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. -from quafu.dagcircuits.instruction_node import InstructionNode -from .qfasmlex import QfasmLexer +import copy +import ply.yacc as yacc +from .qfasm_utils import * +from quafu.circuits.quantum_register import QuantumRegister +from quafu.qfasm.exceptions import ParserError +from .qfasm_lexer import QfasmLexer import numpy as np +from quafu import QuantumCircuit +from quafu.elements.quantum_element.quantum_element import * +from quafu.elements.quantum_element.classical_element import Cif -class DeclarationNode(object): - pass +unaryop = ["sin", "cos", "tan", "exp", "ln", "sqrt", "acos", "atan", "asin"] +unarynp = { + "sin": np.sin, + "cos": np.cos, + "tan": np.tan, + "exp": np.exp, + "ln": np.log, + "sqrt": np.sqrt, + "acos": np.arccos, + "atan": np.arctan, + "asin": np.arcsin, +} +reserved = [ + "creg", + "qreg", + "pi", + "measure", + "include", + "barrier", + "gate", + "opaque", + "reset", + "if", + "OPENQASM", +] -class QregNode(DeclarationNode): - def __init__(self, n): - self.n = n +class QfasmParser(object): + """OPENQASM2.0 Parser""" - def __repr__(self): - return self.__str__() + def __init__(self, filepath: str = None, debug=False): + self.lexer = QfasmLexer(filepath) + self.tokens = self.lexer.tokens + self.precedence = ( + ("left", "+", "-"), + ("left", "*", "/"), + ("right", "^"), + ("right", "UMINUS"), + ) + self.nuop = ["barrier", "reset", "measure"] + self.stdgate = list(gate_classes.keys()) + # extent keyword(the ) + self.stdgate.extend(["U", "CX"]) + self.parser = yacc.yacc(module=self, debug=debug) + # when there is reset/op after measure/if, set to false + self.executable_on_backend = True + self.has_measured = False + self.circuit = QuantumCircuit(0) + self.global_symtab = {} + self.add_U_CX() + # function argument symtab + self.symtab = {} + # qubit num used + self.qnum = 0 + # cbit num used + self.cnum = 0 - def __str__(self): - return "qreg[%d]" % self.n + def add_U_CX(self): + # Add U and CX in global_symtab + U_Id = Id("U", -1, None) + CX_Id = Id("CX", -1, None) + UNode = SymtabNode("GATE", U_Id) + UNode.fill_gate(qargs=[None], cargs=[None, None, None]) + CXNode = SymtabNode("GATE", CX_Id) + CXNode.fill_gate(qargs=[None, None], cargs=[]) + self.global_symtab["U"] = UNode + self.global_symtab["CX"] = CXNode + + # parse data + def parse(self, data, debug=False): + self.parser.parse(data, lexer=self.lexer, debug=debug) + if self.circuit is None: + raise ParserError("Exception in parser;") + return self.circuit + + def updateSymtab(self, symtabnode: SymtabNode): + # update Symtab + # reg + # print(symtabnode) + if symtabnode.name in reserved: + raise ParserError(f"Name cannot be reserved word:{reserved}") + if symtabnode.is_global: + if symtabnode.name in self.global_symtab: + hasnode = self.global_symtab[symtabnode.name] + raise ParserError( + f"Duplicate declaration for {symtabnode.name} at line {symtabnode.lineno} file {symtabnode.filename}", + f"First occureence at line {hasnode.lineno} file {hasnode.filename}", + ) + else: + # just for arg and qarg in gate declaration, so it can duplicate + if symtabnode.name in self.symtab: + hasnode = self.symtab[symtabnode.name] + raise ParserError( + f"Duplicate declaration for {symtabnode.name} at line {symtabnode.lineno} file {symtabnode.filename}" + ) + if symtabnode.type == "QREG": + symtabnode.start = self.qnum + self.qnum += symtabnode.num + # add QuantumRegister + if len(self.circuit.qregs) == 0: + self.circuit.qregs.append(QuantumRegister(self.qnum, name="q")) + else: + self.circuit.qregs[0] = QuantumRegister(self.qnum, name="q") + if symtabnode.type == "CREG": + symtabnode.start = self.cnum + self.cnum += symtabnode.num -class CregNode(DeclarationNode): - def __init__(self, n): - self.n = n + if symtabnode.is_global: + self.global_symtab[symtabnode.name] = symtabnode + else: + self.symtab[symtabnode.name] = symtabnode - def __repr__(self): - return self.__str__() + def handle_gateins(self, gateins: GateInstruction): + gate_list = [] + # end of recurse + if gateins.name in self.stdgate and gateins.name not in [ + "reset", + "barrier", + "measure", + ]: + args = [] + # add qubits to args, it's might be a qubit or a qreg + for qarg in gateins.qargs: + if isinstance(qarg, IndexedId): + # check qreg's num is the same + if len(args) >= 1 and len(args[0]) != 1: + raise ParserError( + f"The num of qreg's qubit is inconsistent at line{gateins.lineno} file{gateins.filename}." + ) + symnode = self.global_symtab[qarg.name] + args.append([symnode.start + qarg.num]) + elif isinstance(qarg, Id): + # check qreg's num is the same + symnode = self.global_symtab[qarg.name] + if len(args) >= 1 and symnode.num != len(args[0]): + raise ParserError( + f"The num of qreg's qubit is inconsistent at line{gateins.lineno} file{gateins.filename}." + ) + tempargs = [] + for i in range(symnode.num): + tempargs.append(symnode.start + i) + args.append(tempargs) - def __str__(self): - return "creg[%d]" % self.n + # call many times + for i in range(len(args[0])): + oneargs = [] + for arg in args: + oneargs.append(arg[i]) + # if it's U or CX + if gateins.name == "CX": + gateins.name = "cx" + if gateins.name == "U": + gate_list.append(gate_classes["rz"](*[*oneargs, gateins.cargs[2]])) + gate_list.append(gate_classes["ry"](*[*oneargs, gateins.cargs[0]])) + gate_list.append(gate_classes["rz"](*[*oneargs, gateins.cargs[1]])) + else: + # add carg to args if there is + if gateins.cargs is not None and len(gateins.cargs) > 0: + oneargs.extend(gateins.cargs) + gate_list.append(gate_classes[gateins.name](*oneargs)) + # if op is barrier or reset or measure + elif gateins.name in ["reset", "barrier"]: + nametoclass = {"reset": Reset, "barrier": Barrier} + for qarg in gateins.qargs: + symnode = self.global_symtab[qarg.name] + if isinstance(qarg, Id): + qlist = [] + for i in range(symnode.num): + qlist.append(symnode.start + i) + gate_list.append(nametoclass[gateins.name](qlist)) + elif isinstance(qarg, IndexedId): + gate_list.append( + nametoclass[gateins.name]([symnode.start + qarg.num]) + ) -class IncludeNode(DeclarationNode): - def __init__(self, filename): - self.file = filename + # we have check the num of cbit and qbit + elif gateins.name == "measure": + bitmap = {} + qarg = gateins.qargs[0] + cbit = gateins.cbits[0] + symnode = self.global_symtab[qarg.name] + symnodec = self.global_symtab[cbit.name] + if isinstance(qarg, Id): + for i in range(symnode.num): + bitmap[symnode.start + i] = symnodec.start + i + elif isinstance(qarg, IndexedId): + bitmap[symnode.start + qarg.num] = symnodec.start + cbit.num + # gate_list.append(Measure(bitmap=bitmap)) + # TODO + self.circuit.measure(list(bitmap.keys()), list(bitmap.values())) + # if it's not a gate that can be trans to circuit gate, just recurse it + else: + gatenode: SymtabNode = self.global_symtab[gateins.name] + qargdict = {} + for i in range(len(gatenode.qargs)): + qargdict[gatenode.qargs[i].name] = gateins.qargs[i] + cargdict = {} + for i in range(len(gatenode.cargs)): + cargdict[gatenode.cargs[i].name] = gateins.cargs[i] + for ins in gatenode.instructions: + # cannot recurse itself! + if ins.name == gateins.name: + raise ParserError( + f"The gate {gateins.name} call itself, it's forbiddened at line {gateins.lineno} file {gateins.filename}" + ) + # change qarg/carg, no cbit in gate param + # deep copy + newins = copy.deepcopy(ins) + # change newins's qarg to real q + for i in range(len(newins.qargs)): + newins.qargs[i] = qargdict[newins.qargs[i].name] + # change newins's carg to real carg (consider exp) + for i in range(len(newins.cargs)): + if not ( + isinstance(newins.cargs[i], int) + or isinstance(newins.cargs[i], float) + ): + # for expression + newins.cargs[i] = self.compute_exp(newins.cargs[i], cargdict) + # now, recurse + gate_list.extend(self.handle_gateins(newins)) - def __repr__(self): - return self.__str__() + return gate_list - def __str__(self): - return "include %s" % self.file + def compute_exp(self, carg, cargdict: dict): + # recurse + if isinstance(carg, int) or isinstance(carg, float): + return carg + # if it's id, should get real number from gateins + elif isinstance(carg, Id): + return cargdict[carg.name] + elif isinstance(carg, UnaryExpr): + if carg.type == "-": + return -self.compute_exp(carg.children[0], cargdict) + elif carg.type in unaryop: + return unarynp[carg.type](self.compute_exp(carg.children[0], cargdict)) + elif isinstance(carg, BinaryExpr): + if carg.type == "+": + return self.compute_exp(carg.children[0], cargdict) + self.compute_exp( + carg.children[1], cargdict + ) + elif carg.type == "-": + return self.compute_exp(carg.children[0], cargdict) - self.compute_exp( + carg.children[1], cargdict + ) + elif carg.type == "*": + return self.compute_exp(carg.children[0], cargdict) * self.compute_exp( + carg.children[1], cargdict + ) + elif carg.type == "/": + return self.compute_exp(carg.children[0], cargdict) / self.compute_exp( + carg.children[1], cargdict + ) + elif carg.type == "^": + return self.compute_exp(carg.children[0], cargdict) ** self.compute_exp( + carg.children[1], cargdict + ) + def addInstruction(self, qc: QuantumCircuit, ins): + if ins is None: + return + if isinstance(ins, GateInstruction): + gate_list = self.handle_gateins(ins) + for gate in gate_list: + # print(self.circuit.num) + qc.add_gate(gate) + elif isinstance(ins, IfInstruction): + symtabnode = self.global_symtab[ins.cbits.name] + if isinstance(ins.cbits, Id): + cbit = [symtabnode.start, symtabnode.start + symtabnode.num] + else: + cbit = [symtabnode.start + ins.cbits.num] + # get quantum gate + gate_list = self.handle_gateins(ins.instruction) + qc.add_gate(Cif(cbit=cbit, condition=ins.value, instructions=gate_list)) + else: + raise ParserError(f"Unexpected exception when parse.") -class OPENQASMNode(DeclarationNode): - def __init__(self, version): - self.version = version + def check_measure_bit(self, gateins: GateInstruction): + cbit = gateins.cbits[0] + qarg = gateins.qargs[0] + cbit_num = 0 + qbit_num = 0 + # check qubit + if qarg.name not in self.global_symtab: + raise ParserError( + f"The qubit {qarg.name} is undefined in qubit register at line {qarg.lineno} file {qarg.filename}" + ) + symnode = self.global_symtab[qarg.name] + if symnode.type != "QREG": + raise ParserError( + f"{qarg.name} is not declared as qubit register at line {qarg.lineno} file {qarg.filename}" + ) + if isinstance(qarg, IndexedId): + qbit_num = 1 + if qarg.num + 1 > symnode.num: + raise ParserError( + f"Qubit arrays {qarg.name} are out of bounds at line {qarg.lineno} file {qarg.filename}" + ) + else: + qbit_num = symnode.num + # check cbit + if cbit.name not in self.global_symtab: + raise ParserError( + f"The classical bit {cbit.name} is undefined in classical bit register at line {cbit.lineno} file {cbit.filename}" + ) + symnode = self.global_symtab[cbit.name] + if symnode.type != "CREG": + raise ParserError( + f"{cbit.name} is not declared as classical bit register at line {cbit.lineno} file {cbit.filename}" + ) + if isinstance(cbit, IndexedId): + cbit_num = 1 + if cbit.num + 1 > symnode.num: + raise ParserError( + f"Classical bit arrays {cbit.name} are out of bounds at line {cbit.lineno} file {cbit.filename}" + ) + else: + cbit_num = symnode.num + # check qubits'num matches cbits's num + if cbit_num != qbit_num: + raise ParserError( + f"MEASURE: the num of qubit and clbit doesn't match at line {gateins.lineno} file {gateins.filename}" + ) - def __repr__(self): - return self.__str__() + def check_qargs(self, gateins: GateInstruction): + # check gatename declared + qargslist = [] + if gateins.name not in self.nuop: + if gateins.name not in self.global_symtab: + raise ParserError( + f"The gate {gateins.name} is undefined at line {gateins.lineno} file {gateins.filename}" + ) + # check if gateins.name is a gate + gatenote = self.global_symtab[gateins.name] + if gatenote.type != "GATE": + raise ParserError( + f"The {gateins.name} is not declared as a gate at line {gateins.lineno} file {gateins.filename}" + ) + # check args matches gate's declared args + if len(gateins.qargs) != len(gatenote.qargs): + raise ParserError( + f"The numbe of qubit declared in gate {gateins.name} is inconsistent with instruction at line {gateins.lineno} file {gateins.filename}" + ) + # check qubits must from global symtab + for qarg in gateins.qargs: + if qarg.name not in self.global_symtab: + raise ParserError( + f"The qubit {qarg.name} is undefined in qubit register at line {qarg.lineno} file {qarg.filename}" + ) + symnode = self.global_symtab[qarg.name] + if symnode.type != "QREG": + raise ParserError( + f"{qarg.name} is not declared as qubit register at line {qarg.lineno} file {qarg.filename}" + ) + # check if the qarg is out of bounds when qarg's type is indexed_id + if isinstance(qarg, IndexedId): + if qarg.num + 1 > symnode.num: + raise ParserError( + f"Qubit arrays {qarg.name} are out of bounds at line {qarg.lineno} file {qarg.filename}" + ) + qargslist.append((qarg.name, qarg.num)) + else: + for num in range(symnode.num): + qargslist.append((qarg.name, num)) + # check distinct qubits + if len(qargslist) != len(set(qargslist)): + raise ParserError( + f"Qubit used as different argument when call gate {gateins.name} at line {gateins.lineno} file {gateins.filename}" + ) - def __str__(self): - return "OPENQASM %.1f" % self.version + def check_cargs(self, gateins: GateInstruction): + # check that cargs belongs to unary (they must be int or float) + # cargs is different from CREG + if gateins.name not in self.nuop: + if gateins.name not in self.global_symtab: + raise ParserError( + f"The gate {gateins.name} is undefined at line {gateins.lineno} file {gateins.filename}" + ) + gatenote = self.global_symtab[gateins.name] + if gatenote.type != "GATE": + raise ParserError( + f"The {gateins.name} is not declared as a gate at line {gateins.lineno} file {gateins.filename}" + ) + # check every carg in [int, float] + for carg in gateins.cargs: + if not (isinstance(carg, int) or isinstance(carg, float)): + raise ParserError( + f"Classical argument must be of type int or float at line {gateins.lineno} file {gateins.filename}" + ) + # check cargs's num matches gate's delcared cargs + if len(gateins.cargs) != len(gatenote.cargs): + raise ParserError( + f"The number of classical argument declared in gate {gateins.name} is inconsistent with instruction at line {gateins.lineno} file {gateins.filename}" + ) + def check_gate_qargs(self, gateins: GateInstruction): + # check type and number + qargs = gateins.qargs + gatename = gateins.name + qargsname = [] + if gatename != "barrier": + # check gatename declared + if gatename not in self.global_symtab: + raise ParserError( + f"The gate {gatename} is undefined at line {gateins.lineno} file {gateins.filename}" + ) + gatenode = self.global_symtab[gatename] + if gatenode.type != "GATE": + raise ParserError( + f"The {gatename} is not declared as a gate at line {gateins.lineno} file {gateins.filename}" + ) + # check qarg's num matches gate's qargs, except barrier + if len(gatenode.qargs) != len(qargs): + raise ParserError( + f"The numbe of qubit declared in gate {gatename} is inconsistent with instruction at line {gateins.lineno} file {gateins.filename}" + ) + # check gate_op's qubit args, must from gate declared argument + for qarg in qargs: + qargsname.append(qarg.name) + # check qarg declaration + if qarg.name not in self.symtab: + raise ParserError( + f"The qubit {qarg.name} is undefined in gate qubit parameters at line {qarg.lineno} file {qarg.filename}" + ) + symnode = self.symtab[qarg.name] + if symnode.type != "QARG": + raise ParserError( + f"{qarg.name} is not declared as a qubit at line {qarg.lineno} file {qarg.filename}" + ) + if len(qargs) != len(set(qargsname)): + raise ParserError( + f"A qubit used as different argument when call gate {gateins.name} at line {gateins.lineno} file {gateins.filename}" + ) -class QfasmParser(object): - tokens = QfasmLexer.tokens + def check_gate_cargs(self, gateins: GateInstruction): + # check gate_op's classcal args, must matches num declared by gate + if gateins.name == "barrier" and len(gateins.cargs) > 0: + raise ParserError( + f"Barrier can not receive classical argument at line {gateins.lineno} file {gateins.filename}" + ) + if gateins.name != "barrier": + if gateins.name not in self.global_symtab: + raise ParserError( + f"The gate {gateins.name} is undefined at line {gateins.lineno} file {gateins.filename}" + ) + gatenode = self.global_symtab[gateins.name] + if gatenode.type != "GATE": + raise ParserError( + f"The {gateins.name} is not declared as a gate at line {gateins.lineno} file {gateins.filename}" + ) + if len(gateins.cargs) != len(gatenode.cargs): + raise ParserError( + f"The number of classical argument declared in gate {gateins.name} is inconsistent with instruction at line {gateins.lineno} file {gateins.filename}" + ) + # check carg must from gate declared argument or int/float + for carg in gateins.cargs: + # recurse check expression + self.check_carg_declartion(carg) - def __init__(self, debug=False): - self.parser = yacc.yacc(module=self, debug=debug) - self.parsed_nodes = [] - self.lexer = QfasmLexer() + def check_carg_declartion(self, node): + if isinstance(node, int) or isinstance(node, float): + return + if isinstance(node, Id): + # check declaration + if node.name not in self.symtab: + raise ParserError( + f"The classical argument {node.name} is undefined at line {node.lineno} file {node.filename}" + ) + symnode = self.symtab[node.name] + if symnode.type != "CARG": + raise ParserError( + f"The {node.name} is not declared as a classical bit at line {node.lineno} file {node.filename}" + ) + return + if isinstance(node, UnaryExpr): + self.check_carg_declartion(node.children[0]) + elif isinstance(node, BinaryExpr): + for i in range(2): + self.check_carg_declartion(node.children[i]) - def parse(self, input: str): - self.parsed_nodes = self.parser.parse(input, lexer=QfasmLexer()) - return self.parsed_nodes + start = "main" - def p_main_0(self, p): + def p_main(self, p): """ main : program """ - p[0] = [p[1]] + # now get the root node, return Citcuit + self.circuit.executable_on_backend = self.executable_on_backend - def p_main_1(self, p): + # when reduce statement into program, insert it to circuit and update symtab if it can. + def p_program(self, p): """ - main : main program + program : statement """ - p[1].append(p[2]) - p[0] = p[1] + p[0] = self.circuit + self.addInstruction(p[0], p[1]) - def p_program(self, p): + def p_program_list(self, p): """ - program : instruction - | declaration + program : program statement """ p[0] = p[1] + self.addInstruction(p[0], p[2]) + + # statement |= qdecl | gatedecl | qop | if | barrier + + # statement + # error -> ply.lex.LexToken(value) + # others -> p -> ply.yacc.YaccProduction + # -> p[1] -> t.value + def p_statement_openqasm(self, p): + """ + statement : OPENQASM FLOAT ';' + | OPENQASM FLOAT error + | OPENQASM error + """ + if len(p) == 3: + raise ParserError(f"Expecting FLOAT after OPENQASM, received {p[2].value}") + if p[3] != ";": + raise ParserError("Expecting ';' at end of OPENQASM statement") + if p[2] != 2.0: + raise ParserError("Only support OPENQASM 2.0 version") + p[0] = None - def p_declaration(self, p): + # qop + def p_statement_qop(self, p): """ - declaration : openqasm - | include - | qreg - | creg + statement : qop ';' + | qop error + | qif ';' + | qif error """ + if p[2] != ";": + raise ParserError(f"Expecting ';' behind statement") p[0] = p[1] - def p_openqasm(self, p): + def p_statement_empty(self, p): """ - openqasm : OPENQASM FLOAT ';' + statement : ';' """ - p[0] = OPENQASMNode(p[2]) + p[0] = None - def p_include(self, p): + def p_statement_qif(self, p): """ - include : INCLUDE STRING ';' + qif : IF '(' primary MATCHES INT ')' qop + | IF '(' primary MATCHES INT error + | IF '(' primary MATCHES error + | IF '(' primary error + | IF '(' error + | IF error """ - p[0] = IncludeNode(p[2]) + # check primary is a creg and check range + if len(p) == 7: + raise ParserError( + f"Illegal IF statement, Expecting ')' at line {p[1].lineno} file {p[1].filename}" + ) + if len(p) == 6: + raise ParserError( + f"Illegal IF statement, Expecting INT: the Rvalue can only be INT at line {p[1].lineno} file {p[1].filename}" + ) + if len(p) == 5: + raise ParserError( + f"Illegal IF statement, Expecting '==' at line {p[1].lineno} file {p[1].filename}" + ) + if len(p) == 4: + raise ParserError( + f"Illegal IF statement, Expecting Cbit: the Lvalue can only be cbit at line {p[1].lineno} file {p[1].filename}" + ) + if len(p) == 3: + raise ParserError( + f"Illegal IF statement, Expecting '(' at line {p[1].lineno} file {p[1].filename}" + ) + + cbit = p[3] + if cbit.name not in self.global_symtab: + raise ParserError( + f"The classical bit {cbit.name} is undefined in classical bit register at line {cbit.lineno} file {cbit.filename}" + ) + symnode = self.global_symtab[cbit.name] + if symnode.type != "CREG": + raise ParserError( + f"{cbit.name} is not declared as classical bit register at line {cbit.lineno} file {cbit.filename}" + ) + # check range if IndexedId + if isinstance(cbit, IndexedId): + if cbit.num >= symnode.num: + raise ParserError( + f"{cbit.name} out of range at line {cbit.lineno} file {cbit.filename}" + ) + # optimization: If the value that creg can represent is smaller than Rvalue, just throw it + if p[5] > 2: + p[0] = None + else: + p[0] = IfInstruction( + node=p[1], cbits=p[3], value=p[5], instruction=p[7] + ) + elif isinstance(cbit, Id): + # optimization: If the value that creg can represent is smaller than Rvalue, just throw it + num = symnode.num + if pow(2, num) - 1 < p[5]: + p[0] = None + else: + p[0] = IfInstruction( + node=p[1], cbits=p[3], value=p[5], instruction=p[7] + ) + self.executable_on_backend = False - def p_qreg(self, p): # TODO:verify register name + def p_unitaryop(self, p): """ - qreg : QREG bitreg ';' + qop : id primary_list + | id '(' ')' primary_list + | id '(' expression_list ')' primary_list """ - p[0] = QregNode(p[2]) + # return circuit gate instance + if len(p) == 5: + p[0] = GateInstruction(node=p[1], qargs=p[4], cargs=[]) + if len(p) == 3: + p[0] = GateInstruction(node=p[1], qargs=p[2], cargs=[]) + if len(p) == 6: + p[0] = GateInstruction(node=p[1], qargs=p[5], cargs=p[3]) + # check args + self.check_qargs(p[0]) + self.check_cargs(p[0]) + if self.has_measured: + self.executable_on_backend = False - def p_creg(self, p): + def p_unitaryop_error(self, p): """ - creg : CREG bitreg ';' + qop : id '(' ')' error + | id '(' error + | id '(' expression_list ')' error + | id '(' expression_list error """ - p[0] = CregNode(p[2]) + if len(p) == 4 or (len(p) == 5 and p[4] != ")"): + raise ParserError( + f"Expecting ')' after '(' at line {p[1].lineno} file {p[1].filename}" + ) + raise ParserError( + f"Expecting qubit list, received {p[len(p)-1].value} at line {p[1].lineno} file {p[1].filename}" + ) - def p_instruction(self, p): + # measure + def p_measure(self, p): """ - instruction : gate ';' - | pulse ';' - | measure ';' + qop : MEASURE primary ASSIGN primary + """ + # check and return gateInstruction + p[0] = GateInstruction(node=p[1], qargs=[p[2]], cargs=[], cbits=[p[4]]) + self.check_measure_bit(p[0]) + self.has_measured = True + + def p_measure_error(self, p): + """ + qop : MEASURE primary ASSIGN error + | MEASURE primary error + | MEASURE error + """ + if len(p) == 5: + raise ParserError( + f"Expecting qubit or qubit register after '->' at line {p[1].lineno} file {p[1].filename}" + ) + if len(p) == 4: + raise ParserError( + f"Expecting '->' for MEASURE at line {p[1].lineno} file {p[1].filename}" + ) + if len(p) == 3: + raise ParserError( + f"Expecting qubit or qubit register after 'measure' at line {p[1].lineno} file {p[1].filename}" + ) + + # barrier + def p_barrier(self, p): + """ + qop : BARRIER primary_list + """ + # check and return gateInstruction + p[0] = GateInstruction(node=p[1], qargs=p[2], cargs=[]) + self.check_qargs(p[0]) + + def p_barrier_error(self, p): + """ + qop : BARRIER error + """ + raise ParserError( + f"Expecting Qubit:BARRIER only opperate qubits at line {p[1].lineno} file {p[1].filename}" + ) + + # reset + def p_reset(self, p): + """ + qop : RESET primary + """ + p[0] = GateInstruction(node=p[1], qargs=[p[2]], cargs=[]) + self.check_qargs(p[0]) + self.executable_on_backend = False + + def p_reset_error(self, p): + """ + qop : RESET error + """ + raise ParserError( + f"Expecting Qubit: RESET only opperate qubit at line {p[1].lineno} file {p[1].filename}" + ) + + # gate_qarg_list + def p_gate_qarg_list_begin(self, p): + """ + qarg_list : id + """ + p[0] = [p[1]] + newsymtabnode = SymtabNode("QARG", p[1], False, True) + self.updateSymtab(newsymtabnode) + + def p_gate_qarg_list_next(self, p): + """ + qarg_list : qarg_list ',' id """ p[0] = p[1] + p[0].append(p[3]) + newsymtabnode = SymtabNode("QARG", p[3], False, True) + self.updateSymtab(newsymtabnode) - def p_arg_list_0(self, p): + # gate_carg_list + def p_gate_carg_list_begin(self, p): """ - arg_list : expression + carg_list : id """ p[0] = [p[1]] + newsymtabnode = SymtabNode("CARG", p[1], False) + self.updateSymtab(newsymtabnode) - def p_arg_list_1(self, p): + def p_gate_carg_list_next(self, p): """ - arg_list : arg_list ',' expression + carg_list : carg_list ',' id """ - p[1].append(p[3]) p[0] = p[1] + p[0].append(p[3]) + newsymtabnode = SymtabNode("CARG", p[3], False) + self.updateSymtab(newsymtabnode) - def p_gate_like_0(self, p): + # gatedecl + def p_statement_gatedecl_nolr(self, p): """ - gate : id qubit_list + statement : GATE id gate_scope qarg_list gate_body + | GATE error + | GATE id gate_scope error + | GATE id gate_scope qarg_list error """ - p[0] = InstructionNode(p[1], p[2], None, None, None, "", None, "") + if len(p) == 3: + raise ParserError( + f"Expecting ID after 'gate', received {p[len(p)-1].value} at line {p[1].lineno} file {p[1].filename}" + ) + if len(p) == 5: + raise ParserError( + f"Expecting '(' or qubit list after gate name, received {p[len(p)-1].value} at line {p[1].lineno} file {p[1].filename}" + ) + if len(p) == 6 and not isinstance(p[5], list): + raise ParserError( + f"Expecting gate body qubit list, received {p[len(p)-1].value} at line {p[1].lineno} file {p[1].filename}" + ) + newsymtabnode = SymtabNode("GATE", p[2]) + newsymtabnode.fill_gate(p[4], p[5]) + self.updateSymtab(newsymtabnode) - def p_gate_like_1(self, p): + def p_statement_gatedecl_noargs(self, p): """ - gate : id '(' arg_list ')' qubit_list + statement : GATE id gate_scope '(' ')' qarg_list gate_body + | GATE id gate_scope '(' error + | GATE id gate_scope '(' ')' error + | GATE id gate_scope '(' ')' qarg_list error """ - p[0] = InstructionNode(p[1], p[5], p[3], None, None, "", None, "") + if len(p) == 6: + raise ParserError( + f"Expecting ')' or argument list after '(', received {p[len(p)-1].value} at line {p[1].lineno} file {p[1].filename}" + ) + if len(p) == 7: + raise ParserError( + f"Expecting qubit list after ')', received {p[len(p)-1].value} at line {p[1].lineno} file {p[1].filename}" + ) + if len(p) == 8 and not isinstance(p[7], list): + raise ParserError( + f"Expecting gate body after qubit list, received {p[len(p)-1].value} at line {p[1].lineno} file {p[1].filename}" + ) + newsymtabnode = SymtabNode("GATE", p[2]) + newsymtabnode.fill_gate(p[6], p[7]) + self.updateSymtab(newsymtabnode) - def p_pulse_like_0(self, p): + def p_statement_gatedecl_args(self, p): """ - pulse : id '(' time ',' arg_list ')' qubit_list + statement : GATE id gate_scope '(' carg_list ')' qarg_list gate_body + | GATE id gate_scope '(' carg_list ')' qarg_list error + | GATE id gate_scope '(' carg_list ')' error + | GATE id gate_scope '(' carg_list error """ - p[0] = InstructionNode(p[1], p[7], p[5], p[3][0], p[3][1], "", None, "") + if len(p) == 7: + raise ParserError( + f"Expecting ')' after argument list, received {p[len(p)-1].value} at line {p[1].lineno} file {p[1].filename}" + ) + if len(p) == 8: + raise ParserError( + f"Expecting qubit list after ')', received {p[len(p)-1].value} at line {p[1].lineno} file {p[1].filename}" + ) + if len(p) == 9 and not isinstance(p[8], list): + raise ParserError( + f"Expecting gate body after qubit list, received {p[len(p)-1].value} at line {p[1].lineno} file {p[1].filename}" + ) + if len(p) != 9: + raise ParserError(f"Invaild GATE statement") + newsymtabnode = SymtabNode("GATE", p[2]) + newsymtabnode.fill_gate(p[7], p[8], p[5]) + self.updateSymtab(newsymtabnode) - def p_measure_0(self, p): + def p_gate_scope(self, _): """ - measure : MEASURE bitreg ASSIGN bitreg + gate_scope : """ - p[0] = InstructionNode("measure", {p[2]: p[4]}, None, None, None, "", None, "") + self.symtab = {} - def p_pulse_like_1(self, p): + # gatebody + def p_gate_body_emptybody(self, p): """ - pulse : id '(' time ',' arg_list ',' channel ')' qubit_list + gate_body : '{' gate_scope '}' + | '{' gate_scope error """ - p[0] = InstructionNode(p[1], p[9], p[5], p[3][0], p[3][1], p[7], None, "") + if p[3] != "}": + raise ParserError( + "Expecting '}' at the end of gate definition; received " + p[3].value + ) + p[0] = [] - def p_bitreg(self, p): + def p_gate_body(self, p): """ - bitreg : id '[' INT ']' + gate_body : '{' gop_list gate_scope '}' + | '{' gop_list gate_scope error """ - p[0] = p[3] + if p[4] != "}": + raise ParserError( + "Expecting '}' at the end of gate definition; received " + p[4].value + ) + p[0] = p[2] - def p_qubit_list_0(self, p): + def p_gop_list_begin(self, p): """ - qubit_list : bitreg + gop_list : gop """ p[0] = [p[1]] - def p_qubit_list_1(self, p): + def p_gop_list_next(self, p): """ - qubit_list : qubit_list ',' bitreg + gop_list : gop_list gop """ - p[1].append(p[3]) p[0] = p[1] + p[0].append(p[2]) + + # gop + # CX | U | ID(cargs)qargs | reset | + def p_gop_nocargs(self, p): + """ + gop : id id_list ';' + | id id_list error + | id '(' ')' id_list ';' + | id '(' ')' id_list error + | id '(' ')' error + | id '(' error + """ + if len(p) == 4 and p[2] == "(": + raise ParserError( + f"Expecting ')' for gate {p[1].name} at line {p[1].lineno} file {p[1].filename}" + ) + if len(p) == 4 and p[3] != ";": + raise ParserError( + f"Expecting ';' after gate {p[1].name} at line {p[1].lineno} file {p[1].filename}" + ) + if len(p) == 6 and p[5] != ";": + raise ParserError( + f"Expecting ';' after gate {p[1].name} at line {p[1].lineno} file {p[1].filename}" + ) + if len(p) == 5: + raise ParserError( + f"Expecting ID: Invalid qubit list for gate {p[1].name} at line {p[1].lineno} file {p[1].filename}" + ) + qargs = p[2] if len(p) == 4 else p[4] + p[0] = GateInstruction(node=p[1], qargs=qargs, cargs=[]) + self.check_gate_qargs(p[0]) + self.check_gate_cargs(p[0]) + + def p_gop_cargs(self, p): + """ + gop : id '(' expression_list ')' id_list ';' + | id '(' expression_list ')' id_list error + | id '(' expression_list ')' error + | id '(' expression_list error + """ + if len(p) == 7 and p[6] != ";": + raise ParserError( + f"Expecting ';' after gate {p[1].name} at line {p[1].lineno}" + ) + if len(p) == 6: + raise ParserError( + f"Expecting qubit id after gate {p[1].name} at line {p[1].lineno}" + ) + if len(p) == 5: + raise ParserError( + f"Expecting ')' after gate {p[1].name} at line {p[1].lineno}" + ) + p[0] = GateInstruction(node=p[1], qargs=p[5], cargs=p[3]) + # check qubit + self.check_gate_qargs(p[0]) + # check expression_list + self.check_gate_cargs(p[0]) + + def p_gop_barrier(self, p): + """ + gop : BARRIER id_list ';' + | BARRIER id_list error + | BARRIER error + """ + if len(p) == 3: + raise ParserError( + f"Expecting ID: Invalid barrier qubit list inside gate definition, at line {p[1].lineno} file {p[1].filename}" + ) + if len(p) == 4 and p[3] != ";": + raise ParserError( + f"Expecting ';' after barrier at line {p[1].lineno} file {p[1].filename}" + ) + p[0] = GateInstruction(node=p[1], qargs=p[2], cargs=[]) + self.check_gate_qargs(p[0]) - def p_channel(self, p): + # regdecl + def p_statement_bitdecl(self, p): """ - channel : CHANNEL + statement : qdecl ';' + | cdecl ';' + | qdecl error + | cdecl error + | error """ + if len(p) == 2: + raise ParserError(f"Expecting valid statement") + if p[2] != ";": + raise ParserError( + f"Expecting ';' in qreg or creg declaration at line {p.lineno(2)}" + ) p[0] = p[1] - def p_time(self, p): + def p_qdecl(self, p): """ - time : INT UNIT + qdecl : QREG indexed_id + | QREG error """ - p[0] = (p[1], p[2]) + if not isinstance(p[2], IndexedId): + raise ParserError( + f"Expecting ID[int] after QREG at line {p[1].lineno} file {p[1].filename}, received {p[2].value}" + ) + if p[2].num <= 0: + raise ParserError( + f"QREG size must be positive at line {p[2].lineno} file {p[2].filename}" + ) + newsymtabnode = SymtabNode("QREG", p[2], True, True) + self.updateSymtab(newsymtabnode) + p[0] = None - def p_expression_none(self, p): + def p_cdecl(self, p): """ - expression : NONE + cdecl : CREG indexed_id + | CREG error """ + if not isinstance(p[2], IndexedId): + raise ParserError( + f"Expecting ID[int] after CREG at line {p[1].lineno} file {p[1].filename}, received {p[2].value}" + ) + if p[2].num <= 0: + raise ParserError( + f"CREG size must be positive at line {p[2].lineno} file {p[2].filename}" + ) + newsymtabnode = SymtabNode("CREG", p[2], True, False) + self.updateSymtab(newsymtabnode) + p[0] = None + + # id + def p_id(self, p): + """ + id : ID + | error + """ + # It's instance of Id class, passed from t.value + if not isinstance(p[1], Id): + raise ParserError(f"Expecting an ID, received {str(p[1].value)}") p[0] = p[1] - def p_expression_term(self, p): - "expression : term" + # indexed_id + def p_indexed_id(self, p): + """ + indexed_id : id '[' INT ']' + | id '[' INT error + | id '[' error + """ + if len(p) == 4 or (len(p) == 5 and p[4] != "]"): + raise ParserError( + f"Expecting INT after [, received{str(p[len(p)-1].value)}" + ) + if len(p) == 5 and p[4] == "]": + p[0] = IndexedId(p[1], p[3]) + + # primary + # only q or q[], used for U CX measure reset + def p_primary(self, p): + """ + primary : id + | indexed_id + """ p[0] = p[1] - def p_expression_m(self, p): + # primary_list + # the anylist in bnf + def p_primary_list(self, p): + """ + primary_list : primary + | primary_list ',' primary + """ + if len(p) == 2: + p[0] = [p[1]] + else: + p[0] = p[1] + p[0].append(p[3]) + + # id_list + # for decl gate + def p_id_list_begin(self, p): + """ + id_list : id + """ + p[0] = [p[1]] + + def p_id_list_next(self, p): + """ + id_list : id_list ',' id + """ + p[0] = p[1] + p[0].append(p[3]) + + # unary + def p_unary_int(self, p): + """ + unary : INT + """ + p[0] = int(p[1]) + + def p_unary_float(self, p): + """ + unary : FLOAT + """ + p[0] = float(p[1]) + + def p_unary_pi(self, p): """ - expression : '-' expression + unary : PI """ - p[0] = -p[2] + p[0] = np.pi - def p_term_factor(self, p): + # id from ID + def p_unary_id(self, p): """ - term : factor + unary : id """ p[0] = p[1] - def p_binary_operators(self, p): - """expression : expression '+' term - | expression '-' term - term : term '*' factor - | term '/' factor""" - if p[2] == "+": - p[0] = p[1] + p[3] - elif p[2] == "-": - p[0] = p[1] - p[3] - elif p[2] == "*": - p[0] = p[1] * p[3] - elif p[2] == "/": - p[0] = p[1] / p[3] - - def p_factor_0(self, p): - """ - factor : FLOAT - | INT + # expr + def p_expr_binary(self, p): + """ + expression : expression '*' expression + | expression '/' expression + | expression '+' expression + | expression '-' expression + | expression '^' expression + """ + if p[2] == "/" and p[3] == 0: + raise ParserError( + f"Divided by 0 at line {self.lexer.lexer.lineno} file {self.lexer.lexer.filename}" + ) + if isinstance(p[1], Node) or isinstance(p[3], Node): + p[0] = BinaryExpr(p[2], p[1], p[3]) + else: + # int or float + if p[2] == "*": + p[0] = p[1] * p[3] + elif p[2] == "/": + p[0] = p[1] / p[3] + elif p[2] == "^": + p[0] = p[1] ** p[3] + elif p[2] == "+": + p[0] = p[1] + p[3] + elif p[2] == "-": + p[0] = p[1] - p[3] + + def p_expr_uminus(self, p): + """ + expression : - expression %prec UMINUS + """ + if isinstance(p[2], Node): + p[0] = UnaryExpr("-", p[2]) + else: + # int or float + p[0] = -p[2] + + def p_expr_unary(self, p): + """ + expression : unary """ p[0] = p[1] - def p_factor_1(self, p): + def p_expr_pare(self, p): """ - factor : '(' expression ')' + expression : '(' expression ')' """ p[0] = p[2] - def p_factor_pi(self, p): + def p_expr_mathfunc(self, p): """ - factor : PI + expression : id '(' expression ')' """ - p[0] = np.pi + if p[1].name not in unaryop: + raise ParserError( + f"Math function {p[1].name} not supported, only support {unaryop} line {p[1].lineno} file {p[1].filename}" + ) + if not isinstance(p[3], Node): + p[0] = unarynp[p[1].name](p[3]) + else: + p[0] = UnaryExpr(p[1].name, p[3]) - def p_id(self, p): - """id : ID""" + # Exprlist + def p_exprlist_begin(self, p): + """ + expression_list : expression + """ + p[0] = [p[1]] + + def p_exprlist_next(self, p): + """ + expression_list : expression_list ',' expression + """ p[0] = p[1] + p[0].append(p[3]) + + # Only filename provide string + # So, It will never get a string in parser + def p_ignore(self, _): + """ + ignore : STRING + """ + pass + + def p_empty(self, p): + """ + empty : + """ + pass def p_error(self, p): - if p: - print("Syntax error at token", p.type) - # Just discard the token and tell the parser it's okay. - self.parser.errok() - else: - print("Syntax error at EOF") + # EOF case + if p is None or self.lexer.lexer.token() is None: + raise ParserError("Error at end of file") + print( + f"Error near line {self.lexer.lexer.lineno}, Column {self.cal_column(self.lexer.data, p)}" + ) + + def cal_column(self, data: str, p): + "Compute the column" + begin_of_line = data.rfind("\n", 0, p.lexpos) + begin_of_line = max(0, begin_of_line) + column = p.lexpos - begin_of_line + 1 + return column From e4b7df1859c4246fc39ec018279ad3f16171a4d2 Mon Sep 17 00:00:00 2001 From: beizha Date: Sat, 16 Sep 2023 18:38:03 +0800 Subject: [PATCH 09/15] Change the way convert qasm to circuit with PLY Parser --- quafu/qfasm/qfasmlex.py | 112 ---------------------------------------- 1 file changed, 112 deletions(-) delete mode 100644 quafu/qfasm/qfasmlex.py diff --git a/quafu/qfasm/qfasmlex.py b/quafu/qfasm/qfasmlex.py deleted file mode 100644 index a12497e..0000000 --- a/quafu/qfasm/qfasmlex.py +++ /dev/null @@ -1,112 +0,0 @@ -# Qfasm is modified OPENQASM 2.0. Currently it is mainly used to -# transfer circuit data to backends. Further it will -# support more instructions (classical or quantum) to enable -# interaction with quantum hardware - -import ply.lex as lex -import numpy as np - - -class QfasmLexer(object): - def __init__(self): - self.build() - - def input(self, data): - self.data = data - self.lexer.input(data) - - def token(self): - ret = self.lexer.token() - return ret - - literals = r'=()[]{};<>,.+-/*^"' - - reserved = { - "creg": "CREG", - "qreg": "QREG", - "pi": "PI", - "measure": "MEASURE", - "include": "INCLUDE", - } - - tokens = [ - "FLOAT", - "INT", - "STRING", - "ASSIGN", - "MATCHES", - "ID", - "UNIT", - "CHANNEL", - "OPENQASM", - "NONE", - ] + list(reserved.values()) - - def t_FLOAT(self, t): - r"(([1-9]\d*\.\d*)|(0\.\d*[1-9]\d*))" - t.value = float(t.value) - return t - - def t_INT(self, t): - r"\d+" - t.value = int(t.value) - return t - - def t_STRING(self, t): - r"\"([^\\\"]|\\.)*\" " - return t - - def t_ASSIGN(self, t): - r"->" - return t - - def t_MATCHES(self, t): - r"==" - return t - - def t_UNIT(self, t): - r"ns|us" - return t - - def t_CHANNEL(self, t): - r"XY|Z" - return t - - def t_OPENQASM(self, t): - r"OPENQASM" - return t - - def t_NONE(self, t): - r"None" - return t - - def t_ID(self, t): - r"[a-z][a-zA-Z0-9_]*" - t.type = self.reserved.get(t.value, "ID") - return t - - t_ignore = " \t\r" - - def t_error(self, t): - print("Illegal character '%s'" % t.value[0]) - - def t_newline(self, t): - r"\n+" - t.lexer.lineno += len(t.value) - - def build(self, **kwargs): - self.lexer = lex.lex(module=self, **kwargs) - - def test(self, data): - self.lexer.input(data) - while True: - tok = self.lexer.token() - if not tok: - break - print(tok) - - -if __name__ == "__main__": - m = QfasmLexer() - m.build() - m.test("rx(21ns, pi) q[0], q[1]") From b038cec3dad419bd3fd5071c0b255e3c147b398f Mon Sep 17 00:00:00 2001 From: beizha Date: Sat, 16 Sep 2023 18:38:44 +0800 Subject: [PATCH 10/15] Add support for PLY Parser --- quafu/circuits/quantum_circuit.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/quafu/circuits/quantum_circuit.py b/quafu/circuits/quantum_circuit.py index c9bc451..d8d75a1 100644 --- a/quafu/circuits/quantum_circuit.py +++ b/quafu/circuits/quantum_circuit.py @@ -18,6 +18,7 @@ import quafu.elements.element_gates as qeg from quafu.elements.quantum_element.pulses.quantum_pulse import QuantumPulse +from quafu.elements.quantum_element.quantum_element import Reset from ..elements.quantum_element import ( Barrier, Delay, @@ -44,6 +45,7 @@ def __init__(self, num: int, *args, **kwargs): self.openqasm = "" self.circuit = [] self.measures = {} + self.executable_on_backend = True self._used_qubits = [] self._parameterized_gates = [] @@ -639,6 +641,19 @@ def delay(self, pos, duration, unit="ns") -> "QuantumCircuit": self.add_gate(Delay(pos, duration, unit=unit)) return self + def reset(self, qlist:List[int]= None) -> "QuantumCircuit": + """ + Add reset for qubits in qlist. + + Args: + qlist (list[int]): A list contain the qubit need add reset. When qlist contain at least two qubit, the barrier will be added from minimum qubit to maximum qubit. For example: barrier([0, 2]) create barrier for qubits 0, 1, 2. To create discrete barrier, using barrier([0]), barrier([2]). + """ + if qlist is None: + qlist = list(range(self.num)) + self.add_gate(Reset(qlist)) + self.executable_on_backend = False + return self + def xy(self, qs: int, qe: int, duration: int, unit: str = "ns") -> "QuantumCircuit": """ XY resonance time evolution for quantum simulator @@ -722,7 +737,7 @@ def unitary(self, matrix: np.ndarray, pos: List[int]): """ compiler = qeg.UnitaryDecomposer(array=matrix, qubits=pos) compiler.apply_to_qc(self) - + def measure(self, pos: List[int] = None, cbits: List[int] = None) -> None: """ Measurement setting for experiment device. From 821491ddab15ef59e47061ae05ccb3ad9b4a49e6 Mon Sep 17 00:00:00 2001 From: beizha Date: Sat, 16 Sep 2023 18:39:59 +0800 Subject: [PATCH 11/15] Add classical op cif --- quafu/elements/quantum_element/classical_element.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 quafu/elements/quantum_element/classical_element.py diff --git a/quafu/elements/quantum_element/classical_element.py b/quafu/elements/quantum_element/classical_element.py new file mode 100644 index 0000000..f135199 --- /dev/null +++ b/quafu/elements/quantum_element/classical_element.py @@ -0,0 +1,9 @@ + +class Cif: + name = 'if' + pos = 0 + def __init__(self, cbit, condition, instructions): + # cbit can be a list of cbit or just a cbit + self.cbit = cbit + self.cond = condition + self.instructions = instructions From 29b454ef7673cb38ccdab9f07a5ab72b6846201e Mon Sep 17 00:00:00 2001 From: beizha Date: Sat, 16 Sep 2023 18:40:31 +0800 Subject: [PATCH 12/15] Add reset gate --- .../quantum_element/quantum_element.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/quafu/elements/quantum_element/quantum_element.py b/quafu/elements/quantum_element/quantum_element.py index aa0a8c8..362ad93 100644 --- a/quafu/elements/quantum_element/quantum_element.py +++ b/quafu/elements/quantum_element/quantum_element.py @@ -44,6 +44,29 @@ def to_qasm(self): ) +class Reset(Instruction): + name = 'reset' + + def __init__(self, pos): + super().__init__(pos) + + @property + def pos(self): + return self.__pos + + @pos.setter + def pos(self, pos): + self.__pos = pos + + def __repr__(self): + return f"{self.__class__.__name__}" + + def to_qasm(self): + return "reset " + ",".join( + ["q[%d]" % p for p in range(min(self.pos), max(self.pos) + 1)] + ) + + class Delay(Instruction): name = "delay" @@ -94,3 +117,4 @@ def __init__(self, bitmap: dict): Instruction.register_ins(Delay) Instruction.register_ins(XYResonance) Instruction.register_ins(Measure) +Instruction.register_ins(Reset) From c57925335738fb3269385b8578dd2220b156cfc9 Mon Sep 17 00:00:00 2001 From: beizha Date: Sat, 16 Sep 2023 18:45:34 +0800 Subject: [PATCH 13/15] Fix the issue that not compatible with python3.8 --- quafu/visualisation/circuitPlot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/quafu/visualisation/circuitPlot.py b/quafu/visualisation/circuitPlot.py index 1c151a6..0321e30 100644 --- a/quafu/visualisation/circuitPlot.py +++ b/quafu/visualisation/circuitPlot.py @@ -226,7 +226,7 @@ def _circuit_wires(self): x1 = self.xs[-1] - 1 self._h_wire_points.append([[x0, y], [x1, y]]) - def _inits_label(self, labels: dict[int: str] = None): + def _inits_label(self, labels: dict = None): """ qubit-labeling """ if labels is None: labels = self.q_label @@ -241,7 +241,7 @@ def _inits_label(self, labels: dict[int: str] = None): ) self._text_list.append(txt) - def _measured_label(self, labels: dict[int: str] = None): + def _measured_label(self, labels: dict = None): """ measured qubit-labeling """ if labels is None: labels = self.c_label @@ -553,4 +553,4 @@ def _render_circuit(self): self._render_measure() self._render_barrier() self._render_closed_patch() - self._render_txt() + self._render_txt() \ No newline at end of file From ec1ea1c71a23a01b4f8dcd74ccfe564926334335 Mon Sep 17 00:00:00 2001 From: beizha Date: Sat, 16 Sep 2023 18:48:06 +0800 Subject: [PATCH 14/15] Add licence header --- quafu/elements/quantum_element/classical_element.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/quafu/elements/quantum_element/classical_element.py b/quafu/elements/quantum_element/classical_element.py index f135199..a4d32af 100644 --- a/quafu/elements/quantum_element/classical_element.py +++ b/quafu/elements/quantum_element/classical_element.py @@ -1,3 +1,16 @@ +# (C) Copyright 2023 Beijing Academy of Quantum Information Sciences +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. class Cif: name = 'if' From 494c7e36f7cd264b1575aeb52a9c4b3f3960c485 Mon Sep 17 00:00:00 2001 From: beizha Date: Sat, 16 Sep 2023 18:58:19 +0800 Subject: [PATCH 15/15] add support for include data file --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d968675..dc6a43a 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ package_data={"quafu":["qfasm/*.inc"]}, long_description=long_description, long_description_content_type="text/markdown", - extras_require={"test": ["pytest"]}, + extras_require={"test": ["pytest"]}, python_requires=">=3.8", zip_safe=False, setup_cfg=True,