pythonc++swigor-tools

Using Python callbacks via SWIG in OR Tools


I am hoping that this is a simple SWIG issue. I am using the Google OR-Tools optimization library. It is a C++ library that is wrapped in SWIG (which I know little about). I am having great difficult getting a Python callback function to work. There is a C++ function

DecisionBuilder* MakePhase(const std::vector<IntVar*>& vars,
                           IndexEvaluator1* var_evaluator,
                           IntValueStrategy val_str);

along with

typedef ResultCallback1<int64, int64> IndexEvaluator1;

and the relevant SWIG (I believe) is

  DecisionBuilder* VarEvalValStrPhase(
      const std::vector<IntVar*>& vars,
      ResultCallback1<int64, int64>* var_evaluator,
      operations_research::Solver::IntValueStrategy val_str) {
    return self->MakePhase(vars, var_evaluator, val_str);
  }

and in another SWIG file we have

%{
static int64 PyCallback1Int64Int64(PyObject* pyfunc, int64 i) {
  // () needed to force creation of one-element tuple
  PyObject* pyresult = PyEval_CallFunction(pyfunc, "(l)", static_cast<long>(i));
  int64 result = 0;
  if (!pyresult) {
    PyErr_SetString(PyExc_RuntimeError,
                    "ResultCallback1<int64, int64> invocation failed.");
  } else {
    result = PyInt_AsLong(pyresult);
    Py_DECREF(pyresult);
  }
  return result;
}
%}

%typemap(in) ResultCallback1<int64, int64>* {
  if (!PyCallable_Check($input)) {
    PyErr_SetString(PyExc_TypeError, "Need a callable object!");
    SWIG_fail;
  }
  $1 = NewPermanentCallback(&PyCallback1Int64Int64, $input);
}

In my Python module I have defined a function, Run1, as follows (and here is where part of me thinks there should be some type casts, but I gather that is not the Python way):

def Run1(index1):
    return index1

and set

selector_callback = Run1
solver = pywrapcp.Solver("graph-coloring")

Finally, I call

solver.Phase(nodes,
             selector_callback,
             solver.INT_VALUE_DEFAULT)

and here alas is where things go kablooie, I always get the following error:

  File "C:\dev\Python27\lib\site-packages\ortools-1.3853-py2.7-win-amd64.egg\ortools\constraint_solver\pywrapcp.py", line 457, in Phase
    def Phase(self, *args): return _pywrapcp.Solver_Phase(self, *args)
NotImplementedError: Wrong number or type of arguments for overloaded function 'Solver_Phase'.
  Possible C/C++ prototypes are:
    operations_research::Solver::MakePhase(std::vector< operations_research::IntVar *,std::allocator< operations_research::IntVar * > > const &,operations_research::Solver::IntVarStrategy,operations_research::Solver::IntValueStrategy)
    operations_research::Solver::MakePhase(std::vector< operations_research::IntervalVar *,std::allocator< operations_research::IntervalVar * > > const &,operations_research::Solver::IntervalStrategy)
    operations_research::Solver::MakePhase(std::vector< operations_research::SequenceVar *,std::allocator< operations_research::SequenceVar * > > const &,operations_research::Solver::SequenceStrategy)

The difficulty is with the callback function in the second argument; if I use one of the built-in values instead of the callback the operation is successful. But, I do need to have my own function in there.

I am not importing any SWIG files in my module. Do I need to do this?


Solution

  • So after days, I found the answer. If I call

    solver.VarEvalValStrPhase(nodes,
                 selector_callback,
                 solver.INT_VALUE_DEFAULT)
    

    instead of the standard function name Phase referenced throughout the manuals, it works. If I were to use another argument combination I'd have to use another function name I believe. It appears that overloading fails in this case. Which is fine, but a warning from the developers would have been nice.