[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