GALAHAD BQP package#

purpose#

The bqp package uses a preconditioned, projected-gradient method to solve a given bound-constrained convex quadratic program. The aim is to minimize the quadratic objective function

\[q(x) = f + g^T x + \frac{1}{2} x^T H x,\]
subject to the simple bounds
\[x_l \leq x \leq x_u,\]
where \(H\) is a given \(n\) by \(n\) symmetric postive-semi-definite matrix, \(g\) is a vector, \(f\) is a scalar, and any of the components of the vectors \(x_l\) or \(x_u\) may be infinite. The method offers the choice of direct and iterative solution of the key regularization subproblems, and is most suitable for problems involving a large number of unknowns \(x\).

See Section 4 of $GALAHAD/doc/bqp.pdf for a brief description of the method employed and other details.

terminology#

Any required solution \(x\) necessarily satisfies the primal optimality conditions

\[x_l \leq x \leq x_u,\]
the dual optimality conditions
\[H x + g = z, \;\; z = z_l + z_u, z_l \geq 0 \;\;\mbox{and}\;\; z_u \leq 0,\]
and the complementary slackness conditions
\[(x -x_l )^{T} z_l = 0 \;\;\mbox{and}\;\;(x -x_u )^{T} z_u = 0,\]
where the vector \(z\) is known as the dual variables for the bounds, and where the vector inequalities hold component-wise.

method#

Projected-gradient methods iterate towards a point that satisfies these conditions by ultimately aiming to satisfy \(H x + g = z\) and \(z = z_l + z_u\), while satifying the remaining optimality conditions at each stage. Appropriate norms of the amounts by which the optimality conditions fail to be satisfied are known as the primal and dual infeasibility, and the violation of complementary slackness, respectively.

The method is iterative. Each iteration proceeds in two stages. Firstly, the so-called generalized Cauchy point for the quadratic objective is found. (The purpose of this point is to ensure that the algorithm converges and that the set of bounds which are satisfied as equations at the solution is rapidly identified.) Thereafter an improvement to the objective is sought using either a direct-matrix or truncated conjugate-gradient algorithm.

reference#

This is a specialised version of the method presented in

A. R. Conn, N. I. M. Gould and Ph. L. Toint, Global convergence of a class of trust region algorithms for optimization with simple bounds. SIAM Journal on Numerical Analysis 25 (1988) 433-460.

matrix storage#

The symmetric \(n\) by \(n\) matrix \(H\) may be presented and stored in a variety of formats. But crucially symmetry is exploited by only storing values from the lower triangular part (i.e, those entries that lie on or below the leading diagonal).

Dense storage format: The matrix \(H\) is stored as a compact dense matrix by rows, that is, the values of the entries of each row in turn are stored in order within an appropriate real one-dimensional array. Since \(H\) is symmetric, only the lower triangular part (that is the part \(H_{ij}\) for \(1 \leq j \leq i \leq n\)) need be held. In this case the lower triangle should be stored by rows, that is component \((i-1) * i / 2 + j\) of the storage array H_val will hold the value \(H_{ij}\) (and, by symmetry, \(H_{ji}\)) for \(1 \leq j \leq i \leq n\). The string H_type = ‘dense’ should be specified.

Sparse co-ordinate storage format: Only the nonzero entries of the matrices are stored. For the \(l\)-th entry, \(1 \leq l \leq ne\), of \(H\), its row index i, column index j and value \(H_{ij}\), \(1 \leq j \leq i \leq n\), are stored as the \(l\)-th components of the integer arrays H_row and H_col and real array H_val, respectively, while the number of nonzeros is recorded as H_ne = \(ne\). Note that only the entries in the lower triangle should be stored. The string H_type = ‘coordinate’ should be specified.

Sparse row-wise storage format: Again only the nonzero entries are stored, but this time they are ordered so that those in row i appear directly before those in row i+1. For the i-th row of \(H\) the i-th component of the integer array H_ptr holds the position of the first entry in this row, while H_ptr(n+1) holds the total number of entries plus one. The column indices j, \(1 \leq j \leq i\), and values \(H_{ij}\) of the entries in the i-th row are stored in components l = H_ptr(i), …, H_ptr(i+1)-1 of the integer array H_col, and real array H_val, respectively. Note that as before only the entries in the lower triangle should be stored. For sparse matrices, this scheme almost always requires less storage than its predecessor. The string H_type = ‘sparse_by_rows’ should be specified.

Diagonal storage format: If \(H\) is diagonal (i.e., \(H_{ij} = 0\) for all \(1 \leq i \neq j \leq n\)) only the diagonals entries \(H_{ii}\), \(1 \leq i \leq n\) need be stored, and the first n components of the array H_val may be used for the purpose. The string H_type = ‘diagonal’ should be specified.

Multiples of the identity storage format: If \(H\) is a multiple of the identity matrix, (i.e., \(H = \alpha I\) where \(I\) is the n by n identity matrix and \(\alpha\) is a scalar), it suffices to store \(\alpha\) as the first component of H_val. The string H_type = ‘scaled_identity’ should be specified.

The identity matrix format: If \(H\) is the identity matrix, no values need be stored. The string H_type = ‘identity’ should be specified.

The zero matrix format: The same is true if \(H\) is the zero matrix, but now the string H_type = ‘zero’ or ‘none’ should be specified.

introduction to function calls#

To solve a given problem, functions from the bqp package must be called in the following order:

  • bqp_initialize - provide default control parameters and set up initial data structures

  • bqp_read_specfile (optional) - override control values by reading replacement values from a file

  • set up problem data structures and fixed values by caling one of

    • bqp_import - in the case that \(H\) is explicitly available

    • bqp_import_without_h - in the case that only the effect of applying \(H\) to a vector is possible

  • bqp_reset_control (optional) - possibly change control parameters if a sequence of problems are being solved

  • solve the problem by calling one of

  • bqp_information (optional) - recover information about the solution and solution process

  • bqp_terminate - deallocate data structures

See the examples section for illustrations of use.

parametric real type T#

Below, the symbol T refers to a parametric real type that may be Float32 (single precision) or Float64 (double precision). Calable functions as described are with T as Float64, but variants (with the additional suffix _s, e.g., bqp_initialize_s) are available with T as Float32.

callable functions#

    function bqp_initialize(data, control, status)

Set default control values and initialize private data

Parameters:

data

holds private internal data

control

is a structure containing control information (see bqp_control_type)

status

is a scalar variable of type Int32 that gives the exit status from the package. Possible values are (currently):

  • 0

    The initialization was successful.

    function bqp_read_specfile(control, specfile)

Read the content of a specification file, and assign values associated with given keywords to the corresponding control parameters. An in-depth discussion of specification files is available, and a detailed list of keywords with associated default values is provided in $GALAHAD/src/bqp/BQP.template. See also Table 2.1 in the Fortran documentation provided in $GALAHAD/doc/bqp.pdf for a list of how these keywords relate to the components of the control structure.

Parameters:

control

is a structure containing control information (see bqp_control_type)

specfile

is a one-dimensional array of type Vararg{Cchar} that must give the name of the specification file

    function bqp_import(control, data, status, n, H_type, ne,
                        H_row, H_col, H_ptr)

Import problem data into internal storage prior to solution.

Parameters:

control

is a structure whose members provide control parameters for the remaining procedures (see bqp_control_type)

data

holds private internal data

status

is a scalar variable of type Int32 that gives the exit status from the package. Possible values are:

  • 1

    The import was successful, and the package is ready for the solve phase

  • -1

    An allocation error occurred. A message indicating the offending array is written on unit control.error, and the returned allocation status and a string containing the name of the offending array are held in inform.alloc_status and inform.bad_alloc respectively.

  • -2

    A deallocation error occurred. A message indicating the offending array is written on unit control.error and the returned allocation status and a string containing the name of the offending array are held in inform.alloc_status and inform.bad_alloc respectively.

  • -3

    The restriction n > 0 or requirement that type contains its relevant string ‘dense’, ‘coordinate’, ‘sparse_by_rows’ or ‘diagonal’ has been violated.

n

is a scalar variable of type Int32 that holds the number of variables.

H_type

is a one-dimensional array of type Vararg{Cchar} that specifies the symmetric storage scheme used for the Hessian. It should be one of ‘coordinate’, ‘sparse_by_rows’, ‘dense’, ‘diagonal’ or ‘absent’, the latter if access to the Hessian is via matrix-vector products; lower or upper case variants are allowed.

ne

is a scalar variable of type Int32 that holds the number of entries in the lower triangular part of H in the sparse co-ordinate storage scheme. It need not be set for any of the other three schemes.

H_row

is a one-dimensional array of size ne and type Int32 that holds the row indices of the lower triangular part of H in the sparse co-ordinate storage scheme. It need not be set for any of the other three schemes, and in this case can be C_NULL

H_col

is a one-dimensional array of size ne and type Int32 that holds the column indices of the lower triangular part of H in either the sparse co-ordinate, or the sparse row-wise storage scheme. It need not be set when the dense or diagonal storage schemes are used, and in this case can be C_NULL

H_ptr

is a one-dimensional array of size n+1 and type Int32 that holds the starting position of each row of the lower triangular part of H, as well as the total number of entries, in the sparse row-wise storage scheme. It need not be set when the other schemes are used, and in this case can be C_NULL

    function bqp_import_without_h(control, data, status, n)

Import problem data into internal storage prior to solution.

Parameters:

control

is a structure whose members provide control parameters for the remaining procedures (see bqp_control_type)

data

holds private internal data

status

is a scalar variable of type Int32 that gives the exit status from the package. Possible values are:

  • 1

    The import was successful, and the package is ready for the solve phase

  • -1

    An allocation error occurred. A message indicating the offending array is written on unit control.error, and the returned allocation status and a string containing the name of the offending array are held in inform.alloc_status and inform.bad_alloc respectively.

  • -2

    A deallocation error occurred. A message indicating the offending array is written on unit control.error and the returned allocation status and a string containing the name of the offending array are held in inform.alloc_status and inform.bad_alloc respectively.

  • -3. The restriction n > 0 has been violated.

n

is a scalar variable of type Int32 that holds the number of variables.

    function bqp_reset_control(control, data, status)

Reset control parameters after import if required.

Parameters:

control

is a structure whose members provide control parameters for the remaining procedures (see bqp_control_type)

data

holds private internal data

status

is a scalar variable of type Int32 that gives the exit status from the package. Possible values are:

  • 1

    The import was successful, and the package is ready for the solve phase

    function bqp_solve_given_h(data, status, n, h_ne, H_val, g, f,
                               x_l, x_u, x, z, x_stat)

Solve the bound-constrained quadratic program when the Hessian \(H\) is available.

Parameters:

data

holds private internal data

status

is a scalar variable of type Int32 that gives the entry and exit status from the package.

On initial entry, status must be set to 1.

Possible exit values are:

  • 0

    The run was successful.

  • -1

    An allocation error occurred. A message indicating the offending array is written on unit control.error, and the returned allocation status and a string containing the name of the offending array are held in inform.alloc_status and inform.bad_alloc respectively.

  • -2

    A deallocation error occurred. A message indicating the offending array is written on unit control.error and the returned allocation status and a string containing the name of the offending array are held in inform.alloc_status and inform.bad_alloc respectively.

  • -3

    The restriction n > 0 or requirement that a type contains its relevant string ‘dense’, ‘coordinate’, ‘sparse_by_rows’ or ‘diagonal’ has been violated.

  • -4

    The simple-bound constraints are inconsistent.

  • -9

    The analysis phase of the factorization failed; the return status from the factorization package is given in the component inform.factor_status

  • -10

    The factorization failed; the return status from the factorization package is given in the component inform.factor_status.

  • -11

    The solution of a set of linear equations using factors from the factorization package failed; the return status from the factorization package is given in the component inform.factor_status.

  • -16

    The problem is so ill-conditioned that further progress is impossible.

  • -17

    The step is too small to make further impact.

  • -18

    Too many iterations have been performed. This may happen if control.maxit is too small, but may also be symptomatic of a badly scaled problem.

  • -19

    The CPU time limit has been reached. This may happen if control.cpu_time_limit is too small, but may also be symptomatic of a badly scaled problem.

  • -20

    The Hessian matrix \(H\) appears to be indefinite. specified.

  • -23

    An entry from the strict upper triangle of \(H\) has been

n

is a scalar variable of type Int32 that holds the number of variables

h_ne

is a scalar variable of type Int32 that holds the number of entries in the lower triangular part of the Hessian matrix \(H\).

H_val

is a one-dimensional array of size h_ne and type T that holds the values of the entries of the lower triangular part of the Hessian matrix \(H\) in any of the available storage schemes.

g

is a one-dimensional array of size n and type T that holds the linear term \(g\) of the objective function. The j-th component of g, j = 1, … , n, contains \(g_j\).

f

is a scalar of type T that holds the constant term \(f\) of the objective function.

x_l

is a one-dimensional array of size n and type T that holds the lower bounds \(x^l\) on the variables \(x\). The j-th component of x_l, j = 1, … , n, contains \(x^l_j\).

x_u

is a one-dimensional array of size n and type T that holds the upper bounds \(x^l\) on the variables \(x\). The j-th component of x_u, j = 1, … , n, contains \(x^l_j\).

x

is a one-dimensional array of size n and type T that holds the values \(x\) of the optimization variables. The j-th component of x, j = 1, … , n, contains \(x_j\).

z

is a one-dimensional array of size n and type T that holds the values \(z\) of the dual variables. The j-th component of z, j = 1, … , n, contains \(z_j\).

x_stat

is a one-dimensional array of size n and type Int32 that gives the optimal status of the problem variables. If x_stat(j) is negative, the variable \(x_j\) most likely lies on its lower bound, if it is positive, it lies on its upper bound, and if it is zero, it lies between its bounds.

    function bqp_solve_reverse_h_prod(data, status, n, g, f,
                                      x_l, x_u, x, z, x_stat, v,
                                      prod, nz_v, nz_v_start,
                                      nz_v_end, nz_prod, nz_prod_end)

Solve the bound-constrained quadratic program when the products of the Hessian \(H\) with specified vectors may be computed by the calling program.

Parameters:

data

holds private internal data

status

is a scalar variable of type Int32 that gives the entry and exit status from the package.

Possible exit values are:

  • 0

    The run was successful.

  • -1

    An allocation error occurred. A message indicating the offending array is written on unit control.error, and the returned allocation status and a string containing the name of the offending array are held in inform.alloc_status and inform.bad_alloc respectively.

  • -2

    A deallocation error occurred. A message indicating the offending array is written on unit control.error and the returned allocation status and a string containing the name of the offending array are held in inform.alloc_status and inform.bad_alloc respectively.

  • -3

    The restriction n > 0 or requirement that a type contains its relevant string ‘dense’, ‘coordinate’, ‘sparse_by_rows’ or ‘diagonal’ has been violated.

  • -4

    The simple-bound constraints are inconsistent.

  • -9

    The analysis phase of the factorization failed; the return status from the factorization package is given in the component inform.factor_status

  • -10

    The factorization failed; the return status from the factorization package is given in the component inform.factor_status.

  • -11

    The solution of a set of linear equations using factors from the factorization package failed; the return status from the factorization package is given in the component inform.factor_status.

  • -16

    The problem is so ill-conditioned that further progress is impossible.

  • -17

    The step is too small to make further impact.

  • -18

    Too many iterations have been performed. This may happen if control.maxit is too small, but may also be symptomatic of a badly scaled problem.

  • -19

    The CPU time limit has been reached. This may happen if control.cpu_time_limit is too small, but may also be symptomatic of a badly scaled problem.

  • -20

    The Hessian matrix \(H\) appears to be indefinite. specified.

  • -23

    An entry from the strict upper triangle of \(H\) has been specified.

  • 2

    The product \(Hv\) of the Hessian \(H\) with a given output vector \(v\) is required from the user. The vector \(v\) will be stored in v and the product \(Hv\) must be returned in prod, and bqp_solve_reverse_h_prod re-entered with all other arguments unchanged.

  • 3

    The product \(Hv\) of the Hessian H with a given output vector \(v\) is required from the user. Only components nz_v[nz_v_start-1:nz_v_end-1] of the vector \(v\) stored in v are nonzero. The resulting product \(Hv\) must be placed in prod, and bqp_solve_reverse_h_prod re-entered with all other arguments unchanged.

  • 4

    The product \(Hv\) of the Hessian H with a given output vector \(v\) is required from the user. Only components nz_v[nz_v_start-1:nz_v_end-1] of the vector \(v\) stored in v are nonzero. The resulting nonzeros in the product \(Hv\) must be placed in their appropriate comnponents of prod, while a list of indices of the nonzeros placed in nz_prod[0 : nz_prod_end-1]. bqp_solve_reverse_h_prod should then be re-entered with all other arguments unchanged. Typically v will be very sparse (i.e., nz_p_end-nz_p_start will be small).

n

is a scalar variable of type Int32 that holds the number of variables

g

is a one-dimensional array of size n and type T that holds the linear term \(g\) of the objective function. The j-th component of g, j = 1, … , n, contains \(g_j\).

f

is a scalar of type T that holds the constant term \(f\) of the objective function.

x_l

is a one-dimensional array of size n and type T that holds the lower bounds \(x^l\) on the variables \(x\). The j-th component of x_l, j = 1, … , n, contains \(x^l_j\).

x_u

is a one-dimensional array of size n and type T that holds the upper bounds \(x^l\) on the variables \(x\). The j-th component of x_u, j = 1, … , n, contains \(x^l_j\).

x

is a one-dimensional array of size n and type T that holds the values \(x\) of the optimization variables. The j-th component of x, j = 1, … , n, contains \(x_j\).

z

is a one-dimensional array of size n and type T that holds the values \(z\) of the dual variables. The j-th component of z, j = 1, … , n, contains \(z_j\).

x_stat

is a one-dimensional array of size n and type Int32 that gives the optimal status of the problem variables. If x_stat(j) is negative, the variable \(x_j\) most likely lies on its lower bound, if it is positive, it lies on its upper bound, and if it is zero, it lies between its bounds.

v

is a one-dimensional array of size n and type T that is used for reverse communication (see status=2-4 above for details)

prod

is a one-dimensional array of size n and type T that is used for reverse communication (see status=2-4 above for details)

nz_v

is a one-dimensional array of size n and type Int32 that is used for reverse communication (see status=3-4 above for details)

nz_v_start

is a scalar of type Int32 that is used for reverse communication (see status=3-4 above for details)

nz_v_end

is a scalar of type Int32 that is used for reverse communication (see status=3-4 above for details)

nz_prod

is a one-dimensional array of size n and type Int32 that is used for reverse communication (see status=4 above for details)

nz_prod_end

is a scalar of type Int32 that is used for reverse communication (see status=4 above for details)

    function bqp_information(data, inform, status)

Provides output information

Parameters:

data

holds private internal data

inform

is a structure containing output information (see bqp_inform_type)

status

is a scalar variable of type Int32 that gives the exit status from the package. Possible values are (currently):

  • 0

    The values were recorded successfully

    function bqp_terminate(data, control, inform)

Deallocate all internal private storage

Parameters:

data

holds private internal data

control

is a structure containing control information (see bqp_control_type)

inform

is a structure containing output information (see bqp_inform_type)

available structures#

bqp_control_type structure#

    struct bqp_control_type{T}
      f_indexing::Bool
      error::Int32
      out::Int32
      print_level::Int32
      start_print::Int32
      stop_print::Int32
      print_gap::Int32
      maxit::Int32
      cold_start::Int32
      ratio_cg_vs_sd::Int32
      change_max::Int32
      cg_maxit::Int32
      sif_file_device::Int32
      infinity::T
      stop_p::T
      stop_d::T
      stop_c::T
      identical_bounds_tol::T
      stop_cg_relative::T
      stop_cg_absolute::T
      zero_curvature::T
      cpu_time_limit::T
      exact_arcsearch::Bool
      space_critical::Bool
      deallocate_error_fatal::Bool
      generate_sif_file::Bool
      sif_file_name::NTuple{31,Cchar}
      prefix::NTuple{31,Cchar}
      sbls_control::sbls_control_type{T}

detailed documentation#

control derived type as a Julia structure

components#

Bool f_indexing

use C or Fortran sparse matrix indexing

Int32 error

unit number for error and warning diagnostics

Int32 out

general output unit number

Int32 print_level

the level of output required

Int32 start_print

on which iteration to start printing

Int32 stop_print

on which iteration to stop printing

Int32 print_gap

how many iterations between printing

Int32 maxit

how many iterations to perform (-ve reverts to HUGE(1)-1)

Int32 cold_start

cold_start should be set to 0 if a warm start is required (with variable assigned according to B_stat, see below), and to any other value if the values given in prob.X suffice

Int32 ratio_cg_vs_sd

the ratio of how many iterations use CG rather steepest descent

Int32 change_max

the maximum number of per-iteration changes in the working set permitted when allowing CG rather than steepest descent

Int32 cg_maxit

how many CG iterations to perform per BQP iteration (-ve reverts to n+1)

Int32 sif_file_device

the unit number to write generated SIF file describing the current problem

T infinity

any bound larger than infinity in modulus will be regarded as infinite

T stop_p

the required accuracy for the primal infeasibility

T stop_d

the required accuracy for the dual infeasibility

T stop_c

the required accuracy for the complementary slackness

T identical_bounds_tol

any pair of constraint bounds (x_l,x_u) that are closer than i dentical_bounds_tol will be reset to the average of their values

T stop_cg_relative

the CG iteration will be stopped as soon as the current norm of the preconditioned gradient is smaller than max( stop_cg_relative * initial preconditioned gradient, stop_cg_absolute)

T stop_cg_absolute

see stop_cg_relative

T zero_curvature

threshold below which curvature is regarded as zero

T cpu_time_limit

the maximum CPU time allowed (-ve = no limit)

Bool exact_arcsearch

exact_arcsearch is true if an exact arcsearch is required, and false if approximation suffices

Bool space_critical

if space_critical is true, every effort will be made to use as little space as possible. This may result in longer computation times

Bool deallocate_error_fatal

if deallocate_error_fatal is true, any array/pointer deallocation error will terminate execution. Otherwise, computation will continue

Bool generate_sif_file

if generate_sif_file is true, a SIF file describing the current problem will be generated

NTuple{31,Cchar} sif_file_name

name (max 30 characters) of generated SIF file containing input problem

NTuple{31,Cchar} prefix

all output lines will be prefixed by a string (max 30 characters) prefix(2:LEN(TRIM(.prefix))-1) where prefix contains the required string enclosed in quotes, e.g. “string” or ‘string’

struct sbls_control_type sbls_control

control parameters for SBLS

bqp_time_type structure#

    struct bqp_time_type
      total::Float32
      analyse::Float32
      factorize::Float32
      solve::Float32

detailed documentation#

time derived type as a Julia structure

components#

Float32 total

total time

Float32 analyse

time for the analysis phase

Float32 factorize

time for the factorization phase

Float32 solve

time for the linear solution phase

bqp_inform_type structure#

    struct bqp_inform_type{T}
      status::Int32
      alloc_status::Int32
      factorization_status::Int32
      iter::Int32
      cg_iter::Int32
      obj::T
      norm_pg::T
      bad_alloc::NTuple{81,Cchar}
      time::bqp_time_type
      sbls_inform::sbls_inform_type{T}

detailed documentation#

inform derived type as a Julia structure

components#

Int32 status

reported return status:

  • 0

    success

  • -1

    allocation error

  • -2

    deallocation error

  • -3

    matrix data faulty (.n < 1, .ne < 0)

  • -20

    alegedly +ve definite matrix is not

Int32 alloc_status

Fortran STAT value after allocate failure.

Int32 factorization_status

status return from factorization

Int32 iter

number of iterations required

Int32 cg_iter

number of CG iterations required

T obj

current value of the objective function

T norm_pg

current value of the projected gradient

NTuple{81,Cchar} bad_alloc

name of array which provoked an allocate failure

struct bqp_time_type time

times for various stages

struct sbls_inform_type sbls_inform

inform values from SBLS

example calls#

This is an example of how to use the package to solve a bound-constrained QP; the code is available in $GALAHAD/src/bqp/C/bqpt.c . A variety of supported Hessian and constraint matrix storage formats are shown.

/* bqpt.c */
/* Full test for the BQP C interface using C sparse matrix indexing */

#include <stdio.h>
#include <math.h>
#include "galahad_precision.h"
#include "galahad_cfunctions.h"
#include "galahad_bqp.h"

int main(void) {

    // Derived types
    void *data;
    struct bqp_control_type control;
    struct bqp_inform_type inform;

    // Set problem data
    ipc_ n = 10; // dimension
    ipc_ H_ne = 2 * n - 1; // Hesssian elements, NB lower triangle
    ipc_ H_dense_ne = n * ( n + 1 ) / 2; // dense Hessian elements
    ipc_ H_row[H_ne]; // row indices,
    ipc_ H_col[H_ne]; // column indices
    ipc_ H_ptr[n+1];  // row pointers
    rpc_ H_val[H_ne]; // values
    rpc_ H_dense[H_dense_ne]; // dense values
    rpc_ H_diag[n];   // diagonal values
    rpc_ g[n];  // linear term in the objective
    rpc_ f = 1.0;  // constant term in the objective
    rpc_ x_l[n]; // variable lower bound
    rpc_ x_u[n]; // variable upper bound
    rpc_ x[n]; // variables
    rpc_ z[n]; // dual variables

    // Set output storage
    ipc_ x_stat[n]; // variable status
    char st = ' ';
    ipc_ i, l, status;

    g[0] = 2.0;
    for( ipc_ i = 1; i < n; i++) g[i] = 0.0;
    x_l[0] = -1.0;
    for( ipc_ i = 1; i < n; i++) x_l[i] = - INFINITY;
    x_u[0] = 1.0;
    x_u[1] = INFINITY;
    for( ipc_ i = 2; i < n; i++) x_u[i] = 2.0;

    // H = tridiag(2,1), H_dense = diag(2)

    l = 0 ;
    H_ptr[0] = l;
    H_row[l] = 0; H_col[l] = 0; H_val[l] = 2.0;
    for( ipc_ i = 1; i < n; i++)
    {
      l = l + 1;
      H_ptr[i] = l;
      H_row[l] = i; H_col[l] = i - 1; H_val[l] = 1.0;
      l = l + 1;
      H_row[l] = i; H_col[l] = i; H_val[l] = 2.0;
    }
    H_ptr[n] = l + 1;

    l = - 1 ;
    for( ipc_ i = 0; i < n; i++)
    {
      H_diag[i] = 2.0;
      for( ipc_ j = 0; j <= i; j++)
      {
        l = l + 1;
        if ( j < i - 1 ) {
          H_dense[l] = 0.0;
        }
        else if ( j == i - 1 ) {
          H_dense[l] = 1.0;
        }
        else {
          H_dense[l] = 2.0;
        }
      }
    }

    printf(" C sparse matrix indexing\n\n");

    printf(" basic tests of bqp storage formats\n\n");

    for( ipc_ d=1; d <= 4; d++){

        // Initialize BQP
        bqp_initialize( &data, &control, &status );

        // Set user-defined control options
        control.f_indexing = false; // C sparse matrix indexing

        // Start from 0
        for( ipc_ i = 0; i < n; i++) x[i] = 0.0;
        for( ipc_ i = 0; i < n; i++) z[i] = 0.0;

        switch(d){
            case 1: // sparse co-ordinate storage
                st = 'C';
                bqp_import( &control, &data, &status, n,
                            "coordinate", H_ne, H_row, H_col, NULL );
                bqp_solve_given_h( &data, &status, n, H_ne, H_val, g, f,
                                   x_l, x_u, x, z, x_stat );
                break;
            printf(" case %1" i_ipc_ " break\n",d);
            case 2: // sparse by rows
                st = 'R';
                bqp_import( &control, &data, &status, n,
                             "sparse_by_rows", H_ne, NULL, H_col, H_ptr );
                bqp_solve_given_h( &data, &status, n, H_ne, H_val, g, f,
                                   x_l, x_u, x, z, x_stat );
                break;
            case 3: // dense
                st = 'D';
                bqp_import( &control, &data, &status, n,
                             "dense", H_dense_ne, NULL, NULL, NULL );
                bqp_solve_given_h( &data, &status, n, H_dense_ne, H_dense,
                                   g, f, x_l, x_u, x, z, x_stat );
                break;
            case 4: // diagonal
                st = 'L';
                bqp_import( &control, &data, &status, n,
                             "diagonal", H_ne, NULL, NULL, NULL );
                bqp_solve_given_h( &data, &status, n, n, H_diag, g, f,
                                   x_l, x_u, x, z, x_stat );
                break;
            }
        bqp_information( &data, &inform, &status );

        if(inform.status == 0){
            printf("%c:%6" i_ipc_ " iterations. Optimal objective value = %5.2f status = %1" i_ipc_ "\n",
                   st, inform.iter, inform.obj, inform.status);
        }else{
            printf("%c: BQP_solve exit status = %1" i_ipc_ "\n", st, inform.status);
        }
        //printf("x: ");
        //for( ipc_ i = 0; i < n; i++) printf("%f ", x[i]);
        //printf("\n");
        //printf("gradient: ");
        //for( ipc_ i = 0; i < n; i++) printf("%f ", g[i]);
        //printf("\n");

        // Delete internal workspace
        bqp_terminate( &data, &control, &inform );
    }

    printf("\n tests reverse-communication options\n\n");

    // reverse-communication input/output
    ipc_ nz_v_start, nz_v_end, nz_prod_end;
    ipc_ nz_v[n], nz_prod[n], mask[n];
    rpc_ v[n], prod[n];

    nz_prod_end = 0;

    // Initialize BQP
    bqp_initialize( &data, &control, &status );
   // control.print_level = 1;

   // Set user-defined control options
    control.f_indexing = false; // C sparse matrix indexing

    // Start from 0
    for( ipc_ i = 0; i < n; i++) x[i] = 0.0;
    for( ipc_ i = 0; i < n; i++) z[i] = 0.0;

    st = 'I';
    for( ipc_ i = 0; i < n; i++) mask[i] = 0;
    bqp_import_without_h( &control, &data, &status, n ) ;
    while(true){ // reverse-communication loop
        bqp_solve_reverse_h_prod( &data, &status, n, g, f, x_l, x_u,
                                  x, z, x_stat, v, prod,
                                  nz_v, &nz_v_start, &nz_v_end,
                                  nz_prod, nz_prod_end );
        if(status == 0){ // successful termination
            break;
        }else if(status < 0){ // error exit
            break;
        }else if(status == 2){ // evaluate Hv
          prod[0] = 2.0 * v[0] + v[1];
          for( ipc_ i = 1; i < n-1; i++) prod[i] = 2.0 * v[i] + v[i-1] + v[i+1];
          prod[n-1] = 2.0 * v[n-1] + v[n-2];
        }else if(status == 3){ // evaluate Hv for sparse v
          for( ipc_ i = 0; i < n; i++) prod[i] = 0.0;
          for( ipc_ l = nz_v_start - 1; l < nz_v_end; l++){
             i = nz_v[l];
             if (i > 0) prod[i-1] = prod[i-1] + v[i];
             prod[i] = prod[i] + 2.0 * v[i];
             if (i < n-1) prod[i+1] = prod[i+1] + v[i];
           }
        }else if(status == 4){ // evaluate sarse Hv for sparse v
          nz_prod_end = 0;
          for( ipc_ l = nz_v_start - 1; l < nz_v_end; l++){
             i = nz_v[l];
             if (i > 0){
               if (mask[i-1] == 0){
                 mask[i-1] = 1;
                 nz_prod[nz_prod_end] = i - 1;
                 nz_prod_end = nz_prod_end + 1;
                 prod[i-1] = v[i];
               }else{
                 prod[i-1] = prod[i-1] + v[i];
               }
             }
             if (mask[i] == 0){
               mask[i] = 1;
               nz_prod[nz_prod_end] = i;
               nz_prod_end = nz_prod_end + 1;
               prod[i] = 2.0 * v[i];
             }else{
               prod[i] = prod[i] + 2.0 * v[i];
             }
             if (i < n-1){
               if (mask[i+1] == 0){
                 mask[i+1] = 1;
                 nz_prod[nz_prod_end] = i + 1;
                 nz_prod_end = nz_prod_end + 1;
                 prod[i+1] = prod[i+1] + v[i];
               }else{
                 prod[i+1] = prod[i+1] + v[i];
               }
             }
          }
          for( ipc_ l = 0; l < nz_prod_end; l++) mask[nz_prod[l]] = 0;
        }else{
            printf(" the value %1" i_ipc_ " of status should not occur\n", status);
            break;
        }
    }

    // Record solution information
    bqp_information( &data, &inform, &status );

    // Print solution details
    if(inform.status == 0){
        printf("%c:%6" i_ipc_ " iterations. Optimal objective value = %5.2f status = %1" i_ipc_ "\n",
               st, inform.iter, inform.obj, inform.status);
    }else{
        printf("%c: BQP_solve exit status = %1" i_ipc_ "\n", st, inform.status);
    }
    //printf("x: ");
    //for( ipc_ i = 0; i < n; i++) printf("%f ", x[i]);
    //printf("\n");
    //printf("gradient: ");
    //for( ipc_ i = 0; i < n; i++) printf("%f ", g[i]);
    //printf("\n");

    // Delete internal workspace
    bqp_terminate( &data, &control, &inform );
}