[pypy-svn] r23614 - pypy/dist/pypy/lib/logic/computation_space

auc at codespeak.net auc at codespeak.net
Thu Feb 23 12:19:34 CET 2006


Author: auc
Date: Thu Feb 23 12:19:28 2006
New Revision: 23614

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/distributor.py
   pypy/dist/pypy/lib/logic/computation_space/strategies.py
   pypy/dist/pypy/lib/logic/computation_space/test_computationspace.py
Log:
locking, utilities, tests (clone), solve_all


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 Feb 23 12:19:28 2006
@@ -2,7 +2,8 @@
 # * support several distribution strategies
 # * add a linear constraint solver (vital for fast
 #   constraint propagation over finite integer domains)
-
+# * make all propagators live in their own threads and
+#   be awakened by variable/domains events
 
 from threading import Thread, Condition, RLock, local
 
@@ -74,9 +75,9 @@
         self.in_transaction = False
         self.bind_lock = RLock()
         self.var_lock = RLock()
-        self.status = None
-        self.status_condition = Condition()
+        self.dist_lock = RLock()
         self.distributor = DefaultDistributor(self)
+        self.status = Unknown
         self.parent = parent
         self.children = set()
         
@@ -103,6 +104,7 @@
             self.constraints = parent.constraints
             self.doms = {} # shall be copied by clone
             self.root = parent.root
+            self.status = parent.status
             self.distributor = parent.distributor.__class__(self)
             self._init_choose_commit()
 
@@ -115,6 +117,23 @@
         self.CHOOSE = self._make_choice_var()
         self.STABLE = self._make_stable_var()
 
+#-- utilities -------------------------------------------------
+
+    def __str__(self):
+        ret = ["<space:\n"]
+        for v, d in self.doms.items():
+            if self.dom(v) != NoDom:
+                ret.append('  ('+str(v)+':'+str(d)+')\n')
+        ret.append(">")
+        return ' '.join(ret)
+
+    def pretty_doms(self):
+        print "(-- domains --"
+        for v, d in self.doms.items():
+            if d != NoDom:
+                print ' ', str(d)
+        print " -- domains --)"
+
 #-- Computation Space -----------------------------------------
 
     def _make_choice_var(self):
@@ -146,16 +165,15 @@
                 self.status = Succeeded
 
     def _distributable(self):
-        if self.status not in (Failed, Succeeded):
-            self.status = Unknown
-            # sync. barrier with distributor (?)
-            print "distributable vars :", self.root.val
-            for var in self.root.val:
-                print "   ", var, " -> ", self.doms[var]
-                if self.dom(var).size() > 1 :
-                    self.status = Distributable
-                    return True
-        return False
+        self.dist_lock.acquire()
+        try:
+            if self.status not in (Failed, Succeeded):
+                for var in self.root.val:
+                    if self.dom(var).size() > 1 :
+                        return True
+            return False
+        finally:
+            self.dist_lock.release()
         # in The Book : "the space has one thread that is
         # suspended on a choice point with two or more alternatives.
         # A space can have at most one choice point; attempting to
@@ -184,7 +202,6 @@
         for var in spc.vars:
             if self.dom(var) != NoDom:
                 spc.set_dom(var, self.dom(var).copy())
-        spc.status = Distributable
         return spc
 
     def inject(self, restricting_problem):
@@ -199,14 +216,15 @@
            some_number must satisfy 1=<I=<N where N is the first arg
            of the Choose call.
         """
-        print "SPACE commited to", choice
+        #print "SPACE commited to", choice
         # block future calls to Ask until the distributor
         # binds STABLE
+        old_stable_var = self.STABLE
         self.STABLE = self._make_stable_var()
-        print "SPACE binds CHOOSE to", choice
+        self._del_var(old_stable_var)
+        #print "SPACE binds CHOOSE to", choice
         self.bind(self.CHOOSE, choice)
 
-
     def choose(self, nb_choices):
         """
         waits for stability
@@ -276,7 +294,6 @@
         try:
             return self.doms[var]
         except KeyError:
-            print "warning : setting no domain for", var
             self.doms[var] = NoDom
             return NoDom
 
@@ -308,6 +325,12 @@
         if self.is_bound(var): # the speculative val
             return self.dom(var)[0]
         return NoValue
+
+    def _del_var(self, var):
+        """purely private stuff, use at your own perils"""
+        self.vars.remove(var)
+        if self.doms.has_key(var):
+            del self.doms[var]
     
     #-- Constraints -------------------------
 
@@ -333,6 +356,8 @@
             if self.dom(var) != NoDom: varset.add(var)
         return varset
 
+    #-- Constraint propagation ---------------
+
     def satisfiable(self, constraint):
         """ * satisfiable (k) checks that the constraint k
               can be satisfied wrt its variable domains
@@ -363,7 +388,6 @@
         self.restore_domains(old_domains)
         return True
 
-
     def get_satisfying_domains(self, constraint):
         """computes the smallest satisfying domains"""
         assert constraint in self.constraints
@@ -399,15 +423,11 @@
 
     def satisfy_all(self):
         """really PROPAGATE"""
-      
-        print "propagating on %s" % fif(self.top_level(),
-                                        'top', 'child')
         const_q = [(const.estimateCost(), const)
                    for const in self.constraints]
         affected_constraints = set()
         while True:
             if not const_q:
-                #XXX: estimateCost
                 const_q = [(const.estimateCost(), const)
                            for const in affected_constraints]
                 if not const_q:

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 Feb 23 12:19:28 2006
@@ -231,7 +231,6 @@
         variables.sort()
 
         go_on = 1
-        print 
         while go_on:
             yield kwargs
             # try to instanciate the next variable

Modified: pypy/dist/pypy/lib/logic/computation_space/distributor.py
==============================================================================
--- pypy/dist/pypy/lib/logic/computation_space/distributor.py	(original)
+++ pypy/dist/pypy/lib/logic/computation_space/distributor.py	Thu Feb 23 12:19:28 2006
@@ -140,17 +140,19 @@
     def run(self):
         self.cs._process() # propagate first
         self.cs.STABLE.bind(0)
-        while self.cs.status == Distributable:
+        while self.cs._distributable():
             choice = self.cs.choose(self.nb_subdomains())
             # racey ... ?
-            self.new_distribute(choice)
+            self.distribute(choice-1)
             self.cs._process()
+            old_choose_var = self.cs.CHOOSE
             self.cs.CHOOSE = self.cs._make_choice_var()
+            self.cs._del_var(old_choose_var)
             self.cs.STABLE.bind(0) # unlocks Ask
         print "-- distributor terminated --"
 
 
-    def new_distribute(self, choice):
+    def distribute(self, choice):
         """See AbstractDistributor"""
         variable = self.findSmallestDomain()
         #variables = self.cs.get_variables_with_a_domain()
@@ -164,27 +166,6 @@
         self.cs.dom(variable).remove_values(values[end:])
 
 
-    ### some tests rely on this old
-    ### do_everything-at-once version
-        
-    def _distribute(self, *args):
-        """See AbstractDistributor"""
-        variable = self.__to_split
-        nb_subspaces = len(args)
-        values = args[0][variable].get_values()
-        nb_elts = max(1, len(values)*1./nb_subspaces)
-        slices = [(int(math.floor(index * nb_elts)),
-                   int(math.floor((index + 1) * nb_elts)))
-                  for index in range(nb_subspaces)]
-        if self.verbose:
-            print 'Distributing domain for variable', variable
-        modified = []
-        for (dom, (end, start)) in zip(args, slices) :
-            dom[variable].remove_values(values[:end])
-            dom[variable].remove_values(values[start:])
-            modified.append(dom[variable])
-        return modified
-
 class DichotomyDistributor(SplitDistributor):
     """distributes domains by splitting the smallest domain in
     two equal parts or as equal as possible"""

Modified: pypy/dist/pypy/lib/logic/computation_space/strategies.py
==============================================================================
--- pypy/dist/pypy/lib/logic/computation_space/strategies.py	(original)
+++ pypy/dist/pypy/lib/logic/computation_space/strategies.py	Thu Feb 23 12:19:28 2006
@@ -3,7 +3,7 @@
 class StrategyDistributionMismatch(Exception):
     pass
 
-def dfs_one_solution(problem):
+def dfs_one(problem):
     """depth-first single-solution search
        assumes the space default distributor is
        dichotomic"""
@@ -27,13 +27,136 @@
         else:
             raise StrategyDistributionMismatch()
                                                
-    print 1
     space = csp.ComputationSpace(problem)
-    print 2
     solved_space = do_dfs(space)
     if solved_space == None: return None
     return solved_space.merge()
 
 
 
+#-- solve_all
 
+def solve_all(problem):
+
+    solutions = []
+    sp_stack = []
+
+    sp_stack.append(csp.ComputationSpace(problem))
+
+    while 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.Alternatives(2):
+            print ' '*len(sp_stack), "branches ..."
+            sp1 = space.clone()
+            sp1.commit(1)
+            sp_stack.append(sp1)
+            sp2 = space.clone()
+            sp2.commit(2)
+            sp_stack.append(sp2)
+
+    return [sp.merge() for sp in solutions]
+    
+## declare
+
+## % This version of SolveAll will do a depth-first or breadth-first 
+## % traversal of the search space depending on the WhichFirst parameter.
+## fun {SolveAll WhichFirst Script} 
+##    {TouchAll {Solve WhichFirst Script}} 
+## end
+
+## fun {TouchAll L}
+##    case L of
+##       nil then skip
+##    [] _ | Rest then {TouchAll Rest _}
+##    end
+##    L
+## end
+
+## % Returns a lazy list of solutions for Script.
+## % The list is ordered depth-first or breadth-first
+## % depending on the WhichFirst parameter.
+## % The allowable values are depth and breadth.
+## fun {Solve WhichFirst Script}
+
+## % All the subsidiary function are declared within Solve so
+## % that we won't have to pass WhichFirst around.
+## % The body of Solve is at the bottom.
+
+##    % Each of the elements in Ss is either a Space or a 
+##    % commitTo(<Space> <Int>) record. A commitTo(<Space> <Int>) 
+##    % record identifies a Space that is ready to commit to
+##    % the Ith choice at a choice point.
+##    % Returns all solutions using either depth-first or
+##    % breadth-first depending on the value of WhichFirst.
+##    fun lazy {SolveSpaces Ss}
+##       case Ss of
+##          nil then nil
+##       [] S | SRest then
+##          % S is either a Space or a commitTo(<Space> <Int>) record.
+##          case S of
+## 	     commitTo(S I) then
+## 	     Clone = {Space.clone S} 
+##          in
+## 	     % Start the Ith branch in the clone of S.
+## 	     {Space.commit Clone I}
+## 	     {SolveSpaces Clone|SRest}
+##          else % S is a Space.
+##              {SolveSpace {Space.ask S} S SRest}
+##          end
+##       end
+##    end
+
+##    % Deal with Space S, which is in state SpaceState
+##    fun {SolveSpace SpaceState S SRest}
+##       case SpaceState of
+##          failed then {SolveSpaces SRest}  
+##       [] succeeded then {Space.merge S}|{SolveSpaces SRest}  
+##       [] alternatives(N) then
+##          {SolveSpaces {NewSpaceList {Choices S N} SRest}}
+##       end
+##    end
+
+##    % The choices are commitTo(<Space> <Int>) records. They
+##    % keep track of the branch to which to commit.
+##    fun {Choices S N}
+##       {List.mapInd
+##        {List.make N} % Generates N elements for Map to use.
+##        % Keep track of which branch to commit to.
+##        fun {$ I _} commitTo(S I) end}
+##    end
+
+##    % Put the Choices at the front or back of the existing list
+##    % of pending Spaces depending on WhichFirst.  For efficiency 
+##    % the lists could be replaced with difference lists.
+##    fun {NewSpaceList Choices Ss}
+##       % This is the only place where WhichFirst matters.
+##       % In depth-first search, the list of pending spaces is a stack.
+##       if WhichFirst == depth then {Append Choices Ss}
+##       % In breadth-first search, the list of pending spaces is a queue.
+##       elseif WhichFirst == breadth then {Append Ss Choices}
+##       else {Raise traversalSpecificationError(WhichFirst)} nil
+##       end
+##    end
+
+## in
+## % The body of Solve
+##    {SolveSpaces [{Space.new Script}]} 
+## end
+
+## % ==============================================================
+## % Example to illustrate depth-first vs. breadth-first
+## fun {ABC} choice a [] b [] c end end
+
+## % The "problem" looks at all lists of length =< 3.
+## % The "Show" documents the order in which the lists
+## % are generated.
+## fun {Problem List}
+##    if {Length List} > 3 then fail end
+##    {Show List}
+##    {Problem {Append List [{ABC}]}}
+## end

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 Feb 23 12:19:28 2006
@@ -497,32 +497,27 @@
         spc = newspace(problems.satisfiable_problem)
         assert spc.ask() == space.Alternatives(2)
 
-##     def test_old_distribute(self):
-##         spc = newspace(problems.satisfiable_problem)
-##         new_domains = [tuple(d.items()) for d in
-##                        spc.distributor.distribute()]
-##         x, y, z = (spc.get_var_by_name('x'),
-##                    spc.get_var_by_name('y'),
-##                    spc.get_var_by_name('z'))
-##         expected_domains = [tuple({x: c.FiniteDomain([6]),
-##                              y: c.FiniteDomain([2]),
-##                              z: c.FiniteDomain([4]),
-##                              w: c.FiniteDomain([5])}.items()),
-##                             tuple({x: c.FiniteDomain([6]),
-##                              y: c.FiniteDomain([2]),
-##                              z: c.FiniteDomain([4]),
-##                              w: c.FiniteDomain([6, 7])}.items())]
-##         print new_domains, expected_domains
-##         assert len(new_domains) == len(expected_domains)
-##         for (d1, d2) in zip(new_domains, expected_domains):
-##             assert len(d1) == len(d2)
-##             for (e1, e2) in zip(d1, d2):
-##                 assert e1 == e2
-
     def test_clone_and_process(self):
         spc = newspace(problems.satisfiable_problem)
         assert spc.ask() == space.Alternatives(2)
         new_spc = spc.clone()
+        assert new_spc.parent == spc
+        assert new_spc.vars == spc.vars
+        assert new_spc.names == spc.names
+        assert new_spc.root == spc.root
+        assert new_spc.constraints == spc.constraints
+        assert new_spc.distributor != spc.distributor
+        it1 = [item for item in spc.doms.items()
+               if item[1] != space.NoDom]
+        it2 = [item for item in new_spc.doms.items()
+               if item[1] != space.NoDom]
+        it1.sort()
+        it2.sort()
+        for (v1, d1), (v2, d2) in zip (it1, it2):
+            assert v1 == v2
+            assert d1 == d2
+            assert id(v1) == id(v2)
+            assert id(d1) != id(d2)
         # following couple of ops superceeded by inject()
         x, y, z = new_spc.find_vars('x', 'y', 'z')
         new_spc.add_constraint([x], 'x == 0')
@@ -580,24 +575,24 @@
         assert (x, y, z) == nspc.root.val
 
     def test_scheduling_problem_dfs_one_solution(self):
-        sol = strategies.dfs_one_solution(problems.conference_scheduling)
+        sol = strategies.dfs_one(problems.conference_scheduling)
 
         sol2 = [var.val for var in sol]
-        print sol2
-        assert sol2 == [('room A', 'day 1 AM'),
-                        ('room B', 'day 2 AM'),
-                        ('room C', 'day 2 PM'),
+        assert sol2 == [('room A', 'day 1 PM'),
+                        ('room B', 'day 2 PM'),
                         ('room C', 'day 2 AM'),
-                        ('room C', 'day 1 PM'),
+                        ('room C', 'day 2 PM'),
                         ('room C', 'day 1 AM'),
-                        ('room A', 'day 2 AM'),
-                        ('room B', 'day 1 PM'),
+                        ('room C', 'day 1 PM'),
                         ('room A', 'day 2 PM'),
-                        ('room A', 'day 1 PM')]
+                        ('room B', 'day 1 AM'),
+                        ('room A', 'day 2 AM'),
+                        ('room A', 'day 1 AM')]
+
 
 
-    def test_scheduling_problem_dfs_all_solutions(self):
-        sols = strategies.dfs_all_solutions(problems.conference_scheduling)
+    def test_scheduling_problem_all_solutions(self):
+        sols = strategies.solve_all(problems.conference_scheduling)
         assert len(sols) == 64
 
         



More information about the Pypy-commit mailing list