Program Listing for File python_potential_wrapper.hpp#

↰ Return to documentation for file (source/pele/python_potential_wrapper.hpp)

#ifndef _PELE_POTENTIAL_FUNCTION_H
#define _PELE_POTENTIAL_FUNCTION_H
#include <Python.h>
#include <iostream>
#include <numpy/arrayobject.h>
#include <stdexcept>

#include "array.hpp"
#include "base_potential.hpp"

//#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION

namespace pele {

/*
class PotentialFunction : public BasePotential
{
    public:
        typedef double EnergyCallback(Array<double>, void *);
        typedef double EnergyGradientCallback(Array<double>, Array<double>, void
*);

        PotentialFunction(EnergyCallback *get_energy, EnergyGradientCallback
*get_energy_gradient, void *userdata) :    _get_energy(get_energy),
_get_energy_gradient(get_energy_gradient), _userdata(userdata) {}

        virtual double get_energy(Array<double> const & x) { return
(*_get_energy)(x, _userdata); } ; virtual double
get_energy_gradient(Array<double> const & x, Array<double> & grad) {  return
(*_get_energy_gradient)(x, grad, _userdata); }

    private:
        EnergyCallback *_get_energy;
        EnergyGradientCallback *_get_energy_gradient;
        void *_userdata;
};
*/

class PythonPotential : public BasePotential {
  PyObject *_potential;

 public:
  PythonPotential(PyObject *potential) : _potential(potential) {
    Py_XINCREF(_potential);

    // import the the numpy array API.  This is commented because
    // it is now done in the cython module file.  It is possible I
    // need to define some preprocessor variables like
    // NO_IMPORT_ARRAY and PY_ARRAY_UNIQUE_SYMBOL, but the
    // documentation is a bit confusing.
    // http://docs.scipy.org/doc/numpy/reference/c-api.array.html
    // import_array();
  }

  virtual ~PythonPotential() { Py_XDECREF(_potential); }

  virtual double get_energy(Array<double> const &x) {
    // create a numpy array from x
    // copy the data from x because becase the python object might
    // live longer than the data in x.data
    // otherwise this alone would do PyObject * numpyx =
    // PyArray_SimpleNewFromData(1, &N, NPY_DOUBLE, x.data());
    npy_intp N = (npy_intp)x.size();
    PyObject *numpyx = PyArray_SimpleNew(1, &N, NPY_DOUBLE);
    if (!numpyx) {
      std::cerr << "created numpy object is NULL\n";
      throw std::runtime_error("created numpy object is NULL\n");
    }
    double *xdata = (double *)PyArray_DATA((PyArrayObject *)numpyx);
    for (size_t i = 0; i < x.size(); ++i) {
      xdata[i] = x[i];
    }

// call the function getEnergy
#if PY_MAJOR_VERSION >= 3
    PyObject *name = PyUnicode_FromString("getEnergy");
#else
    PyObject *name = PyString_FromString("getEnergy");
#endif
    PyObject *returnval =
        PyObject_CallMethodObjArgs(_potential, name, numpyx, NULL);
    Py_XDECREF(name);
    Py_XDECREF(numpyx);
    if (!returnval) {
      // parse error
      throw std::runtime_error("getEnergy returned NULL");
    }
    // std::cout << "    done calling get energy\n";

    // parse the returned tuple
    double energy = PyFloat_AsDouble(returnval);
    Py_XDECREF(returnval);
    // TODO: error checking
    if (PyErr_Occurred()) {
      PyErr_Clear();
      PyErr_SetString(
          PyExc_TypeError,
          "return value of getEnergy could not be converted to float");
      throw std::runtime_error(
          "return value of getEnergy could not be converted to float");
    }

    return energy;
  }

  virtual double get_energy_gradient(Array<double> const &x,
                                     Array<double> &grad) {
    if (x.size() != grad.size()) {
      throw std::invalid_argument("grad.size() be the same as x.size()");
    }

    // create a numpy array from x
    // copy the data from x because because the python object might
    // live longer than the data in x.data
    npy_intp N = (npy_intp)x.size();
    PyObject *numpyx = PyArray_SimpleNew(1, &N, NPY_DOUBLE);
    if (!numpyx) {
      std::cerr << "created numpy object is NULL\n";
      throw std::runtime_error("created numpy object is NULL\n");
    }
    double *numpyx_data = (double *)PyArray_DATA((PyArrayObject *)numpyx);
    for (size_t i = 0; i < x.size(); ++i) {
      numpyx_data[i] = x[i];
    }

// call the function getEnergy
#if PY_MAJOR_VERSION >= 3
    PyObject *name = PyUnicode_FromString("getEnergyGradient");
#else
    PyObject *name = PyString_FromString("getEnergyGradient");
#endif
    PyObject *returnval =
        PyObject_CallMethodObjArgs(_potential, name, numpyx, NULL);
    Py_XDECREF(numpyx);
    Py_XDECREF(name);
    if (!returnval) {
      // parse error
      throw std::runtime_error("getEnergyGradient return is NULL");
    }

    // parse the returned tuple into a doulbe and a numpy array
    double energy;
    PyObject *npgrad_returned;  // the reference count for this does not need to
                                // be decreased
    if (!PyArg_ParseTuple(returnval, "dO", &energy, &npgrad_returned)) {
      Py_XDECREF(returnval);
      throw std::runtime_error("failed to parse the tuple");
    }

    // convert the returned gradient into an array which I know I
    // can safely use as a double array.
    // note: NPY_CARRAY is for numpy version 1.6, for later version use
    // NPY_ARRAY_CARRAY
    PyObject *npgrad_safe =
        PyArray_FromAny(npgrad_returned, PyArray_DescrFromType(NPY_DOUBLE), 1,
                        1, NPY_ARRAY_CARRAY, NULL);
    if (!npgrad_safe) {
      Py_XDECREF(returnval);
      throw std::runtime_error(
          "gradient returned by getEnergyGradient could "
          "not be converted to numpy double array");
    }
    // check the size of the gradient array
    if (static_cast<size_t>(PyArray_Size(npgrad_safe)) != grad.size()) {
      PyErr_SetString(PyExc_IndexError,
                      "gradient returned by getEnergyGradient has wrong size.");
      Py_XDECREF(returnval);
      Py_XDECREF(npgrad_safe);
      throw std::runtime_error(
          "return value of getEnergy could not be converted to float");
    }

    // copy the gradient into grad
    double *gdata = (double *)PyArray_DATA((PyArrayObject *)npgrad_safe);
    for (size_t i = 0; i < grad.size(); ++i) {
      grad[i] = gdata[i];
    }
    // std::cout << "    done copying grad\n";

    // decrease referenece counts on Python objects
    Py_XDECREF(returnval);
    Py_XDECREF(npgrad_safe);

    return energy;
  }
};
}  // namespace pele
#endif