[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