Here is a short write up on an experiment I did using PyEDA for the purpose of developing a "poor-man's" random constraint solver using Python.
At it's core, PyEDA is a library that provides support for the specification of complex boolean functions.
It provides a very clean interface to PicoSAT, a popular SAT solver.
The first realization I made was that PyEDA did not support integer values. With the help of the author of PyEDA, the following solution was devised.
You can specify integers using function arrays :
class Int: def __init__(self, name, width=3, value=None): self.name = name self.width = width if value != None: self.farray = int2exprs(value, self.width) # a real value else: self.farray = exprvars(name, width) # empty variables
A function is array is nothing but an array of boolean variables.
The next step is to add operators.
def __eq__(self, other): name = "{}eq{}".format(self.name, other.name) f = Int(name, self.width) f.farray = farray([Equal(i, j) for i, j in zip(self.farray, other.farray)]) return f def __add__(self, other): sum, carry = ripple_carry_add(self.farray, other.farray) name = "{}add{}".format(self.name, other.name) f = Int(name, self.width) f.farray = farray([i & ~(carry[-1] & carry[-2]) for i in sum]) # this is completely incorrect, need to fix overflow issue return f
A wrapper to the SAT solve function call and a __str__ method :
def satisfy_all(self): return self.farray.uand().satisfy_all() def __str__(self): return self.name + " : " + str(self.farray)
def from_soln(self, soln): value = 0 for boolvar in sorted(soln.keys(), reverse=True): tmp = str(boolvar) if tmp.startswith(self.name): value = value | (int(soln[boolvar]) * pow(2,int(tmp[-2]))) return value
I'll conclude this knowledge share post with 3 simple examples :
1. A == B
This works very well.
>>> a = Int('A', 3)
>>> b = Int('B', 3)
>>> f = (a == b)
>>> for soln in f.satisfy_all():
>>> print("a="+str(a.from_soln(soln))+" b="+str(b.from_soln(soln)))
a=0 b=0
a=4 b=4
a=2 b=2
a=6 b=6
a=1 b=1
a=5 b=5
a=3 b=3
a=7 b=7
2. F == (A + B)
This works well, but there is an overflow bug that I have chosen not to fix.
>>> a = Int('A', 3)
>>> b = Int('B', 3)
>>> f = Int('F', 3)
>>> g = (f == (a + b))
>>> for soln in g.satisfy_all():
>>> print("a="+str(a.from_soln(soln))+" b="+str(b.from_soln(soln))+" f="+str(f.from_soln(soln)))
a=0 b=0 f=0
a=0 b=4 f=4
a=0 b=2 f=2
a=0 b=6 f=6
a=0 b=1 f=1
[..]
a=2 b=6 f=0 <-- overflow bug
2. F == (A + B), F == 3
The same overflow bug happens here, but I also identified a more serious issue. Since F is kept constant, the SAT solver does not return it's var.
a = Int('A', 3)
b = Int('B', 3)
f = Int('F', 3, 3)
g = (f == (a + b))
for soln in g.satisfy_all():
print("a="+str(a.from_soln(soln))+" b="+str(b.from_soln(soln))+" f="+str(f.from_soln(soln)))
a=0 b=3 f=0 <-- f is not returned by SAT solver
a=2 b=1 f=0
a=1 b=2 f=0
a=4 b=7 f=0 <-- overflow bug
[..]
This concludes my experiment with PyEDA. It was an worthwhile and interesting experiment, but it's time to move on.
As far as "poor-man's" constraint solver goes, this doesn't come close. I would point interested individuals to the CRAVE project (or, commercial tools!).
No comments:
Post a Comment