diff --git a/QGL/qasm/create_ast.py b/QGL/qasm/create_ast.py new file mode 100644 index 00000000..46740f75 --- /dev/null +++ b/QGL/qasm/create_ast.py @@ -0,0 +1,159 @@ +""" +Original Author: Guilhem Ribeill + +Copyright 2020 Raytheon BBN Technologies + +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 os, sys +from lark import Lark, tree, ast_utils, Transformer, v_args +from lark.tree import Meta +import ast + +py=""" +pots = 1000 +pans = 2000 +pens = 2001 +fens = pens + 1 +fens += 1 +q1 = qubit() +""" +print("---- Python AST -----") +# print("Python AST:", tree) +print(ast.dump(ast.parse(py))) + + + + +this_module = sys.modules[__name__] + +from dataclasses import dataclass + +# Define AST passthroughs + +class _Ast(ast_utils.Ast): + pass + +class _Decl(_Ast): + pass + +class _Expr(_Ast): + pass + +class _Assignment(_Ast): + pass +# Define useful AST classes + +@dataclass +class ConstDecl(_Decl): + name: str + value: _Expr + +@dataclass +class Version(_Ast): + number: int + +@dataclass +class Assign(_Ast): + value: object + +@dataclass +class AugAssign(_Ast): + operand: str + value: object + +class ClassicalAssignment(_Ast): + def __init__(self, name, expr): + self.name = name + self.expr = expr + print(name, expr) + # return None + def __str__(self): + return(f"ClassicalAssignment(name={self.name}, expr={self.expr})") + + +try: + import pydot + _has_pydot = True +except ImportError: + _has_pydot = False + +grammar_path = os.path.join(os.path.dirname( + os.path.abspath(__file__)), + "grammar.lark") +with open(grammar_path, "r") as f: + _QASM_GRAMMAR = f.read() + +class QASM3Parser(Lark): + + def __init__(self, **kwargs): + super().__init__(_QASM_GRAMMAR, **kwargs) + self.cst = None + + def build_tree(self, input): + self.cst = self.parse(input) + + def __str__(self): + return self.cst.pretty() + + def cst_graph(self, filename): + if _has_pydot: + tree.pydot__tree_to_png(self.cst, filename) + else: + raise ModuleNotFoundError("Please install pydot to generate tree graphs.") + + def run(self): + transformer = ast_utils.create_transformer(sys.modules[__name__], QASM3Transformer()) + self.ast = transformer.transform(self.cst) + return self.ast + # return QASM3Transformer().transform(self.cst) + + def ast_graph(self, filename): + if _has_pydot: + tree.pydot__tree_to_png(self.ast, filename) + else: + raise ModuleNotFoundError("Please install pydot to generate tree graphs.") + + +class QASM3Transformer(Transformer): + def start(self, x): + return x + def expr(self, x): + return x + def NUMBER(self, tok): + "Convert the value of `tok` from string to int, while maintaining line number & column." + return float(tok) #tok.update(value=float(tok)) + def id(self, tok): + return str(tok[0].value) + + +if __name__ == '__main__': + q = QASM3Parser() + with open(sys.argv[1]) as f: + src = f.read() + q.build_tree(src) + q.cst_graph("qasm_tree.png") + print("---- SRC -----") + print(src) + print("---- CST -----") + print(q.cst) + print("\n---- AST -----") + # def prpr(x, n=1): + # if isinstance(x, list): + + for l in q.run(): + # if isinstance(l, list): + print(l) + # print(q.run()) + # q.ast_graph("qasm_tree_ast.png") + diff --git a/QGL/qasm/grammar.lark b/QGL/qasm/grammar.lark new file mode 100644 index 00000000..c40f95c4 --- /dev/null +++ b/QGL/qasm/grammar.lark @@ -0,0 +1,284 @@ +////////////////////////////////////////////////////////////////////////////// +//Original Author: Guilhem Ribeill +// +//Copyright 2020 Raytheon BBN Technologies +// +//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. +////////////////////////////////////////////////////////////////////////////// + +// OpenQASM 3.0 Grammar in EBNF syntax for use with the Lark parser engine + +// Comments and whitespace +%import common.WS +%import common.CPP_COMMENT +%import common.C_COMMENT +%ignore WS +%ignore CPP_COMMENT +%ignore C_COMMENT + +_ENDL: ";" + +///////////////////////////////////////////////////////// +// Terminals + +//Constant terminals +%import common.ESCAPED_STRING +%import common.SIGNED_NUMBER +%import common.INT + +STR: ESCAPED_STRING + +TEXT: /[\S\t ]+/ + +BIN_NUMBER: /0b[01]+/ +HEX_NUMBER: /0x[0-9a-fA-F]+/ +CONSTANT : "pi" | "tau" | "e" +NUMBER: HEX_NUMBER | BIN_NUMBER | SIGNED_NUMBER | CONSTANT +BOOL: "true" | "false" + +TIME: "ns" | "us" | "ms" | "s" | "dt" + +//Variable types +ID: /\$*[a-zA-Z_%][a-zA-Z0-9_]*/ //TODO: Support unicode + +//Quantum types +QTYPE : "qubit" | "qreg" + +//Classical types +RTYPE : "bit" | "creg" +NUMTYPE : "int" | "uint" | "float" | "fixed" | "angle" +BTYPE : "bool" +CTYPE : RTYPE | NUMTYPE | BTYPE + +//Math +MATH_FUNCS : "sqrt" | "exp" | "log" | "abs" + | "sin" | "cos" | "tan" + | "popcount" | "lengthof" + +//Operators +UNARY_OP : "~" | "!" | "-" + +INCR_OP : "++" | "--" + +ARITH_OP : "+" | "-" | "*" | "/" +BITS_OP : "&" | "|" | "^" | "<<" | ">>" | "rotl" | "rotr" +BOOL_OP : ">" | "<" | ">=" | "<=" | "==" | "!=" | "&&" | "||" | "in" +BINARY_OP : ARITH_OP | BITS_OP | BOOL_OP + +ASSIGN_OP : "+=" | "-=" | "*=" | "/=" + +BUILTIN_GATE : "U" | "CX" | "reset" + +///////////////////////////////////////////////////////// +//Rules + +//Basic structure +?start : version include* statement+ + +version : "OPENQASM" NUMBER _ENDL + +include : "include" STR _ENDL + +pragma : "#PRAGMA" TEXT + +block : "{" block* statement* "}" + +?statement : pragma + | decl + | gatedecl + | branch + | loop + | control + | subroutine + | kernel + | extern + | qblock + | qstatement + | cstatement + | defcal + + +///////////////////////////////////////////////////////// +//Variable delcaration +?decl : qdecl _ENDL + | cdecl _ENDL + | const_decl _ENDL + | tdecl _ENDL + +qdecl : qtype ID + +cdecl : ctype ID assignment? + +tdecl : ttype ID assignment? + +const_decl : "const" ID "=" expr + +?assignment : "=" expr -> assign + | ASSIGN_OP expr -> aug_assign + +modifier: index + | slice + +///////////////////////////////////////////////////////// +// Register aliasing, concatenation, slicing and indexing + +alias : "let" ID "=" concat + | "let" ID "=" ID slice + | "let" ID "=" ID index + +concat : ID "||" ID ("||" ID)* + +slice : "[" range "]" + +range : expr ":" expr (":" expr)? + +index : "[" expr ("," expr)? "]" + +///////////////////////////////////////////////////////// +//Quantum operatiors and gates + +qblock : "{" qblock* qstatement* "}" + +qstatement : gatecall _ENDL + | meas_decl _ENDL + | measure _ENDL + +gatedecl :"gate" gatedef qblock + +gatedef : ID ("(" carg_list? ")")? id_list + +gatecall : gatemod? gate ("(" expr_list? ")")? duration? id_list + +gate : BUILTIN_GATE + | ID + | gatemod gate + +gatemod : "inv" "@" -> gate_inv + | "pow" "(" SIGNED_NUMBER ")" "@" -> gate_pow + | "ctrl" "@" -> gate_ctrl + +measure : "measure" id_list + +meas_decl : measure "->" id_list + | id_list "=" measure + +defcal : "defcal" gatedef qblock* cblock* + | "defcalgrammar" "\"" ID "\"" _ENDL + + +///////////////////////////////////////////////////////// +//Classical operations + +cblock : "{" cblock* cstatement* "}" + +?cstatement : expr _ENDL + | "return" cstatement -> return + +expr : ID assignment -> classical_assignment + | unary_op expr + | expr incr_op + | call "(" expr_list? ")" + | member + | measure + | expr binary_op expr -> bin_op + | value + | tvalue + | ID + +call : MATH_FUNCS + | cast + | ID + +cast : ctype + +member : ID "in" "{" expr_list "}" + | ID "in" slice + +expr_list : (expr ",")* expr + +binary_op : BINARY_OP +unary_op : UNARY_OP +incr_op : INCR_OP + +///////////////////////////////////////////////////////// +//Control flow +branch_block : statement + | block + +branch : "if" "(" expr ")" branch_block ("else" branch_block)? + +loop : for_loop branch_block + | while_loop branch_block + +for_loop : "for" member + +while_loop: "while" "(" expr ")" + +control : "break" _ENDL -> break + | "continue" _ENDL -> continue + | "end" _ENDL -> end + +///////////////////////////////////////////////////////// +//Timing instructions + +barrier : "barrier" id_list + +delay : "delay" index id_list + +box : "boxas" ID qblock + | "boxto" tvalue qblock + +ttype : "length" -> length + | "stretch" INT? -> stretch + | "duration" -> duration_decl + +duration : "[" tvalue "]" + | "[" expr "]" + | "[" "stretchinf" "]" + +///////////////////////////////////////////////////////// +//Subroutines + +subroutine : "def" ID ("(" carg_list? qarg_list? ")")? return_sig? block + +kernel : "kernel" ID ("(" carg_list? ")")? return_sig? + +extern : "extern" ID ("(" id_list? ")")? (return_sig ID)? _ENDL + +return_sig : "->" carg + + +///////////////////////////////////////////////////////// +// Helpers +id_index : ID index? +?id_list : (id_index ",")* id_index + +carg : ctype association +qarg : qtype association + +?carg_list : (carg ",")* carg +?qarg_list : (qarg ",")* qarg + +association: ":" ID + +//Type declaration with width +?qtype : QTYPE ("[" SIGNED_NUMBER "]")? +?ctype : CTYPE ("[" SIGNED_NUMBER ("," SIGNED_NUMBER)* "]")? + +?tvalue : NUMBER TIME + +?value : NUMBER + | STR + | BOOL + + diff --git a/QGL/qasm/parse.py b/QGL/qasm/parse.py new file mode 100644 index 00000000..c8629e6e --- /dev/null +++ b/QGL/qasm/parse.py @@ -0,0 +1,59 @@ +""" +Original Author: Guilhem Ribeill + +Copyright 2020 Raytheon BBN Technologies + +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 os, sys +import logging +from lark import Lark, tree, logger + +#logger.setLevel(logging.DEBUG) + +try: + import pydot + _has_pydot = True +except ImportError: + _has_pydot = False + +grammar_path = os.path.join(os.path.dirname( + os.path.abspath(__file__)), + "grammar.lark") +with open(grammar_path, "r") as f: + _QASM_GRAMMAR = f.read() + +class QASM3Parser(Lark): + + def __init__(self, **kwargs): + super().__init__(_QASM_GRAMMAR, **kwargs) + self.cst = None + + def build_tree(self, input): + self.cst = self.parse(input) + + def __str__(self): + return self.cst.pretty() + + def cst_graph(self, filename): + if _has_pydot: + tree.pydot__tree_to_png(self.cst, filename) + else: + raise ModuleNotFoundError("Please install pydot to generate tree graphs.") + +if __name__ == "__main__": + with open(sys.argv[1]) as f: + parser = QASM3Parser(debug=True) + parser.build_tree(f.read()) + print(parser) + parser.cst_graph("test_qasm.png") diff --git a/QGL/qasm/test_all.py b/QGL/qasm/test_all.py new file mode 100644 index 00000000..48d2c647 --- /dev/null +++ b/QGL/qasm/test_all.py @@ -0,0 +1,25 @@ +import os, sys, glob +from parse import * + +qasm_path = "/home/gribeill/GitHub/openqasm/examples/*.qasm" + +fails = [] + +files = glob.glob(qasm_path) + +print(files) + +for fn in files: + p = QASM3Parser() + with open(fn) as f: + try: + p.build_tree(f.read()) + except: + fails.append(fn) + +Nt = len(files) +Nf = len(fails) + +print(f"Failed on {Nf} out of {Nt} files!") +for f in fails: + print(f) diff --git a/requirements.txt b/requirements.txt index d0054aad..723b4d45 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,5 @@ numpy >= 1.11.1 scipy >= 0.17.1 networkx >= 1.11 bqplot >= 0.12.2 -sqlalchemy >= 1.2.15 \ No newline at end of file +sqlalchemy >= 1.2.15 +lark-parser >= 0.11.3 diff --git a/setup.py b/setup.py index c3b08ab6..13bdc5ae 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,9 @@ from setuptools import setup, find_packages +extras = { + 'with_qasm': ['lark-parser >= 0.11'] +} + setup(name='QGL', version='2020.1', packages=find_packages(exclude=["tests"]), @@ -12,13 +16,15 @@ "scipy >= 0.17.1", "networkx >= 1.11", "bqplot >= 0.12.2", - "sqlalchemy >= 1.2.15" + "sqlalchemy >= 1.2.15", + "lark-parser >= 0.11.03" ], description="Quantum Gate Language (QGL) is a domain specific language embedded in python for specifying pulse sequences.", long_description_content_type='text/markdown', long_description=open('README.md').read(), python_requires='>=3.6', - keywords="quantum qubit experiment configuration gate language" + keywords="quantum qubit experiment configuration gate language", + extras_requier=extras ) # python setup.py sdist diff --git a/tests/test_QASM.py b/tests/test_QASM.py new file mode 100644 index 00000000..7109893d --- /dev/null +++ b/tests/test_QASM.py @@ -0,0 +1,25 @@ +# Test QASM parsing and (eventually) compilation +import os +import unittest + +from QGL.qasm.parse import QASM3Parser + +def get_qasm_test_file(filename): + path = os.path.dirname(os.path.abspath(__file__)) + file = os.path.join(path, "test_data", "qasm", filename) + with open(file, "r") as f: + qasm = f.read() + return qasm + +class ParseTestCase(unittest.TestCase): + + def setUp(self): + pass + + def test_parse_simple(self): + qasm = get_qasm_test_file("basic.qasm") + parser = QASM3Parser() + parser.build_tree(qasm) + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/tests/test_data/qasm/basic.qasm b/tests/test_data/qasm/basic.qasm new file mode 100644 index 00000000..c5cba60a --- /dev/null +++ b/tests/test_data/qasm/basic.qasm @@ -0,0 +1,21 @@ +OPENQASM 3.0; +#PRAGMA I <3 QGL + +qubit q1; +length t; + +//A very special gate +gate X(angle[32]: phi) q { + U(phi, -pi/2, -pi/2) q; +} + +/* Let's do a ramsey experiment! + Fun! */ + +for t in 4ns:10us:20ns { + reset q1; + X(pi/2) q1; + delay[t] q1; + X(pi/2) q1; + measure q1; +} \ No newline at end of file