# HG changeset patch # User ludal@logilab.fr # Date 1235006866 -3600 # Thu Feb 19 02:27:46 2009 +0100 # Node ID f14ebce130ddd822ac4bc643f3084c520cecb5ea # Parent d7877cf6281eb7b6175c62fdd767511559153f43 solveur RQL avec gecode diff --git a/__pkginfo__.py b/__pkginfo__.py --- a/__pkginfo__.py +++ b/__pkginfo__.py @@ -32,4 +32,12 @@ pyversions = ['2.4'] +from distutils.core import Extension + include_dirs = [] + +ext_modules = [ Extension('rql_solve', + ['gecode_solver.cpp'], + libraries=['gecodeint', 'gecodekernel', 'gecodesearch'], + + ) ] diff --git a/analyze.py b/analyze.py --- a/analyze.py +++ b/analyze.py @@ -15,8 +15,16 @@ from rql import TypeResolverException, nodes from pprint import pprint +from copy import deepcopy -class CSPProblem(object): +try: + import rql_solve +except ImportError: + rql_solve = None + # Gecode solver not available + + +class ConstraintCSPProblem(object): def __init__(self): self.constraints = [] self.domains = {} @@ -85,7 +93,6 @@ expr = " or ".join( list(orred) ) self.add_expr( tuple(variables), expr ) - class _eq(object): def __init__(self, var, val): self.var = var @@ -97,37 +104,9 @@ def get_vars(self, s): s.add(self.var) - def simplify(self, doms): - dom = doms[self.var] - if self.val not in dom: - return -1 - if len(dom)==1: - return 1 - return 0 + def for_gecode(self, all_vars, all_values): + return ["eq", all_vars.index( self.var ), all_values.index(self.val) ] - def accept(self, vis): - vis.visit_eq(self) - -class _true(object): - def __str__(self): - return "True" - def get_vars(self, s): - pass - def simplify(self, doms): - return 1 - def accept(self, vis): - vis.visit_true( self ) - -class _false(object): - def __str__(self): - return "False" - def get_vars(self, s): - pass - def simplify(self, doms): - return -1 - def accept(self, vis): - vis.visit_false( self ) - class _eqv(object): def __init__(self, vars): self.vars = set(vars) @@ -135,140 +114,78 @@ return '(' + " == ".join( str(t) for t in self.vars ) + ')' def get_vars(self, s): s+=self.vars - def simplify(self, doms): - return 0 - def accept(self, vis): - vis.visit_eqv( self ) + def for_gecode(self, all_vars, all_values): + l = ["eqv"] + for v in self.vars: + l.append( all_vars.index( v ) ) + return l class _and(object): def __init__(self): self.cond = [] def add(self, expr): self.cond.append( expr ) + #if expr not in self.cond: + # self.cond.append( expr ) def __str__(self): return '(' + " and ".join( str(t) for t in self.cond ) + ')' def get_vars(self, s): for t in self.cond: t.get_vars(s) - def accept(self, vis): - vis.visit_and( self ) + def for_gecode(self, all_vars, all_values): + l = ["and"] + for n in self.cond: + l.append( n.for_gecode(all_vars, all_values) ) + return l - def simplify(self, doms): - cd = [] - for f in self.cond: - res = f.simplify(doms) - if res==-1: - # always false - self.cond = [ _false() ] - return -1 - if res==1: - # always true don't keep it - continue - cd.append(f) - self.cond = cd - if not cd: - # all true - self.cond = [ _true() ] - return 1 - return 0 class _or(_and): def __str__(self): return '(' + " or ".join( str(t) for t in self.cond ) + ')' - - def simplify(self, doms): - cd = [] - for f in self.cond: - res = f.simplify(doms) - if res==1: - # always true - self.cond = [ _true() ] - return 1 - if res==-1: - # always false don't keep it - continue - cd.append(f) - self.cond = cd - if not cd: - self.cond = [ _false() ] - return -1 - return 0 - def accept(self, vis): - vis.visit_or( self ) - - -class DistributeVisitor(object): - """The purpose of this visitor is to distribute and's over - or's in the expression tree. For example if A and B is - represented as A*B and A or B as A+B we want to transform : - A*(B+C*(D+E)+A*B) into A*B+A*C*D+A*C*E+A*A*B - """ - def __init__(self): - self.stack = [] - def visit_and(self, node): - first = self.cond.pop(0) - if isinstance(first, _or): - distrib = _or.cond[:] - else: - distrib = [first] - while self.cond: - next = self.cond.pop(0) - - def visit_or(self, node): - pass - def visit_true(self, node): - pass - def visit_false(self, node): - pass - def visit_eq(self, node): - pass - def visit_eqv(self, node): - pass - -class _CSPProblem(object): + def for_gecode(self, all_vars, all_values): + l = ["or"] + for n in self.cond: + l.append( n.for_gecode(all_vars, all_values) ) + return l +# TODO: refactor/optimize: +# now that gecode solver is working we don't need the above _and/_or... classes +# we can generate the constraint tree directly as ["and", ['or',...], ... ] +# Another thing that +class GecodeCSPProblem(object): def __init__(self): self.constraints = [] self.op = _and() self.domains = {} + def get_output(self): + return "" + def solve(self): + # assign an integer to each var and each domain values + all_values = set() + all_vars = sorted(self.domains.keys()) + for values in self.domains.values(): + all_values.update(values) + all_values = sorted(all_values) + constraints = self.op.for_gecode( all_vars, all_values ) + var_domains = [] + for var in all_vars: + dom = [] + for val in self.domains[var]: + dom.append( all_values.index(val) ) + var_domains.append( dom ) + + sols = rql_solve.solve( var_domains, len(all_values), constraints ) + rql_sols = [] + for s in sols: + r={} + for var,val in zip(all_vars,s): + r[var] = all_values[val] + rql_sols.append(r) + return rql_sols + def add_var(self, name, values): self.domains[name] = set(values) - def get_domains(self): - self.simplify() - d = {} - for var, dom in self.domains.items(): - print var, "==", dom - d[var] = fd.FiniteDomain(dom) - return d - - def get_constraints(self): - self.simplify() - lst = [] - for expr in self.op.cond: - constr = str(expr) - print constr - if isinstance(expr, _eq): - # taken into account bye simplify - continue - vrs = set() - expr.get_vars( vrs ) - lst.append( fd.make_expression( tuple(vrs), constr ) ) - print "----------" - return lst - - def simplify(self): - print "EQN=", self.op - for op in self.op.cond: - if isinstance(op, _eqv): - s = self.domains[op.vars[0]] - for var in op.vars: - s.intersection_update( self.domains[var] ) - self.domains[var] = s - if isinstance(op, _eq): - self.domains[op.var].intersection_update([op.val]) - self.op.simplify(self.domains) - def and_eq( self, var, value ): eq = _eq(var, value) self.op.add(eq) @@ -310,11 +227,16 @@ else: or2 = _or() for t in types: - or2.add( _eq(var, types[0]) ) + or2.add( _eq(var, t) ) anded.add( or2 ) orred.add(anded) self.op.add(orred) +if rql_solve is None: + CSPProblem = ConstraintCSPProblem +else: + CSPProblem = GecodeCSPProblem + class ETypeResolver(object): """Resolve variables types according to the schema. @@ -361,7 +283,6 @@ pprint(domains) print "CONSTRAINTS:" pprint(constraints.scons) - domains = constraints.get_domains() sols = constraints.solve() diff --git a/gecode_solver.cpp b/gecode_solver.cpp new file mode 100644 --- /dev/null +++ b/gecode_solver.cpp @@ -0,0 +1,269 @@ +#include <Python.h> +#include <iostream> +#include "gecode/kernel.hh" +#include "gecode/int.hh" +#include "gecode/search.hh" + +using namespace std; +using namespace Gecode; + +class RqlContext { +public: + RqlContext(long nvars, PyObject* domains, + long nvalues, PyObject* constraints, PyObject* sols): + solutions(-1), + time(1000), + fails(-1), + nvars(nvars), + nvalues(nvalues), + constraints(constraints), + sols(sols), + domains(domains), + verbosity(false) + { + } + + long solutions; + long time; + long fails; + long nvars; + long nvalues; + PyObject* constraints; + PyObject* sols; + PyObject* domains; + bool verbosity; +}; + +class RqlSolver : public Space { +protected: + IntVarArray variables; +public: + RqlSolver() {} + RqlSolver(const RqlContext& pb): + variables(this,pb.nvars,0,pb.nvalues-1) + { + BoolVar root(this, 1,1); + set_domains( pb.domains ); + add_constraints( pb.constraints, root ); + + branch(this, variables, INT_VAR_NONE, INT_VAL_MIN); + } + + ~RqlSolver() {}; + + RqlSolver(bool share, RqlSolver& s) : Space(share,s) + { + variables.update(this, share, s.variables); + } + + void set_domains( PyObject* domains ) + { + PyObject* ovalues; + int n = PyList_Size( domains ); + for(int var=0;var<n;++var) { + int i, nval; + ovalues = PyList_GetItem( domains, var ); + nval = PyList_Size( ovalues ); + int* vals = new int[nval]; + for(i=0;i<nval;++i) { + vals[i] = PyInt_AsLong( PyList_GetItem( ovalues, i ) ); + } + IntSet gvalues(vals,nval); + dom(this, variables[var], gvalues); + delete [] vals; + } + } + + void add_constraints( PyObject* desc, BoolVar& var ) + { + PyObject* type; + char* s_type; + type = PyList_GetItem( desc, 0 ); + s_type = PyString_AsString( type ); + if (strcmp(s_type, "eq")==0) { + add_equality( desc, var ); + } else if (strcmp(s_type, "eqv")==0) { + add_equivalence( desc, var ); + } else if (strcmp(s_type, "and")==0) { + add_and( desc, var ); + } else if (strcmp(s_type, "or")==0) { + add_or( desc, var ); + } + } + + long get_int( PyObject* lst, int index ) { + PyObject* val; + val = PyList_GetItem(lst, index); + return PyInt_AsLong(val); + } + + void add_equality( PyObject* desc, BoolVar& var ) { + long variable, value; + + variable = get_int( desc, 1 ); + value = get_int( desc, 2 ); + rel(this, variables[variable], IRT_EQ, value, var); + } + + void add_equivalence( PyObject* desc, BoolVar& var ) { + int len = PyList_Size(desc); + int var0 = get_int( desc, 1 ); + + for (int i=1;i<len-1;++i) { + int var1 = get_int(desc, i+1); + rel( this, variables[var0], IRT_EQ, variables[var1], var ); + } + } + + void add_and( PyObject* desc, BoolVar& var ) { + int len = PyList_Size(desc); + BoolVarArray terms(this, len-1,0,1); + for(int i=0;i<len-1;++i) { + PyObject* expr = PyList_GetItem(desc, i+1); + add_constraints( expr, terms[i] ); + } + rel(this, BOT_AND, terms, var); + } + + void add_or( PyObject* desc, BoolVar& var ) { + int len = PyList_Size(desc); + BoolVarArray terms(this, len-1,0,1); + for(int i=0;i<len-1;++i) { + PyObject* expr = PyList_GetItem(desc, i+1); + add_constraints( expr, terms[i] ); + } + rel(this, BOT_OR, terms, var); + } + + template <template<class> class Engine> + static void run( RqlContext& pb, Search::Stop* stop ) + { + double t0 = 0; + int i = pb.solutions; + //Timer t; + RqlSolver* s = new RqlSolver( pb ); + //t.start(); + unsigned int n_p = 0; + unsigned int n_b = 0; + if (s->status() != SS_FAILED) { + n_p = s->propagators(); + n_b = s->branchings(); + } + Search::Options opts; + //opts.c_d = pb.c_d; + //opts.a_d = pb.a_d; + opts.stop = stop; + Engine<RqlSolver> e(s, opts); + delete s; + do { + RqlSolver* ex = e.next(); + if (ex == NULL) + break; + ex->print(pb); + delete ex; + //t0 = t0 + t.stop(); + } while (--i != 0 && t0 < pb.time); + Search::Statistics stat = e.statistics(); + if (pb.verbosity) { + cout << endl; + cout << "Initial" << endl + << "\tpropagators: " << n_p << endl + << "\tbranchings: " << n_b << endl + << endl + << "Summary" << endl + //<< "\truntime: " << t.stop() << endl + << "\truntime: " << t0 << endl + << "\tsolutions: " << abs(static_cast<int>(pb.solutions) - i) << endl + << "\tpropagations: " << stat.propagate << endl + << "\tfailures: " << stat.fail << endl + << "\tclones: " << stat.clone << endl + << "\tcommits: " << stat.commit << endl + << "\tpeak memory: " + << static_cast<int>((stat.memory+1023) / 1024) << " KB" + << endl; + } + } + virtual void print(RqlContext& pb) { + PyObject *tuple, *ival; + tuple = PyTuple_New( pb.nvars ); + for(int i=0;i<pb.nvars;++i) { + ival = PyInt_FromLong( variables[i].val() ); + PyTuple_SetItem( tuple, i, ival ); + } + PyList_Append( pb.sols, tuple ); + } + virtual Space* copy(bool share) { + return new RqlSolver(share, *this); + } + +}; + +class FailTimeStop : public Search::Stop { +private: + Search::TimeStop *ts; + Search::FailStop *fs; +public: + FailTimeStop(int fails, int time):ts(0L),fs(0L) { + if (time>=0) + ts = new Search::TimeStop(time); + if (fails>=0) { + fs = new Search::FailStop(fails); + } + } + bool stop(const Search::Statistics& s) { + int sigs = PyErr_CheckSignals(); + bool fs_stop = false; + bool ts_stop = false; + if (fs) { + fs_stop = fs->stop(s); + } + if (ts) { + ts_stop = ts->stop(s); + } + return sigs || fs_stop || ts_stop; + } + /// Create appropriate stop-object + static Search::Stop* create(int fails, int time) { + return new FailTimeStop(fails, time); + } +}; + +static void _solve( RqlContext& ctx ) +{ + Search::Stop *stop = FailTimeStop::create(ctx.fails, ctx.time); + + RqlSolver::run<DFS>( ctx, stop ); +} + + +static PyObject * +rql_solve(PyObject *self, PyObject *args) +{ + PyObject* sols = 0L; + PyObject* constraints; + PyObject* domains; + long nvars, nvalues; + sols = PyList_New(0); + if (!PyArg_ParseTuple(args, "OiO", &domains, &nvalues, &constraints)) + return NULL; + nvars = PyList_Size(domains); + RqlContext ctx(nvars, domains, nvalues, constraints, sols ); + _solve( ctx ); + return sols; +} + +static PyMethodDef SolveRqlMethods[] = { + {"solve", rql_solve, METH_VARARGS, + "Solve RQL variable types problem."}, + {NULL, NULL, 0, NULL} /* Sentinel */ +}; + + +PyMODINIT_FUNC +initrql_solve(void) +{ + PyObject* m; + m = Py_InitModule("rql_solve", SolveRqlMethods); + if (m == NULL) + return; +}