From f916dc659697f8073cace231a65b12b992bdf518 Mon Sep 17 00:00:00 2001 From: "Davide Gessa (dakk)" Date: Mon, 16 Oct 2023 12:25:50 +0200 Subject: [PATCH 1/9] add types to env --- qlasskit/ast2logic/env.py | 26 ++++++++++++++++++++++---- qlasskit/ast2logic/t_arguments.py | 23 +++++++++++------------ test/test_qlassf_bool.py | 3 +-- test/test_qlassf_tuple.py | 2 +- 4 files changed, 35 insertions(+), 19 deletions(-) diff --git a/qlasskit/ast2logic/env.py b/qlasskit/ast2logic/env.py index b60dbdfe..c73b3a1f 100644 --- a/qlasskit/ast2logic/env.py +++ b/qlasskit/ast2logic/env.py @@ -12,20 +12,39 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import List +from typing import List, Tuple from typing_extensions import TypeAlias -from ..types import Qint # noqa: F401, E402 +from ..types import BUILTIN_TYPES, Qint, Qtype # noqa: F401, E402 from . import exceptions from .typing import Arg Binding: TypeAlias = Arg +TypeBinding = Tuple[str, Qtype] class Env: def __init__(self): self.bindings: List[Binding] = [] + self.types: List[TypeBinding] = [] + + for t in BUILTIN_TYPES: + self.bind_type((t.__name__, t)) + + def bind_type(self, bb: TypeBinding): + if self.know_type(bb[0]): + return + self.types.append(bb) + + def know_type(self, type_name: str) -> bool: + return len(list(filter(lambda x: x[0] == type_name, self.types))) == 1 + + def gettype(self, type_name: str) -> Qtype: + try: + return list(filter(lambda x: x[0] == type_name, self.types))[0][1] + except: + raise exceptions.UnboundException(type_name, self) def bind(self, bb: Binding): if bb.name in self: @@ -34,8 +53,7 @@ def bind(self, bb: Binding): self.bindings.append(bb) def __contains__(self, key): - if len(list(filter(lambda x: x.name == key, self.bindings))) == 1: - return True + return len(list(filter(lambda x: x.name == key, self.bindings))) == 1 def __getitem__(self, key): try: diff --git a/qlasskit/ast2logic/t_arguments.py b/qlasskit/ast2logic/t_arguments.py index 293457e4..908a99ac 100644 --- a/qlasskit/ast2logic/t_arguments.py +++ b/qlasskit/ast2logic/t_arguments.py @@ -16,11 +16,11 @@ from ..types import * # noqa: F401, F403 from ..types import TType -from . import exceptions +from . import Env, exceptions from .typing import Arg, Args -def translate_argument(ann, base="") -> Arg: +def translate_argument(ann, env, base="") -> Arg: def to_name(a): return a.attr if isinstance(a, ast.Attribute) else a.id @@ -34,31 +34,30 @@ def to_name(a): al.append(f"{base}.{ind}") ttypes.append(bool) else: - inner_arg = translate_argument(i, base=f"{base}.{ind}") + inner_arg = translate_argument(i, env, base=f"{base}.{ind}") ttypes.append(inner_arg.ttype) al.extend(inner_arg.bitvec) ind += 1 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(base, eval(to_name(ann)), arg_list) - # Bool elif to_name(ann) == "bool": return Arg(base, bool, [f"{base}"]) + # Check if it is a know type in env + elif env.know_type(to_name(ann)): + t = env.gettype(to_name(ann)) + arg_list = [f"{base}.{i}" for i in range(t.BIT_SIZE)] + return Arg(base, t, arg_list) + else: raise exceptions.UnknownTypeException(ann) -def translate_arguments(args) -> Args: +def translate_arguments(args, env: Env) -> Args: """Parse an argument list""" args_unrolled = map( - lambda arg: translate_argument(arg.annotation, base=arg.arg), args + lambda arg: translate_argument(arg.annotation, env=env, base=arg.arg), args ) return list(args_unrolled) diff --git a/test/test_qlassf_bool.py b/test/test_qlassf_bool.py index 8bee8f16..8c931a3f 100644 --- a/test/test_qlassf_bool.py +++ b/test/test_qlassf_bool.py @@ -186,8 +186,7 @@ def test_assign3(self): qf = qlassf(f, to_compile=False) self.assertEqual(len(qf.expressions), 5) self.assertEqual(qf.expressions[-1][1], ITE(d & e, g, h)) - # TODO: this is not simulable without recycling registers - # compute_and_compare_results(self, qf) + compute_and_compare_results(self, qf) def test_reassign_exception(self): f = "def test(a: bool) -> bool:\n\ta = not a\n\treturn a" diff --git a/test/test_qlassf_tuple.py b/test/test_qlassf_tuple.py index cf03d4a9..58f858d5 100644 --- a/test/test_qlassf_tuple.py +++ b/test/test_qlassf_tuple.py @@ -50,7 +50,7 @@ def test_tuple_ite(self): self.assertEqual(len(qf.expressions), 2) self.assertEqual(qf.expressions[0][1], ITE(b, a_1, a_0)) self.assertEqual(qf.expressions[1][1], ITE(b, a_0, a_1)) - # compute_and_compare_results(self, qf) # TODO: fix + compute_and_compare_results(self, qf) def test_tuple_arg_assign(self): f = ( From 20b03a4ed871a191a0d2abd570efd81716b56335 Mon Sep 17 00:00:00 2001 From: "Davide Gessa (dakk)" Date: Mon, 16 Oct 2023 12:27:29 +0200 Subject: [PATCH 2/9] allow new type injection on qlassf --- qlasskit/ast2logic/t_ast.py | 4 ++-- qlasskit/qlassf.py | 13 ++++++++++--- qlasskit/types/__init__.py | 2 ++ test/test_ast2logic_t_arg.py | 16 ++++++++-------- 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/qlasskit/ast2logic/t_ast.py b/qlasskit/ast2logic/t_ast.py index 4af197db..5196685a 100644 --- a/qlasskit/ast2logic/t_ast.py +++ b/qlasskit/ast2logic/t_ast.py @@ -30,14 +30,14 @@ def translate_ast(fun) -> LogicFun: # env contains names visible from the current scope env = Env() - args: Args = translate_arguments(fun.args.args) + args: Args = translate_arguments(fun.args.args, env) [env.bind(arg) for arg in args] if not fun.returns: raise exceptions.NoReturnTypeException() - ret_ = translate_argument(fun.returns) # TODO: we need to preserve this + ret_ = translate_argument(fun.returns, env) # TODO: we need to preserve this ret_size = len(ret_) exps = [] diff --git a/qlasskit/qlassf.py b/qlasskit/qlassf.py index fd5c5ed9..cb07363f 100644 --- a/qlasskit/qlassf.py +++ b/qlasskit/qlassf.py @@ -20,6 +20,7 @@ from . import compiler from .ast2logic import Args, BoolExpList, flatten, translate_ast from .types import * # noqa: F403, F401 +from .types import Qtype MAX_TRUTH_TABLE_SIZE = 20 @@ -151,7 +152,9 @@ def f(self) -> Callable: return self.original_f @staticmethod - def from_function(f: Union[str, Callable], to_compile=True) -> "QlassF": + def from_function( + f: Union[str, Callable], types: List[Qtype] = [], to_compile: bool = True + ) -> "QlassF": """Create a QlassF from a function or a string containing a function""" if isinstance(f, str): exec(f) @@ -168,10 +171,14 @@ def from_function(f: Union[str, Callable], to_compile=True) -> "QlassF": return qf -def qlassf(f: Union[str, Callable], to_compile=True) -> QlassF: +def qlassf( + f: Union[str, Callable], types: List[Qtype] = [], to_compile: bool = True +) -> QlassF: """Decorator / function creating a QlassF object Args: f: String or function + types (List[Qtype], optional): A list of new types to bind + to_compile (bool, optional): Compile the circuit after parsing """ - return QlassF.from_function(f, to_compile) + return QlassF.from_function(f, types, to_compile) diff --git a/qlasskit/types/__init__.py b/qlasskit/types/__init__.py index 649eb3b1..87ebe7c3 100644 --- a/qlasskit/types/__init__.py +++ b/qlasskit/types/__init__.py @@ -15,3 +15,5 @@ from .qbool import Qbool # noqa: F401 from .qint import Qint, Qint2, Qint4, Qint8, Qint12, Qint16 # noqa: F401 from .qtype import Qtype, TExp, TType # noqa: F401 + +BUILTIN_TYPES = [Qint2, Qint4, Qint8, Qint12, Qint16] diff --git a/test/test_ast2logic_t_arg.py b/test/test_ast2logic_t_arg.py index 12fdff4e..51e02b68 100644 --- a/test/test_ast2logic_t_arg.py +++ b/test/test_ast2logic_t_arg.py @@ -25,14 +25,14 @@ def test_unknown_type(self): ann_ast = ast.parse(f).body[0].annotation self.assertRaises( exceptions.UnknownTypeException, - lambda ann_ast: ast2logic.translate_argument(ann_ast, "a"), + lambda ann_ast: ast2logic.translate_argument(ann_ast, ast2logic.Env(), "a"), ann_ast, ) def test_bool(self): f = "a: bool" ann_ast = ast.parse(f).body[0].annotation - c = ast2logic.translate_argument(ann_ast, "a") + c = ast2logic.translate_argument(ann_ast, ast2logic.Env(), "a") self.assertEqual(c.name, "a") self.assertEqual(c.ttype, bool) self.assertEqual(c.bitvec, ["a"]) @@ -40,7 +40,7 @@ def test_bool(self): def test_qint2(self): f = "a: Qint2" ann_ast = ast.parse(f).body[0].annotation - c = ast2logic.translate_argument(ann_ast, "a") + c = ast2logic.translate_argument(ann_ast, ast2logic.Env(), "a") self.assertEqual(c.name, "a") self.assertEqual(c.ttype, Qint2) self.assertEqual(c.bitvec, ["a.0", "a.1"]) @@ -48,7 +48,7 @@ def test_qint2(self): def test_qint4(self): f = "a: Qint4" ann_ast = ast.parse(f).body[0].annotation - c = ast2logic.translate_argument(ann_ast, "a") + c = ast2logic.translate_argument(ann_ast, ast2logic.Env(), "a") self.assertEqual(c.name, "a") self.assertEqual(c.ttype, Qint4) self.assertEqual(c.bitvec, ["a.0", "a.1", "a.2", "a.3"]) @@ -56,7 +56,7 @@ def test_qint4(self): def test_tuple(self): f = "a: Tuple[bool, bool]" ann_ast = ast.parse(f).body[0].annotation - c = ast2logic.translate_argument(ann_ast, "a") + c = ast2logic.translate_argument(ann_ast, ast2logic.Env(), "a") self.assertEqual(c.name, "a") self.assertEqual(c.ttype, Tuple[bool, bool]) self.assertEqual(c.bitvec, ["a.0", "a.1"]) @@ -64,7 +64,7 @@ def test_tuple(self): 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") + c = ast2logic.translate_argument(ann_ast, ast2logic.Env(), "a") 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"]) @@ -72,7 +72,7 @@ def test_tuple_of_tuple(self): 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") + c = ast2logic.translate_argument(ann_ast, ast2logic.Env(), "a") 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"]) @@ -80,7 +80,7 @@ def test_tuple_of_tuple2(self): 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") + c = ast2logic.translate_argument(ann_ast, ast2logic.Env(), "a") self.assertEqual(c.name, "a") self.assertEqual(c.ttype, Tuple[Qint2, Qint2]) self.assertEqual( From d7d7d3ef5c272c6edcb9f7b3a135385c57dba6be Mon Sep 17 00:00:00 2001 From: "Davide Gessa (dakk)" Date: Mon, 16 Oct 2023 13:00:45 +0200 Subject: [PATCH 3/9] const_to_qtype --- qlasskit/ast2logic/t_expression.py | 21 ++++++++++++++------- qlasskit/types/__init__.py | 13 +++++++++++++ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/qlasskit/ast2logic/t_expression.py b/qlasskit/ast2logic/t_expression.py index ea7de0cc..6b20ee21 100644 --- a/qlasskit/ast2logic/t_expression.py +++ b/qlasskit/ast2logic/t_expression.py @@ -17,7 +17,7 @@ from sympy import Symbol from sympy.logic import ITE, And, Not, Or, false, true -from ..types import Qbool, Qint, Qint2, Qint4, Qint8, Qint12, Qint16, TExp +from ..types import Qbool, Qint, TExp, const_to_qtype from . import Env, exceptions @@ -143,14 +143,11 @@ def unfold(v_exps, op): return (bool, true) elif expr.value is False: return (bool, false) - elif isinstance(expr.value, int): - v = expr.value - for t in [Qint2, Qint4, Qint8, Qint12, Qint16]: - if v < 2**t.BIT_SIZE: - return Qint.fill((t, Qint.const(v))) + q_value = const_to_qtype(expr.value) - raise Exception(f"Constant value is too big: {v}") + if q_value: + return q_value else: raise exceptions.ExpressionNotHandledException(expr) @@ -170,10 +167,14 @@ def unfold(v_exps, op): tleft = translate_expression(expr.left, env) tcomp = translate_expression(expr.comparators[0], env) + # TODO: check comparability here + # Eq if isinstance(expr.ops[0], ast.Eq): if tleft[0] == bool and tcomp[0] == bool: return (bool, Qbool.eq(tleft[1], tcomp[1])) + + # TODO: get here method from type class automatically, and use a type comparison table elif issubclass(tleft[0], Qint) and issubclass(tcomp[0], Qint): # type: ignore return Qint.eq(tleft, tcomp) @@ -183,6 +184,8 @@ def unfold(v_exps, op): elif isinstance(expr.ops[0], ast.NotEq): if tleft[0] == bool and tcomp[0] == bool: return (bool, Qbool.neq(tleft[1], tcomp[1])) + + # TODO: get here method from type class automatically, and use a type comparison table elif issubclass(tleft[0], Qint) and issubclass(tcomp[0], Qint): # type: ignore return Qint.neq(tleft, tcomp) @@ -190,6 +193,7 @@ def unfold(v_exps, op): # Lt elif isinstance(expr.ops[0], ast.Lt): + # TODO: get here method from type class automatically, and use a type comparison table if issubclass(tleft[0], Qint) and issubclass(tcomp[0], Qint): # type: ignore return Qint.lt(tleft, tcomp) @@ -197,6 +201,7 @@ def unfold(v_exps, op): # LtE elif isinstance(expr.ops[0], ast.LtE): + # TODO: get here method from type class automatically, and use a type comparison table if issubclass(tleft[0], Qint) and issubclass(tcomp[0], Qint): # type: ignore return Qint.lte(tleft, tcomp) @@ -204,6 +209,7 @@ def unfold(v_exps, op): # Gt elif isinstance(expr.ops[0], ast.Gt): + # TODO: get here method from type class automatically, and use a type comparison table if issubclass(tleft[0], Qint) and issubclass(tcomp[0], Qint): # type: ignore return Qint.gt(tleft, tcomp) @@ -211,6 +217,7 @@ def unfold(v_exps, op): # GtE elif isinstance(expr.ops[0], ast.GtE): + # TODO: get here method from type class automatically, and use a type comparison table if issubclass(tleft[0], Qint) and issubclass(tcomp[0], Qint): # type: ignore return Qint.gte(tleft, tcomp) diff --git a/qlasskit/types/__init__.py b/qlasskit/types/__init__.py index 87ebe7c3..9092b956 100644 --- a/qlasskit/types/__init__.py +++ b/qlasskit/types/__init__.py @@ -12,8 +12,21 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Any + from .qbool import Qbool # noqa: F401 from .qint import Qint, Qint2, Qint4, Qint8, Qint12, Qint16 # noqa: F401 from .qtype import Qtype, TExp, TType # noqa: F401 BUILTIN_TYPES = [Qint2, Qint4, Qint8, Qint12, Qint16] + + +def const_to_qtype(value: Any): + if isinstance(value, int): + for det_type in [Qint2, Qint4, Qint8, Qint12, Qint16]: + if value < 2**det_type.BIT_SIZE: + return det_type.const(value) + + raise Exception(f"Constant value is too big: {value}") + + return None From dc80c2dfcbc9af7d21010206a368c95f3f5347b2 Mon Sep 17 00:00:00 2001 From: "Davide Gessa (dakk)" Date: Mon, 16 Oct 2023 13:04:33 +0200 Subject: [PATCH 4/9] rename decompose_to_symbols --- qlasskit/ast2logic/__init__.py | 2 +- qlasskit/ast2logic/t_expression.py | 6 +++--- qlasskit/ast2logic/t_statement.py | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/qlasskit/ast2logic/__init__.py b/qlasskit/ast2logic/__init__.py index 97395573..7915a41e 100644 --- a/qlasskit/ast2logic/__init__.py +++ b/qlasskit/ast2logic/__init__.py @@ -17,7 +17,7 @@ from .utils import flatten # noqa: F401, E402 from .typing import Args, BoolExpList # 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 +from .t_expression import translate_expression, decompose_to_symbols # noqa: F401, E402 from .t_statement import translate_statement # noqa: F401, E402 from .t_ast import translate_ast # noqa: F401, E402 from . import exceptions # noqa: F401, E402 diff --git a/qlasskit/ast2logic/t_expression.py b/qlasskit/ast2logic/t_expression.py index 6b20ee21..d8017b36 100644 --- a/qlasskit/ast2logic/t_expression.py +++ b/qlasskit/ast2logic/t_expression.py @@ -21,13 +21,13 @@ from . import Env, exceptions -def type_of_exp(vlist, base, res=[]) -> List[Symbol]: - """Type inference for expressions: iterate over val, and decompose to bool""" +def decompose_to_symbols(vlist, base, res=[]) -> List[Symbol]: + """Decompose exp to symbols""" if isinstance(vlist, list): i = 0 res = [] for in_val in vlist: - r_new = type_of_exp(in_val, f"{base}.{i}", res) + r_new = decompose_to_symbols(in_val, f"{base}.{i}", res) if isinstance(r_new, list): res.extend(r_new) else: diff --git a/qlasskit/ast2logic/t_statement.py b/qlasskit/ast2logic/t_statement.py index 20551133..1bc82067 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 Binding, Env, exceptions, translate_expression, type_of_exp +from . import Binding, Env, decompose_to_symbols, exceptions, translate_expression def translate_statement( # noqa: C901 @@ -58,14 +58,14 @@ def translate_statement( # noqa: C901 raise exceptions.SymbolReassignedException(target) tval, val = translate_expression(stmt.value, env) # TODO: typecheck - res = type_of_exp(val, f"{target}") + res = decompose_to_symbols(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): texp, vexp = translate_expression(stmt.value, env) # TODO: typecheck - res = type_of_exp(vexp, "_ret") + res = decompose_to_symbols(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 From cc0936ebbcfe018dea36023298780b8c6f9cbe8c Mon Sep 17 00:00:00 2001 From: "Davide Gessa (dakk)" Date: Mon, 16 Oct 2023 13:38:36 +0200 Subject: [PATCH 5/9] type abi, and comparison simplification --- qlasskit/__init__.py | 1 - qlasskit/ast2logic/exceptions.py | 5 ++ qlasskit/ast2logic/t_expression.py | 84 +++++++++--------------------- qlasskit/types/__init__.py | 18 +++++-- qlasskit/types/qbool.py | 11 ++-- qlasskit/types/qint.py | 38 ++++++++------ qlasskit/types/qtype.py | 65 ++++++++++++++++++++++- test/utils.py | 4 +- 8 files changed, 138 insertions(+), 88 deletions(-) diff --git a/qlasskit/__init__.py b/qlasskit/__init__.py index 1a2208c3..658af585 100644 --- a/qlasskit/__init__.py +++ b/qlasskit/__init__.py @@ -20,7 +20,6 @@ from .ast2logic import exceptions # noqa: F401 from .types import ( # noqa: F401, F403 Qtype, - Qbool, Qint, Qint2, Qint4, diff --git a/qlasskit/ast2logic/exceptions.py b/qlasskit/ast2logic/exceptions.py index d93b0660..8ae82cf8 100644 --- a/qlasskit/ast2logic/exceptions.py +++ b/qlasskit/ast2logic/exceptions.py @@ -15,6 +15,11 @@ import ast +class OperationNotSupportedException(Exception): + def __init__(self, tt, op): + super().__init__(f"Operation '{op}' not supported by type {tt}") + + class TypeErrorException(Exception): def __init__(self, got, excepted): super().__init__(f"Got '{got}' excepted '{excepted}'") diff --git a/qlasskit/ast2logic/t_expression.py b/qlasskit/ast2logic/t_expression.py index d8017b36..b1d3065d 100644 --- a/qlasskit/ast2logic/t_expression.py +++ b/qlasskit/ast2logic/t_expression.py @@ -17,7 +17,7 @@ from sympy import Symbol from sympy.logic import ITE, And, Not, Or, false, true -from ..types import Qbool, Qint, TExp, const_to_qtype +from ..types import Qbool, Qtype, TExp, const_to_qtype from . import Env, exceptions @@ -167,65 +167,33 @@ def unfold(v_exps, op): tleft = translate_expression(expr.left, env) tcomp = translate_expression(expr.comparators[0], env) - # TODO: check comparability here - - # Eq - if isinstance(expr.ops[0], ast.Eq): - if tleft[0] == bool and tcomp[0] == bool: - return (bool, Qbool.eq(tleft[1], tcomp[1])) - - # TODO: get here method from type class automatically, and use a type comparison table - elif issubclass(tleft[0], Qint) and issubclass(tcomp[0], Qint): # type: ignore - return Qint.eq(tleft, tcomp) - - raise exceptions.TypeErrorException(tcomp[0], tleft[0]) - - # NotEq - elif isinstance(expr.ops[0], ast.NotEq): - if tleft[0] == bool and tcomp[0] == bool: - return (bool, Qbool.neq(tleft[1], tcomp[1])) - - # TODO: get here method from type class automatically, and use a type comparison table - elif issubclass(tleft[0], Qint) and issubclass(tcomp[0], Qint): # type: ignore - return Qint.neq(tleft, tcomp) - - raise exceptions.TypeErrorException(tcomp[0], tleft[0]) - - # Lt - elif isinstance(expr.ops[0], ast.Lt): - # TODO: get here method from type class automatically, and use a type comparison table - if issubclass(tleft[0], Qint) and issubclass(tcomp[0], Qint): # type: ignore - return Qint.lt(tleft, tcomp) - - raise exceptions.TypeErrorException(tcomp[0], tleft[0]) - - # LtE - elif isinstance(expr.ops[0], ast.LtE): - # TODO: get here method from type class automatically, and use a type comparison table - if issubclass(tleft[0], Qint) and issubclass(tcomp[0], Qint): # type: ignore - return Qint.lte(tleft, tcomp) - - raise exceptions.TypeErrorException(tcomp[0], tleft[0]) - - # Gt - elif isinstance(expr.ops[0], ast.Gt): - # TODO: get here method from type class automatically, and use a type comparison table - if issubclass(tleft[0], Qint) and issubclass(tcomp[0], Qint): # type: ignore - return Qint.gt(tleft, tcomp) - - raise exceptions.TypeErrorException(tcomp[0], tleft[0]) - - # GtE - elif isinstance(expr.ops[0], ast.GtE): - # TODO: get here method from type class automatically, and use a type comparison table - if issubclass(tleft[0], Qint) and issubclass(tcomp[0], Qint): # type: ignore - return Qint.gte(tleft, tcomp) - - raise exceptions.TypeErrorException(tcomp[0], tleft[0]) + # Check comparability + if tleft[0] == bool and tcomp[0] == bool: + op_type = Qbool + elif issubclass(tleft[0], Qtype) and issubclass(tcomp[0], Qtype): # type: ignore + if not tleft[0].comparable(tcomp[0]): # type: ignore + raise exceptions.TypeErrorException(tcomp[0], tleft[0]) + op_type = tleft[0] # type: ignore + + # Call the comparator + comparators = [ + (ast.Eq, "eq"), + (ast.NotEq, "neq"), + (ast.Lt, "lt"), + (ast.LtE, "lte"), + (ast.Gt, "gt"), + (ast.GtE, "gte"), + ] + + for ast_comp, comp_name in comparators: + if isinstance(expr.ops[0], ast_comp): + if not hasattr(op_type, comp_name): + raise exceptions.OperationNotSupportedException(op_type, comp_name) + + return getattr(op_type, comp_name)(tleft, tcomp) # Is | IsNot | In | NotIn - else: - raise exceptions.ExpressionNotHandledException(expr) + raise exceptions.ExpressionNotHandledException(expr) # Binop elif isinstance(expr, ast.BinOp): diff --git a/qlasskit/types/__init__.py b/qlasskit/types/__init__.py index 9092b956..052d7bff 100644 --- a/qlasskit/types/__init__.py +++ b/qlasskit/types/__init__.py @@ -11,12 +11,24 @@ # 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. +# isort:skip_file from typing import Any -from .qbool import Qbool # noqa: F401 -from .qint import Qint, Qint2, Qint4, Qint8, Qint12, Qint16 # noqa: F401 -from .qtype import Qtype, TExp, TType # noqa: F401 +from sympy.logic import Not, Xor + + +def _neq(a, b): + return Xor(a, b) + + +def _eq(a, b): + return Not(_neq(a, b)) + + +from .qtype import Qtype, TExp, TType # noqa: F401, E402 +from .qbool import Qbool # noqa: F401, E402 +from .qint import Qint, Qint2, Qint4, Qint8, Qint12, Qint16 # noqa: F401, E402 BUILTIN_TYPES = [Qint2, Qint4, Qint8, Qint12, Qint16] diff --git a/qlasskit/types/qbool.py b/qlasskit/types/qbool.py index e52b72bb..d6ab03d3 100644 --- a/qlasskit/types/qbool.py +++ b/qlasskit/types/qbool.py @@ -11,15 +11,14 @@ # 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 sympy.logic import Not, Xor +from . import TExp, _eq, _neq class Qbool: @staticmethod - def neq(a, b): - return Xor(a, b) + def eq(tleft: TExp, tcomp: TExp) -> TExp: + return (tleft[0], _eq(tleft[1], tcomp[1])) @staticmethod - def eq(a, b): - return Not(Qbool.neq(a, b)) + def neq(tleft: TExp, tcomp: TExp) -> TExp: + return (tleft[0], _neq(tleft[1], tcomp[1])) diff --git a/qlasskit/types/qint.py b/qlasskit/types/qint.py index cf461fe3..e714e293 100644 --- a/qlasskit/types/qint.py +++ b/qlasskit/types/qint.py @@ -12,13 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import List, Tuple +from typing import List from sympy import Symbol from sympy.logic import And, Not, Or, false, true -from .qbool import Qbool -from .qtype import Qtype, TExp, TType +from . import _eq, _neq +from .qtype import Qtype, TExp class Qint(int, Qtype): @@ -28,27 +28,29 @@ def __init__(self, value): super().__init__() self.value = value - def __getitem__(self, i): - if i > self.BIT_SIZE: - raise Exception("Unbound") - - return self.to_bool_str()[i] == "1" - @classmethod def from_bool(cls, v: List[bool]): return cls(int("".join(map(lambda x: "1" if x else "0", v[::-1])), 2)) - def to_bool_str(self) -> str: + def to_bin(self) -> str: s = bin(self.value)[2:][0 : self.BIT_SIZE] return ("0" * (self.BIT_SIZE - len(s)) + s)[::-1] - @staticmethod - def const(v: int) -> List[bool]: + @classmethod + def comparable(cls, other_type=None) -> bool: + """Return true if the type is comparable with itself or + with [other_type]""" + if not other_type or issubclass(other_type, Qint): + return True + return False + + @classmethod + def const(cls, v: int) -> TExp: """Return the list of bool representing an int""" - return list(map(lambda c: True if c == "1" else False, bin(v)[2:]))[::-1] + return (cls, list(map(lambda c: True if c == "1" else False, bin(v)[2:]))[::-1]) @staticmethod - def fill(v: Tuple[TType, List[bool]]) -> Tuple[TType, List[bool]]: + def fill(v: TExp) -> TExp: """Fill a Qint to reach its bit_size""" if len(v[1]) < v[0].BIT_SIZE: # type: ignore v = ( @@ -57,12 +59,14 @@ def fill(v: Tuple[TType, List[bool]]) -> Tuple[TType, List[bool]]: ) return v + # Comparators + @staticmethod def eq(tleft: TExp, tcomp: TExp) -> TExp: """Compare two Qint for equality""" ex = true for x in zip(tleft[1], tcomp[1]): - ex = And(ex, Qbool.eq(x[0], x[1])) + ex = And(ex, _eq(x[0], x[1])) if len(tleft[1]) > len(tcomp[1]): for x in tleft[1][len(tcomp[1]) :]: @@ -79,7 +83,7 @@ def neq(tleft: TExp, tcomp: TExp) -> TExp: """Compare two Qint for inequality""" ex = false for x in zip(tleft[1], tcomp[1]): - ex = Or(ex, Qbool.neq(x[0], x[1])) + ex = Or(ex, _neq(x[0], x[1])) if len(tleft[1]) > len(tcomp[1]): for x in tleft[1][len(tcomp[1]) :]: @@ -102,7 +106,7 @@ def gt(tleft: TExp, tcomp: TExp) -> TExp: else: ex = Or(ex, And(*(prev + [a, Not(b)]))) - prev.append(Qbool.eq(a, b)) + prev.append(_eq(a, b)) if len(tleft[1]) > len(tcomp[1]): for x in tleft[1][len(tcomp[1]) :]: diff --git a/qlasskit/types/qtype.py b/qlasskit/types/qtype.py index 34999621..8255bee7 100644 --- a/qlasskit/types/qtype.py +++ b/qlasskit/types/qtype.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Tuple +from typing import Any, List, Tuple from sympy.logic.boolalg import Boolean from typing_extensions import TypeAlias @@ -23,3 +23,66 @@ class Qtype: BIT_SIZE = 0 + + def __getitem__(self, i): + """Return the i-nth bit value""" + if i > self.BIT_SIZE: + raise Exception("Unbound") + + return self.to_bin()[i] == "1" + + def to_bin(self) -> str: + """Return the binary representation of the value""" + raise Exception("abstract") + + @classmethod + def from_bool(cls, v: List[bool]) -> "Qtype": + """Return the Qtype object from a list of booleans""" + raise Exception("abstract") + + @classmethod + def comparable(cls, other_type=None) -> bool: + """Return true if the type is comparable with itself or + with [other_type]""" + raise Exception("abstract") + + @classmethod + def size(cls) -> int: + """Return the size in bit""" + return cls.BIT_SIZE + + @classmethod + def const(cls, value: Any) -> TExp: + """Return a list of bool representing the value""" + raise Exception("abstract") + + @staticmethod + def fill(v: TExp) -> TExp: + """Fill with leading false""" + raise Exception("abstract") + + # Comparators + + @staticmethod + def eq(tleft: TExp, tcomp: TExp) -> TExp: + raise Exception("abstract") + + @staticmethod + def neq(tleft: TExp, tcomp: TExp) -> TExp: + raise Exception("abstract") + + @staticmethod + def gt(tleft: TExp, tcomp: TExp) -> TExp: + raise Exception("abstract") + + @staticmethod + def gte(tleft: TExp, tcomp: TExp) -> TExp: + raise Exception("abstract") + + @staticmethod + def lt(tleft: TExp, tcomp: TExp) -> TExp: + raise Exception("abstract") + + @staticmethod + def lte(tleft: TExp, tcomp: TExp) -> TExp: + raise Exception("abstract") diff --git a/test/utils.py b/test/utils.py index 300253a4..6e03d0ed 100644 --- a/test/utils.py +++ b/test/utils.py @@ -115,9 +115,9 @@ def res_to_str(res): elif type(res) == int: qi = Qint(res) qi.BIT_SIZE = len(bin(res)) - 2 - return qi.to_bool_str() + return qi.to_bin() else: - return res.to_bool_str() + return res.to_bin() args = [] i = 0 From 0975213a277e06f1b6afff6e312418b7fbe04063 Mon Sep 17 00:00:00 2001 From: "Davide Gessa (dakk)" Date: Tue, 17 Oct 2023 16:07:25 +0200 Subject: [PATCH 6/9] allow custom types and tests --- qlasskit/ast2logic/t_ast.py | 3 ++- qlasskit/qlassf.py | 2 +- test/test_qlassf.py | 17 ++++++++++++++++- test/utils.py | 11 ++++------- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/qlasskit/ast2logic/t_ast.py b/qlasskit/ast2logic/t_ast.py index 5196685a..5ff82268 100644 --- a/qlasskit/ast2logic/t_ast.py +++ b/qlasskit/ast2logic/t_ast.py @@ -24,11 +24,12 @@ from .typing import Args, LogicFun -def translate_ast(fun) -> LogicFun: +def translate_ast(fun, types) -> LogicFun: fun_name: str = fun.name # env contains names visible from the current scope env = Env() + [env.bind_type((t.__name__, t)) for t in types] args: Args = translate_arguments(fun.args.args, env) diff --git a/qlasskit/qlassf.py b/qlasskit/qlassf.py index cb07363f..6d161f35 100644 --- a/qlasskit/qlassf.py +++ b/qlasskit/qlassf.py @@ -162,7 +162,7 @@ def from_function( fun_ast = ast.parse(f if isinstance(f, str) else inspect.getsource(f)) fun = fun_ast.body[0] - fun_name, args, fun_ret, exps = translate_ast(fun) + fun_name, args, fun_ret, exps = translate_ast(fun, types) original_f = eval(fun_name) if isinstance(f, str) else f qf = QlassF(fun_name, original_f, args, fun_ret, exps) diff --git a/test/test_qlassf.py b/test/test_qlassf.py index 6fab4b91..f659330d 100644 --- a/test/test_qlassf.py +++ b/test/test_qlassf.py @@ -14,7 +14,7 @@ import unittest -from qlasskit import Qint4, Qint12, QlassF, qlassf +from qlasskit import Qint, Qint4, Qint12, QlassF, exceptions, qlassf from . import utils from .utils import COMPILATION_ENABLED, compute_and_compare_results @@ -34,6 +34,21 @@ def test_decorator(self): self.assertTrue(isinstance(c, QlassF)) +class TestQlassfCustomTypes(unittest.TestCase): + def test_custom_qint3(self): + qf = qlassf( + utils.test_qint3, types=[utils.Qint3], to_compile=COMPILATION_ENABLED + ) + compute_and_compare_results(self, qf) + + def test_custom_qint3_notfound(self): + self.assertRaises( + exceptions.UnknownTypeException, + lambda f: qlassf(f, types=[], to_compile=COMPILATION_ENABLED), + utils.test_qint3, + ) + + class TestQlassfTruthTable(unittest.TestCase): def test_not_truth(self): f = "def test(a: bool) -> bool:\n\treturn not a" diff --git a/test/utils.py b/test/utils.py index 6e03d0ed..e9e69c66 100644 --- a/test/utils.py +++ b/test/utils.py @@ -29,17 +29,14 @@ def test_not(a: bool) -> bool: return not a -# def get_qlassf_input_bits(qf: QlassF) -> int: -# pass +class Qint3(Qint): + BIT_SIZE = 3 -# def get_input_combinations(n_bits: int) -> List[List[bool]]: -# pass +def test_qint3(a: Qint3) -> bool: + return not a[0] -# def compute_originalf_results(qf: QlassF) -> List[List[bool]]: -# pass - aer_simulator = Aer.get_backend("aer_simulator") From cfa6e2173409badf0173acafb995dc0aa39c7964 Mon Sep 17 00:00:00 2001 From: "Davide Gessa (dakk)" Date: Tue, 17 Oct 2023 16:32:00 +0200 Subject: [PATCH 7/9] qtype to amplitude and export --- qlasskit/types/qint.py | 5 +++++ qlasskit/types/qtype.py | 14 +++++++++++++- test/test_qlassf_int.py | 16 +++++++++++++++- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/qlasskit/types/qint.py b/qlasskit/types/qint.py index e714e293..00c653eb 100644 --- a/qlasskit/types/qint.py +++ b/qlasskit/types/qint.py @@ -36,6 +36,11 @@ def to_bin(self) -> str: s = bin(self.value)[2:][0 : self.BIT_SIZE] return ("0" * (self.BIT_SIZE - len(s)) + s)[::-1] + def to_amplitudes(self) -> List[complex]: + ampl = [0.] * 2**self.BIT_SIZE + ampl[self.value] = 1 + return ampl + @classmethod def comparable(cls, other_type=None) -> bool: """Return true if the type is comparable with itself or diff --git a/qlasskit/types/qtype.py b/qlasskit/types/qtype.py index 8255bee7..00524884 100644 --- a/qlasskit/types/qtype.py +++ b/qlasskit/types/qtype.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, List, Tuple +from typing import Any, List, Tuple, Literal from sympy.logic.boolalg import Boolean from typing_extensions import TypeAlias @@ -35,6 +35,18 @@ def to_bin(self) -> str: """Return the binary representation of the value""" raise Exception("abstract") + def to_amplitudes(self) -> List[complex]: + """Return complex amplitudes to initialize the current value on a quantum circuit""" + raise Exception("abstract") + + def export(self, mode: Literal["amplitudes", "binary"] = "binary"): + if mode == "amplitudes": + return self.to_amplitudes() + elif mode == "binary": + return self.to_bin() + else: + raise Exception(f"Mode {mode} not supported") + @classmethod def from_bool(cls, v: List[bool]) -> "Qtype": """Return the Qtype object from a list of booleans""" diff --git a/test/test_qlassf_int.py b/test/test_qlassf_int.py index 8b3a793c..dd31f98e 100644 --- a/test/test_qlassf_int.py +++ b/test/test_qlassf_int.py @@ -26,13 +26,27 @@ _ret = Symbol("_ret") + +class TestQint(unittest.TestCase): + def test_qint2_to_amplitudes(self): + c = Qint2(int(1)).to_amplitudes() + self.assertEqual(c, [0.0, 1, 0.0, 0.0]) + self.assertEqual(c, Qint2(1).export('amplitudes')) + + def test_qint2_to_bin(self): + c = Qint2(1).to_bin() + self.assertEqual(c, '10') + self.assertEqual(c, Qint2(1).export('binary')) + + + @parameterized_class( ("ttype", "ttype_str", "ttype_size"), [ (Qint2, "Qint2", 2), (Qint4, "Qint4", 4), (Qint8, "Qint8", 8), - ], + ] ) class TestQlassfIntParametrized_2_4_8(unittest.TestCase): def test_int_arg(self): From 3b91a4fe340680ce63e0722ec3c2a11117598776 Mon Sep 17 00:00:00 2001 From: "Davide Gessa (dakk)" Date: Tue, 17 Oct 2023 16:41:44 +0200 Subject: [PATCH 8/9] doc for qtype --- TODO.md | 1 + docs/source/index.rst | 1 + docs/source/qcircuit.rst | 3 ++- docs/source/qlassf.rst | 1 + docs/source/types.rst | 8 ++++++++ 5 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 docs/source/types.rst diff --git a/TODO.md b/TODO.md index 9c3f583f..968de9ee 100644 --- a/TODO.md +++ b/TODO.md @@ -47,6 +47,7 @@ - [x] Fix code structure and typing location ### Week 4: (16 Oct 23) +- [x] Extensible type system - [ ] Publish doc - [ ] Int arithmetic: + - [ ] Function call diff --git a/docs/source/index.rst b/docs/source/index.rst index 8b03c5d6..5231df63 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -21,6 +21,7 @@ Python and translate them into unitary operators (gates) for use in quantum circ howitworks supported qlassf + types qcircuit diff --git a/docs/source/qcircuit.rst b/docs/source/qcircuit.rst index 67effcf2..b007f764 100644 --- a/docs/source/qcircuit.rst +++ b/docs/source/qcircuit.rst @@ -3,6 +3,7 @@ QCircuit QCircuit represents a quantum circuit inside the Qlasskit library. -.. automodule:: qlasskit.qcircuit +.. autoclass:: qlasskit.qcircuit.QCircuit :members: + :undoc-members: diff --git a/docs/source/qlassf.rst b/docs/source/qlassf.rst index 6dfe1584..9ba2a636 100644 --- a/docs/source/qlassf.rst +++ b/docs/source/qlassf.rst @@ -5,3 +5,4 @@ QlassF is the key component of the qlasskit library. .. automodule:: qlasskit.qlassf :members: + :undoc-members: diff --git a/docs/source/types.rst b/docs/source/types.rst new file mode 100644 index 00000000..5791c9e5 --- /dev/null +++ b/docs/source/types.rst @@ -0,0 +1,8 @@ +Types +==================================== + +All supported types by the qlasskit library are inherit from the base `Qtype`. + +.. autoclass:: qlasskit.Qtype + :members: + :undoc-members: From 0fd2e089b703a5b008953fcd6264fc8ba649edaa Mon Sep 17 00:00:00 2001 From: "Davide Gessa (dakk)" Date: Tue, 17 Oct 2023 16:42:48 +0200 Subject: [PATCH 9/9] fix linter --- qlasskit/types/qint.py | 6 +++--- qlasskit/types/qtype.py | 8 ++++---- test/test_qlassf_int.py | 12 +++++------- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/qlasskit/types/qint.py b/qlasskit/types/qint.py index 00c653eb..6c7049ba 100644 --- a/qlasskit/types/qint.py +++ b/qlasskit/types/qint.py @@ -36,11 +36,11 @@ def to_bin(self) -> str: s = bin(self.value)[2:][0 : self.BIT_SIZE] return ("0" * (self.BIT_SIZE - len(s)) + s)[::-1] - def to_amplitudes(self) -> List[complex]: - ampl = [0.] * 2**self.BIT_SIZE + def to_amplitudes(self) -> List[float]: + ampl = [0.0] * 2**self.BIT_SIZE ampl[self.value] = 1 return ampl - + @classmethod def comparable(cls, other_type=None) -> bool: """Return true if the type is comparable with itself or diff --git a/qlasskit/types/qtype.py b/qlasskit/types/qtype.py index 00524884..1c7f7e2c 100644 --- a/qlasskit/types/qtype.py +++ b/qlasskit/types/qtype.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, List, Tuple, Literal +from typing import Any, List, Literal, Tuple from sympy.logic.boolalg import Boolean from typing_extensions import TypeAlias @@ -35,8 +35,8 @@ def to_bin(self) -> str: """Return the binary representation of the value""" raise Exception("abstract") - def to_amplitudes(self) -> List[complex]: - """Return complex amplitudes to initialize the current value on a quantum circuit""" + def to_amplitudes(self) -> List[float]: + """Return amplitudes to initialize the current value on a quantum circuit""" raise Exception("abstract") def export(self, mode: Literal["amplitudes", "binary"] = "binary"): @@ -45,7 +45,7 @@ def export(self, mode: Literal["amplitudes", "binary"] = "binary"): elif mode == "binary": return self.to_bin() else: - raise Exception(f"Mode {mode} not supported") + raise Exception(f"Mode {mode} not supported") @classmethod def from_bool(cls, v: List[bool]) -> "Qtype": diff --git a/test/test_qlassf_int.py b/test/test_qlassf_int.py index dd31f98e..988f57aa 100644 --- a/test/test_qlassf_int.py +++ b/test/test_qlassf_int.py @@ -26,27 +26,25 @@ _ret = Symbol("_ret") - class TestQint(unittest.TestCase): def test_qint2_to_amplitudes(self): c = Qint2(int(1)).to_amplitudes() self.assertEqual(c, [0.0, 1, 0.0, 0.0]) - self.assertEqual(c, Qint2(1).export('amplitudes')) - + self.assertEqual(c, Qint2(1).export("amplitudes")) + def test_qint2_to_bin(self): c = Qint2(1).to_bin() - self.assertEqual(c, '10') - self.assertEqual(c, Qint2(1).export('binary')) + self.assertEqual(c, "10") + self.assertEqual(c, Qint2(1).export("binary")) - @parameterized_class( ("ttype", "ttype_str", "ttype_size"), [ (Qint2, "Qint2", 2), (Qint4, "Qint4", 4), (Qint8, "Qint8", 8), - ] + ], ) class TestQlassfIntParametrized_2_4_8(unittest.TestCase): def test_int_arg(self):