Skip to content

Commit

Permalink
separate boolean optmization in boolopt module
Browse files Browse the repository at this point in the history
  • Loading branch information
dakk committed Nov 8, 2023
1 parent 19f932d commit c85b9a4
Show file tree
Hide file tree
Showing 10 changed files with 240 additions and 76 deletions.
1 change: 1 addition & 0 deletions qlasskit/ast2ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,4 +403,5 @@ def ast2ast(a_tree):
a_tree = IndexReplacer().visit(a_tree)

a_tree = ASTRewriter().visit(a_tree)
# print(ast.dump(a_tree))
return a_tree
16 changes: 16 additions & 0 deletions qlasskit/boolopt/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# 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.
# isort:skip_file

from .sympytransformer import SympyTransformer # noqa: F401
119 changes: 115 additions & 4 deletions qlasskit/bool_optimizer.py → qlasskit/boolopt/bool_optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,16 @@
from typing import Dict

from sympy import Symbol
from sympy.logic.boolalg import Boolean
from sympy.logic.boolalg import Boolean, simplify_logic

from .ast2logic import BoolExpList
from ..ast2logic import BoolExpList
from . import SympyTransformer
from .exp_transformers import (
remove_Implies,
remove_ITE,
transform_or2and,
transform_or2xor,
)


def remove_const_exps(exps: BoolExpList) -> BoolExpList:
Expand Down Expand Up @@ -110,19 +117,123 @@ def should_add(s, e, n_exps2):
def merge_unnecessary_assigns(exps: BoolExpList) -> BoolExpList:
"""Translate exp like: __a.0 = !a, a = __a.0 ===> a = !a"""
n_exps: BoolExpList = []
rep_d = {}

for s, e in exps:
if len(n_exps) >= 1 and n_exps[-1][0] == e:
if len(n_exps) >= 1 and n_exps[-1][0] == e: # and n_exps[-1][0].name[2:] == s:
old = n_exps.pop()
n_exps.append((s, old[1]))
rep_d[old[0]] = old[1]
n_exps.append((s, e.subs(rep_d)))
else:
n_exps.append((s, e.subs(rep_d)))

return n_exps


def remove_unnecessary_aliases(exps: BoolExpList) -> BoolExpList:
"""Translate exps like: (__d.0, a), (d.0, __d.0 & a) to => (d.0, a & a)"""
n_exps: BoolExpList = []
rep_d = {}

for s, e in exps:
if len(n_exps) >= 1 and n_exps[-1][0] in e.free_symbols:
old = n_exps.pop()
rep_d[old[0]] = old[1]
n_exps.append((s, e.subs(rep_d)))
else:
n_exps.append((s, e.subs(rep_d)))

return n_exps


def remove_aliases(exps: BoolExpList) -> BoolExpList:
aliases = {}
n_exps = []
for s, e in exps:
if isinstance(e, Symbol):
aliases[s] = e
elif s in aliases:
del aliases[s]
n_exps.append((s, e.subs(aliases)))
else:
n_exps.append((s, e.subs(aliases)))

return n_exps


def s2_mega(exps: BoolExpList) -> BoolExpList:
n_exps: BoolExpList = []
exp_d = {}

for s, e in exps:
exp_d[s] = e
n_exps.append((s, e.subs(exp_d)))

s_count = {}
exps = n_exps

for s, e in exps:
if s.name not in s_count:
s_count[s.name] = 0

for x in e.free_symbols:
if x.name in s_count:
s_count[x.name] += 1

n_exps = []
for s, e in exps:
if s_count[s.name] > 0 or s.name[0:4] == "_ret":
n_exps.append((s, e))

return n_exps


def exps_simplify(exps: BoolExpList) -> BoolExpList:
return list(map(lambda e: (e[0], simplify_logic(e[1])), exps))


# [(h, a_list.0.0 & a_list.0.1), (h, a_list.1.0 & a_list.1.1 & h),
# (h, a_list.2.0 & a_list.2.1 & h), (_ret, a_list.3.0 & a_list.3.1 & h)]
# TO
# (_ret, a_list_3_0 & a_list_3_1 & a_list_2_0 & a_list_2_1 & a_list_1_0 & a_list_1_1 &
# a_list_0_0 & a_list_0_1)


class BoolOptimizerProfile:
def __init__(self, steps):
self.steps = steps

def apply(self, exps):
for opt in self.steps:
if isinstance(opt, SympyTransformer):
exps = list(map(lambda e: (e[0], opt.visit(e[1])), exps))
else:
exps = opt(exps)
return exps


bestWorkingOptimizer = BoolOptimizerProfile(
[
remove_const_exps,
remove_unnecessary_assigns,
merge_unnecessary_assigns,
remove_ITE(),
remove_Implies(),
transform_or2xor(),
transform_or2and(),
]
)


experimentalOptimizer = BoolOptimizerProfile(
[
remove_const_exps,
remove_unnecessary_assigns,
merge_unnecessary_assigns,
# remove_unnecessary_aliases,
# s2_mega,
# subsitute_exps,
# exps_simplify,
# remove_aliases,
]
)
63 changes: 63 additions & 0 deletions qlasskit/boolopt/exp_transformers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# 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.

from sympy.logic import And, Not, Or, Xor

from . import SympyTransformer


class remove_ITE(SympyTransformer):
def visit_ITE(self, expr):
c = self.visit(expr.args[0])
return self.visit(
Or(
And(c, self.visit(expr.args[1])),
And(Not(c), self.visit(expr.args[2])),
)
)


class remove_Implies(SympyTransformer):
def visit_Implies(self, expr):
return self.visit(Or(Not(self.visit(expr.args[0])), self.visit(expr.args[1])))


class transform_or2xor(SympyTransformer):
# Or(And(a,b), And(!a,!b)) = !Xor(a,b)
def visit_Or(self, expr):
if (
len(expr.args) == 2
and isinstance(expr.args[0], And)
and isinstance(expr.args[1], And)
and (
(
expr.args[1].args[0] == Not(expr.args[0].args[0])
and expr.args[1].args[1] == Not(expr.args[0].args[1])
)
or (
Not(expr.args[1].args[0]) == expr.args[0].args[0]
and Not(expr.args[1].args[1]) == expr.args[0].args[1]
)
)
):
a = self.visit(expr.args[0].args[0])
b = self.visit(expr.args[0].args[1])
return Not(Xor(a, b))
else:
return super().visit_Or(expr)


class transform_or2and(SympyTransformer):
def visit_Or(self, expr):
return Not(And(*[Not(self.visit(e)) for e in expr.args]))
37 changes: 37 additions & 0 deletions qlasskit/boolopt/sympytransformer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from sympy.logic import ITE, And, Implies, Not, Or, Xor


class SympyTransformer:
def visit(self, e):
if isinstance(e, And):
return self.visit_And(e)
elif isinstance(e, Or):
return self.visit_Or(e)
elif isinstance(e, Not):
return self.visit_Not(e)
elif isinstance(e, Implies):
return self.visit_Implies(e)
elif isinstance(e, ITE):
return self.visit_ITE(e)
elif isinstance(e, Xor):
return self.visit_Xor(e)
else:
return e

def visit_And(self, e):
return And(*[self.visit(a) for a in e.args])

def visit_Or(self, e):
return Or(*[self.visit(a) for a in e.args])

def visit_Not(self, e):
return Not(self.visit(e.args[0]))

def visit_ITE(self, e):
return ITE(*[self.visit(a) for a in e.args])

def visit_Implies(self, e):
return Implies(*[self.visit(a) for a in e.args])

def visit_Xor(self, e):
return Xor(*[self.visit(a) for a in e.args])
2 changes: 1 addition & 1 deletion qlasskit/compiler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from typing import Literal, get_args

from .expqmap import ExpQMap # noqa: F401
from .compiler import Compiler, CompilerException, optimizer # noqa: F401
from .compiler import Compiler, CompilerException # noqa: F401

try:
import tweedledum # noqa: F401
Expand Down
60 changes: 0 additions & 60 deletions qlasskit/compiler/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.


from sympy import Symbol
from sympy.logic import ITE, And, Implies, Not, Or, Xor
from sympy.logic.boolalg import Boolean, BooleanFalse, BooleanTrue # , to_anf

from .. import QCircuit
from ..ast2logic.typing import Arg, Args, BoolExpList

Expand All @@ -25,66 +20,11 @@ class CompilerException(Exception):
pass


def optimizer(expr: Boolean) -> Boolean:
if isinstance(expr, Symbol):
return expr

elif isinstance(expr, ITE):
c = optimizer(expr.args[0])
return optimizer(
Or(And(c, optimizer(expr.args[1])), And(Not(c), optimizer(expr.args[2])))
)

elif isinstance(expr, Implies):
return optimizer(Or(Not(optimizer(expr.args[0])), optimizer(expr.args[1])))

elif isinstance(expr, Not):
return Not(optimizer(expr.args[0]))

elif isinstance(expr, And):
return And(*[optimizer(e) for e in expr.args])

# Or(And(a,b), And(!a,!b)) = !Xor(a,b)
elif (
isinstance(expr, Or)
and len(expr.args) == 2
and isinstance(expr.args[0], And)
and isinstance(expr.args[1], And)
and (
(
expr.args[1].args[0] == Not(expr.args[0].args[0])
and expr.args[1].args[1] == Not(expr.args[0].args[1])
)
or (
Not(expr.args[1].args[0]) == expr.args[0].args[0]
and Not(expr.args[1].args[1]) == expr.args[0].args[1]
)
)
):
a = optimizer(expr.args[0].args[0])
b = optimizer(expr.args[0].args[1])
return Not(Xor(a, b))

# Translate or to and
elif isinstance(expr, Or):
return Not(And(*[Not(optimizer(e)) for e in expr.args]))

elif isinstance(expr, Xor):
return Xor(*[optimizer(e) for e in expr.args])

elif isinstance(expr, BooleanTrue) or isinstance(expr, BooleanFalse):
return expr

else:
raise Exception(expr)


class Compiler:
def __init__(self):
pass

def _symplify_exp(self, exp):
exp = optimizer(exp)
# exp = to_anf(exp, deep=False)
return exp

Expand Down
15 changes: 5 additions & 10 deletions qlasskit/qlassf.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,8 @@

from .ast2ast import ast2ast
from .ast2logic import Arg, Args, BoolExpList, LogicFun, flatten, translate_ast
from .bool_optimizer import (
merge_unnecessary_assigns,
remove_const_exps,
remove_unnecessary_assigns,
)
from .boolopt.bool_optimizer import bestWorkingOptimizer # noqa: F401
from .boolopt.bool_optimizer import experimentalOptimizer # noqa: F401
from .compiler import SupportedCompiler, to_quantum
from .types import * # noqa: F403, F401
from .types import Qtype, type_repr
Expand Down Expand Up @@ -203,11 +200,9 @@ def from_function(
fun_name, args, fun_ret, exps = translate_ast(fun, types, defs)
original_f = eval(fun_name) if isinstance(f, str) else f

# Optimize the expression list
exps = remove_const_exps(exps)
exps = remove_unnecessary_assigns(exps)
exps = merge_unnecessary_assigns(exps)
# exps = subsitute_exps(exps)
exps = bestWorkingOptimizer.apply(exps)

# print(exps)

# Return the qlassf object
qf = QlassF(fun_name, original_f, args, fun_ret, exps)
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
author_email="[email protected]",
packages=[
"qlasskit",
"qlasskit.boolopt",
"qlasskit.types",
"qlasskit.ast2logic",
"qlasskit.qcircuit",
Expand Down
Loading

0 comments on commit c85b9a4

Please sign in to comment.