Skip to content

Commit

Permalink
Allow quantum gates inside sympy expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
dakk committed Nov 15, 2023
1 parent fcdb0f8 commit b1f0bb3
Show file tree
Hide file tree
Showing 11 changed files with 156 additions and 22 deletions.
2 changes: 1 addition & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@

- [x] Cirq exporter
- [x] Qint multiplier
- [x] Allow quantum gates inside sympy expressions
- [ ] Improve exporting utilities
- [ ] Use cases
- [ ] Documentation
Expand All @@ -117,7 +118,6 @@

## Future features

- [ ] Allow quantum gates inside sympy expressions, in order to define optimized parts
- [ ] Publish doc on github
- [ ] Int arithmetic expressions (/, %)
- [ ] Parametrized qlassf
Expand Down
1 change: 1 addition & 0 deletions qlasskit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@
Qint16,
Qlist,
)
from .boolquant import Q # noqa: F401
18 changes: 18 additions & 0 deletions qlasskit/ast2logic/t_expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from sympy import Symbol
from sympy.logic import ITE, And, Not, Or, Xor, false, true

from ..boolquant import QuantumBooleanGate
from ..types import Qbool, Qtype, TExp, const_to_qtype
from . import Env, exceptions

Expand Down Expand Up @@ -277,6 +278,23 @@ def unfold(v_exps, op):

# Call
elif isinstance(expr, ast.Call):
# Quantum hybrid
if (
isinstance(expr.func, ast.Attribute)
and isinstance(expr.func.value, ast.Name)
and expr.func.value.id == "Q"
):
gate = expr.func.attr
args = [translate_expression(e, env) for e in expr.args]
args_v = [b for (a, b) in args]

q_gate = QuantumBooleanGate.build(gate)

if len(args_v) == 1 and isinstance(args_v[0], list):
return args[0][0], [q_gate(a) for a in args_v[0]]
else:
return args[0][0], q_gate(*args_v)

if not hasattr(expr.func, "id"):
raise exceptions.ExpressionNotHandledException(expr)

Expand Down
36 changes: 36 additions & 0 deletions qlasskit/boolquant.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from copy import deepcopy
from sympy import Function
from sympy.logic.boolalg import Boolean


class QuantumBooleanGate(Function, Boolean):
def build(name: str):
return type(name, (QuantumBooleanGate,), { })


class Q:
"""An identity wrapper for python"""

def H(*args):
return args

def Z(*args):
return args

def Y(*args):
return args

def X(*args):
return args

def T(*args):
return args

def S(*args):
return args

def CX(*args):
return args

def MCX(*args):
return args
39 changes: 19 additions & 20 deletions qlasskit/compiler/internalcompiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,17 @@
from sympy.logic.boolalg import Boolean, BooleanFalse, BooleanTrue

from ..ast2logic.typing import Arg, Args, BoolExpList
from ..boolquant import QuantumBooleanGate
from ..qcircuit import QCircuit, QCircuitEnhanced
from . import Compiler, CompilerException, ExpQMap


class InternalCompiler(Compiler):
"""InternalCompiler translating an expression list to quantum circuit"""

def compile(self, name, args: Args, returns: Arg, exprs: BoolExpList) -> QCircuit:
def compile(
self, name, args: Args, returns: Arg, exprs: BoolExpList, uncompute=True
) -> QCircuit:
qc = QCircuitEnhanced(name=name)
self.expqmap = ExpQMap()

Expand All @@ -44,12 +47,6 @@ def compile(self, name, args: Args, returns: Arg, exprs: BoolExpList) -> QCircui
is_temp = sym.name[0:2] == "__"
symp_exp = self._symplify_exp(exp)

# X = Y: perform a "copy"
# if isinstance(exp, Symbol):
# iret = qc.get_free_ancilla()
# qc.cx(qc[exp], iret)
# else:

if isinstance(symp_exp, BooleanFalse):
if not a_false:
a_false = qc.add_qubit("FALSE")
Expand All @@ -69,12 +66,8 @@ def compile(self, name, args: Args, returns: Arg, exprs: BoolExpList) -> QCircui
self.expqmap.remove(qc.uncompute())

qc.remove_identities()
qc.uncompute_all(keep=[qc[r] for r in returns.bitvec])

# circ_qi = qc.export("circuit", "qiskit")
# print(circ_qi.draw("text"))
# print()
# print()
if uncompute:
qc.uncompute_all(keep=[qc[r] for r in returns.bitvec])

return qc

Expand Down Expand Up @@ -134,14 +127,20 @@ def compile_expr( # noqa: C901

return dest

# elif isinstance(expr, BooleanFalse):
# return qc.get_free_ancilla()
# Hybrid Quantum-Boolean circuit
elif isinstance(expr, QuantumBooleanGate):
erets = list(map(lambda e: self.compile_expr(qc, e), expr.args)) # type: ignore
gate = expr.__class__.__name__.lower()

if hasattr(qc, gate):
if gate[0] == "m":
getattr(qc, gate)(erets[0:-1], erets[-1])
else:
getattr(qc, gate)(*erets)

return erets[-1]

# elif isinstance(expr, BooleanTrue):
# if dest is None:
# dest = qc.get_free_ancilla()
# qc.x(dest)
# return dest
raise Exception(f"Unknown gate: {gate}")

else:
raise CompilerException(expr)
5 changes: 4 additions & 1 deletion qlasskit/compiler/tweedledumcompiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,11 @@ class TweedledumCompiler(Compiler):
"""Compile using tweedledum synthesis library"""

def compile( # noqa: C901
self, name, args: Args, returns: Arg, exprs: BoolExpList
self, name, args: Args, returns: Arg, exprs: BoolExpList, uncompute: bool = True
) -> QCircuit:
if not uncompute:
raise Exception("Disabled uncompute not supported on tweedledum")

exprs = [(symb, self._symplify_exp(exp)) for symb, exp in exprs]
_logic_network = sympy_to_logic_network(name, args, returns, exprs)

Expand Down
10 changes: 10 additions & 0 deletions qlasskit/qcircuit/gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ def __init__(self):
super().__init__("X")


class Y(QGate):
def __init__(self):
super().__init__("Y")


class S(QGate):
def __init__(self):
super().__init__("S")


class T(QGate):
def __init__(self):
super().__init__("T")
Expand Down
15 changes: 15 additions & 0 deletions qlasskit/qcircuit/qcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,21 @@ def x(self, w: int):
w = self[w]
self.append(gates.X(), [w])

def y(self, w: int):
"""Y gate"""
w = self[w]
self.append(gates.Y(), [w])

def t(self, w: int):
"""T gate"""
w = self[w]
self.append(gates.T(), [w])

def s(self, w: int):
"""S gate"""
w = self[w]
self.append(gates.S(), [w])

def cx(self, w1, w2):
"""CX gate"""
w1, w2 = self[w1], self[w2]
Expand Down
1 change: 1 addition & 0 deletions qlasskit/qlassfun.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from .ast2ast import ast2ast
from .ast2logic import Arg, Args, BoolExpList, LogicFun, flatten, translate_ast
from .boolopt import BoolOptimizerProfile, bestWorkingOptimizer
from .boolquant import Q # noqa: F403, F401
from .compiler import SupportedCompiler, to_quantum
from .qcircuit import QCircuitWrapper
from .types import * # noqa: F403, F401
Expand Down
9 changes: 9 additions & 0 deletions qlasskit/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ def _half_adder(a, b): # Carry x Sum
def _full_adder(c, a, b): # Carry x Sum
return (a & b) ^ (a ^ b) & c, Xor(Xor(a, b), c)

# from ..boolquant import H, CX, MCX
# from sympy import Symbol
# o = Symbol('new_qubit')
# o = MCX(a, b, o)
# b = CX(a, b)
# o = MCX(b, c, o)
# c = CX(b,c)
# return c, o


from .qtype import Qtype, TExp, TType # noqa: F401, E402
from .qbool import Qbool # noqa: F401, E402
Expand Down
42 changes: 42 additions & 0 deletions test/test_qlassf_hybrid_quantum.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# 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 qlasskit import QlassF, exceptions, qlassf

from .utils import COMPILATION_ENABLED, ENABLED_COMPILERS, qiskit_measure_and_count


class TestQlassfHybridQuantum(unittest.TestCase):
def test_h(self):
f = "def test(a: bool) -> bool:\n\treturn Q.H(a)"
qf = qlassf(f, to_compile=COMPILATION_ENABLED, uncompute=False)
count = qiskit_measure_and_count(qf.circuit().export(), 128)
[self.assertEqual(x in count, True) for x in ["0", "1"]]

def test_h_multi(self):
f = "def test(a: Qint2) -> Qint2:\n\treturn Q.H(a)"
qf = qlassf(f, to_compile=COMPILATION_ENABLED, uncompute=False)
count = qiskit_measure_and_count(qf.circuit().export(), 128)
[self.assertEqual(x in count, True) for x in ["00", "11", "01", "11"]]

def test_bell(self):
f = "def test(a: bool, b: bool) -> bool:\n\treturn Q.CX(Q.H(a), b)"
qf = qlassf(f, to_compile=COMPILATION_ENABLED, uncompute=False)
count = qiskit_measure_and_count(qf.circuit().export(), 1024)
[self.assertEqual(x in count, True) for x in ["00", "11"]]
self.assertEqual(len(count.keys()), 2)

0 comments on commit b1f0bb3

Please sign in to comment.