diff --git a/TODO.md b/TODO.md index ab4a584c..2e36d238 100644 --- a/TODO.md +++ b/TODO.md @@ -27,7 +27,14 @@ - [x] Poc compiler 2 using qcircuit abstraction - [x] OpenQASM3 exporter +#### Typechecker branch +- [x] Translate_expr should returns ttype*expr +- [x] Args should also hold the original type +- [x] Transform Env to a class holding also the original types +- [x] Typecheck all the expressions + ### Week 3: (9 Oct 23) +- [ ] Int: comparison - eq - [ ] Qubit garbage uncomputing and recycling - [ ] Test: add qubit usage check - [ ] Compiler: remove consecutive X gates @@ -97,4 +104,8 @@ - [ ] QuTip - [ ] Pennylane - [ ] Cirq -- [ ] Sympy quantum computing expressions \ No newline at end of file +- [ ] Sympy quantum computing expressions + +### Tools + +- [ ] py2qasm tools \ No newline at end of file diff --git a/qlasskit/__init__.py b/qlasskit/__init__.py index 7d17724e..f73abcdd 100644 --- a/qlasskit/__init__.py +++ b/qlasskit/__init__.py @@ -17,5 +17,5 @@ from .qcircuit import QCircuit # noqa: F401 from .qlassf import QlassF, qlassf # noqa: F401 -from .typing import Qint2, Qint4, Qint8, Qint12, Qint16, Qtype # noqa: F401 +from .typing import Qint, Qint2, Qint4, Qint8, Qint12, Qint16, Qtype # noqa: F401 from .ast2logic import exceptions # noqa: F401 diff --git a/qlasskit/ast2logic/__init__.py b/qlasskit/ast2logic/__init__.py index e77e3f31..11709b64 100644 --- a/qlasskit/ast2logic/__init__.py +++ b/qlasskit/ast2logic/__init__.py @@ -13,10 +13,7 @@ # limitations under the License. # isort:skip_file -from typing import List - -Env = List[str] - +from .env import Env, Binding # noqa: F401, E402 from .utils import flatten # noqa: F401, E402 from .t_arguments import translate_argument, translate_arguments # noqa: F401, E402 from .t_expression import translate_expression, type_of_exp # noqa: F401, E402 diff --git a/qlasskit/ast2logic/env.py b/qlasskit/ast2logic/env.py new file mode 100644 index 00000000..4d61c58f --- /dev/null +++ b/qlasskit/ast2logic/env.py @@ -0,0 +1,43 @@ +# Copyright 2023 Davide Gessa + +# 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 typing import List + +from typing_extensions import TypeAlias + +from ..typing import Arg, Qint # noqa: F401, E402 +from . import exceptions + +Binding: TypeAlias = Arg + + +class Env: + def __init__(self): + self.bindings: List[Binding] = [] + + def bind(self, bb: Binding): + if bb.name in self: + raise Exception("duplicate bind") + + self.bindings.append(bb) + + def __contains__(self, key): + if len(list(filter(lambda x: x.name == key, self.bindings))) == 1: + return True + + def __getitem__(self, key): + try: + return list(filter(lambda x: x.name == key, self.bindings))[0] + except: + raise exceptions.UnboundException(key, self) diff --git a/qlasskit/ast2logic/exceptions.py b/qlasskit/ast2logic/exceptions.py index 4ab2e69e..650163df 100644 --- a/qlasskit/ast2logic/exceptions.py +++ b/qlasskit/ast2logic/exceptions.py @@ -15,6 +15,11 @@ import ast +class TypeErrorException(Exception): + def __init__(self, got, excepted): + super().__init__(f"Got '{got}' excepted '{excepted}'") + + class NoReturnTypeException(Exception): def __init__(self): super().__init__("Return type is mandatory") @@ -35,6 +40,11 @@ def __init__(self, ob, message=None): super().__init__(ast.dump(ob) + f": {message}" if message else "") +class OutOfBoundException(Exception): + def __init__(self, size, i): + super().__init__(f"size is {size}: {i} accessed") + + class UnboundException(Exception): def __init__(self, symbol, env): super().__init__(f"{symbol} in {env}") diff --git a/qlasskit/ast2logic/t_arguments.py b/qlasskit/ast2logic/t_arguments.py index 46dc9ce2..f86b177b 100644 --- a/qlasskit/ast2logic/t_arguments.py +++ b/qlasskit/ast2logic/t_arguments.py @@ -12,13 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. import ast -from typing import List +from typing import List, Tuple -from ..typing import Args -from . import exceptions, flatten +from ..typing import Arg, Args, Qint, Qint2, Qint4, Qint8, Qint12, Qint16 # noqa: F +from . import exceptions +from .t_expression import TType -def translate_argument(ann, base="") -> List[str]: +def translate_argument(ann, base="") -> Arg: def to_name(a): return a.attr if isinstance(a, ast.Attribute) else a.id @@ -26,25 +27,29 @@ def to_name(a): if isinstance(ann, ast.Subscript) and ann.value.id == "Tuple": # type: ignore al = [] ind = 0 + ttypes: List[TType] = [] for i in ann.slice.value.elts: # type: ignore if isinstance(i, ast.Name) and to_name(i) == "bool": al.append(f"{base}.{ind}") + ttypes.append(bool) else: - inner_list = translate_argument(i, base=f"{base}.{ind}") - al.extend(inner_list) + inner_arg = translate_argument(i, base=f"{base}.{ind}") + ttypes.append(inner_arg.ttype) + al.extend(inner_arg.bitvec) ind += 1 - return al + ttypes_t = tuple(ttypes) + return Arg(base, Tuple[ttypes_t], al) # QintX elif to_name(ann)[0:4] == "Qint": n = int(to_name(ann)[4::]) arg_list = [f"{base}.{i}" for i in range(n)] # arg_list.append((f"{base}{arg.arg}", n)) - return arg_list + return Arg(base, eval(to_name(ann)), arg_list) # Bool elif to_name(ann) == "bool": - return [f"{base}"] + return Arg(base, bool, [f"{base}"]) else: raise exceptions.UnknownTypeException(ann) @@ -55,4 +60,4 @@ def translate_arguments(args) -> Args: args_unrolled = map( lambda arg: translate_argument(arg.annotation, base=arg.arg), args ) - return flatten(list(args_unrolled)) + return list(args_unrolled) diff --git a/qlasskit/ast2logic/t_ast.py b/qlasskit/ast2logic/t_ast.py index f2aded2a..f7953716 100644 --- a/qlasskit/ast2logic/t_ast.py +++ b/qlasskit/ast2logic/t_ast.py @@ -28,17 +28,17 @@ def translate_ast(fun) -> LogicFun: fun_name: str = fun.name # env contains names visible from the current scope - env: Env = [] + env = Env() args: Args = translate_arguments(fun.args.args) - # TODO: types are string; maybe a translate_type? - for a_name in args: - env.append(a_name) + + [env.bind(arg) for arg in args] if not fun.returns: raise exceptions.NoReturnTypeException() - ret_size = len(translate_argument(fun.returns)) + ret_ = translate_argument(fun.returns) # TODO: we need to preserve this + ret_size = len(ret_) exps = [] for stmt in fun.body: diff --git a/qlasskit/ast2logic/t_expression.py b/qlasskit/ast2logic/t_expression.py index 08a860ea..76684673 100644 --- a/qlasskit/ast2logic/t_expression.py +++ b/qlasskit/ast2logic/t_expression.py @@ -12,50 +12,43 @@ # See the License for the specific language governing permissions and # limitations under the License. import ast +from typing import List, Tuple, get_args from sympy import Symbol from sympy.logic import ITE, And, Not, Or, false, true from sympy.logic.boolalg import Boolean +from typing_extensions import TypeAlias from . import Env, exceptions +TType: TypeAlias = object -def type_of_exp(vlist, base, env, res=[]): + +def type_of_exp(vlist, base, res=[]) -> List[Symbol]: """Type inference for expressions: iterate over val, and decompose to bool""" if isinstance(vlist, list): i = 0 res = [] for in_val in vlist: - r_new, env = type_of_exp(in_val, f"{base}.{i}", env, res) + r_new = type_of_exp(in_val, f"{base}.{i}", res) if isinstance(r_new, list): res.extend(r_new) else: res.append(r_new) i += 1 - return res, env + return res else: new_symb = (f"{base}", vlist) - env.append(new_symb[0]) - return [new_symb], env + return [new_symb] -def translate_expression(expr, env: Env) -> Boolean: # noqa: C901 +def translate_expression(expr, env: Env) -> Tuple[TType, Boolean]: # noqa: C901 """Translate an expression""" # Name reference if isinstance(expr, ast.Name): - if expr.id not in env: - # Handle complex types - rl = [] - for sym in env: - if sym[0 : (len(expr.id) + 1)] == f"{expr.id}.": - rl.append(Symbol(sym)) - - if len(rl) == 0: - raise exceptions.UnboundException(expr.id, env) - - return rl - return Symbol(expr.id) + binding = env[expr.id] + return (binding.ttype, binding.to_exp()) # Subscript: a[0][1] elif isinstance(expr, ast.Subscript): @@ -77,10 +70,24 @@ def unroll_subscripts(sub, st): else: sn = unroll_subscripts(expr, "") - if sn not in env: + if sn.split(".")[0] not in env: raise exceptions.UnboundException(sn, env) - return Symbol(sn) + # Get the inner type + inner_type = env[sn.split(".")[0]].ttype + for i in sn.split(".")[1:]: + if hasattr(inner_type, "BIT_SIZE"): + if int(i) < inner_type.BIT_SIZE: + inner_type = bool + else: + raise exceptions.OutOfBoundException(inner_type.BIT_SIZE, i) + else: + if int(i) < len(get_args(inner_type)): + inner_type = get_args(inner_type)[int(i)] + else: + raise exceptions.OutOfBoundException(len(get_args(inner_type)), i) + + return (inner_type, Symbol(sn)) # Boolop: and, or elif isinstance(expr, ast.BoolOp): @@ -90,43 +97,59 @@ def unfold(v_exps, op): op(v_exps[0], unfold(v_exps[1::], op)) if len(v_exps) > 1 else v_exps[0] ) - v_exps = [translate_expression(e_in, env) for e_in in expr.values] + vt_exps = [translate_expression(e_in, env) for e_in in expr.values] + v_exps = [x[1] for x in vt_exps] + for x in vt_exps: + if x[0] != bool: + raise exceptions.TypeErrorException(x[0], bool) - return unfold(v_exps, And if isinstance(expr.op, ast.And) else Or) + return (bool, unfold(v_exps, And if isinstance(expr.op, ast.And) else Or)) # Unary: not elif isinstance(expr, ast.UnaryOp): if isinstance(expr.op, ast.Not): - return Not(translate_expression(expr.operand, env)) + texp, exp = translate_expression(expr.operand, env) + + if texp != bool: + raise exceptions.TypeErrorException(texp, bool) + + return (bool, Not(exp)) else: raise exceptions.ExpressionNotHandledException(expr) # If expression elif isinstance(expr, ast.IfExp): - # (condition) and (true_value) or (not condition) and (false_value) - # return Or( - # And(translate_expression(expr.test, env), translate_expression(expr.body, env)), - # And(Not(translate_expression(expr.test, env)), translate_expression(expr.orelse, env)) - # ) - return ITE( - translate_expression(expr.test, env), - translate_expression(expr.body, env), - translate_expression(expr.orelse, env), + te_test = translate_expression(expr.test, env) + te_true = translate_expression(expr.body, env) + te_false = translate_expression(expr.orelse, env) + + if te_test[0] != bool: + raise exceptions.TypeErrorException(te_test[0], bool) + + if te_true[0] != te_false[0]: + raise exceptions.TypeErrorException(te_false[0], te_true[0]) + + return ( + te_true[0], + ITE(te_test[1], te_true[1], te_false[1]), ) # Constant elif isinstance(expr, ast.Constant): if expr.value is True: - return true + return (bool, true) elif expr.value is False: - return false + return (bool, false) else: raise exceptions.ExpressionNotHandledException(expr) # Tuple elif isinstance(expr, ast.Tuple): - elts = [translate_expression(elt, env) for elt in expr.elts] - return elts + telts = [translate_expression(elt, env) for elt in expr.elts] + elts = [x[1] for x in telts] + tlts = [x[0] for x in telts] + + return (Tuple[tuple(tlts)], elts) # Compare operator elif isinstance(expr, ast.Compare): diff --git a/qlasskit/ast2logic/t_statement.py b/qlasskit/ast2logic/t_statement.py index fe592c25..8a65a977 100644 --- a/qlasskit/ast2logic/t_statement.py +++ b/qlasskit/ast2logic/t_statement.py @@ -17,7 +17,7 @@ from sympy import Symbol from sympy.logic.boolalg import Boolean -from . import Env, exceptions, translate_expression, type_of_exp +from . import Binding, Env, exceptions, translate_expression, type_of_exp def translate_statement( # noqa: C901 @@ -57,14 +57,16 @@ def translate_statement( # noqa: C901 if target in env: raise exceptions.SymbolReassignedException(target) - val = translate_expression(stmt.value, env) - res, env = type_of_exp(val, f"{target}", env) + tval, val = translate_expression(stmt.value, env) # TODO: typecheck + res = type_of_exp(val, f"{target}") + env.bind(Binding(target, tval, [x[0] for x in res])) res = list(map(lambda x: (Symbol(x[0]), x[1]), res)) return res, env elif isinstance(stmt, ast.Return): - vexp = translate_expression(stmt.value, env) - res, env = type_of_exp(vexp, "_ret", env) + texp, vexp = translate_expression(stmt.value, env) # TODO: typecheck + res = type_of_exp(vexp, "_ret") + env.bind(Binding("_ret", texp, [x[0] for x in res])) res = list(map(lambda x: (Symbol(x[0]), x[1]), res)) return res, env diff --git a/qlasskit/compiler/poccompiler2.py b/qlasskit/compiler/poccompiler2.py index f292fec9..e4b28b9a 100644 --- a/qlasskit/compiler/poccompiler2.py +++ b/qlasskit/compiler/poccompiler2.py @@ -28,7 +28,8 @@ def compile(self, name, args: Args, ret_size: int, exprs: BoolExpList) -> QCircu qc = QCircuit(name=name) for arg in args: - qc.add_qubit(arg) + for arg_b in arg.bitvec: + qc.add_qubit(arg_b) for sym, exp in exprs: iret = self.compile_expr(qc, self._symplify_exp(exp)) diff --git a/qlasskit/qlassf.py b/qlasskit/qlassf.py index e2e7e581..fb7699cc 100644 --- a/qlasskit/qlassf.py +++ b/qlasskit/qlassf.py @@ -14,10 +14,11 @@ import ast import inspect +from functools import reduce from typing import Callable, List, Tuple, Union # noqa: F401 from . import compiler -from .ast2logic import translate_ast +from .ast2logic import flatten, translate_ast from .typing import * # noqa: F403, F401 from .typing import Args, BoolExpList @@ -60,14 +61,15 @@ def __add__(self, qf2) -> "QlassF": def truth_table_header(self) -> List[str]: """Returns the list of string containing the truth table header""" - header = [x for x in self.args] + header = flatten(list(map(lambda a: a.bitvec, self.args))) header.extend([sym.name for (sym, retex) in self.expressions[-self.ret_size :]]) return header def truth_table(self) -> List[List[bool]]: """Returns the truth table for the function using the sympy boolean for computing""" truth = [] - bits = len(self.args) + arg_bits = flatten(list(map(lambda a: a.bitvec, self.args))) + bits = len(arg_bits) if (bits + self.ret_size) > MAX_TRUTH_TABLE_SIZE: raise Exception( @@ -78,13 +80,13 @@ def truth_table(self) -> List[List[bool]]: bin_str = bin(i)[2:] bin_str = "0" * (bits - len(bin_str)) + bin_str bin_arr = list(map(lambda c: c == "1", bin_str)) - known = list(zip(self.args, bin_arr)) + known = list(zip(arg_bits, bin_arr)) for ename, exp in self.expressions: exp_sub = exp.subs(known) known.append((ename, exp_sub)) - res = known[0 : len(self.args)] + known[-self.ret_size :] + res = known[0 : len(arg_bits)] + known[-self.ret_size :] res_clean = list(map(lambda y: y[1], res)) truth.append(res_clean) @@ -121,7 +123,7 @@ def gate(self, framework="qiskit"): @property def input_size(self) -> int: """Return the size of the inputs (in bits)""" - return len(self.args) + return reduce(lambda a, b: a + len(b), self.args, 0) @property def num_qubits(self) -> int: diff --git a/qlasskit/typing.py b/qlasskit/typing.py index d1a94c6c..790c8b95 100644 --- a/qlasskit/typing.py +++ b/qlasskit/typing.py @@ -17,7 +17,28 @@ from sympy import Symbol from sympy.logic.boolalg import Boolean -Args = List[str] +# from .ast2logic.t_expression import TType + + +class Arg: + def __init__(self, name: str, ttype: object, bitvec: List[str]): + self.name = name + self.ttype = ttype + self.bitvec = bitvec + + def __repr__(self): + return f"{self.name} - {self.ttype} - {', '.join(self.bitvec)}" + + def __len__(self) -> int: + return len(self.bitvec) + + def to_exp(self) -> List[Symbol]: + if len(self) > 1: + return list(map(Symbol, self.bitvec)) + return Symbol(self.bitvec[0]) + + +Args = List[Arg] BoolExpList = List[Tuple[Symbol, Boolean]] LogicFun = Tuple[str, Args, int, BoolExpList] @@ -41,35 +62,46 @@ def to_bool(self): class Qint(int, Qtype): - def __init__(self, value, bit_size=8): + BIT_SIZE = 8 + + def __init__(self, value): super().__init__() self.value = value - self.bit_size = bit_size class Qint2(Qint): + BIT_SIZE = 2 + def __init__(self, value): - super().__init__(value, bit_size=2) + super().__init__(value) class Qint4(Qint): + BIT_SIZE = 4 + def __init__(self, value): - super().__init__(value, bit_size=4) + super().__init__(value) class Qint8(Qint): + BIT_SIZE = 8 + def __init__(self, value): - super().__init__(value, bit_size=8) + super().__init__(value) class Qint12(Qint): + BIT_SIZE = 12 + def __init__(self, value): - super().__init__(value, bit_size=12) + super().__init__(value) class Qint16(Qint): + BIT_SIZE = 16 + def __init__(self, value): - super().__init__(value, bit_size=16) + super().__init__(value) # class Qpair diff --git a/test/test_ast2logic_t_arg.py b/test/test_ast2logic_t_arg.py index 5ad0a39a..12fdff4e 100644 --- a/test/test_ast2logic_t_arg.py +++ b/test/test_ast2logic_t_arg.py @@ -14,8 +14,9 @@ import ast import unittest +from typing import Tuple -from qlasskit import ast2logic, exceptions +from qlasskit import Qint2, Qint4, ast2logic, exceptions class TestAst2Logic_translate_argument(unittest.TestCase): @@ -32,44 +33,58 @@ def test_bool(self): f = "a: bool" ann_ast = ast.parse(f).body[0].annotation c = ast2logic.translate_argument(ann_ast, "a") - self.assertEqual(c, ["a"]) + self.assertEqual(c.name, "a") + self.assertEqual(c.ttype, bool) + self.assertEqual(c.bitvec, ["a"]) def test_qint2(self): f = "a: Qint2" ann_ast = ast.parse(f).body[0].annotation c = ast2logic.translate_argument(ann_ast, "a") - self.assertEqual(c, ["a.0", "a.1"]) + self.assertEqual(c.name, "a") + self.assertEqual(c.ttype, Qint2) + self.assertEqual(c.bitvec, ["a.0", "a.1"]) def test_qint4(self): f = "a: Qint4" ann_ast = ast.parse(f).body[0].annotation c = ast2logic.translate_argument(ann_ast, "a") - self.assertEqual(c, ["a.0", "a.1", "a.2", "a.3"]) + self.assertEqual(c.name, "a") + self.assertEqual(c.ttype, Qint4) + self.assertEqual(c.bitvec, ["a.0", "a.1", "a.2", "a.3"]) def test_tuple(self): f = "a: Tuple[bool, bool]" ann_ast = ast.parse(f).body[0].annotation c = ast2logic.translate_argument(ann_ast, "a") - self.assertEqual(c, ["a.0", "a.1"]) + self.assertEqual(c.name, "a") + self.assertEqual(c.ttype, Tuple[bool, bool]) + self.assertEqual(c.bitvec, ["a.0", "a.1"]) def test_tuple_of_tuple(self): f = "a: Tuple[Tuple[bool, bool], bool]" ann_ast = ast.parse(f).body[0].annotation c = ast2logic.translate_argument(ann_ast, "a") - self.assertEqual(c, ["a.0.0", "a.0.1", "a.1"]) + self.assertEqual(c.name, "a") + self.assertEqual(c.ttype, Tuple[Tuple[bool, bool], bool]) + self.assertEqual(c.bitvec, ["a.0.0", "a.0.1", "a.1"]) def test_tuple_of_tuple2(self): f = "a: Tuple[bool, Tuple[bool, bool]]" ann_ast = ast.parse(f).body[0].annotation c = ast2logic.translate_argument(ann_ast, "a") - self.assertEqual(c, ["a.0", "a.1.0", "a.1.1"]) + self.assertEqual(c.name, "a") + self.assertEqual(c.ttype, Tuple[bool, Tuple[bool, bool]]) + self.assertEqual(c.bitvec, ["a.0", "a.1.0", "a.1.1"]) def test_tuple_of_int2(self): f = "a: Tuple[Qint2, Qint2]" ann_ast = ast.parse(f).body[0].annotation c = ast2logic.translate_argument(ann_ast, "a") + self.assertEqual(c.name, "a") + self.assertEqual(c.ttype, Tuple[Qint2, Qint2]) self.assertEqual( - c, + c.bitvec, [ "a.0.0", "a.0.1", diff --git a/test/test_qlassf_bool.py b/test/test_qlassf_bool.py index 55dc3159..8de90600 100644 --- a/test/test_qlassf_bool.py +++ b/test/test_qlassf_bool.py @@ -19,7 +19,7 @@ from qlasskit import QlassF, exceptions, qlassf -from .utils import compare_circuit_truth_table +from .utils import COMPILATION_ENABLED, compare_circuit_truth_table a, b, c, d, e, g, h = symbols("a,b,c,d,e,g,h") _ret = Symbol("_ret") @@ -47,7 +47,7 @@ def test_no_return_type(self): def test_arg_identity(self): ex = a f = "def test(a: bool) -> bool:\n\treturn a" - qf = qlassf(f, to_compile=True) + qf = qlassf(f, to_compile=COMPILATION_ENABLED) self.assertEqual(len(qf.expressions), 1) self.assertEqual(qf.expressions[0][0], _ret) self.assertEqual(qf.expressions[0][1], ex) @@ -56,7 +56,7 @@ def test_arg_identity(self): def test_not_arg(self): ex = Not(a) f = "def test(a: bool) -> bool:\n\treturn not a" - qf = qlassf(f, to_compile=True) + qf = qlassf(f, to_compile=COMPILATION_ENABLED) self.assertEqual(len(qf.expressions), 1) self.assertEqual(qf.expressions[0][0], _ret) self.assertEqual(qf.expressions[0][1], ex) @@ -65,7 +65,7 @@ def test_not_arg(self): def test_and(self): ex = And(Not(a), b) f = "def test(a: bool, b: bool) -> bool:\n\treturn not a and b" - qf = qlassf(f, to_compile=True) + qf = qlassf(f, to_compile=COMPILATION_ENABLED) self.assertEqual(len(qf.expressions), 1) self.assertEqual(qf.expressions[0][0], _ret) self.assertEqual(qf.expressions[0][1], ex) @@ -74,7 +74,7 @@ def test_and(self): def test_or(self): ex = Or(Not(a), b) f = "def test(a: bool, b: bool) -> bool:\n\treturn not a or b" - qf = qlassf(f, to_compile=True) + qf = qlassf(f, to_compile=COMPILATION_ENABLED) self.assertEqual(len(qf.expressions), 1) self.assertEqual(qf.expressions[0][0], _ret) self.assertEqual(qf.expressions[0][1], ex) @@ -83,7 +83,7 @@ def test_or(self): def test_multiple_arg(self): ex = And(a, And(Not(b), c)) f = "def test(a: bool, b: bool, c: bool) -> bool:\n\treturn a and (not b) and c" - qf = qlassf(f, to_compile=True) + qf = qlassf(f, to_compile=COMPILATION_ENABLED) self.assertEqual(len(qf.expressions), 1) self.assertEqual(qf.expressions[0][0], _ret) self.assertEqual(qf.expressions[0][1], ex) @@ -92,7 +92,7 @@ def test_multiple_arg(self): def test_multiple_arg2(self): ex = And(a, And(Not(b), Or(a, c))) f = "def test(a: bool, b: bool, c: bool) -> bool:\n\treturn a and (not b) and (a or c)" - qf = qlassf(f, to_compile=True) + qf = qlassf(f, to_compile=COMPILATION_ENABLED) self.assertEqual(len(qf.expressions), 1) self.assertEqual(qf.expressions[0][0], _ret) self.assertEqual(qf.expressions[0][1], ex) @@ -101,7 +101,7 @@ def test_multiple_arg2(self): def test_ifexp(self): ex = ITE(a, true, false) f = "def test(a: bool) -> bool:\n\treturn True if a else False" - qf = qlassf(f, to_compile=True) + qf = qlassf(f, to_compile=COMPILATION_ENABLED) self.assertEqual(len(qf.expressions), 1) self.assertEqual(qf.expressions[0][0], _ret) self.assertEqual(qf.expressions[0][1], ex) @@ -110,7 +110,7 @@ def test_ifexp(self): def test_ifexp2(self): ex = ITE(And(a, And(Not(b), c)), true, false) f = "def test(a: bool, b: bool, c: bool) -> bool:\n\treturn True if a and (not b) and c else False" - qf = qlassf(f, to_compile=True) + qf = qlassf(f, to_compile=COMPILATION_ENABLED) self.assertEqual(len(qf.expressions), 1) self.assertEqual(qf.expressions[0][0], _ret) self.assertEqual(qf.expressions[0][1], ex) @@ -126,7 +126,7 @@ def test_ifexp3(self): "def test(a: bool, b: bool, c: bool) -> bool:\n" + "\treturn (c and not b) if a and ((not b) and c) else (a and not c)" ) - qf = qlassf(f, to_compile=True) + qf = qlassf(f, to_compile=COMPILATION_ENABLED) self.assertEqual(len(qf.expressions), 1) self.assertEqual(qf.expressions[0][0], _ret) self.assertEqual(qf.expressions[0][1], exp) @@ -134,7 +134,7 @@ def test_ifexp3(self): def test_assign(self): f = "def test(a: bool, b: bool) -> bool:\n\tc = a and b\n\treturn c" - qf = qlassf(f, to_compile=True) + qf = qlassf(f, to_compile=COMPILATION_ENABLED) self.assertEqual(len(qf.expressions), 2) self.assertEqual(qf.expressions[0][0], c) self.assertEqual(qf.expressions[0][1], And(a, b)) @@ -148,7 +148,7 @@ def test_assign2(self): + "\td = a and (not b) and c\n" + "\treturn True if d else False" ) - qf = qlassf(f, to_compile=True) + qf = qlassf(f, to_compile=COMPILATION_ENABLED) self.assertEqual(len(qf.expressions), 2) self.assertEqual(qf.expressions[0][0], d) self.assertEqual(qf.expressions[0][1], And(a, And(Not(b), c))) diff --git a/test/test_qlassf_int.py b/test/test_qlassf_int.py index df969142..970eb1d4 100644 --- a/test/test_qlassf_int.py +++ b/test/test_qlassf_int.py @@ -19,7 +19,7 @@ from qlasskit import QlassF, exceptions, qlassf -from .utils import compare_circuit_truth_table +from .utils import COMPILATION_ENABLED, compare_circuit_truth_table a, b, c, d = symbols("a,b,c,d") _ret = Symbol("_ret") @@ -28,7 +28,7 @@ class TestQlassfInt(unittest.TestCase): def test_int_arg(self): f = "def test(a: Qint2) -> bool:\n\treturn a[0]" - qf = qlassf(f, to_compile=True) + qf = qlassf(f, to_compile=COMPILATION_ENABLED) self.assertEqual(len(qf.expressions), 1) self.assertEqual(qf.expressions[0][0], _ret) self.assertEqual(qf.expressions[0][1], Symbol("a.0")) @@ -36,7 +36,7 @@ def test_int_arg(self): def test_int_arg2(self): f = "def test(a: Qint2, b: bool) -> bool:\n\treturn True if a[0] and b else a[1]" - qf = qlassf(f, to_compile=True) + qf = qlassf(f, to_compile=COMPILATION_ENABLED) self.assertEqual(len(qf.expressions), 1) self.assertEqual(qf.expressions[0][0], _ret) self.assertEqual( @@ -47,12 +47,12 @@ def test_int_arg2(self): def test_int_arg_unbound_index(self): f = "def test(a: Qint2) -> bool:\n\treturn a[5]" self.assertRaises( - exceptions.UnboundException, lambda f: qlassf(f, to_compile=False), f + exceptions.OutOfBoundException, lambda f: qlassf(f, to_compile=False), f ) def test_int_tuple(self): f = "def test(a: Tuple[Qint2, Qint2]) -> bool:\n\treturn a[0][0] and a[1][1]" - qf = qlassf(f, to_compile=True) + qf = qlassf(f, to_compile=COMPILATION_ENABLED) self.assertEqual(len(qf.expressions), 1) self.assertEqual(qf.expressions[0][0], _ret) self.assertEqual(qf.expressions[0][1], And(Symbol("a.0.0"), Symbol("a.1.1"))) @@ -60,7 +60,7 @@ def test_int_tuple(self): def test_int_identity(self): f = "def test(a: Qint2) -> Qint2:\n\treturn a" - qf = qlassf(f, to_compile=True) + qf = qlassf(f, to_compile=COMPILATION_ENABLED) self.assertEqual(len(qf.expressions), 2) self.assertEqual(qf.expressions[0][0], Symbol("_ret.0")) self.assertEqual(qf.expressions[0][1], Symbol("a.0")) @@ -71,7 +71,7 @@ def test_int_identity(self): # TODO: need consts # def test_int_const(self): # f = "def test(a: Qint2) -> Qint2:\n\tc=1\n\treturn a" - # qf = qlassf(f, to_compile=True) + # qf = qlassf(f, to_compile=COMPILATION_ENABLED) # TODO: need comparators # def test_int_compare(self): diff --git a/test/test_qlassf_tuple.py b/test/test_qlassf_tuple.py index 1c9089ee..b6edf6f4 100644 --- a/test/test_qlassf_tuple.py +++ b/test/test_qlassf_tuple.py @@ -20,7 +20,7 @@ from qlasskit import QlassF, exceptions, qlassf -from .utils import compare_circuit_truth_table +from .utils import COMPILATION_ENABLED, compare_circuit_truth_table a, b, c, d = symbols("a,b,c,d") _ret = Symbol("_ret") @@ -33,7 +33,7 @@ class TestQlassfTuple(unittest.TestCase): def test_tuple_arg(self): f = "def test(a: Tuple[bool, bool]) -> bool:\n\treturn a[0] and a[1]" - qf = qlassf(f, to_compile=True) + qf = qlassf(f, to_compile=COMPILATION_ENABLED) self.assertEqual(len(qf.expressions), 1) self.assertEqual(qf.expressions[0][0], _ret) self.assertEqual(qf.expressions[0][1], And(a_0, a_1)) @@ -46,7 +46,7 @@ def test_tuple_arg_assign(self): + "\tc = a[1]\n" + "\treturn b and c" ) - qf = qlassf(f, to_compile=True) + qf = qlassf(f, to_compile=COMPILATION_ENABLED) self.assertEqual(len(qf.expressions), 3) self.assertEqual(qf.expressions[-1][0], _ret) self.assertEqual(qf.expressions[-1][1], And(b, c)) @@ -54,7 +54,7 @@ def test_tuple_arg_assign(self): def test_tuple_of_tuple_arg(self): f = "def test(a: Tuple[Tuple[bool, bool], bool]) -> bool:\n\treturn a[0][0] and a[0][1] and a[1]" - qf = qlassf(f, to_compile=True) + qf = qlassf(f, to_compile=COMPILATION_ENABLED) self.assertEqual(len(qf.expressions), 1) self.assertEqual(qf.expressions[0][0], _ret) self.assertEqual( @@ -67,7 +67,7 @@ def test_tuple_of_tuple_of_tuple_arg(self): "def test(a: Tuple[Tuple[Tuple[bool, bool], bool], bool]) -> bool:\n" + "\treturn a[0][0][0] and a[0][0][1] and a[0][1] and a[1]" ) - qf = qlassf(f, to_compile=True) + qf = qlassf(f, to_compile=COMPILATION_ENABLED) self.assertEqual(len(qf.expressions), 1) self.assertEqual(qf.expressions[0][0], _ret) self.assertEqual( @@ -78,7 +78,7 @@ def test_tuple_of_tuple_of_tuple_arg(self): def test_tuple_assign(self): f = "def test(a: Tuple[bool, bool]) -> bool:\n\tb = (a[1],a[0])\n\treturn b[0] and b[1]" - qf = qlassf(f, to_compile=True) + qf = qlassf(f, to_compile=COMPILATION_ENABLED) self.assertEqual(len(qf.expressions), 3) self.assertEqual(qf.expressions[-1][0], _ret) self.assertEqual(qf.expressions[-1][1], And(b_0, b_1)) @@ -90,7 +90,7 @@ def test_tuple_assign2(self): + "\tb = (a[0][1],a[0][0],a[1])\n" + "\treturn b[0] and b[1] and b[2]" ) - qf = qlassf(f, to_compile=True) + qf = qlassf(f, to_compile=COMPILATION_ENABLED) self.assertEqual(len(qf.expressions), 4) self.assertEqual(qf.expressions[-1][0], _ret) self.assertEqual(qf.expressions[-1][1], And(b_0, And(b_1, Symbol("b.2")))) @@ -102,7 +102,7 @@ def test_tuple_assign3(self): + "\tb = (a[0][1],(a[0][0],a[1]))\n" + "\treturn b[0] and b[1][0] and b[1][1]" ) - qf = qlassf(f, to_compile=True) + qf = qlassf(f, to_compile=COMPILATION_ENABLED) self.assertEqual(len(qf.expressions), 4) self.assertEqual(qf.expressions[-1][0], _ret) self.assertEqual( @@ -112,7 +112,7 @@ def test_tuple_assign3(self): def test_tuple_result(self): f = "def test(a: bool, b: bool) -> Tuple[bool,bool]:\n\treturn a,b" - qf = qlassf(f, to_compile=True) + qf = qlassf(f, to_compile=COMPILATION_ENABLED) self.assertEqual(len(qf.expressions), 2) self.assertEqual(qf.expressions[0][0], Symbol("_ret.0")) self.assertEqual(qf.expressions[0][1], a) diff --git a/test/utils.py b/test/utils.py index 415221ed..52b108f6 100644 --- a/test/utils.py +++ b/test/utils.py @@ -17,6 +17,8 @@ from qlasskit import QlassF +COMPILATION_ENABLED = True + def test_not(a: bool) -> bool: return not a @@ -45,6 +47,8 @@ def qiskit_measure_and_count(circ, shots=1): def compare_circuit_truth_table(cls, qf): + if not COMPILATION_ENABLED: + return truth_table = qf.truth_table() gate = qf.gate()