[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