diff --git a/TODO.md b/TODO.md index ee26c60f..b00a4d79 100644 --- a/TODO.md +++ b/TODO.md @@ -50,7 +50,7 @@ - [x] Extensible type system - [x] Builtin functions: max(), min(), len() - [x] Function call (to builtin) -- [x] Int arithmetic: + +- [x] Int arithmetic: +, - - [x] Qtype: bitwise not - [x] Qtype: shift right / left - [x] Int: subtraction @@ -62,10 +62,14 @@ - [x] Remove unneccessary expressions - [x] Remove quantum circuit identities - [x] For unrolling +- [x] Bitwise xor, or, and - [ ] Aggregate cascading expressions in for unrolling ### Week 2: (30 Oct 23) ### Week 3: (6 Nov 23) + +- [ ] Slideshow for UF midterm + ### Week 4: (13 Nov 23) ## Month 3: @@ -93,13 +97,13 @@ - [ ] Publish doc on github - [ ] Inner function -- [ ] Int arithmetic expressions (*, /) +- [ ] Int arithmetic expressions (*, /, mod) - [ ] Parametrized qlassf - [ ] Lambda - [ ] FixedList type - [ ] Builtin functions: map, count - [ ] Ast2logic: fixed size loops unrolling -- [ ] Builtin functions: sum(), all(), +- [ ] Builtin functions: sum(), all(), any() - [ ] First beta release ### Language support @@ -135,4 +139,9 @@ ### Tools - [ ] py2qasm tool -- [ ] py2boolexp tool \ No newline at end of file +- [ ] py2boolexp tool + + +### Experiments + +- [ ] Logic2FPGA backend \ No newline at end of file diff --git a/qlasskit/ast2ast.py b/qlasskit/ast2ast.py index 3f622c52..c652f5b8 100644 --- a/qlasskit/ast2ast.py +++ b/qlasskit/ast2ast.py @@ -76,7 +76,7 @@ def visit_Assign(self, node): # TODO: support unrolling tuple # TODO: if value is not self referencing, we can skip this (ie: a = b + 1) - + # Reassigning an already present variable (use a temp variable) if was_known and not isinstance(node.value, ast.Constant): new_targ = ast.Name(id=f"__{node.targets[0].id}", ctx=ast.Load()) diff --git a/qlasskit/ast2logic/env.py b/qlasskit/ast2logic/env.py index a2d9164f..06540e0b 100644 --- a/qlasskit/ast2logic/env.py +++ b/qlasskit/ast2logic/env.py @@ -31,8 +31,7 @@ def __init__(self) -> None: for t in BUILTIN_TYPES: self.bind_type((t.__name__, t)) # type: ignore - - + def __repr__(self): return str((self.bindings, self.types)) @@ -56,7 +55,7 @@ def bind(self, bb: Binding, rebind=False): if rebind: self.bindings.remove(self[bb.name]) - + self.bindings.append(bb) def __contains__(self, key): diff --git a/qlasskit/ast2logic/t_expression.py b/qlasskit/ast2logic/t_expression.py index cac71ed2..56a4444f 100644 --- a/qlasskit/ast2logic/t_expression.py +++ b/qlasskit/ast2logic/t_expression.py @@ -15,7 +15,7 @@ from typing import List, Tuple, get_args from sympy import Symbol -from sympy.logic import ITE, And, Not, Or, false, true +from sympy.logic import ITE, And, Not, Or, Xor, false, true from ..types import Qbool, Qtype, TExp, const_to_qtype from . import Env, exceptions @@ -205,16 +205,29 @@ def unfold(v_exps, op): # Binop elif isinstance(expr, ast.BinOp): - # Sub | Mult | MatMult | Div | Mod | Pow | - # | BitOr | BitXor | BitAnd | FloorDiv + # Sub | Mult | MatMult | Div | Mod | Pow | FloorDiv # print(ast.dump(expr)) tleft = translate_expression(expr.left, env) tright = translate_expression(expr.right, env) + if tleft[0] == bool and tright[0] == bool: + if isinstance(expr.op, ast.BitXor): + return bool, Xor(tleft[1], tright[1]) + elif isinstance(expr.op, ast.BitAnd): + return bool, And(tleft[1], tright[1]) + elif isinstance(expr.op, ast.BitOr): + return bool, Or(tleft[1], tright[1]) + if isinstance(expr.op, ast.Add) and hasattr(tleft[0], "add"): return tleft[0].add(tleft, tright) elif isinstance(expr.op, ast.Sub) and hasattr(tleft[0], "sub"): return tleft[0].sub(tleft, tright) + elif isinstance(expr.op, ast.BitXor) and hasattr(tleft[0], "bitwise_xor"): + return tleft[0].bitwise_xor(tleft, tright) + elif isinstance(expr.op, ast.BitAnd) and hasattr(tleft[0], "bitwise_and"): + return tleft[0].bitwise_and(tleft, tright) + elif isinstance(expr.op, ast.BitOr) and hasattr(tleft[0], "bitwise_or"): + return tleft[0].bitwise_or(tleft, tright) elif ( isinstance(expr.op, ast.LShift) and hasattr(tleft[0], "shift_left") diff --git a/qlasskit/compiler/poccompiler2.py b/qlasskit/compiler/poccompiler2.py index 7cb49518..716e7cc4 100644 --- a/qlasskit/compiler/poccompiler2.py +++ b/qlasskit/compiler/poccompiler2.py @@ -64,7 +64,8 @@ def compile(self, name, args: Args, returns: Arg, exprs: BoolExpList) -> QCircui for arg in args: for arg_b in arg.bitvec: - qc.add_qubit(arg_b) + qi = qc.add_qubit(arg_b) + # qc.ancilla_lst.add(qi) self.expqmap = ExpQMap() @@ -88,7 +89,7 @@ def compile(self, name, args: Args, returns: Arg, exprs: BoolExpList) -> QCircui qc.remove_identities() return qc - def compile_expr(self, qc: QCircuit, expr: Boolean) -> int: # noqa: C901 + def compile_expr(self, qc: QCircuit, expr: Boolean, dest=None) -> int: # noqa: C901 if isinstance(expr, Symbol): return qc[expr.name] @@ -105,31 +106,33 @@ def compile_expr(self, qc: QCircuit, expr: Boolean) -> int: # noqa: C901 self.expqmap.update_exp_for_qubit(eret, expr) return eret else: - fa = qc.get_free_ancilla() - qc.cx(eret, fa) - qc.x(fa) + if dest is None: + dest = qc.get_free_ancilla() + qc.cx(eret, dest) + qc.x(dest) qc.mark_ancilla(eret) self.garbage_collect(qc) - self.expqmap[expr] = fa + self.expqmap[expr] = dest - return fa + return dest elif isinstance(expr, And): erets = list(map(lambda e: self.compile_expr(qc, e), expr.args)) - fa = qc.get_free_ancilla() + if dest is None: + dest = qc.get_free_ancilla() qc.barrier("and") - qc.mcx(erets, fa) + qc.mcx(erets, dest) [qc.mark_ancilla(eret) for eret in erets] self.garbage_collect(qc) - self.expqmap[expr] = fa + self.expqmap[expr] = dest - return fa + return dest elif isinstance(expr, Xor): erets = list(map(lambda e: self.compile_expr(qc, e), expr.args)) @@ -138,30 +141,32 @@ def compile_expr(self, qc: QCircuit, expr: Boolean) -> int: # noqa: C901 qc.barrier("xor") if last in qc.ancilla_lst: - fa = last + dest = last self.expqmap.update_exp_for_qubit(last, expr) else: - fa = qc.get_free_ancilla() + if dest is None: + dest = qc.get_free_ancilla() - qc.cx(last, fa) + qc.cx(last, dest) qc.mark_ancilla(last) - self.expqmap[expr] = fa + self.expqmap[expr] = dest for x in erets: - qc.cx(x, fa) + qc.cx(x, dest) [qc.mark_ancilla(eret) for eret in erets] self.garbage_collect(qc) - return fa + return dest elif isinstance(expr, BooleanFalse): return qc.get_free_ancilla() elif isinstance(expr, BooleanTrue): - fa = qc.get_free_ancilla() - qc.x(fa) - return fa + if dest is None: + dest = qc.get_free_ancilla() + qc.x(dest) + return dest else: raise CompilerException(expr) diff --git a/qlasskit/qlassf.py b/qlasskit/qlassf.py index 133643fc..d78a0e85 100644 --- a/qlasskit/qlassf.py +++ b/qlasskit/qlassf.py @@ -51,23 +51,23 @@ def remove_const_exps(exps: BoolExpList, fun_ret: Arg) -> BoolExpList: # const: Dict[Symbol, Boolean] = {} # n_exps: BoolExpList = [] # print(exps) - + # for i in range(len(exps)): # (s, e) = exps[i] # e = e.subs(const) # const[s] = e - + # for x in e.free_symbols: # if x in const: # n_exps.append((x, const[x])) # del const[x] - + # for (s,e) in const.items(): # if s == e: # continue - + # n_exps.append((s,e)) - + # print(n_exps) # print() # print() diff --git a/qlasskit/types/qint.py b/qlasskit/types/qint.py index 2d1b87ca..308c6f7e 100644 --- a/qlasskit/types/qint.py +++ b/qlasskit/types/qint.py @@ -15,7 +15,7 @@ from typing import List from sympy import Symbol -from sympy.logic import And, Not, Or, false, true +from sympy.logic import And, Not, Or, Xor, false, true from . import _eq, _full_adder, _neq from .qtype import Qtype, TExp @@ -171,6 +171,30 @@ def sub(cls, tleft: TExp, tright: TExp) -> TExp: su = cls.add(an, cls.fill(tright)) # type: ignore return cls.bitwise_not(su) # type: ignore + @classmethod + def bitwise_generic(cls, op, tleft: TExp, tright: TExp) -> TExp: + """Bitwise generic""" + if len(tleft[1]) > len(tright[1]): + tright = tleft[0].fill(tright) # type: ignore + + elif len(tleft[1]) < len(tright[1]): + tleft = tright[0].fill(tleft) # type: ignore + + newl = [op(a, b) for (a, b) in zip(tleft[1], tright[1])] + return (tright[0], newl) + + @classmethod + def bitwise_xor(cls, tleft: TExp, tright: TExp) -> TExp: + return cls.bitwise_generic(Xor, tleft, tright) + + @classmethod + def bitwise_and(cls, tleft: TExp, tright: TExp) -> TExp: + return cls.bitwise_generic(And, tleft, tright) + + @classmethod + def bitwise_or(cls, tleft: TExp, tright: TExp) -> TExp: + return cls.bitwise_generic(Or, tleft, tright) + class Qint2(Qint): BIT_SIZE = 2 diff --git a/qlasskit/types/qtype.py b/qlasskit/types/qtype.py index e6f3ffeb..c12ca685 100644 --- a/qlasskit/types/qtype.py +++ b/qlasskit/types/qtype.py @@ -123,3 +123,15 @@ def add(tleft: TExp, tcomp: TExp) -> TExp: @staticmethod def sub(tleft: TExp, tcomp: TExp) -> TExp: raise Exception("abstract") + + @staticmethod + def bitwsie_xor(tleft: TExp, tcomp: TExp) -> TExp: + raise Exception("abstract") + + @staticmethod + def bitwsie_and(tleft: TExp, tcomp: TExp) -> TExp: + raise Exception("abstract") + + @staticmethod + def bitwsie_or(tleft: TExp, tcomp: TExp) -> TExp: + raise Exception("abstract") diff --git a/test/test_qlassf_bool.py b/test/test_qlassf_bool.py index 16d8b80b..b9dfc36f 100644 --- a/test/test_qlassf_bool.py +++ b/test/test_qlassf_bool.py @@ -195,3 +195,20 @@ def test_assign3(self): # qf = qlassf(f, to_compile=False) # self.assertEqual(len(qf.expressions), 2) # self.assertEqual(qf.expressions[-1][1], ITE(d & e, g, h)) + + +class TestQlassfBoolBitwise(unittest.TestCase): + def test_bitwise_and(self): + f = f"def test(a: bool, b: bool) -> bool:\n\treturn a & b" + qf = qlassf(f, to_compile=COMPILATION_ENABLED) + compute_and_compare_results(self, qf) + + def test_bitwise_or(self): + f = f"def test(a: bool, b: bool) -> bool:\n\treturn a | b" + qf = qlassf(f, to_compile=COMPILATION_ENABLED) + compute_and_compare_results(self, qf) + + def test_bitwise_xor(self): + f = f"def test(a: bool, b: bool) -> bool:\n\treturn a ^ b" + qf = qlassf(f, to_compile=COMPILATION_ENABLED) + compute_and_compare_results(self, qf) diff --git a/test/test_qlassf_int.py b/test/test_qlassf_int.py index 707dc95b..1dfa9e80 100644 --- a/test/test_qlassf_int.py +++ b/test/test_qlassf_int.py @@ -350,6 +350,30 @@ def test_sub_const3(self): compute_and_compare_results(self, qf) +@parameterized_class( + ("ttype", "ttype_str", "ttype_size"), + [ + (Qint2, "Qint2", 2), + (Qint4, "Qint4", 4), + ], +) +class TestQlassfIntBitwise(unittest.TestCase): + def test_bitwise_and(self): + f = f"def test(a: {self.ttype_str}, b: {self.ttype_str}) -> {self.ttype_str}:\n\treturn a & b" + qf = qlassf(f, to_compile=COMPILATION_ENABLED) + compute_and_compare_results(self, qf) + + def test_bitwise_or(self): + f = f"def test(a: {self.ttype_str}, b: {self.ttype_str}) -> {self.ttype_str}:\n\treturn a | b" + qf = qlassf(f, to_compile=COMPILATION_ENABLED) + compute_and_compare_results(self, qf) + + def test_bitwise_xor(self): + f = f"def test(a: {self.ttype_str}, b: {self.ttype_str}) -> {self.ttype_str}:\n\treturn a ^ b" + qf = qlassf(f, to_compile=COMPILATION_ENABLED) + compute_and_compare_results(self, qf) + + class TestQlassfIntReassign(unittest.TestCase): def test_reassign_newvar(self): f = "def test(a: Qint2) -> Qint2:\n\tb = 0\n\tb = a + 1\n\treturn b"