[pypy-svn] r25390 - pypy/dist/pypy/objspace/logic
auc at codespeak.net
auc at codespeak.net
Wed Apr 5 16:30:54 CEST 2006
Author: auc
Date: Wed Apr 5 16:30:53 2006
New Revision: 25390
Added:
pypy/dist/pypy/objspace/logic/constraint.py
pypy/dist/pypy/objspace/logic/distributor.py
pypy/dist/pypy/objspace/logic/domain.py
Log:
to be made interplevel friendly
Added: pypy/dist/pypy/objspace/logic/constraint.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/objspace/logic/constraint.py Wed Apr 5 16:30:53 2006
@@ -0,0 +1,387 @@
+from variable import NoDom
+import operator
+
+#-- Exceptions ---------------------------------------
+
+class ConsistencyFailure(Exception):
+ """The repository is not in a consistent state"""
+ pass
+
+class DomainlessVariables(Exception):
+ """A constraint can't be defined on variables
+ without a domain"""
+ pass
+
+#-- Constraints ------------------------------------------
+
+class AbstractConstraint(object):
+
+ def __init__(self, c_space, variables):
+ """variables is a list of variables which appear in the formula"""
+ self.cs = c_space
+ self._names_to_vars = {}
+ for var in variables:
+ if self.cs.dom(var) == NoDom:
+ raise DomainlessVariables
+ self._names_to_vars[var.name] = var
+ self._variables = variables
+
+ def affected_variables(self):
+ """ Return a list of all variables affected by this constraint """
+ return self._variables
+
+ def isVariableRelevant(self, variable):
+ return variable in self._variables
+
+ def estimate_cost(self):
+ """Return an estimate of the cost of the narrowing of the constraint"""
+ return reduce(operator.mul,
+ [self.cs.dom(var).size() for var in self._variables])
+
+ def copy_to(self, space):
+ return self.__class__(space, self._variables)
+
+ def __eq__(self, other): #FIXME and parent
+ if not isinstance(other, self.__class__): return False
+ return self._variables == other._variables
+
+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 copy_to(self, space):
+ raise NotImplementedError
+
+ 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 revise(self, domains):
+ domain = domains[self._variable]
+ operator = self._operator
+ ref = self._reference
+ try:
+ for val in domain.get_values() :
+ if not operator(val, ref) :
+ domain.remove_value(val)
+ except ConsistencyFailure:
+ raise ConsistencyFailure('inconsistency while applying %s' % \
+ repr(self))
+ return 1
+
+ def __eq__(self, other):
+ raise NotImplementedError
+
+def make_lambda_head(vars):
+ var_ids = ','.join([var.name for var in vars])
+ return 'lambda ' + var_ids + ':'
+
+def expand_expr_template(expr, vars):
+ for var in vars:
+ expr.replace(var.name, var.name + '.val')
+ return expr
+
+
+class AllDistinct(AbstractConstraint):
+ """Contraint: all values must be distinct"""
+
+ def __init__(self, c_space, variables):
+ assert len(variables)>1
+ AbstractConstraint.__init__(self, c_space, variables)
+ # worst case complexity
+ self.__cost = len(variables) * (len(variables) - 1) / 2
+
+ def __repr__(self):
+ return '<AllDistinct %s>' % str(self._variables)
+
+ def copy_to(self, space):
+ return self.__class__(space, self._variables)
+
+ def estimateCost(self, domains):
+ return self.__cost
+
+ def test_solution(self, sol):
+ """test a solution against this constraint
+ accept a mapping of variable names to value"""
+ values = sol.items()
+ value_set = set(values)
+ return len(value_set) == len(sol)
+
+ def revise(self):
+ variables = [(self.cs.dom(variable).size(),
+ variable, self.cs.dom(variable))
+ for variable in self._variables]
+
+ variables.sort()
+ # if a domain has a size of 1,
+ # then the value must be removed from the other domains
+ for size, var, dom in variables:
+ if dom.size() == 1:
+ print "AllDistinct removes values"
+ for _siz, _var, _dom in variables:
+ if _var != var:
+ try:
+ _dom.remove_value(dom.get_values()[0])
+ except KeyError:
+ # we ignore errors caused by the removal of
+ # non existing values
+ pass
+
+ # if there are less values than variables, the constraint fails
+ values = {}
+ for size, var, dom in variables:
+ for val in dom:
+ values[val] = 0
+ if len(values) < len(variables):
+ print "AllDistinct failed"
+ raise ConsistencyFailure()
+
+ # the constraint is entailed if all domains have a size of 1
+ for variable in variables:
+ if variable[2].size() != 1:
+ return 0
+
+ # Question : did we *really* completely check
+ # our own alldistinctness predicate ?
+
+ return 1
+
+
+class Expression(AbstractConstraint):
+ """A constraint represented as a python expression."""
+ _FILTER_CACHE = {}
+
+ def __init__(self, c_space, variables, formula, typ='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"""
+ self.formula = formula
+ self.type = typ
+ AbstractConstraint.__init__(self, c_space, variables)
+ try:
+ self.filterFunc = Expression._FILTER_CACHE[formula]
+ except KeyError:
+ self.filterFunc = eval(make_lambda_head(variables) \
+ + expand_expr_template(formula, variables), {}, {})
+ Expression._FILTER_CACHE[formula] = self.filterFunc
+
+ def test_solution(self, sol ):
+ """test a solution against this constraint
+ accept a mapping of variable names to value"""
+ args = []
+ for var in self._variables:
+ args.append( sol[var.name] )
+ return self.filterFunc( *args )
+
+
+ def copy_to(self, space):
+ return self.__class__(space, self._variables,
+ self.formula, self.type)
+
+ 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.name] = {}
+ return result_cache
+
+
+ def _assign_values(self):
+ variables = []
+ kwargs = {}
+ for variable in self._variables:
+ domain = self.cs.dom(variable)
+ values = domain.get_values()
+ variables.append((domain.size(), [variable, values, 0, len(values)]))
+ kwargs[variable.name] = 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].name] = curr[1][curr[2]]
+ break
+ else:
+ curr[2] = 0
+ kwargs[curr[0].name] = curr[1][0]
+ else:
+ # it's over
+ go_on = 0
+
+ def revise(self):
+ # removed domain arg. (auc, ale)
+ """generic propagation algorithm for n-ary expressions"""
+ maybe_entailed = 1
+ ffunc = self.filterFunc
+ result_cache = self._init_result_cache()
+ for kwargs in self._assign_values():
+ 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 = self.cs.dom(self._names_to_vars[var])
+ domain.remove_values([val for val in domain 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 __eq__(self, other):
+ if not super(Expression, self).__eq__(other): return False
+ r1 = self.formula == other.formula
+ r2 = self.type == other.type
+ return r1 and r2
+
+
+ def __repr__(self):
+ return '<%s>' % 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 copy_to(self, space):
+ raise NotImplementedError
+
+ def revise(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)
Added: pypy/dist/pypy/objspace/logic/distributor.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/objspace/logic/distributor.py Wed Apr 5 16:30:53 2006
@@ -0,0 +1,123 @@
+import math
+
+def arrange_domains(cs, variables):
+ """build a data structure from var to dom
+ that satisfies distribute & friends"""
+ new_doms = {}
+ for var in variables:
+ new_doms[var] = cs.dom(var).copy()
+ return new_doms
+
+class AbstractDistributor(object):
+ """_distribute is left unimplemented."""
+
+ def __init__(self, c_space, nb_subspaces=2):
+ self.nb_subspaces = nb_subspaces
+ self.cs = c_space
+ self.verbose = 0
+
+ def set_space(self, space):
+ self.cs = space
+
+ def findSmallestDomain(self):
+ """returns the variable having the smallest domain.
+ (or one of such varibles if there is a tie)
+ """
+ vars_ = [var for var in self.cs.get_variables_with_a_domain()
+ if self.cs.dom(var).size() > 1]
+
+ best = vars_[0]
+ for var in vars_:
+ if self.cs.dom(var).size() < self.cs.dom(best).size():
+ best = var
+
+ return best
+
+ def nb_subdomains(self):
+ """return number of sub domains to explore"""
+ return self.nb_subspaces
+
+ def distribute(self, choice):
+ raise NotImplementedError("Use a concrete implementation of "
+ "the Distributor interface")
+
+## def distribute(self, verbose=0):
+## """do the minimal job and let concrete class distribute variables
+## """
+## self.verbose = verbose
+## variables = self.cs.get_variables_with_a_domain()
+## replicas = []
+## for i in range(self.nb_subdomains()):
+## replicas.append(arrange_domains(self.cs, variables))
+## modified_domains = self._distribute(*replicas)
+## for domain in modified_domains:
+## domain.reset_flags()
+## return replicas
+
+
+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 distribute(self, dom1, dom2):
+ """See AbstractDistributor"""
+ raise NotImplementedError
+ 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].remove_value(values[0])
+ 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, c_space, nb_subspaces=3):
+ AbstractDistributor.__init__(self, c_space, nb_subspaces)
+ self.__to_split = None
+
+ def nb_subdomains(self):
+ """See AbstractDistributor"""
+ self.__to_split = self.findSmallestDomain()
+ if self.nb_subspaces:
+ return min(self.nb_subspaces,
+ self.cs.dom(self.__to_split).size())
+ else:
+ return self.cs.dom(self.__to_split).size()
+
+
+ def distribute(self, choice):
+ variable = self.findSmallestDomain()
+ nb_subspaces = self.nb_subdomains()
+ values = self.cs.dom(variable).get_values()
+ nb_elts = max(1, len(values)*1./nb_subspaces)
+ start, end = (int(math.floor(choice * nb_elts)),
+ int(math.floor((choice + 1) * nb_elts)))
+ self.cs.dom(variable).remove_values(values[:start])
+ self.cs.dom(variable).remove_values(values[end:])
+
+ for const in self.cs.dependant_constraints(variable):
+ self.cs.event_set.add(const)
+
+
+class DichotomyDistributor(SplitDistributor):
+ """distributes domains by splitting the smallest domain in
+ two equal parts or as equal as possible"""
+ def __init__(self, c_space):
+ SplitDistributor.__init__(self, c_space, 2)
+
+
+class EnumeratorDistributor(SplitDistributor):
+ """distributes domains by splitting the smallest domain
+ in domains of size 1."""
+ def __init__(self, c_space):
+ SplitDistributor.__init__(self, c_space, 0)
+
+DefaultDistributor = DichotomyDistributor
Added: pypy/dist/pypy/objspace/logic/domain.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/objspace/logic/domain.py Wed Apr 5 16:30:53 2006
@@ -0,0 +1,101 @@
+from pypy.interpreter.error import OperationError
+
+from pypy.interpreter import baseobjspace, gateway
+from pypy.interpreter.baseobjspace import Wrappable
+
+from pypy.objspace.std.objspace import W_Object
+
+# ?
+from pypy.objspace.std.listobject import W_ListObject, W_TupleObject
+
+
+class ConsistencyFailure(Exception):
+ """The repository is not in a consistent state"""
+ pass
+
+
+class W_AbstractDomain(Wrappable):
+ """Implements the functionnality related to the changed flag.
+ Can be used as a starting point for concrete domains"""
+
+ def __init__(self, space):
+ self._space = space
+ self.__changed = 0
+
+ def w_reset_flags(self):
+ self.__changed = 0
+
+ def w_has_changed(self):
+ return self.__changed
+
+ def _value_removed(self):
+ """The implementation of remove_value should call this method"""
+ self.__changed = 1
+ if self.size() == 0:
+ raise ConsistencyFailure()
+
+W_AbstractDomain.typedef = TypeDef("W_AbstractDomain",
+ reset_flags = interp2app(W_AbstractDomain.w_reset_flags)
+ has_changed = interp2app(W_AbstractDomain.w_has_changed))
+
+class W_FiniteDomain(AbstractDomain):
+ """
+ Variable Domain with a finite set of possible 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"""
+ AbstractDomain.__init__(self)
+ self.set_values(values)
+
+ def set_values(self, values):
+ self._values = set(values)
+
+ def w_remove_value(self, value):
+ """Remove value of domain and check for consistency"""
+ self._values.remove(value)
+ self._value_removed()
+
+ def w_remove_values(self, values):
+ """Remove values of domain and check for consistency"""
+ if values:
+ for val in values :
+ self._values.remove(val)
+ self._value_removed()
+ __delitem__ = remove_value
+
+ def w_size(self):
+ """computes the size of a finite domain"""
+ return len(self._values)
+ __len__ = size
+
+ def w_get_values(self):
+ """return all the values in the domain
+ in an indexable sequence"""
+ return list(self._values)
+
+ def __iter__(self):
+ return iter(self._values)
+
+ def w_copy(self):
+ """clone the domain"""
+ return FiniteDomain(self)
+
+ def __repr__(self):
+ return '<FD %s>' % str(self.get_values())
+
+ def __eq__(self, other):
+ if other is NoDom: return False
+ return self._values == other._values
+
+ def __ne__(self, other):
+ return not self == other
+
+ def intersection(self, other):
+ if other is None: return self.get_values()
+ return self._values & other._values
+
+W_FiniteDomain.typedef = TypeDef("W_FiniteDomain",
+
More information about the Pypy-commit
mailing list