Compiling L-BFGS-B on Mac OS X and mixing with c++

Alec Jacobson

August 17, 2010

weblog/

A new optimization problem required a black box solver that could impose constant bounds on the variables. We opted to use an implementation of L-BFGS-B which does exactly that. I'm working on a Mac OS X, 10.6, machine and our code base is in c++. So I needed to compile the L-BFGS-B code, which is in fortran and be able to call it from cpp files.

Compiling L-BFGS-B

Note: the following solution requires that you have a proper f2c installed. I used these steps to compile a universal (32-bit and 64-bit) f2c. Download the source. You'll notice there are four *.f files: routines.f contains the solver driver*.f contain examples showing you how to set up the parameters to the solver The Makefile included in the source will try to compile all four files into *.o object files then make executables out of each example. When I try:
make
it seems that while the object files for each *.f file get created, my f77 doesn't understand how to make the executables. I get errors like this:
f77 -O driver3.o routines.o -o x.lbfgsb3
invalid parameter driver3.o
Undefined symbols:
  "_MAIN__", referenced from:
      _main in libf2c.a(main.o)
ld: symbol(s) not found
collect2: ld returned 1 exit status
This would be OK because I don't really care about the example executables. But in order to figure out how to call subroutines from routines.o from C/C++, I'll need to know that they work in the first place. Moreover, f77 was creating x86_64 object files and my code base is i386 (though I'm trying my hardest to build the dependencies as universals). After digging around in the F77 script I first found where I could tell the compiler to make universal binaries. I do that by setting the CFLAGS environment variable. So as a replacement for the faulty Makefile, I've composed this script which should do the same thing but make everything universal for the mac: Save this in the same directory as the *.f files as make_all.sh:
#!/bin/bash

# save old CFLAGS value
OLD_CFLAGS=$CFLAGS;

# set CFLAGS used by F77 to contain gcc parameters necessary to produce
# universal object files
export CFLAGS="$CFLAGS -arch i386 -arch x86_64";

# compile the algorithm as a universal object file
f77 -O -c -o routines.o routines.f

# compile each of the examples as universal executables
for i in 1 2 3
do
  # translate driver*.f to driver*.c
  f2c driver$i.f
  # compile driver*.c to universal binary driver*.o
  cc $CFLAGS -c -o driver$i.o driver$i.c
  # link driver*.o and routines.o into unversal binary executable x.lbfgsb*
  cc $CFLAGS driver$i.o routines.o -lf2c -lm -o x.lbfgsb$i
  # remove driver*.c
  rm driver$i.c
done

# restore old CFLAGS value
export CFLAGS='$OLD_CFLAGS';
Call it by issuing:
bash make_all.sh
You can test that everything went ok by executing ./x.lbfgsb1, ./x.lbfgsb2, ./x.lbfgsb3.

Mixing with C++

Now that we have a universal routines.o, we would like to be able to call the algorithm subroutines from a c++ program. I could use f2c to "translate driver1.f" into c++, but all it does is translate it into c and put it in an extern "C" block. Instead I used f2c to translate driver1.f into c code, then methodically human-translated that into a (more) human-readable cpp program. Below is my human readable version of driver1.f. Save it in a file called driver1.cpp:
/* driver1.f -- translated by f2c (version 20090411).
   You must link the resulting object file with libf2c:
	on Microsoft Windows system, link with libf2c.lib;
	on Linux or Unix systems, link with .../path/to/libf2c.a -lm
	or, if you install libf2c.a in a standard place, with -lf2c -lm
	-- in that order, at the end of the command line, as in
		cc *.o -lf2c -lm
	Source for libf2c is in /netlib/f2c/libf2c.zip, e.g.,

		http://www.netlib.org/f2c/libf2c.zip
*/

// Compile this with
// !c++ routines.o -lf2c -lm -o driver1cpp driver1.cpp

#include "f2c.h"
#include "stdio.h"

/*                             DRIVER 1 */
/*     -------------------------------------------------------------- */
/*                SIMPLE DRIVER FOR L-BFGS-B (version 2.1) */
/*     -------------------------------------------------------------- */

/*        L-BFGS-B is a code for solving large nonlinear optimization */
/*             problems with simple bounds on the variables. */

/*        The code can also be used for unconstrained problems and is */
/*        as efficient for these problems as the earlier limited memory */
/*                          code L-BFGS. */

/*        This is the simplest driver in the package. It uses all the */
/*                    default settings of the code. */


/*     References: */

/*        [1] R. H. Byrd, P. Lu, J. Nocedal and C. Zhu, ``A limited */
/*        memory algorithm for bound constrained optimization'', */
/*        SIAM J. Scientific Computing 16 (1995), no. 5, pp. 1190--1208. */

/*        [2] C. Zhu, R.H. Byrd, P. Lu, J. Nocedal, ``L-BFGS-B: FORTRAN */
/*        Subroutines for Large Scale Bound Constrained Optimization'' */
/*        Tech. Report, NAM-11, EECS Department, Northwestern University, */
/*        1994. */


/*          (Postscript files of these papers are available via anonymous */
/*           ftp to eecs.nwu.edu in the directory pub/lbfgs/lbfgs_bcm.) */

/*                              *  *  * */

/*        NEOS, November 1994. (Latest revision June 1996.) */
/*        Optimization Technology Center. */
/*        Argonne National Laboratory and Northwestern University. */
/*        Written by */
/*                           Ciyou Zhu */
/*        in collaboration with R.H. Byrd, P. Lu-Chen and J. Nocedal. */

/*     NOTE: The user should adapt the subroutine 'timer' if 'etime' is */
/*           not available on the system.  An example for system */
/*           AIX Version 3.2 is available at the end of this driver. */

    // These are fortran wrappers and must be in C
    extern "C" {
      int setulb_(integer *, integer *, doublereal *, 
	    doublereal *, doublereal *, integer *, doublereal *, doublereal *,
	     doublereal *, doublereal *, doublereal *, integer *, char *, 
	    integer *, char *, logical *, integer *, doublereal *, ftnlen, 
	    ftnlen);

      /* Builtin functions */
      integer s_wsfe(cilist *), e_wsfe(void);
      /* Subroutine */ int s_copy(char *, char *, ftnlen, ftnlen);
      integer s_cmp(char *, char *, ftnlen, ftnlen);
      /* Subroutine */ int s_stop(char *, ftnlen);
    }

/*     ************** */
/* Main program */ 
int main(void)
{
    /* Local variables */
    doublereal f, g[1024];
    doublereal l[1024];
    integer m, n;
    doublereal u[1024], x[1024], t1, t2, wa[42584];
    integer nbd[1024], iwa[3072];
    char task[60];
    doublereal factr;
    char csave[60];
    doublereal dsave[29];
    integer isave[44];
    logical lsave[4];
    doublereal pgtol;
    integer iprint;


/*     This simple driver demonstrates how to call the L-BFGS-B code to */
/*       solve a sample problem (the extended Rosenbrock function */
/*       subject to bounds on the variables). The dimension n of this */
/*       problem is variable. */
/*        nmax is the dimension of the largest problem to be solved. */
/*        mmax is the maximum number of limited memory corrections. */
/*     Declare the variables needed by the code. */
/*       A description of all these variables is given at the end of */
/*       the driver. */
/*     Declare a few additional variables for this sample problem. */
/*     We wish to have output at every iteration. */
    iprint = 1;
/*     We specify the tolerances in the stopping criteria. */
    factr = 1e7;
    pgtol = 1e-5;
/*     We specify the dimension n of the sample problem and the number */
/*        m of limited memory corrections stored.  (n and m should not */
/*        exceed the limits nmax and mmax respectively.) */
    n = 25;
    m = 5;
/*     We now provide nbd which defines the bounds on the variables: */
/*                    l   specifies the lower bounds, */
/*                    u   specifies the upper bounds. */
/*     First set bounds on the odd-numbered variables. */
    for(int i = 0; i < n; i+=2){
	nbd[i] = 2;
	l[i] = 1.;
	u[i] = 100.;
    }
/*     Next set bounds on the even-numbered variables. */
    for(int i = 1; i < n; i+=2){
	nbd[i] = 2;
	l[i] = -100.;
	u[i] = 100.;
    }
/*     We now define the starting point. */
    for(int i = 1; i < n; i++){
	x[i] = 3.;
    }

    printf("     Solving sample problem.\n");
    printf("f = 0.0 at the optimal solution.)\n");
/*     We start the iteration by initializing task. */

    s_copy(task, "START", (ftnlen)60, (ftnlen)5);
/*        ------- the beginning of the loop ---------- */
    while(true){
/*       This is the call to the L-BFGS-B code. */
      setulb_(&n, &m, x, l, u, nbd, &f, g, &factr, &pgtol, wa, iwa, task, &
              iprint, csave, lsave, isave, dsave, (ftnlen)60, (ftnlen)60);

      if (s_cmp(task, "FG", (ftnlen)2, (ftnlen)2) == 0) {
/*          the minimization routine has returned to request the */
/*          function f and gradient g values at the current x. */

/*          Compute function value f for the sample problem. */
          f = .25*(x[0]-1.)*(x[0]-1.);
          for(int i = 1;i<n;i++){
              f += (x[i] - x[i-1]*x[i-1])*(x[i] - x[i-1]*x[i-1]);
          }
          f *= 4.;

/*          Compute gradient g for the sample problem. */
          t1 = x[1] - x[0]*x[0];
          g[0] = (x[0] - 1.) * 2. - x[0] * 16. * t1;
          for(int i = 1;i<n-1;i++){
              t2 = t1;
              t1=x[i+1]-x[i]*x[i];
              g[i] = 8.0*t2-16.*x[i]*t1;
          }
          g[n - 1] = t1 * 8.;
/*        go back to the minimization routine. */
      }else if (s_cmp(task, "NEW_X", (ftnlen)5, (ftnlen)5) == 0) {
/*        the minimization routine has returned with a new iterate, */
/*        and we have opted to continue the iteration. */
      }else{
        // anything other than "FG" or "NEW_X" means termination
/*         If task is neither FG nor NEW_X we terminate execution. */
        break;
      }
    }
    return 0;
}
Note: You'll get runtime errors if you find/replace all of the "integer" keywords with "int". I guess these are defined by f2c.h. All I know is, leave them alone. Compile this with:
c++ -arch x86_64 -arch i386 routines.o -lf2c -lm -o driver1cpp driver1.cpp
And you should be able to execute your cpp program with
./driver1cpp