Skip to content
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
2 changes: 2 additions & 0 deletions qlasskit/ast2logic/t_expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,8 @@ def unfold(v_exps, op):
return tleft[0].mul(tleft, tright)
elif isinstance(expr.op, ast.Mod):
return tleft[0].mod(tleft, tright) # type: ignore
elif isinstance(expr.op, ast.FloorDiv) and hasattr(tleft[0], "floor_div"):
return tleft[0].floor_div(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"):
Expand Down
99 changes: 97 additions & 2 deletions qlasskit/types/qint.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import List, cast
from typing import List, Optional, cast

from sympy import Symbol
from sympy.logic import And, Not, Or, Xor, false, true
from sympy.logic import ITE, And, Not, Or, Xor, false, true

from . import TypeErrorException, _eq, _full_adder, _neq
from .qtype import Qtype, TExp, TType, bin_to_bool_list, bool_list_to_bin
Expand Down Expand Up @@ -295,6 +295,101 @@ def mod(cls, tleft: TExp, tright: TExp) -> TExp: # noqa: C901
tval = tright[0].sub(tright, tright[0].const(1))
return tleft[0].bitwise_and(tleft, tval)

@staticmethod
def _floor_div_const_optimize(
cls, tleft: TExp, tright_value: int
) -> Optional[TExp]:
"""Optimize division by constant divisors"""
if tright_value == 1:
return tleft
if tright_value > 0 and (tright_value & (tright_value - 1)) == 0:
power = 0
temp = tright_value
while temp > 1:
temp >>= 1
power += 1
return cls.shift_right(tleft, power)
if tright_value > 1 and tright_value % 2 == 0:
return cls.floor_div(
cls.shift_right(tleft, 1), cls.const(tright_value // 2)
)
return None

@staticmethod
def _floor_div_handle_const_divisor(
cls, tleft: TExp, tright: TExp
) -> Optional[TExp]:
"""Handle constant divisor optimizations"""
if not cls.is_const(tright):
return None
tright_qtype = cast(Qtype, tright[0]).from_bool(tright[1])
tright_value = cast(QintImp, tright_qtype)
if tright_value == 0:
raise ZeroDivisionError("division by zero")
return cls._floor_div_const_optimize(cls, tleft, tright_value)

@staticmethod
def _floor_div_normalize_sizes(cls, tleft: TExp, tright: TExp) -> tuple[TExp, TExp]:
"""Normalize operand sizes"""
if len(tleft[1]) > len(tright[1]):
tright = cast(Qtype, tleft[0]).fill(tright)
elif len(tleft[1]) < len(tright[1]):
tleft = cast(Qtype, tright[0]).fill(tleft)
return tleft, tright

@staticmethod
def _floor_div_long_division(cls, tleft: TExp, tright: TExp, n: int) -> TExp:
"""Perform long division algorithm"""
tleft_type = cast(Qtype, tleft[0])
Q = tleft_type.fill(cls.const(0))
R = tleft_type.fill(cls.const(0))

for i in range(n - 1, -1, -1):
R = cls.shift_left(R, 1)
R_bits = list(R[1])
R_bits[0] = tleft[1][i]
R = (R[0], R_bits)

r_gte_d = cls.gte(R, tright)
new_R = cls.sub(R, tright)
R = (
R[0],
[ITE(r_gte_d[1], new_R[1][j], R[1][j]) for j in range(len(R[1]))],
)

Q_bits = list(Q[1])
Q_bits[i] = Or(Q_bits[i], r_gte_d[1])
Q = (Q[0], Q_bits)

return Q

@classmethod
def floor_div(cls, tleft: TExp, tright: TExp) -> TExp:
"""Floor divide two Qint"""
if not issubclass(tleft[0], Qtype) or not issubclass(tright[0], Qtype):
raise TypeErrorException(
tleft[0] if not issubclass(tleft[0], Qtype) else tright[0], Qtype
)

opt_result = cls._floor_div_handle_const_divisor(cls, tleft, tright)
if opt_result is not None:
return opt_result

if cls.is_const(tleft):
tleft_value = cast(Qtype, tleft[0]).from_bool(tleft[1])
if tleft_value == 0:
return cls.const(0)

tleft, tright = cls._floor_div_normalize_sizes(cls, tleft, tright)

if cls.is_const(tleft) and cls.is_const(tright):
tleft_val = cast(QintImp, cast(Qtype, tleft[0]).from_bool(tleft[1]))
tright_val = cast(QintImp, cast(Qtype, tright[0]).from_bool(tright[1]))
if tleft_val < tright_val:
return cls.const(0)

return cls._floor_div_long_division(cls, tleft, tright, len(tleft[1]))

@classmethod
def bitwise_generic(cls, op, tleft: TExp, tright: TExp) -> TExp:
"""Bitwise generic"""
Expand Down
4 changes: 4 additions & 0 deletions qlasskit/types/qtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,10 @@ def mul(tleft: TExp, tright: TExp) -> TExp:
def mod(tleft: TExp, tright: TExp) -> TExp:
raise Exception("abstract mod")

@staticmethod
def floor_div(tleft: TExp, tright: TExp) -> TExp:
raise Exception("abstract floor_div")

@staticmethod
def bitwise_xor(tleft: TExp, tright: TExp) -> TExp:
raise Exception("abstract bitwise_xor")
Expand Down
40 changes: 39 additions & 1 deletion test/qlassf/test_int.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def test_int_identity(self):
compute_and_compare_results(self, qf)

def test_int_const_compare_eq(self):
f = f"def test(a: {self.ttype_str}) -> bool:\n\treturn a == {int(self.ttype_size/2-1)}"
f = f"def test(a: {self.ttype_str}) -> bool:\n\treturn a == {int(self.ttype_size / 2 - 1)}"
qf = qlassf(f, to_compile=COMPILATION_ENABLED, compiler=self.compiler)
self.assertEqual(len(qf.expressions), 1)
self.assertEqual(qf.expressions[0][0], _ret)
Expand Down Expand Up @@ -386,6 +386,44 @@ def test_mod_const_in_var(self):
compute_and_compare_results(self, qf)


@parameterized_class(
("val", "compiler"),
inject_parameterized_compilers(
[
(2,),
(3,),
(4,),
(5,),
(6,),
(8,),
]
),
)
class TestQlassfIntFloorDiv(unittest.TestCase):
def test_floor_div_const(self):
f = f"def test(a: Qint[4]) -> Qint[4]: return a // {self.val}"
qf = qlassf(f, to_compile=COMPILATION_ENABLED, compiler=self.compiler)
compute_and_compare_results(self, qf)

def test_floor_div_const_in_var(self):
f = f"def test(a: Qint[4]) -> Qint[4]:\n\tb = {self.val}\n\treturn a // b"
qf = qlassf(f, to_compile=COMPILATION_ENABLED, compiler=self.compiler)
compute_and_compare_results(self, qf)


@parameterized_class(("compiler"), ENABLED_COMPILERS)
class TestQlassfIntFloorDivGeneral(unittest.TestCase):
def test_floor_div(self):
f = "def test(a: Qint[4], b: Qint[4]) -> Qint[4]: return a // b"
qf = qlassf(f, to_compile=COMPILATION_ENABLED, compiler=self.compiler)
compute_and_compare_results(self, qf)

def test_floor_div_qint2(self):
f = "def test(a: Qint[2], b: Qint[2]) -> Qint[2]: return a // b"
qf = qlassf(f, to_compile=COMPILATION_ENABLED, compiler=self.compiler)
compute_and_compare_results(self, qf)


@parameterized_class(("compiler"), ENABLED_COMPILERS)
class TestQlassfIntSub(unittest.TestCase):
def test_sub_const(self):
Expand Down
71 changes: 37 additions & 34 deletions test/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,63 +209,66 @@ def res_to_str(res):
return res_original_str


def compute_and_compare_results(cls, qf, test_original_f=True, test_qcircuit=True):
"""Create and simulate the qcircuit, and compare the result with the
truthtable and with the original_f"""
def _prepare_truth_table(qf, test_qcircuit):
"""Prepare truth table and quantum circuit truth subset"""
MAX_Q_SIM = 64
MAX_C_SIM = 2**9
qc_truth = None
test_qcircuit = test_qcircuit and COMPILATION_ENABLED

truth_table = qf.truth_table(MAX_C_SIM)

if len(truth_table) > MAX_C_SIM:
truth_table = [random.choice(truth_table) for x in range(MAX_C_SIM)]

qc_truth = None
if len(truth_table) > MAX_Q_SIM and test_qcircuit:
qc_truth = [random.choice(truth_table) for x in range(MAX_Q_SIM)]
elif test_qcircuit:
qc_truth = truth_table

# circ_qi = qf.circuit().export("circuit", "qiskit")
return truth_table, qc_truth


def _test_qcircuit_result(cls, qf, truth_line, truth_str):
"""Test quantum circuit result for a truth line"""
max_qubits = (
qf.input_size
+ len(qf.expressions)
+ sum([gateinputcount(e[1]) for e in qf.expressions])
)
cls.assertLessEqual(qf.num_qubits, max_qubits)

if os.getenv("GITHUB_ACTIONS"):
res_qc = compute_result_of_qcircuit(cls, qf, truth_line)
cls.assertEqual(truth_str, res_qc)
else:
try:
res_qc2 = compute_result_of_qcircuit_using_cnotsim(cls, qf, truth_line)
cls.assertEqual(truth_str, res_qc2)
except GateNotSimulableException:
res_qc = compute_result_of_qcircuit(cls, qf, truth_line)
cls.assertEqual(truth_str, res_qc)


def compute_and_compare_results(cls, qf, test_original_f=True, test_qcircuit=True):
"""Create and simulate the qcircuit, and compare the result with the
truthtable and with the original_f"""
truth_table, qc_truth = _prepare_truth_table(qf, test_qcircuit)

update_statistics(
qf.circuit().num_qubits, qf.circuit().num_gates, qf.circuit().gate_stats
)

# print(qf.expressions)
# print(circ_qi.draw("text"))
# print(circ_qi.qasm())

for truth_line in truth_table:
# Extract str of truthtable and result
truth_str = "".join(
map(lambda x: "1" if x else "0", truth_line[-qf.output_size :])
)

# Calculate and compare the originalf result
if test_original_f:
res_original = compute_result_of_originalf(cls, qf, truth_line)
cls.assertEqual(truth_str, res_original)
try:
res_original = compute_result_of_originalf(cls, qf, truth_line)
cls.assertEqual(truth_str, res_original)
except ZeroDivisionError:
continue

# Calculate and compare the gate result
if qc_truth and truth_line in qc_truth and test_qcircuit:
max_qubits = (
qf.input_size
+ len(qf.expressions)
+ sum([gateinputcount(e[1]) for e in qf.expressions])
)
cls.assertLessEqual(qf.num_qubits, max_qubits)

if os.getenv("GITHUB_ACTIONS"):
res_qc = compute_result_of_qcircuit(cls, qf, truth_line)
cls.assertEqual(truth_str, res_qc)
else:
try:
res_qc2 = compute_result_of_qcircuit_using_cnotsim(
cls, qf, truth_line
)
cls.assertEqual(truth_str, res_qc2)
except GateNotSimulableException:
res_qc = compute_result_of_qcircuit(cls, qf, truth_line)
cls.assertEqual(truth_str, res_qc)
_test_qcircuit_result(cls, qf, truth_line, truth_str)
Loading