/* glpipm2.c */

/*----------------------------------------------------------------------
-- Copyright (C) 2000, 2001, 2002 Andrew Makhorin <mao@mai2.rcnet.ru>,
--               Department for Applied Informatics, Moscow Aviation
--               Institute, Moscow, Russia. All rights reserved.
--
-- This file is a part of GLPK (GNU Linear Programming Kit).
--
-- GLPK is free software; you can redistribute it and/or modify it
-- under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2, or (at your option)
-- any later version.
--
-- GLPK is distributed in the hope that it will be useful, but WITHOUT
-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
-- or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
-- License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with GLPK; see the file COPYING. If not, write to the Free
-- Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-- 02111-1307, USA.
----------------------------------------------------------------------*/

#include <stddef.h>
#include <time.h>
#include "glpipm.h"

/*----------------------------------------------------------------------
-- ipm1_driver - driver for the primal-dual interior point method.
--
-- *Synopsis*
--
-- #include "glpipm.h"
-- int ipm1_driver(LP *lp, LPSOL *sol);
--
-- *Description*
--
-- The ipm1_driver routine is a driver for the routine ipm1 which
-- implements primal-dual interior point method for linear programming.
--
-- The ipm1_driver routine transforms the original LP problem, which lp
-- points to, to the standard form (all constraints are equalities, all
-- variables are non-negative), calls the ipm1 routine for solving the
-- transformed problem, and recovers solution of the original problem
-- storing it into the LP solution block, which sol points to.
--
-- *Returns*
--
-- The ipm1_driver routine returns the same code which is reported by
-- the ipm1 routine. */

static LP *lp;
/* linear programming (LP) problem data block */

static LPSOL *sol;
/* linear programming (LP) problem solution block */

static int *ref; /* int ref[1+lp->m+lp->n]; */
/* this array contains references from variables of the original LP
   problem to the components of the transformed LP problem */

/* the following five components defines the transformed LP problem in
   the standard form:
   minimize    F = c'*x + c[0]
   subject to  A*x = b, x >= 0 */

static int m;
/* number of constraints */

static int n;
/* number of variables */

static MAT *A; /* MAT A[m,n]; */
/* matrix of constraint coefficients */

static double *b; /* double b[1+m]; */
/* vector of right-hand sides */

static double *c; /* double c[1+n]; */
/* vector of coefficients of the objective function and the constant
   term c[0] */

/* the following three arrays defines a solution of the transformed LP
   problem obtained by the interior point solver */

static double *x; /* double x[1+n]; */
/* values of primal variables */

static double *y; /* double y[1+m]; */
/* values of dual variables for equality constraints */

static double *z; /* double z[1+n]; */
/* values of dual variables for non-negativity constraints */

/*----------------------------------------------------------------------
-- calc_mn - determine dimension of the transformed LP problem.
--
-- This routine calculates number of rows (m) and number of columns (n)
-- of the transformed LP. */

static void calc_mn(void)
{     int k;
      m = n = 0;
      for (k = 1; k <= lp->m; k++)
      {  switch (lp->type[k])
         {  case 'F': break;
            case 'L': m++; n++; break;
            case 'U': m++; n++; break;
            case 'D': m += 2; n += 2; break;
            case 'S': m++; break;
            default: insist(lp->type[k] != lp->type[k]);
         }
      }
      for (k = lp->m+1; k <= lp->m+lp->n; k++)
      {  switch (lp->type[k])
         {  case 'F': n += 2; break;
            case 'L': n++; break;
            case 'U': n++; break;
            case 'D': m++; n += 2; break;
            case 'S': break;
            default: insist(lp->type[k] != lp->type[k]);
         }
      }
      return;
}

/*----------------------------------------------------------------------
-- transform - transform original LP to the standard formulation.
--
-- This routine transforms the original LP problem to the standard
-- formulation. */

static void transform(void)
{     int i, j, k;
      /* initialize components of the transformed LP */
      clear_mat(A);
      for (i = 1; i <= m; i++) b[i] = 0.0;
      c[0] = lp->c[0];
      for (j = 1; j <= n; j++) c[j] = 0.0;
      /* i and j are respectively number of current row and number of
         current column of the transformed LP */
      i = j = 0;
      /* transform rows (auxiliary variables) */
      for (k = 1; k <= lp->m; k++)
      {  switch (lp->type[k])
         {  case 'F':
               /* source: -inf < (L.F.) < +inf */
               /* result: ignore free row */
               ref[k] = 0;
               break;
            case 'L':
               /* source: lb <= (L.F.) < +inf */
               /* result: (L.F.) - x' = lb, x' >= 0 */
               i++; j++;
               ref[k] = i;
               new_elem(A, i, j, -1.0);
               b[i] = lp->lb[k];
               break;
            case 'U':
               /* source: -inf < (L.F.) <= ub */
               /* result: (L.F.) + x' = ub, x' >= 0 */
               i++; j++;
               ref[k] = i;
               new_elem(A, i, j, +1.0);
               b[i] = lp->ub[k];
               break;
            case 'D':
               /* source: lb <= (L.F.) <= ub */
               /* result: (L.F.) - x' = lb, x' + x'' = ub - lb */
               i++; j++;
               ref[k] = i;
               new_elem(A, i, j, -1.0);
               b[i] = lp->lb[k];
               i++;
               new_elem(A, i, j, +1.0);
               j++;
               new_elem(A, i, j, +1.0);
               b[i] = lp->ub[k] - lp->lb[k];
               break;
            case 'S':
               /* source: (L.F.) = lb */
               /* result: (L.F.) = lb */
               i++;
               ref[k] = i;
               b[i] = lp->lb[k];
               break;
            default: insist(lp->type[k] != lp->type[k]);
         }
      }
      /* transform columns (structural variables) */
      for (k = lp->m+1; k <= lp->m+lp->n; k++)
      {  ELEM *e;
         switch (lp->type[k])
         {  case 'F':
               /* source: -inf < x < +inf */
               /* result: x = x' - x'', x' >= 0, x'' >= 0 */
               j++;
               ref[k] = j;
               for (e = lp->A->col[k-lp->m]; e != NULL; e = e->col)
               {  if (ref[e->i] != 0)
                     new_elem(A, ref[e->i], j, +e->val);
               }
               c[j] = +lp->c[k-lp->m];
               j++;
               for (e = lp->A->col[k-lp->m]; e != NULL; e = e->col)
               {  if (ref[e->i] != 0)
                     new_elem(A, ref[e->i], j, -e->val);
               }
               c[j] = -lp->c[k-lp->m];
               break;
            case 'L':
               /* source: lb <= x < +inf */
               /* result: x = lb + x', x' >= 0 */
               j++;
               ref[k] = j;
               for (e = lp->A->col[k-lp->m]; e != NULL; e = e->col)
               {  if (ref[e->i] != 0)
                  {  new_elem(A, ref[e->i], j, e->val);
                     b[ref[e->i]] -= e->val * lp->lb[k];
                  }
               }
               c[j] = lp->c[k-lp->m];
               c[0] += c[j] * lp->lb[k];
               break;
            case 'U':
               /* source: -inf < x <= ub */
               /* result: x = ub - x', x' >= 0 */
               j++;
               ref[k] = j;
               for (e = lp->A->col[k-lp->m]; e != NULL; e = e->col)
               {  if (ref[e->i] != 0)
                  {  new_elem(A, ref[e->i], j, e->val);
                     b[ref[e->i]] += e->val * lp->ub[k];
                  }
               }
               c[j] = lp->c[k-lp->m];
               c[0] -= c[j] * lp->ub[k];
               break;
            case 'D':
               /* source: lb <= x <= ub */
               /* result: x = lb + x', x' + x'' = ub - lb */
               j++;
               ref[k] = j;
               for (e = lp->A->col[k-lp->m]; e != NULL; e = e->col)
               {  if (ref[e->i] != 0)
                  {  new_elem(A, ref[e->i], j, e->val);
                     b[ref[e->i]] -= e->val * lp->lb[k];
                  }
               }
               c[j] = lp->c[k-lp->m];
               c[0] += c[j] * lp->lb[k];
               i++;
               new_elem(A, i, j, +1.0);
               j++;
               new_elem(A, i, j, +1.0);
               b[i] = lp->ub[k] - lp->lb[k];
               break;
            case 'S':
               /* source: x = lb */
               /* result: just substitute */
               ref[k] = 0;
               for (e = lp->A->col[k-lp->m]; e != NULL; e = e->col)
               {  if (ref[e->i] != 0)
                     b[ref[e->i]] -= e->val * lp->lb[k];
               }
               c[0] += lp->c[k-lp->m] * lp->lb[k];
               break;
            default: insist(lp->type[k] != lp->type[k]);
         }
      }
      insist(i == m && j == n);
      /* change sign of the objective func in case of maximization */
      if (lp->dir == '+')
         for (j = 0; j <= n; j++) c[j] = -c[j];
      /* end of transformation */
      return;
}

/*----------------------------------------------------------------------
-- recover - recover solution of the original LP problem.
--
-- This routine recovers solution of the original LP problem using the
-- solution of the transformed LP problem obtained by the solver. */

static void recover(int status)
{     int j, k;
      insist(lp->m == sol->m && lp->n == sol->n);
      /* set solution status */
      if (status == 0)
         sol->status = 'O'; /* solution is optimal */
      else
         sol->status = '?'; /* solution is undefined */
      /* compute values of structural variables */
      for (k = lp->m+1; k <= lp->m+lp->n; k++)
      {  j = ref[k];
         switch (lp->type[k])
         {  case 'F':
               /* source: -inf < x < +inf */
               /* result: x = x' - x'', x' >= 0, x'' >= 0 */
               sol->valx[k] = x[j] - x[j+1];
               break;
            case 'L':
               /* source: lb <= x < +inf */
               /* result: x = lb + x', x' >= 0 */
               sol->valx[k] = lp->lb[k] + x[j];
               break;
            case 'U':
               /* source: -inf < x <= ub */
               /* result: x = ub - x', x' >= 0 */
               sol->valx[k] = lp->ub[k] - x[j];
               break;
            case 'D':
               /* source: lb <= x <= ub */
               /* result: x = lb + x', x' + x'' = ub - lb */
               sol->valx[k] = lp->lb[k] + x[j];
               break;
            case 'S':
               /* source: x = lb */
               /* result: just substitute */
               sol->valx[k] = lp->lb[k];
               break;
            default: insist(lp->type[k] != lp->type[k]);
         }
         sol->tagx[k] = '?';
         sol->dx[k] = 0.0;
      }
      /* compute value of the objective function */
      sol->objval = 0.0;
      for (j = 1; j <= lp->n; j++)
         sol->objval += lp->c[j] * sol->valx[lp->m+j];
      /* compute values of auxiliary variables */
      for (k = 1; k <= lp->m; k++)
      {  ELEM *e;
         sol->valx[k] = 0.0;
         for (e = lp->A->row[k]; e != NULL; e = e->row)
            sol->valx[k] += e->val * sol->valx[lp->m+e->j];
         sol->tagx[k] = '?';
         sol->dx[k] = 0.0;
      }
      return;
}

/*----------------------------------------------------------------------
-- ipm1_driver - driver routine.
--
-- This is driver routine for the primal-dual interior point method. */

int ipm1_driver(LP *_lp, LPSOL *_sol)
{     int status;
      lp = _lp;
      sol = _sol;
      if (!(lp->m == sol->m && lp->n == sol->n))
         fault("ipm1_driver: inconsistent dimension");
      /* determine dimension of the transformed LP problem */
      print("ipm1_driver: original LP problem has %d rows and %d column"
         "s", lp->m, lp->n);
      calc_mn();
      print("ipm1_driver: transformed LP problem has %d rows and %d col"
         "umns", m, n);
      /* allocate working arrays */
      ref = ucalloc(1+lp->m+lp->n, sizeof(int));
      A = create_mat(m, n);
      b = ucalloc(1+m, sizeof(double));
      c = ucalloc(1+n, sizeof(double));
      x = ucalloc(1+n, sizeof(double));
      y = ucalloc(1+m, sizeof(double));
      z = ucalloc(1+n, sizeof(double));
      /* transform original LP to the standard formulation */
      transform();
      /* solve the transformed LP problem */
      status = ipm1(A, b, c, x, y, z);
      /* recover solution of the original LP problem */
      recover(status);
      /* free memory allocated to working arrays */
      ufree(ref);
      delete_mat(A);
      ufree(b);
      ufree(c);
      ufree(x);
      ufree(y);
      ufree(z);
      /* return to the calling program */
      return status;
}

/* eof */
