Skip to content

Commit

Permalink
qchar type (#16)
Browse files Browse the repository at this point in the history
qchar type
  • Loading branch information
dakk committed Mar 22, 2024
1 parent 481695a commit 7d66ae9
Show file tree
Hide file tree
Showing 9 changed files with 226 additions and 3 deletions.
2 changes: 1 addition & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
- [x] QuTip Support
- [x] Parameter bind (https://github.com/dakk/qlasskit/issues/10)
- [x] QUBO, Ising and BQM exporter
- [x] Datatype: Char


## Future features
Expand All @@ -103,7 +104,6 @@
- [ ] Int arithmetic: div
- [ ] Lambda
- [ ] Builtin function: map
- [ ] Datatype: Char
- [ ] Datatype: Dict
- [ ] Datatype: Float
- [ ] Datatype: Enum
Expand Down
8 changes: 7 additions & 1 deletion docs/source/supported.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ Unsigned integers; this type has subtypes for different Qint sizes (Qint2, Qint4
Single bit of the Qint are accessible by the subscript operator `[]`.


Qchar
^^^^^

A character.

Tuple
^^^^^

Expand Down Expand Up @@ -184,7 +189,8 @@ Bultin functions:
- `sum(Tuple)`, `sum(Qlist)`: returns the sum of the elemnts of a tuple / list
- `all(Tuple)`, `all(Qlist)`: returns True if all of the elemnts are True
- `any(Tuple)`, `any(Qlist)`: returns True if any of the elemnts are True

- `ord(Qchar)`: returns the integer value of the given Qchar
- `chr(Qint)`: returns the char given its ascii code


Statements
Expand Down
2 changes: 2 additions & 0 deletions qlasskit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@
const_to_qtype,
interpret_as_qtype,
Qtype,
Qchar,
Qint,
Qint2,
Qint3,
Qint4,
Qint5,
Qint6,
Qint7,
Qint8,
Qint12,
Qint16,
Expand Down
18 changes: 18 additions & 0 deletions qlasskit/ast2ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,18 @@ def __call_anyall(self, node):
op = ast.Or() if node.func.id == "any" else ast.And()
return ast.BoolOp(op=op, values=args)

def __call_chr(self, node):
if len(node.args) != 1:
raise Exception(f"chr() takes exactly 1 argument ({len(node.args)} given)")
args = self.__unroll_arg(node.args[0])
return args[0]

def __call_ord(self, node):
if len(node.args) != 1:
raise Exception(f"ord() takes exactly 1 argument ({len(node.args)} given)")
args = self.__unroll_arg(node.args[0])
return args[0]

def visit_Call(self, node):
node.args = [self.visit(ar) for ar in node.args]
if not hasattr(node.func, "id"):
Expand All @@ -477,6 +489,12 @@ def visit_Call(self, node):
elif node.func.id == "sum":
return self.__call_sum(node)

elif node.func.id == "ord":
return self.__call_ord(node)

elif node.func.id == "chr":
return self.__call_chr(node)

elif node.func.id in ["any", "all"]:
return self.__call_anyall(node)

Expand Down
7 changes: 7 additions & 0 deletions qlasskit/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,15 @@ def _full_adder(c, a, b): # Carry x Sum
from .qbool import Qbool # noqa: F401, E402
from .qlist import Qlist # noqa: F401, E402
from .qmatrix import Qmatrix # noqa: F401, E402
from .qchar import Qchar # noqa: F401, E402
from .qint import ( # noqa: F401, E402
Qint,
Qint2,
Qint3,
Qint4,
Qint5,
Qint6,
Qint7,
Qint8,
Qint12,
Qint16,
Expand All @@ -66,9 +68,11 @@ def _full_adder(c, a, b): # Carry x Sum
Qint4,
Qint5,
Qint6,
Qint7,
Qint8,
Qint12,
Qint16,
Qchar,
Qlist,
Qmatrix,
]
Expand All @@ -82,6 +86,9 @@ def const_to_qtype(value: Any) -> TExp:

raise Exception(f"Constant value is too big: {value}")

elif isinstance(value, str):
return Qchar.const(value)

raise Exception(f"Unable to infer type of constant: {value}")


Expand Down
83 changes: 83 additions & 0 deletions qlasskit/types/qchar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Copyright 2024 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 Any, List

from sympy.logic import And, Or, false, true

from . import _eq, _neq
from .qint import Qint
from .qtype import Qtype, TExp


class Qchar(str, Qtype):
BIT_SIZE = 8

def __init__(self, value: str):
super().__init__()
assert len(value) == 1
self.value = value[0]

def to_bin(self) -> str:
s = bin(ord(self.value))[2:][0 : self.BIT_SIZE]
return ("0" * (self.BIT_SIZE - len(s)) + s)[::-1]

def to_amplitudes(self) -> List[float]:
ampl = [0.0] * 2**self.BIT_SIZE
ampl[ord(self.value)] = 1
return ampl

@classmethod
def from_bool(cls, v: List[bool]):
bin_str = "".join(map(lambda x: "1" if x else "0", v))
return cls(chr(int(bin_str[::-1], 2)))

@classmethod
def comparable(cls, other_type=None) -> bool:
return other_type == cls or issubclass(other_type, Qint)

@classmethod
def fill(cls, v: TExp) -> TExp:
if len(v[1]) < cls.BIT_SIZE: # type: ignore
v = (
cls,
v[1] + (cls.BIT_SIZE - len(v[1])) * [False], # type: ignore
)
return v

@classmethod
def const(cls, value: Any) -> TExp:
assert len(value) == 1
cval = list(
map(lambda c: True if c == "1" else False, bin(ord(value))[2:][::-1])
) # [::-1]
return cls.fill((cls, cval))

# Comparators

@staticmethod
def eq(tleft: TExp, tcomp: TExp) -> TExp:
ex = true
for x in zip(tleft[1], tcomp[1]):
ex = And(ex, _eq(x[0], x[1]))

return (bool, ex)

@staticmethod
def neq(tleft: TExp, tcomp: TExp) -> TExp:
ex = false
for x in zip(tleft[1], tcomp[1]):
ex = Or(ex, _neq(x[0], x[1]))

return (bool, ex)
4 changes: 4 additions & 0 deletions qlasskit/types/qint.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,10 @@ class Qint6(Qint):
BIT_SIZE = 6


class Qint7(Qint):
BIT_SIZE = 7


class Qint8(Qint):
BIT_SIZE = 8

Expand Down
103 changes: 103 additions & 0 deletions test/qlassf/test_char.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# 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.

import unittest

from parameterized import parameterized_class
from sympy import Symbol
from sympy.logic import And, Not

from qlasskit import Qchar, qlassf

from ..utils import COMPILATION_ENABLED, ENABLED_COMPILERS, compute_and_compare_results


@parameterized_class(("compiler"), ENABLED_COMPILERS)
class TestQchar(unittest.TestCase):
def test_qchar_to_bin_and_from_bool(self):
c = Qchar("a").to_bin()
self.assertEqual(c, "01100001"[::-1])
self.assertEqual(c, Qchar("a").export("binary"))
self.assertEqual(
Qchar.from_bool(
[False, True, True, False, False, False, False, True][::-1]
),
"a",
)

def test_char_arg_eq(self):
f = "def test(a: Qchar) -> bool:\n\treturn a == 'a'"
qf = qlassf(f, to_compile=COMPILATION_ENABLED, compiler=self.compiler)
self.assertEqual(len(qf.expressions), 1)
self.assertEqual(qf.expressions[0][0], Symbol("_ret"))
a = [Symbol(f"a.{i}") for i in range(8)]
self.assertEqual(
qf.expressions[0][1],
And(
a[0], a[5], a[6], Not(a[1]), Not(a[2]), Not(a[3]), Not(a[4]), Not(a[7])
),
)
compute_and_compare_results(self, qf)

def test_char_arg_neq(self):
f = "def test(a: Qchar) -> bool:\n\treturn a != 'a'"
qf = qlassf(f, to_compile=COMPILATION_ENABLED, compiler=self.compiler)
self.assertEqual(len(qf.expressions), 1)
self.assertEqual(qf.expressions[0][0], Symbol("_ret"))
a = [Symbol(f"a.{i}") for i in range(8)]
self.assertEqual(
qf.expressions[0][1],
Not(
And(
a[0],
a[5],
a[6],
Not(a[1]),
Not(a[2]),
Not(a[3]),
Not(a[4]),
Not(a[7]),
)
),
)
compute_and_compare_results(self, qf)

def test_char_return(self):
f = "def test(a: Qchar) -> Qchar:\n\treturn 'z' if a == 'a' else 'a'"
qf = qlassf(f, to_compile=COMPILATION_ENABLED, compiler=self.compiler)
compute_and_compare_results(self, qf)

def test_char_return2(self):
f = "def test(a: Qchar) -> Qchar:\n\treturn a"
qf = qlassf(f, to_compile=COMPILATION_ENABLED, compiler=self.compiler)
compute_and_compare_results(self, qf)

def test_char_return_tuple(self):
f = (
"def test(a: Qchar) -> Tuple[Qchar, bool]:\n"
"\tb = a == 'z'\n\treturn (a, b)"
)
qf = qlassf(f, to_compile=COMPILATION_ENABLED, compiler=self.compiler)
self.assertEqual(len(qf.expressions), 9)
compute_and_compare_results(self, qf)

def test_char_ord(self):
f = "def test(a: Qchar) -> bool:\n\treturn ord(a) == 97"
qf = qlassf(f, to_compile=COMPILATION_ENABLED, compiler=self.compiler)
compute_and_compare_results(self, qf)

def test_char_chr(self):
f = "def test(a: Qchar) -> bool:\n\treturn a == chr(97)"
qf = qlassf(f, to_compile=COMPILATION_ENABLED, compiler=self.compiler)
compute_and_compare_results(self, qf)
2 changes: 1 addition & 1 deletion test/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ def res_to_str(res):
return "1" if res else "0"
elif type(res) is tuple or type(res) is list:
return "".join([res_to_str(x) for x in res])
elif type(res) is int:
elif type(res) is int or type(res) is str:
qc = const_to_qtype(res)
try:
qi = qf.returns.ttype.from_bool(qc[1])
Expand Down

0 comments on commit 7d66ae9

Please sign in to comment.