[pypy-svn] r24479 - pypy/dist/pypy/lib/logic/computation_space
auc at codespeak.net
auc at codespeak.net
Thu Mar 16 16:35:31 CET 2006
Author: auc
Date: Thu Mar 16 16:35:28 2006
New Revision: 24479
Modified:
pypy/dist/pypy/lib/logic/computation_space/computationspace.py
pypy/dist/pypy/lib/logic/computation_space/constraint.py
pypy/dist/pypy/lib/logic/computation_space/problems.py
pypy/dist/pypy/lib/logic/computation_space/solvers.py
pypy/dist/pypy/lib/logic/computation_space/test_computationspace.py
Log:
cleanup, soon alldistinct
Modified: pypy/dist/pypy/lib/logic/computation_space/computationspace.py
==============================================================================
--- pypy/dist/pypy/lib/logic/computation_space/computationspace.py (original)
+++ pypy/dist/pypy/lib/logic/computation_space/computationspace.py Thu Mar 16 16:35:28 2006
@@ -10,21 +10,8 @@
from distributor import DefaultDistributor
import event # NewSpace, Clone, Revise
-class Succeeded: pass
-class Failed(Exception): pass
-
-class Alternative(object):
-
- def __init__(self, choices):
- self._choices = choices
-
- def __eq__(self, other):
- if other is None: return False
- if not isinstance(other, Alternative): return False
- return self._choices == other._choices
-
- def __str__(self):
- return "Alternatives(%s)" % self._choices
+Failed = 0
+Succeeded = 1
def NoProblem():
"""the empty problem, used by clone()"""
@@ -100,6 +87,9 @@
ret.append(">")
return ' '.join(ret)
+ def __repr__(self):
+ return "<space n°%s>" % self.id
+
def __eq__(self, spc):
"""space equality defined as :
* identity, or
@@ -151,7 +141,6 @@
res = False
return res
-
#-- Computation Space -----------------------------------------
#-- space helpers -----------------------------------------
@@ -184,7 +173,7 @@
if self.status in (Failed, Succeeded):
return self.status
if self._distributable():
- return Alternative(self.distributor.nb_subdomains())
+ return self.distributor.nb_subdomains()
def clone(self):
spc = ComputationSpace(NoProblem, parent=self)
@@ -263,7 +252,7 @@
self.names[name] = var
print "just created new var %s" % var
- def get_var_by_name(self, name):
+ def find_var(self, name):
"""looks up one variable"""
try:
return self.names[name]
@@ -279,12 +268,12 @@
raise NotInStore(str(names))
def is_bound(self, var):
- """check wether a var is locally bound"""
+ """check wether a var has a singleton domain"""
return len(self.dom(var)) == 1
def val(self, var):
- """return the local binding without blocking"""
- if self.is_bound(var): # the speculative val
+ """return the speculative"""
+ if self.is_bound(var):
return self.dom(var)[0]
return NoValue
@@ -347,9 +336,6 @@
#-- Constraint propagation ---------------
- def add_distributed(self, var):
- self.changelog.append(var)
-
def _init_constraint_queue(self):
cqueue = []
init_const_set = set()
@@ -365,7 +351,7 @@
return cqueue
def satisfy_all(self):
- """really PROPAGATE"""
+ """really PROPAGATE from AC3"""
const_q = self._init_constraint_queue()
assert const_q != []
const_q.sort()
@@ -379,7 +365,7 @@
const_q.sort()
affected_constraints.clear()
cost, const = const_q.pop(0)
- entailed = const.revise3()
+ entailed = const.revise()
for var in const.affected_variables():
dom = self.dom(var)
if not dom.has_changed():
Modified: pypy/dist/pypy/lib/logic/computation_space/constraint.py
==============================================================================
--- pypy/dist/pypy/lib/logic/computation_space/constraint.py (original)
+++ pypy/dist/pypy/lib/logic/computation_space/constraint.py Thu Mar 16 16:35:28 2006
@@ -51,14 +51,12 @@
def remove_value(self, value):
"""Remove value of domain and check for consistency"""
-## print "removing", value, "from", self._values.keys()
- del self._values[value]
+ self._values.remove(value)
self._value_removed()
def remove_values(self, values):
"""Remove values of domain and check for consistency"""
if values:
-## print "removing", values, "from", self._values.keys()
for val in values :
self._values.remove(val)
self._value_removed()
@@ -159,7 +157,7 @@
def getVariable(self):
return self._variable
- def revise3(self, domains):
+ def revise(self, domains):
domain = domains[self._variable]
operator = self._operator
ref = self._reference
@@ -184,6 +182,72 @@
expr.replace(var.name, var.name + '.val')
return expr
+
+class AllDistinct(AbstractConstraint):
+ """Contraint: all values must be distinct"""
+
+ def __init__(self, c_space, variables):
+ assert len(variables)>1
+ AbstractConstraint.__init__(self, c_space, variables)
+ # worst case complexity
+ self.__cost = len(variables) * (len(variables) - 1) / 2
+
+ def __repr__(self):
+ return '<AllDistinct %s>' % str(self._variables)
+
+ def copy_to(self, space):
+ return self.__class__(space, self._variables)
+
+ def estimateCost(self, domains):
+ return self.__cost
+
+ def test_solution(self, sol):
+ """test a solution against this constraint
+ accept a mapping of variable names to value"""
+ values = sol.items()
+ value_set = set(values)
+ return len(value_set) == len(sol)
+
+ def revise(self):
+ variables = [(self.cs.dom(variable).size(),
+ variable, self.cs.dom(variable))
+ for variable in self._variables]
+
+ variables.sort()
+ # if a domain has a size of 1,
+ # then the value must be removed from the other domains
+ for size, var, dom in variables:
+ if dom.size() == 1:
+ print "AllDistinct removes values"
+ for _siz, _var, _dom in variables:
+ if _var != var:
+ try:
+ _dom.remove_value(dom.get_values()[0])
+ except KeyError:
+ # we ignore errors caused by the removal of
+ # non existing values
+ pass
+
+ # if there are less values than variables, the constraint fails
+ values = {}
+ for size, var, dom in variables:
+ for val in dom:
+ values[val] = 0
+ if len(values) < len(variables):
+ print "AllDistinct failed"
+ raise ConsistencyFailure()
+
+ # the constraint is entailed if all domains have a size of 1
+ for variable in variables:
+ if variable[2].size() != 1:
+ return 0
+
+ # Question : did we *really* completely check
+ # our own alldistinctness predicate ?
+
+ return 1
+
+
class Expression(AbstractConstraint):
"""A constraint represented as a python expression."""
_FILTER_CACHE = {}
@@ -202,7 +266,7 @@
Expression._FILTER_CACHE[formula] = self.filterFunc
def test_solution(self, sol ):
- """FOR TESTING: test a solution against this constraint
+ """test a solution against this constraint
accept a mapping of variable names to value"""
args = []
for var in self._variables:
@@ -249,7 +313,7 @@
# it's over
go_on = 0
- def revise3(self):
+ def revise(self):
# removed domain arg. (auc, ale)
"""generic propagation algorithm for n-ary expressions"""
maybe_entailed = 1
@@ -305,7 +369,7 @@
def copy_to(self, space):
raise NotImplementedError
- def revise3(self, domains):
+ def revise(self, domains):
"""specialized narrowing algorithm for binary expressions
Runs much faster than the generic version"""
maybe_entailed = 1
Modified: pypy/dist/pypy/lib/logic/computation_space/problems.py
==============================================================================
--- pypy/dist/pypy/lib/logic/computation_space/problems.py (original)
+++ pypy/dist/pypy/lib/logic/computation_space/problems.py Thu Mar 16 16:35:28 2006
@@ -87,15 +87,15 @@
cs.set_dom(v, c.FiniteDomain(dom_values))
for conf in ('c03','c04','c05','c06'):
- v = cs.get_var_by_name(conf)
+ v = cs.find_var(conf)
cs.add_constraint([v], "%s[0] == 'room C'" % v.name)
for conf in ('c01','c05','c10'):
- v = cs.get_var_by_name(conf)
+ v = cs.find_var(conf)
cs.add_constraint([v], "%s[1].startswith('day 1')" % v.name)
for conf in ('c02','c03','c04','c09'):
- v = cs.get_var_by_name(conf)
+ v = cs.find_var(conf)
cs.add_constraint([v], "%s[1].startswith('day 2')" % v.name)
groups = (('c01','c02','c03','c10'),
@@ -103,6 +103,14 @@
('c03','c05','c06','c07'),
('c01','c03','c07','c08'))
+ from constraint import AllDistinct
+
+ # for now, this has the incredible effect
+ # of making the solvers run forever ...
+## for group in groups:
+## cs.add_expression(AllDistinct(cs, tuple([cs.find_var(v)
+## for v in group])))
+
for g in groups:
for conf1 in g:
for conf2 in g:
@@ -138,7 +146,7 @@
offsets = [(r,c) for r in [-1,0,1] for c in [-1,0,1]]
subsquares = [(r,c) for r in [2,5,8] for c in [2,5,8]]
for rc in subsquares:
- sub = [cs.get_var_by_name('v%d%d'% (rc[0] + off[0],rc[1] + off[1])) for off in offsets]
+ sub = [cs.find_var('v%d%d'% (rc[0] + off[0],rc[1] + off[1])) for off in offsets]
cs.add_constraint(sub, 'sum([%s]) == 45' % ', '.join([v.name for v in sub]))
for v in sub:
for m in sub[sub.index(v)+1:]:
Modified: pypy/dist/pypy/lib/logic/computation_space/solvers.py
==============================================================================
--- pypy/dist/pypy/lib/logic/computation_space/solvers.py (original)
+++ pypy/dist/pypy/lib/logic/computation_space/solvers.py Thu Mar 16 16:35:28 2006
@@ -3,19 +3,23 @@
class StrategyDistributionMismatch(Exception):
pass
-def dfs_one(problem):
+#--- this set of solvers assumes a dichotomic distributor
+
+Alternatives = 2
+
+def rec_solve_one(problem):
"""depth-first single-solution search
assumes the space default distributor is
dichotomic"""
def do_dfs(space):
- print "do_dfs"
+ print "do_dfs_one"
status = space.ask()
if status == csp.Failed:
return None
elif status == csp.Succeeded:
return space
- elif status == csp.Alternative(2):
+ elif status == Alternatives:
new_space = space.clone()
space.commit(1)
outcome = do_dfs(space)
@@ -34,13 +38,15 @@
#-- solve_all, switchable direction (takes problem)
+from collections import deque
+
class Depth: pass
class Breadth: pass
-def solve_all(problem, direction=Depth):
+def iter_solve_all(problem, direction=Depth):
solutions = []
- sp_stack = []
+ sp_stack = deque([])
sp_stack.append(csp.ComputationSpace(problem))
if direction == Depth:
@@ -48,16 +54,17 @@
sp_stack.append(space)
else:
def collect(space):
- sp_stack.insert(0, space)
+ sp_stack.appendleft(space)
while len(sp_stack):
+ print "depth is ", len(sp_stack)
space = sp_stack.pop()
print ' '*len(sp_stack), "ask ..."
status = space.ask()
if status == csp.Succeeded:
print ' '*len(sp_stack), "solution !"
solutions.append(space)
- elif status == csp.Alternative(2):
+ elif status == Alternatives:
print ' '*len(sp_stack), "branches ..."
sp1 = space.clone()
sp1.commit(1)
@@ -70,9 +77,9 @@
#-- pythonic lazy solve_all (takes space)
-def lazily_solve_all(space, direction=Depth):
+def lazily_iter_solve_all(space, direction=Depth):
- sp_stack = []
+ sp_stack = deque([])
sp_stack.append(space)
if direction == Depth:
@@ -80,7 +87,7 @@
sp_stack.append(space)
else:
def collect(space):
- sp_stack.insert(0, space)
+ sp_stack.appendleft(space)
while len(sp_stack):
space = sp_stack.pop()
@@ -89,7 +96,7 @@
if status == csp.Succeeded:
print ' '*len(sp_stack), "solution !"
yield space.merge()
- elif status == csp.Alternative(2):
+ elif status == Alternatives:
print ' '*len(sp_stack), "branches ..."
sp1 = space.clone()
sp1.commit(1)
Modified: pypy/dist/pypy/lib/logic/computation_space/test_computationspace.py
==============================================================================
--- pypy/dist/pypy/lib/logic/computation_space/test_computationspace.py (original)
+++ pypy/dist/pypy/lib/logic/computation_space/test_computationspace.py Thu Mar 16 16:35:28 2006
@@ -30,11 +30,11 @@
class TestStoreUnification:
- def test_get_by_name(self):
+ def test_find_var(self):
sp = newspace()
x = sp.var('x')
- assert x == sp.get_var_by_name('x')
- raises(space.NotInStore, sp.get_var_by_name, 'y')
+ assert x == sp.find_var('x')
+ raises(space.NotInStore, sp.find_var, 'y')
def test_add_expression(self):
sp = newspace()
@@ -55,7 +55,7 @@
sp.set_dom(y, c.FiniteDomain([2, 3]))
sp.set_dom(z, c.FiniteDomain([3, 4]))
k = c.Expression(sp, [x, y, z], 'x == y + z')
- raises(c.ConsistencyFailure, k.revise3)
+ raises(c.ConsistencyFailure, k.revise)
def test_narrowing_domains_success(self):
sp = newspace()
@@ -64,7 +64,7 @@
sp.set_dom(y, c.FiniteDomain([2, 3]))
sp.set_dom(z, c.FiniteDomain([3, 4]))
k = c.Expression(sp, [x, y, z], 'x == y + z')
- k.revise3()
+ k.revise()
assert sp.dom(x) == c.FiniteDomain([5])
assert sp.dom(y) == c.FiniteDomain([2])
assert sp.dom(z) == c.FiniteDomain([3])
@@ -75,9 +75,6 @@
class TestComputationSpace:
- def setup_method(self, meth):
- pass
-
def test_bind_cs_root(self):
spc = newspace(problems.satisfiable_problem)
assert '__root__' in spc.names
@@ -86,7 +83,7 @@
def test_ask_alternatives(self):
spc = newspace(problems.satisfiable_problem)
- assert spc.ask() == space.Alternative(2)
+ assert spc.ask() == 2
def test_clone(self):
"""checks that a chain of initially s1 = s2
@@ -119,11 +116,11 @@
space.add_constraint([y], 'y < 2')
spc = newspace(problems.satisfiable_problem)
- assert spc.ask() == space.Alternative(2)
+ assert spc.ask() == 2
new_spc = spc.clone()
new_spc.ask()
new_spc.inject(more_constraints)
- assert spc.ask() == space.Alternative(2)
+ assert spc.ask() == 2
assert new_spc.ask() == space.Succeeded
def test_merge(self):
@@ -134,39 +131,39 @@
space.add_constraint([y], 'y < 2')
spc = newspace(problems.satisfiable_problem)
- assert spc.ask() == space.Alternative(2)
+ assert spc.ask() == 2
new_spc = spc.clone()
new_spc.ask()
new_spc.inject(more_constraints)
- assert spc.ask() == space.Alternative(2)
+ assert spc.ask() == 2
assert new_spc.ask() == space.Succeeded
x, y, z = new_spc.find_vars('x', 'y', 'z')
res = new_spc.merge()
assert res.values() == [0, 0, 0]
def test_scheduling_dfs_one_solution(self):
- sol = solvers.dfs_one(problems.conference_scheduling)
+ sol = solvers.rec_solve_one(problems.conference_scheduling)
spc = space.ComputationSpace(problems.conference_scheduling)
assert spc.test_solution( sol )
def test_scheduling_all_solutions_dfs(self):
- sols = solvers.solve_all(problems.conference_scheduling)
+ sols = solvers.iter_solve_all(problems.conference_scheduling)
assert len(sols) == 64
spc = space.ComputationSpace(problems.conference_scheduling)
for s in sols:
assert spc.test_solution( s )
-
def test_scheduling_all_solutions_lazily_dfs(self):
sp = space.ComputationSpace(problems.conference_scheduling)
- for sol in solvers.lazily_solve_all(sp):
+ for sol in solvers.lazily_iter_solve_all(sp):
assert sp.test_solution(sol)
def test_scheduling_all_solutions_bfs(self):
- sols = solvers.solve_all(problems.conference_scheduling,
- direction=solvers.Breadth)
+ sols = solvers.iter_solve_all(problems.conference_scheduling,
+ direction=solvers.Breadth)
+
assert len(sols) == 64
spc = space.ComputationSpace(problems.conference_scheduling)
for s in sols:
@@ -175,11 +172,11 @@
def test_scheduling_all_solutions_lazily_bfs(self):
sp = space.ComputationSpace(problems.conference_scheduling)
- for sol in solvers.lazily_solve_all(sp, direction=solvers.Breadth):
+ for sol in solvers.lazily_iter_solve_all(sp, direction=solvers.Breadth):
assert sp.test_solution(sol)
- def notest_sudoku(self):
+ def no_test_sudoku(self):
spc = newspace(problems.sudoku)
print spc.constraints
@@ -193,12 +190,12 @@
for col in range(1,10):
if line[col-1] != ' ':
tup = ('v%d%d' % (col, row), int(line[col-1]))
- space.add_constraint([space.get_var_by_name(tup[0])],'%s == %d' % tup)
+ space.add_constraint([space.find_var(tup[0])],'%s == %d' % tup)
row += 1
spc.inject(more_constraints)
print spc.constraints
- sol_iter = solvers.lazily_solve_all(spc)
+ sol_iter = solvers.lazily_iter_solve_all(spc)
sol = sol_iter.next()
print sol
assert spc.test_solution(sol)
More information about the Pypy-commit
mailing list