C ExampleΒΆ

Using the C interface to solve a trust region subproblem is done by repeated calls to trlib_krylov_min(), using a reverse communication pattern.

This is explained in detail in the API doc of trlib_krylov_min(), here we show an example how to solve a trust region subproblem and resolve it with a different radius:

First we include the necessary headers and provide declarations for the linear algebra routines provided by BLAS and LAPACK:

#include <stdlib.h>
#include "math.h"
#include "string.h"
#include "trlib.h"

// blas
void daxpy_(trlib_int_t *n, trlib_flt_t *alpha, trlib_flt_t *x, trlib_int_t *incx, trlib_flt_t *y, trlib_int_t *incy);
void dscal_(trlib_int_t *n, trlib_flt_t *alpha, trlib_flt_t *x, trlib_int_t *incx);
void dcopy_(trlib_int_t *n, trlib_flt_t *x, trlib_int_t *incx, trlib_flt_t *y, trlib_int_t *incy);
trlib_flt_t dnrm2_(trlib_int_t *n, trlib_flt_t *x, trlib_int_t *incx);
trlib_flt_t ddot_(trlib_int_t *n, trlib_flt_t *x, trlib_int_t *incx, trlib_flt_t *y, trlib_int_t *incy);
// lapack
void dgemv_(char *trans, trlib_int_t *m, trlib_int_t *n, trlib_flt_t *alpha, trlib_flt_t *a, trlib_int_t *lda, trlib_flt_t *x, trlib_int_t *incx, trlib_flt_t *beta, trlib_flt_t *y, trlib_int_t *incy);

Then we define a data structure to be used in the reverse communication with trlib_krylov_min():

struct trlib_qpdata {
     trlib_int_t n;                     ///< dimension of problem
     trlib_int_t maxiter;               ///< maximum number of Krylov subspace iterations
     trlib_int_t *iwork;                ///< integer work space
     trlib_flt_t *fwork;                ///< floating point workspace
     trlib_flt_t *gradient;             ///< gradient of QP
     trlib_int_t hotstart;              ///< flag that determines if hotstarted or not
     void (*hv_cb)(const int n, const double *, double *);
                                        ///< callback to compute hessian vector product
     trlib_int_t iter;                  ///< iteration counter
     trlib_flt_t *g;                    ///< gradient of Krylov iteration
     trlib_flt_t *gm;                   ///< previous gradient of Krylov iteration
     trlib_flt_t *p;                    ///< direction
     trlib_flt_t *Hp;                   ///< hessian product
     trlib_flt_t *Q;                    ///< matrix with Lanczos directions
 };

The following function fills useful defaults in such a data structure once the problem data are known:

int prepare_qp(trlib_int_t n, trlib_int_t maxiter,
       const double *gradient,
       void (*hv_cb)(const int n, const double *, double *),
       struct trlib_qpdata *data) {
    data->n = n;
    data->maxiter = maxiter;
    data->gradient = (double *)gradient;
    data->hv_cb = hv_cb;
    data->hotstart = 0;
    trlib_int_t iwork_size, fwork_size, h_pointer;
    trlib_krylov_memory_size(maxiter, &iwork_size, &fwork_size, &h_pointer);
    data->iwork = malloc(iwork_size*sizeof(trlib_int_t));
    data->fwork = malloc(fwork_size*sizeof(trlib_flt_t));
    data->g = malloc(n*sizeof(double));
    data->gm = malloc(n*sizeof(double));
    data->p = malloc(n*sizeof(double));
    data->Hp = malloc(n*sizeof(double));
    data->Q = malloc((maxiter+1)*n*sizeof(double));
    data->iter = 0;
    return 0;
}

This function cleans up the data after the solution process has been finished:

int destroy_qp(struct trlib_qpdata *data) {
    free(data->iwork);
    free(data->fwork);
    free(data->g);
    free(data->gm);
    free(data->p);
    free(data->Hp);
    free(data->Q);
    return 0;
}

This function does the actual solution process by answering the reverse communication requests:

int solve_qp(struct trlib_qpdata *data, trlib_flt_t radius, double *sol, double *lam) {

    // some default settings
    trlib_int_t equality = 0;
    trlib_int_t maxlanczos = 100;
    trlib_int_t ctl_invariant = 0;
    trlib_int_t refine = 1;
    trlib_int_t verbose = 1;
    trlib_int_t unicode = 0;
    trlib_flt_t tol_rel_i = -2.0;
    trlib_flt_t tol_abs_i = 0.0;
    trlib_flt_t tol_rel_b = -3.0;
    trlib_flt_t tol_abs_b = 0.0;
    trlib_flt_t obj_lo = -1e20;
    trlib_int_t convexify = 1;
    trlib_int_t earlyterm = 1;

    trlib_int_t ret = 0;

    trlib_int_t n = data->n;
    trlib_int_t init = 0, inc = 1, itp1 = 0;
    trlib_flt_t minus = -1.0, one = 1.0, z = 0.0, half = .5;
    if(!data->hotstart) {
        init = TRLIB_CLS_INIT;
        trlib_krylov_prepare_memory(data->maxiter, data->fwork);
    }
    else { init = TRLIB_CLS_HOTSTART; }

    trlib_flt_t v_dot_g = 0.0, p_dot_Hp = 0.0, flt1, flt2, flt3;
    trlib_int_t action, ityp;

    trlib_int_t iwork_size, fwork_size, h_pointer;
    trlib_krylov_memory_size(data->maxiter, &iwork_size, &fwork_size, &h_pointer);

    while(1) {
        ret = trlib_krylov_min(init, radius, equality, data->maxiter, maxlanczos,
                tol_rel_i, tol_abs_i, tol_rel_b, tol_abs_b,
                TRLIB_EPS*TRLIB_EPS, obj_lo, ctl_invariant, convexify, earlyterm,
                v_dot_g, v_dot_g, p_dot_Hp, data->iwork, data->fwork,
                refine, verbose, unicode, "", stdout, NULL,
                &action, &(data->iter), &ityp, &flt1, &flt2, &flt3);
        init = 0;
        switch(action) {
            case TRLIB_CLA_INIT:
                memset(sol, 0, n*sizeof(trlib_flt_t)); memset(data->gm, 0, n*sizeof(trlib_flt_t));
                dcopy_(&n, data->gradient, &inc, data->g, &inc);
                v_dot_g = ddot_(&n, data->g, &inc, data->g, &inc);
                // p = -g
                dcopy_(&n, data->g, &inc, data->p, &inc); dscal_(&n, &minus, data->p, &inc);
                // Hp = H*p
                data->hv_cb(n, data->p, data->Hp);
                p_dot_Hp = ddot_(&n, data->p, &inc, data->Hp, &inc);
                // Q(0:n) = g
                dcopy_(&n, data->g, &inc, data->Q, &inc);
                // Q(0:n) = g/sqrt(<g,g>)
                flt1 = 1.0/sqrt(v_dot_g); dscal_(&n, &flt1, data->Q, &inc);
                break;
            case TRLIB_CLA_RETRANSF:
                itp1 = data->iter+1;
                // s = Q_i * h_i
                dgemv_("N", &n, &itp1, &one, data->Q, &n, data->fwork+h_pointer, &inc, &z, sol, &inc);
                break;
            case TRLIB_CLA_UPDATE_STATIO:
                // s += flt1*p
                if (ityp == TRLIB_CLT_CG) { daxpy_(&n, &flt1, data->p, &inc, sol, &inc); };
                break;
            case TRLIB_CLA_UPDATE_GRAD:
                if (ityp == TRLIB_CLT_CG) {
                    // Q(iter*n:(iter+1)*n) = flt2*g
                    dcopy_(&n, data->g, &inc, data->Q+(data->iter)*n, &inc);
                    dscal_(&n, &flt2, data->Q+(data->iter)*n, &inc);
                    // gm = g; g += flt1*Hp
                    dcopy_(&n, data->g, &inc, data->gm, &inc);
                    daxpy_(&n, &flt1, data->Hp, &inc, data->g, &inc);
                }
                if (ityp == TRLIB_CLT_L) {
                    // s = Hp + flt1*g + flt2*gm
                    dcopy_(&n, data->Hp, &inc, sol, &inc);
                    daxpy_(&n, &flt1, data->g, &inc, sol, &inc);
                    daxpy_(&n, &flt2, data->gm, &inc, sol, &inc);
                    // gm = flt3*g
                    dcopy_(&n, data->g, &inc, data->gm, &inc); dscal_(&n, &flt3, data->gm, &inc);
                    // g = s
                    dcopy_(&n, sol, &inc, data->g, &inc);
                }
                v_dot_g = ddot_(&n, data->g, &inc, data->g, &inc);
                break;
            case TRLIB_CLA_UPDATE_DIR:
                if (ityp == TRLIB_CLT_CG) {
                    // p = -g + flt2 * p
                    dscal_(&n, &flt2, data->p, &inc);
                    daxpy_(&n, &minus, data->g, &inc, data->p, &inc);
                }
                if (ityp == TRLIB_CLT_L) {
                    // p = flt1*g
                    dcopy_(&n, data->g, &inc, data->p, &inc);
                    dscal_(&n, &flt1, data->p, &inc);
                }
                // Hp = H*p
                data->hv_cb(n, data->p, data->Hp);
                p_dot_Hp = ddot_(&n, data->p, &inc, data->Hp, &inc);
                if ( ityp == TRLIB_CLT_L) {
                    // Q(iter*n:(iter+1)*n) = p
                    dcopy_(&n, data->p, &inc, data->Q+(data->iter)*n, &inc);
                }
                break;
            case TRLIB_CLA_CONV_HARD:
                itp1 = data->iter+1;
                trlib_flt_t *temp = malloc(n*sizeof(trlib_flt_t));
                // temp = H*s
                data->hv_cb(n, sol, temp);
                // temp = H*s + g
                daxpy_(&n, &one, data->gradient, &inc, temp, &inc);
                // temp = H*s + g + flt1*s
                daxpy_(&n, &flt1, sol, &inc, temp, &inc);
                v_dot_g = ddot_(&n, temp, &inc, temp, &inc);
                free(temp);
                break;
            case TRLIB_CLA_NEW_KRYLOV:
                printf("Hit invariant Krylov subspace. Please implement proper reorthogonalization!");
                break;
            case TRLIB_CLA_OBJVAL:
                trlib_flt_t *temp = malloc(n*sizeof(trlib_flt_t));
                // temp = H*s
                data->hv_cb(n, sol, temp);
                // temp = .5*H*s + g
                daxpy_(&n, &half, data->gradient, &inc, temp, &inc);
                // g_dot_g = .5 s^T H*s + g^T s
                g_dot_g = ddot_(&n, temp, &inc, sol, &inc);
                free(temp);
        }
        if( ret < 10 ) { break; }
    }
    *lam = data->fwork[7];

    if(!data->hotstart) { data->hotstart = 1; }
    return ret;
}

This function defines for a test example the function that computes hessian vector products:

/* Test the driver program to solve a 3D problem with two different radii
 */

void hessvec(const int n, const double *d, double *Hd) {
    Hd[0] = d[0] + 4.0*d[2];
    Hd[1] = 2.0*d[1];
    Hd[2] = 4.0*d[0] + 3.0*d[2];
}

This main routine initializes data to solve a trust region subproblem with radius 2.0 and hotstart it afterwards to resolve with radius 1.0 from the previous solution:

int main () {

    trlib_int_t n = 3;          // number of variables
    trlib_int_t maxiter = 10*n; // maximum number of CG iterations

    // gradient of QP
    double *g = malloc(n*sizeof(double));
    g[0] = 5.0; g[1] = 0.0; g[2] = 4.0;

    // get datastructure for QP solution and prepare it
    struct trlib_qpdata data;
    prepare_qp(n, maxiter, g, &hessvec, &data);

    // allocate memory for solution
    double *sol = malloc(n*sizeof(double));
    double lam = 0.0;

    // solve QP with trust region radius 2.0
    double radius = 2.0;
    printf("Attempting to solve trust region problem with radius %f\n", radius);
    solve_qp(&data, radius, sol, &lam);
    printf("Got lagrange multiplier %f and solution vector [%f %f %f]\n\n", lam, sol[0], sol[1], sol[2]);

    // resolve QP with trust region radius 1.0
    radius = 1.0;
    printf("Attempting to solve trust region problem with radius %f\n", radius);
    solve_qp(&data, radius, sol, &lam);
    printf("Got lagrange multiplier %f and solution vector [%f %f %f]\n\n", lam, sol[0], sol[1], sol[2]);

    // clean up
    destroy_qp(&data);
    free(g); free(sol);

    return 0;

}