Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extensible type system #3

Merged
merged 9 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Python and translate them into unitary operators (gates) for use in quantum circ
howitworks
supported
qlassf
types
qcircuit


Expand Down
3 changes: 2 additions & 1 deletion docs/source/qcircuit.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ QCircuit

QCircuit represents a quantum circuit inside the Qlasskit library.

.. automodule:: qlasskit.qcircuit
.. autoclass:: qlasskit.qcircuit.QCircuit
:members:
:undoc-members:

1 change: 1 addition & 0 deletions docs/source/qlassf.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ QlassF is the key component of the qlasskit library.

.. automodule:: qlasskit.qlassf
:members:
:undoc-members:
8 changes: 8 additions & 0 deletions docs/source/types.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Types
====================================

All supported types by the qlasskit library are inherit from the base `Qtype`.

.. autoclass:: qlasskit.Qtype
:members:
:undoc-members:
1 change: 0 additions & 1 deletion qlasskit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
from .ast2logic import exceptions # noqa: F401
from .types import ( # noqa: F401, F403
Qtype,
Qbool,
Qint,
Qint2,
Qint4,
Expand Down
2 changes: 1 addition & 1 deletion qlasskit/ast2logic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
26 changes: 22 additions & 4 deletions qlasskit/ast2logic/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand Down
5 changes: 5 additions & 0 deletions qlasskit/ast2logic/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}'")
Expand Down
23 changes: 11 additions & 12 deletions qlasskit/ast2logic/t_arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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)
7 changes: 4 additions & 3 deletions qlasskit/ast2logic/t_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,21 @@
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)
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 = []
Expand Down
89 changes: 32 additions & 57 deletions qlasskit/ast2logic/t_expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@
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, Qtype, TExp, const_to_qtype
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:
Expand Down Expand Up @@ -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)

Expand All @@ -170,55 +167,33 @@ def unfold(v_exps, op):
tleft = translate_expression(expr.left, env)
tcomp = translate_expression(expr.comparators[0], env)

# Eq
if isinstance(expr.ops[0], ast.Eq):
if tleft[0] == bool and tcomp[0] == bool:
return (bool, Qbool.eq(tleft[1], tcomp[1]))
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]))
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):
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):
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):
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):
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):
Expand Down
6 changes: 3 additions & 3 deletions qlasskit/ast2logic/t_statement.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
15 changes: 11 additions & 4 deletions qlasskit/qlassf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -151,15 +152,17 @@ 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)

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)
Expand All @@ -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)
Loading
Loading