> Continuous Optimization > Solving Network-Flow Problems > Example: Using the Network Optimizer with the Callable Library netex1.c

In the standard distribution of ILOG CPLEX, the file netex1.c contains code that creates, solves, and displays the solution of the network-flow problem illustrated in Figure 10.1.

Briefly, the main function initializes the ILOG CPLEX environment and creates the problem object; it also calls the optimizer to solve the problem and retrieves the solution.

In detail, main first calls the Callable Library routine CPXopenCPLEX. As explained in Initialize the ILOG CPLEX Environment, CPXopenCPLEX must always be the first ILOG CPLEX routine called in a ILOG CPLEX Callable Library application. Those routines create the ILOG CPLEX environment and return a pointer (called env) to it. This pointer will be passed to every Callable Library routine. If this initialization routine fails, env will be NULL and the error code indicating the reason for the failure will be written to status. That error code can be transformed into a string by the Callable Library routine CPXgeterrorstring.

After main initializes the ILOG CPLEX environment, it uses the Callable Library routine CPXsetintparam to turn on the ILOG CPLEX screen indicator parameter CPX_PARAM_SCRIND so that ILOG CPLEX output appears on screen. If this parameter is turned off, ILOG CPLEX does not produce viewable output, neither on screen, nor in a log file. It is a good idea to turn this parameter on when you are debugging your application.

The Callable Library routine CPXNETcreateprob creates an empty problem object, that is, a minimum-cost network-flow problem with no arcs and no nodes.

The function buildNetwork populates the problem object; that is, it loads the problem data into the problem object. Pointer variables in the example are initialized as NULL so that you can check whether they point to valid data - a good programming practice. The most important calls in this function are to the Callable Library routines, CPXNETaddnodes, which adds nodes with the specified supply values to the network problem, and CPXNETaddarcs, which adds the arcs connecting the nodes with the specified objective values and bounds. In this example, both routines are called with their last argument NULL indicating that no names are assigned to the network nodes and arcs. If you want to name arcs and nodes in your problem, pass an array of strings instead.

The function buildNetwork also includes a few routines that are not strictly necessary to this example, but illustrate concepts you may find useful in other applications. To delete a node and all arcs dependent on that node, it uses the Callable Library routine CPXNETdelnodes. To change the objective sense to minimization, it uses the Callable Library routine CPXNETchgobjsen.

Also buildNetwork sets the row growth and column growth parameters CPX_PARAM_ROWGROWTH and CPX_PARAM_COLGROWTH. These parameters specify the amount that internal arrays are extended if more nodes or arcs are added than currently fit in allocated memory. If you build up a problem by adding nodes and arcs one by one, and if these parameters are set to a low value, then internal arrays will be frequently reallocated; frequent reallocation may negatively impact performance. Ideally, these parameters are set to the maximum number of nodes and arcs that the problem will ever have. This setting will avoid all reallocation and therefore provide best performance. The parameter CPX_PARAM_ROWGROWTH pertains to adding nodes to a network problem (and rows to an LP, QP, or MIP problem) whereas CPX_PARAM_COLGROWTH pertains to adding arcs to a network problem (or columns to an LP, QP, or MIP problem).

Look again at main, where it actually calls the network optimizer with the Callable Library routine, CPXNETprimopt. If CPXNETprimopt returns a nonzero value, then an error has occurred; otherwise, the optimization was successful. Before retrieving that solution, it is necessary to allocate arrays to hold it. Then use CPXNETsolution to copy the solution into those arrays. After displaying the solution on screen, write the network problem into a file, netex1.net in the NET file format.

The TERMINATE: label is used as a place for the program to exit if any type of error occurs. Therefore, code following this label cleans up: it frees the memory that has been allocated for the solution data; it frees the network object by calling CPXNETfreeprob; and it frees the ILOG CPLEX environment by calling CPXcloseCPLEX. All freeing should be done only if the data is actually available. The Callable Library routine CPXcloseCPLEX should always be the last ILOG CPLEX routine called in a ILOG CPLEX Callable Library application. In other words, all ILOG CPLEX objects that have been allocated should be freed before the call to CPXcloseCPLEX.

The complete program, netex1.c, appears here and online in the standard distribution.

/*------------------------------------------------------------------------*/
/*  File: examples/src/netex1.c                                           */
/*  Version 9.0                                                           */
/*------------------------------------------------------------------------*/
/*  Copyright (C) 1997-2003 by ILOG.                                      */
/*  All Rights Reserved.                                                  */
/*  Permission is expressly granted to use this example in the            */
/*  course of developing applications that use ILOG products.             */
/*------------------------------------------------------------------------*/

/* netex1.c - Entering and optimizing a network problem */

/* Import the CPLEX function declarations and the C library 
   header file stdio.h with the include of cplex.h. */

#include <ilcplex/cplex.h>
#include <stdlib.h>

/* Import the declarations for the string functions */

#include <string.h>



/* Forward declaration for function at end of program */

static int
   buildNetwork  (CPXENVptr env, CPXNETptr net);

static void
   free_and_null (char **ptr);


int
main (void)
{
   /* Declare variables and arrays for retrieving problem data and
      solution information later on. */

   int      narcs;
   int      nnodes;
   int      solstat;
   double   objval;
   double   *x     = NULL;
   double   *pi    = NULL;
   double   *slack = NULL;
   double   *dj    = NULL;

   CPXENVptr env = NULL;
   CPXNETptr net = NULL;
   int       status;
   int       i, j;

   /* Initialize the CPLEX environment */

   env = CPXopenCPLEX (&status);

   /* If an error occurs, the status value indicates the reason for
      failure.  A call to CPXgeterrorstring will produce the text of
      the error message.  Note that CPXopenCPLEX produces no
      output, so the only way to see the cause of the error is to use
      CPXgeterrorstring.  For other CPLEX routines, the errors will
      be seen if the CPX_PARAM_SCRIND indicator is set to CPX_ON.  */

   if ( env == NULL ) {
      char  errmsg[1024];
      fprintf (stderr, "Could not open CPLEX environment.\n");
      CPXgeterrorstring (env, status, errmsg);
      fprintf (stderr, "%s", errmsg);
      goto TERMINATE;
   }

   /* Turn on output to the screen */

   status = CPXsetintparam (env, CPX_PARAM_SCRIND, CPX_ON);
   if ( status ) {
      fprintf (stderr, 
               "Failure to turn on screen indicator, error %d.\n", status);
      goto TERMINATE;
   }

   /* Create the problem. */

   net = CPXNETcreateprob (env, &status, "netex1");

   /* A returned pointer of NULL may mean that not enough memory
      was available or there was some other problem.  In the case of 
      failure, an error message will have been written to the error 
      channel from inside CPLEX.  In this example, the setting of
      the parameter CPX_PARAM_SCRIND causes the error message to
      appear on stdout.  */

   if ( net == NULL ) {
      fprintf (stderr, "Failed to create network object.\n");
      goto TERMINATE;
   }

   /* Fill in the data for the problem.  Note that since the space for
      the data already exists in local variables, we pass the arrays
      directly to the routine to fill in the data structures.  */

   status = buildNetwork (env, net);

   if ( status ) {
      fprintf (stderr, "Failed to build network problem.\n");
      goto TERMINATE;
   }

   /* Optimize the problem and obtain solution. */

   status = CPXNETprimopt (env, net);
   if ( status ) {
      fprintf (stderr, "Failed to optimize network.\n");
      goto TERMINATE;
   }

   /* get network dimensions */

   narcs  = CPXNETgetnumarcs  (env, net);
   nnodes = CPXNETgetnumnodes (env, net);

   /* allocate memory for solution data */

   x     = (double *) malloc (narcs  * sizeof (double));
   dj    = (double *) malloc (narcs  * sizeof (double));
   pi    = (double *) malloc (nnodes * sizeof (double));
   slack = (double *) malloc (nnodes * sizeof (double));

   if ( x     == NULL ||
        dj    == NULL ||
        pi    == NULL ||
        slack == NULL   ) {
      fprintf (stderr, "Failed to allocate arrays.\n");
      goto TERMINATE;
   }

   status = CPXNETsolution (env, net, &solstat, &objval, x, pi, slack, dj);
   if ( status ) {
      fprintf (stderr, "Failed to obtain solution.\n");
      goto TERMINATE;
   }

   /* Write the output to the screen. */

   printf ("\nSolution status = %d\n", solstat);
   printf ("Solution value  = %f\n\n", objval);

   for (i = 0; i < nnodes; i++) {
      printf ("Node %2d:  Slack = %10f  Pi = %10f\n", i, slack[i], pi[i]);
   }

   for (j = 0; j < narcs; j++) {
      printf ("Arc  %2d:  Value = %10f  Reduced cost = %10f\n",
              j, x[j], dj[j]);
   }

   /* Finally, write a copy of the problem to a file. */

   status = CPXNETwriteprob (env, net, "netex1.net", NULL);
   if ( status ) {
      fprintf (stderr, "Failed to write network to disk.\n");
      goto TERMINATE;
   }
   
TERMINATE:

   /* Free memory for solution data */

   free_and_null ((char **) &x);
   free_and_null ((char **) &dj);
   free_and_null ((char **) &pi);
   free_and_null ((char **) &slack);

   /* Free up the problem as allocated by CPXNETcreateprob, if necessary */

   if ( net != NULL ) {
      CPXNETfreeprob (env, &net);
      if ( status ) {
         fprintf (stderr, "CPXNETfreeprob failed, error code %d.\n", status);
      }
   }

   /* Free up the CPLEX environment, if necessary */

   if ( env != NULL ) {
      status = CPXcloseCPLEX (&env);

      /* Note that CPXcloseCPLEX produces no output,
         so the only way to see the cause of the error is to use
         CPXgeterrorstring.  For other CPLEX routines, the errors will
         be seen if the CPX_PARAM_SCRIND indicator is set to CPX_ON. */

      if ( status ) {
      char  errmsg[1024];
         fprintf (stderr, "Could not close CPLEX environment.\n");
         CPXgeterrorstring (env, status, errmsg);
         fprintf (stderr, "%s", errmsg);
      }
   }
     
   return (status);

}  /* END main */



static int
buildNetwork (CPXENVptr env, CPXNETptr net)
{
   int status = 0;

   /* definitions to improve readability */

#  define NNODES  8
#  define NARCS  14
#  define inf    CPX_INFBOUND

   /* Define list of supply values for the nodes */

   double supply[NNODES] = {20.0, 0.0, 0.0, -15.0, 5.0, 0.0, 0.0, -10.0};

   /* Define list of tail or from-node indices as well as head or
      to-node indices for the arcs.  Notice that according to C
      standard the first node has index 0. */

   int    tail[NARCS] = {   0,    1,    2,    3,    6,    5,    4,
                            4,    2,    3,    3,    5,    5,    1};
   int    head[NARCS] = {   1,    2,    3,    6,    5,    7,    7,
                            1,    1,    4,    5,    3,    4,    5};

   /* Define list of objective values and lower and upper bound values
      for the arcs */

   double obj [NARCS] = { 3.0,  3.0,  4.0,  3.0,  5.0,  6.0,  7.0,
                          4.0,  2.0,  6.0,  5.0,  4.0,  3.0,  6.0};
   double ub  [NARCS] = {24.0, 25.0, 12.0, 10.0,  9.0,  inf, 20.0,
                         10.0,  5.0, 15.0, 10.0, 11.0,  6.0,  inf};
   double lb  [NARCS] = {18.0,  0.0, 12.0,  0.0,  0.0, -inf,  0.0,
                          0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0};

   /* Delete existing network.  This is not necessary in this
      context since we know we have an empty network object.
      Notice that CPXNETdelnodes deletes all arcs incident to
      the deleted nodes as well.  Therefore this one function
      call effectively deletes an existing network problem. */

   if ( CPXNETgetnumnodes (env, net) > 0 ) {
      status = CPXNETdelnodes (env, net, 0,
                               CPXNETgetnumnodes (env, net)-1);
      if ( status ) goto TERMINATE;
   }

   /* Set growth rates for rows/nodes and columns/arcs.  This
      is to avoid internal memory reallocations while adding
      nodes and arcs.  Since we are adding all nodes and all
      arcs using only one function call for each it is actually
      unnecessary, but if more function calls are used, finding
      the right settings may improve performance. */

   status = CPXsetintparam (env, CPX_PARAM_ROWGROWTH, NNODES);
   if ( status ) goto TERMINATE;

   status = CPXsetintparam (env, CPX_PARAM_COLGROWTH, NARCS);
   if ( status ) goto TERMINATE;

   /* Set optimization sense */

   status = CPXNETchgobjsen (env, net, CPX_MIN);
   if ( status ) goto TERMINATE;

   /* Add nodes to network along with their supply values,
      but without any names. */

   status = CPXNETaddnodes (env, net, NNODES, supply, NULL);
   if ( status ) goto TERMINATE;

   /* Add arcs to network along with their objective values and
      bounds, but without any names. */

   status = CPXNETaddarcs (env, net, NARCS, tail, head, lb, ub, obj, NULL);
   if ( status ) goto TERMINATE;

TERMINATE:

   return (status);

}  /* END buildnetwork */


static void
free_and_null (char **ptr)
{
   if ( *ptr != NULL ) {
      free (*ptr);
      *ptr = NULL;
   }
} /* END free_and_null */