# HG changeset patch # User ludal@logilab.fr # Date 1235090534 -3600 # Fri Feb 20 01:42:14 2009 +0100 # Node ID 2fc6914a8913af97f7fa45b4dbaf6af117f9870e # Parent bdcec2ec4b883a0fb1de1a958ada4d7e122d2852 review, comments and error checking diff --git a/gecode_solver.cpp b/gecode_solver.cpp --- a/gecode_solver.cpp +++ b/gecode_solver.cpp @@ -1,6 +1,7 @@ #include <Python.h> #include <iostream> #include <string.h> +#include <exception> #include "gecode/kernel.hh" #include "gecode/int.hh" #include "gecode/search.hh" @@ -8,19 +9,81 @@ using namespace std; using namespace Gecode; +#define USE_CLOCK +#ifdef USE_CLOCK +#include <ctime> + +/// Timer interface stolen from gecode examples +class Timer { +private: + clock_t t0; +public: + void start(void); + double stop(void); +}; + +forceinline void +Timer::start(void) { + t0 = clock(); +} + +forceinline double +Timer::stop(void) { + return (static_cast<double>(clock()-t0) / CLOCKS_PER_SEC) * 1000.0; +} +#else +#include <sys/time.h> +#include <unistd.h> + +/// Timer interface stolen from gecode examples +class Timer { +private: + struct timeval t0; +public: + void start(void); + double stop(void); +}; + +forceinline void +Timer::start(void) { + gettimeofday( &t0, NULL ); +} +forceinline double +Timer::stop(void) { + struct timeval t1; + gettimeofday( &t1, NULL ); + return (t1.tv_sec - t0.tv_sec)+1e-6*(t1.tv_usec - t0.tv_usec); +} +#endif + +enum { + _AND = 0, + _OR = 1, + _EQ = 2, + _EQV = 3 +}; + +class RqlError : public exception { +}; + class RqlContext { + /** Context holding the problem for solving Rql constraints + we keep the info as Python objects and parse the problem + during the creation of the Gecode problem + */ 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) + solutions(-1), // return every solutions + time(1000), // time limit in case the problem is too big + fails(-1), // ?? used by GecodeStop ... + nvars(nvars), // Number of variables + nvalues(nvalues), // Number of values + constraints(constraints), // A python list, holding the root of the problem + sols(sols), // an empty list that will receive the solutions + domains(domains), // A PyList of PyList, one for each var, + // holding the allowable integer values + verbosity(false) // can help debugging { } @@ -36,17 +99,45 @@ }; class RqlSolver : public Space { +/* A gecode Space + this is a strange beast that requires special methods and + behavior (mostly, copy and (bool,share,space) constructor +*/ protected: + /* The variables we try to find values for + these are the only 'public' variable of the + problem. + + we use a lot more intermediate variables but + they shouldn't be member of the space + */ IntVarArray variables; + public: - RqlSolver() {} RqlSolver(const RqlContext& pb): - variables(this,pb.nvars,0,pb.nvalues-1) + variables(this, // all gecode variable keep a reference to the space + pb.nvars, // number of variables + 0, // minimum domain value + pb.nvalues-1) // max domain value (included) { + /* Since we manipulate Boolean expression and + we need to assign truth value to subexpression + eg (a+b)*(c+d) will be translated as : + root = x1 * x2 + x1 = a+b + x2 = c+d + root = True + */ BoolVar root(this, 1,1); + set_domains( pb.domains ); add_constraints( pb.constraints, root ); - + + /* the branching strategy, there must be one, + changing it might improve performance, but + in out case, we almost never propagate (ie + gecode solves the problem during its creation) + */ branch(this, variables, INT_VAR_NONE, INT_VAL_MIN); } @@ -54,20 +145,45 @@ RqlSolver(bool share, RqlSolver& s) : Space(share,s) { + /* this is necessary for the solver to fork space + while branching + */ variables.update(this, share, s.variables); } void set_domains( PyObject* domains ) { PyObject* ovalues; + if (!PyList_Check(domains)) { + throw RqlError(); + } int n = PyList_Size( domains ); - for(int var=0;var<n;++var) { + for(int var=0 ;var<n; ++var) { + /* iterate of domains which should contains + list of values + domains[0] contains possible values for var[0]... + */ int i, nval; ovalues = PyList_GetItem( domains, var ); + if (!PyList_Check(ovalues)) { + throw RqlError(); + } nval = PyList_Size( ovalues ); + + /* It's a bit cumbersome to construct an IntSet, but + it's the only way to reduce an integer domain to + a discrete set + */ int* vals = new int[nval]; for(i=0;i<nval;++i) { + //refcount ok, borrowed ref vals[i] = PyInt_AsLong( PyList_GetItem( ovalues, i ) ); + if (vals[i]<0) { + /* we don't have negative values and PyInt_AsLong returns + -1 if the object is not an Int */ + delete [] vals; + throw RqlError(); + } } IntSet gvalues(vals,nval); dom(this, variables[var], gvalues); @@ -75,50 +191,87 @@ } } + /* Dispatch method from Node to specific node type */ 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) { + long type; + + if (!PyList_Check(desc)) { + throw RqlError(); + } + /* the first element of each list (node) is + a symbolic Int from _AND, _OR, _EQ, _EQV + */ + type = PyInt_AsLong( PyList_GetItem( desc, 0 ) ); + + switch(type) { + case _AND: + add_and( desc, var ); + break; + case _OR: + add_or( desc, var ); + break; + case _EQ: add_equality( desc, var ); - } else if (strcmp(s_type, "eqv")==0) { + break; + case _EQV: 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 ); + break; + default: + throw RqlError(); } } - long get_int( PyObject* lst, int index ) { + /* retrieve an int from a list, throw error if int is <0 */ + long get_uint( PyObject* lst, int index ) { PyObject* val; val = PyList_GetItem(lst, index); + if (val<0) { + throw RqlError(); + } return PyInt_AsLong(val); } - void add_equality( PyObject* desc, BoolVar& var ) { + /* post gecode condition for Var == Value + we can't use domain restriction since this + condition can be part of an OR clause + + so we post (var == value) <=> expr_value + */ + void add_equality( PyObject* desc, BoolVar& expr_value ) { long variable, value; - variable = get_int( desc, 1 ); - value = get_int( desc, 2 ); - rel(this, variables[variable], IRT_EQ, value, var); + variable = get_uint( desc, 1 ); + value = get_uint( desc, 2 ); + rel(this, variables[variable], IRT_EQ, value, expr_value); } - void add_equivalence( PyObject* desc, BoolVar& var ) { - int len = PyList_Size(desc); - int var0 = get_int( desc, 1 ); + /* post gecode condition for Var[i] == Var[j] ... == Var[k] + there's no operator for assigning chained equality to boolean + + so we post for 1<=i<=N (var[0] == var[i]) <=> bool[i] + and bool[1] & ... & bool[N] <=> expr_value + that means if all vars are equal expr_value is true + if all vars are different from var[0] expr_value is false + if some are equals and some false, the constraint is unsatisfiable + */ + void add_equivalence( PyObject* desc, BoolVar& expr_value ) { + int len = PyList_Size(desc); + int var0 = get_uint( desc, 1 ); + BoolVarArray terms(this, len-2,0,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 ); + int var1 = get_uint(desc, i+1); + rel( this, variables[var0], IRT_EQ, variables[var1], terms[i-1] ); } + rel(this, BOT_AND, terms, expr_value); } + /* simple and relation between nodes */ 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] ); @@ -126,9 +279,11 @@ rel(this, BOT_AND, terms, var); } + /* simple or relation between nodes */ 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] ); @@ -139,60 +294,71 @@ 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(); + 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->add_new_solution(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 + << "\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; + } } - 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) { + + + /* We append each solutions to `sols` as a + tuple `t` of the values assigned to each var + that is t[i] = solution for var[i] + */ + virtual void add_new_solution(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 ); } + + /* another function need by gecode kernel */ virtual Space* copy(bool share) { return new RqlSolver(share, *this); } @@ -244,12 +410,21 @@ 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 ); + sols = PyList_New(0); + try { + if (!PyList_Check(domains)) { + throw RqlError(); + } + nvars = PyList_Size(domains); + RqlContext ctx(nvars, domains, nvalues, constraints, sols ); + _solve( ctx ); + } catch(RqlError& e) { + Py_DECREF(sols); + PyErr_SetString(PyExc_RuntimeError, "Error parsing constraints"); + return NULL; + }; return sols; }