[pypy-svn] r39042 - in pypy/dist/pypy: lib/cslib lib/cslib/doc lib/cslib/examples lib/cslib/test module/_cslib rlib/cslib
auc at codespeak.net
auc at codespeak.net
Fri Feb 16 19:33:38 CET 2007
Author: auc
Date: Fri Feb 16 19:33:37 2007
New Revision: 39042
Added:
pypy/dist/pypy/lib/cslib/
pypy/dist/pypy/lib/cslib/__init__.py (contents, props changed)
pypy/dist/pypy/lib/cslib/app_distributors.py (contents, props changed)
pypy/dist/pypy/lib/cslib/app_fd.py (contents, props changed)
pypy/dist/pypy/lib/cslib/app_propagation.py (contents, props changed)
pypy/dist/pypy/lib/cslib/distributors.py (contents, props changed)
pypy/dist/pypy/lib/cslib/doc/
pypy/dist/pypy/lib/cslib/examples/
pypy/dist/pypy/lib/cslib/fd.py (contents, props changed)
pypy/dist/pypy/lib/cslib/fi.py (contents, props changed)
pypy/dist/pypy/lib/cslib/propagation.py (contents, props changed)
pypy/dist/pypy/lib/cslib/test/
pypy/dist/pypy/module/_cslib/
pypy/dist/pypy/module/_cslib/__init__.py (contents, props changed)
pypy/dist/pypy/module/_cslib/constraint.py (contents, props changed)
pypy/dist/pypy/module/_cslib/fd.py (contents, props changed)
pypy/dist/pypy/module/_cslib/propagation.py (contents, props changed)
pypy/dist/pypy/rlib/cslib/
pypy/dist/pypy/rlib/cslib/__init__.py (contents, props changed)
pypy/dist/pypy/rlib/cslib/btree.py (contents, props changed)
pypy/dist/pypy/rlib/cslib/rconstraint.py (contents, props changed)
pypy/dist/pypy/rlib/cslib/rdistributor.py (contents, props changed)
pypy/dist/pypy/rlib/cslib/rdomain.py (contents, props changed)
pypy/dist/pypy/rlib/cslib/rpropagation.py (contents, props changed)
Log:
(alf, auc, ludal) provide constraint solver with logilab.constraint interface,
apropriately stuffed into lib, module, rlib
Added: pypy/dist/pypy/lib/cslib/__init__.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/lib/cslib/__init__.py Fri Feb 16 19:33:37 2007
@@ -0,0 +1,7 @@
+"Constraint Solver in Python."
+
+from propagation import Repository, Solver
+#from distributors import DefaultDistributor
+import fd
+#import fi
+__all__ = ['Repository', 'Solver', 'fd']
Added: pypy/dist/pypy/lib/cslib/app_distributors.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/lib/cslib/app_distributors.py Fri Feb 16 19:33:37 2007
@@ -0,0 +1,154 @@
+"""
+distributors - part of constraint satisfaction solver.
+"""
+
+import math, random
+
+def make_new_domains(domains):
+ """return a shallow copy of dict of domains passed in argument"""
+ domain = {}
+ for key, value in domains.items():
+ domain[key] = value.copy()
+ return domain
+
+class AbstractDistributor(object):
+ """Implements DistributorInterface but abstract because
+ _distribute is left unimplemented."""
+
+ def __init__(self, nb_subspaces=2):
+ self.nb_subspaces = nb_subspaces
+ self.verbose = 0
+
+ def findSmallestDomain(self, domains):
+ """returns the variable having the smallest domain.
+ (or one of such varibles if there is a tie)
+ """
+ domlist = [(dom.size(), variable ) for variable, dom in domains.items()
+ if dom.size() > 1]
+ domlist.sort()
+ return domlist[0][1]
+
+ def findLargestDomain(self, domains):
+ """returns the variable having the largest domain.
+ (or one of such variables if there is a tie)
+ """
+ domlist = [(dom.size(), variable) for variable, dom in domains.items()
+ if dom.size() > 1]
+ domlist.sort()
+ return domlist[-1][1]
+
+ def nb_subdomains(self, domains):
+ """return number of sub domains to explore"""
+ return self.nb_subspaces
+
+ def distribute(self, domains, verbose=0):
+ """do the minimal job and let concrete class distribute variables
+ """
+ self.verbose = verbose
+ replicas = []
+ for i in range(self.nb_subdomains(domains)):
+ replicas.append(make_new_domains(domains))
+ modified_domains = self._distribute(*replicas)
+ for domain in modified_domains:
+ domain.clear_change()
+ return replicas
+
+ def _distribute(self, *args):
+ """ method to implement in concrete class
+
+ take self.nb_subspaces copy of the original domains as argument
+ distribute the domains and return each modified domain
+ """
+ raise NotImplementedError("Use a concrete implementation of "
+ "the Distributor interface")
+
+class NaiveDistributor(AbstractDistributor):
+ """distributes domains by splitting the smallest domain in 2 new domains
+ The first new domain has a size of one,
+ and the second has all the other values"""
+
+ def __init__(self):
+ AbstractDistributor.__init__(self)
+
+ def _distribute(self, dom1, dom2):
+ """See AbstractDistributor"""
+ variable = self.findSmallestDomain(dom1)
+ values = dom1[variable].get_values()
+ if self.verbose:
+ print 'Distributing domain for variable', variable, \
+ 'at value', values[0]
+ dom1[variable].remove_values(values[1:])
+ dom2[variable].removeValue(values[0])
+ return (dom1[variable], dom2[variable])
+
+
+class RandomizingDistributor(AbstractDistributor):
+ """distributes domains as the NaiveDistrutor, except that the unique
+ value of the first domain is picked at random."""
+
+ def __init__(self):
+ AbstractDistributor.__init__(self)
+
+ def _distribute(self, dom1, dom2):
+ """See AbstractDistributor"""
+ variable = self.findSmallestDomain(dom1)
+ values = dom1[variable].get_values()
+ distval = random.choice(values)
+ values.remove(distval)
+ if self.verbose:
+ print 'Distributing domain for variable', variable, \
+ 'at value', distval
+ dom1[variable].remove_values(values)
+ dom2[variable].removeValue(distval)
+ return (dom1[variable], dom2[variable])
+
+
+class SplitDistributor(AbstractDistributor):
+ """distributes domains by splitting the smallest domain in
+ nb_subspaces equal parts or as equal as possible.
+ If nb_subspaces is 0, then the smallest domain is split in
+ domains of size 1"""
+
+ def __init__(self, nb_subspaces=3):
+ AbstractDistributor.__init__(self, nb_subspaces)
+ self.__to_split = None
+ def nb_subdomains(self, domains):
+ """See AbstractDistributor"""
+ self.__to_split = self.findSmallestDomain(domains)
+ if self.nb_subspaces:
+ return min(self.nb_subspaces, domains[self.__to_split].size())
+ else:
+ return domains[self.__to_split].size()
+
+ 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"""
+ def __init__(self):
+ SplitDistributor.__init__(self, 2)
+
+
+class EnumeratorDistributor(SplitDistributor):
+ """distributes domains by splitting the smallest domain
+ in domains of size 1."""
+ def __init__(self):
+ SplitDistributor.__init__(self, 0)
+
+DefaultDistributor = DichotomyDistributor
Added: pypy/dist/pypy/lib/cslib/app_fd.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/lib/cslib/app_fd.py Fri Feb 16 19:33:37 2007
@@ -0,0 +1,351 @@
+"""Tools to work with finite domain variables and constraints
+
+This module provides the following usable classes:
+ * FiniteDomain: a class for storing FiniteDomains
+ * Expression: a constraint represented as an expression
+ * BinaryExpression: a binary constraint represented as an expression
+ * various BasicConstraint classes
+
+The Expression and BinaryExpression classes can be constructed using the
+make_expression factory function. """
+
+import operator
+
+from propagation import AbstractDomain, BasicConstraint, \
+ ConsistencyFailure, AbstractConstraint
+
+
+class FiniteDomain(AbstractDomain):
+ """
+ Variable Domain with a finite set of possible values
+ """
+
+ _copy_count = 0
+ _write_count = 0
+
+ def __init__(self, values):
+ """values is a list of values in the domain
+ This class uses a dictionnary to make sure that there are
+ no duplicate values"""
+ AbstractDomain.__init__(self)
+ if isinstance(values, FiniteDomain):
+ # do a copy on write
+ self._cow = True
+ values._cow = True
+ FiniteDomain._copy_count += 1
+ self._values = values._values
+ else:
+ assert len(values) > 0
+ self.setValues(values)
+
+ ##self.getValues = self._values.keys
+
+ def setValues(self, values):
+ self._cow = False
+ FiniteDomain._write_count += 1
+ self._values = {}
+ for val in values:
+ self._values[val] = 0
+
+ def removeValue(self, value):
+ """Remove value of domain and check for consistency"""
+## print "removing", value, "from", self._values.keys()
+ if self._cow:
+ self.setValues(self._values)
+ del self._values[value]
+ self._valueRemoved()
+
+ def removeValues(self, values):
+ """Remove values of domain and check for consistency"""
+ if self._cow:
+ self.setValues(self._values)
+ if values:
+## print "removing", values, "from", self._values.keys()
+ for val in values :
+ del self._values[val]
+ self._valueRemoved()
+ __delitem__ = removeValue
+
+ def size(self):
+ """computes the size of a finite domain"""
+ return len(self._values)
+ __len__ = size
+
+ def getValues(self):
+ """return all the values in the domain"""
+ return self._values.keys()
+
+ def __iter__(self):
+ return iter(self._values)
+
+ def copy(self):
+ """clone the domain"""
+ return FiniteDomain(self)
+
+ def __repr__(self):
+ return '<FiniteDomain %s>' % str(self.getValues())
+
+##
+## Constraints
+##
+class AllDistinct(AbstractConstraint):
+ """Contraint: all values must be distinct"""
+
+ def __init__(self, variables):
+ assert len(variables)>1
+ AbstractConstraint.__init__(self, variables)
+ # worst case complexity
+ self.__cost = len(variables) * (len(variables) - 1) / 2
+
+ def __repr__(self):
+ return '<AllDistinct %s>' % str(self._variables)
+
+ def estimateCost(self, domains):
+ """return cost"""
+ return self.__cost
+
+ def narrow(self, domains):
+ """narrowing algorithm for the constraint"""
+ variables = [(domains[variable].size(), variable, domains[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:
+ for _siz, _var, _dom in variables:
+ if _var != var:
+ try:
+ _dom.removeValue(dom.getValues()[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):
+ 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
+ return 1
+
+
+
+
+class Expression(AbstractConstraint):
+ """A constraint represented as a python expression."""
+ _FILTER_CACHE = {}
+
+ def __init__(self, variables, formula, type='fd.Expression'):
+ """variables is a list of variables which appear in the formula
+ formula is a python expression that will be evaluated as a boolean"""
+ AbstractConstraint.__init__(self, variables)
+ self.formula = formula
+ self.type = type
+ try:
+ self.filterFunc = Expression._FILTER_CACHE[formula]
+ except KeyError:
+ self.filterFunc = eval('lambda %s: %s' % \
+ (','.join(variables), formula), {}, {})
+ Expression._FILTER_CACHE[formula] = self.filterFunc
+
+ def _init_result_cache(self):
+ """key = (variable,value), value = [has_success,has_failure]"""
+ result_cache = {}
+ for var_name in self._variables:
+ result_cache[var_name] = {}
+ return result_cache
+
+
+ def _assign_values(self, domains):
+ variables = []
+ kwargs = {}
+ for variable in self._variables:
+ domain = domains[variable]
+ values = domain.get_values()
+ variables.append((domain.size(), [variable, values, 0, len(values)]))
+ kwargs[variable] = values[0]
+ # sort variables to instanciate those with fewer possible values first
+ variables.sort()
+
+ go_on = 1
+ while go_on:
+ yield kwargs
+ # try to instanciate the next variable
+ for size, curr in variables:
+ if (curr[2] + 1) < curr[-1]:
+ curr[2] += 1
+ kwargs[curr[0]] = curr[1][curr[2]]
+ break
+ else:
+ curr[2] = 0
+ kwargs[curr[0]] = curr[1][0]
+ else:
+ # it's over
+ go_on = 0
+
+
+ def narrow(self, domains):
+ """generic narrowing algorithm for n-ary expressions"""
+ maybe_entailed = 1
+ ffunc = self.filterFunc
+ result_cache = self._init_result_cache()
+ for kwargs in self._assign_values(domains):
+ if maybe_entailed:
+ for var, val in kwargs.iteritems():
+ if val not in result_cache[var]:
+ break
+ else:
+ continue
+ if ffunc(**kwargs):
+ for var, val in kwargs.items():
+ result_cache[var][val] = 1
+ else:
+ maybe_entailed = 0
+
+ try:
+ for var, keep in result_cache.iteritems():
+ domain = domains[var]
+ domain.remove_values([val for val in domain.get_values()
+ if val not in keep])
+ except ConsistencyFailure:
+ raise ConsistencyFailure('Inconsistency while applying %s' % \
+ repr(self))
+ except KeyError:
+ # There are no more value in result_cache
+ pass
+
+ return maybe_entailed
+
+ def __repr__(self):
+ return '<%s "%s">' % (self.type, self.formula)
+
+class BinaryExpression(Expression):
+ """A binary constraint represented as a python expression
+
+ This implementation uses a narrowing algorithm optimized for
+ binary constraints."""
+
+ def __init__(self, variables, formula, type = 'fd.BinaryExpression'):
+ assert len(variables) == 2
+ Expression.__init__(self, variables, formula, type)
+
+ def narrow(self, domains):
+ """specialized narrowing algorithm for binary expressions
+ Runs much faster than the generic version"""
+ maybe_entailed = 1
+ var1 = self._variables[0]
+ dom1 = domains[var1]
+ values1 = dom1.get_values()
+ var2 = self._variables[1]
+ dom2 = domains[var2]
+ values2 = dom2.get_values()
+ ffunc = self.filterFunc
+ if dom2.size() < dom1.size():
+ var1, var2 = var2, var1
+ dom1, dom2 = dom2, dom1
+ values1, values2 = values2, values1
+
+ kwargs = {}
+ keep1 = {}
+ keep2 = {}
+ maybe_entailed = 1
+ try:
+ # iterate for all values
+ for val1 in values1:
+ kwargs[var1] = val1
+ for val2 in values2:
+ kwargs[var2] = val2
+ if val1 in keep1 and val2 in keep2 and maybe_entailed == 0:
+ continue
+ if ffunc(**kwargs):
+ keep1[val1] = 1
+ keep2[val2] = 1
+ else:
+ maybe_entailed = 0
+
+ dom1.remove_values([val for val in values1 if val not in keep1])
+ dom2.remove_values([val for val in values2 if val not in keep2])
+
+ except ConsistencyFailure:
+ raise ConsistencyFailure('Inconsistency while applying %s' % \
+ repr(self))
+ except Exception:
+ print self, kwargs
+ raise
+ return maybe_entailed
+
+
+def make_expression(variables, formula, constraint_type=None):
+ """create a new constraint of type Expression or BinaryExpression
+ The chosen class depends on the number of variables in the constraint"""
+ # encode unicode
+ vars = []
+ for var in variables:
+ if type(var) == type(u''):
+ vars.append(var.encode())
+ else:
+ vars.append(var)
+ if len(vars) == 2:
+ if constraint_type is not None:
+ return BinaryExpression(vars, formula, constraint_type)
+ else:
+ return BinaryExpression(vars, formula)
+
+ else:
+ if constraint_type is not None:
+ return Expression(vars, formula, constraint_type)
+ else:
+ return Expression(vars, formula)
+
+
+class Equals(BasicConstraint):
+ """A basic constraint variable == constant value"""
+ def __init__(self, variable, reference):
+ BasicConstraint.__init__(self, variable, reference, operator.eq)
+
+class NotEquals(BasicConstraint):
+ """A basic constraint variable != constant value"""
+ def __init__(self, variable, reference):
+ BasicConstraint.__init__(self, variable, reference, operator.ne)
+
+class LesserThan(BasicConstraint):
+ """A basic constraint variable < constant value"""
+ def __init__(self, variable, reference):
+ BasicConstraint.__init__(self, variable, reference, operator.lt)
+
+class LesserOrEqual(BasicConstraint):
+ """A basic constraint variable <= constant value"""
+ def __init__(self, variable, reference):
+ BasicConstraint.__init__(self, variable, reference, operator.le)
+
+class GreaterThan(BasicConstraint):
+ """A basic constraint variable > constant value"""
+ def __init__(self, variable, reference):
+ BasicConstraint.__init__(self, variable, reference, operator.gt)
+
+class GreaterOrEqual(BasicConstraint):
+ """A basic constraint variable >= constant value"""
+ def __init__(self, variable, reference):
+ BasicConstraint.__init__(self, variable, reference, operator.ge)
+
+def _in(v, set):
+ """test presence of v in set"""
+ return v in set
+
+class InSet(BasicConstraint):
+ """A basic contraint variable in set value"""
+ def __init__(self, variable, set):
+ BasicConstraint.__init__(self, variable, set, _in )
+
+
+
+
Added: pypy/dist/pypy/lib/cslib/app_propagation.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/lib/cslib/app_propagation.py Fri Feb 16 19:33:37 2007
@@ -0,0 +1,283 @@
+"""The code of the constraint propagation algorithms"""
+
+import operator
+#from time import strftime
+
+class ConsistencyFailure(Exception):
+ """The repository is not in a consistent state"""
+ pass
+
+class Repository(object):
+ """Stores variables, domains and constraints
+ Propagates domain changes to constraints
+ Manages the constraint evaluation queue"""
+
+ def __init__(self, variables, domains, constraints = None):
+ # encode unicode
+ for i, var in enumerate(variables):
+ if type(var) == type(u''):
+ variables[i] = var.encode()
+
+ self._variables = variables # list of variable names
+ self._domains = domains # maps variable name to domain object
+ self._constraints = [] # list of constraint objects
+# self._queue = [] # queue of constraints waiting to be processed
+ self._variableListeners = {}
+ for var in self._variables:
+ self._variableListeners[var] = []
+ assert self._domains.has_key(var)
+ for constr in constraints or ():
+ self.addConstraint(constr)
+
+ def __repr__(self):
+ return '<Repository nb_constraints=%d domains=%s>' % \
+ (len(self._constraints), self._domains)
+
+ def addConstraint(self, constraint):
+ if isinstance(constraint, BasicConstraint):
+ # Basic constraints are processed just once
+ # because they are straight away entailed
+ var = constraint.getVariable()
+ constraint.narrow({var: self._domains[var]})
+ else:
+ self._constraints.append(constraint)
+ for var in constraint.affectedVariables():
+ self._variableListeners[var].append(constraint)
+
+ def _removeConstraint(self, constraint):
+ self._constraints.remove(constraint)
+ for var in constraint.affectedVariables():
+ try:
+ self._variableListeners[var].remove(constraint)
+ except ValueError:
+ raise ValueError('Error removing constraint from listener',
+ var,
+ self._variableListeners[var],
+ constraint)
+
+ def getDomains(self):
+ return self._domains
+
+ def distribute(self, distributor, verbose=0):
+ """Create new repository using the distributor and self """
+ for domains in distributor.distribute(self._domains, verbose):
+ yield Repository(self._variables, domains, self._constraints)
+
+ def revise(self, verbose=0):
+ """Prunes the domains of the variables
+ This method calls constraint.narrow() and queues constraints
+ that are affected by recent changes in the domains.
+ Returns True if a solution was found"""
+ if verbose:
+ print '** Consistency **'
+
+ _queue = [ (constr.estimateCost(self._domains),
+ constr) for constr in self._constraints ]
+ _queue.sort()
+ _affected_constraints = {}
+ while True:
+ if not _queue:
+ # refill the queue if some constraints have been affected
+ _queue = [(constr.estimateCost(self._domains),
+ constr) for constr in _affected_constraints]
+ if not _queue:
+ break
+ _queue.sort()
+ _affected_constraints.clear()
+ if verbose > 2:
+ print 'Queue', _queue
+ cost, constraint = _queue.pop(0)
+ if verbose > 1:
+ print 'Trying to entail constraint',
+ print constraint, '[cost:%d]' % cost
+ entailed = constraint.narrow(self._domains)
+ for var in constraint.affectedVariables():
+ # affected constraints are listeners of
+ # affected variables of this constraint
+ dom = self._domains[var]
+ if not dom.has_changed():
+ continue
+ if verbose > 1 :
+ print ' -> New domain for variable', var, 'is', dom
+ for constr in self._variableListeners[var]:
+ if constr is not constraint:
+ _affected_constraints[constr] = True
+ dom.clear_change()
+ if entailed:
+ if verbose:
+ print "--> Entailed constraint", constraint
+ self._removeConstraint(constraint)
+ if constraint in _affected_constraints:
+ del _affected_constraints[constraint]
+
+ for domain in self._domains.itervalues():
+ if domain.size() != 1:
+ return 0
+ return 1
+
+class Solver(object):
+ """Top-level object used to manage the search"""
+
+ def __init__(self, distributor=None):
+ """if no distributer given, will use the default one"""
+ if distributor is None:
+ from distributors import DefaultDistributor
+ distributor = DefaultDistributor()
+ self._distributor = distributor
+ self.verbose = 1
+ self.max_depth = 0
+
+ def solve_one(self, repository, verbose=0):
+ """Generates only one solution"""
+ self.verbose = verbose
+ self.max_depth = 0
+ try:
+ return self._solve(repository).next()
+ except StopIteration:
+ return
+
+ def solve_best(self, repository, cost_func, verbose=0):
+ """Generates solution with an improving cost"""
+ self.verbose = verbose
+ self.max_depth = 0
+ best_cost = None
+ for solution in self._solve(repository):
+ cost = cost_func(**solution)
+ if best_cost is None or cost <= best_cost:
+ best_cost = cost
+ yield solution, cost
+
+ def solve_all(self, repository, verbose=0):
+ """Generates all solutions"""
+ self.verbose = verbose
+ self.max_depth = 0
+ for solution in self._solve(repository):
+ yield solution
+
+ def solve(self, repository, verbose=0):
+ """return list of all solutions"""
+ self.max_depth = 0
+ solutions = []
+ for solution in self.solve_all(repository, verbose):
+ solutions.append(solution)
+ return solutions
+
+ def _solve(self, repository, recursion_level=0):
+ """main generator"""
+ solve = self._solve
+ verbose = self.verbose
+ if recursion_level > self.max_depth:
+ self.max_depth = recursion_level
+ if verbose:
+ print '*** [%d] Solve called with repository' % recursion_level,
+ print repository
+ try:
+ foundSolution = repository.revise(verbose)
+ except ConsistencyFailure, exc:
+ if verbose:
+ print exc
+ pass
+ else:
+ if foundSolution:
+ solution = {}
+ for variable, domain in repository.getDomains().items():
+ solution[variable] = domain.get_values()[0]
+ if verbose:
+ print '### Found Solution', solution
+ print '-'*80
+ yield solution
+ else:
+ for repo in repository.distribute(self._distributor,
+ verbose):
+ for solution in solve(repo, recursion_level+1):
+ if solution is not None:
+ yield solution
+
+ if recursion_level == 0 and self.verbose:
+ print 'Finished search'
+ print 'Maximum recursion depth = ', self.max_depth
+
+
+
+
+
+
+class BasicConstraint(object):
+ """A BasicConstraint, which is never queued by the Repository
+ A BasicConstraint affects only one variable, and will be entailed
+ on the first call to narrow()"""
+
+ def __init__(self, variable, reference, operator):
+ """variables is a list of variables on which
+ the constraint is applied"""
+ self._variable = variable
+ self._reference = reference
+ self._operator = operator
+
+ def __repr__(self):
+ return '<%s %s %s>'% (self.__class__, self._variable, self._reference)
+
+ def isVariableRelevant(self, variable):
+ return variable == self._variable
+
+ def estimateCost(self, domains):
+ return 0 # get in the first place in the queue
+
+ def affectedVariables(self):
+ return [self._variable]
+
+ def getVariable(self):
+ return self._variable
+
+ def narrow(self, domains):
+ domain = domains[self._variable]
+ operator = self._operator
+ ref = self._reference
+ try:
+ for val in domain.getValues() :
+ if not operator(val, ref) :
+ domain.removeValue(val)
+ except ConsistencyFailure:
+ raise ConsistencyFailure('inconsistency while applying %s' % \
+ repr(self))
+ return 1
+
+
+class AbstractDomain(object):
+ """Implements the functionnality related to the changed flag.
+ Can be used as a starting point for concrete domains"""
+
+ def __init__(self):
+ self.__changed = 0
+
+ def resetFlags(self):
+ self.__changed = 0
+
+ def hasChanged(self):
+ return self.__changed
+
+ def _valueRemoved(self):
+ """The implementation of removeValue should call this method"""
+ self.__changed = 1
+ if self.size() == 0:
+ raise ConsistencyFailure()
+
+class AbstractConstraint(object):
+
+ def __init__(self, variables):
+ """variables is a list of variables which appear in the formula"""
+ self._variables = variables
+
+ def affectedVariables(self):
+ """ Return a list of all variables affected by this constraint """
+ return self._variables
+
+ def isVariableRelevant(self, variable):
+ return variable in self._variables
+
+ def estimateCost(self, domains):
+ """Return an estimate of the cost of the narrowing of the constraint"""
+ return reduce(operator.mul,
+ [domains[var].size() for var in self._variables])
+
+
Added: pypy/dist/pypy/lib/cslib/distributors.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/lib/cslib/distributors.py Fri Feb 16 19:33:37 2007
@@ -0,0 +1,2 @@
+from _cslib import DefaultDistributor
+
Added: pypy/dist/pypy/lib/cslib/fd.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/lib/cslib/fd.py Fri Feb 16 19:33:37 2007
@@ -0,0 +1,7 @@
+from _cslib import FiniteDomain, _make_expression, AllDistinct
+
+
+def make_expression(variables, formula):
+ func = 'lambda %s:%s' % (','.join(variables),
+ formula)
+ return _make_expression(variables, formula, eval(func))
Added: pypy/dist/pypy/lib/cslib/fi.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/lib/cslib/fi.py Fri Feb 16 19:33:37 2007
@@ -0,0 +1,326 @@
+"""Tools to work with finite interval domain interval and constraints
+"""
+
+from propagation import AbstractDomain, BasicConstraint, \
+ ConsistencyFailure, AbstractConstraint
+
+from distributors import AbstractDistributor
+
+class Interval:
+ """representation of an interval
+ This class is used to give back results from a FiniteIntervalDomain
+ """
+ def __init__(self, start, end):
+ self._start = start
+ self._end = end
+
+ def __repr__(self):
+ return "<Interval [%.2f %.2f[>" % (self._start, self._end)
+
+ def __eq__(self, other):
+ return self._start == other._start and \
+ self._end == other._end
+
+class FiniteIntervalDomain(AbstractDomain):
+ """
+ Domain for a variable with interval values.
+ """
+
+ def __init__(self, lowestMin, highestMax,
+ min_length, max_length=None, resolution=1):
+ """
+ lowestMin is the lowest value of a low boundary for a variable (inclusive).
+ highestMax is the highest value of a high boundary for a variable (exclusive).
+ min_length is the minimum width of the interval.
+ max_length is the maximum width of the interval.
+ Use None to have max = min.
+ resolution is the precision to use for constraint satisfaction. Defaults to 1
+ """
+ assert highestMax >= lowestMin
+ if max_length is None:
+ max_length = min_length
+ assert 0 <= min_length <= max_length
+ assert min_length <= highestMax - lowestMin
+ assert resolution > 0
+ AbstractDomain.__init__(self)
+ self.lowestMin = lowestMin
+ self.highestMax = highestMax
+ self._min_length = min_length
+ max_length = min(max_length, highestMax - lowestMin)
+ self._max_length = max_length
+ self._resolution = resolution
+
+ def __eq__(self, other):
+
+ return self.lowestMin == other.lowestMin and \
+ self.highestMax == other.highestMax and \
+ self._min_length == other._min_length and \
+ self._max_length == other._max_length and \
+ self._resolution == other._resolution
+
+ def getValues(self):
+ return list(self.iter_values())
+
+ def iter_values(self):
+ length = self._min_length
+ while length <= self._max_length:
+ start = self.lowestMin
+ while start + length <= self.highestMax:
+ yield Interval(start, start+length)
+ start += self._resolution
+ length += self._resolution
+
+
+ def size(self):
+ """computes the size of a finite interval"""
+ size = 0
+ length = self._min_length
+ while length <= self._max_length :
+ size += ((self.highestMax - length) - self.lowestMin) / self._resolution + 1
+ length += self._resolution
+ return size
+
+ def _highestMin(self):
+ return self.highestMax - self._min_length
+
+ def _lowestMax(self):
+ return self.lowestMin + self._min_length
+
+ lowestMax = property(_lowestMax, None, None, "")
+
+ highestMin = property(_highestMin, None, None, "")
+
+ def copy(self):
+ """clone the domain"""
+ return FiniteIntervalDomain(self.lowestMin, self.highestMax,
+ self._min_length, self._max_length,
+ self._resolution)
+
+ def setLowestMin(self, new_lowestMin):
+ self.lowestMin = new_lowestMin
+ self._valueRemoved()
+
+ def setHighestMax(self, new_highestMax):
+ self.highestMax = new_highestMax
+ self._valueRemoved()
+
+ def setMinLength(self, new_min):
+ self._min_length = new_min
+ self._valueRemoved()
+
+ def setMaxLength(self, new_max):
+ self._max_length = new_max
+ self._valueRemoved()
+
+ def overlap(self, other):
+ return other.highestMax > self.lowestMin and \
+ other.lowestMin < self.highestMax
+
+ def no_overlap_impossible(self, other):
+ return self.lowestMax > other.highestMin and \
+ other.lowestMax > self.highestMin
+
+ def hasSingleLength(self):
+ return self._max_length == self._min_length
+
+ def _valueRemoved(self):
+ if self.lowestMin >= self.highestMax:
+ raise ConsistencyFailure("earliest start [%.2f] higher than latest end [%.2f]" %
+ (self.lowestMin, self.highestMax))
+ if self._min_length > self._max_length:
+ raise ConsistencyFailure("min length [%.2f] greater than max length [%.2f]" %
+ (self._min_length, self._max_length))
+
+ self._max_length = min(self._max_length, self.highestMax - self.lowestMin)
+
+ if self.size() == 0:
+ raise ConsistencyFailure('size is 0')
+
+
+
+ def __repr__(self):
+ return '<FiniteIntervalDomain from [%.2f, %.2f[ to [%.2f, %.2f[>' % (self.lowestMin,
+ self.lowestMax,
+ self.highestMin,
+ self.highestMax)
+
+##
+## Distributors
+##
+
+class FiniteIntervalDistributor(AbstractDistributor):
+ """Distributes a set of FiniteIntervalDomain
+ The distribution strategy is the following:
+ - the smallest domain of size > 1 is picked
+ - if its max_length is greater than its min_length, a subdomain if size
+ min_length is distributed, with the same boundaries
+ - otherwise, a subdomain [lowestMin, lowestMax[ is distributed
+ """
+ def __init__(self):
+ AbstractDistributor.__init__(self)
+
+ def _split_values(self, copy1, copy2):
+ if copy1.hasSingleLength():
+ copy1.highestMax = copy1.lowestMin + copy1._min_length
+ copy2.lowestMin += copy2._resolution
+ else:
+ copy1._max_length = copy1._min_length
+ copy2._min_length += copy2._resolution
+
+
+ def _distribute(self, dom1, dom2):
+ variable = self.findSmallestDomain(dom1)
+ if self.verbose:
+ print 'Distributing domain for variable', variable
+ splitted = dom1[variable]
+ cpy1 = splitted.copy()
+ cpy2 = splitted.copy()
+
+ self._split_values(cpy1, cpy2)
+
+ dom1[variable] = cpy1
+ dom2[variable] = cpy2
+
+ return cpy1, cpy2
+
+
+
+##
+## Constraints
+##
+
+class AbstractFIConstraint(AbstractConstraint):
+ def __init__(self, var1, var2):
+ AbstractConstraint.__init__(self, (var1, var2))
+
+ def estimateCost(self, domains):
+ return 1
+
+ def __repr__(self):
+ return '<%s %s>' % (self.__class__.__name__, str(self._variables))
+
+ def __eq__(self, other):
+ return repr(self) == repr(other)
+
+ def __hash__(self):
+ # FIXME: to be able to add constraints in Sets (and compare them)
+ # FIXME: improve implementation
+ variables = tuple(sorted(self._variables))
+ return hash((self.__class__.__name__, variables))
+
+ def narrow(self, domains):
+ """narrowing algorithm for the constraint"""
+ dom1 = domains[self._variables[0]]
+ dom2 = domains[self._variables[1]]
+
+ return self._doNarrow(dom1, dom2)
+
+ def _doNarrow(self, dom1, dom2):
+ """virtual method which does the real work"""
+ raise NotImplementedError
+
+
+
+# FIXME: deal with more than 2 domains at once ?
+class NoOverlap(AbstractFIConstraint):
+
+ def __eq__(self, other):
+ return isinstance(other, NoOverlap) and \
+ set(self._variables) == set(other._variables)
+
+ def _doNarrow(self, dom1, dom2):
+ if not dom1.overlap(dom2):
+ return 1
+ elif dom1.no_overlap_impossible(dom2) :
+ raise ConsistencyFailure
+ elif dom1.lowestMax == dom2.highestMin and dom2.lowestMax > dom1.highestMin :
+ dom1.setHighestMax(dom2.highestMin)
+ dom2.setLowestMin(dom1.lowestMax)
+ return 1
+ elif dom1.lowestMax > dom2.highestMin and dom2.lowestMax == dom1.highestMin :
+ dom2.setHighestMax(dom1.highestMin)
+ dom1.setLowestMin(dom2.lowestMax)
+ return 1
+ return 0
+
+class StartsBeforeStart(AbstractFIConstraint):
+
+ def _doNarrow(self, dom1, dom2):
+
+ if dom1.lowestMin > dom2.highestMin:
+ raise ConsistencyFailure
+ if dom1.highestMin < dom2.lowestMin:
+ return 1
+ return 0
+
+class StartsBeforeEnd(AbstractFIConstraint):
+
+ def _doNarrow(self, dom1, dom2):
+ if dom1.lowestMin > dom2.highestMax:
+ raise ConsistencyFailure
+ if dom1.highestMin < dom2.lowestMax:
+ return 1
+ return 0
+
+class EndsBeforeStart(AbstractFIConstraint):
+
+ def _doNarrow(self, dom1, dom2):
+ if dom1.lowestMax > dom2.highestMin:
+ raise ConsistencyFailure
+ if dom1.highestMax < dom2.lowestMin:
+ return 1
+ if dom1.highestMax > dom2.highestMin:
+ dom1.setHighestMax(dom2.highestMin)
+ return 0
+
+class EndsBeforeEnd(AbstractFIConstraint):
+
+ def _doNarrow(self, dom1, dom2):
+ if dom1.lowestMax > dom2.highestMax:
+ raise ConsistencyFailure
+ if dom1.highestMax < dom2.lowestMax:
+ return 1
+ if dom1.highestMax > dom2.highestMax:
+ dom1.setHighestMax(dom2.highestMax)
+ return 0
+
+class StartsAfterStart(AbstractFIConstraint):
+
+ def _doNarrow(self, dom1, dom2):
+ if dom1.highestMin < dom2.lowestMin:
+ raise ConsistencyFailure
+ if dom1.lowestMin > dom2.highestMin:
+ return 1
+ if dom1.lowestMin < dom2.lowestMin:
+ dom1.setLowestMin(dom2.lowestMin)
+ return 0
+
+class StartsAfterEnd(AbstractFIConstraint):
+
+ def _doNarrow(self, dom1, dom2):
+ if dom1.highestMin < dom2.lowestMax:
+ raise ConsistencyFailure
+ if dom1.lowestMin > dom2.highestMax:
+ return 1
+ if dom1.lowestMin < dom2.lowestMax:
+ dom1.setLowestMin(dom2.lowestMax)
+ return 0
+
+class EndsAfterStart(AbstractFIConstraint):
+
+ def _doNarrow(self, dom1, dom2):
+ if dom1.highestMax < dom2.lowestMin:
+ raise ConsistencyFailure
+ if dom1.lowestMax > dom2.highestMin:
+ return 1
+ return 0
+
+class EndsAfterEnd(AbstractFIConstraint):
+
+ def _doNarrow(self, dom1, dom2):
+ if dom1.highestMax < dom2.lowestMax:
+ raise ConsistencyFailure
+ if dom1.lowestMax > dom2.highestMax:
+ return 1
+ return 0
+
Added: pypy/dist/pypy/lib/cslib/propagation.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/lib/cslib/propagation.py Fri Feb 16 19:33:37 2007
@@ -0,0 +1 @@
+from _cslib import Repository, Solver
Added: pypy/dist/pypy/module/_cslib/__init__.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/module/_cslib/__init__.py Fri Feb 16 19:33:37 2007
@@ -0,0 +1,16 @@
+# Package initialisation
+from pypy.interpreter.mixedmodule import MixedModule
+
+#print '_csp module'
+
+class Module(MixedModule):
+ appleveldefs = {
+ }
+
+ interpleveldefs = {
+ 'FiniteDomain' : 'fd.make_fd',
+ 'AllDistinct' : 'constraint.make_alldistinct',
+ '_make_expression': 'constraint.interp_make_expression',
+ 'Repository' : 'propagation.make_repo',
+ 'Solver' : 'propagation.make_solver'
+ }
Added: pypy/dist/pypy/module/_cslib/constraint.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/module/_cslib/constraint.py Fri Feb 16 19:33:37 2007
@@ -0,0 +1,185 @@
+from pypy.interpreter.baseobjspace import Wrappable
+from pypy.interpreter import baseobjspace, typedef, gateway
+from pypy.interpreter.gateway import interp2app
+from pypy.interpreter.function import Function
+from pypy.interpreter.error import OperationError
+
+from pypy.objspace.std.listobject import W_ListObject
+from pypy.objspace.std.tupleobject import W_TupleObject
+from pypy.objspace.std.stringobject import W_StringObject
+from pypy.objspace.std.dictobject import W_DictObject
+
+from pypy.module._cslib.fd import _FiniteDomain
+from pypy.rlib import rconstraint as rc
+
+class W_AbstractConstraint(baseobjspace.Wrappable):
+
+ def __init__(self, space, constraint):
+ """variables is a list of variables which appear in the formula"""
+ self.space = space
+ assert isinstance( constraint, rc.AbstractConstraint )
+ self.constraint = constraint
+
+ def w_affected_variables(self):
+ """ Return a list of all variables affected by this constraint """
+ return self.space.wrap(self._variables)
+
+ def affected_variables(self):
+ return self._variables
+
+ def w_revise(self, w_domains):
+ assert isinstance(w_domains, W_DictObject)
+ doms = {}
+ spc = self.space
+ for var, dom in w_domains.content.items():
+ doms[spc.str_w(var)] = dom
+ return self.space.newbool(self.revise(doms))
+
+ def w_estimate_cost(self, w_domains):
+ assert isinstance(w_domains, W_DictObject)
+ cost = 1
+ doms = w_domains.content
+ for var in self._variables:
+ dom = doms[self.space.wrap(var)]
+ assert isinstance(dom, W_AbstractDomain)
+ cost = cost * dom.size()
+ return self.space.newint(cost)
+
+W_AbstractConstraint.typedef = typedef.TypeDef(
+ "W_AbstractConstraint")
+
+
+
+class _Expression(rc.Expression):
+ """A constraint represented as a python expression."""
+
+ def __init__(self, space, w_variables, w_formula, w_filter_func):
+ """variables is a list of variables which appear in the formula
+ formula is a python expression that will be evaluated as a boolean"""
+ self.space = space
+ variables = []
+ for w_var in space.unpackiterable( w_variables ):
+ variables.append( space.str_w(w_var) )
+ if len(variables)==0:
+ raise OperationError( space.w_ValueError,
+ space.wrap("need at least one variable") )
+ rc.Expression.__init__(self, variables )
+ self.formula = self.space.str_w(w_formula)
+ # self.filter_func is a function taking keyword arguments and returning a boolean
+ self.w_filter_func = w_filter_func
+
+ def filter_func(self, kwargs):
+ space = self.space
+ w_kwargs = space.newdict()
+ for var, value in kwargs.items():
+ dom = self.doms[var]
+ assert isinstance( dom, _FiniteDomain )
+ #print '!!!!', dom.w_values, value
+ w_val = space.getitem( dom.w_values, space.wrap(value) )
+ w_kwargs.content[space.wrap(var)] = w_val
+ return space.is_true(space.call(self.w_filter_func,
+ space.newlist([]),
+ w_kwargs))
+
+
+ def __repr__(self):
+ return '<%s>' % self.formula
+
+
+class _BinaryExpression(rc.BinaryExpression):
+ """A constraint represented as a python expression."""
+
+ def __init__(self, space, w_variables, w_formula, w_filter_func):
+ """variables is a list of variables which appear in the formula
+ formula is a python expression that will be evaluated as a boolean"""
+ self.space = space
+ variables = []
+ for w_var in space.unpackiterable( w_variables ):
+ variables.append( space.str_w(w_var) )
+ if len(variables)==0:
+ raise OperationError( space.w_ValueError,
+ space.wrap("need at least one variable") )
+ rc.BinaryExpression.__init__(self, variables )
+ self.formula = self.space.str_w(w_formula)
+ # self.filter_func is a function taking keyword arguments and returning a boolean
+ self.w_filter_func = w_filter_func
+ self.kwcache = {}
+
+ def filter_func(self, kwargs):
+ space = self.space
+ var1 = self._variables[0]
+ var2 = self._variables[1]
+ arg1 = kwargs[var1]
+ arg2 = kwargs[var2]
+ t = (arg1,arg2)
+ if t in self.kwcache:
+ return self.kwcache[t]
+ w_kwargs = space.newdict()
+
+ dom = self.doms[var1]
+ w_val = space.getitem( dom.w_values, space.wrap(arg1) )
+ w_kwargs.content[space.wrap(var1)] = w_val
+
+ dom = self.doms[var2]
+ w_val = space.getitem( dom.w_values, space.wrap(arg2) )
+ w_kwargs.content[space.wrap(var2)] = w_val
+
+ res = space.is_true(space.call(self.w_filter_func,
+ space.newlist([]),
+ w_kwargs))
+ self.kwcache[t] = res
+ return res
+
+
+class W_Expression(W_AbstractConstraint):
+
+ def __init__(self, space, w_variables, w_formula, w_filter_func):
+ if space.int_w(space.len(w_variables)) == 2:
+ constraint = _BinaryExpression(space, w_variables, w_formula, w_filter_func)
+ else:
+ constraint = _Expression(space, w_variables, w_formula, w_filter_func)
+ W_AbstractConstraint.__init__(self, space, constraint)
+
+
+W_Expression.typedef = typedef.TypeDef("W_Expression",
+ W_AbstractConstraint.typedef)
+
+
+def interp_make_expression(space, w_variables, w_formula, w_callable):
+ """create a new constraint of type Expression or BinaryExpression
+ The chosen class depends on the number of variables in the constraint"""
+ if not isinstance(w_formula, W_StringObject):
+ raise OperationError(space.w_TypeError,
+ space.wrap('formula must be a string.'))
+ return W_Expression(space, w_variables, w_formula, w_callable)
+
+
+#--- Alldistinct
+
+class _AllDistinct(rc.AllDistinct):
+ """Contraint: all values must be distinct"""
+
+ def __init__(self, space, w_variables):
+ variables = []
+ for w_var in space.unpackiterable( w_variables ):
+ variables.append( space.str_w(w_var) )
+ if len(variables)==0:
+ raise OperationError( space.w_ValueError,
+ space.wrap("need at least one variable") )
+ rc.AllDistinct.__init__(self, variables)
+
+
+class W_AllDistinct(W_AbstractConstraint):
+
+ def __init__(self, space, w_variables):
+ constraint = _AllDistinct(space, w_variables)
+ W_AbstractConstraint.__init__(self, space, constraint)
+
+
+W_AllDistinct.typedef = typedef.TypeDef(
+ "W_AllDistinct", W_AbstractConstraint.typedef)
+
+#function bolted into the space to serve as constructor
+def make_alldistinct(space, w_variables):
+ return space.wrap(W_AllDistinct(space, w_variables))
+
Added: pypy/dist/pypy/module/_cslib/fd.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/module/_cslib/fd.py Fri Feb 16 19:33:37 2007
@@ -0,0 +1,50 @@
+from pypy.interpreter.error import OperationError
+
+from pypy.interpreter import typedef, gateway, baseobjspace
+from pypy.interpreter.gateway import interp2app
+
+from pypy.objspace.std.listobject import W_ListObject, W_TupleObject
+from pypy.objspace.std.intobject import W_IntObject
+
+from pypy.rlib import rdomain as rd
+
+class _FiniteDomain(rd.BaseFiniteDomain):
+ """
+ Variable Domain with a finite set of possible values
+ """
+
+ def __init__(self, w_values, values):
+ """values is a list of values in the domain
+ This class uses a dictionnary to make sure that there are
+ no duplicate values"""
+
+ assert isinstance(w_values, W_ListObject)
+ self.w_values = w_values
+ self._values = {}
+
+ if values is None:
+ for k in range(len(w_values.wrappeditems)):
+ self._values[k] = True
+ else:
+ self._values = values.copy()
+
+ self._changed = False
+
+ def copy(self):
+ return _FiniteDomain(self.w_values, self._values)
+
+class W_FiniteDomain(baseobjspace.Wrappable):
+ def __init__(self, w_values, values):
+ assert isinstance(w_values, W_ListObject)
+ self.domain = _FiniteDomain( w_values, values )
+
+def make_fd(space, w_values):
+ if not isinstance(w_values, W_ListObject):
+ if not isinstance(w_values, W_TupleObject):
+ raise OperationError(space.w_TypeError,
+ space.wrap('first argument must be a list.'))
+ return W_FiniteDomain(w_values, None)
+
+W_FiniteDomain.typedef = typedef.TypeDef(
+ "W_FiniteDomain")
+
Added: pypy/dist/pypy/module/_cslib/propagation.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/module/_cslib/propagation.py Fri Feb 16 19:33:37 2007
@@ -0,0 +1,86 @@
+from pypy.rlib.rpropagation import Repository, Solver
+import pypy.rlib.rdistributor as rd
+from pypy.module._cslib import fd
+from pypy.module._cslib.constraint import W_AbstractConstraint
+
+from pypy.interpreter.error import OperationError
+
+from pypy.interpreter import typedef, gateway, baseobjspace
+from pypy.interpreter.gateway import interp2app
+
+#from pypy.objspace.std.listobject import W_ListObject, W_TupleObject
+from pypy.objspace.std.intobject import W_IntObject
+from pypy.objspace.std.listobject import W_ListObject
+from pypy.objspace.std.tupleobject import W_TupleObject
+from pypy.objspace.std.stringobject import W_StringObject
+from pypy.objspace.std.dictobject import W_DictObject
+
+
+class _Repository(Repository):
+
+ def __init__(self, space, w_variables, w_domains, w_constraints):
+ # let's just give everything unwrapped to our parent
+ doms = {}
+ for var, dom in w_domains.content.items():
+ assert isinstance( dom, fd.W_FiniteDomain )
+ assert isinstance( var, W_StringObject )
+ doms[space.str_w(var)] = dom.domain
+ constraints = []
+ for w_constraint in space.unpackiterable( w_constraints ):
+ if not isinstance( w_constraint, W_AbstractConstraint ):
+ raise OperationError( space.w_TypeError,
+ space.wrap("constraints needs to be a sequence of constraints" ) )
+ constraints.append( w_constraint.constraint )
+ Repository.__init__(self, doms, constraints)
+
+
+class W_Repository(baseobjspace.Wrappable):
+
+ def __init__(self, space, w_variables, w_domains, w_constraints):
+ self.repo = _Repository(space, w_variables, w_domains, w_constraints)
+
+
+W_Repository.typedef = typedef.TypeDef("W_Repository")
+
+def make_repo(space, w_variables, w_domains, w_constraints):
+ if not isinstance(w_domains,W_DictObject):
+ raise OperationError(space.w_TypeError,
+ space.wrap('domains must be a dictionary'))
+ return W_Repository(space, w_variables, w_domains, w_constraints)
+
+
+class W_Solver(baseobjspace.Wrappable):
+
+ def __init__(self, space):
+ self.space = space
+ self.solver = Solver(rd.DefaultDistributor())
+
+ def w_solve(self, w_repo, w_verbosity):
+ space = self.space
+ if not isinstance(w_repo, W_Repository):
+ raise OperationError(space.w_TypeError,
+ space.wrap('first argument must be a repository.'))
+ if not isinstance(w_verbosity, W_IntObject):
+ raise OperationError(space.w_TypeError,
+ space.wrap('second argument must be an int.'))
+ self._verb = w_verbosity.intval
+ sols = self.solver.solve_all(w_repo.repo)
+ sols_w = []
+ for sol in sols:
+ w_dict = space.newdict()
+ for var,value in sol.items():
+ domain = w_repo.repo._domains[var]
+ assert isinstance( domain, fd._FiniteDomain )
+ w_var = space.wrap(var)
+ w_value = space.getitem( domain.w_values, space.wrap(value) )
+ space.setitem( w_dict, w_var, w_value )
+ sols_w.append( w_dict )
+ return space.newlist(sols_w)
+
+W_Solver.typedef = typedef.TypeDef(
+ 'W_Solver',
+ solve = interp2app(W_Solver.w_solve))
+
+
+def make_solver(space):
+ return W_Solver(space)
Added: pypy/dist/pypy/rlib/cslib/__init__.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/rlib/cslib/__init__.py Fri Feb 16 19:33:37 2007
@@ -0,0 +1 @@
+#
Added: pypy/dist/pypy/rlib/cslib/btree.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/rlib/cslib/btree.py Fri Feb 16 19:33:37 2007
@@ -0,0 +1,43 @@
+"""
+A minimalist binary tree implementation
+whose values are (descendants of) BTreeNodes.
+This alleviates some typing difficulties when
+using TimSort on lists of the form [(key, Thing), ...]
+"""
+
+class BTreeNode:
+
+ def __init__(self, key):
+ self.key = key
+ self.left = None
+ self.right = None
+
+ def add(self, val):
+ key = val.key
+ assert isinstance(key, int)
+ assert isinstance(val, BTreeNode)
+
+ if key > self.key:
+ if self.right:
+ self.right.add(val)
+ else:
+ self.right = val
+ else:
+ if self.left:
+ self.left.add(val)
+ else:
+ self.left = val
+
+
+ def _values(self, dest):
+ if self.left:
+ self.left._values(dest)
+ dest.append(self)
+ if self.right:
+ self.right._values(dest)
+
+ def get_values(self):
+ dest = []
+ self._values( dest )
+ return dest
+
Added: pypy/dist/pypy/rlib/cslib/rconstraint.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/rlib/cslib/rconstraint.py Fri Feb 16 19:33:37 2007
@@ -0,0 +1,262 @@
+from pypy.rlib.btree import BTreeNode
+from pypy.rlib.rdomain import BaseFiniteDomain, ConsistencyError
+
+
+class AbstractConstraint:
+
+ def __init__(self, variables):
+ """variables is a list of variables which appear in the formula"""
+ assert isinstance(variables, list)
+
+ self._variables = variables
+
+ def revise(self, domains):
+ "domains : {'var':Domain}"
+ return False
+
+ def estimate_cost(self, domains):
+ cost = 1
+ for var in self._variables:
+ dom = domains[var]
+ assert isinstance(dom, BaseFiniteDomain)
+ cost = cost * dom.size()
+ return cost
+
+
+class Quadruple(BTreeNode):
+ def __init__(self, key, varname, values, index):
+ BTreeNode.__init__( self, key )
+ self.var = varname
+ self.values = values
+ self.index = index
+
+class Expression(AbstractConstraint):
+ """A constraint represented as a functional expression."""
+
+ def __init__(self, variables):
+ AbstractConstraint.__init__(self, variables)
+ self.doms = {}
+
+ def filter_func(self, kwargs):
+ return False
+
+ def _init_result_cache(self):
+ """key = (variable,value), value = [has_success,has_failure]"""
+ result_cache = {}
+ for var in self._variables:
+ result_cache[var] = {}
+ return result_cache
+
+ def _assign_values(self, doms):
+ kwargs = {}
+ sorted_vars = None
+ for variable in self._variables:
+ domain = doms[variable]
+ assert isinstance(domain, BaseFiniteDomain)
+ values = domain.get_values()
+ node = Quadruple(domain.size(),
+ variable,
+ values,
+ 0)
+ if sorted_vars is None:
+ sorted_vars = node
+ else:
+ sorted_vars.add( node )
+ kwargs[variable] = values[0]
+
+ # get sorted variables to instanciate those with fewer possible values first
+ assert sorted_vars is not None
+ self._assign_values_state = sorted_vars.get_values()
+ return kwargs
+
+ def _next_value(self, kwargs):
+
+ # try to instanciate the next variable
+ variables = self._assign_values_state
+
+ for curr in variables:
+ if curr.index < curr.key:
+ kwargs[curr.var] = curr.values[curr.index]
+ curr.index += 1
+ break
+ else:
+ curr.index = 0
+ kwargs[curr.var] = curr.values[0]
+ else:
+ # it's over
+ return None
+ return kwargs
+
+ def revise(self, doms):
+ """generic propagation algorithm for n-ary expressions"""
+ self.doms = doms
+ maybe_entailed = True
+ result_cache = self._init_result_cache()
+
+ kwargs = self._assign_values(doms)
+
+ while 1:
+ kwargs = self._next_value(kwargs)
+ if kwargs is None:
+ break
+
+ if maybe_entailed:
+ for varname, val in kwargs.iteritems():
+ val_dict = result_cache[varname]
+ if val not in val_dict:
+ break
+ else:
+ continue
+ if self.filter_func(kwargs):
+ for var, val in kwargs.items():
+ var_dict = result_cache[var]
+ var_dict[val] = True
+ else:
+ maybe_entailed = False
+
+ try: # XXX domains in rlib, too
+ for varname, keep in result_cache.items():
+ domain = doms[varname]
+ assert isinstance(domain, BaseFiniteDomain)
+ domain.remove_values([val
+ for val in domain.get_values()
+ if val not in keep])
+ except KeyError:
+ # There are no more value in result_cache
+ pass
+
+ return maybe_entailed
+
+
+ def __repr__(self):
+ return '<%s>' % self.formula
+
+
+#--- Alldistinct
+
+class VarDom(BTreeNode):
+ def __init__(self, key, var, dom):
+ BTreeNode.__init__(self, key)
+ self.var = var
+ self.dom = dom
+
+class AllDistinct(AbstractConstraint):
+ """Contraint: all values must be distinct"""
+
+ def __init__(self, variables):
+ AbstractConstraint.__init__(self, variables)
+ # worst case complexity
+ self._cost = len(self._variables) * (len(self._variables) - 1) / 2
+
+ def estimate_cost(self, domains):
+ return self._cost
+
+ def revise(self, doms):
+
+ sorted_vars = None
+ for var in self._variables:
+ dom = doms[var]
+ assert isinstance(dom, BaseFiniteDomain)
+ node = VarDom(dom.size(), var, dom)
+ if sorted_vars is None:
+ sorted_vars = node
+ else:
+ sorted_vars.add(node)
+
+ assert sorted_vars is not None
+ variables = sorted_vars.get_values()
+
+ # if a domain has a size of 1,
+ # then the value must be removed from the other domains
+ for var_dom in variables:
+ if var_dom.dom.size() == 1:
+ #print "AllDistinct removes values"
+ for var_dom2 in variables:
+ if var_dom2.var != var_dom.var:
+ try:
+ var_dom2.dom.remove_value(var_dom.dom.get_values()[0])
+ except KeyError, e:
+ # we ignore errors caused by the removal of
+ # non existing values
+ pass
+
+ # if there are less values than variables, the constraint fails
+ values = {}
+ for var_dom in variables:
+ for val in var_dom.dom.get_values():
+ values[val] = 0
+
+ if len(values) < len(variables):
+ #print "AllDistinct failed"
+ raise ConsistencyError
+
+ # the constraint is entailed if all domains have a size of 1
+ for var_dom in variables:
+ if not var_dom.dom.size() == 1:
+ return False
+
+ #print "All distinct entailed"
+ return True
+
+
+
+#--- Binary expressions
+
+class BinaryExpression(Expression):
+ """A binary constraint represented as a python expression
+
+ This implementation uses a narrowing algorithm optimized for
+ binary constraints."""
+
+ def __init__(self, variables):
+ assert len(variables) == 2
+ Expression.__init__(self, variables)
+
+ def revise(self, domains):
+ """specialized pruning algorithm for binary expressions
+ Runs much faster than the generic version"""
+ self.doms = domains
+ maybe_entailed = True
+ var1 = self._variables[0]
+ dom1 = domains[var1]
+ values1 = dom1.get_values()
+ var2 = self._variables[1]
+ dom2 = domains[var2]
+ values2 = dom2.get_values()
+ if dom2.size() < dom1.size():
+ var1, var2 = var2, var1
+ dom1, dom2 = dom2, dom1
+ values1, values2 = values2, values1
+
+ kwargs = {}
+ keep1 = {}
+ keep2 = {}
+ maybe_entailed = True
+ # iterate for all values
+ for val1 in values1:
+ kwargs[var1] = val1
+ for val2 in values2:
+ kwargs[var2] = val2
+ if val1 in keep1 and val2 in keep2 and not maybe_entailed:
+ continue
+ if self.filter_func(kwargs):
+ keep1[val1] = 1
+ keep2[val2] = 1
+ else:
+ maybe_entailed = False
+
+ dom1.remove_values([val for val in values1 if val not in keep1])
+ dom2.remove_values([val for val in values2 if val not in keep2])
+
+ return maybe_entailed
+
+
+class BinEq(BinaryExpression):
+ def filter_func(self, kwargs):
+ values = kwargs.values()
+ return values[0]==values[1]
+
+class BinLt(BinaryExpression):
+ def filter_func(self, kwargs):
+ values = kwargs.values()
+ return values[0] < values[1]
Added: pypy/dist/pypy/rlib/cslib/rdistributor.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/rlib/cslib/rdistributor.py Fri Feb 16 19:33:37 2007
@@ -0,0 +1,77 @@
+"""
+distributors - part of constraint satisfaction solver.
+"""
+
+def make_new_domains(domains):
+ """return a shallow copy of dict of domains passed in argument"""
+ new_domains = {}
+ for key, domain in domains.items():
+ new_domains[key] = domain.copy()
+ return new_domains
+
+class AbstractDistributor:
+ """Implements DistributorInterface but abstract because
+ _distribute is left unimplemented."""
+
+ def find_smallest_domain(self, domains):
+ """returns the variable having the smallest domain.
+ (or one of such varibles if there is a tie)
+ """
+ k = 0
+ doms = domains.items()
+ while k<len(doms):
+ var, dom = doms[k]
+ sz = dom.size()
+ if sz>1:
+ min_size = sz
+ min_var = var
+ break
+ k += 1
+ else:
+ raise RuntimeError, "should not be here"
+ while k<len(doms):
+ var, dom = doms[k]
+ if 1 < dom.size() < min_size:
+ min_var = var
+ min_size = dom.size()
+ k += 1
+ return min_var
+
+ def distribute(self, domains):
+ """
+ domains -> two variants of the same modified domain
+ do the minimal job and let concrete class distribute variables
+ """
+ doms1 = make_new_domains(domains)
+ doms2 = make_new_domains(domains)
+ for modified_domain in self._distribute(doms1,doms2):
+ modified_domain._changed = False
+ return [doms1,doms2]
+
+class AllOrNothingDistributor(AbstractDistributor):
+ """distributes domains by splitting the smallest domain in 2 new domains
+ The first new domain has a size of one,
+ and the second has all the other values"""
+
+ def _distribute(self, doms1, doms2):
+ """See AbstractDistributor"""
+ variable = self.find_smallest_domain(doms1)
+ values = doms1[variable].get_values()
+ doms1[variable].remove_values(values[1:])
+ doms2[variable].remove_value(values[0])
+ return [doms1[variable], doms2[variable]]
+
+class DichotomyDistributor(AbstractDistributor):
+ """distributes domains by splitting the smallest domain in
+ two equal parts or as equal as possible."""
+
+ def _distribute(self, doms1, doms2):
+ """See AbstractDistributor"""
+ variable = self.find_smallest_domain(doms1)
+ values = doms1[variable].get_values()
+ middle = len(values)/2
+ doms1[variable].remove_values(values[:middle])
+ doms2[variable].remove_values(values[middle:])
+ return [doms1[variable], doms2[variable]]
+
+DefaultDistributor = DichotomyDistributor
Added: pypy/dist/pypy/rlib/cslib/rdomain.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/rlib/cslib/rdomain.py Fri Feb 16 19:33:37 2007
@@ -0,0 +1,93 @@
+
+class ConsistencyError(Exception):
+ pass
+
+class BaseFiniteDomain:
+ """
+ Variable Domain with a finite set of int values
+ """
+
+ def __init__(self, values):
+ """values is a list of values in the domain
+ This class uses a dictionnary to make sure that there are
+ no duplicate values"""
+ #assert isinstance(values, dict)
+ self._values = values.copy()
+ self._changed = False
+
+ def copy(self):
+ return BaseFiniteDomain(self._values)
+
+ def _value_removed(self):
+ "The implementation of remove_value should call this method"
+ if self.size() == 0:
+ raise ConsistencyError, "tried to make a domain empty"
+
+ def remove_value(self, value):
+ """Remove value of domain and check for consistency"""
+ del self._values[value]
+ self._value_removed()
+ self._changed = True
+
+ def remove_values(self, values):
+ assert isinstance(values, list)
+ if len(values) > 0:
+ for val in values:
+ del self._values[val]
+ self._value_removed()
+ self._changed = True
+
+ def size(self):
+ """computes the size of a finite domain"""
+ return len(self._values)
+
+ def get_values(self):
+ return self._values.keys()
+
+
+ def __repr__(self):
+ return "<Domain %s>" % self._values.keys()
+
+# XXX finish this
+class TodoBaseFiniteDomain:
+ """
+ Variable Domain with a finite set of int values
+ """
+
+ def __init__(self, values):
+ """values is a list of values in the domain
+ This class uses a dictionnary to make sure that there are
+ no duplicate values"""
+ assert isinstance(values, int)
+ self._values = [True] * values
+
+ def copy(self):
+ dom = BaseFiniteDomain(len(self._values))
+ for i, v in enumerate(self._values):
+ dom._values[i] = v
+
+ def _value_removed(self):
+ "The implementation of remove_value should call this method"
+ if self.size() == 0:
+ raise ConsistencyError, "tried to make a domain empty"
+
+ def remove_value(self, value):
+ """Remove value of domain and check for consistency"""
+ assert isinstance(value, int)
+ del self._values[value]
+ self._value_removed()
+
+ def remove_values(self, values):
+ assert isinstance(values, list)
+ if len(values) > 0:
+ for val in values:
+ del self._values[val]
+ self._value_removed()
+
+ def size(self):
+ """computes the size of a finite domain"""
+ return len(self._values)
+
+ def get_values(self):
+ return self._values.keys()
+
Added: pypy/dist/pypy/rlib/cslib/rpropagation.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/rlib/cslib/rpropagation.py Fri Feb 16 19:33:37 2007
@@ -0,0 +1,151 @@
+"""The code of the constraint propagation algorithms"""
+from pypy.rlib.rconstraint import AbstractConstraint, ConsistencyError
+
+class Repository:
+ """Stores variables, domains and constraints
+ Propagates domain changes to constraints
+ Manages the constraint evaluation queue"""
+
+ def __init__(self, domains, constraints):
+ self._variables = domains.keys() # list of variable names
+ self._domains = domains # maps variable name to domain object
+ self._constraints = [] # list of constraint objects
+ self._variableListeners = {}
+ for var in self._variables:
+ self._variableListeners[var] = []
+ for constr in constraints:
+ self.add_constraint( constr )
+
+ def __repr__(self):
+ return '<Repository nb_constraints=%d domains=%s>' % \
+ (len(self._constraints), self._domains)
+
+ def add_constraint(self, constraint):
+ assert isinstance( constraint, AbstractConstraint )
+ if 0: # isinstance(constraint, BasicConstraint):
+ # Basic constraints are processed just once
+ # because they are straight away entailed
+ var = constraint.getVariable()
+ constraint.revise({var: self._domains[var]})
+ else:
+ self._constraints.append(constraint)
+ for var in constraint._variables:
+ self._variableListeners[var].append(constraint)
+
+ def _remove_constraint(self, constraint):
+ self._constraints.remove(constraint)
+ for var in constraint._variables:
+ try:
+ self._variableListeners[var].remove(constraint)
+ except ValueError:
+ raise ValueError('Error removing constraint from listener',
+ var,
+ self._variableListeners[var],
+ constraint)
+
+ def get_domains(self):
+ return self._domains
+
+ def distribute(self, distributor):
+ """
+ create new repository using the distributor and self
+ using changed domains
+ """
+ d1, d2 = distributor.distribute(self._domains)
+ return [Repository(d1, self._constraints),
+ Repository(d2, self._constraints)]
+
+ def propagate(self):
+ """Prunes the domains of the variables
+ This method calls constraint.narrow() and queues constraints
+ that are affected by recent changes in the domains.
+ Returns True if a solution was found"""
+
+ _queue = [(constr.estimate_cost(self._domains), constr)
+ for constr in self._constraints ]
+ # XXX : _queue.sort()
+ _affected_constraints = {}
+ while True:
+ if not _queue:
+ # refill the queue if some constraints have been affected
+ _queue = [(constr.estimate_cost(self._domains), constr)
+ for constr in _affected_constraints]
+ if not _queue:
+ break
+ # XXX _queue.sort()
+ _affected_constraints.clear()
+ cost, constraint = _queue.pop(0)
+ entailed = constraint.revise(self._domains)
+ for var in constraint._variables:
+ # affected constraints are listeners of
+ # affected variables of this constraint
+ dom = self._domains[var]
+ if not dom._changed: # XXX
+ continue
+ for constr in self._variableListeners[var]:
+ if constr is not constraint:
+ _affected_constraints[constr] = True
+ dom._changed = False
+ if entailed:
+ self._remove_constraint(constraint)
+ if constraint in _affected_constraints:
+ del _affected_constraints[constraint]
+
+ for domain in self._domains.values():
+ if domain.size() != 1:
+ return 0
+ return 1
+
+
+ def solve_all(self, distributor):
+ solver = Solver(distributor)
+ return solver.solve_all(self)
+
+
+import os
+
+class Solver:
+ """Top-level object used to manage the search"""
+
+ def __init__(self, distributor):
+ """if no distributer given, will use the default one"""
+ self._verb = 0
+ self._distributor = distributor
+ self.todo = []
+
+ def solve_one(self, repository):
+ self.todo = [repository]
+ return self.next_sol()
+
+ def solve_all(self, repository):
+ self.todo = [repository]
+ solutions = []
+ while True:
+ sol = self.next_sol()
+ if sol is not None:
+ solutions.append( sol )
+ if self._verb:
+ os.write(1, 'found solution : %s\n' % sol)
+ else:
+ break
+ return solutions
+
+ def next_sol(self):
+ found_solution = False
+ todo = self.todo
+ while todo:
+ repo = todo.pop()
+ try:
+ found_solution = repo.propagate()
+ except ConsistencyError:
+ continue
+ if found_solution:
+ solution = {}
+ for variable, domain in repo.get_domains().items():
+ solution[variable] = domain.get_values()[0]
+ return solution
+ else:
+ for rep in repo.distribute(self._distributor):
+ todo.append( rep )
+ return None
+
More information about the Pypy-commit
mailing list