Initializing help system before first use

The Xpress NonLinear API Functions

Topics covered in this chapter:

Instead of writing an extended MPS file and reading in the model from the file, it is possible to embed Xpress NonLinear directly into your application, and to create the problem, solve it and analyze the solution entirely by using the Xpress NonLinear API functions. This example uses the C header files and API calls. We shall assume you have some familiarity with the Xpress Optimizer API functions.

The structure of the model and the naming system will follow that used in the previous section, so you should read Modeling in Extended MPS Format first.

The nonlinear API is fully accessible through xprs.h alone. Functions are prefixed XPRSnlp (nonlinear modeling and solving) or XPRSslp (SLP-specific coefficient and variable management) and operate directly on an XPRSprob handle. There is no need to create a separate problem object or to call a separate initialization function for the nonlinear solver.

The XSLP API, which uses a separate XSLPprob handle and requires xslp.h, also remains available. See The XSLP API at the end of this chapter for a description of the XSLP API and a mapping to the corresponding XPRSnlp* and XPRSslp* function names.

Header files

Including xprs.h is sufficient to access the full XPRSnlp* and XPRSslp* API.

#include "xprs.h"

Initialization

Only the standard Xpress Optimizer initialization and problem creation are required. All Xpress NonLinear functions return a code indicating whether the function completed successfully. A non-zero value indicates an error. For ease of reading, we have for the most part omitted the tests on the return codes, but a well-written program should always test the values.

XPRSprob prob;

if (ReturnValue=XPRSinit(NULL)) goto ErrorReturn;
if (ReturnValue=XPRScreateprob(&prob)) goto ErrorReturn;

There is no separate initialization call or problem handle for the nonlinear solver. The single XPRSprob is used directly with all XPRSnlp* and XPRSslp* functions.

Callbacks

It is good practice to set up at least a message callback, so that any messages produced by the system appear on the screen or in a file. The standard XPRSaddcbmessage function is used; it covers both linear and nonlinear solver messages.

XPRSaddcbmessage(mprob, MessageCallback, NULL, 0);

void MessageCallback(XPRSprob my_prob, void *my_object,
       const char *msg, int len, int msg_type)
{
  switch (msg_type) {
  case 4: /* error */
  case 3: /* warning */
  case 2: /* dialogue */
  case 1: /* information */
    printf("%s\n", msg);
    break;
  default: /* exiting */
    fflush(stdout);
    break;
  }
}

This is a simple callback routine, which prints any message to standard output.

Creating the linear part of the problem

The linear part of the problem, and the definitions of the rows and columns of the problem are carried out using the normal Xpress Optimizer functions.

#define MAXROW 20
#define MAXCOL 20
#define MAXELT 50
  int nRow, nCol, nSide, nRowName, nColName;
  int Sin, Cos;
  char RowType[MAXROW];
  double RHS[MAXROW], OBJ[MAXCOL], Element[MAXELT];
  double Lower[MAXCOL], Upper[MAXCOL];
  int ColStart[MAXCOL+1], RowIndex[MAXELT];
  char RowNames[500], ColNames[500];

In this example, we have set the dimensions by using #define statements, rather than working out the actual sizes required from the number of sides and then allocating the space dynamically.

  nSide = 5;
  nRowName = 0;
  nColName = 0;

By making the number of sides a variable (nSide) we can create other polygons by changing its value.

It is useful – at least while building a model – to be able to see what has been created. We will therefore create meaningful names for the rows and columns. nRowName and nColName count along the character buffers RowNames and ColNames.

  nRow = nSide-2 + (nSide-1)*(nSide-2)/2 + 1;
  nCol = (nSide-1)*2 + 2;
  for (i=0; i<nRow; i++) RHS[i] = 0;

The number of constraints is:

nSide-2 for the relationships between adjacent thetas.
(nSide-1)*(nSide-2)/2 for the distances between pairs of vertices.
1 for the OBJEQ non-linear "objective function".

The number of columns is:

nSide-1 for the thetas.
nSide-1 for the rhos.
1 for the OBJX objective function column.

We are using "C"-style numbering for rows and columns, so the counting starts from zero.

  nRow = 0;
  RowType[nRow++] = 'E'; /* OBJEQ */
  nRowName = nRowName + 1 + sprintf(&RowNames[nRowName], "OBJEQ");
  for (i=1; i<nSide-1; i++) {
    RowType[nRow++] = 'G'; /* T2T1 .. T4T3 */
    RHS[i] = 0.001;
    nRowName = nRowName + 1 + sprintf(&RowNames[nRowName], "T%dT%d", i+1, i);
  }

This sets the row type indicator for OBJEQ and the theta relationships, with a right hand side of 0.001. We also create row names in the RowNames buffer. Each name is terminated by a NULL character (automatically placed there by the sprintf function). sprintf returns the length of the string written, excluding the terminating NULL character.

  for (i=1; i<nSide-1; i++) {
    for (j=i+1; j<nSide; j++) {
      RowType[nRow] = 'L';
      RHS[nRow++] = 1.0;
      nRowName = nRowName + 1 + sprintf(&RowNames[nRowName], "V%dV%d", i, j);
    }
  }

This defines the L-type rows which constrain the distances between pairs of vertices. The right hand side is 1.0 (the maximum value) and the names are of the form ViVj.

  for (i=0; i<nCol; i++) {
    OBJ[i] = 0;        /* objective function */
    Lower[i] = 0;      /* lower bound normally zero */
    Upper[i] = XPRS_PLUSINFINITY; /* upper bound = infinity */
  }

This sets up the standard column data, with objective function entries of zero, and default bounds of zero to plus infinity. We shall change these for the individual items as required.

  nCol = 0;
  nElement = 0;
  ColStart[nCol] = nElement;
  OBJ[nCol] = 1.0;
  Lower[nCol++] = XPRS_MINUSINFINITY; /* free column */
  Element[nElement] = -1.0;
  RowIndex[nElement++] = 0;
  nColName = nColName + 1 + sprintf(&ColNames[nColName], "OBJX");

This starts the construction of the matrix elements. nElement counts through the Element and RowIndex arrays, nCol counts through the ColStart, OBJ, Lower and Upper arrays. The first column, OBJX, has the objective function value of +1 and a value of -1 in the OBJEQ row. It is also defined to be "free", by making its lower bound equal to minus infinity.

  iRow = 0
  for (i=1; i<nSide; i++) {
    nColName = nColName + 1 + sprintf(&ColNames[nColName], "THETA%d", i);
    ColStart[nCol++] = nElement;
    if (i < nSide-1) {
      Element[nElement] = -1;
      RowIndex[nElement++] = iRow+1;
    }
    if (i > 1) {
      Element[nElement] = 1;
      RowIndex[nElement++] = iRow;
    }
    iRow++;
  }

This creates the relationships between adjacent thetas. The tests on i are to deal with the first and last thetas which do not have relationships with both their predecessor and successor.

  Upper[nCol-1] = 3.1415926;

This sets the bound on the final theta to be π. The column index is nCol-1 because nCol has already been incremented.

  for (i=1; i<nSide; i++) {
    Lower[nCol] = 0.01;         /* lower bound */
    Upper[nCol] = 1;
    ColStart[nCol++] = nElement;
    nColName = nColName + 1 + sprintf(&ColNames[nColName], "RHO%d", i);
  }
  ColStart[nCol] = nElement;

The remaining columns – the rho variables – have only non-linear formulas and so they do not appear in the linear section except as empty columns. They are bounded between 0.01 and 1.0 but have no entries. The final entry in ColStart is one after the end of the last column.

  XPRSloadlp(mprob, "Polygon", nCol, nRow, RowType, RHS, NULL,
    OBJ, ColStart, NULL, RowIndex, Element, Lower, Upper);

This actually loads the model into Xpress Optimizer. We are not using ranges or column element counts, which is why the two arguments are NULL.

  XPRSaddnames(mprob, 1, RowNames, 0, nRow-1);
  XPRSaddnames(mprob, 2, ColNames, 0, nCol-1);

The row and column names can now be added.

Adding the non-linear part of the problem

Be warned – this section is complicated, but it is the most efficient way – from SLP's point of view – to input formulae. See the next section for a much easier (but less efficient) way of inputting the formulae directly.

#define MAXTOKEN 200
#define MAXFORM 20
...
  int Sin, Cos;
  FormulaStart[MAXFORM];
  Type[MAXTOKEN];
  double Value[MAXTOKEN];

The arrays for the non-linear part can often be re-used from the linear part. The new arrays are FormulaStart for the nonlinear formulas and Type and Value to hold the internal forms of the formulae.

  XPRSgetindex(mprob, XPRS_NLPINTERNALFUNCNAMES, "SIN", &Sin);
  XPRSgetindex(mprob, XPRS_NLPINTERNALFUNCNAMES, "COS", &Cos);

We will be using the Xpress NonLinear internal functions SIN and COS. The XPRSgetindex function with type XPRS_NLPINTERNALFUNCNAMES finds the index of an Xpress NonLinear internal function by name.

  nToken = 0;
  nForm = 0;
  RowIndex[nForm] = 0;
  FormulaStart[nForm++] = nToken;

For each nonlinear formula, the following information is required:

RowIndex the index of the row.
FormulaStart the beginning of the internal formula array for the nonlinear formula.

  for (i=1; i<nSide-1; i++) {
    Type[nToken] = XPRS_TOK_COL;
    Value[nToken++] = nSide+i+1;
    Type[nToken] = XPRS_TOK_COL;
    Value[nToken++] = nSide+i;
    Type[nToken] = XPRS_TOK_OP;
    Value[nToken++] = XPRS_OP_MULTIPLY;
    Type[nToken] = XPRS_TOK_RB;
    Value[nToken++] = 0;
    Type[nToken] = XPRS_TOK_COL;
    Value[nToken++] = i+1;
    Type[nToken] = XPRS_TOK_COL;
    Value[nToken++] = i;
    Type[nToken] = XPRS_TOK_OP;
    Value[nToken++] = XPRS_OP_MINUS;
    Type[nToken] = XPRS_TOK_IFUN;
    Value[nToken++] = Sin;
    Type[nToken] = XPRS_TOK_OP
    Value[nToken++] = XPRS_OP_MULTIPLY;
    if (i>1) {
      Type[nToken] = XPRS_TOK_OP;
      Value[nToken++] = XPRS_OP_PLUS;
    }
  }

This looks very complicated, but it is really just rather large. We are using the "reverse Polish" or "parsed" form of the formula for area. The original formula, written in the normal way, would look like this:
RHO2 * RHO1 * SIN ( THETA2 - THETA1 ) + .......
In reverse Polish notation, tokens are pushed onto the stack or popped from it. Typically, this means that a binary operation A x B is written as A B x (push A, push B, pop A and B and push the result). The first term of our area formula then becomes:
RHO2 RHO1 * ) THETA2 THETA1 - SIN *
Notice that the right hand bracket appears as an explicit token. This allows the SIN function to identify where its argument list starts – and incidentally allows functions to have varying numbers of arguments.

Each token of the formula is written as two items – Type and Value.
Type is an integer and is one of the defined types of token, as given in the xprs.h header file. XPRS_TOK_CON, for example, is a constant; XPRS_TOK_COL is a column.
Value is a double precision value, and its meaning depends on the corresponding Type. For a Type of XPRS_TOK_CON, Value is the constant value; for XPRS_TOK_COL, Value is the column number; for XPRS_TOK_OP (arithmetic operation), Value is the operand number as defined in xprs.h; for a function (type XPRS_TOK_IFUN for internal functions, XPRS_TOK_FUN for user functions), Value is the function number.
A list of tokens for a formula is always terminated by a token of type XPRS_TOK_EOF.

The loop writes each term in order, and adds terms (using the XPRS_OP_PLUS operator) after the first pass through the loop.

  for (i=1; i<nSide-1; i++) {
    for (j=i+1; j<nSide; j++) {
      RowIndex[nForm] = iRow++;
      FormulaStart[nForm++] = nToken;

      Type[nToken] = XPRS_TOK_COL;
      Value[nToken++] = nSide+i;
      Type[nToken] = XPRS_TOK_CON;
      Value[nToken++] = 2;
      Type[nToken] = XPRS_TOK_OP;
      Value[nToken++] = XPRS_OP_EXPONENT;
      Type[nToken] = XPRS_TOK_COL;
      Value[nToken++] = nSide+j;
      Type[nToken] = XPRS_TOK_CON;
      Value[nToken++] = 2;
      Type[nToken] = XPRS_TOK_OP;
      Value[nToken++] = XPRS_OP_PLUS;
      Type[nToken] = XPRS_TOK_CON;
      Value[nToken++] = 2;
      Type[nToken] = XPRS_TOK_COL;
      Value[nToken++] = nSide+i;
      Type[nToken] = XPRS_TOK_OP;
      Value[nToken++] = XPRS_OP_MULTIPLY;
      Type[nToken] = XPRS_TOK_COL;
      Value[nToken++] = nSide+j;
      Type[nToken] = XPRS_TOK_OP;
      Value[nToken++] = XPRS_OP_MULTIPLY;
      Type[nToken] = XPRS_TOK_RB;
      Value[nToken++] = 0;
      Type[nToken] = XPRS_TOK_COL;
      Value[nToken++] = j;
      Type[nToken] = XPRS_TOK_COL;
      Value[nToken++] = i;
      Type[nToken] = XPRS_TOK_OP;
      Value[nToken++] = XPRS_OP_MINUS;
      Type[nToken] = XPRS_TOK_IFUN;
      Value[nToken++] = Cos;
      Type[nToken] = XPRS_TOK_OP
      Value[nToken++] = XPRS_OP_MULTIPLY;
      Type[nToken] = XPRS_TOK_OP;
      Value[nToken++] = XPRS_OP_MINUS;
      Type[nToken] = XPRS_TOK_EOF;
      Value[nToken++] = 0;
    }
  }

This writes the formula for the distances between pairs of vertices. It follows the same principle as the previous formula, writing the formula in parsed form as:
RHOi 2 ^ RHOj 2 ^ + 2 RHOi * RHOj * ) THETAj THETAi - COS * -

XPRSnlploadformulas(mprob, nForm, RowIndex, FormulaStart, 1, Type, Value);

The XPRSnlploadformulas function is the most efficient way of loading non-linear formulas into a problem. It takes an XPRSprob as its first argument and replaces any existing nonlinear formulas. There is an XPRSnlpaddformulas function which is identical except that it does not delete any existing nonlinear formulas first. There is also an XPRSnlpchgformula function, which can be used to change individual nonlinear formulas one at a time. Because we are using internal parsed format, the "Parsed" flag in the argument list is set to 1.

Adding the non-linear part of the problem using character formulae

Provided that all entities – in particular columns and user functions – have explicit and unique names, the non-linear part can be input by writing the formulae as character strings. This is not as efficient as using the XPRSnlploadformulas() function but is generally easier to understand.

/* Build up nonlinear formulas */
/* Allow space for largest formula - approx 50 characters per side for area */
  FormBuffer = (char *) malloc(50*nSide);

We shall be using large formulae, so we need a character buffer large enough to hold the largest formula we are using. The estimate here is 50 characters per side of the polygon for the area formula, which is the largest we are using.

/* Area */

  BufferPos = 0;
  for (i=1; i<nSide-1; i++) {
    if (i > 1) {
      BufferPos = BufferPos + sprintf(&FormBuffer[BufferPos], " + ");
    }
    BufferPos = BufferPos + sprintf(&FormBuffer[BufferPos], "RHO%d * RHO%d *
                SIN ( THETA%d - THETA%d )", i+1, i, i+1, i);
  }
  XPRSnlpchgformulastr(mprob, 0, FormBuffer);

The area formula is of the form:
(RHO2*RHO1*SIN(THETA2-THETA1) + RHO3*RHO2*SIN(THETA3-THETA2) + ... ) / 2
The loop writes the product for each consecutive pair of vertices and also puts in the "+" sign after the first one. Note that it is important to put spaces around the individual items for the parser to be able to distinguish them.

The XPRSnlpchgformulastr function changes the nonlinear formula for a single row using a character string. The arguments to the function are:

prob the Xpress problem pointer.
row the index of the row.
formula the formula, written in character form.

In this case, the row index is zero.

/* Distances */
  for (i=1; i<nSide-1; i++) {
    for (j=i+1; j<nSide; j++) {
      sprintf(FormBuffer, "RHO%d ^ 2 + RHO%d ^ 2 - 2 * RHO%d * RHO%d *
              COS ( THETA%d - THETA%d )", j, i, j, i, j, i);
      XPRSnlpchgformulastr(mprob, iRow, FormBuffer);
      iRow++;
    }

This creates the formula for the distance between pairs of vertices and writes each into a new row.

Provided you have given names to any user functions in your program, you can use them in a formula in exactly the same way as SIN and COS have been used above.

Checking the data

The XPRSwriteprob function writes a problem in text form which can then be checked manually. The problem can then be run using the XSLP console program, provided there are no user functions which refer back into your compiled program. In particular, this facility does allow small versions of a problem to be checked before moving on to the full size ones.

XPRSwriteprob(mprob, "testmat.lp", "l");

The first argument is the Xpress problem pointer; the second is the name of the file to be produced. The last argument allows various different types of output including "l" for the more human-readable ".lp" format and "s" for "scrambled" names – that is, internally-generated names will be used rather than those you have provided. For checking purposes, this is obviously not a good idea.

Solving and writing the solution

XPRSchgobjsense(mprob, XPRS_OBJ_MAXIMIZE);
XPRSoptimize(mprob, "", &solvestatus, &solstatus);

The XPRSoptimize function performs a non-linear optimization on the current problem when nonlinear data is loaded. To maximize, call XPRSchgobjsense with XPRS_OBJ_MAXIMIZE before calling XPRSoptimize; minimization is the default sense. The second argument can be used to pass flags as defined in the Xpress Optimizer Reference Manual. The third and fourth arguments receive the solve status and solution status on return: solvestatus takes the same values as the SOLVESTATUS attribute (how the solve terminated, e.g. completed or interrupted) and solstatus takes the same values as SOLSTATUS (the quality of the solution found, e.g. optimal or infeasible). Both may be NULL if not required.

XPRSwriteslxsol(mprob);

The standard solution file can be obtained by using the XPRSwriteslxsol function. To obtain the solution as well as activities and dual values in memory, use the XPRSgetsolution family of functions.

In addition, you can use XPRSslpgetcolinfo to query SLP-specific values for variables that appear in nonlinear formulas or that have nonlinear formulas. If cascading is enabled (see the Xpress NonLinear reference manual for more details) so that Xpress NonLinear recalculates the values of the dependent SLP variables at each SLP iteration, the recalculated value differs from the LP solution value returned by XPRSgetlpsol.

Closing the program

XPRSdestroyprob frees all resources allocated for the problem, including any nonlinear data. The XPRSfree function then releases global optimizer resources. Because all nonlinear state is held inside the XPRSprob, no separate nonlinear teardown call is needed.

  XPRSdestroyprob(mprob);
  XPRSfree();

If these functions are not called, the current call may appear to have worked and terminated correctly. However, in such a case there may be areas of memory which are not returned to the system, and so repeated calls to create and solve problems within the same running application will result in progressive loss of available memory to the system, which will manifest itself in poorer performance and could ultimately produce a system crash.

Adding initial values

So far, Xpress NonLinear has started by using values which it estimates for itself. Because most of the variables are bounded, these initial values are fairly reasonable, and the model will solve. However, in general, you will need to provide initial values for at least some of the variables. Initial values are provided using the XPRSnlpsetinitval function.

The XSLP API

The XSLP API uses a separate XSLPprob handle and functions prefixed XSLP. It requires an additional header and a different initialization sequence compared to the XPRSnlp* and XPRSslp* interface described above. The general recommendation for new users is to use the XPRS-API explained above which avoids having to maintain two separate problems.

XSLP header files

The XSLP API requires xslp.h:

#include "xprs.h"
#include "xslp.h"

XSLP initialization and teardown

The XSLP API uses a separate XSLPprob that wraps the underlying XPRSprob. Both must be initialized and destroyed:

XPRSprob mprob;
XSLPprob sprob;

if (ReturnValue=XPRSinit(NULL)) goto ErrorReturn;
if (ReturnValue=XSLPinit()) goto ErrorReturn;
if (ReturnValue=XPRScreateprob(&mprob)) goto ErrorReturn;
if (ReturnValue=XSLPcreateprob(&sprob, &mprob)) goto ErrorReturn;

/* ... model building and solving ... */

XSLPdestroyprob(sprob);
XPRSdestroyprob(mprob);
XSLPfree();
XPRSfree();

All XSLP API functions take sprob (an XSLPprob) rather than mprob (an XPRSprob) as their first argument.

Function mapping: XSLP to XPRSnlp/XPRSslp

The table below maps the most commonly used XSLP* functions to their XPRSnlp*/XPRSslp* equivalents. Where the first argument changes from XSLPprob to XPRSprob, pass mprob instead of sprob.

XSLP function XPRSnlp/XPRSslp equivalent
XSLPinit() not required
XSLPcreateprob(&sprob, &mprob) not required
XSLPfree() not required
XSLPdestroyprob(sprob) not required
XSLPsetcbmessage(sprob, func, data) XPRSsetcbmessage(mprob, func, data)
XSLPgetindex(sprob, XSLP_INTERNALFUNCNAMES, name, &idx) XPRSgetindex(mprob, XPRS_NLPINTERNALFUNCNAMES, name, &idx)
XSLPloadformulas(sprob, ...) XPRSnlploadformulas(mprob, ...)
XSLPaddformulas(sprob, ...) XPRSnlpaddformulas(mprob, ...)
XSLPchgformula(sprob, row, parsed, type, val) XPRSnlpchgformula(mprob, row, parsed, type, val)
XSLPchgformulastring(sprob, row, formula) XPRSnlpchgformulastr(mprob, row, formula)
XSLPnlpoptimize(sprob, flags) XPRSoptimize(mprob, flags, &solvestatus, &solstatus)
XSLPwriteprob(sprob, filename, flags) XPRSwriteprob(mprob, filename, flags)
XSLPgetvar(sprob, col, ...) XPRSslpgetcolinfo(mprob, type, col, p_info)
XSLPconstruct(sprob) XPRSslpconstruct(mprob)
XSLPcascade(sprob) XPRSslpcascade(mprob)
XSLPloadcoefs(sprob, ...) XPRSslploadcoefs(mprob, ...)
XSLPaddcoefs(sprob, ...) XPRSslpaddcoefs(mprob, ...)

The XSLP message callback uses an XSLPprob as its first argument. The callback registered with XPRSaddcbmessage uses an XPRSprob:

/* XSLP callback signature */
void XSLPMessage(XSLPprob my_prob, void *my_object,
       char *msg, int len, int msg_type) { ... }

/* Current callback signature */
void MessageCallback(XPRSprob my_prob, void *my_object,
       const char *msg, int len, int msg_type) { ... }

When using the XSLP API with xslp.h, the XSLP_* token and operator constants (XSLP_COL, XSLP_OP, XSLP_IFUN, XSLP_MULTIPLY, XSLP_EOF, etc.) are defined as aliases for the corresponding XPRS_TOK_* and XPRS_OP_* constants in xprs.h. They can be used interchangeably. See the Xpress NonLinear Reference Manual for the full list of token and operator constants.


© 2001-2026 Fair Isaac Corporation. All rights reserved. This documentation is the property of Fair Isaac Corporation (“FICO”). Receipt or possession of this documentation does not convey rights to disclose, reproduce, make derivative works, use, or allow others to use it except solely for internal evaluation purposes to determine whether to purchase a license to the software described in this documentation, or as otherwise set forth in a written software license agreement between you and FICO (or a FICO affiliate). Use of this documentation and the software described in it must conform strictly to the foregoing permitted uses, and no other use is permitted.