Constrained Levenberg-Marquardt using the InMin library

This is the equivalent program to the own shown in this old blog post, but implemented using the new InMin library. The program demonstrates Levenberg-Marquardt fitting of a function with a box constraint on the parameters.

// Bojan Nikolic <bojan@bnikolic.co.uk>
//
// Example of constrained Levenberg-Marquardt optimisiation using the
// inmin library http://www.bnikolic.co.uk/inmin/inmin-library.html

#include <vector>
#include <cmath>
#include <iostream>

#include <boost/assign/list_of.hpp>

#include "inmin_lm.h"

/** A toy second-degree polynomial model
 */
struct PolyModel {

  /// The model parameters
  double a, b, c;

  /// These are the observation points, i.e., the points at which the
  /// model should be evaluated
  std::vector<double> x;

  // The observed points
  std::vector<double> obs;

  PolyModel(const std::vector<double> &x):
    a(1.0),
    b(2.0),
    c(3.0),
    x(x)
  {
  }

  void operator() (std::vector<double> &res)
  {
    const size_t n=x.size();
    res.resize(n);
    for(size_t i=0; i<n; ++i)
      res[i]=a+b*x[i]+c*pow(x[i],2);
  }
};


/// This function interfaces between the imnin library and simple C++
/// code. It is hard coded for this particular problem.
void LMHelper(const double *x,
              double *res,
              void *data)
{
  PolyModel *pmodel=reinterpret_cast<PolyModel*>(data);
  pmodel->a=x[0];
  pmodel->b=x[1];
  pmodel->c=x[2];

  std::cout<<">> Interim point: "
           <<x[0]<<","
           <<x[1]<<","
           <<x[2]<<std::endl;


  std::vector<double> scratch;

  (*pmodel)(scratch);

  for (size_t i=0; i<pmodel->obs.size(); ++i)
  {
    res[i]=scratch[i]-pmodel->obs[i];
  }
}

void minimise(PolyModel &model,
              std::vector<double> &obs,
              std::vector<double> &lowerbounds,
              std::vector<double> &upperbounds)
{

  inmin_lm_in in;
  memset(&in, 0, sizeof(inmin_lm_in));
  in.N=3;
  in.M=obs.size();
  in.f=LMHelper;

  in.ftol=1e-8;
  in.xtol=1e-8;
  in.gtol=1e-8;
  in.maxfev=100;

  model.obs=obs;

  double box[6];
  for (size_t i=0; i<3; ++i)
  {
     box[i*2]=lowerbounds[i];
     box[i*2+1]=upperbounds[i];
  }
  in.box=box;

  std::vector<double> res(3);
  inmin_lm_out out;
  out.x=&res[0];
  out.covar=NULL;

  double ic[] = {0.0,0.0,1.0};

  inmin_lm_run(&in,
               ic,
               &out,
               &model);

  std::cout<<"The final result is:"
           <<res[0]<<","
           <<res[1]<<","
           <<res[2]<<std::endl;

};




int main(void)
{
  // This is the list of values of x at which the unknown function is
  // observed
  std::vector<double> x = boost::assign::list_of
    (-10)(-8)(-5)(1)(4)(10)(40);

  PolyModel model(x);

  // The observed values corresponding to the x coordinates above. For
  // this problem simply create mock data using the model itself
  std::vector<double> obs;
  model(obs);

  std::cout<<"Simulated observation: ";
  for (size_t i=0; i<obs.size(); ++i)
  {
    std::cout<<obs[i]<<",";
  }
  std::cout<<std::endl;

  std::vector<double> lower = boost::assign::list_of
    (-10)(-10)(-10);

  // Make the upper bound of the first model parameter smaller than
  // 1.0 to show off the constrained minimisation
  std::vector<double> upper = boost::assign::list_of
    (0.5)(10)(10);


  // Run the minimiser
  minimise(model,
           obs,
           lower,
           upper);

}