diff --git a/TODO.md b/TODO.md index 7dad7a7d..913a23f4 100644 --- a/TODO.md +++ b/TODO.md @@ -21,6 +21,7 @@ - [x] Ast2logic: fix type inference on assign - [x] Ast2logic: handle multiple result - [x] Ast2logic: fix ret_type for multiple results +- [x] QlassF: truth table creation - [ ] Extend testing to compilation - [ ] Int arithmetic expressions - [ ] Compiler: prepare invertible abstraction @@ -31,12 +32,15 @@ - [ ] Compiler: base invertible to qcircuit translator ### Week 4: (16 Oct 23) +- [ ] Ast2logic: fixed size loops unrolling - [ ] Invertible representation simplification (to reduce number of garbage wires) - [ ] Garbage uncomputing and recycling ## Month 2: ### Week 1: (23 Oct 23) +- [ ] Parametrized qlassf + ### Week 2: (30 Oct 23) ### Week 3: (6 Nov 23) diff --git a/qlasskit/__init__.py b/qlasskit/__init__.py index 3568d931..2fb70352 100644 --- a/qlasskit/__init__.py +++ b/qlasskit/__init__.py @@ -16,4 +16,4 @@ from . import exceptions # noqa: F401 from .qlassf import QlassF, qlassf # noqa: F401 -from .typing import Qtype # noqa: F401 +from .typing import Qint2, Qint4, Qint8, Qint12, Qint16, Qtype # noqa: F401 diff --git a/qlasskit/ast2logic/t_expression.py b/qlasskit/ast2logic/t_expression.py index d63dd717..8ca1dd92 100644 --- a/qlasskit/ast2logic/t_expression.py +++ b/qlasskit/ast2logic/t_expression.py @@ -15,9 +15,10 @@ from sympy import Symbol from sympy.logic import ITE, And, Not, Or, false, true +from sympy.logic.boolalg import Boolean from .. import exceptions -from ..typing import BoolExp, Env +from ..typing import Env def type_of_exp(vlist, base, env, res=[]): @@ -39,7 +40,7 @@ def type_of_exp(vlist, base, env, res=[]): return [new_symb], env -def translate_expression(expr, env: Env) -> BoolExp: # noqa: C901 +def translate_expression(expr, env: Env) -> Boolean: # noqa: C901 """Translate an expression""" # Name reference diff --git a/qlasskit/ast2logic/t_statement.py b/qlasskit/ast2logic/t_statement.py index 8493b951..c5376b84 100644 --- a/qlasskit/ast2logic/t_statement.py +++ b/qlasskit/ast2logic/t_statement.py @@ -15,15 +15,16 @@ from typing import List, Tuple from sympy import Symbol +from sympy.logic.boolalg import Boolean from .. import exceptions -from ..typing import BoolExp, Env +from ..typing import Env from . import translate_expression, type_of_exp def translate_statement( # noqa: C901 stmt, env: Env -) -> Tuple[List[Tuple[str, BoolExp]], Env]: +) -> Tuple[List[Tuple[str, Boolean]], Env]: """Parse a statement""" # match stmt: if isinstance(stmt, ast.If): @@ -56,7 +57,7 @@ def translate_statement( # noqa: C901 target = stmt.targets[0].id if target in env: - raise exceptions.SymbolReassingedException(target) + raise exceptions.SymbolReassignedException(target) val = translate_expression(stmt.value, env) res, env = type_of_exp(val, f"{target}", env) diff --git a/qlasskit/compiler/compiler.py b/qlasskit/compiler/compiler.py index 4132bab7..e2d2ee5a 100644 --- a/qlasskit/compiler/compiler.py +++ b/qlasskit/compiler/compiler.py @@ -14,8 +14,9 @@ from sympy import Symbol, simplify, symbols from sympy.logic import ITE, And, Implies, Not, Or, boolalg +from sympy.logic.boolalg import Boolean -from ..typing import BoolExp, BoolExpList +from ..typing import BoolExpList class CompilerException(Exception): @@ -88,7 +89,7 @@ def compile(self, exprs: BoolExpList): if sym == Symbol("_ret"): # TODO: this won't work with multiple res return iret, gl - def compile_expr(self, expr: BoolExp): # noqa: C901 + def compile_expr(self, expr: Boolean): # noqa: C901 # match expr: if isinstance(expr, Symbol): if expr.name not in self.qmap: diff --git a/qlasskit/exceptions.py b/qlasskit/exceptions.py index 365a0698..4ab2e69e 100644 --- a/qlasskit/exceptions.py +++ b/qlasskit/exceptions.py @@ -45,6 +45,6 @@ def __init__(self, name, val): super().__init__(f"{name} is costant = {val}") -class SymbolReassingedException(Exception): +class SymbolReassignedException(Exception): def __init__(self, name): super().__init__(f"{name} cannot be reassinged") diff --git a/qlasskit/qlassf.py b/qlasskit/qlassf.py index 6c85f131..dc2b65c6 100644 --- a/qlasskit/qlassf.py +++ b/qlasskit/qlassf.py @@ -20,6 +20,8 @@ from .typing import * # noqa: F403, F401 from .typing import Args, BoolExpList +MAX_TRUTH_TABLE_SIZE = 20 + class QlassF: """Class representing a quantum classical circuit""" @@ -55,6 +57,38 @@ def __add__(self, qf2) -> "QlassF": """Adds two qlassf and return the combination""" raise Exception("not implemented") + 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.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) + + if (bits + self.ret_size) > MAX_TRUTH_TABLE_SIZE: + raise Exception( + f"Max truth table size reached: {bits + self.ret_size} > {MAX_TRUTH_TABLE_SIZE}" + ) + + for i in range(2**bits): + 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)) + + 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_clean = list(map(lambda y: y[1], res)) + truth.append(res_clean) + + return truth + def compile(self): # TODO: compile all expression and create a one gate only self._compiled_gate = compiler.to_quantum(self.expressions) diff --git a/qlasskit/typing.py b/qlasskit/typing.py index 461c0ba4..85e84c2c 100644 --- a/qlasskit/typing.py +++ b/qlasskit/typing.py @@ -12,15 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import List, Tuple, Union +from typing import List, Tuple from sympy import Symbol -from sympy.logic import ITE, And, Not, Or +from sympy.logic.boolalg import Boolean Args = List[str] Env = List[str] -BoolExp = Union[Symbol, And, Or, Not, ITE, bool] -BoolExpList = List[Tuple[Symbol, BoolExp]] +BoolExpList = List[Tuple[Symbol, Boolean]] LogicFun = Tuple[str, Args, int, BoolExpList] diff --git a/test/test_qlassf.py b/test/test_qlassf.py new file mode 100644 index 00000000..2fcdb693 --- /dev/null +++ b/test/test_qlassf.py @@ -0,0 +1,86 @@ +import unittest + +from qlasskit import Qint4, QlassF, qlassf + +from . import utils + + +class TestQlassfDecorator(unittest.TestCase): + def test_decorator(self): + c = qlassf(utils.test_not, to_compile=False) + self.assertTrue(isinstance(c, QlassF)) + + +class TestQlassfTruthTable(unittest.TestCase): + def test_not_truth(self): + f = "def test(a: bool) -> bool:\n\treturn not a" + qf = qlassf(f, to_compile=False) + tt = qf.truth_table() + self.assertEqual( + tt, + [[False, True], [True, False]], + ) + + def test_and_truth(self): + f = "def test(a: bool, b: bool) -> bool:\n\treturn a and b" + qf = qlassf(f, to_compile=False) + tt = qf.truth_table() + self.assertEqual( + tt, + [ + [False, False, False], + [False, True, False], + [True, False, False], + [True, True, True], + ], + ) + + def test_or_truth(self): + f = "def test(a: bool, b: bool) -> bool:\n\treturn a or b" + qf = qlassf(f, to_compile=False) + tt = qf.truth_table() + self.assertEqual( + tt, + [ + [False, False, False], + [False, True, True], + [True, False, True], + [True, True, True], + ], + ) + + def test_big_truth(self): + f = "def test(a: Qint4) -> Qint4:\n\treturn a" + qf = qlassf(f, to_compile=False) + tt = qf.truth_table() + tth = qf.truth_table_header() + + self.assertEqual( + tth, ["a.0", "a.1", "a.2", "a.3", "_ret.0", "_ret.1", "_ret.2", "_ret.3"] + ) + self.assertEqual( + tt, + [ + [False, False, False, False] * 2, + [False, False, False, True] * 2, + [False, False, True, False] * 2, + [False, False, True, True] * 2, + [False, True, False, False] * 2, + [False, True, False, True] * 2, + [False, True, True, False] * 2, + [False, True, True, True] * 2, + [True, False, False, False] * 2, + [True, False, False, True] * 2, + [True, False, True, False] * 2, + [True, False, True, True] * 2, + [True, True, False, False] * 2, + [True, True, False, True] * 2, + [True, True, True, False] * 2, + [True, True, True, True] * 2, + ], + ) + + def test_too_big_truth(self): + f = "def test(a: Qint12) -> Qint12:\n\treturn a" + qf = qlassf(f, to_compile=False) + self.assertRaises(Exception, lambda: qf.truth_table()) diff --git a/test/test_qlassf_bool.py b/test/test_qlassf_bool.py index fd4ae966..b13a7bb3 100644 --- a/test/test_qlassf_bool.py +++ b/test/test_qlassf_bool.py @@ -145,7 +145,7 @@ def test_assign3(self): def test_reassign_exception(self): f = "def test(a: bool) -> bool:\n\ta = not a\n\treturn a" self.assertRaises( - exceptions.SymbolReassingedException, + exceptions.SymbolReassignedException, lambda f: qlassf(f, to_compile=False), f, ) diff --git a/test/test_qlassf_decorator.py b/test/test_qlassf_decorator.py deleted file mode 100644 index 4fe79040..00000000 --- a/test/test_qlassf_decorator.py +++ /dev/null @@ -1,11 +0,0 @@ -import unittest - -from qlasskit import QlassF, qlassf - -from . import utils - - -class TestQlassfDecorator(unittest.TestCase): - def test_decorator(self): - c = qlassf(utils.test_not, to_compile=False) - self.assertTrue(isinstance(c, QlassF))