alpar@1: /* glpapi07.c (exact simplex solver) */ alpar@1: alpar@1: /*********************************************************************** alpar@1: * This code is part of GLPK (GNU Linear Programming Kit). alpar@1: * alpar@1: * Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, alpar@1: * 2009, 2010 Andrew Makhorin, Department for Applied Informatics, alpar@1: * Moscow Aviation Institute, Moscow, Russia. All rights reserved. alpar@1: * E-mail: . alpar@1: * alpar@1: * GLPK is free software: you can redistribute it and/or modify it alpar@1: * under the terms of the GNU General Public License as published by alpar@1: * the Free Software Foundation, either version 3 of the License, or alpar@1: * (at your option) any later version. alpar@1: * alpar@1: * GLPK is distributed in the hope that it will be useful, but WITHOUT alpar@1: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY alpar@1: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public alpar@1: * License for more details. alpar@1: * alpar@1: * You should have received a copy of the GNU General Public License alpar@1: * along with GLPK. If not, see . alpar@1: ***********************************************************************/ alpar@1: alpar@1: #include "glpapi.h" alpar@1: #include "glpssx.h" alpar@1: alpar@1: /*********************************************************************** alpar@1: * NAME alpar@1: * alpar@1: * glp_exact - solve LP problem in exact arithmetic alpar@1: * alpar@1: * SYNOPSIS alpar@1: * alpar@1: * int glp_exact(glp_prob *lp, const glp_smcp *parm); alpar@1: * alpar@1: * DESCRIPTION alpar@1: * alpar@1: * The routine glp_exact is a tentative implementation of the primal alpar@1: * two-phase simplex method based on exact (rational) arithmetic. It is alpar@1: * similar to the routine glp_simplex, however, for all internal alpar@1: * computations it uses arithmetic of rational numbers, which is exact alpar@1: * in mathematical sense, i.e. free of round-off errors unlike floating alpar@1: * point arithmetic. alpar@1: * alpar@1: * Note that the routine glp_exact uses inly two control parameters alpar@1: * passed in the structure glp_smcp, namely, it_lim and tm_lim. alpar@1: * alpar@1: * RETURNS alpar@1: * alpar@1: * 0 The LP problem instance has been successfully solved. This code alpar@1: * does not necessarily mean that the solver has found optimal alpar@1: * solution. It only means that the solution process was successful. alpar@1: * alpar@1: * GLP_EBADB alpar@1: * Unable to start the search, because the initial basis specified alpar@1: * in the problem object is invalid--the number of basic (auxiliary alpar@1: * and structural) variables is not the same as the number of rows in alpar@1: * the problem object. alpar@1: * alpar@1: * GLP_ESING alpar@1: * Unable to start the search, because the basis matrix correspodning alpar@1: * to the initial basis is exactly singular. alpar@1: * alpar@1: * GLP_EBOUND alpar@1: * Unable to start the search, because some double-bounded variables alpar@1: * have incorrect bounds. alpar@1: * alpar@1: * GLP_EFAIL alpar@1: * The problem has no rows/columns. alpar@1: * alpar@1: * GLP_EITLIM alpar@1: * The search was prematurely terminated, because the simplex alpar@1: * iteration limit has been exceeded. alpar@1: * alpar@1: * GLP_ETMLIM alpar@1: * The search was prematurely terminated, because the time limit has alpar@1: * been exceeded. */ alpar@1: alpar@1: static void set_d_eps(mpq_t x, double val) alpar@1: { /* convert double val to rational x obtaining a more adequate alpar@1: fraction than provided by mpq_set_d due to allowing a small alpar@1: approximation error specified by a given relative tolerance; alpar@1: for example, mpq_set_d would give the following alpar@1: 1/3 ~= 0.333333333333333314829616256247391... -> alpar@1: -> 6004799503160661/18014398509481984 alpar@1: while this routine gives exactly 1/3 */ alpar@1: int s, n, j; alpar@1: double f, p, q, eps = 1e-9; alpar@1: mpq_t temp; alpar@1: xassert(-DBL_MAX <= val && val <= +DBL_MAX); alpar@1: #if 1 /* 30/VII-2008 */ alpar@1: if (val == floor(val)) alpar@1: { /* if val is integral, do not approximate */ alpar@1: mpq_set_d(x, val); alpar@1: goto done; alpar@1: } alpar@1: #endif alpar@1: if (val > 0.0) alpar@1: s = +1; alpar@1: else if (val < 0.0) alpar@1: s = -1; alpar@1: else alpar@1: { mpq_set_si(x, 0, 1); alpar@1: goto done; alpar@1: } alpar@1: f = frexp(fabs(val), &n); alpar@1: /* |val| = f * 2^n, where 0.5 <= f < 1.0 */ alpar@1: fp2rat(f, 0.1 * eps, &p, &q); alpar@1: /* f ~= p / q, where p and q are integers */ alpar@1: mpq_init(temp); alpar@1: mpq_set_d(x, p); alpar@1: mpq_set_d(temp, q); alpar@1: mpq_div(x, x, temp); alpar@1: mpq_set_si(temp, 1, 1); alpar@1: for (j = 1; j <= abs(n); j++) alpar@1: mpq_add(temp, temp, temp); alpar@1: if (n > 0) alpar@1: mpq_mul(x, x, temp); alpar@1: else if (n < 0) alpar@1: mpq_div(x, x, temp); alpar@1: mpq_clear(temp); alpar@1: if (s < 0) mpq_neg(x, x); alpar@1: /* check that the desired tolerance has been attained */ alpar@1: xassert(fabs(val - mpq_get_d(x)) <= eps * (1.0 + fabs(val))); alpar@1: done: return; alpar@1: } alpar@1: alpar@1: static void load_data(SSX *ssx, LPX *lp) alpar@1: { /* load LP problem data into simplex solver workspace */ alpar@1: int m = ssx->m; alpar@1: int n = ssx->n; alpar@1: int nnz = ssx->A_ptr[n+1]-1; alpar@1: int j, k, type, loc, len, *ind; alpar@1: double lb, ub, coef, *val; alpar@1: xassert(lpx_get_num_rows(lp) == m); alpar@1: xassert(lpx_get_num_cols(lp) == n); alpar@1: xassert(lpx_get_num_nz(lp) == nnz); alpar@1: /* types and bounds of rows and columns */ alpar@1: for (k = 1; k <= m+n; k++) alpar@1: { if (k <= m) alpar@1: { type = lpx_get_row_type(lp, k); alpar@1: lb = lpx_get_row_lb(lp, k); alpar@1: ub = lpx_get_row_ub(lp, k); alpar@1: } alpar@1: else alpar@1: { type = lpx_get_col_type(lp, k-m); alpar@1: lb = lpx_get_col_lb(lp, k-m); alpar@1: ub = lpx_get_col_ub(lp, k-m); alpar@1: } alpar@1: switch (type) alpar@1: { case LPX_FR: type = SSX_FR; break; alpar@1: case LPX_LO: type = SSX_LO; break; alpar@1: case LPX_UP: type = SSX_UP; break; alpar@1: case LPX_DB: type = SSX_DB; break; alpar@1: case LPX_FX: type = SSX_FX; break; alpar@1: default: xassert(type != type); alpar@1: } alpar@1: ssx->type[k] = type; alpar@1: set_d_eps(ssx->lb[k], lb); alpar@1: set_d_eps(ssx->ub[k], ub); alpar@1: } alpar@1: /* optimization direction */ alpar@1: switch (lpx_get_obj_dir(lp)) alpar@1: { case LPX_MIN: ssx->dir = SSX_MIN; break; alpar@1: case LPX_MAX: ssx->dir = SSX_MAX; break; alpar@1: default: xassert(lp != lp); alpar@1: } alpar@1: /* objective coefficients */ alpar@1: for (k = 0; k <= m+n; k++) alpar@1: { if (k == 0) alpar@1: coef = lpx_get_obj_coef(lp, 0); alpar@1: else if (k <= m) alpar@1: coef = 0.0; alpar@1: else alpar@1: coef = lpx_get_obj_coef(lp, k-m); alpar@1: set_d_eps(ssx->coef[k], coef); alpar@1: } alpar@1: /* constraint coefficients */ alpar@1: ind = xcalloc(1+m, sizeof(int)); alpar@1: val = xcalloc(1+m, sizeof(double)); alpar@1: loc = 0; alpar@1: for (j = 1; j <= n; j++) alpar@1: { ssx->A_ptr[j] = loc+1; alpar@1: len = lpx_get_mat_col(lp, j, ind, val); alpar@1: for (k = 1; k <= len; k++) alpar@1: { loc++; alpar@1: ssx->A_ind[loc] = ind[k]; alpar@1: set_d_eps(ssx->A_val[loc], val[k]); alpar@1: } alpar@1: } alpar@1: xassert(loc == nnz); alpar@1: xfree(ind); alpar@1: xfree(val); alpar@1: return; alpar@1: } alpar@1: alpar@1: static int load_basis(SSX *ssx, LPX *lp) alpar@1: { /* load current LP basis into simplex solver workspace */ alpar@1: int m = ssx->m; alpar@1: int n = ssx->n; alpar@1: int *type = ssx->type; alpar@1: int *stat = ssx->stat; alpar@1: int *Q_row = ssx->Q_row; alpar@1: int *Q_col = ssx->Q_col; alpar@1: int i, j, k; alpar@1: xassert(lpx_get_num_rows(lp) == m); alpar@1: xassert(lpx_get_num_cols(lp) == n); alpar@1: /* statuses of rows and columns */ alpar@1: for (k = 1; k <= m+n; k++) alpar@1: { if (k <= m) alpar@1: stat[k] = lpx_get_row_stat(lp, k); alpar@1: else alpar@1: stat[k] = lpx_get_col_stat(lp, k-m); alpar@1: switch (stat[k]) alpar@1: { case LPX_BS: alpar@1: stat[k] = SSX_BS; alpar@1: break; alpar@1: case LPX_NL: alpar@1: stat[k] = SSX_NL; alpar@1: xassert(type[k] == SSX_LO || type[k] == SSX_DB); alpar@1: break; alpar@1: case LPX_NU: alpar@1: stat[k] = SSX_NU; alpar@1: xassert(type[k] == SSX_UP || type[k] == SSX_DB); alpar@1: break; alpar@1: case LPX_NF: alpar@1: stat[k] = SSX_NF; alpar@1: xassert(type[k] == SSX_FR); alpar@1: break; alpar@1: case LPX_NS: alpar@1: stat[k] = SSX_NS; alpar@1: xassert(type[k] == SSX_FX); alpar@1: break; alpar@1: default: alpar@1: xassert(stat != stat); alpar@1: } alpar@1: } alpar@1: /* build permutation matix Q */ alpar@1: i = j = 0; alpar@1: for (k = 1; k <= m+n; k++) alpar@1: { if (stat[k] == SSX_BS) alpar@1: { i++; alpar@1: if (i > m) return 1; alpar@1: Q_row[k] = i, Q_col[i] = k; alpar@1: } alpar@1: else alpar@1: { j++; alpar@1: if (j > n) return 1; alpar@1: Q_row[k] = m+j, Q_col[m+j] = k; alpar@1: } alpar@1: } alpar@1: xassert(i == m && j == n); alpar@1: return 0; alpar@1: } alpar@1: alpar@1: int glp_exact(glp_prob *lp, const glp_smcp *parm) alpar@1: { glp_smcp _parm; alpar@1: SSX *ssx; alpar@1: int m = lpx_get_num_rows(lp); alpar@1: int n = lpx_get_num_cols(lp); alpar@1: int nnz = lpx_get_num_nz(lp); alpar@1: int i, j, k, type, pst, dst, ret, *stat; alpar@1: double lb, ub, *prim, *dual, sum; alpar@1: if (parm == NULL) alpar@1: parm = &_parm, glp_init_smcp((glp_smcp *)parm); alpar@1: /* check control parameters */ alpar@1: if (parm->it_lim < 0) alpar@1: xerror("glp_exact: it_lim = %d; invalid parameter\n", alpar@1: parm->it_lim); alpar@1: if (parm->tm_lim < 0) alpar@1: xerror("glp_exact: tm_lim = %d; invalid parameter\n", alpar@1: parm->tm_lim); alpar@1: /* the problem must have at least one row and one column */ alpar@1: if (!(m > 0 && n > 0)) alpar@1: { xprintf("glp_exact: problem has no rows/columns\n"); alpar@1: return GLP_EFAIL; alpar@1: } alpar@1: #if 1 alpar@1: /* basic solution is currently undefined */ alpar@1: lp->pbs_stat = lp->dbs_stat = GLP_UNDEF; alpar@1: lp->obj_val = 0.0; alpar@1: lp->some = 0; alpar@1: #endif alpar@1: /* check that all double-bounded variables have correct bounds */ alpar@1: for (k = 1; k <= m+n; k++) alpar@1: { if (k <= m) alpar@1: { type = lpx_get_row_type(lp, k); alpar@1: lb = lpx_get_row_lb(lp, k); alpar@1: ub = lpx_get_row_ub(lp, k); alpar@1: } alpar@1: else alpar@1: { type = lpx_get_col_type(lp, k-m); alpar@1: lb = lpx_get_col_lb(lp, k-m); alpar@1: ub = lpx_get_col_ub(lp, k-m); alpar@1: } alpar@1: if (type == LPX_DB && lb >= ub) alpar@1: { xprintf("glp_exact: %s %d has invalid bounds\n", alpar@1: k <= m ? "row" : "column", k <= m ? k : k-m); alpar@1: return GLP_EBOUND; alpar@1: } alpar@1: } alpar@1: /* create the simplex solver workspace */ alpar@1: xprintf("glp_exact: %d rows, %d columns, %d non-zeros\n", alpar@1: m, n, nnz); alpar@1: #ifdef HAVE_GMP alpar@1: xprintf("GNU MP bignum library is being used\n"); alpar@1: #else alpar@1: xprintf("GLPK bignum module is being used\n"); alpar@1: xprintf("(Consider installing GNU MP to attain a much better perf" alpar@1: "ormance.)\n"); alpar@1: #endif alpar@1: ssx = ssx_create(m, n, nnz); alpar@1: /* load LP problem data into the workspace */ alpar@1: load_data(ssx, lp); alpar@1: /* load current LP basis into the workspace */ alpar@1: if (load_basis(ssx, lp)) alpar@1: { xprintf("glp_exact: initial LP basis is invalid\n"); alpar@1: ret = GLP_EBADB; alpar@1: goto done; alpar@1: } alpar@1: /* inherit some control parameters from the LP object */ alpar@1: #if 0 alpar@1: ssx->it_lim = lpx_get_int_parm(lp, LPX_K_ITLIM); alpar@1: ssx->it_cnt = lpx_get_int_parm(lp, LPX_K_ITCNT); alpar@1: ssx->tm_lim = lpx_get_real_parm(lp, LPX_K_TMLIM); alpar@1: #else alpar@1: ssx->it_lim = parm->it_lim; alpar@1: ssx->it_cnt = lp->it_cnt; alpar@1: ssx->tm_lim = (double)parm->tm_lim / 1000.0; alpar@1: #endif alpar@1: ssx->out_frq = 5.0; alpar@1: ssx->tm_beg = xtime(); alpar@1: ssx->tm_lag = xlset(0); alpar@1: /* solve LP */ alpar@1: ret = ssx_driver(ssx); alpar@1: /* copy back some statistics to the LP object */ alpar@1: #if 0 alpar@1: lpx_set_int_parm(lp, LPX_K_ITLIM, ssx->it_lim); alpar@1: lpx_set_int_parm(lp, LPX_K_ITCNT, ssx->it_cnt); alpar@1: lpx_set_real_parm(lp, LPX_K_TMLIM, ssx->tm_lim); alpar@1: #else alpar@1: lp->it_cnt = ssx->it_cnt; alpar@1: #endif alpar@1: /* analyze the return code */ alpar@1: switch (ret) alpar@1: { case 0: alpar@1: /* optimal solution found */ alpar@1: ret = 0; alpar@1: pst = LPX_P_FEAS, dst = LPX_D_FEAS; alpar@1: break; alpar@1: case 1: alpar@1: /* problem has no feasible solution */ alpar@1: ret = 0; alpar@1: pst = LPX_P_NOFEAS, dst = LPX_D_INFEAS; alpar@1: break; alpar@1: case 2: alpar@1: /* problem has unbounded solution */ alpar@1: ret = 0; alpar@1: pst = LPX_P_FEAS, dst = LPX_D_NOFEAS; alpar@1: #if 1 alpar@1: xassert(1 <= ssx->q && ssx->q <= n); alpar@1: lp->some = ssx->Q_col[m + ssx->q]; alpar@1: xassert(1 <= lp->some && lp->some <= m+n); alpar@1: #endif alpar@1: break; alpar@1: case 3: alpar@1: /* iteration limit exceeded (phase I) */ alpar@1: ret = GLP_EITLIM; alpar@1: pst = LPX_P_INFEAS, dst = LPX_D_INFEAS; alpar@1: break; alpar@1: case 4: alpar@1: /* iteration limit exceeded (phase II) */ alpar@1: ret = GLP_EITLIM; alpar@1: pst = LPX_P_FEAS, dst = LPX_D_INFEAS; alpar@1: break; alpar@1: case 5: alpar@1: /* time limit exceeded (phase I) */ alpar@1: ret = GLP_ETMLIM; alpar@1: pst = LPX_P_INFEAS, dst = LPX_D_INFEAS; alpar@1: break; alpar@1: case 6: alpar@1: /* time limit exceeded (phase II) */ alpar@1: ret = GLP_ETMLIM; alpar@1: pst = LPX_P_FEAS, dst = LPX_D_INFEAS; alpar@1: break; alpar@1: case 7: alpar@1: /* initial basis matrix is singular */ alpar@1: ret = GLP_ESING; alpar@1: goto done; alpar@1: default: alpar@1: xassert(ret != ret); alpar@1: } alpar@1: /* obtain final basic solution components */ alpar@1: stat = xcalloc(1+m+n, sizeof(int)); alpar@1: prim = xcalloc(1+m+n, sizeof(double)); alpar@1: dual = xcalloc(1+m+n, sizeof(double)); alpar@1: for (k = 1; k <= m+n; k++) alpar@1: { if (ssx->stat[k] == SSX_BS) alpar@1: { i = ssx->Q_row[k]; /* x[k] = xB[i] */ alpar@1: xassert(1 <= i && i <= m); alpar@1: stat[k] = LPX_BS; alpar@1: prim[k] = mpq_get_d(ssx->bbar[i]); alpar@1: dual[k] = 0.0; alpar@1: } alpar@1: else alpar@1: { j = ssx->Q_row[k] - m; /* x[k] = xN[j] */ alpar@1: xassert(1 <= j && j <= n); alpar@1: switch (ssx->stat[k]) alpar@1: { case SSX_NF: alpar@1: stat[k] = LPX_NF; alpar@1: prim[k] = 0.0; alpar@1: break; alpar@1: case SSX_NL: alpar@1: stat[k] = LPX_NL; alpar@1: prim[k] = mpq_get_d(ssx->lb[k]); alpar@1: break; alpar@1: case SSX_NU: alpar@1: stat[k] = LPX_NU; alpar@1: prim[k] = mpq_get_d(ssx->ub[k]); alpar@1: break; alpar@1: case SSX_NS: alpar@1: stat[k] = LPX_NS; alpar@1: prim[k] = mpq_get_d(ssx->lb[k]); alpar@1: break; alpar@1: default: alpar@1: xassert(ssx != ssx); alpar@1: } alpar@1: dual[k] = mpq_get_d(ssx->cbar[j]); alpar@1: } alpar@1: } alpar@1: /* and store them into the LP object */ alpar@1: pst = pst - LPX_P_UNDEF + GLP_UNDEF; alpar@1: dst = dst - LPX_D_UNDEF + GLP_UNDEF; alpar@1: for (k = 1; k <= m+n; k++) alpar@1: stat[k] = stat[k] - LPX_BS + GLP_BS; alpar@1: sum = lpx_get_obj_coef(lp, 0); alpar@1: for (j = 1; j <= n; j++) alpar@1: sum += lpx_get_obj_coef(lp, j) * prim[m+j]; alpar@1: lpx_put_solution(lp, 1, &pst, &dst, &sum, alpar@1: &stat[0], &prim[0], &dual[0], &stat[m], &prim[m], &dual[m]); alpar@1: xfree(stat); alpar@1: xfree(prim); alpar@1: xfree(dual); alpar@1: done: /* delete the simplex solver workspace */ alpar@1: ssx_delete(ssx); alpar@1: /* return to the application program */ alpar@1: return ret; alpar@1: } alpar@1: alpar@1: /* eof */