To this end I created a Python module I can import into Python that will optimize this problem for us. From Python, I wanted to be able to send my initial guess of p, my measured data, and the x-values these measurements were taken at and have returned the parameter values as well as the optimization information. To interface with C++ from Python, I made use of the Numpy C-API. You can use Cython for this as well and it would probably be easier to implement but I haven't tried that yet. Using Microsoft's Visual Studio program I created a static library of the levmar routine in a separate project. I then reference that library in this DLL (pyd) module. My C++ code is as follows:
#include "Python.h"
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#include "arrayobject.h"
#include <iostream>
#include <iomanip>
#include <cmath>
#include <cfloat>
#include "levmar.h"
using namespace std;
struct argstruct {
double *xval;
};
double lorentz(double *p, double x) {
return p[2] / (1.0 + pow((x / p[0] - 1.0), 4) * pow(p[1], 2));
}
void lorentzfit(double *p, double *x, int m, int n, void *data) {
register int i;
struct argstruct *xp;
double xval;
xp = (struct argstruct *) data;
for(i=0; i<n i++) {
xval="xp-">xval[i];
x[i] = lorentz(p, xval);
}
}
static PyObject *levmarfit(PyObject *self, PyObject *args) {
PyArrayObject *xval, *yval, *p0, *results;
npy_intp *dimx, *dimy, *dimp;
argstruct adata;
int ndx, ndy, ndp, ret, parse_ind;
double *x, *p;
double opts[LM_OPTS_SZ] = {LM_INIT_MU, 1E-15, 1E-15, 1E-20, -LM_DIFF_DELTA};
double info[LM_INFO_SZ];
parse_ind = PyArg_ParseTuple(args, "O!O!O!", &PyArray_Type, &xval, &PyArray_Type, &yval, &PyArray_Type, &p0);
if (!parse_ind) {
PyErr_SetString(PyExc_StandardError, "Parsing of function arguments failed. Check function inputs.");
return NULL;
} else {
if (PyArray_TYPE(xval) != NPY_DOUBLE || PyArray_TYPE(yval) != NPY_DOUBLE || PyArray_TYPE(p0) != NPY_DOUBLE) {
PyErr_SetString(PyExc_TypeError, "Argument inputs are not of type \"double\".");
return NULL;
}
}
ndx = PyArray_NDIM(xval);
ndy = PyArray_NDIM(yval);
ndp = PyArray_NDIM(p0);
if (ndx != 1 || ndy != 1 || ndp != 1) {
PyErr_SetString(PyExc_AssertionError, "The dimension of the function input(s) not equal to 1");
return NULL;
}
dimx = PyArray_SHAPE(xval);
dimy = PyArray_SHAPE(yval);
dimp = PyArray_SHAPE(p0);
if (dimx[0] != dimy[0]) {
PyErr_SetString(PyExc_AssertionError, "The length of x and y vectors are not equal");
return NULL;
}
if (dimp[0] != 3) {
PyErr_SetString(PyExc_AssertionError, "Length of initial parameter vector not equal to 3.\
\nThe function being fitted takes 3 parameters.");
return NULL;
}
adata.xval = (double *) PyArray_DATA(xval);
x = (double *) PyArray_DATA(yval);
p = (double *) PyArray_DATA(p0);
ret = dlevmar_dif(lorentzfit, p, x, (int)dimp[0], (int)dimy[0], 100000, opts, info, NULL, NULL, (void *) &adata);
results = (PyArrayObject *) PyArray_SimpleNewFromData(1, dimp, NPY_DOUBLE, (void *) p);
return Py_BuildValue("(O,[d,d,i,i,i,i])",results, info[0], info[1], (int)info[5], (int)info[6], (int)info[7], (int)info[9]);
}
static PyMethodDef pylevmarmethods[] = {
{"levmarfit", levmarfit, METH_VARARGS, "Joel Vroom"},
{NULL, NULL, 0, NULL}
};
PyMODINIT_FUNC initpylevmar1(void) {
Py_InitModule("pylevmar1", pylevmarmethods);
import_array();
}
Granted, it is a lot of code for something you could easily do in Python but, in some cases, you are dealing with complicated models (eg. Heston's semi-analytical option pricing model) that simply take way too long in Python. When this code is compiled, you can use the resultant pylevmar1.pyd file as follows (see lines #3 and #34):import numpy as np
from scipy.optimize import leastsq
import matplotlib.pyplot as plt
import pylevmar1
np.random.seed(22)
def lorentz(p,x):
return p[2] / (1.0 + (x / p[0] - 1.0)**4 * p[1]**2)
def errorfunc(p,x,z):
return lorentz(p,x)-z
p = np.array([0.5, 0.25, 1.0], dtype=np.double)
x = np.linspace(-1.5, 2.5, num=30, endpoint=True)
noise = np.random.randn(30) * 0.05
z = lorentz(p,x)
noisyz = z + noise
p0 = np.array([-2.0, -4.0, 6.8], dtype=np.double) #Initial guess
##---------------------------------------------------------------------------##
## Scipy.optimize.leastsq
solp, ier = leastsq(errorfunc,
p0,
args=(x,noisyz),
Dfun=None,
full_output=False,
ftol=1e-15,
xtol=1e-15,
maxfev=100000,
epsfcn=1e-20,
factor=0.1)
##---------------------------------------------------------------------------##
## C++ Levmar
(sol, info) = pylevmar1.levmarfit(x, noisyz, p0)
##---------------------------------------------------------------------------##
Here I am comparing my C++ levmar result with the scipy.optimize.leastsq result. Both results match and yield:
p = array([ 0.50708339, 0.26618568, 1.02169709])
When I throw these two routine's in a loop and execute them 10,000 times (with different noise) you can see the speed improvement (183x):
Scipy.optimize.leastsq runtime.....263.484s
C++ Levmar runtime........................1.439s

No comments:
Post a Comment