Skip to content

Commit

Permalink
heuristic based expression solving by random assignments
Browse files Browse the repository at this point in the history
  • Loading branch information
izgzhen committed Oct 18, 2019
1 parent ea919f6 commit 563f830
Show file tree
Hide file tree
Showing 7 changed files with 311 additions and 11 deletions.
158 changes: 158 additions & 0 deletions cozy/random_assignment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import random
import string
from itertools import product

from cozy.common import FrozenDict
from cozy.syntax import *
from cozy.syntax_tools import free_vars
from cozy.evaluation import eval, mkval
from cozy.target_syntax import EFlatMap, EFilter, EMap, EStateVar
from cozy.value_types import Bag


def random_value(t):
"""
Construct a stream of values in type t
"""
if isinstance(t, TBag):
yield Bag()
for v in random_value(t.elem_type):
yield Bag((v,))
for v1 in random_value(t.elem_type):
for v2 in random_value(t.elem_type):
yield Bag((v1, v2))
elif isinstance(t, TInt):
yield random.randint(0, 100)
yield 0
elif isinstance(t, TNative):
yield (t.name, 0)
elif isinstance(t, TFloat):
yield random.randint(0, 100) / 100.0
yield 0.0
elif isinstance(t, TBool):
yield True
yield False
elif isinstance(t, TString):
yield ''.join(random.choice(string.ascii_letters) for _ in range(8))
elif isinstance(t, TRecord):
iterables = [random_value(ty) for _, ty in t.fields]
for vs in product(*iterables):
yield FrozenDict({field[0]: v for v, field in zip(vs, t.fields)})
else:
raise Exception("Unknown type for random value construction: {}".format(t))


def get_pulled(e):
"""
Strip EStateVar from a pulled expression in list comprehension
"""
if isinstance(e, EStateVar):
return e.e
return e


def extract_listcomp(e):
"""
Extract list comprehension components from its desugared form
:param e: list comprehension expression
:return: list comprehension structure { "P": ..., "C": ..., "V": ... }.
"P" is pulled expressions, "C" is condition, "V" is returned value.
In the written form, it is { V(p0, ..., pn) | p0 <- P_0, ..., pn <- P_n, C(p0, ..., pn)}.
Notice that all V and C already have free variables p0 to pn.
If the structure doesn't follow our assumption, return None
"""
if isinstance(e, EFlatMap):
pulled = e.e
f = e.transform_function
var = f.arg
ebody = f.body
lc = extract_listcomp(ebody)
if lc is not None:
lc["P"][var] = get_pulled(pulled)
return lc
elif isinstance(e, EMap):
f = e.transform_function
ebody = f.body
lc = extract_listcomp(e.e)
if lc is not None:
lc["V"] = ebody
return lc
elif isinstance(e, EFilter):
lc = {"C": e.predicate.body, "P": {e.predicate.arg: get_pulled(e.e)}}
return lc
return None


def get_cond(lc):
"""
Turn list comprehension structure into a conjunctive formula that specifies each pulled collection expression
is a singleton and all individual elements in singletons satisfy the condition
"""
singletons = [EEq(ESingleton(var).with_type(TBag(var.type)), p) for var, p in lc["P"].items()]
return EAll([lc["C"]] + singletons)


def unsatisfiable(e):
"""
Heuristic to decide whether e is unsatisfiable quickly.
it is a partial procedure: the possible outputs are True (indicating unsatisfiability) and None (indicating unknown).
"""
# It is unsatisfiable that two structurally equivalent list comprehensions are not semantically equivalent
if isinstance(e, EUnaryOp) and e.op == "not" and isinstance(e.e, EBinOp) and e.e.op == "==":
e1 = e.e.e1
e2 = e.e.e2
while isinstance(e1, EStateVar):
e1 = e1.e
while isinstance(e2, EStateVar):
e2 = e2.e
if isinstance(e1, EFlatMap) and isinstance(e2, EFlatMap):
lc1 = extract_listcomp(e1)
lc2 = extract_listcomp(e2)
if lc1 is not None and lc2 is not None:
if lc1 == lc2:
return True
return False


def _satisfy(e, solver):
"""
Heuristic to decide whether e is satisfiable quickly.
it is a partial procedure: the possible outputs are a satisfying assignment or None (indicating unknown)
it is allowed to indicate unknown with an arbitrary exception
(in which case falling back to the symbolic solver is a reasonable choice)
"""
if isinstance(e, EUnaryOp) and e.op == "not" and isinstance(e.e, EBinOp) and e.e.op == "==":
e1 = e.e.e1
e2 = e.e.e2
if isinstance(e1, EFlatMap) and isinstance(e2, EFlatMap):
lc1 = extract_listcomp(e1)
lc2 = extract_listcomp(e2)
if lc1 is not None and lc2 is not None:
cond1 = get_cond(lc1)
cond2 = get_cond(lc2)
sat1 = solver.satisfy(cond1)
sat2 = solver.satisfy(cond2)
if sat1 is None and sat2 is not None:
return {k: v for k, v in sat2.items() if k not in lc2["P"]}
if sat1 is not None and sat2 is None:
return {k: v for k, v in sat1.items() if k not in lc1["P"]}

iterables = [random_value(v.type) for v in free_vars(e)]
ids = [v.id for v in free_vars(e)]
for vs in product(*iterables):
assignments = {}
for id_, val in zip(ids, vs):
assignments[id_] = val
sat = eval(e, assignments)
if sat:
return assignments
return None


def satisfy(e, solver):
assignments = _satisfy(e, solver)
if assignments is not None:
for v in solver.vars:
if v.id not in assignments:
assignments[v.id] = mkval(v.type)
return assignments
23 changes: 21 additions & 2 deletions cozy/synthesis/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

from multiprocessing import Value

from cozy import random_assignment
from cozy.syntax import (
INT, BOOL, TMap,
Op,
Expand All @@ -30,7 +31,7 @@
from cozy.typecheck import is_collection, is_scalar
from cozy.syntax_tools import subst, pprint, free_vars, fresh_var, alpha_equivalent, strip_EStateVar, freshen_binders, wrap_naked_statevars, break_conj, inline_lets
from cozy.wf import exp_wf
from cozy.common import No, OrderedSet, unique, OrderedSet, StopException
from cozy.common import No, unique, OrderedSet, StopException
from cozy.solver import valid, solver_for_context, ModelCachingSolver
from cozy.evaluation import construct_value
from cozy.cost_model import CostModel, Order, LINEAR_TIME_UOPS
Expand Down Expand Up @@ -96,6 +97,9 @@
description="Applies a limit to the number of improvements cozy will run"
+ "on the specification. (-1) means no limit.")

allow_random_assignment_heuristic = Option("allow-random-assignment-heuristic", bool, True,
description="Use a random assignment heuristic instead of solver to solve sat/unsat problem")

def never_stop():
"""Takes no arguments, always returns False."""
return False
Expand Down Expand Up @@ -242,7 +246,22 @@ def improve(

# 2. check
with task("verifying candidate"):
counterexample = solver.satisfy(ENot(EEq(target, new_target)))
# try heuristic based solving first
e = ENot(EEq(target, new_target))
if allow_random_assignment_heuristic.value:
if random_assignment.unsatisfiable(e):
counterexample = None
else:
try:
counterexample = random_assignment.satisfy(e, solver)
except Exception:
counterexample = None
if counterexample is None:
event("failed assignmnents: for %s\n" % e)
counterexample = solver.satisfy(e)
event("counter-example: for %s\n" % counterexample)
else:
counterexample = solver.satisfy(e)

if counterexample is not None:
if counterexample in examples:
Expand Down
1 change: 1 addition & 0 deletions examples/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
*.jar
*.synthesized
listcomp
select
6 changes: 6 additions & 0 deletions examples/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,14 @@ listcomp.h: listcomp-flatmap.ds
select.h: select-flatmap.ds
cozy -t $(TIMEOUT) --allow-big-sets select-flatmap.ds --c++ select.h -p 8080 --verbose --save select.synthesized

select: select.cpp select.h
g++ -std=c++11 -O3 -Werror '$<' -o '$@'

listcomp: listcomp.cpp listcomp.h
g++ -std=c++11 -O3 -Werror '$<' -o '$@'

run-select: select
time ./select

run-listcomp: listcomp
time ./listcomp
21 changes: 21 additions & 0 deletions examples/select.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#include "select.h"
#include <iostream>

void query(SelectFlatmap::_Type3508 elem) {
std::cout << "Found elem(" << elem._0 << "," <<
elem._1 << "," << elem._2 << "," << elem._3 << ")" << std::endl;
}

int main() {
SelectFlatmap::R r(15, "hello");
std::vector<SelectFlatmap::R> Rs;
Rs.push_back(r);

SelectFlatmap::W w("world", 100);
std::vector<SelectFlatmap::W> Ws;
Ws.push_back(w);

SelectFlatmap m(Rs, Ws, Ws, Ws);
m.q(query);
return 0;
}
Loading

0 comments on commit 563f830

Please sign in to comment.