/* ------------------------------------------------------------------------- Presolve routines for lp_solve v5.0+ ------------------------------------------------------------------------- Author: Kjell Eikland Contact: kjell.eikland@broadpark.no License terms: LGPL. Requires: lp_lib.h, lp_presolve, lp_crash.h, lp_scale.h, lp_report.h Release notes: v1.0.0 1 January 2003 Initial crude version used with lp_solve v4. v5.0.0 1 January 2004 Significantly expanded and repackaged presolve routines for lp_solve v5 release. v5.0.1 1 April 2004 Added reference to new crash module v5.1.0 20 August 2004 Reworked infeasibility detection. Added encapsulation of presolve undo logic. v5.1.1 10 September 2004 Added variable bound tightening based on full-constraint information, as well as variable fixing by duality. v5.2.0 1 January 2005 Fixes to bound fixing handling. Added fast batch compression after presolve. Restructured calls by adding presolve wrapper. Major optimization of identification logic along with bug fixes. Enabled storage of eliminated matrix data. Added function to report on constraint classes. v5.5.0 1 June 2005 Added implied slack presolve, restructured looping logic to be more modular, and made active row/column selection logic faster. v5.5.1 18 June 2005 Finished sparsity-enhancing logic and added initial version of column aggregation code. ------------------------------------------------------------------------- */ #include #include "commonlib.h" #include "lp_lib.h" #include "lp_presolve.h" #include "lp_crash.h" #include "lp_scale.h" #include "lp_report.h" #ifdef FORTIFY # include "lp_fortify.h" #endif #define presolve_setstatus(one, two) presolve_setstatusex(one, two, __LINE__, __FILE__) STATIC int presolve_setstatusex(presolverec *psdata, int status, int lineno, char *filename) { if((status == INFEASIBLE) || (status == UNBOUNDED)) { report(psdata->lp, #ifdef Paranoia NORMAL, #else DETAILED, #endif "presolve_setstatus: Status set to '%s' on code line %d, file '%s'\n", (status == INFEASIBLE ? "INFEASIBLE" : "UNBOUNDED"), lineno, (filename == NULL ? "Unknown" : filename)); } return( status ); } STATIC MYBOOL presolve_statuscheck(presolverec *psdata, int *status) { if(*status == RUNNING) { lprec *lp = psdata->lp; if(!mat_validate(lp->matA)) *status = MATRIXERROR; else if(userabort(lp, -1)) *status = lp->spx_status; } return( (MYBOOL) (*status == RUNNING) ); } STATIC MYBOOL presolve_createUndo(lprec *lp) { if(lp->presolve_undo != NULL) presolve_freeUndo(lp); lp->presolve_undo = (presolveundorec *) calloc(1, sizeof(presolveundorec)); lp->presolve_undo->lp = lp; if(lp->presolve_undo == NULL) return( FALSE ); return( TRUE ); } STATIC MYBOOL inc_presolve_space(lprec *lp, int delta, MYBOOL isrows) { int i, ii, oldrowcolalloc, rowcolsum, oldrowalloc, oldcolalloc; presolveundorec *psundo = lp->presolve_undo; if(psundo == NULL) { presolve_createUndo(lp); psundo = lp->presolve_undo; } /* Set constants */ oldrowalloc = lp->rows_alloc-delta; oldcolalloc = lp->columns_alloc-delta; oldrowcolalloc = lp->sum_alloc-delta; rowcolsum = lp->sum_alloc + 1; /* Reallocate lp memory */ if(isrows) allocREAL(lp, &psundo->fixed_rhs, lp->rows_alloc+1, AUTOMATIC); else allocREAL(lp, &psundo->fixed_obj, lp->columns_alloc+1, AUTOMATIC); allocINT(lp, &psundo->var_to_orig, rowcolsum, AUTOMATIC); allocINT(lp, &psundo->orig_to_var, rowcolsum, AUTOMATIC); /* Fill in default values, where appropriate */ if(isrows) ii = oldrowalloc+1; else ii = oldcolalloc+1; for(i = oldrowcolalloc+1; i < rowcolsum; i++, ii++) { psundo->var_to_orig[i] = 0; psundo->orig_to_var[i] = 0; if(isrows) psundo->fixed_rhs[ii] = 0; else psundo->fixed_obj[ii] = 0; } return(TRUE); } STATIC MYBOOL presolve_setOrig(lprec *lp, int orig_rows, int orig_cols) { presolveundorec *psundo = lp->presolve_undo; if(psundo == NULL) return( FALSE ); psundo->orig_rows = orig_rows; psundo->orig_columns = orig_cols; psundo->orig_sum = orig_rows + orig_cols; if(lp->wasPresolved) presolve_fillUndo(lp, orig_rows, orig_cols, FALSE); return( TRUE ); } STATIC MYBOOL presolve_fillUndo(lprec *lp, int orig_rows, int orig_cols, MYBOOL setOrig) { int i; presolveundorec *psundo = lp->presolve_undo; for(i = 0; i <= orig_rows; i++) { psundo->var_to_orig[i] = i; psundo->orig_to_var[i] = i; psundo->fixed_rhs[i] = 0; } for(i = 1; i <= orig_cols; i++) { psundo->var_to_orig[orig_rows + i] = i; psundo->orig_to_var[orig_rows + i] = i; psundo->fixed_obj[i] = 0; } if(setOrig) presolve_setOrig(lp, orig_rows, orig_cols); return( TRUE ); } STATIC MYBOOL presolve_rebuildUndo(lprec *lp, MYBOOL isprimal) { int ik, ie, ix, j, k, *colnrDep; REAL hold, *value, *solution, *slacks; presolveundorec *psdata = lp->presolve_undo; MATrec *mat = NULL; /* Point to and initialize undo structure at first call */ if(isprimal) { if(psdata->primalundo != NULL) mat = psdata->primalundo->tracker; solution = lp->full_solution + lp->presolve_undo->orig_rows; slacks = lp->full_solution; } else { if(psdata->dualundo != NULL) mat = psdata->dualundo->tracker; solution = lp->full_duals; slacks = lp->full_duals + lp->presolve_undo->orig_rows; } if(mat == NULL) return( FALSE ); /* Loop backward over the undo chain */ for(j = mat->col_tag[0]; j > 0; j--) { ix = mat->col_tag[j]; ik = mat->col_end[j-1]; ie = mat->col_end[j]; colnrDep = &COL_MAT_ROWNR(ik); value = &COL_MAT_VALUE(ik); hold = 0; k = 0; for(; ik < ie; ik++, colnrDep += matRowColStep, value += matValueStep) { /* Constant term */ if(*colnrDep == 0) hold += *value; /* Special case with dependence on a slack variable */ else if(isprimal && (*colnrDep > lp->presolve_undo->orig_columns)) { k = (*colnrDep) - lp->presolve_undo->orig_columns; hold -= (*value) * slacks[k]; slacks[k] = 0; } else if(!isprimal && (*colnrDep > lp->presolve_undo->orig_rows)) { k = (*colnrDep) - lp->presolve_undo->orig_rows; hold -= (*value) * slacks[k]; slacks[k] = 0; } /* Dependence on other user variable */ else hold -= (*value) * solution[*colnrDep]; *value = 0; } if(fabs(hold) > lp->epsvalue) solution[ix] = hold; } return( TRUE ); } STATIC MYBOOL presolve_freeUndo(lprec *lp) { presolveundorec *psundo = lp->presolve_undo; if(psundo == NULL) return( FALSE ); FREE(psundo->orig_to_var); FREE(psundo->var_to_orig); FREE(psundo->fixed_rhs); FREE(psundo->fixed_obj); if(psundo->deletedA != NULL) freeUndoLadder(&(psundo->deletedA)); if(psundo->primalundo != NULL) freeUndoLadder(&(psundo->primalundo)); if(psundo->dualundo != NULL) freeUndoLadder(&(psundo->dualundo)); FREE(lp->presolve_undo); return( TRUE ); } STATIC void presolve_storeDualUndo(presolverec *psdata, int rownr, int colnr) { lprec *lp = psdata->lp; MYBOOL firstdone = FALSE; int ix, iix, item; REAL Aij = get_mat(lp, rownr, colnr); MATrec *mat = lp->matA; if(presolve_collength(psdata, colnr) == 0) return; /* Add undo information for the dual of the deleted constraint */ item = 0; for(ix = presolve_nextrow(psdata, colnr, &item); ix >= 0; ix = presolve_nextrow(psdata, colnr, &item)) { iix = COL_MAT_ROWNR(ix); if(iix == rownr) continue; if(!firstdone) firstdone = addUndoPresolve(lp, FALSE, rownr, get_mat(lp, 0, colnr)/Aij, get_mat_byindex(lp, ix, FALSE, TRUE)/Aij, iix); else appendUndoPresolve(lp, FALSE, get_mat_byindex(lp, ix, FALSE, TRUE)/Aij, iix); } } /* ----------------------------------------------------------------------------- */ /* Presolve debugging routines */ /* ----------------------------------------------------------------------------- */ STATIC MYBOOL presolve_SOScheck(presolverec *psdata) { MYBOOL status = TRUE; lprec *lp = psdata->lp; int *list, i, j, n, k, nk, colnr, nSOS = SOS_count(lp), nerr = 0; SOSrec *SOS; if(nSOS == 0) return( status ); /* For each SOS and each member check validity */ for(i = 1; i<= nSOS; i++) { SOS = lp->SOS->sos_list[i-1]; list = SOS->members; n = list[0]; for(j = 1; j<= n; j++) { colnr = list[j]; /* Check valid range */ if((colnr < 1) || (colnr > lp->columns)) { nerr++; report(lp, IMPORTANT, "presolve_SOScheck: A - Column index %d is outside of valid range\n", colnr); } /* Check for deletion */ if(!isActiveLink(psdata->cols->varmap, colnr)) { nerr++; report(lp, IMPORTANT, "presolve_SOScheck: B - Column index %d has been marked for deletion\n", colnr); } /* Check if sorted member array is Ok */ if(SOS_member_index(lp->SOS, i, colnr) != j) { nerr++; report(lp, IMPORTANT, "presolve_SOScheck: C - Column index %d not found in fast search array\n", colnr); } /* Check for variable membership in this SOS record of the sparse storage */ k = lp->SOS->memberpos[colnr-1]; nk = lp->SOS->memberpos[colnr]; while((k < nk) && (lp->SOS->membership[k] != i)) k++; if(k >= nk) { nerr++; report(lp, IMPORTANT, "presolve_SOScheck: D - Column index %d was not found in sparse array\n", colnr); } } } /* Check that all members in the sparse array can be validated as SOS members */ for(colnr = 1; colnr <= lp->columns; colnr++) { k = lp->SOS->memberpos[colnr-1]; nk = lp->SOS->memberpos[colnr]; for(; k < nk; k++) { if(!SOS_is_member(lp->SOS, lp->SOS->membership[k], colnr)) { nerr++; report(lp, IMPORTANT, "presolve_SOScheck: E - Sparse array did not indicate column index %d as member of SOS %d\n", colnr, lp->SOS->membership[k]); } } } status = (MYBOOL) (nerr == 0); if(!status) report(lp, IMPORTANT, "presolve_SOScheck: There were %d errors\n", nerr); return( status ); } /* ----------------------------------------------------------------------------- */ /* Presolve routines for tightening the model */ /* ----------------------------------------------------------------------------- */ INLINE REAL presolve_roundrhs(lprec *lp, REAL value, MYBOOL isGE) { #ifdef DoPresolveRounding REAL eps = PRESOLVE_EPSVALUE*1000, /* REAL eps = PRESOLVE_EPSVALUE*pow(10.0,MAX(0,log10(1+fabs(value)))), */ testout = my_precision(value, eps); #if 1 if(my_chsign(isGE, value-testout) < 0) value = testout; #elif 0 if(my_chsign(isGE, value-testout) < 0) value = testout; else if(value != testout) value += my_chsign(isGE, (value-testout)/2); /* value = testout + my_chsign(isGE, (value-testout)/2); */ #else if(testout != value) value += my_chsign(isGE, eps*1000); /* BASE OPTION */ #endif #endif return( value ); } INLINE REAL presolve_roundval(lprec *lp, REAL value) { #ifdef DoPresolveRounding /* value = my_precision(value, PRESOLVE_EPSVALUE*MAX(1,log10(1+fabs(value)))); */ value = my_precision(value, PRESOLVE_EPSVALUE); /* BASE OPTION */ #endif return( value ); } INLINE MYBOOL presolve_mustupdate(lprec *lp, int colnr) { #if 0 return( my_infinite(lp, get_lowbo(lp, colnr)) || my_infinite(lp, get_upbo(lp, colnr)) ); #else return( my_infinite(lp, lp->orig_lowbo[lp->rows+colnr]) || my_infinite(lp, lp->orig_upbo[lp->rows+colnr]) ); #endif } INLINE REAL presolve_sumplumin(lprec *lp, int item, psrec *ps, MYBOOL doUpper) { REAL *plu = (doUpper ? ps->pluupper : ps->plulower), *neg = (doUpper ? ps->negupper : ps->neglower); if(fabs(plu[item]) >= lp->infinite) return( plu[item] ); else if(fabs(neg[item]) >= lp->infinite) return( neg[item] ); else return( plu[item]+neg[item] ); } INLINE void presolve_range(lprec *lp, int rownr, psrec *ps, REAL *loValue, REAL *hiValue) { *loValue = presolve_sumplumin(lp, rownr, ps, FALSE); *hiValue = presolve_sumplumin(lp, rownr, ps, TRUE); } STATIC void presolve_rangeorig(lprec *lp, int rownr, psrec *ps, REAL *loValue, REAL *hiValue, REAL delta) { delta = my_chsign(is_chsign(lp, rownr), lp->presolve_undo->fixed_rhs[rownr] + delta); *loValue = presolve_sumplumin(lp, rownr, ps, FALSE) + delta; *hiValue = presolve_sumplumin(lp, rownr, ps, TRUE) + delta; } STATIC MYBOOL presolve_rowfeasible(presolverec *psdata, int rownr, MYBOOL userowmap) { lprec *lp = psdata->lp; MYBOOL status = TRUE; int contype, origrownr = rownr; REAL LHS, RHS, value; /* Optionally loop across all active rows in the provided map (debugging) */ if(userowmap) rownr = firstActiveLink(psdata->rows->varmap); /* Now do once for ingoing rownr or loop across rowmap */ while((status == TRUE) && (rownr != 0)) { /* Check the lower bound */ value = presolve_sumplumin(lp, rownr, psdata->rows, TRUE); LHS = get_rh_lower(lp, rownr); if(value < LHS-lp->epssolution) { contype = get_constr_type(lp, rownr); report(lp, NORMAL, "presolve_rowfeasible: Lower bound infeasibility in %s row %s (%g << %g)\n", get_str_constr_type(lp, contype), get_row_name(lp, rownr), value, LHS); if(rownr != origrownr) report(lp, NORMAL, " ... Input row base used for testing was %s\n", get_row_name(lp, origrownr)); status = FALSE; } /* Check the upper bound */ value = presolve_sumplumin(lp, rownr, psdata->rows, FALSE); RHS = get_rh_upper(lp, rownr); if(value > RHS+lp->epssolution) { contype = get_constr_type(lp, rownr); report(lp, NORMAL, "presolve_rowfeasible: Upper bound infeasibility in %s row %s (%g >> %g)\n", get_str_constr_type(lp, contype), get_row_name(lp, rownr), value, RHS); status = FALSE; } if(userowmap) rownr = nextActiveLink(psdata->rows->varmap, rownr); else rownr = 0; } return( status ); } STATIC MYBOOL presolve_debugmap(presolverec *psdata, char *caption) { lprec *lp = psdata->lp; MATrec *mat = lp->matA; int colnr, ix, ie, nx, jx, je, *cols, *rows, n; int nz = mat->col_end[lp->columns] - 1; MYBOOL status = FALSE; for(colnr = 1; colnr <= lp->columns; colnr++) { rows = psdata->cols->next[colnr]; if(!isActiveLink(psdata->cols->varmap, colnr)) { if(rows != NULL) { report(lp, SEVERE, "presolve_debugmap: Inactive column %d is non-empty\n", colnr); goto Done; } else continue; } if(rows == NULL) report(lp, SEVERE, "presolve_debugmap: Active column %d is empty\n", colnr); je = *rows; rows++; for(jx = 1; jx <= je; jx++, rows++) { if((*rows < 0) || (*rows > nz)) { report(lp, SEVERE, "presolve_debugmap: NZ index %d for column %d out of range (index %d<=%d)\n", *rows, colnr, jx, je); goto Done; } cols = psdata->rows->next[COL_MAT_ROWNR(*rows)]; ie = cols[0]; n = 0; for(ix = 1; ix <= ie; ix++) { nx = cols[ix]; if((nx < 0) || (nx > nz)) { report(lp, SEVERE, "presolve_debugmap: NZ index %d for column %d to row %d out of range\n", nx, colnr, jx); goto Done; } } } } status = TRUE; Done: if(!status && (caption != NULL)) report(lp, SEVERE, "...caller was '%s'\n", caption); return( status ); } STATIC MYBOOL presolve_validate(presolverec *psdata, MYBOOL forceupdate) { int i, ie, j, je, k, rownr, *items; REAL upbound, lobound, value; lprec *lp = psdata->lp; MATrec *mat = lp->matA; MYBOOL status = mat->row_end_valid && !forceupdate; if(status) return( status ); else if(!mat->row_end_valid) status = mat_validate(mat); else status = forceupdate; if(status) { /* First update rows... */ for(i = 1; i <= lp->rows; i++) { psdata->rows->plucount[i] = 0; psdata->rows->negcount[i] = 0; psdata->rows->pluneg[i] = 0; if(!isActiveLink(psdata->rows->varmap, i)) { FREE(psdata->rows->next[i]); } else { /* Create next column pointers by row */ k = mat_rowlength(mat, i); allocINT(lp, &(psdata->rows->next[i]), k+1, AUTOMATIC); items = psdata->rows->next[i]; je = mat->row_end[i]; k = 0; for(j = mat->row_end[i-1]; j < je; j++) if(isActiveLink(psdata->cols->varmap, ROW_MAT_COLNR(j))) { k++; items[k] = j; } items[0] = k; } } /* ...then update columns */ for(j = 1; j <= lp->columns; j++) { psdata->cols->plucount[j] = 0; psdata->cols->negcount[j] = 0; psdata->cols->pluneg[j] = 0; if(!isActiveLink(psdata->cols->varmap, j)) { FREE(psdata->cols->next[j]); } else { upbound = get_upbo(lp, j); lobound = get_lowbo(lp, j); if(is_semicont(lp, j) && (upbound > lobound)) { if(lobound > 0) lobound = 0; else if(upbound < 0) upbound = 0; } /* Create next row pointers by column */ k = mat_collength(mat, j); allocINT(lp, &(psdata->cols->next[j]), k+1, AUTOMATIC); items = psdata->cols->next[j]; ie = mat->col_end[j]; k = 0; for(i = mat->col_end[j-1]; i < ie; i++) { rownr = COL_MAT_ROWNR(i); if(isActiveLink(psdata->rows->varmap, rownr)) { k++; items[k] = i; /* Cumulate counts */ value = COL_MAT_VALUE(i); if(my_chsign(is_chsign(lp, rownr), value) > 0) { psdata->rows->plucount[rownr]++; psdata->cols->plucount[j]++; } else { psdata->rows->negcount[rownr]++; psdata->cols->negcount[j]++; } if((lobound < 0) && (upbound >= 0)) { psdata->rows->pluneg[rownr]++; psdata->cols->pluneg[j]++; } } } items[0] = k; } } #ifdef Paranoia presolve_debugmap(psdata, "presolve_validate"); #endif } return( status ); } STATIC MYBOOL presolve_rowtallies(presolverec *psdata, int rownr, int *plu, int *neg, int *pluneg) { REAL value; lprec *lp = psdata->lp; MATrec *mat = lp->matA; int ix, jx, ib = 0; MYBOOL chsign = is_chsign(lp, rownr); /* Initialize */ *plu = 0; *neg = 0; *pluneg = 0; /* Loop over still active row members */ for(ix = presolve_nextcol(psdata, rownr, &ib); ix >= 0; ix = presolve_nextcol(psdata, rownr, &ib)) { /* Get matrix column and value */ jx = ROW_MAT_COLNR(ix); value = ROW_MAT_VALUE(ix); /* Cumulate counts */ if(my_chsign(chsign, value) > 0) (*plu)++; else (*neg)++; if((get_lowbo(lp, jx) < 0) && (get_upbo(lp, jx) >= 0)) (*pluneg)++; } return( TRUE ); } STATIC MYBOOL presolve_debugrowtallies(presolverec *psdata) { lprec *lp = psdata->lp; int i, plu, neg, pluneg, nerr = 0; for(i = 1; i <= lp->rows; i++) if(isActiveLink(psdata->rows->varmap, i) && presolve_rowtallies(psdata, i, &plu, &neg, &pluneg)) { if((psdata->rows->plucount[i] != plu) || (psdata->rows->negcount[i] != neg) || (psdata->rows->pluneg[i] != pluneg)) { nerr++; report(lp, SEVERE, "presolve_debugrowtallies: Detected inconsistent count for row %d\n", i); } } return( (MYBOOL) (nerr == 0) ); } STATIC int presolve_debugcheck(lprec *lp, LLrec *rowmap, LLrec *colmap) { int i, j, errc = 0; /* Validate constraint bounds */ for(i = 1; i < lp->rows; i++) { if((rowmap != NULL) && !isActiveLink(rowmap, i)) continue; /* Check if we have a negative range */ if(lp->orig_upbo[i] < 0) { errc++; report(lp, SEVERE, "presolve_debugcheck: Detected negative range %g for row %d\n", lp->orig_upbo[i], i); } } /* Validate variables */ for(j = 1; j < lp->columns; j++) { if((colmap != NULL) && !isActiveLink(colmap, j)) continue; i = lp->rows+j; /* Check if we have infeasible bounds */ if(lp->orig_lowbo[i] > lp->orig_upbo[i]) { errc++; report(lp, SEVERE, "presolve_debugcheck: Detected UB < LB for column %d\n", j); } } /* Return total number of errors */ return( errc ); } STATIC MYBOOL presolve_candeletevar(presolverec *psdata, int colnr) { lprec *lp = psdata->lp; int usecount = SOS_memberships(lp->SOS, colnr); return( (MYBOOL) ((lp->SOS == NULL) || (usecount == 0) || (/*is_presolve(lp, PRESOLVE_SOS) &&*/ (((lp->SOS->sos1_count == lp->SOS->sos_count)) || (usecount == SOS_is_member_of_type(lp->SOS, colnr, SOS1))))) ); } STATIC int presolve_rowlengthex(presolverec *psdata, int rownr) { int j1 = psdata->rows->plucount[rownr] + psdata->rows->negcount[rownr]; #ifdef Paranoia int j2 = presolve_rowlength(psdata, rownr); if(j1 != j2) { report(psdata->lp, SEVERE, "presolve_rowlengthex: Expected row length %d, but found %d in row %s\n", j2, j1, get_row_name(psdata->lp, rownr)); j1 = -j1; } #endif return( j1 ); } STATIC int presolve_rowlengthdebug(presolverec *psdata) { int rownr, n = 0; for(rownr = firstActiveLink(psdata->rows->varmap); rownr != 0; rownr = nextActiveLink(psdata->rows->varmap, rownr)) n += presolve_rowlengthex(psdata, rownr); return( n ); } INLINE int presolve_nextrecord(psrec *ps, int recnr, int *previtem) { int *nzlist = ps->next[recnr], nzcount = nzlist[0], status = -1; /* Check if we simply wish the last active column */ if(previtem == NULL) { if(nzlist != NULL) status = nzlist[*nzlist]; return( status ); } /* Step to next */ #ifdef Paranoia else if((*previtem < 0) || (*previtem > nzcount)) return( status ); #endif (*previtem)++; /* Set the return values */ if(*previtem > nzcount) (*previtem) = 0; else status = nzlist[*previtem]; return( status ); } INLINE int presolve_nextcol(presolverec *psdata, int rownr, int *previtem) /* Find the first active (non-eliminated) nonzero column in rownr after prevcol */ { return( presolve_nextrecord(psdata->rows, rownr, previtem) ); } INLINE int presolve_lastcol(presolverec *psdata, int rownr) { return( presolve_nextrecord(psdata->rows, rownr, NULL) ); } INLINE int presolve_nextrow(presolverec *psdata, int colnr, int *previtem) /* Find the first active (non-eliminated) nonzero row in colnr after prevrow */ { return( presolve_nextrecord(psdata->cols, colnr, previtem) ); } INLINE int presolve_lastrow(presolverec *psdata, int colnr) { return( presolve_nextrecord(psdata->cols, colnr, NULL) ); } INLINE void presolve_adjustrhs(presolverec *psdata, int rownr, REAL fixdelta, REAL epsvalue) { lprec *lp = psdata->lp; lp->orig_rhs[rownr] -= fixdelta; if(epsvalue > 0) #if 1 my_roundzero(lp->orig_rhs[rownr], epsvalue); #else lp->orig_rhs[rownr] = presolve_roundrhs(lp, lp->orig_rhs[rownr], FALSE); #endif lp->presolve_undo->fixed_rhs[rownr] += fixdelta; } STATIC int presolve_shrink(presolverec *psdata, int *nConRemove, int *nVarRemove) { SOSgroup *SOS = psdata->lp->SOS; int status = RUNNING, countR = 0, countC = 0, i, ix, n, *list; REAL fixValue; /* Remove empty rows */ list = psdata->rows->empty; if(list != NULL) { n = list[0]; for(i = 1; i <= n; i++) if(isActiveLink(psdata->rows->varmap, list[i])) { presolve_rowremove(psdata, list[i], FALSE); countR++; } if(nConRemove != NULL) (*nConRemove) += countR; list[0] = 0; } /* Fix and remove empty columns (unless they are in a SOS) */ list = psdata->cols->empty; if(list != NULL) { n = list[0]; for(i = 1; i <= n; i++) { ix = list[i]; if(isActiveLink(psdata->cols->varmap, ix)) { if(presolve_colfixdual(psdata, ix, &fixValue, &status)) { if(!presolve_colfix(psdata, ix, fixValue, TRUE, nVarRemove)) { status = presolve_setstatus(psdata, INFEASIBLE); break; } presolve_colremove(psdata, ix, FALSE); countC++; } else if(SOS_is_member(SOS, 0, ix)) report(psdata->lp, DETAILED, "presolve_shrink: Empty column %d is member of a SOS\n", ix); } } list[0] = 0; } return( status ); } STATIC void presolve_rowremove(presolverec *psdata, int rownr, MYBOOL allowcoldelete) { lprec *lp = psdata->lp; MATrec *mat = lp->matA; int ix, ie, nx, jx, je, *cols, *rows, n, colnr; #ifdef Paranoia if((rownr < 1) || (rownr > lp->rows)) report(lp, SEVERE, "presolve_rowremove: Row %d out of range\n", rownr); #endif /* Remove this row for each column that is active in the row */ cols = psdata->rows->next[rownr]; ie = *cols; cols++; for(ix = 1; ix <= ie; ix++, cols++) { n = 0; colnr = ROW_MAT_COLNR(*cols); rows = psdata->cols->next[colnr]; je = rows[0]; /* See if we can narrow the search window */ jx = je / 2; if((jx > 5) && (rownr >= COL_MAT_ROWNR(rows[jx]))) n = jx-1; else jx = 1; /* Do the compression loop */ for(; jx <= je; jx++) { nx = rows[jx]; if(COL_MAT_ROWNR(nx) != rownr) { n++; rows[n] = nx; } } rows[0] = n; /* Make sure we delete columns that have become empty */ #if 1 if((n == 0) && allowcoldelete) { int *list = psdata->cols->empty; n = ++list[0]; list[n] = colnr; } #endif } FREE(psdata->rows->next[rownr]); removeLink(psdata->rows->varmap, rownr); switch(get_constr_type(lp, rownr)) { case LE: removeLink(psdata->LTmap, rownr); break; case EQ: removeLink(psdata->EQmap, rownr); break; } if(isActiveLink(psdata->INTmap, rownr)) removeLink(psdata->INTmap, rownr); } STATIC int presolve_colremove(presolverec *psdata, int colnr, MYBOOL allowrowdelete) { lprec *lp = psdata->lp; #ifdef Paranoia if((colnr < 1) || (colnr > lp->columns)) report(lp, SEVERE, "presolve_colremove: Column %d out of range\n", colnr); if(!isActiveLink(psdata->cols->varmap, colnr) || !presolve_candeletevar(psdata, colnr)) colnr = -1; else #endif { MATrec *mat = lp->matA; int ix, ie, nx, jx, je, *cols, *rows, n, rownr; /* Remove this column for each row that is active in the column */ rows = psdata->cols->next[colnr]; je = *rows; rows++; for(jx = 1; jx <= je; jx++, rows++) { n = 0; rownr = COL_MAT_ROWNR(*rows); cols = psdata->rows->next[rownr]; ie = cols[0]; /* See if we can narrow the search window */ ix = ie / 2; if((ix > 5) && (colnr >= ROW_MAT_COLNR(cols[ix]))) n = ix-1; else ix = 1; /* Do the compression loop */ for(; ix <= ie; ix++) { nx = cols[ix]; if(ROW_MAT_COLNR(nx) != colnr) { n++; cols[n] = nx; } } cols[0] = n; /* Make sure we delete rows that become empty */ #if 1 if((n == 0) && allowrowdelete) { int *list = psdata->rows->empty; n = ++list[0]; list[n] = rownr; } #endif } FREE(psdata->cols->next[colnr]); /* Update other counts */ if(SOS_is_member(lp->SOS, 0, colnr)) { if(lp->sos_priority != NULL) { lp->sos_vars--; if(is_int(lp, colnr)) lp->sos_ints--; } SOS_member_delete(lp->SOS, 0, colnr); clean_SOSgroup(lp->SOS, TRUE); if(SOS_count(lp) == 0) free_SOSgroup(&(lp->SOS)); } /* Finally remove the column from the active column list */ colnr = removeLink(psdata->cols->varmap, colnr); } return( colnr ); } STATIC int presolve_redundantSOS(presolverec *psdata, int *nb, int *nSum) { lprec *lp = psdata->lp; int i, ii, k, kk, j, nrows = lp->rows, *fixed = NULL, iBoundTighten = 0, status = RUNNING; SOSrec *SOS; /* Is there anything to do? */ i = ii = SOS_count(lp); if(ii == 0) return( status ); /* Allocate working member list */ if(!allocINT(lp, &fixed, lp->columns+1, FALSE) ) return( lp->spx_status ); /* Check if we have SOS'es that are already satisfied or fixable/satisfiable */ while(i > 0) { SOS = lp->SOS->sos_list[i-1]; kk = SOS->members[0]; fixed[0] = 0; for(k = 1; k <= kk; k++) { j = SOS->members[k]; if((get_lowbo(lp, j) > 0) && !is_semicont(lp, j)) { fixed[++fixed[0]] = k; /* Abort if we have identified SOS infeasibility */ if(fixed[0] > SOS->type) { status = presolve_setstatus(psdata, INFEASIBLE); goto Done; } } } /* If there were an exact number of non-zero SOS members, check their sequentiality */ if(fixed[0] == SOS->type) { /* Check sequentiality of members with non-zero lower bounds */ for(k = 2; k <= fixed[0]; k++) { if(fixed[k] != fixed[k-1]+1) { status = presolve_setstatus(psdata, INFEASIBLE); goto Done; } } /* Fix other member variables to zero, if necessary */ for(k = kk; k > 0; k--) { j = SOS->members[k]; if((get_lowbo(lp, j) > 0) && !is_semicont(lp, j)) continue; if(!presolve_colfix(psdata, j, 0.0, AUTOMATIC, &iBoundTighten)) { status = presolve_setstatus(psdata, INFEASIBLE); goto Done; } } /* Remove the SOS */ delete_SOSrec(lp->SOS, i /* , FALSE */); } /* Otherwise, try to fix variables outside the SOS type window */ else if(fixed[0] > 0) { for(k = kk; k > 0; k--) { if((k > fixed[fixed[0]]-SOS->type) && /* After leading entries */ (k < fixed[1]+SOS->type)) /* Before trailing entries */ continue; j = SOS->members[k]; SOS_member_delete(lp->SOS, i, j); /* if(get_upbo(lp, j) - get_lowbo(lp, j) < lp->epsprimal) */ if(is_fixedvar(lp, nrows+j)) continue; if(!presolve_colfix(psdata, j, 0.0, AUTOMATIC, &iBoundTighten)) { status = presolve_setstatus(psdata, INFEASIBLE); goto Done; } } } i--; } /* Update the sparse member map if there were SOS deletions; Remember that delete_SOSrec() above specified deferred updating! */ i = SOS_count(lp); if((i < ii) || (iBoundTighten > 0)) { SOS_member_updatemap(lp->SOS); } /* Update tag orders */ for(; i > 0; i--) lp->SOS->sos_list[i-1]->tagorder = i; Done: FREE(fixed); (*nb) += iBoundTighten; (*nSum) += iBoundTighten; return( status ); } STATIC MYBOOL presolve_fixSOS1(presolverec *psdata, int colnr, REAL fixvalue, int *nr, int *nv) { lprec *lp = psdata->lp; int i, k, j; SOSrec *SOS; REAL newvalue; MYBOOL *fixed = NULL, status = FALSE; /* Allocate working member list */ if(!allocMYBOOL(lp, &fixed, lp->columns+1, TRUE) ) return(FALSE); /* Fix variables in SOS's where colnr is a member */ i = SOS_count(lp); while(i > 0) { /* Set next SOS target (note that colnr has been tested earlier as not being a member of a higher order SOS) */ SOS = lp->SOS->sos_list[i-1]; if(SOS_is_member(lp->SOS, i, colnr)) { for(k = SOS->members[0]; k > 0; k--) { j = SOS->members[k]; if(fixed[j]) continue; if(j == colnr) { fixed[j] = TRUE; newvalue = fixvalue; } else { fixed[j] = AUTOMATIC; newvalue = 0.0; } /* If it is a member of a higher order SOS then just change bounds */ if(!presolve_candeletevar(psdata, j)) { set_bounds(lp, j, newvalue, newvalue); fixed[j] = TRUE | AUTOMATIC; psdata->forceupdate = TRUE; } /* Otherwise fix it in preparation for removal */ else if(!presolve_colfix(psdata, j, newvalue, TRUE, nv)) goto Done; } } i--; } /* Delete SOS'es or SOS member variables where we can */ k = i = SOS_count(lp); while(i > 0) { /* Set next SOS target */ SOS = lp->SOS->sos_list[i-1]; if(SOS_is_member(lp->SOS, i, colnr)) { /* Always delete SOS1's */ if(SOS->type == SOS1) delete_SOSrec(lp->SOS, i /* , FALSE */); /* Only delete leading or trailing SOS members in higher-order SOS'es that are fixed at 0; (note that this section of the code will never be called in the current setup) */ else { /* First the leading entries... */ for(j = 1; j <= SOS->members[0]; j++) { if(fixed[SOS->members[j]] == AUTOMATIC) SOS_member_delete(lp->SOS, i, SOS->members[j]); } /* ...then trailing entries */ for(j = SOS->members[0]; j > 0; j--) { if(fixed[SOS->members[j]] == AUTOMATIC) SOS_member_delete(lp->SOS, i, SOS->members[j]); } } } i--; } /* Update the sparse member map if there were SOS deletions; delete_SOSrec() above specified deferred updating */ i = SOS_count(lp); if(i < k) SOS_member_updatemap(lp->SOS); /* Delete the variables that have been fixed */ k = 0; for(j = lp->columns; j > 0; j--) { if((fixed[j] == TRUE) || (fixed[j] == AUTOMATIC)) { presolve_colremove(psdata, j, TRUE); k++; } } /* Update tag orders */ i = SOS_count(lp); for(; i > 0; i--) lp->SOS->sos_list[i-1]->tagorder = i; status = TRUE; Done: FREE(fixed); return( status ); } STATIC void presolve_setEQ(presolverec *psdata, int rownr) { lprec *lp = psdata->lp; if(is_constr_type(lp, rownr, LE)) removeLink(psdata->LTmap, rownr); setLink(psdata->EQmap, rownr); set_constr_type(lp, rownr, EQ); psdata->dv_lobo[rownr] = -lp->infinite; psdata->dv_upbo[rownr] = lp->infinite; } STATIC MYBOOL presolve_singletonbounds(presolverec *psdata, int rownr, int colnr, REAL *lobound, REAL *upbound, REAL *aval) { lprec *lp = psdata->lp; REAL coeff_a, epsvalue = psdata->epsvalue; MYBOOL isneg; /* Compute row singleton variable range */ if(is_constr_type(lp, rownr, EQ) && (fabs(*lobound) < epsvalue)) *lobound = *upbound = 0; else { if(aval == NULL) coeff_a = get_mat(lp, rownr, colnr); else coeff_a = *aval; isneg = (MYBOOL) (coeff_a < 0); if(*lobound > -lp->infinite) *lobound /= coeff_a; else if(isneg) *lobound = -(*lobound); if(*upbound < lp->infinite) *upbound /= coeff_a; else if(isneg) *upbound = -(*upbound); if(isneg) swapREAL(lobound, upbound); } /* Check against bound - handle SC variables specially */ if(is_semicont(lp, colnr)) { coeff_a = get_lowbo(lp, colnr); if(coeff_a > 0) { SETMAX(*lobound, 0.0); SETMIN(*upbound, get_upbo(lp, colnr)); } else { coeff_a = get_upbo(lp, colnr); if(coeff_a > 0) { SETMAX(*lobound, get_lowbo(lp, colnr)); SETMIN(*upbound, 0.0); } } } else { SETMAX(*lobound, get_lowbo(lp, colnr)); SETMIN(*upbound, get_upbo(lp, colnr)); } /* Return with consistency status */ #ifdef DoPresolveRelativeTest isneg = (MYBOOL) (my_reldiff(*upbound, *lobound) >= - epsvalue); #else isneg = (MYBOOL) (*upbound >= *lobound - epsvalue); #endif if(!isneg) { /* Attempt bound-related error correction */ if(fabs(my_reldiff(*lobound, get_upbo(lp, colnr))) < PRESOLVE_BOUNDSLACK*epsvalue) *lobound = get_upbo(lp, colnr); else if(fabs(my_reldiff(*upbound, get_lowbo(lp, colnr))) < PRESOLVE_BOUNDSLACK*epsvalue) *upbound = get_lowbo(lp, colnr); #ifdef DoPresolveRelativeTest isneg = (MYBOOL) (my_reldiff(*upbound, *lobound) >= - epsvalue); #else isneg = (MYBOOL) (*upbound >= *lobound - epsvalue); #endif if(!isneg) report(lp, NORMAL, "presolve_singletonbounds: Singleton variable %s in row %s infeasibility (%g << %g)\n", get_col_name(lp, colnr), get_row_name(lp, rownr), *lobound, *upbound); } return( isneg ); } STATIC MYBOOL presolve_altsingletonvalid(presolverec *psdata, int rownr, int colnr, REAL reflotest, REAL refuptest) { lprec *lp = psdata->lp; REAL coeff_bl, coeff_bu, epsvalue = psdata->epsvalue; coeff_bl = get_rh_lower(lp, rownr); coeff_bu = get_rh_upper(lp, rownr); /* Check base data validity */ #ifdef DoPresolveRelativeTest if((my_reldiff(refuptest, reflotest) < -epsvalue) || #else if((reflotest > refuptest + epsvalue) || #endif !presolve_singletonbounds(psdata, rownr, colnr, &coeff_bl, &coeff_bu, NULL)) return( FALSE ); /* Base data is Ok, now check against against each other */ epsvalue = MAX(reflotest-coeff_bu, coeff_bl-refuptest) / epsvalue; if(epsvalue > PRESOLVE_BOUNDSLACK) { report(lp, NORMAL, "presolve_altsingletonvalid: Singleton variable %s in row %s infeasible (%g)\n", get_col_name(lp, colnr), get_row_name(lp, rownr), MAX(reflotest-coeff_bu, coeff_bl-refuptest)); return( FALSE ); } else return( TRUE ); } STATIC MYBOOL presolve_multibounds(presolverec *psdata, int rownr, int colnr, REAL *lobound, REAL *upbound, REAL *aval, MYBOOL *rowbinds) { lprec *lp = psdata->lp; MYBOOL rowbindsvar = FALSE, status = FALSE; REAL coeff_a, LHS, RHS, netX, Xupper, Xlower, epsvalue = psdata->epsvalue; /* Get variable bounds for netting */ LHS = *lobound; RHS = *upbound; Xlower = get_lowbo(lp, colnr); Xupper = get_upbo(lp, colnr); /* Identify opportunity for bound tightening */ if(aval == NULL) coeff_a = get_mat(lp, rownr, colnr); else coeff_a = *aval; netX = presolve_sumplumin(lp, rownr, psdata->rows, TRUE); if(!my_infinite(lp, LHS) && !my_infinite(lp, netX)) { if(coeff_a > 0) { LHS -= netX-coeff_a*Xupper; LHS /= coeff_a; if(LHS > Xlower + epsvalue) { Xlower = presolve_roundrhs(lp, LHS, TRUE); status = TRUE; } else if(LHS > Xlower - epsvalue) rowbindsvar = TRUE; } else { LHS -= netX-coeff_a*Xlower; LHS /= coeff_a; if(LHS < Xupper - epsvalue) { Xupper = presolve_roundrhs(lp, LHS, FALSE); status = AUTOMATIC; } else if(LHS < Xupper + epsvalue) rowbindsvar = AUTOMATIC; } } netX = presolve_sumplumin(lp, rownr, psdata->rows, FALSE); if(!my_infinite(lp, RHS) && !my_infinite(lp, netX)) { if(coeff_a < 0) { if(!my_infinite(lp, Xupper)) { RHS -= netX-coeff_a*Xupper; RHS /= coeff_a; if(RHS > Xlower + epsvalue) { Xlower = presolve_roundrhs(lp, RHS, TRUE); status |= TRUE; } else if(RHS > Xlower - epsvalue) rowbindsvar |= TRUE; } } else if(!my_infinite(lp, Xlower)) { RHS -= netX-coeff_a*Xlower; RHS /= coeff_a; if(RHS < Xupper - epsvalue) { Xupper = presolve_roundrhs(lp, RHS, FALSE); status |= AUTOMATIC; } else if(RHS < Xupper + epsvalue) rowbindsvar |= AUTOMATIC; } } *lobound = Xlower; *upbound = Xupper; if(rowbinds != NULL) *rowbinds = rowbindsvar; return(status); } STATIC MYBOOL isnz_origobj(lprec *lp, int colnr) { return( (MYBOOL) (lp->orig_obj[colnr] != 0) ); } STATIC MYBOOL presolve_testrow(presolverec *psdata, int lastrow) { if(psdata->forceupdate) { presolve_updatesums(psdata); psdata->forceupdate = FALSE; } if(!presolve_rowfeasible(psdata, 0, TRUE)) return( FALSE ); else return( TRUE ); } STATIC MYBOOL presolve_coltighten(presolverec *psdata, int colnr, REAL LOnew, REAL UPnew, int *count) { lprec *lp = psdata->lp; int elmnr, elmend, k, oldcount = 0, newcount = 0, deltainf; REAL LOold, UPold, Value, margin = psdata->epsvalue; MATrec *mat = lp->matA; REAL *value; int *rownr; /* Attempt correction of marginally equal, but inconsistent input values */ Value = UPnew - LOnew; if((Value <= -margin) && (Value > -lp->epsprimal)) { if(fabs(fmod(UPnew, 1.0)) < margin) LOnew = UPnew; else UPnew = LOnew; } /* Check if there is anything to do */ LOold = get_lowbo(lp, colnr); UPold = get_upbo(lp, colnr); #ifdef Paranoia if(((LOold > LOnew) && !is_semicont(lp, colnr)) || (UPold < UPnew)) { report(lp, SEVERE, "presolve_coltighten: Inconsistent new bounds requested for column %d\n", colnr); return( FALSE ); } #endif if(count != NULL) newcount = *count; oldcount = newcount; /* Modify inf-count */ deltainf = 0; if((UPold < lp->infinite) || (LOold > -lp->infinite)) deltainf -= 1; if((UPnew < lp->infinite) || (LOnew > -lp->infinite)) deltainf += 1; if(isnz_origobj(lp, colnr)) psdata->rows->infcount[0] += deltainf; elmnr = mat->col_end[colnr-1]; elmend = mat->col_end[colnr]; rownr = &COL_MAT_ROWNR(elmnr); for(; elmnr < elmend; elmnr++, rownr += matRowColStep) { k = *rownr; if(isActiveLink(psdata->rows->varmap, k)) psdata->rows->infcount[k] += deltainf; } /* Look for opportunity to tighten upper variable bound */ if((UPnew < lp->infinite) && (UPnew+margin < UPold)) { if(is_int(lp, colnr)) UPnew = floor(UPnew+margin); if(UPold < lp->infinite) { /* First do OF */ k = 0; Value = my_chsign(is_chsign(lp, k), lp->orig_obj[colnr]); if((Value > 0) && (psdata->rows->pluupper[k] < lp->infinite)) psdata->rows->pluupper[k] += (UPnew-UPold)*Value; else if((Value < 0) && (psdata->rows->negupper[k] < lp->infinite)) psdata->rows->negupper[k] += (LOnew-LOold)*Value; psdata->rows->infcount[k] += deltainf; /* Then scan the constraint rows */ elmnr = mat->col_end[colnr-1]; elmend = mat->col_end[colnr]; rownr = &COL_MAT_ROWNR(elmnr); value = &COL_MAT_VALUE(elmnr); for(; elmnr < elmend; elmnr++, rownr += matRowColStep, value += matValueStep) { k = *rownr; if(!isActiveLink(psdata->rows->varmap, k)) continue; Value = my_chsign(is_chsign(lp, k), *value); if((Value > 0) && (psdata->rows->pluupper[k] < lp->infinite)) psdata->rows->pluupper[k] += (UPnew-UPold)*Value; else if((Value < 0) && (psdata->rows->negupper[k] < lp->infinite)) psdata->rows->negupper[k] += (LOnew-LOold)*Value; } } else psdata->forceupdate = TRUE; if(UPnew < UPold) { UPold = UPnew; newcount++; } } /* Look for opportunity to tighten lower variable bound */ if((LOnew > -lp->infinite) && (LOnew-margin > LOold)) { if(is_int(lp, colnr)) LOnew = ceil(LOnew-margin); if(LOold > -lp->infinite) { /* First do OF */ k = 0; Value = my_chsign(is_chsign(lp, k), lp->orig_obj[colnr]); if((Value > 0) && (psdata->rows->plulower[k] > -lp->infinite)) psdata->rows->plulower[k] += (LOnew-LOold)*Value; else if((Value < 0) && (psdata->rows->neglower[k] > -lp->infinite)) psdata->rows->neglower[k] += (UPnew-UPold)*Value; /* Then scan the constraint rows */ elmnr = mat->col_end[colnr-1]; elmend = mat->col_end[colnr]; rownr = &COL_MAT_ROWNR(elmnr); value = &COL_MAT_VALUE(elmnr); for(; elmnr < elmend; elmnr++, rownr += matRowColStep, value += matValueStep) { k = *rownr; if(!isActiveLink(psdata->rows->varmap, k)) continue; Value = my_chsign(is_chsign(lp, k), *value); if((Value > 0) && (psdata->rows->plulower[k] > -lp->infinite)) psdata->rows->plulower[k] += (LOnew-LOold)*Value; else if((Value < 0) && (psdata->rows->neglower[k] > -lp->infinite)) psdata->rows->neglower[k] += (UPnew-UPold)*Value; } } else psdata->forceupdate = TRUE; if(LOnew > LOold) { LOold = LOnew; newcount++; } } /* Now set the new variable bounds, if they are tighter */ if(newcount > oldcount) { UPnew = presolve_roundval(lp, UPnew); LOnew = presolve_roundval(lp, LOnew); if(LOnew > UPnew) { if(LOnew-UPnew < margin) { LOnew = UPnew; } else { report(lp, NORMAL, "presolve_coltighten: Found column %s with LB %g > UB %g\n", get_col_name(lp, colnr), LOnew, UPnew); return( FALSE ); } } if(lp->spx_trace || (lp->verbose > DETAILED)) report(lp, NORMAL, "presolve_coltighten: Replaced bounds on column %s to [%g ... %g]\n", get_col_name(lp, colnr), LOnew, UPnew); set_bounds(lp, colnr, LOnew, UPnew); } if(count != NULL) *count = newcount; return( TRUE ); } STATIC int presolve_rowtighten(presolverec *psdata, int rownr, int *tally, MYBOOL intsonly) { lprec *lp = psdata->lp; MYBOOL rowbinds; int item = 0, jx, jjx, ix, idxn = 0, *idxbound = NULL, status = RUNNING; REAL *newbound = NULL, RHlo = get_rh_lower(lp, rownr), RHup = get_rh_upper(lp, rownr), VARlo, VARup, Aval; MATrec *mat = lp->matA; jx = presolve_rowlength(psdata, rownr); allocREAL(lp, &newbound, 2*jx, TRUE); allocINT (lp, &idxbound, 2*jx, TRUE); /* Identify bound tightening for each active variable in the constraint */ for(jx = presolve_nextcol(psdata, rownr, &item); jx >= 0; jx = presolve_nextcol(psdata, rownr, &item)) { jjx = ROW_MAT_COLNR(jx); Aval = ROW_MAT_VALUE(jx); Aval = my_chsign(rownr, Aval); VARlo = RHlo; VARup = RHup; presolve_multibounds(psdata, rownr,jjx, &VARlo, &VARup, &Aval, &rowbinds); if(rowbinds & TRUE) { idxbound[idxn] = -jjx; newbound[idxn] = VARlo; idxn++; } if(rowbinds & AUTOMATIC) { idxbound[idxn] = jjx; newbound[idxn] = VARup; idxn++; } } /* Loop over the bounds identified for tightening and perform update */ ix = 0; while(ix < idxn) { jjx = idxbound[ix]; jx = abs(jjx); /* Skip free variables and non-ints, if specified */ if(is_unbounded(lp, jx) || (intsonly && !is_int(lp, jx))) continue; VARlo = get_lowbo(lp, jx); VARup = get_upbo(lp, jx); /* while((ix < idxn) && (jx == abs(jjx))) { */ while((ix < idxn) && (jx == abs((jjx = idxbound[ix])))) { if(jjx < 0) VARlo = newbound[ix]; else VARup = newbound[ix]; ix++; } if(!presolve_coltighten(psdata, jx, VARlo, VARup, tally)) { status = presolve_setstatus(psdata, INFEASIBLE); break; } } FREE(newbound); FREE(idxbound); return(status); } STATIC void set_dv_bounds(presolverec *psdata, int rownr, REAL lowbo, REAL upbo) { psdata->dv_lobo[rownr] = lowbo; psdata->dv_upbo[rownr] = upbo; } STATIC REAL get_dv_lower(presolverec *psdata, int rownr) { return( psdata->dv_lobo[rownr] ); } STATIC REAL get_dv_upper(presolverec *psdata, int rownr) { return( psdata->dv_upbo[rownr] ); } STATIC MYBOOL presolve_rowfix(presolverec *psdata, int rownr, REAL newvalue, MYBOOL remove, int *tally) { lprec *lp = psdata->lp; int i, ix, ie; MYBOOL isneg, lofinite, upfinite, doupdate = FALSE, chsign = is_chsign(lp, rownr); REAL lobound, upbound, lovalue, upvalue, Value, fixvalue, fixprod, mult; MATrec *mat = lp->matA; psrec *ps = psdata->cols; /* Set "fixed" value in case we are deleting a variable */ upbound = get_dv_upper(psdata, rownr); lobound = get_dv_lower(psdata, rownr); if(remove) { if(upbound-lobound < psdata->epsvalue) { if((newvalue > lobound) && (newvalue < upbound)) fixvalue = newvalue; else fixvalue = lobound; } else { if(my_infinite(lp, newvalue) && (get_rh(lp, rownr) == 0)) fixvalue = ((lobound <= 0) && (upbound >= 0) ? 0 : MIN(upbound, lobound)); else fixvalue = newvalue; } set_dv_bounds(psdata, rownr, fixvalue, fixvalue); if(fixvalue != 0) addUndoPresolve(lp, FALSE, rownr, fixvalue, 0, 0); mult = -1; } else { mult = 1; fixvalue = 0; } /* Loop over rows to update statistics */ ix = mat->row_end[rownr - 1]; ie = mat->row_end[rownr]; for(; ix < ie; ix++) { /* Retrieve row data and adjust RHS if we are deleting a variable */ i = ROW_MAT_COLNR(ix); Value = ROW_MAT_VALUE(ix); if(Value == 0) continue; if(remove && (fixvalue != 0)) { fixprod = Value*fixvalue; lp->orig_obj[i] -= fixprod; my_roundzero(lp->orig_obj[i], psdata->epsvalue); lp->presolve_undo->fixed_obj[i] += fixprod; } /* Prepare for further processing */ Value = my_chsign(chsign, Value); isneg = (MYBOOL) (Value < 0); /* Reduce row variable counts if we are removing the variable */ if(!isActiveLink(ps->varmap, i)) continue; if(remove) { if(isneg) { ps->negcount[i]--; } else { ps->plucount[i]--; } if((lobound < 0) && (upbound >= 0)) { ps->pluneg[i]--; } } /* Compute associated constraint contribution values */ upfinite = (MYBOOL) (upbound < lp->infinite); lofinite = (MYBOOL) (lobound > -lp->infinite); if(upfinite || lofinite) { if(remove) ps->infcount[i]--; else ps->infcount[i]++; } upvalue = my_if(upfinite, Value*upbound, my_chsign(isneg, lp->infinite)); lovalue = my_if(lofinite, Value*lobound, my_chsign(isneg, -lp->infinite)); /* Cumulate effective upper column bound (only bother with non-finite bound) */ if(isneg) { if((ps->negupper[i] < lp->infinite) && lofinite) { ps->negupper[i] += mult*lovalue; ps->negupper[i] = presolve_roundrhs(lp, ps->negupper[i], FALSE); } else if(remove && !lofinite) doupdate = TRUE; else ps->negupper[i] = lp->infinite; } else { if((ps->pluupper[i] < lp->infinite) && upfinite) { ps->pluupper[i] += mult*upvalue; ps->pluupper[i] = presolve_roundrhs(lp, ps->pluupper[i], FALSE); } else if(remove && !upfinite) doupdate = TRUE; else ps->pluupper[i] = lp->infinite; } /* Cumulate effective lower column bound (only bother with non-finite bound) */ if(isneg) { if((ps->neglower[i] > -lp->infinite) && upfinite) { ps->neglower[i] += mult*upvalue; ps->neglower[i] = presolve_roundrhs(lp, ps->neglower[i], TRUE); } else if(remove && !upfinite) doupdate = TRUE; else ps->neglower[i] = -lp->infinite; } else { if((ps->plulower[i] > -lp->infinite) && lofinite) { ps->plulower[i] += mult*lovalue; ps->plulower[i] = presolve_roundrhs(lp, ps->plulower[i], TRUE); } else if(remove && !lofinite) doupdate = TRUE; else ps->plulower[i] = -lp->infinite; } /* Validate consistency of eliminated singleton */ if(remove && ((i == 0) || (ps->next[i][0] == 1)) && !psdata->forceupdate) { presolve_range(lp, i, ps, &lovalue, &upvalue); Value = get_mat(lp, 0, i); if((upvalue < Value) || (lovalue > Value)) { report(lp, IMPORTANT, "presolve: Row %s (%g << %g) infeasibility in column %s (OF=%g)\n", get_row_name(lp, rownr), lovalue, upvalue, get_col_name(lp, i), Value); return( FALSE ); } } } if(remove) { psdata->forceupdate |= doupdate; if(tally != NULL) (*tally)++; } return( TRUE ); } STATIC int presolve_colsingleton(presolverec *psdata, int i, int j, int *count) { lprec *lp = psdata->lp; REAL RHlow, RHup, LObound, UPbound, Value; #ifdef Paranoia if(!isActiveLink(psdata->cols->varmap, j)) report(lp, SEVERE, "presolve_colsingleton: Nothing to do, column %d was eliminated earlier\n", j); #endif Value = get_mat(lp,i,j); if(Value == 0) return( RUNNING ); /* Initialize and identify semicontinuous variable */ LObound = get_lowbo(lp, j); UPbound = get_upbo(lp, j); if(is_semicont(lp, j) && (UPbound > LObound)) { if(LObound > 0) LObound = 0; else if(UPbound < 0) UPbound = 0; } /* Get singleton variable bounds */ RHlow = get_rh_lower(lp, i); RHup = get_rh_upper(lp, i); if(!presolve_singletonbounds(psdata, i,j, &RHlow, &RHup, &Value)) return( presolve_setstatus(psdata, INFEASIBLE) ); if(presolve_coltighten(psdata, j, RHlow, RHup, count)) return( RUNNING ); else return( presolve_setstatus(psdata, INFEASIBLE) ); } STATIC MYBOOL presolve_colfix(presolverec *psdata, int colnr, REAL newvalue, MYBOOL remove, int *tally) { lprec *lp = psdata->lp; int i, ix, ie; MYBOOL isneg, lofinite, upfinite, doupdate = FALSE, doOF = TRUE; REAL lobound, upbound, lovalue, upvalue, Value, fixvalue, mult; MATrec *mat = lp->matA; psrec *ps = psdata->rows; REAL *value; int *rownr; /* Set "fixed" value in case we are deleting a variable */ upbound = get_upbo(lp, colnr); lobound = get_lowbo(lp, colnr); if(remove) { if(upbound-lobound < psdata->epsvalue) { if((newvalue > lobound) && (newvalue < upbound)) fixvalue = newvalue; else fixvalue = lobound; } else { if(my_infinite(lp, newvalue) && (get_mat(lp, 0, colnr) == 0)) fixvalue = ((lobound <= 0) && (upbound >= 0) ? 0 : MIN(upbound, lobound)); else fixvalue = newvalue; } #if 1 /* Fast normal version */ set_bounds(lp, colnr, fixvalue, fixvalue); #else /* Slower version that can be used for debugging/control purposes */ presolve_coltighten(psdata, colnr, fixvalue, fixvalue, NULL); lobound = fixvalue; upbound = fixvalue; #endif if(fixvalue != 0) addUndoPresolve(lp, TRUE, colnr, fixvalue, 0, 0); mult = -1; } else { mult = 1; fixvalue = 0; } /* Adjust semi-continuous variable bounds to zero-base */ if(is_semicont(lp, colnr) && (upbound > lobound)) { if(lobound > 0) lobound = 0; else if(upbound < 0) upbound = 0; } /* Loop over rows to update statistics */ ix = mat->col_end[colnr - 1]; ie = mat->col_end[colnr]; rownr = &COL_MAT_ROWNR(ix); value = &COL_MAT_VALUE(ix); for(; doOF || (ix < ie); ix++, rownr += matRowColStep, value += matValueStep) { /* Retrieve row data and adjust RHS if we are deleting a variable */ Restart: if(doOF) { i = 0; Value = lp->orig_obj[colnr]; } else { i = *rownr; Value = *value; if(!isActiveLink(ps->varmap, i)) continue; } if(Value == 0) goto BlockEnd; if(remove && (fixvalue != 0)) presolve_adjustrhs(psdata, i, Value*fixvalue, psdata->epsvalue); /* Prepare for further processing */ Value = my_chsign(is_chsign(lp, i), Value); isneg = (MYBOOL) (Value < 0); /* Reduce row variable counts if we are removing the variable */ if(remove == TRUE) { if(isneg) { ps->negcount[i]--; } else { ps->plucount[i]--; } if((lobound < 0) && (upbound >= 0)) { ps->pluneg[i]--; } } /* Compute associated constraint contribution values */ upfinite = (MYBOOL) (upbound < lp->infinite); lofinite = (MYBOOL) (lobound > -lp->infinite); if(upfinite || lofinite) { if(remove) ps->infcount[i]--; else ps->infcount[i]++; } upvalue = my_if(upfinite, Value*upbound, my_chsign(isneg, lp->infinite)); lovalue = my_if(lofinite, Value*lobound, my_chsign(isneg, -lp->infinite)); /* Cumulate effective upper row bound (only bother with non-finite bound) */ if(isneg) { if((ps->negupper[i] < lp->infinite) && lofinite) { ps->negupper[i] += mult*lovalue; ps->negupper[i] = presolve_roundrhs(lp, ps->negupper[i], FALSE); } else if(remove && !lofinite) doupdate = TRUE; else ps->negupper[i] = lp->infinite; } else { if((ps->pluupper[i] < lp->infinite) && upfinite) { ps->pluupper[i] += mult*upvalue; ps->pluupper[i] = presolve_roundrhs(lp, ps->pluupper[i], FALSE); } else if(remove && !upfinite) doupdate = TRUE; else ps->pluupper[i] = lp->infinite; } /* Cumulate effective lower row bound (only bother with non-finite bound) */ if(isneg) { if((ps->neglower[i] > -lp->infinite) && upfinite) { ps->neglower[i] += mult*upvalue; ps->neglower[i] = presolve_roundrhs(lp, ps->neglower[i], TRUE); } else if(remove && !upfinite) doupdate = TRUE; else ps->neglower[i] = -lp->infinite; } else { if((ps->plulower[i] > -lp->infinite) && lofinite) { ps->plulower[i] += mult*lovalue; ps->plulower[i] = presolve_roundrhs(lp, ps->plulower[i], TRUE); } else if(remove && !lofinite) doupdate = TRUE; else ps->plulower[i] = -lp->infinite; } /* Validate consistency of eliminated singleton */ if(remove && ((i == 0) || (ps->next[i][0] == 1)) && !psdata->forceupdate) { if(i == 0) { lovalue = get_rh_lower(lp, i); upvalue = get_rh_upper(lp, i); report(lp, DETAILED, "presolve_colfix: Objective determined by presolve as %18g\n", (is_maxim(lp) ? upvalue : lovalue)); } else { presolve_range(lp, i, ps, &lovalue, &upvalue); #if 1 Value = 0; #else Value = MAX(fabs(upvalue), fabs(lovalue)); Value = psdata->epsvalue * MAX(1, Value); #endif if((upvalue < get_rh_lower(lp, i)-Value) || (lovalue > get_rh_upper(lp, i)+Value)) { report(lp, NORMAL, "presolve_colfix: Variable %s (%g << %g) infeasibility in row %s (%g << %g)\n", get_col_name(lp, colnr), lovalue, upvalue, get_row_name(lp, i), get_rh_lower(lp,i), get_rh_upper(lp, i)); return( FALSE ); } } } BlockEnd: if(doOF) { doOF = FALSE; if(ix < ie) goto Restart; } } if(remove) { psdata->forceupdate |= doupdate; if(tally != NULL) (*tally)++; } return( TRUE ); } /* Delete the columns of the specified row, but make sure we don't delete SOS variables. Note that we cannot use presolve_nextcol() here, since the variables are deleted. */ STATIC int presolve_rowfixzero(presolverec *psdata, int rownr, int *nv) { lprec *lp = psdata->lp; MATrec *mat = lp->matA; int ix, jx, ib = mat->row_end[rownr-1]; for(ix = mat->row_end[rownr]-1; ix >= ib; ix--) { jx = ROW_MAT_COLNR(ix); if(isActiveLink(psdata->cols->varmap, jx)) { if(!presolve_colfix(psdata, jx, 0.0, TRUE, nv)) return( presolve_setstatus(psdata, INFEASIBLE) ); if(presolve_candeletevar(psdata, jx)) presolve_colremove(psdata, jx, TRUE); } } #ifdef xxParanoia if(!presolve_debugrowtallies(psdata)) return( INFEASIBLE ); #endif return( RUNNING ); } /* Function to find if a variable can be fixed based on considering the dual */ STATIC MYBOOL presolve_colfixdual(presolverec *psdata, int colnr, REAL *fixValue, int *status) { lprec *lp = psdata->lp; MYBOOL hasOF, isMI, isDualFREE = TRUE; int i, ix, ie, *rownr, signOF; REAL *value, loX, upX, eps = psdata->epsvalue; MATrec *mat = lp->matA; /* First check basic variable range */ loX = get_lowbo(lp, colnr); upX = get_upbo(lp, colnr); if(((loX < 0) && (upX > 0)) || (fabs(upX-loX) < lp->epsvalue) || SOS_is_member_of_type(lp->SOS, colnr, SOSn)) return( FALSE ); isMI = (MYBOOL) (upX <= 0); /* Retrieve OF (standard form assuming maximization) */ ix = mat->col_end[colnr - 1]; ie = mat->col_end[colnr]; rownr = &COL_MAT_ROWNR(ix); value = &COL_MAT_VALUE(ix); hasOF = isnz_origobj(lp, colnr); if(hasOF) signOF = my_sign(lp->orig_obj[colnr]); else signOF = 0; /* Loop over all constraints involving active variable (standard form with LE constraints)*/ for(; (ix < ie) && isDualFREE; ix++, rownr += matRowColStep, value += matValueStep) { i = *rownr; if(!isActiveLink(psdata->rows->varmap, i)) continue; if(presolve_rowlength(psdata, i) == 1) { REAL val = my_chsign(is_chsign(lp, i), *value), loR = get_rh_lower(lp, i), upR = get_rh_upper(lp, i); if(!presolve_singletonbounds(psdata, i, colnr, &loR, &upR, &val)) { *status = presolve_setstatus(psdata, INFEASIBLE); return( FALSE ); } if(loR > loX + psdata->epsvalue) loX = presolve_roundrhs(lp, loR, TRUE); if(upR < upX - psdata->epsvalue) upX = presolve_roundrhs(lp, upR, FALSE); continue; } else isDualFREE = my_infinite(lp, get_rh_range(lp, i)) || /* Explicitly free */ ((presolve_sumplumin(lp, i, psdata->rows, TRUE)-eps <= get_rh_upper(lp, i)) && /* Implicitly free */ (presolve_sumplumin(lp, i, psdata->rows, FALSE)+eps >= get_rh_lower(lp, i))); if(isDualFREE) { if(signOF == 0) /* Test on the basis of identical signs in the constraints */ signOF = my_sign(*value); else /* Test on the basis of constraint sign equal to OF sign */ isDualFREE = (MYBOOL) (signOF == my_sign(*value)); } } /* Set fixing value if we were successful */ if(isDualFREE) { if(signOF == 0) { SETMAX(loX, 0); *fixValue = MIN(loX, upX); } else if(signOF > 0) { if(my_infinite(lp, loX)) isDualFREE = FALSE; else { if(is_int(lp, colnr)) *fixValue = ceil(loX-PRESOLVE_EPSVALUE); else *fixValue = loX; } } else { if(my_infinite(lp, upX)) isDualFREE = FALSE; else { if(is_int(lp, colnr) && (upX != 0)) *fixValue = floor(upX+PRESOLVE_EPSVALUE); else *fixValue = upX; } } if((*fixValue != 0) && SOS_is_member(lp->SOS, 0, colnr)) return( FALSE ); } return( isDualFREE ); } #if 0 STATIC MYBOOL presolve_probefix01(presolverec *psdata, int colnr, REAL *fixvalue) { lprec *lp = psdata->lp; int i, ix, item; REAL loLim, absvalue, epsvalue = psdata->epsvalue; MATrec *mat = lp->matA; MYBOOL chsign, canfix = FALSE; if(!is_binary(lp, colnr)) return( canfix ); /* Loop over all active rows to search for fixing opportunity */ item = 0; for(ix = presolve_nextrow(psdata, colnr, &item); (ix >= 0) && !canfix; ix = presolve_nextrow(psdata, colnr, &item)) { i = COL_MAT_ROWNR(ix); *fixvalue = COL_MAT_VALUE(ix); chsign = is_chsign(lp, i); /* First check the lower bound of the normalized constraint */ loLim = presolve_sumplumin(lp, i, psdata->rows, chsign); loLim = my_chsign(chsign, loLim); absvalue = fabs(*fixvalue); canfix = (MYBOOL) ((loLim + absvalue > lp->orig_rhs[i]+epsvalue*MAX(1, absvalue))); /* If we were unsuccessful in fixing above, try the upper bound of the normalized constraint - if it is finite */ if(!canfix && !my_infinite(lp, get_rh_range(lp, i))) { loLim = presolve_sumplumin(lp, i, psdata->rows, (MYBOOL) !chsign); loLim = my_chsign(!chsign, loLim); *fixvalue = -(*fixvalue); canfix = (MYBOOL) ((loLim + absvalue > get_rh_range(lp, i)-lp->orig_rhs[i]+epsvalue*MAX(1, absvalue))); } } /* Check if we were successful in identifying fixing opportunity */ if(canfix) { if(*fixvalue < 0) *fixvalue = 1; else *fixvalue = 0; } return( canfix ); } #else STATIC MYBOOL presolve_probefix01(presolverec *psdata, int colnr, REAL *fixvalue) { lprec *lp = psdata->lp; int i, ix, item; REAL loLim, upLim, range, absvalue, epsvalue = psdata->epsvalue, tolgap; MATrec *mat = lp->matA; MYBOOL chsign, status = FALSE; if(!is_binary(lp, colnr)) return( status ); /* Loop over all active rows to search for fixing opportunity. The logic is that if a constraint gets violated by setting a variable at one of its bounds, then it can be fixed at its opposite bound. */ item = 0; for(ix = presolve_nextrow(psdata, colnr, &item); (ix >= 0); ix = presolve_nextrow(psdata, colnr, &item)) { i = COL_MAT_ROWNR(ix); *fixvalue = COL_MAT_VALUE(ix); absvalue = fabs(*fixvalue); SETMIN(absvalue, 100); tolgap = epsvalue*MAX(1, absvalue); chsign = is_chsign(lp, i); /* Get the constraint value limits based on variable bounds, normalized to LE constraint */ loLim = presolve_sumplumin(lp, i, psdata->rows, FALSE); upLim = presolve_sumplumin(lp, i, psdata->rows, TRUE); if(chsign) { loLim = my_chsign(chsign, loLim); upLim = my_chsign(chsign, upLim); swapREAL(&loLim, &upLim); } /* Check the upper constraint bound for possible violation if the value were to be fixed at 1 */ if(loLim + *fixvalue > lp->orig_rhs[i]+tolgap) { if(*fixvalue < 0) presolve_setstatus(psdata, INFEASIBLE); *fixvalue = 0; break; } /* Check the lower constraint bound for possible violation if the value were to be fixed at 1 */ range = get_rh_range(lp, i); if(!my_infinite(lp, range) && (upLim + *fixvalue < lp->orig_rhs[i]-range-tolgap)) { if(*fixvalue > 0) presolve_setstatus(psdata, INFEASIBLE); *fixvalue = 0; break; } /* Check if we have to fix the value at 1 to avoid constraint infeasibility */ if(psdata->rows->infcount[i] >= 1) continue; if(((*fixvalue < 0) && (upLim + *fixvalue >= loLim-tolgap) && (upLim > lp->orig_rhs[i]+tolgap)) || ((*fixvalue > 0) && (loLim + *fixvalue <= upLim+tolgap) && (loLim < lp->orig_rhs[i]-range-tolgap) && !my_infinite(lp, range))) { *fixvalue = 1; break; } } status = (MYBOOL) (ix >= 0); /* Returns TRUE if fixing opportunity was identified */ return( status ); } #endif STATIC int presolve_probetighten01(presolverec *psdata, int colnr) { lprec *lp = psdata->lp; MYBOOL chsign; int i, ix, item, n = 0; REAL upLim, value, absvalue, epsvalue = psdata->epsvalue; MATrec *mat = lp->matA; #if 0 /* Handled in calling routine */ if(!is_binary(lp, colnr)) return( n ); #endif /* Loop over all active rows and do coefficient tightening for qualifying constraints */ item = 0; for(ix = presolve_nextrow(psdata, colnr, &item); ix >= 0; ix = presolve_nextrow(psdata, colnr, &item)) { i = COL_MAT_ROWNR(ix); value = COL_MAT_VALUE(ix); chsign = is_chsign(lp, i); upLim = presolve_sumplumin(lp, i, psdata->rows, (MYBOOL) !chsign); upLim = my_chsign(chsign, upLim); /* Does this constraint qualify for coefficient tightening? */ absvalue = fabs(value); if(upLim - absvalue < lp->orig_rhs[i]-epsvalue*MAX(1, absvalue)) { REAL delta = lp->orig_rhs[i] - upLim; lp->orig_rhs[i] = upLim; upLim = value - my_chsign(value < 0, delta); COL_MAT_VALUE(ix) = upLim; if(my_sign(value) != my_sign(upLim)) { if(chsign) { psdata->rows->negcount[i]--; psdata->rows->plucount[i]++; } else { psdata->rows->negcount[i]++; psdata->rows->plucount[i]--; } } n++; } } return( n ); } STATIC int presolve_mergerows(presolverec *psdata, int *nRows, int *nSum) { lprec *lp = psdata->lp; MYBOOL candelete; int status = RUNNING, item1, item2, firstix, RT1, RT2, i, ix, iix, j, jjx, n = 0; REAL Value1, Value2, bound; MATrec *mat = lp->matA; for(i = lastActiveLink(psdata->rows->varmap); (i > 0) && (status == RUNNING); ) { /* First scan for rows with identical row lengths */ ix = prevActiveLink(psdata->rows->varmap, i); if(ix == 0) break; /* Don't bother about empty rows or row singletons, since they are handled by PRESOLVE_ROWS */ j = presolve_rowlength(psdata, i); if(j <= 1) { i = ix; continue; } #if 0 /* Enable this to scan all rows back */ RT2 = lp->rows; /* Check abort since this section can be pretty "expensive" */ if(!presolve_statuscheck(psdata, &status)) return( status ); #else RT2 = 2+1; #endif firstix = ix; for(RT1 = 0; (ix > 0) && (RT1 < RT2) && (status == RUNNING); ix = prevActiveLink(psdata->rows->varmap, ix), RT1++) { candelete = FALSE; if(presolve_rowlength(psdata, ix) != j) continue; /* Check if the beginning columns are identical; if not, continue */ item1 = 0; iix = presolve_nextcol(psdata, ix, &item1); item2 = 0; jjx = presolve_nextcol(psdata, i, &item2); if(ROW_MAT_COLNR(iix) != ROW_MAT_COLNR(jjx)) continue; /* We have a candidate row; check if the entries have a fixed non-zero ratio */ Value1 = get_mat_byindex(lp, iix, TRUE, FALSE); Value2 = get_mat_byindex(lp, jjx, TRUE, FALSE); bound = Value1 / Value2; Value1 = bound; /* Loop over remaining entries */ jjx = presolve_nextcol(psdata, i, &item2); for(; (jjx >= 0) && (Value1 == bound); jjx = presolve_nextcol(psdata, i, &item2)) { iix = presolve_nextcol(psdata, ix, &item1); if(ROW_MAT_COLNR(iix) != ROW_MAT_COLNR(jjx)) break; Value1 = get_mat_byindex(lp, iix, TRUE, FALSE); Value2 = get_mat_byindex(lp, jjx, TRUE, FALSE); /* If the ratio is different from the reference value we have a mismatch */ Value1 = Value1 / Value2; if(bound == lp->infinite) bound = Value1; else if(fabs(Value1 - bound) > psdata->epsvalue) break; } /* Check if we found a match (we traversed all active columns without a break) */ if(jjx < 0) { /* Get main reference values */ Value1 = lp->orig_rhs[ix]; Value2 = lp->orig_rhs[i] * bound; /* First check for inconsistent equalities */ if((fabs(Value1 - Value2) > psdata->epsvalue) && ((get_constr_type(lp, ix) == EQ) && (get_constr_type(lp, i) == EQ))) { report(lp, NORMAL, "presolve_mergerows: Inconsistent equalities %d and %d found\n", ix, i); status = presolve_setstatus(psdata, INFEASIBLE); } else { /* Update lower and upper bounds */ if(is_chsign(lp, i) != is_chsign(lp, ix)) bound = -bound; Value1 = get_rh_lower(lp, i); if(Value1 <= -lp->infinite) Value1 *= my_sign(bound); else Value1 *= bound; my_roundzero(Value1, lp->epsdual); /* Extra rounding tolerance *** */ Value2 = get_rh_upper(lp, i); if(Value2 >= lp->infinite) Value2 *= my_sign(bound); else Value2 *= bound; my_roundzero(Value2, lp->epsdual); /* Extra rounding tolerance *** */ if((bound < 0)) swapREAL(&Value1, &Value2); bound = get_rh_lower(lp, ix); if(Value1 > bound + psdata->epsvalue) set_rh_lower(lp, ix, Value1); else Value1 = bound; bound = get_rh_upper(lp, ix); if(Value2 < bound - psdata->epsvalue) set_rh_upper(lp, ix, Value2); else Value2 = bound; /* Check results and make equality if appropriate */ if(fabs(Value2-Value1) < psdata->epsvalue) presolve_setEQ(psdata, ix); else if(Value2 < Value1) { status = presolve_setstatus(psdata, INFEASIBLE); } /* Verify if we can continue */ candelete = (MYBOOL) (status == RUNNING); if(!candelete) { report(lp, NORMAL, "presolve: Range infeasibility found involving rows %s and %s\n", get_row_name(lp, ix), get_row_name(lp, i)); } } } /* Perform i-row deletion if authorized */ if(candelete) { presolve_rowremove(psdata, i, TRUE); n++; break; } } i = firstix; } (*nRows) += n; (*nSum) += n; return( status ); } STATIC MYBOOL presolve_reduceGCD(presolverec *psdata, int *nn, int *nb, int *nsum) { lprec *lp = psdata->lp; MYBOOL status = TRUE; int i, jx, je, in = 0, ib = 0; LLONG GCDvalue; REAL *Avalue, Rvalue, epsvalue = psdata->epsvalue; MATrec *mat = lp->matA; for(i = firstActiveLink(psdata->INTmap); i != 0; i = nextActiveLink(psdata->INTmap, i)) { /* Obtain the row GCD */ jx = mat->row_end[i - 1]; je = mat->row_end[i]; Rvalue = ROW_MAT_VALUE(jx); GCDvalue = abs((int) Rvalue); jx++; if(jx < je) for(; (jx < je) && (GCDvalue > 1); jx++) { Rvalue = fabs(ROW_MAT_VALUE(jx)); GCDvalue = gcd((LLONG) Rvalue, GCDvalue, NULL, NULL); } /* Reduce the coefficients, if possible */ if(GCDvalue > 1) { jx = mat->row_end[i - 1]; je = mat->row_end[i]; for(; jx < je; jx++) { Avalue = &ROW_MAT_VALUE(jx); *Avalue /= GCDvalue; in++; } Rvalue = (lp->orig_rhs[i] / GCDvalue) + epsvalue; lp->orig_rhs[i] = floor(Rvalue); Rvalue = fabs(lp->orig_rhs[i]-Rvalue); if(is_constr_type(lp, i, EQ) && (Rvalue > epsvalue)) { report(lp, NORMAL, "presolve_reduceGCD: Infeasible equality constraint %d\n", i); status = FALSE; break; } if(!my_infinite(lp, lp->orig_upbo[i])) lp->orig_upbo[i] = floor(lp->orig_upbo[i] / GCDvalue); ib++; } } if(status && (in > 0)) report(lp, DETAILED, "presolve_reduceGCD: Did %d constraint coefficient reductions.\n", in); (*nn) += in; (*nb) += ib; (*nsum) += in + ib; return( status ); } STATIC int presolve_knapsack(presolverec *psdata, int *nn) { lprec *lp = psdata->lp; int m, n, i, ix, j, jx, colnr, *rownr = NULL, status = RUNNING; REAL *colOF = lp->orig_obj, value, *ratio = NULL; LLrec *map = psdata->EQmap; MATrec *mat = lp->matA; /* Check if it is worth trying */ m = mat->row_end[0]; if((map->count == 0) || (m < 2)) return( status ); /* Get the OF row */ allocINT(lp, &rownr, map->count+1, FALSE); allocREAL(lp, &ratio, map->count+1, FALSE); /* Loop over each row trying to find equal entries in the OF */ rownr[0] = 0; for(i = firstActiveLink(map); i != 0; i = nextActiveLink(map, i)) { if(get_rh(lp, i) <= 0) continue; jx = mat->row_end[i]; n = 0; for(j = mat->row_end[i-1]; j < jx; j++, n++) { colnr = ROW_MAT_COLNR(j); value = ROW_MAT_VALUE(j); if(colOF[colnr] == 0) break; if(n == 0) { ratio[0] = colOF[colnr] / value; } else if(fabs(value * ratio[0] - colOF[colnr]) > psdata->epsvalue) { n = -1; break; } } /* Register row if we were successful (and row long enough) */ if(n >= 2) { ix = ++rownr[0]; rownr[ix] = i; ratio[ix] = ratio[0]; } } n = rownr[0]; if(n == 0) goto Finish; /* Process the identified rows, eliminating the OF value */ for(ix = 1; ix <= n; ix++) { i = rownr[ix]; jx = mat->row_end[i]; for(j = mat->row_end[i-1]; j < jx; j++) { colnr = ROW_MAT_COLNR(j); colOF[colnr] = 0; } } /* Update key mapper structures */ j = lp->columns; psdata->cols->varmap = cloneLink(psdata->cols->varmap, j+n, TRUE); psdata->forceupdate = TRUE; /* Finally, add helper columns */ for(ix = 1; ix <= n; ix++) { i = rownr[ix]; rownr[0] = 0; colOF[0] = my_chsign(is_maxim(lp), ratio[ix]); rownr[1] = i; colOF[1] = -1; value = get_rh(lp, i); /* j = get_constr_type(lp, i); */ add_columnex(lp, 2, colOF, rownr); set_bounds(lp, lp->columns, value, value); /* presolve_setEQ(psdata, i); */ set_rh(lp, i, 0); appendLink(psdata->cols->varmap, j+ix); } presolve_validate(psdata, TRUE); /* Clean up before returning */ Finish: FREE(rownr); FREE(ratio); (*nn) += n; return( status ); } STATIC MYBOOL presolve_invalideq2(lprec *lp, presolverec *psdata) { int jx, jjx, i = 0, item; MATrec *mat = lp->matA; MYBOOL error = FALSE; do { if(i == 0) i = firstActiveLink(psdata->EQmap); else i = nextActiveLink(psdata->EQmap, i); if(i == 0) return( error ); /* Get the row index of the first 2-element equality */ for(; i > 0; i = nextActiveLink(psdata->EQmap, i)) if(presolve_rowlength(psdata, i) == 2) break; if(i == 0) return( error ); /* Get the first column */ item = 0; jx = presolve_nextcol(psdata, i, &item); if(jx < 0) error = TRUE; jx = ROW_MAT_COLNR(jx); /* Get the second column */ jjx = presolve_nextcol(psdata, i, &item); if(jjx < 0) error = AUTOMATIC; } while(!error); return( error ); } /* Callback to obtain the non-zero rows of equality constraints */ int BFP_CALLMODEL presolve_getcolumnEQ(lprec *lp, int colnr, REAL nzvalues[], int nzrows[], int mapin[]) { int i, ib, ie, nn = 0; MATrec *mat = lp->matA; ib = mat->col_end[colnr-1]; ie = mat->col_end[colnr]; for(; ib < ie; ib++) { i = COL_MAT_ROWNR(ib); if(!is_constr_type(lp, i, EQ) || /* It has to be an equality constraint */ (mapin[i] == 0)) /* And it should not already have been deleted */ continue; if(nzvalues != NULL) { nzrows[nn] = mapin[i]; nzvalues[nn] = COL_MAT_VALUE(ib); } nn++; } return( nn ); } STATIC int presolve_singularities(presolverec *psdata, int *nn, int *nr, int *nv, int *nSum) { lprec *lp = psdata->lp; int i, j, n, *rmapin = NULL, *rmapout = NULL, *cmapout = NULL; if(lp->bfp_findredundant(lp, 0, NULL, NULL, NULL) == 0) return( 0 ); /* Create condensed row map */ allocINT(lp, &rmapin, lp->rows+1, TRUE); allocINT(lp, &rmapout, psdata->EQmap->count+1, FALSE); allocINT(lp, &cmapout, lp->columns+1, FALSE); n = 0; for(i = firstActiveLink(psdata->EQmap); i != 0; i = nextActiveLink(psdata->EQmap, i)) { n++; rmapout[n] = i; rmapin[i] = n; } rmapout[0] = n; n = 0; for(i = firstActiveLink(psdata->cols->varmap); i != 0; i = nextActiveLink(psdata->cols->varmap, i)) { n++; cmapout[n] = i; } cmapout[0] = n; /* Do the rank-revealing factorization */ n = lp->bfp_findredundant(lp, psdata->EQmap->count, presolve_getcolumnEQ, rmapin, cmapout); /* Delete the redundant rows */ for(i = 1; i <= n; i++) { j = rmapin[i]; j = rmapout[j]; presolve_rowremove(psdata, j, TRUE); } (*nn) += n; (*nr) += n; (*nSum) += n; /* Clean up */ FREE(rmapout); FREE(rmapin); FREE(cmapout); return( n ); } STATIC int presolve_elimeq2(presolverec *psdata, int *nn, int *nr, int *nc, int *nSum) { lprec *lp = psdata->lp; int n, i, jx, jjx, k, item, *plucount, *negcount, colplu, colneg, iCoeffChanged = 0, iRowsRemoved = 0, iVarsFixed = 0, nrows = lp->rows, status = RUNNING, *colindex = NULL; MYBOOL freshupdate; REAL Coeff1, Coeff2, Value1, Value2, lobound, upbound, bound, test, product, *colvalue = NULL, *delvalue = NULL, *colitem; MATrec *mat = lp->matA, *rev = NULL; DeltaVrec *DV = NULL; LLrec *EQ2 = NULL; /* See if there is anything to do */ if(psdata->EQmap->count == 0) { (*nSum) = 0; return( status ); } /* Tally counts */ createLink(lp->rows, &EQ2, NULL); if((EQ2 == NULL) || !allocREAL(lp, &colvalue, nrows+1, FALSE) || !allocREAL(lp, &delvalue, nrows+1, FALSE)) goto Finish; for(i = firstActiveLink(psdata->EQmap); i > 0; i = nextActiveLink(psdata->EQmap, i)) { if(presolve_rowlength(psdata, i) == 2) appendLink(EQ2, i); } if(EQ2->count == 0) goto Finish; n = 0; /* Do the elimination loop for all identified 2-element equalities */ for(i = firstActiveLink(EQ2); i > 0; i = nextActiveLink(EQ2, i)) { /* Check if the constraint has been modified by a previous elimination */ if(presolve_rowlength(psdata, i) != 2) continue; /* Get the column indeces of NZ-values of the "pivot" row */ item = 0; jx = presolve_nextcol(psdata, i, &item); /* Eliminated variable coefficient b */ #ifdef Paranoia if(jx < 0) report(lp, SEVERE, "presolve_elimeq2: No qualifying %dst column was found in row %s (ostensible length %d)\n", 1, get_row_name(lp, i), presolve_rowlength(psdata, i)); #endif Coeff2 = ROW_MAT_VALUE(jx); jx = ROW_MAT_COLNR(jx); jjx = presolve_nextcol(psdata, i, &item); /* Non-eliminated variable coefficient a */ #ifdef Paranoia if(jjx < 0) report(lp, SEVERE, "presolve_elimeq2: No qualifying %dnd column was found in row %s (ostensible length %d)\n", 2, get_row_name(lp, i), presolve_rowlength(psdata, i)); #endif Coeff1 = ROW_MAT_VALUE(jjx); jjx = ROW_MAT_COLNR(jjx); /* Check if at least one of the coefficients is large enough to preserve stability; use opposing maximum column values for stability testing. */ if((fabs(Coeff1) < psdata->epspivot*mat->colmax[jx]) && ((fabs(Coeff1) != 1) && (fabs(Coeff2) != 1)) && (fabs(Coeff2) < psdata->epspivot*mat->colmax[jjx])) continue; /* Cannot eliminate a variable if both are SOS members or SC variables */ if((is_semicont(lp, jx) && is_semicont(lp, jjx)) || (SOS_is_member(lp->SOS, 0, jx) && SOS_is_member(lp->SOS, 0, jjx))) continue; /* First check if we are allowed to swap; set swap "blockers" */ k = 0; if(!is_int(lp, jx) && is_int(lp, jjx)) k += 1; else if(!is_semicont(lp, jx) && is_semicont(lp, jjx)) k += 2; else if(!SOS_is_member(lp->SOS, 0, jx) && SOS_is_member(lp->SOS, 0, jjx)) k += 4; /* If there were no blockers, determine if we MUST swap the variable to be eliminated */ if(k == 0) { if(is_int(lp, jx) && !is_int(lp, jjx)) k += 8; else if(is_semicont(lp, jx) && !is_semicont(lp, jjx)) k += 16; else if(SOS_is_member(lp->SOS, 0, jx) && !SOS_is_member(lp->SOS, 0, jjx)) k += 32; /* If we are not forced to swap, decide if it otherwise makes sense - high order */ if(k == 0) { if((fabs(Coeff2) < psdata->epspivot*mat->colmax[jjx]) && (fabs(Coeff1) > psdata->epspivot*mat->colmax[jx])) k += 64; else if(presolve_collength(psdata, jx) > presolve_collength(psdata, jjx)) k += 128; } /* If we are not forced to swap, decide if it otherwise makes sense - low order */ if(k == 0) { Value2 = Coeff1/Coeff2; #ifdef DualFeasibilityLogicEQ2 if((Value2*lp->orig_obj[jx] < 0) && (Value2*lp->orig_obj[jjx] > 0)) /* Seek increased dual feasibility */ k += 256; #endif #ifdef DivisorIntegralityLogicEQ2 if((fabs(modf(Coeff2, &Value2)) >= lp->epsvalue) && /* Seek integrality of result */ (fabs(modf(Coeff1, &Value2)) < lp->epsvalue)) k += 512; else if((fabs(fabs(Coeff2)-1) >= lp->epsvalue) && /* Seek integrality of divisor */ (fabs(fabs(Coeff1)-1) < lp->epsvalue)) k += 1024; #endif } } else k = 0; /* Perform variable index swap if indicated */ if(k != 0) { swapINT(&jx, &jjx); swapREAL(&Coeff1, &Coeff2); } Value1 = lp->orig_rhs[i]/Coeff2; /* Delta constant term */ Value2 = Coeff1/Coeff2; /* Delta variable term */ upbound = lp->orig_upbo[lp->rows+jx]; lobound = lp->orig_lowbo[lp->rows+jx]; if(lp->spx_trace) { report(lp, DETAILED, "Row %3d : Elim %g %s - %d\n", i, Coeff2, get_col_name(lp, jx), jx); report(lp, DETAILED, " Keep %g %s - %d\n", Coeff1, get_col_name(lp, jjx), jjx); } /* Get the coefficient vectors of the independent (jjx) and dependent (jx) columns; the dependent column will be deleted and reconstructed during postsolve. */ freshupdate = (MYBOOL) ((colindex == NULL) || (colindex[jjx] == 0)); if(freshupdate) mat_expandcolumn(mat, jjx, colvalue, NULL, TRUE); else mat_expandcolumn(rev, colindex[jjx], colvalue, NULL, FALSE); if((colindex == NULL) || (colindex[jx] == 0)) mat_expandcolumn(mat, jx, delvalue, NULL, TRUE); else mat_expandcolumn(rev, colindex[jx], delvalue, NULL, FALSE); /* Add variable reconstruction information */ addUndoPresolve(lp, TRUE, jx, Value1, Value2, jjx); /* If possible, tighten the bounds of the uneliminated variable based on the bounds of the eliminated variable. Also handle roundings and attempt precision management. */ bound = lobound; k = nrows+jjx; if(bound > -lp->infinite) { bound = (lp->orig_rhs[i] - Coeff2*bound) / Coeff1; if(Value2 > 0) { test = lp->orig_upbo[k]; if(bound < test - psdata->epsvalue) { if(is_int(lp, jjx)) lp->orig_upbo[k] = floor(bound + lp->epsint); else lp->orig_upbo[k] = presolve_roundrhs(lp, bound, FALSE); } } else { test = lp->orig_lowbo[k]; if(bound > test + psdata->epsvalue) { if(is_int(lp, jjx)) lp->orig_lowbo[k] = ceil(bound - lp->epsint); else lp->orig_lowbo[k] = presolve_roundrhs(lp, bound, TRUE); } } } bound = upbound; if(bound < lp->infinite) { bound = (lp->orig_rhs[i] - Coeff2*bound) / Coeff1; if(Value2 < 0) { test = lp->orig_upbo[k]; if(bound < test - psdata->epsvalue) { if(is_int(lp, jjx)) lp->orig_upbo[k] = floor(bound + lp->epsint); else lp->orig_upbo[k] = presolve_roundrhs(lp, bound, FALSE); } } else { test = lp->orig_lowbo[k]; if(bound > test + psdata->epsvalue) { if(is_int(lp, jjx)) lp->orig_lowbo[k] = ceil(bound - lp->epsint); else lp->orig_lowbo[k] = presolve_roundrhs(lp, bound, TRUE); } } } #ifdef Eq2Reldiff test = 2*lp->epsvalue; #else test = psdata->epsvalue; #endif if(/*(lp->orig_upbo[k] < lp->orig_lowbo[k]) ||*/ #ifdef Eq2Reldiff (fabs(my_reldiff(lp->orig_upbo[k],lp->orig_lowbo[k])) < test)) { #else (fabs(lp->orig_upbo[k] - lp->orig_lowbo[k]) < test)) { #endif my_roundzero(lp->orig_lowbo[k], test); lp->orig_upbo[k] = lp->orig_lowbo[k]; } else { my_roundzero(lp->orig_upbo[k], test); my_roundzero(lp->orig_lowbo[k], test); } if(/*(upbound < lobound) ||*/ #ifdef Eq2Reldiff (fabs(my_reldiff(upbound, lobound)) < test)) { #else (fabs(upbound - lobound) < test)) { #endif my_roundzero(lobound, test); lp->orig_upbo[nrows+jx] = lobound; upbound = lobound; } /* Loop over the non-zero rows of the column (jx) to be eliminated; substitute jx-variable by updating rhs and jjx coefficients */ colitem = colvalue; plucount = psdata->rows->plucount; negcount = psdata->rows->negcount; colplu = 0; colneg = 0; /* Count of non-zeros in the independent column jjx */ item = presolve_collength(psdata, jjx) - 1; if(isnz_origobj(lp, jjx)) item++; for(k = 0; k <= nrows; k++, colitem++) { bound = delvalue[k]; if((k == i) || (bound == 0) || ((k > 0) && !isActiveLink(psdata->rows->varmap, k))) continue; /* Do constraint and nz-count updates for the substituted variable */ product = bound*Value1; /* "Raw"/unsigned data */ presolve_adjustrhs(psdata, k, my_chsign(is_chsign(lp, k), product), test); /* Change back to signed part */ if(*colitem != 0) { if(*colitem > 0) { colplu--; plucount[k]--; } else { colneg--; negcount[k]--; } if((lobound < 0) && (upbound >= 0)) { psdata->cols->pluneg[jjx]--; psdata->rows->pluneg[k]--; } item--; } (*colitem) -= bound*Value2; iCoeffChanged++; /* Update counts */ if(fabs(*colitem) >= mat->epsvalue) { if(*colitem > 0) { colplu++; plucount[k]++; } else { colneg++; negcount[k]++; } if((lobound < 0) && (upbound >= 0)) { psdata->cols->pluneg[jjx]++; psdata->rows->pluneg[k]++; } item++; } else { *colitem = 0; } /* Also reduce count if the row contains the deleted variable */ if(bound > 0) plucount[k]--; else negcount[k]--; } psdata->cols->plucount[jjx] += colplu; psdata->cols->negcount[jjx] += colneg; /* Save the new column */ if(rev == NULL) { DV = createUndoLadder(lp, nrows, lp->columns / RESIZEFACTOR); rev = DV->tracker; rev->epsvalue = mat->epsvalue; allocINT(lp, &(rev->col_tag), mat->columns_alloc+1, FALSE); allocINT(lp, &colindex, lp->columns+1, TRUE); rev->col_tag[0] = 0; } n = rev->col_tag[0] = incrementUndoLadder(DV); mat_setcol(rev, n, 0, colvalue, NULL, FALSE, FALSE); rev->col_tag[n] = jjx; /* Save index to updated vector, but specially handle case where we have the same independent variable for multiple equations! */ if(!freshupdate) rev->col_tag[colindex[jjx]] *= -1; colindex[jjx] = n; /* Delete the column dependent variable */ jx = presolve_colremove(psdata, jx, FALSE); iVarsFixed++; /* Check if we have been lucky enough to have eliminated the independent variable via substitution of the dependent variable */ if(item == 0) { #ifdef Paranoia report(lp, DETAILED, "presolve_elimeq2: Was able to remove variables %d and %d in row %s\n", jx, jjx, get_row_name(lp, i)); #endif if(presolve_colfix(psdata, jjx, 0.0, TRUE, nc)) jjx = presolve_colremove(psdata, jjx, FALSE); } /* Delete the row */ presolve_rowremove(psdata, i, FALSE); iRowsRemoved++; } /* Perform the column updates collected above */ if(n > 0) { mat_mapreplace(mat, psdata->rows->varmap, psdata->cols->varmap, rev); presolve_validate(psdata, TRUE); #ifdef PresolveForceUpdateMax mat_computemax(mat /* , FALSE */); #endif psdata->forceupdate = TRUE; } /* Free work arrays */ Finish: if(DV != NULL) freeUndoLadder(&DV); freeLink(&EQ2); FREE(colvalue); FREE(delvalue); FREE(colindex); /* Update counters */ (*nn) += iCoeffChanged; (*nr) += iRowsRemoved; (*nc) += iVarsFixed; (*nSum) += iCoeffChanged + iRowsRemoved + iVarsFixed; return( status ); } STATIC MYBOOL presolve_impliedfree(lprec *lp, presolverec *psdata, int colnr) { int i, ix, ie; REAL Tlower, Tupper; MYBOOL status, rowbinds, isfree = FALSE; MATrec *mat = lp->matA; if(my_infinite(lp, get_lowbo(lp, colnr)) && my_infinite(lp, get_upbo(lp, colnr))) return( TRUE ); ie = mat->col_end[colnr]; for(ix = mat->col_end[colnr-1]; (isfree != (TRUE | AUTOMATIC)) && (ix < ie); ix++) { i = COL_MAT_ROWNR(ix); if(!isActiveLink(psdata->rows->varmap, i)) continue; Tlower = get_rh_lower(lp, i); Tupper = get_rh_upper(lp, i); status = presolve_multibounds(psdata, i, colnr, &Tlower, &Tupper, NULL, &rowbinds); isfree = isfree | status | rowbinds; } return( (MYBOOL) (isfree == (TRUE | AUTOMATIC)) ); } STATIC MYBOOL presolve_impliedcolfix(presolverec *psdata, int rownr, int colnr, MYBOOL isfree) { lprec *lp = psdata->lp; MYBOOL signflip, undoadded = FALSE; MATrec *mat = lp->matA; int jx, i, ib, ie = mat->row_end[rownr]; REAL varLo = 0, varHi = 0, varRange, conRange = 0, matValue = 0, dual, RHS = lp->orig_rhs[rownr], pivot, matAij = mat_getitem(mat, rownr, colnr), *vecOF = lp->orig_obj; /* We cannot have semi-continuous or non-qualifying integers */ if(is_semicont(lp, colnr) || is_SOS_var(lp, colnr)) return( FALSE ); if(is_int(lp, colnr)) { if(!isActiveLink(psdata->INTmap, rownr) || !is_presolve(lp, PRESOLVE_KNAPSACK)) return( FALSE ); /* colnr must have a coefficient equal to the smallest in the row */ varRange = lp->infinite; i = 0; pivot = 0; for(ib = presolve_nextcol(psdata, rownr, &i); i != 0; ib = presolve_nextcol(psdata, rownr, &i)) { jx = ROW_MAT_COLNR(ib); dual = fabs(ROW_MAT_VALUE(ib)); /* Check if we have the target column and save the pivot value */ if(jx == colnr) { /* Always accept unit coefficient */ if(fabs(dual - 1) < psdata->epsvalue) break; pivot = dual; /* Otherwise continue scan */ } /* Cannot accept case where result can be fractional */ else if((pivot > dual + psdata->epsvalue) || ((pivot > 0) && (fabs(fmod(dual, pivot)) > psdata->epsvalue))) return( FALSE ); } } /* Ascertain that the pivot value is large enough to preserve stability */ pivot = matAij; if(fabs(pivot) < psdata->epspivot*mat->colmax[colnr]) return( FALSE ); /* Must ascertain that the row variables are not SOS'es; this is because the eliminated variable will be a function of another variable. */ if(SOS_count(lp) > 0) { for(ib = mat->row_end[rownr-1]; ib < ie; ib++) if(SOS_is_member(lp->SOS, 0, ROW_MAT_COLNR(ib))) return( FALSE ); } /* Calculate the dual value */ dual = vecOF[colnr]/pivot; /* Here we have free variable in an equality constraint; this means we can can adjust the OF for the deleted variable and also delete the constraint. */ if(isfree && is_constr_type(lp, rownr, EQ)) { matValue = RHS/pivot; if(matValue != 0) undoadded = addUndoPresolve(lp, TRUE, colnr, matValue, 0.0, 0); } else { /* IMPLIEDFREE: For simplicity, ensure that we can keep the slack based at 0, and not its upper bound. Effectively, we consider the constraint an equality, using the information of the sign of the dual. IMPLIEDSLK: Since we already have an equality constraint, we wish to make sure that the ensuing inequality constraint will have an RHS that is non-infinite. */ if(isfree) { SETMIN(RHS, presolve_sumplumin(lp, rownr, psdata->rows, TRUE)); matValue = presolve_sumplumin(lp, rownr, psdata->rows, FALSE); conRange = get_rh_lower(lp, rownr); conRange = RHS - MAX(matValue, conRange); signflip = (MYBOOL) ((dual > 0) && !my_infinite(lp, conRange)); } else { varLo = get_lowbo(lp, colnr); varLo *= (my_infinite(lp, varLo) ? my_sign(pivot) : pivot); varHi = get_upbo(lp, colnr); varHi *= (my_infinite(lp, varHi) ? my_sign(pivot) : pivot); if(pivot < 0) swapREAL(&varHi, &varLo); signflip = my_infinite(lp, varLo); } if(signflip) { mat_multrow(mat, rownr, -1); RHS -= conRange; RHS = -RHS; lp->orig_rhs[rownr] = RHS; pivot = -pivot; dual = -dual; if(!isfree) { varLo = -varLo; varHi = -varHi; swapREAL(&varHi, &varLo); } } matValue = RHS/pivot; /* Prepare for deleting free or implied free variable in inequality constraint. Different strategies need to be used: ACTUAL: Find the proper constraint bound and store undo information for recovering the value of the implied free variable. The constraint is then deleted. We have to adjust the objective function if the OF coefficient for the implied free variable is non-zero. IMPLIED: Convert the constraint to an inequality at the proper bound. For given models, the new equality constraint can later provide an implied slack, which means that a further variable is eliminated, and the constraint again becomes an inequality constraint. Note that this version only implements the ACTUAL mode */ if(isfree) { /* Add undo information connecting the deleted variable to the RHS */ if(matValue != 0) undoadded = addUndoPresolve(lp, TRUE, colnr, matValue, 0.0, 0); /* Add undo information for the dual of the deleted constraint */ if(dual != 0) addUndoPresolve(lp, FALSE, rownr, dual, 0.0, 0); } /* Prepare for deleting implied slack variable. The following two cases are handled: 1. Equality constraint: Convert the constraint to an inequality constraint that is possibly ranged 2. Other constraints: Expand existing slack variable / constraint range, if required. */ else { if(my_infinite(lp, varHi)) varRange = lp->infinite; #ifdef Paranoia else if(my_infinite(lp, varLo)) { report(lp, SEVERE, "presolve_impliedcolfix: Negative infinite limit for variable %d\n", colnr); varRange = lp->infinite; } #endif else varRange = my_precision(fabs(varHi - varLo) + lp->epsvalue, psdata->epsvalue); presolve_adjustrhs(psdata, rownr, varLo, psdata->epsvalue); /* Handle case 1 of an equality constraint */ if(is_constr_type(lp, rownr, EQ)) { /* Make sure we actually have a ranged constraint */ if(varRange > 0) { set_constr_type(lp, rownr, LE); if(!my_infinite(lp, varRange)) lp->orig_upbo[rownr] = varRange; setLink(psdata->LTmap, rownr); removeLink(psdata->EQmap, rownr); } } /* Handle case 2 of an inequality constraint (UNDER CONSTRUCTION!)*/ else { if(!my_infinite(lp, lp->orig_upbo[rownr])) { if(my_infinite(lp, varRange)) lp->orig_upbo[rownr] = lp->infinite; else lp->orig_upbo[rownr] += varHi - varLo; } } /* Update counts */ if(matAij > 0) psdata->rows->plucount[rownr]--; else psdata->rows->negcount[rownr]--; if(my_sign(varLo) != my_sign(varHi)) psdata->rows->pluneg[rownr]--; /* Add undo information for the deleted variable; note that we cannot link the deleted variable to the slack, since it may not be available during undo. We really should have a mini LP to compute this allocation ex-post. */ if(RHS != 0) undoadded = addUndoPresolve(lp, TRUE, colnr, RHS/pivot, 0.0, 0); } } /* Update the OF constant */ if(dual != 0) { presolve_adjustrhs(psdata, 0, dual * RHS, 0); /* lp->orig_rhs[0] -= dual * RHS; */ vecOF[colnr] = 0; } /* Do affine transformation with the constraint row */ i = 0; for(ib = presolve_nextcol(psdata, rownr, &i); ib >= 0; ib = presolve_nextcol(psdata, rownr, &i)) { /* Get the constraint element */ jx = ROW_MAT_COLNR(ib); if(jx == colnr) continue; matValue = ROW_MAT_VALUE(ib); /* Adjust OF for the variable to be deleted */ if(dual != 0) vecOF[jx] -= dual * matValue; /* Add reconstruction/undo parameters for the deleted variable */ if(!undoadded) undoadded = addUndoPresolve(lp, TRUE, colnr, 0.0, matValue/pivot, jx); else appendUndoPresolve(lp, TRUE, matValue/pivot, jx); } return( TRUE ); } STATIC psrec *presolve_initpsrec(lprec *lp, int size) { psrec *ps = (psrec *) calloc(1, sizeof(*ps)); createLink(size, &ps->varmap, NULL); fillLink(ps->varmap); size++; allocINT(lp, &ps->empty, size, FALSE); ps->empty[0] = 0; allocREAL(lp, &ps->pluupper, size, FALSE); allocREAL(lp, &ps->negupper, size, FALSE); allocREAL(lp, &ps->plulower, size, FALSE); allocREAL(lp, &ps->neglower, size, FALSE); allocINT(lp, &ps->infcount, size, FALSE); ps->next = (int **) calloc(size, sizeof(*(ps->next))); allocINT(lp, &ps->plucount, size, TRUE); allocINT(lp, &ps->negcount, size, TRUE); allocINT(lp, &ps->pluneg, size, TRUE); ps->allocsize = size; return( ps ); } STATIC void presolve_freepsrec(psrec **ps) { FREE((*ps)->plucount); FREE((*ps)->negcount); FREE((*ps)->pluneg); FREE((*ps)->infcount); if((*ps)->next != NULL) { int i, n = (*ps)->allocsize; for(i = 0; i < n; i++) FREE((*ps)->next[i]); FREE((*ps)->next); } FREE((*ps)->plulower); FREE((*ps)->neglower); FREE((*ps)->pluupper); FREE((*ps)->negupper); FREE((*ps)->empty); freeLink(&(*ps)->varmap); FREE(*ps); } STATIC presolverec *presolve_init(lprec *lp) { int k, i, ix, ixx, colnr, ncols = lp->columns, nrows = lp->rows; REAL hold; MATrec *mat = lp->matA; presolverec *psdata = NULL; /* Optimize memory usage if we have a very large model; this is to reduce the risk of out-of-memory situations. */ ix = get_nonzeros(lp); ixx = lp->matA->mat_alloc; if((ixx - ix > MAT_START_SIZE) && ((ixx - ix) * 20 > ixx)) mat_memopt(lp->matA, nrows / 20, ncols / 20, ix / 20); psdata = (presolverec *) calloc(1, sizeof(*psdata)); psdata->lp = lp; psdata->rows = presolve_initpsrec(lp, nrows); psdata->cols = presolve_initpsrec(lp, ncols); psdata->epsvalue = PRESOLVE_EPSVALUE; psdata->epspivot = PRESOLVE_EPSPIVOT; psdata->forceupdate = TRUE; /* Save incoming primal bounds */ k = lp->sum + 1; allocREAL(lp, &psdata->pv_lobo, k, FALSE); MEMCOPY(psdata->pv_lobo, lp->orig_lowbo, k); allocREAL(lp, &psdata->pv_upbo, k, FALSE); MEMCOPY(psdata->pv_upbo, lp->orig_upbo, k); /* Create and initialize dual value (Langrangean and slack) limits */ allocREAL(lp, &psdata->dv_lobo, k, FALSE); allocREAL(lp, &psdata->dv_upbo, k, FALSE); for(i = 0; i <= nrows; i++) { psdata->dv_lobo[i] = (is_constr_type(lp, i, EQ) ? -lp->infinite : 0); psdata->dv_upbo[i] = lp->infinite; } k--; for(; i <= k; i++) { psdata->dv_lobo[i] = 0; psdata->dv_upbo[i] = lp->infinite; } /* Create NZ count and sign arrays, and do general initialization of row bounds */ createLink(nrows, &psdata->EQmap, NULL); createLink(nrows, &psdata->LTmap, NULL); createLink(nrows, &psdata->INTmap, NULL); for(i = 1; i <= nrows; i++) { switch (get_constr_type(lp, i)) { case LE: appendLink(psdata->LTmap, i); break; case EQ: appendLink(psdata->EQmap, i); break; } k = mat_rowlength(mat, i); if((lp->int_vars > 0) && (k > 0)) appendLink(psdata->INTmap, i); } /* Seek to reduce set of sum(INT*INT) rows (mainly for GCD coefficient reductions) */ if(psdata->INTmap->count > 0) for(i = 1; i <= nrows; i++) { if(!isActiveLink(psdata->INTmap, i)) continue; /* Disqualify if there is a non-int variable, otherwise find smallest absolute fractional row value */ ix = mat->row_end[i - 1]; ixx = mat->row_end[i]; colnr = 0; for(; ix < ixx; ix++) { if(!is_int(lp, ROW_MAT_COLNR(ix))) { removeLink(psdata->INTmap, i); break; } hold = fabs(ROW_MAT_VALUE(ix)); hold = fmod(hold, 1); /* Adjust colnr to be a decimal scalar */ for(k = 0; (k <= MAX_FRACSCALE) && (hold+psdata->epsvalue < 1); k++) hold *= 10; if(k > MAX_FRACSCALE) { removeLink(psdata->INTmap, i); break; } SETMAX(colnr, k); } if(!isActiveLink(psdata->INTmap, i)) continue; hold = pow(10.0, colnr); /* Also disqualify if the RHS is fractional after scaling */ if(fabs(fmod(lp->orig_rhs[i] * hold, 1)) > psdata->epsvalue) { removeLink(psdata->INTmap, i); continue; } /* We have an all-int constraint, see if we should scale it up */ if(k > 0) { ix = mat->row_end[i - 1]; for(; ix < ixx; ix++) { ROW_MAT_VALUE(ix) *= hold; } lp->orig_rhs[i] *= hold; if(!my_infinite(lp, lp->orig_upbo[i])) lp->orig_upbo[i] *= hold; /* KE: Fix due to Andy Loto - 20070619 */ } } /* Do the real tallying and ordering work */ presolve_validate(psdata, TRUE); return( psdata ); } STATIC void presolve_free(presolverec **psdata) { presolve_freepsrec(&(*psdata)->rows); presolve_freepsrec(&(*psdata)->cols); FREE((*psdata)->dv_lobo); FREE((*psdata)->dv_upbo); FREE((*psdata)->pv_lobo); FREE((*psdata)->pv_upbo); freeLink(&(*psdata)->EQmap); freeLink(&(*psdata)->LTmap); freeLink(&(*psdata)->INTmap); FREE(*psdata); } STATIC int presolve_makefree(presolverec *psdata) { lprec *lp = psdata->lp; int i, ix, j, nn = 0; REAL Xlower, Xupper, losum, upsum, lorhs, uprhs, freeinf = lp->infinite / 10; MATrec *mat = lp->matA; LLrec *colLL = NULL; /* First see if we can relax ranged constraints */ for(i = firstActiveLink(psdata->rows->varmap); i != 0; i = nextActiveLink(psdata->rows->varmap, i)) { if(is_constr_type(lp, i, EQ)) continue; presolve_range(lp, i, psdata->rows, &losum, &upsum); lorhs = get_rh_lower(lp, i); uprhs = get_rh_upper(lp, i); /* Look for opportunity to relax constraint bounds */ if(presolve_rowlength(psdata, i) > 1) { if((is_constr_type(lp, i, GE) && (upsum <= uprhs)) || (is_constr_type(lp, i, LE) && (losum >= lorhs))) set_rh_range(lp, i, lp->infinite); } } /* Collect columns available for bound relaxation (find implied free variables) (consider sorting the list in decending order of column lengths or do call to COLAMD to maximize impact) */ createLink(lp->columns, &colLL, NULL); for(j = firstActiveLink(psdata->cols->varmap); j != 0; j = nextActiveLink(psdata->cols->varmap, j)) if(presolve_impliedfree(lp, psdata, j)) appendLink(colLL, j); /* Find what columns to relax (ideally one per row) */ if(colLL->count > 0) { LLrec *rowLL = NULL; MYBOOL canfree; /* Create row tracker */ createLink(lp->rows, &rowLL, NULL); fillLink(rowLL); /* Loop over all column candidates */ for(j = firstActiveLink(colLL); (j > 0) && (rowLL->count > 0); j = nextActiveLink(colLL, j)) { /* Verify that the variable is applicable */ canfree = TRUE; for(ix = mat->col_end[j-1]; canfree && (ix < mat->col_end[j]); ix++) canfree = isActiveLink(rowLL, COL_MAT_ROWNR(ix)); /* If so, then open the bounds and update the row availability mapper */ if(canfree) { nn++; Xlower = get_lowbo(lp, j); Xupper = get_upbo(lp, j); if(Xlower >= 0) set_bounds(lp, j, 0, freeinf); else if(Xupper <= 0) set_bounds(lp, j, -freeinf, 0); else /* set_bounds(lo, j, -freeinf, freeinf); */ set_unbounded(lp, j); for(ix = mat->col_end[j-1]; ix < mat->col_end[j]; ix++) removeLink(rowLL, COL_MAT_ROWNR(ix)); } } freeLink(&rowLL); } /* Free list and return */ freeLink(&colLL); return( nn ); } STATIC MYBOOL presolve_updatesums(presolverec *psdata) { lprec *lp = psdata->lp; int j; /* Initialize row accumulation arrays */ MEMCLEAR(psdata->rows->pluupper, lp->rows + 1); MEMCLEAR(psdata->rows->negupper, lp->rows + 1); MEMCLEAR(psdata->rows->plulower, lp->rows + 1); MEMCLEAR(psdata->rows->neglower, lp->rows + 1); MEMCLEAR(psdata->rows->infcount, lp->rows + 1); /* Loop over active columns */ for(j = firstActiveLink(psdata->cols->varmap); j != 0; j = nextActiveLink(psdata->cols->varmap, j)) { presolve_colfix(psdata, j, lp->infinite, FALSE, NULL); } #ifdef UseDualPresolve /* Initialize column accumulation arrays */ MEMCLEAR(psdata->cols->pluupper, lp->columns + 1); MEMCLEAR(psdata->cols->negupper, lp->columns + 1); MEMCLEAR(psdata->cols->plulower, lp->columns + 1); MEMCLEAR(psdata->cols->neglower, lp->columns + 1); MEMCLEAR(psdata->cols->infcount, lp->columns + 1); /* Loop over active rows */ for(j = firstActiveLink(psdata->rows->varmap); j != 0; j = nextActiveLink(psdata->rows->varmap, j)) { presolve_rowfix(psdata, j, lp->infinite, FALSE, NULL); } #endif return( TRUE ); } STATIC MYBOOL presolve_finalize(presolverec *psdata) { lprec *lp = psdata->lp; MYBOOL compactvars = FALSE; int ke, n; /* Save eliminated rows and columns for restoration purposes */ #ifdef SavePresolveEliminated psdata->deletedA = mat_extractmat(lp->matA, rowmap, colmap, TRUE); if(!mat_validate(psdata->deletedA)) report(lp, SEVERE, "presolve_finalize: Could not validate matrix with undo data\n"); #endif /* Check if OF columns are to be deleted */ lp->presolve_undo->OFcolsdeleted = FALSE; for(n = firstInactiveLink(psdata->cols->varmap); (n != 0) && !lp->presolve_undo->OFcolsdeleted; n = nextInactiveLink(psdata->cols->varmap, n)) lp->presolve_undo->OFcolsdeleted = (MYBOOL) (lp->orig_obj[n] != 0); /* Delete eliminated columns */ ke = lastInactiveLink(psdata->cols->varmap); n = countInactiveLink(psdata->cols->varmap); if((n > 0) && (ke > 0)) { del_columnex(lp, psdata->cols->varmap); mat_colcompact(lp->matA, lp->presolve_undo->orig_rows, lp->presolve_undo->orig_columns); compactvars = TRUE; } /* Delete eliminated rows */ ke = lastInactiveLink(psdata->rows->varmap); n = countInactiveLink(psdata->rows->varmap); if((n > 0) && (ke > 0)) { del_constraintex(lp, psdata->rows->varmap); mat_rowcompact(lp->matA, TRUE); compactvars = TRUE; } else if(psdata->nzdeleted > 0) mat_zerocompact(lp->matA); /* Do compacting and updating of variable maps */ if(compactvars) varmap_compact(lp, lp->presolve_undo->orig_rows, lp->presolve_undo->orig_columns); /* Reduce memory usage of postsolve matrices */ if(lp->presolve_undo->primalundo != NULL) mat_memopt(lp->presolve_undo->primalundo->tracker, 0, 0, 0); if(lp->presolve_undo->dualundo != NULL) mat_memopt(lp->presolve_undo->dualundo->tracker, 0, 0, 0); /* Round near-zero objective function coefficients and RHS values */ ke = lp->columns; for(n = 1; n <= ke; n++) my_roundzero(lp->orig_obj[n], lp->epsvalue); ke = lp->rows; for(n = 1; n <= ke; n++) my_roundzero(lp->orig_rhs[n], lp->epsvalue); /* Update the SOS sparse mapping */ if(SOS_count(lp) > 0) SOS_member_updatemap(lp->SOS); /* Validate matrix and reconstruct row indexation */ return(mat_validate(lp->matA)); } STATIC MYBOOL presolve_debugdump(lprec *lp, presolverec *psdata, char *filename, MYBOOL doappend) { FILE *output = stdout; int size; MYBOOL ok; ok = (MYBOOL) ((filename == NULL) || ((output = fopen(filename, my_if(doappend, "a", "w"))) != NULL)); if(!ok) return(ok); if((filename == NULL) && (lp->outstream != NULL)) output = lp->outstream; fprintf(output, "\nPRESOLVE - Status at loop %d:%d:%d\n", psdata->outerloops, psdata->middleloops, psdata->innerloops); fprintf(output, "Model size: %d rows (%d equalities, %d less than), %d columns\n", psdata->rows->varmap->count, psdata->EQmap->count, psdata->LTmap->count, psdata->cols->varmap->count); fprintf(output, "\nMAPPERS\n-------\n\n"); size = 1; blockWriteINT(output, "colmap", psdata->cols->varmap->map, 0, size*psdata->cols->varmap->size); blockWriteINT(output, "rowmap", psdata->rows->varmap->map, 0, size*psdata->rows->varmap->size); blockWriteINT(output, "EQmap", psdata->EQmap->map, 0, size*psdata->EQmap->size); blockWriteINT(output, "LTmap", psdata->LTmap->map, 0, size*psdata->LTmap->size); fprintf(output, "\nCOUNTS\n------\n\n"); blockWriteINT(output, "plucount", psdata->rows->plucount, 0, lp->rows); blockWriteINT(output, "negcount", psdata->rows->negcount, 0, lp->rows); blockWriteINT(output, "pluneg", psdata->rows->pluneg, 0, lp->rows); fprintf(output, "\nSUMS\n----\n\n"); blockWriteREAL(output, "pluupper", psdata->rows->pluupper, 0, lp->rows); blockWriteREAL(output, "negupper", psdata->rows->negupper, 0, lp->rows); blockWriteREAL(output, "plulower", psdata->rows->pluupper, 0, lp->rows); blockWriteREAL(output, "neglower", psdata->rows->negupper, 0, lp->rows); if(filename != NULL) fclose(output); return(ok); } int CMP_CALLMODEL compRedundant(const UNIONTYPE QSORTrec *current, const UNIONTYPE QSORTrec *candidate) { int start1 = (int) (current->int4.intpar1), start2 = (int) (candidate->int4.intpar1), result = CMP_COMPARE(start1, start2); if(result == 0) { start1 = (int) (current->int4.intpar2); start2 = (int) (candidate->int4.intpar2); result = -CMP_COMPARE(start1, start2); } return( result ); } int CMP_CALLMODEL compSparsity(const UNIONTYPE QSORTrec *current, const UNIONTYPE QSORTrec *candidate) { int start1 = (int) (current->int4.intpar1), start2 = (int) (candidate->int4.intpar1), result = CMP_COMPARE(start1, start2); if(result == 0) { start1 = (int) (current->int4.intpar2); start2 = (int) (candidate->int4.intpar2); result = -CMP_COMPARE(start1, start2); } if(result == 0) { start1 = (int) (current->int4.intval); start2 = (int) (candidate->int4.intval); result = CMP_COMPARE(start1, start2); } return( result ); } int CMP_CALLMODEL compAggregate(const UNIONTYPE QSORTrec *current, const UNIONTYPE QSORTrec *candidate) { int index1 = (int) (current->pvoidint2.intval), index2 = (int) (candidate->pvoidint2.intval); lprec *lp = (lprec *) current->pvoidint2.ptr; REAL value1 = lp->orig_obj[index1], value2 = lp->orig_obj[index2]; /* Smallest objective coefficient (largest contribution to OF) */ int result = CMP_COMPARE(value1, value2); /* Smallest lower variable bound */ if(result == 0) { index1 += lp->rows; index2 += lp->rows; value1 = lp->orig_lowbo[index1]; value2 = lp->orig_lowbo[index2]; result = CMP_COMPARE(value1, value2); } /* Largest upper variable bound */ if(result == 0) { value1 = lp->orig_upbo[index1]; value2 = lp->orig_upbo[index2]; result = -CMP_COMPARE(value1, value2); } return( result ); } STATIC int presolve_rowdominance(presolverec *psdata, int *nCoeffChanged, int *nRowsRemoved, int *nVarsFixed, int *nSum) { lprec *lp = psdata->lp; MATrec *mat = lp->matA; int i, ii, ib, ie, n, jb, je, jx, *coldel = NULL, status = RUNNING, item, iCoeffChanged = 0, iRowRemoved = 0, iVarFixed = 0; REAL ratio, *rowvalues = NULL; UNIONTYPE QSORTrec *QS = (UNIONTYPE QSORTrec *) calloc(lp->rows+1, sizeof(*QS)); /* Check if we were able to obtain working memory */ if(QS == NULL) return( status); /* A dominating row of variables always satisfy the following criteria: 1) The starting column position is never lower, but could be the same 2) The non-zero row count is always lower */ n = 0; for(i = firstActiveLink(psdata->EQmap); i != 0; i = nextActiveLink(psdata->EQmap, i)) { /* Make sure we have no SOS or semi-continuous variables */ jb = je = 0; if((SOS_count(lp) > 0) || (lp->sc_vars > 0)) { item = 0; for(jb = presolve_nextcol(psdata, i, &item); jb >= 0; jb = presolve_nextcol(psdata, i, &item)) { jx = ROW_MAT_COLNR(jb); if(SOS_is_member(lp->SOS, 0, jx) || is_semicont(lp, jx)) break; } } /* Add to list if we are Ok */ if(jb < 0) { QS[n].int4.intval = i; item = 0; ii = presolve_nextcol(psdata, i, &item); QS[n].int4.intpar1 = ROW_MAT_COLNR(ii); QS[n].int4.intpar2 = presolve_rowlength(psdata, i); n++; } } if(n <= 1) goto Finish; QS_execute(QS, n, (findCompare_func *) compRedundant, NULL); /* Let us start from the top of the list, going forward and looking for the longest possible dominating row */ if(!allocREAL(lp, &rowvalues, lp->columns + 1, TRUE) || !allocINT(lp, &coldel, lp->columns + 1, FALSE)) goto Finish; for(ib = 0; ib < n; ib++) { /* Get row and check if it was previously eliminated */ i = QS[ib].int4.intval; if(i < 0) continue; /* Load the non-zero row values */ item = 0; for(jb = presolve_nextcol(psdata, i, &item); jb >= 0; jb = presolve_nextcol(psdata, i, &item)) { jx = ROW_MAT_COLNR(jb); rowvalues[jx] = ROW_MAT_VALUE(jb); } for(ie = ib+1; ie < n; ie++) { /* Get row and check if it was previously eliminated */ ii = QS[ie].int4.intval; if(ii < 0) continue; #ifdef Paranoia if((QS[ib].int4.intpar1 > QS[ie].int4.intpar1) || ((QS[ib].int4.intpar1 == QS[ie].int4.intpar1) && (QS[ib].int4.intpar2 < QS[ie].int4.intpar2))) report(lp, SEVERE, "presolve_rowdominance: Invalid sorted row order\n"); #endif /* Loop over every row member to confirm that the candidate actually dominates in every position */ if((lp->orig_rhs[i] == 0) && (lp->orig_rhs[ii] == 0)) ratio = 0; else if((lp->orig_rhs[i] != 0) && (lp->orig_rhs[ii] != 0)) ratio = lp->orig_rhs[i] / lp->orig_rhs[ii]; else continue; item = 0; for(jb = presolve_nextcol(psdata, ii, &item); jb >= 0; jb = presolve_nextcol(psdata, ii, &item)) { jx = ROW_MAT_COLNR(jb); if(rowvalues[jx] == 0) break; if(ratio == 0) ratio = rowvalues[jx] / ROW_MAT_VALUE(jb); else if(fabs(rowvalues[jx] - ratio*ROW_MAT_VALUE(jb)) > psdata->epsvalue) break; } /* "We have contact" */ if(jb < 0) { int sign_1 = 0, sign_j = 0; /* Need to fix any superset columns, but require that they have equal signs */ coldel[0] = 0; item = 0; for(jb = presolve_nextcol(psdata, i, &item); jb >= 0; jb = presolve_nextcol(psdata, i, &item)) { jx = ROW_MAT_COLNR(jb); if(mat_findelm(mat, ii, jx) <= 0) { /* Cancel if we detect a free or "quasi-free" variable */ if((lp->orig_lowbo[lp->rows + jx] < 0) && (lp->orig_upbo[lp->rows + jx] > 0)) { coldel[0] = -1; break; } /* Ensure that we are feasible */ else if((lp->orig_lowbo[lp->rows + jx] > 0) || (lp->orig_upbo[lp->rows + jx] < 0)) { report(lp, DETAILED, "presolve_rowdominate: Column %s is infeasible due to conflict in rows %s and %s\n", get_col_name(lp, jx), get_row_name(lp, i), get_row_name(lp, ii)); coldel[0] = -1; break; } /* Check consistency / uniformity of signs */ sign_j = my_sign(ROW_MAT_VALUE(jb)); sign_j = my_chsign(is_negative(lp, jx), sign_j); if(coldel[0] == 0) { sign_1 = sign_j; coldel[++coldel[0]] = jx; } else if(sign_j == sign_1) { coldel[++coldel[0]] = jx; } else { coldel[0] = -1; break; } } } /* Force break / continuation if the superset columns were incompatible */ if(coldel[0] < 0) continue; /* Do the column fixing and deletion (check for infeasibility in the process) */ for(jb = 1; jb <= coldel[0]; jb++) { jx = coldel[jb]; if(!presolve_colfix(psdata, jx, 0, TRUE, &iVarFixed)) { status = presolve_setstatus(psdata, INFEASIBLE); goto Finish; } presolve_colremove(psdata, jx, TRUE); rowvalues[jx] = 0; } /* Then delete the row */ presolve_rowremove(psdata, ii, TRUE); iRowRemoved++; QS[ie].int4.intval = -ii; } } /* Clear the non-zero row values ahead of the next row candidate */ ie = mat->row_end[i-1]; ii = mat->row_end[i]; for(; ie < ii; ie++) rowvalues[ROW_MAT_COLNR(ie)] = 0; } Finish: FREE(QS); FREE(rowvalues); FREE(coldel); (*nCoeffChanged) += iCoeffChanged; (*nRowsRemoved) += iRowRemoved; (*nVarsFixed) += iVarFixed; (*nSum) += iCoeffChanged + iRowRemoved + iVarFixed; return( status ); } #if 0 STATIC int presolve_coldominance01(presolverec *psdata, int *nConRemoved, int *nVarsFixed, int *nSum) /* The current version of this routine eliminates binary variables that are dominated via set coverage or unit knapsack constraints */ { lprec *lp = psdata->lp; MATrec *mat = lp->matA; MYBOOL first; int i, ii, ib, ie, n, jb, je, jx, jj, item, item2, *coldel = NULL, status = RUNNING, iVarFixed = 0; REAL scale, rhsval, *colvalues = NULL; UNIONTYPE QSORTrec *QS = (UNIONTYPE QSORTrec *) calloc(lp->columns+1, sizeof(*QS)); /* Check if we were able to obtain working memory */ if(QS == NULL) return( status); if(lp->int_vars == 0) goto Finish; /* A column dominates another binary variable column with the following criteria: 1) The relative matrix non-zero entries are identical 2) The relative objective coefficient is worse than the other; if the OF coefficients are identical, we can delete an arbitrary variable */ n = 0; for(i = firstActiveLink(psdata->cols->varmap); i != 0; i = nextActiveLink(psdata->cols->varmap, i)) if(is_binary(lp, i) && !SOS_is_member(lp->SOS, 0, i)) { /* Make sure we have an all-binary, unit-coefficient row */ je = mat->col_end[i]; item = 0; for(jb = presolve_nextrow(psdata, i, &item); jb >= 0; jb = presolve_nextrow(psdata, i, &item)) { jx = COL_MAT_ROWNR(jb); if(COL_MAT_VALUE(jb) != 1) break; } /* Add to list if we are Ok */ if(jb < 0) { QS[n].int4.intval = i; item = 0; ii = presolve_nextrow(psdata, i, &item); QS[n].int4.intpar1 = COL_MAT_ROWNR(ii); ii = presolve_collength(psdata, i); QS[n].int4.intpar2 = ii; n++; } } if(n <= 1) { FREE(QS); return( status ); } QS_execute(QS, n, (findCompare_func *) compRedundant, NULL); /* Let us start from the top of the list, going forward and looking for the longest possible dominated column */ if(!allocREAL(lp, &colvalues, lp->rows + 1, TRUE) || !allocINT(lp, &coldel, lp->columns + 1, FALSE)) goto Finish; for(ib = 0; ib < n; ib++) { /* Get column and check if it was previously eliminated */ i = QS[ib].int4.intval; if(i < 0) continue; /* Load the non-zero column values */ item = 0; for(jb = presolve_nextrow(psdata, i, &item); jb >= 0; jb = presolve_nextrow(psdata, i, &item)) { jx = COL_MAT_ROWNR(jb); colvalues[jx] = COL_MAT_VALUE(jb); } coldel[0] = 0; for(ie = ib+1; ie < n; ie++) { /* Insist on identical column lengths (sort is decending in column lengths) */ ii = QS[ib].int4.intpar2 - QS[ie].int4.intpar2; if(ii != 0) break; /* Also insist on identical starting positions */ ii = QS[ib].int4.intpar1 - QS[ie].int4.intpar1; if(ii != 0) break; /* Get column and check if it was previously eliminated */ ii = QS[ie].int4.intval; if(ii < 0) continue; /* Also make sure that the variables have "compatible" bounds */ #if 1 if((fabs(my_reldiff(lp->orig_lowbo[lp->rows + i], lp->orig_lowbo[lp->rows + ii])) > psdata->epsvalue) || (fabs(my_reldiff(lp->orig_upbo[lp->rows + i], lp->orig_upbo[lp->rows + ii] )) > psdata->epsvalue)) continue; #endif #ifdef Paranoia if((QS[ib].int4.intpar1 > QS[ie].int4.intpar1) || ((QS[ib].int4.intpar1 == QS[ie].int4.intpar1) && (QS[ib].int4.intpar2 < QS[ie].int4.intpar2))) report(lp, SEVERE, "presolve_coldominance01: Invalid sorted column order\n"); #endif /* Loop over every column member to confirm that the candidate is relatively identical in every position */ first = TRUE; item = 0; item2 = 0; scale = 1; for(jb = presolve_nextrow(psdata, ii, &item), jj = presolve_nextrow(psdata, i, &item2); jb >= 0; jb = presolve_nextrow(psdata, ii, &item), jj = presolve_nextrow(psdata, i, &item2)) { jx = COL_MAT_ROWNR(jb); if(jx != COL_MAT_ROWNR(jj)) break; if(first) { first = !first; scale = colvalues[jx] / COL_MAT_VALUE(jb); } else { if(fabs(colvalues[jx] - scale * COL_MAT_VALUE(jb)) > psdata->epsvalue) break; } /* Also make sure we have a compatible RHS (since this version of the dominance logic only applies to "sets") */ rhsval = scale*lp->orig_rhs[jx] - 1.0; /* if((rhsval < 0) || (rhsval > 1 + psdata->epsvalue)) */ if(fabs(rhsval) > psdata->epsvalue) break; } /* "We have contact" */ if(jb < 0) { coldel[++coldel[0]] = ii; QS[ie].int4.intval = -ii; } } /* Find the dominant column and delete / fix the others; if there is a tie, simply delete the second candidate */ ii = i; for(jb = 1; jb <= coldel[0]; jb++) { jx = coldel[jb]; if(lp->orig_obj[jx] < lp->orig_obj[ii]) swapINT(&ii, &coldel[jb]); } for(jb = 1; jb <= coldel[0]; jb++) { jx = coldel[jb]; if(!presolve_colfix(psdata, jx, lp->orig_lowbo[lp->rows+jx], TRUE, &iVarFixed)) { status = presolve_setstatus(psdata, INFEASIBLE); goto Finish; } presolve_colremove(psdata, jx, TRUE); } /* Clear the non-zero row values ahead of the next row candidate */ if(ib + 1 < n) { ie = mat->col_end[i-1]; ii = mat->col_end[i]; for(; ie < ii; ie++) colvalues[COL_MAT_ROWNR(ie)] = 0; } } Finish: FREE(QS); FREE(colvalues); FREE(coldel); (*nVarsFixed) += iVarFixed; (*nSum) += iVarFixed; return( status ); } #else /* DEVELOPMENT/TEST CODE FOR POSSIBLE REPLACEMENT OF SIMILAR FUNCTION IN lp_presolve.c */ #define NATURAL int STATIC int presolve_coldominance01(presolverec *psdata, NATURAL *nConRemoved, NATURAL *nVarsFixed, NATURAL *nSum) /* The current version of this routine eliminates binary variables that are dominated via set coverage or unit knapsack constraints */ { lprec *lp = psdata->lp; MATrec *mat = lp->matA; NATURAL i, ib, ie, jx, item, item2, n = lp->int_vars, iVarFixed = 0, nrows = lp->rows, *coldel = NULL; int jb, jj, ii, status = RUNNING; REAL rhsval = 0.0, *colvalues = NULL, *colobj = NULL; LLrec *sets = NULL; UNIONTYPE QSORTrec *QS = (UNIONTYPE QSORTrec *) calloc(n+1, sizeof(*QS)); /* Check if we were able to obtain working memory */ if(QS == NULL) return( status); if(n == 0) goto Finish; /* Create list of set coverage and knapsack constraints */ createLink(nrows, &sets, NULL); for(i = firstActiveLink(psdata->rows->varmap); i != 0; i = nextActiveLink(psdata->rows->varmap, i)) { if((lp->orig_rhs[i] < 0) || (psdata->rows->negcount[i] > 0)) continue; item = 0; for(jb = presolve_nextcol(psdata, i, &item); jb >= 0; jb = presolve_nextcol(psdata, i, &item)) { jx = ROW_MAT_COLNR(jb); if(!is_binary(lp, jx)) break; rhsval = ROW_MAT_VALUE(jb) - 1; if(fabs(rhsval) > lp->epsvalue) break; } if(jb < 0) setLink(sets, i); } if(countActiveLink(sets) == 0) goto Finish; /* A column dominates another binary variable column with the following criteria: 1) The relative matrix non-zero entries are identical 2) The relative objective coefficient is worse than the other; if the OF coefficients are identical, we can delete an arbitrary variable */ n = 0; for(i = firstActiveLink(psdata->cols->varmap); i != 0; i = nextActiveLink(psdata->cols->varmap, i)) if(is_binary(lp, i) && !SOS_is_member(lp->SOS, 0, i)) { /* Make sure the column is member of at least one set */ item = 0; for(jb = presolve_nextrow(psdata, i, &item); jb >= 0; jb = presolve_nextrow(psdata, i, &item)) { jx = COL_MAT_ROWNR(jb); if(isActiveLink(sets, jx)) break; } /* Add to list if set membership test is Ok */ if(jb >= 0) { QS[n].int4.intval = i; item = 0; ii = presolve_nextrow(psdata, i, &item); QS[n].int4.intpar1 = COL_MAT_ROWNR(ii); ii = presolve_collength(psdata, i); QS[n].int4.intpar2 = ii; n++; } } if(n <= 1) { FREE(QS); return( status ); } QS_execute(QS, n, (findCompare_func *) compRedundant, NULL); /* Let us start from the top of the list, going forward and looking for the longest possible dominated column */ if(!allocREAL(lp, &colvalues, nrows + 1, TRUE) || !allocREAL(lp, &colobj, n + 1, FALSE) || !allocINT(lp, &coldel, n + 1, FALSE)) goto Finish; for(ib = 0; ib < n; ib++) { /* Get column and check if it was previously eliminated */ i = QS[ib].int4.intval; if(!isActiveLink(psdata->cols->varmap, i)) continue; /* Load the non-zero column values */ item = 0; for(jb = presolve_nextrow(psdata, i, &item); jb >= 0; jb = presolve_nextrow(psdata, i, &item)) { jx = COL_MAT_ROWNR(jb); colvalues[jx] = COL_MAT_VALUE(jb); } /* Store data for current column */ coldel[0] = 1; coldel[1] = i; colobj[1] = lp->orig_obj[i]; /* Loop over all other columns to see if they have equal constraint coefficients */ for(ie = ib+1; ie < n; ie++) { /* Check if this column was previously eliminated */ ii = QS[ie].int4.intval; if(!isActiveLink(psdata->cols->varmap, ii)) continue; /* Insist on identical column lengths (sort is decending in column lengths) */ ii = QS[ib].int4.intpar2 - QS[ie].int4.intpar2; if(ii != 0) break; /* Also insist on identical starting positions */ ii = QS[ib].int4.intpar1 - QS[ie].int4.intpar1; if(ii != 0) break; /* Get column and check if it was previously eliminated */ ii = QS[ie].int4.intval; #ifdef Paranoia if((QS[ib].int4.intpar1 > QS[ie].int4.intpar1) || ((QS[ib].int4.intpar1 == QS[ie].int4.intpar1) && (QS[ib].int4.intpar2 < QS[ie].int4.intpar2))) report(lp, SEVERE, "presolve_coldominance01: Invalid sorted column order\n"); #endif /* Loop over every column member to confirm that the candidate is identical in every row; we also compute the minimal set order */ rhsval = lp->infinite; item = 0; item2 = 0; for(jb = presolve_nextrow(psdata, ii, &item), jj = presolve_nextrow(psdata, i, &item2); jb >= 0; jb = presolve_nextrow(psdata, ii, &item), jj = presolve_nextrow(psdata, i, &item2)) { jx = COL_MAT_ROWNR(jb); if(jx != COL_MAT_ROWNR(jj)) break; if(isActiveLink(sets, jx)) SETMIN(rhsval, lp->orig_rhs[jx]); } /* "We have contact" */ if(jb < 0) { coldel[++coldel[0]] = ii; colobj[coldel[0]] = lp->orig_obj[ii]; } } /* Find the dominant columns, fix and delete the others */ if(coldel[0] > 1) { qsortex(colobj+1, coldel[0], 0, sizeof(*colobj), FALSE, compareREAL, coldel+1, sizeof(*coldel)); /* if(rhsval+lp->epsvalue < lp->infinite) { */ jb = (NATURAL) (rhsval+lp->epsvalue); /* printf("%f / %d\n", rhsval, jb); */ for(jb++; jb <= coldel[0]; jb++) { jx = coldel[jb]; if(!presolve_colfix(psdata, jx, lp->orig_lowbo[nrows+jx], TRUE, &iVarFixed)) { status = presolve_setstatus(psdata, INFEASIBLE); goto Finish; } presolve_colremove(psdata, jx, TRUE); } /*} */ } /* Clear the non-zero row values ahead of the next row candidate */ if(ib + 1 < n) { ie = mat->col_end[i-1]; ii = mat->col_end[i]; for(; ie < ii; ie++) colvalues[COL_MAT_ROWNR(ie)] = 0; } } Finish: freeLink(&sets); FREE(QS); FREE(colvalues); FREE(coldel); FREE(colobj); (*nVarsFixed) += iVarFixed; (*nSum) += iVarFixed; return( status ); } #endif STATIC int presolve_aggregate(presolverec *psdata, int *nConRemoved, int *nVarsFixed, int *nSum) /* This routine combines compatible or identical columns */ { lprec *lp = psdata->lp; MATrec *mat = lp->matA; MYBOOL first; int i, ii, ib, ie, ix, n, jb, je, jx, jj, item, item2, *coldel = NULL, status = RUNNING, iVarFixed = 0; REAL scale, *colvalues = NULL; UNIONTYPE QSORTrec *QScand = (UNIONTYPE QSORTrec *) calloc(lp->columns+1, sizeof(*QScand)); /* Check if we were able to obtain working memory */ if(QScand == NULL) return( status); /* Obtain the list of qualifying columns to be sorted */ n = 0; for(i = firstActiveLink(psdata->cols->varmap); i != 0; i = nextActiveLink(psdata->cols->varmap, i)) if(!is_semicont(lp, i) && !SOS_is_member(lp->SOS, 0, i)) { QScand[n].int4.intval = i; item = 0; ii = presolve_nextrow(psdata, i, &item); QScand[n].int4.intpar1 = COL_MAT_ROWNR(ii); ii = presolve_collength(psdata, i); QScand[n].int4.intpar2 = ii; n++; } if(n <= 1) { FREE(QScand); return( status ); } QS_execute(QScand, n, (findCompare_func *) compRedundant, NULL); /* Let us start from the top of the list, going forward and looking for the longest possible identical column */ if(!allocREAL(lp, &colvalues, lp->rows + 1, TRUE) || !allocINT(lp, &coldel, lp->columns + 1, FALSE)) goto Finish; for(ib = 0; ib < n; ib++) { /* Get column and check if it was previously eliminated */ i = QScand[ib].int4.intval; if(i < 0) continue; /* Load the non-zero column values of this active/reference column */ item = 0; for(jb = presolve_nextrow(psdata, i, &item); jb >= 0; jb = presolve_nextrow(psdata, i, &item)) { jx = COL_MAT_ROWNR(jb); colvalues[jx] = COL_MAT_VALUE(jb); } coldel[0] = 0; for(ie = ib+1; ie < n; ie++) { /* Insist on identical column lengths (sort is decending in column lengths) */ ii = QScand[ib].int4.intpar2 - QScand[ie].int4.intpar2; if(ii != 0) break; /* Also insist on identical starting positions */ ii = QScand[ib].int4.intpar1 - QScand[ie].int4.intpar1; if(ii != 0) break; /* Get column and check if it was previously eliminated */ ii = QScand[ie].int4.intval; if(ii < 0) continue; /* Loop over every column member to confirm that the candidate is relatively identical in every position */ first = TRUE; item = 0; item2 = 0; scale = 1; for(jb = presolve_nextrow(psdata, ii, &item), jj = presolve_nextrow(psdata, i, &item2); jb >= 0; jb = presolve_nextrow(psdata, ii, &item), jj = presolve_nextrow(psdata, i, &item2)) { jx = COL_MAT_ROWNR(jb); if(jx != COL_MAT_ROWNR(jj)) break; if(first) { first = !first; scale = colvalues[jx] / COL_MAT_VALUE(jb); } else { if(fabs(colvalues[jx] - scale * COL_MAT_VALUE(jb)) > psdata->epsvalue) break; } } /* "We have contact", store the column in the aggregation list */ if(jb < 0) { coldel[++coldel[0]] = ii; QScand[ie].int4.intval = -ii; } } /* Sort the aggregation list if we have aggregation candidates */ if(coldel[0] > 1) { REAL of, ofelim, fixvalue; MYBOOL isint; UNIONTYPE QSORTrec *QSagg = (UNIONTYPE QSORTrec *) calloc(coldel[0], sizeof(*QSagg)); for(jb = 1; jb <= coldel[0]; jb++) { ii = jb - 1; QSagg[ii].pvoidint2.intval = coldel[jb]; QSagg[ii].pvoidint2.ptr = (void *) lp; } QS_execute(QSagg, coldel[0], (findCompare_func *) compAggregate, NULL); /* Process columns with identical OF coefficients */ jb = 0; while((status == RUNNING) && (jb < coldel[0])) { ii = QSagg[jb].pvoidint2.intval; of = lp->orig_obj[ii]; isint = is_int(lp, ii); je = jb + 1; while((status == RUNNING) && (je < coldel[0]) && (fabs(lp->orig_obj[ix = QSagg[je].pvoidint2.intval] - of) < psdata->epsvalue)) { /* We now have two columns with equal OFs; the following cases are possible: 1) The first column has Inf upper bound, which means that it can "absorb" compatible columns, which are then fixed at the appropriate bounds (or zero in case of free variables). 2) The first column has a -Inf lower bound, and further columns are Inf upper bounds, which means steps towards forming a free variable can be made. 3) The first column is a non-Inf upper bound, in which case the bounds are summed into a helper variable and the variable simply deleted. The deleted variables' value are allocated/distributed via a simple linear programming routine at postsolve. In the current version of this code, we only handle case 1. */ if(is_int(lp, ix) == isint) { ofelim = lp->orig_obj[ix]; if(of == 0) scale = 1; else scale = ofelim / of; if(my_infinite(lp, lp->orig_upbo[lp->rows+ii])) { /* Case 1 (recipe.mps) */ if(is_unbounded(lp, ix)) fixvalue = 0; else if(ofelim < 0) fixvalue = lp->orig_upbo[lp->rows+ix]; else fixvalue = lp->orig_lowbo[lp->rows+ix]; if(my_infinite(lp, fixvalue)) status = presolve_setstatus(psdata, UNBOUNDED); else if(!presolve_colfix(psdata, ix, fixvalue, TRUE, &iVarFixed)) status = presolve_setstatus(psdata, INFEASIBLE); else presolve_colremove(psdata, ix, TRUE); } else if(my_infinite(lp, lp->orig_lowbo[lp->rows+ii])) { /* Case 2 */ /* Do nothing */ } else { /* Case 3 */ #if 0 /* Do nothing */ #else if(ofelim >= 0) { fixvalue = lp->orig_lowbo[lp->rows+ix]; lp->orig_upbo[lp->rows+ii] += scale * (lp->orig_upbo[lp->rows+ix] - fixvalue); } else { fixvalue = lp->orig_upbo[lp->rows+ix]; lp->orig_upbo[lp->rows+ii] -= scale * (fixvalue - lp->orig_lowbo[lp->rows+ix]); } if(my_infinite(lp, fixvalue)) status = presolve_setstatus(psdata, UNBOUNDED); else if(!presolve_colfix(psdata, ix, fixvalue, TRUE, &iVarFixed)) status = presolve_setstatus(psdata, INFEASIBLE); else presolve_colremove(psdata, ix, TRUE); #ifdef xxParanoia if(presolve_rowlengthdebug(psdata) > 0) report(lp, SEVERE, "presolve_aggregate: Invalid row count\n"); #endif psdata->forceupdate = TRUE; #endif } } je++; } jb = je; } FREE(QSagg); } /* Clear the non-zero row values ahead of the next row candidate */ if(ib + 1 < n) { ie = mat->col_end[i-1]; ii = mat->col_end[i]; for(; ie < ii; ie++) colvalues[COL_MAT_ROWNR(ie)] = 0; } } Finish: FREE(QScand); FREE(colvalues); FREE(coldel); (*nVarsFixed) += iVarFixed; (*nSum) += iVarFixed; return( status ); } STATIC int presolve_makesparser(presolverec *psdata, int *nCoeffChanged, int *nConRemove, int *nVarFixed, int *nSum) { lprec *lp = psdata->lp; MATrec *mat = lp->matA; MYBOOL chsign; int i, ii, ib, ix, k, n, jb, je, jl, jjb, jje, jjl, jx, jjx, item, itemEQ, *nzidx = NULL, status = RUNNING, iObjChanged = 0, iCoeffChanged = 0, iConRemove = 0; REAL test, ratio, value, valueEQ, *valptr; LLrec *EQlist = NULL; UNIONTYPE QSORTrec *QS = (UNIONTYPE QSORTrec *) calloc(lp->rows, sizeof(*QS)); /* Check if we were able to obtain working memory */ if((QS == NULL) || (psdata->rows->varmap->count == 0) || (psdata->EQmap->count == 0)) return( status); /* Sort rows in 1) increasing order of start index, 2) decreasing length, and 3) non-equalities (i.e. equalities last) */ n = 0; for(i = firstActiveLink(psdata->rows->varmap); i != 0; i = nextActiveLink(psdata->rows->varmap, i)) { k = presolve_rowlength(psdata, i); if(k >= 2) { item = 0; ii = presolve_nextcol(psdata, i, &item); #ifdef Paranoia if((ii < 0) || (item == 0)) { report(lp, SEVERE, "presolve_makesparser: Unexpected zero-length row %d\n", i); continue; } #endif QS[n].int4.intval = my_chsign(is_constr_type(lp, i, EQ), i); QS[n].int4.intpar1 = ROW_MAT_COLNR(ii); QS[n].int4.intpar2 = k; n++; } } if(n <= 1) { FREE(QS); return( status ); } QS_execute(QS, n, (findCompare_func *) compSparsity, NULL); /* Create associated sorted map of indeces to equality constraints; note that we need to have a unit offset for compatibility. */ allocINT(lp, &nzidx, lp->columns + 1, FALSE); createLink(lp->rows, &EQlist, NULL); for(ib = 0; ib < n; ib++) { i = QS[ib].int4.intval; if(i < 0) appendLink(EQlist, ib + 1); } /* Loop over all equality masks */ for(ix = firstActiveLink(EQlist); ix != 0; ) { /* Get row starting and ending positions of the mask */ ii = abs(QS[ix-1].int4.intval); jjb = QS[ix-1].int4.intpar1; jje = presolve_lastcol(psdata, ii); jje = ROW_MAT_COLNR(jje); jjl = QS[ix-1].int4.intpar2; /* Scan the OF */ i = 0; chsign = is_chsign(lp, i); test = ratio = 0.0; itemEQ = 0; nzidx[0] = 0; while(((jjx = presolve_nextcol(psdata, ii, &itemEQ)) >= 0) && /*(itemEQ > 0) && */ (fabs(test-ratio) < psdata->epsvalue)) { valueEQ = ROW_MAT_VALUE(jjx); if(valueEQ == 0) continue; k = ROW_MAT_COLNR(jjx); value = lp->orig_obj[k]; if(fabs(value) < psdata->epsvalue) break; if(ratio == 0.0) { test = ratio = value / valueEQ; } else test = value / valueEQ; /* Store nz index */ nzidx[++nzidx[0]] = k; } /* We were successful if the equality was completely traversed; we will then zero-out the OF coefficients and update the constant term. */ if((itemEQ == 0) && (nzidx[0] > 0) && (fabs(test-ratio) < psdata->epsvalue)) { for(k = 1; k <= nzidx[0]; k++) { /* We should add recovery data for the zero'ed coefficient here */ jx = nzidx[k]; value = lp->orig_obj[jx]; lp->orig_obj[jx] = 0.0; /* Update counts */ value = my_chsign(chsign, value); if(value < 0) { psdata->rows->negcount[i]--; psdata->cols->negcount[jx]--; } else { psdata->rows->plucount[i]--; psdata->cols->plucount[jx]--; } iObjChanged++; } value = ratio * lp->orig_rhs[ii]; presolve_adjustrhs(psdata, i, value, psdata->epsvalue); } /* Scan for compatible constraints that can be masked for sparsity elimination */ for(ib = 1; ib < ix; ib++) { /* Get row starting and ending positions of the target constraint */ i = abs(QS[ib-1].int4.intval); jb = QS[ib-1].int4.intpar1; je = presolve_lastcol(psdata, i); je = ROW_MAT_COLNR(je); jl = QS[ib-1].int4.intpar2; /* Check if there is a window mismatch */ if((jjb < jb) || (jje > je) || (jjl > jl)) goto NextEQ; /* We have a window match; now check if there is a (scalar) member-by-member match as well. We approach this in the following manner: 1) Get first (or next) member of active equality 2) Loop to matching member in the target constraint, but abandon if no match 3) Set ratio if this is the first match, otherwise compare ratio and abandon on mismatch 4) Go to 1) of there are more elements in the active equality 5) Proceed to do sparsity elimination if we were successful. */ chsign = is_chsign(lp, i); test = ratio = 0.0; itemEQ = 0; item = 0; nzidx[0] = 0; while(((jjx = presolve_nextcol(psdata, ii, &itemEQ)) >= 0) && /*(itemEQ > 0) &&*/ (fabs(test-ratio) < psdata->epsvalue)) { valueEQ = ROW_MAT_VALUE(jjx); if(valueEQ == 0) continue; jx = 0; jjx = ROW_MAT_COLNR(jjx); for(k = presolve_nextcol(psdata, i, &item); (jx < jjx) && (item > 0); k = presolve_nextcol(psdata, i, &item)) { jx = ROW_MAT_COLNR(k); /* Do we have a column index match? */ if(jx == jjx) { value = ROW_MAT_VALUE(k); /* Abandon if we have a zero value */ if(value == 0) goto NextEQ; if(ratio == 0.0) { test = ratio = value / valueEQ; } else test = value / valueEQ; /* Store nz index */ nzidx[++nzidx[0]] = k; break; } /* Give up matching if there is overshooting */ else if(jx > jjx) goto NextEQ; } } /* We were successful if the equality was completely traversed */ if((itemEQ == 0) && (nzidx[0] > 0) && (fabs(test-ratio) < psdata->epsvalue)) { /* Check if we have found parametrically indentical constraints */ if(presolve_rowlength(psdata, i) == presolve_rowlength(psdata,ii)) { value = lp->orig_rhs[i]; valueEQ = lp->orig_rhs[ii]; /* Are they both equalities? */ if(is_constr_type(lp, i, EQ)) { /* Determine applicable ratio for the RHS */ if(fabs(valueEQ) < psdata->epsvalue) { if(fabs(value) < psdata->epsvalue) test = ratio; else test = lp->infinite; } else test = value / valueEQ; /* Check for infeasibility */ if(fabs(test-ratio) > psdata->epsvalue) { report(lp, NORMAL, "presolve_sparser: Infeasibility of relatively equal constraints %d and %d\n", i, ii); status = presolve_setstatus(psdata, INFEASIBLE); goto Finish; } /* Otherwise we can delete a redundant constraint */ else { removeLink(EQlist, i); presolve_rowremove(psdata, i, TRUE); MEMCOPY(&QS[ib-1], &QS[ib], n-ib); n--; iConRemove++; } } /* ... if not, then delete the inequality, since the equality dominates */ else { /* First verify feasibility of the RHS */ if((value+psdata->epsvalue < valueEQ) || (value-get_rh_range(lp, i)-psdata->epsvalue > valueEQ)) { report(lp, NORMAL, "presolve_sparser: Infeasibility of relatively equal RHS values for %d and %d\n", i, ii); status = presolve_setstatus(psdata, INFEASIBLE); goto Finish; } presolve_rowremove(psdata, i, TRUE); MEMCOPY(&QS[ib-1], &QS[ib], n-ib); n--; iConRemove++; } } /* Otherwise zero-out the target constraint coefficients and update the RHS */ else { for(k = 1; k <= nzidx[0]; k++) { /* We should add recovery data for the zero'ed coefficient here */ jjx = nzidx[k]; jx = ROW_MAT_COLNR(jjx); valptr = &ROW_MAT_VALUE(jjx); value = *valptr; *valptr = 0.0; /* Update counts */ value = my_chsign(chsign, value); if(value < 0) { psdata->rows->negcount[i]--; psdata->cols->negcount[jx]--; } else { psdata->rows->plucount[i]--; psdata->cols->plucount[jx]--; } iCoeffChanged++; } value = ratio * lp->orig_rhs[ii]; presolve_adjustrhs(psdata, i, value, psdata->epsvalue); } } } /* Get next equality index */ NextEQ: ix = nextActiveLink(EQlist, ix); } Finish: FREE(QS); freeLink(&EQlist); FREE(nzidx); /* Let us condense the matrix if we modified the constraint matrix */ if(iCoeffChanged > 0) { mat->row_end_valid = FALSE; mat_zerocompact(mat); presolve_validate(psdata, TRUE); #ifdef PresolveForceUpdateMax mat_computemax(mat /* , FALSE */); #endif psdata->forceupdate = TRUE; } (*nConRemove) += iConRemove; (*nCoeffChanged) += iCoeffChanged + iObjChanged; (*nSum) += iCoeffChanged + iObjChanged + iConRemove; return( status ); } STATIC int presolve_SOS1(presolverec *psdata, int *nCoeffChanged, int *nConRemove, int *nVarFixed, int *nSOS, int *nSum) { lprec *lp = psdata->lp; MYBOOL candelete, SOS_GUBactive = FALSE; int iCoeffChanged = 0, iConRemove = 0, iSOS = 0, i,ix,iix, j,jx,jjx, status = RUNNING; REAL Value1; MATrec *mat = lp->matA; for(i = lastActiveLink(psdata->rows->varmap); i > 0; ) { candelete = FALSE; Value1 = get_rh(lp, i); jx = get_constr_type(lp, i); if((Value1 == 1) && (presolve_rowlength(psdata, i) >= MIN_SOS1LENGTH) && ((SOS_GUBactive && (jx != GE)) || (!SOS_GUBactive && (jx == LE)))) { jjx = mat->row_end[i-1]; iix = mat->row_end[i]; for(; jjx < iix; jjx++) { j = ROW_MAT_COLNR(jjx); if(!isActiveLink(psdata->cols->varmap, j)) continue; if(!is_binary(lp, j) || (ROW_MAT_VALUE(jjx) != 1)) break; } if(jjx >= iix) { char SOSname[16]; /* Define a new SOS instance */ ix = SOS_count(lp) + 1; sprintf(SOSname, "SOS_%d", ix); ix = add_SOS(lp, SOSname, 1, ix, 0, NULL, NULL); if(jx == EQ) SOS_set_GUB(lp->SOS, ix, TRUE); Value1 = 0; jjx = mat->row_end[i-1]; for(; jjx < iix; jjx++) { j = ROW_MAT_COLNR(jjx); if(!isActiveLink(psdata->cols->varmap, j)) continue; Value1 += 1; append_SOSrec(lp->SOS->sos_list[ix-1], 1, &j, &Value1); } candelete = TRUE; iSOS++; } } /* Get next row and do the deletion of the previous, if indicated */ ix = i; i = prevActiveLink(psdata->rows->varmap, i); if(candelete) { presolve_rowremove(psdata, ix, TRUE); iConRemove++; } } if(iSOS) report(lp, DETAILED, "presolve_SOS1: Converted %5d constraints to SOS1.\n", iSOS); clean_SOSgroup(lp->SOS, (MYBOOL) (iSOS > 0)); (*nCoeffChanged) += iCoeffChanged; (*nConRemove) += iConRemove; (*nSOS) += iSOS; (*nSum) += iCoeffChanged+iConRemove+iSOS; return( status ); } STATIC int presolve_boundconflict(presolverec *psdata, int baserowno, int colno) { REAL Value1, Value2; lprec *lp = psdata->lp; MATrec *mat = lp->matA; int ix, item = 0, status = RUNNING; if(baserowno <= 0) do { ix = presolve_nextrow(psdata, colno, &item); if(ix < 0) return( status ); baserowno = COL_MAT_ROWNR(ix); } while(presolve_rowlength(psdata, baserowno) != 1); Value1 = get_rh_upper(lp, baserowno), Value2 = get_rh_lower(lp, baserowno); if(presolve_singletonbounds(psdata, baserowno, colno, &Value2, &Value1, NULL)) { int iix; item = 0; for(ix = presolve_nextrow(psdata, colno, &item); ix >= 0; ix = presolve_nextrow(psdata, colno, &item)) { iix = COL_MAT_ROWNR(ix); if((iix != baserowno) && (presolve_rowlength(psdata, iix) == 1) && !presolve_altsingletonvalid(psdata, iix, colno, Value2, Value1)) { status = presolve_setstatus(psdata, INFEASIBLE); break; } } } else status = presolve_setstatus(psdata, INFEASIBLE); return( status ); } STATIC int presolve_columns(presolverec *psdata, int *nCoeffChanged, int *nConRemove, int *nVarFixed, int *nBoundTighten, int *nSum) { lprec *lp = psdata->lp; MYBOOL candelete, isOFNZ, unbounded, probefix = is_presolve(lp, PRESOLVE_PROBEFIX), #if 0 probereduce = is_presolve(lp, PRESOLVE_PROBEREDUCE), #endif colfixdual = is_presolve(lp, PRESOLVE_COLFIXDUAL); int iCoeffChanged = 0, iConRemove = 0, iVarFixed = 0, iBoundTighten = 0, status = RUNNING, ix, j, countNZ, item; REAL Value1; for(j = firstActiveLink(psdata->cols->varmap); (j != 0) && (status == RUNNING); ) { /* Don't presolve members SOS'es */ if(SOS_is_member(lp->SOS, 0, j)) { j = nextActiveLink(psdata->cols->varmap, j); continue; } /* Initialize */ countNZ = presolve_collength(psdata, j); isOFNZ = isnz_origobj(lp, j); Value1 = get_lowbo(lp, j); unbounded = is_unbounded(lp, j); /* Clear unnecessary semicont-definitions */ if((lp->sc_vars > 0) && (Value1 == 0) && is_semicont(lp, j)) set_semicont(lp, j, FALSE); candelete = FALSE; item = 0; ix = lp->rows + j; /* Check if the variable is unused */ if((countNZ == 0) && !isOFNZ) { if(Value1 != 0) report(lp, DETAILED, "presolve_columns: Eliminated unused variable %s\n", get_col_name(lp,j)); candelete = TRUE; } /* Check if the variable has a cost, but is not limited by constraints */ else if((countNZ == 0) && isOFNZ) { if(lp->orig_obj[j] < 0) Value1 = get_upbo(lp, j); if(fabs(Value1) >= lp->infinite) { report(lp, DETAILED, "presolve_columns: Unbounded variable %s\n", get_col_name(lp,j)); status = presolve_setstatus(psdata, UNBOUNDED); } else { /* Fix the value at its best bound */ report(lp, DETAILED, "presolve_columns: Eliminated trivial variable %s fixed at %g\n", get_col_name(lp,j), Value1); candelete = TRUE; } } /* Check if the variable can be eliminated because it is fixed */ else if(isOrigFixed(lp, ix)) { if(countNZ > 0) { status = presolve_boundconflict(psdata, -1, j); if(status != RUNNING) break; } report(lp, DETAILED, "presolve_columns: Eliminated variable %s fixed at %g\n", get_col_name(lp,j), Value1); candelete = TRUE; } #if 0 /* Merge OF-constraint column doubleton in equality constraint (if it has not been captured by the singleton free variable rule above) */ else if((countNZ == 1) && isOFNZ && ((i = presolve_nextrow(psdata, j, &item)) >= 0) && is_constr_type(lp, i = COL_MAT_ROWNR(i), EQ)) { MATrec *mat = lp->matA; /* Merge the constraint into the OF */ Value1 = lp->orig_obj[j] / get_mat(lp, i, j); for(jx = mat->row_end[i-1]; jx < mat->row_end[i]; jx++) { jjx = ROW_MAT_COLNR(jx); lp->orig_obj[jjx] -= Value1 * ROW_MAT_VALUE(jx); } Value2 = lp->orig_rhs[i]; presolve_adjustrhs(psdata, 0, Value1 * Value2, 0.0); /* Verify feasibility */ Value2 /= get_mat(lp, i, j); if((Value2 < get_lowbo(lp, j)) || (Value2 > get_upbo(lp, j))) { status = presolve_setstatus(psdata, INFEASIBLE); break; } /* Do column (and flag row) deletion */ presolve_rowremove(psdata, i, TRUE); psdata->forceupdate = TRUE; iConRemove++; candelete = TRUE; } #endif /* Look for opportunity to fix column based on the dual */ else if(colfixdual && presolve_colfixdual(psdata, j, &Value1, &status)) { if(my_infinite(lp, Value1)) { report(lp, DETAILED, "presolve_columns: Unbounded variable %s\n", get_col_name(lp,j)); status = presolve_setstatus(psdata, UNBOUNDED); } else { /* Fix the value at its best bound */ report(lp, DETAILED, "presolve_columns: Eliminated dual-zero variable %s fixed at %g\n", get_col_name(lp,j), Value1); candelete = TRUE; } } /* Do probing of binary variables to see if we can fix them */ else if(probefix && is_binary(lp, j) && presolve_probefix01(psdata, j, &Value1)) { report(lp, DETAILED, "presolve_columns: Fixed binary variable %s at %g\n", get_col_name(lp,j), Value1); candelete = TRUE; } #if 0 /* Do probing of binary variables to see if we can tighten their coefficients */ else if(probereduce && is_binary(lp, j) && (ix = presolve_probetighten01(psdata, j) > 0)) { report(lp, DETAILED, "presolve_columns: Tightened coefficients for binary variable %s in %d rows\n", get_col_name(lp,j), ix); iCoeffChanged += ix; psdata->forceupdate = TRUE; } #endif /* Perform fixing and deletion, if indicated */ if(candelete) { /* If we have a SOS1 member variable fixed at a non-zero value, then we must fix the other member variables at zero and delete the SOS(es) */ if((Value1 != 0) && SOS_is_member(lp->SOS, 0, j)) { ix = iVarFixed; if(!presolve_fixSOS1(psdata, j, Value1, &iConRemove, &iVarFixed)) status = presolve_setstatus(psdata, INFEASIBLE); if(iVarFixed > ix) psdata->forceupdate = TRUE; break; } else { if(!presolve_colfix(psdata, j, Value1, TRUE, &iVarFixed)) { status = presolve_setstatus(psdata, INFEASIBLE); break; } j = presolve_colremove(psdata, j, TRUE); } } else j = nextActiveLink(psdata->cols->varmap, j); } /* Remove any "hanging" empty row and columns */ if(status == RUNNING) status = presolve_shrink(psdata, &iConRemove, &iVarFixed); (*nCoeffChanged) += iCoeffChanged; (*nConRemove) += iConRemove; (*nVarFixed) += iVarFixed; (*nBoundTighten) += iBoundTighten; (*nSum) += iCoeffChanged+iConRemove+iVarFixed+iBoundTighten; return( status ); } STATIC int presolve_freeandslacks(presolverec *psdata, int *nCoeffChanged, int *nConRemove, int *nVarFixed, int *nSum) { lprec *lp = psdata->lp; MYBOOL isOFNZ, unbounded, impliedfree = is_presolve(lp, PRESOLVE_IMPLIEDFREE), impliedslack = is_presolve(lp, PRESOLVE_IMPLIEDSLK); int iCoeffChanged = 0, iConRemove = 0, iVarFixed = 0, status = RUNNING, i, ix, j, countNZ; REAL coeff_bl, coeff_bu; MATrec *mat = lp->matA; if(impliedfree || impliedslack) for(j = firstActiveLink(psdata->cols->varmap); j != 0; ) { /* Check and initialize */ if((presolve_collength(psdata, j) != 1) || is_int(lp, j) || is_semicont(lp, j) || !presolve_candeletevar(psdata, j)) { j = nextActiveLink(psdata->cols->varmap, j); continue; } ix = 0; i = COL_MAT_ROWNR(presolve_nextrow(psdata, j, &ix)); isOFNZ = isnz_origobj(lp, j); countNZ = presolve_rowlength(psdata, i); coeff_bu = get_upbo(lp, j); coeff_bl = get_lowbo(lp, j); unbounded = my_infinite(lp, coeff_bl) && my_infinite(lp, coeff_bu); ix = lp->rows + j; /* Eliminate singleton free variable and its associated constraint */ if(impliedfree && unbounded && presolve_impliedcolfix(psdata, i, j, TRUE)) { report(lp, DETAILED, "presolve_freeandslacks: Eliminated free variable %s and row %s\n", get_col_name(lp, j), get_row_name(lp, i)); presolve_rowremove(psdata, i, TRUE); iConRemove++; j = presolve_colremove(psdata, j, TRUE); iVarFixed++; } /* Check for implied slack variable in equality constraint */ else if(impliedslack && (countNZ > 1) && is_constr_type(lp, i, EQ) && presolve_impliedcolfix(psdata, i, j, FALSE)) { report(lp, DETAILED, "presolve_freeandslacks: Eliminated implied slack variable %s via row %s\n", get_col_name(lp, j), get_row_name(lp, i)); psdata->forceupdate = TRUE; j = presolve_colremove(psdata, j, TRUE); iVarFixed++; } /* Check for implied (generalized) slack variable in inequality constraint */ else if(impliedslack && !isOFNZ && my_infinite(lp, coeff_bu) && /* Consider removing this test */ #if 0 /* Force zero-bounded implicit slack */ (coeff_bl == 0)) && #else !my_infinite(lp, coeff_bl) && #endif (countNZ > 1) && !is_constr_type(lp, i, EQ)) { REAL *target, ValueA = COL_MAT_VALUE(presolve_lastrow(psdata, j)); #if 0 coeff_bu = get_rh_upper(lp, i); coeff_bl = get_rh_lower(lp, i); if(!presolve_singletonbounds(psdata, i, j, &coeff_bl, &coeff_bu, &ValueA)) { status = presolve_setstatus(psdata, INFEASIBLE); break; } #endif if((coeff_bl != 0) && !my_infinite(lp, coeff_bl) && !my_infinite(lp, coeff_bu)) coeff_bu -= coeff_bl; /* If the coefficient is negative, reduce the lower bound / increase range */ if(ValueA > 0) { target = &lp->orig_upbo[i]; if(!my_infinite(lp, *target)) { if(my_infinite(lp, coeff_bu)) { *target = lp->infinite; psdata->forceupdate = TRUE; } else { *target += ValueA * coeff_bu; *target = presolve_roundrhs(lp, *target, FALSE); } } } /* Otherwise see if the upper bound should be changed */ else { target = &lp->orig_rhs[i]; if(my_infinite(lp, coeff_bu) || my_infinite(lp, *target)) { /* Do we suddenly find that the constraint becomes redundant? (e226.mps) */ if(my_infinite(lp, lp->orig_upbo[i])) { presolve_rowremove(psdata, i, TRUE); iConRemove++; } /* Or does the upper bound of a ranged constraint become Inf? */ else { *target -= lp->orig_upbo[i]; *target = -(*target); mat_multrow(mat, i, -1); lp->orig_upbo[i] = lp->infinite; psdata->forceupdate = TRUE; } } else { *target -= ValueA * coeff_bu; *target = presolve_roundrhs(lp, *target, FALSE); } } presolve_colfix(psdata, j, coeff_bl, TRUE, &iVarFixed); report(lp, DETAILED, "presolve_freeandslacks: Eliminated duplicate slack variable %s via row %s\n", get_col_name(lp, j), get_row_name(lp, i)); j = presolve_colremove(psdata, j, TRUE); } /* Go to next column */ else j = nextActiveLink(psdata->cols->varmap, j); } (*nCoeffChanged) += iCoeffChanged; (*nConRemove) += iConRemove; (*nVarFixed) += iVarFixed; (*nSum) += iCoeffChanged+iConRemove+iVarFixed; return( status ); } STATIC int presolve_preparerows(presolverec *psdata, int *nBoundTighten, int *nSum) { lprec *lp = psdata->lp; MYBOOL impliedfree = is_presolve(lp, PRESOLVE_IMPLIEDFREE), tightenbounds = is_presolve(lp, PRESOLVE_BOUNDS); int iRangeTighten = 0, iBoundTighten = 0, status = RUNNING, i, j; REAL losum, upsum, lorhs, uprhs, epsvalue = psdata->epsvalue; MATrec *mat = lp->matA; for(i = lastActiveLink(psdata->rows->varmap); i > 0; i = prevActiveLink(psdata->rows->varmap, i)) { /* First identify any full row infeasibilities */ j = presolve_rowlengthex(psdata, i); #ifdef Paranoia if(!presolve_testrow(psdata, nextActiveLink(psdata->rows->varmap, i))) { #else if((j > 1) && !psdata->forceupdate && !presolve_rowfeasible(psdata, i, FALSE)) { #endif status = presolve_setstatus(psdata, INFEASIBLE); break; } /* Do bound (LHS) or constraint range (RHS) tightening if we will later identify implied free variables (tends to produce degeneracy otherwise) */ if(impliedfree && (j > 1) && mat_validate(mat)){ /* Look for opportunity to tighten constraint bounds (and check for feasibility again) */ presolve_range(lp, i, psdata->rows, &losum, &upsum); lorhs = get_rh_lower(lp, i); uprhs = get_rh_upper(lp, i); if((losum > MIN(upsum, uprhs)+epsvalue) || (upsum < MAX(losum, lorhs)-epsvalue)) { report(lp, NORMAL, "presolve_preparerows: Variable bound / constraint value infeasibility in row %s.\n", get_row_name(lp, i)); status = presolve_setstatus(psdata, INFEASIBLE); break; } if(losum > lorhs+epsvalue) { set_rh_lower(lp, i, presolve_roundrhs(lp, losum, TRUE)); iRangeTighten++; } if(upsum < uprhs-epsvalue) { set_rh_upper(lp, i, presolve_roundrhs(lp, upsum, FALSE)); iRangeTighten++; } } /* Seek to tighten bounds on individual variables */ if(tightenbounds && mat_validate(mat)) { #if 1 if(j > 1) status = presolve_rowtighten(psdata, i, &iBoundTighten, FALSE); #else if((MIP_count(lp) > 0) && (j > 1)) status = presolve_rowtighten(psdata, i, &iBoundTighten, TRUE); #endif } /* Look for opportunity to convert ranged constraint to equality-type */ if(!is_constr_type(lp, i, EQ) && (get_rh_range(lp, i) < epsvalue)) { presolve_setEQ(psdata, i); iRangeTighten++; } } psdata->forceupdate |= (MYBOOL) (iBoundTighten > 0); (*nBoundTighten) += iBoundTighten+iRangeTighten; (*nSum) += iBoundTighten+iRangeTighten; return( status ); } STATIC int presolve_rows(presolverec *psdata, int *nCoeffChanged, int *nConRemove, int *nVarFixed, int *nBoundTighten, int *nSum) { lprec *lp = psdata->lp; MYBOOL candelete; int iCoeffChanged = 0, iConRemove = 0, iVarFixed = 0, iBoundTighten = 0, status = RUNNING, i,ix, j,jx, item; REAL Value1, Value2, losum, upsum, lorhs, uprhs, epsvalue = psdata->epsvalue; MATrec *mat = lp->matA; for(i = lastActiveLink(psdata->rows->varmap); (i > 0) && (status == RUNNING); ) { candelete = FALSE; /* First identify any full row infeasibilities Note: Handle singletons below to ensure that conflicting multiple singleton rows with this variable do not provoke notice of infeasibility */ j = presolve_rowlengthex(psdata, i); if((j > 1) && !psdata->forceupdate && !presolve_rowfeasible(psdata, i, FALSE)) { status = presolve_setstatus(psdata, INFEASIBLE); break; } presolve_range(lp, i, psdata->rows, &losum, &upsum); lorhs = get_rh_lower(lp, i); uprhs = get_rh_upper(lp, i); #ifdef Paranoia if((losum>uprhs+epsvalue) || (upsum= -epsvalue)) { #else /* Version that deletes bound-fixed columns here */ if((j == 1) && (uprhs-lorhs >= -epsvalue)) { #endif item = 0; jx = presolve_nextcol(psdata, i, &item); j = ROW_MAT_COLNR(jx); /* Make sure we don't have conflicting other singleton rows with this variable */ Value1 = lp->infinite; Value2 = -Value1; if(presolve_collength(psdata, j) > 1) status = presolve_boundconflict(psdata, i, j); else if(is_constr_type(lp, i, EQ)) { Value2 = ROW_MAT_VALUE(jx); Value1 = lp->orig_rhs[i] / Value2; if(Value2 < 0) swapREAL(&losum, &upsum); if((Value1 < losum / my_if(my_infinite(lp, losum), my_sign(Value2), Value2) - epsvalue) || (Value1 > upsum / my_if(my_infinite(lp, upsum), my_sign(Value2), Value2) + epsvalue)) status = presolve_setstatus(psdata, INFEASIBLE); Value2 = Value1; } /* Proceed to fix and remove variable (if it is not a SOS member) */ if(status == RUNNING) { if((fabs(Value2-Value1) < epsvalue) && (fabs(Value2) > epsvalue)) { MYBOOL isSOS = (MYBOOL) (SOS_is_member(lp->SOS, 0, j) != FALSE), deleteSOS = isSOS && presolve_candeletevar(psdata, j); if((Value1 != 0) && deleteSOS) { if(!presolve_fixSOS1(psdata, j, Value1, &iConRemove, &iVarFixed)) status = presolve_setstatus(psdata, INFEASIBLE); psdata->forceupdate = TRUE; } else { if(!presolve_colfix(psdata, j, Value1, (MYBOOL) !isSOS, NULL)) status = presolve_setstatus(psdata, INFEASIBLE); else if(isSOS && !deleteSOS) iBoundTighten++; else { presolve_colremove(psdata, j, TRUE); iVarFixed++; } } } else status = presolve_colsingleton(psdata, i, j, &iBoundTighten); } if(status == INFEASIBLE) { break; } if(psdata->forceupdate != AUTOMATIC) { /* Store dual recovery information and code for deletion */ presolve_storeDualUndo(psdata, i, j); candelete = TRUE; } } /* Delete non-empty rows and variables that are completely determined at zero */ else if((j > 0) /* Only examine non-empty rows, */ && (fabs(lp->orig_rhs[i]) < epsvalue) /* .. and the current RHS is zero, */ && ((psdata->rows->plucount[i] == 0) || (psdata->rows->negcount[i] == 0)) /* .. and the parameter signs are all equal, */ && (psdata->rows->pluneg[i] == 0) /* .. and no (quasi) free variables, */ && (is_constr_type(lp, i, EQ) #ifdef FindImpliedEqualities || (fabs(lorhs-upsum) < epsvalue) /* Convert to equalities */ || (fabs(uprhs-losum) < epsvalue) /* Convert to equalities */ #endif ) ) { /* Delete the columns we can delete */ status = presolve_rowfixzero(psdata, i, &iVarFixed); /* Then delete the row, which is redundant */ if(status == RUNNING) candelete = TRUE; } /* Check if we have a constraint made redundant through bounds on individual variables; such constraints are often referred to as "forcing constraints" */ else if((losum >= lorhs-epsvalue) && (upsum <= uprhs+epsvalue)) { /* Check if we can also fix all the variables */ if(fabs(losum-upsum) < epsvalue) { item = 0; jx = presolve_nextcol(psdata, i, &item); while((status == RUNNING) && (jx >= 0)) { j = ROW_MAT_COLNR(jx); Value1 = get_lowbo(lp, j); if(presolve_colfix(psdata, j, Value1, TRUE, &iVarFixed)) { presolve_colremove(psdata, j, TRUE); iVarFixed++; jx = presolve_nextcol(psdata, i, &item); } else status = presolve_setstatus(psdata, INFEASIBLE); } } candelete = TRUE; } /* Get next row and do the deletion of the previous, if indicated */ ix = i; i = prevActiveLink(psdata->rows->varmap, i); if(candelete) { presolve_rowremove(psdata, ix, TRUE); iConRemove++; } } /* Remove any "hanging" empty row and columns */ if(status == RUNNING) status = presolve_shrink(psdata, &iConRemove, &iVarFixed); (*nCoeffChanged) += iCoeffChanged; (*nConRemove) += iConRemove; (*nVarFixed) += iVarFixed; (*nBoundTighten) += iBoundTighten; (*nSum) += iCoeffChanged+iConRemove+iVarFixed+iBoundTighten; return( status ); } /* Top level presolve routine */ STATIC int presolve(lprec *lp) { int status = RUNNING, i, j = 0, jx = 0, jjx = 0, k, oSum, iCoeffChanged = 0, iConRemove = 0, iVarFixed = 0, iBoundTighten = 0, iSOS = 0, iSum = 0, nCoeffChanged = 0, nConRemove = 0, nVarFixed = 0, nBoundTighten = 0, nSOS = 0, nSum = 0; REAL Value1, Value2, initrhs0 = lp->orig_rhs[0]; presolverec *psdata = NULL; MATrec *mat = lp->matA; #if 0 lp->do_presolve = PRESOLVE_ROWS; report(lp, IMPORTANT, "presolve: Debug override of presolve setting to %d\n", lp->do_presolve); #endif /* Lock the variable mapping arrays and counts ahead of any row/column deletion or creation in the course of presolve, solvelp or postsolve */ if(!lp->varmap_locked) varmap_lock(lp); /* Check if we have already done presolve */ mat_validate(mat); if(lp->wasPresolved) { if(SOS_count(lp) > 0) { SOS_member_updatemap(lp->SOS); make_SOSchain(lp, (MYBOOL) ((lp->do_presolve & PRESOLVE_LASTMASKMODE) != PRESOLVE_NONE)); } if((lp->solvecount > 1) && (lp->bb_level < 1) && ((lp->scalemode & SCALE_DYNUPDATE) != 0)) auto_scale(lp); if(!lp->basis_valid) { crash_basis(lp); report(lp, DETAILED, "presolve: Had to repair broken basis.\n"); } lp->timepresolved = timeNow(); return(status); } /* Produce original model statistics (do hoops to produce correct stats if we have SOS'es) */ i = SOS_count(lp); if(i > 0) { SOS_member_updatemap(lp->SOS); lp->sos_vars = SOS_memberships(lp->SOS, 0); } REPORT_modelinfo(lp, TRUE, "SUBMITTED"); report(lp, NORMAL, " \n"); if(i > 0) lp->sos_vars = 0; /* Finalize basis indicators; if no basis was created earlier via set_basis or crash_basis then simply set the default basis. */ if(!lp->basis_valid) lp->var_basic[0] = AUTOMATIC; /* Flag that we are presolving */ #if 0 write_lp(lp, "test_in.lp"); /* Write to lp-formatted file for debugging */ /*write_mps(lp, "test_in.mps");*/ /* Write to lp-formatted file for debugging */ #endif /* Update inf norms and check for potential factorization trouble */ mat_computemax(mat /*, FALSE */); #if 0 Value1 = fabs(lp->negrange); if(is_obj_in_basis(lp) && (mat->dynrange < Value1) && vec_computeext(lp->orig_obj, 1, lp->columns, TRUE, &i, &j)) { /* Compute relative scale metric */ Value2 = fabs(lp->orig_obj[j]/lp->orig_obj[i]) / mat->dynrange; if(Value2 < 1.0) Value2 = 1.0 / Value2; /* Determine if we should alert modeler and possibly move the OF out of the coefficient matrix */ if((Value2 > Value1) /* Case with extreme scale difference */ #if 1 || (mat->dynrange == 1.0) /* Case where we have an all-unit coefficient matrix, possibly totally unimodular */ #endif ) if((lp->simplex_strategy & SIMPLEX_DYNAMIC) > 0) { clear_action(&lp->algopt, ALGOPT_OBJINBASIS); report(lp, NORMAL, "Moved objective function out of the basis matrix to enhance factorization accuracy.\n"); } else if(mat->dynrange > 1.0) report(lp, IMPORTANT, "Warning: Objective/matrix coefficient magnitude differences will cause inaccuracy!\n"); } #endif /* Do traditional simple presolve */ yieldformessages(lp); if((lp->do_presolve & PRESOLVE_LASTMASKMODE) == PRESOLVE_NONE) { mat_checkcounts(mat, NULL, NULL, TRUE); i = 0; } else { if(lp->full_solution == NULL) allocREAL(lp, &lp->full_solution, lp->sum_alloc+1, TRUE); /* Identify infeasible SOS'es prior to any pruning */ j = 0; for(i = 1; i <= SOS_count(lp); i++) { k = SOS_infeasible(lp->SOS, i); if(k > 0) { presolverec psdata; psdata.lp = lp; report(lp, NORMAL, "presolve: Found SOS %d (type %d) to be range-infeasible on variable %d\n", i, SOS_get_type(lp->SOS, i), k); status = presolve_setstatus(&psdata, INFEASIBLE); j++; } } if(j > 0) goto Finish; /* Create and initialize the presolve data structures */ psdata = presolve_init(lp); /* Reentry point for the outermost, computationally expensive presolve loop */ psdata->outerloops = 0; do { psdata->outerloops++; iCoeffChanged = 0; iConRemove = 0; iVarFixed = 0; iBoundTighten = 0; iSOS = 0; oSum = nSum; /* Do the middle elimination loop */ do { psdata->middleloops++; nSum += iSum; iSum = 0; /* Accumulate constraint bounds based on bounds on individual variables. */ j = 0; while(presolve_statuscheck(psdata, &status) && psdata->forceupdate) { psdata->forceupdate = FALSE; /* Update sums, but limit iteration count to avoid possible "endless" loops with only marginal bound improvements */ if(presolve_updatesums(psdata) && (j < MAX_PSBOUNDTIGHTENLOOPS)) { /* Do row preparation useful for subsequent column and row presolve operations */ if((psdata->outerloops == 1) && (psdata->middleloops == 1)) status = presolve_preparerows(psdata, &iBoundTighten, &iSum); nBoundTighten += iBoundTighten; iBoundTighten = 0; nSum += iSum; iSum = 0; j++; if(status != RUNNING) report(lp, NORMAL, "presolve: Break after bound tightening iteration %d.\n", j); } } if(status != RUNNING) break; /* Do the relatively cheap innermost elimination loop */ do { psdata->innerloops++; nSum += iSum; iSum = 0; /* Eliminate empty rows, convert row singletons to bounds, tighten bounds, and remove always satisfied rows */ if(presolve_statuscheck(psdata, &status) && is_presolve(lp, PRESOLVE_ROWS)) status = presolve_rows(psdata, &iCoeffChanged, &iConRemove, &iVarFixed, &iBoundTighten, &iSum); /* Eliminate empty or fixed columns (including trivial OF column singletons) */ if(presolve_statuscheck(psdata, &status) && is_presolve(lp, PRESOLVE_COLS)) status = presolve_columns(psdata, &iCoeffChanged, &iConRemove, &iVarFixed, &iBoundTighten, &iSum); /* Presolve SOS'es if possible (always do this) */ if(presolve_statuscheck(psdata, &status)) status = presolve_redundantSOS(psdata, &iBoundTighten, &iSum); } while((status == RUNNING) && (iSum > 0)); if(status != RUNNING) break; /* Merge compatible similar rows; loop backwards over every row */ if(presolve_statuscheck(psdata, &status) && (psdata->outerloops == 1) && (psdata->middleloops <= MAX_PSMERGELOOPS) && is_presolve(lp, PRESOLVE_MERGEROWS)) status = presolve_mergerows(psdata, &iConRemove, &iSum); /* Eliminate dominated rows */ if(presolve_statuscheck(psdata, &status) && is_presolve(lp, PRESOLVE_ROWDOMINATE)) presolve_rowdominance(psdata, &iCoeffChanged, &iConRemove, &iVarFixed, &iSum); /* See if we can convert some constraints to SOSes (only SOS1 handled) */ if(presolve_statuscheck(psdata, &status) && (MIP_count(lp) > 0) && is_presolve(lp, PRESOLVE_SOS)) status = presolve_SOS1(psdata, &iCoeffChanged, &iConRemove, &iVarFixed, &iSOS, &iSum); /* Eliminate dominated columns in set coverage models */ if(presolve_statuscheck(psdata, &status) && (lp->int_vars > 1) && is_presolve(lp, PRESOLVE_COLDOMINATE)) presolve_coldominance01(psdata, &iConRemove, &iVarFixed, &iSum); /* Aggregate compatible columns */ if(presolve_statuscheck(psdata, &status) && /*TRUE ||*/ is_presolve(lp, PRESOLVE_AGGREGATE)) presolve_aggregate(psdata, &iConRemove, &iVarFixed, &iSum); /* Eliminate free variables and implied slacks */ if(presolve_statuscheck(psdata, &status) && /* !is_presolve(lp, PRESOLVE_ELIMEQ2) && */ is_presolve(lp, PRESOLVE_IMPLIEDSLK | PRESOLVE_IMPLIEDFREE)) status = presolve_freeandslacks(psdata, &iCoeffChanged, &iConRemove, &iVarFixed, &iSum); } while((status == RUNNING) && (iSum > 0)); if(status != RUNNING) break; /* Check if we can do elimination of rank-deficient equality constraints */ if(presolve_statuscheck(psdata, &status) && (psdata->EQmap->count > 1) && is_presolve(lp, PRESOLVE_LINDEP)) { #if 0 REPORT_mat_mmsave(lp, "A.mtx", NULL, FALSE, "Constraint matrix A"); #endif presolve_singularities(psdata, &iCoeffChanged, &iConRemove, &iVarFixed, &iSum); } /* Eliminate variable and tighten bounds using 2-element EQs; note that this involves modifying the coefficients of A and can therefore be a slow operation. */ if(presolve_statuscheck(psdata, &status) && is_presolve(lp, PRESOLVE_ELIMEQ2)) { jjx = 0; do { jjx += iSum; status = presolve_elimeq2(psdata, &iCoeffChanged, &iConRemove, &iVarFixed, &iSum); } while((status == RUNNING) && (iSum > jjx)); iSum = jjx; #if 0 /* Eliminate free variables and implied slacks */ if(presolve_statuscheck(psdata, &status) && is_presolve(lp, PRESOLVE_IMPLIEDSLK | PRESOLVE_IMPLIEDFREE)) status = presolve_freeandslacks(psdata, &iCoeffChanged, &iConRemove, &iVarFixed, &iSum); #endif } /* Increase A matrix sparsity by discovering common subsets using EQs */ if(presolve_statuscheck(psdata, &status) && (psdata->EQmap->count > 0) && is_presolve(lp, PRESOLVE_SPARSER)) status = presolve_makesparser(psdata, &iCoeffChanged, &iConRemove, &iVarFixed, &iSum); /* Do GCD-based coefficient reductions (also does row scaling, even if no rhs INT truncations are possible) */ if(presolve_statuscheck(psdata, &status) && (psdata->INTmap->count > 0) && is_presolve(lp, PRESOLVE_REDUCEGCD)) if(!presolve_reduceGCD(psdata, &iCoeffChanged, &iBoundTighten, &iSum)) status = presolve_setstatus(psdata, INFEASIBLE); /* Simplify knapsack or set coverage models where OF coefficients are duplicated in the constraints. At the cost of adding helper columns, this increases sparsity and facilitates identification of lower and upper bounds. */ if(presolve_statuscheck(psdata, &status) && is_presolve(lp, PRESOLVE_KNAPSACK)) { i = iCoeffChanged; status = presolve_knapsack(psdata, &iCoeffChanged); } /* Remove any "hanging" empty row and columns */ if(status == RUNNING) status = presolve_shrink(psdata, &iConRemove, &iVarFixed); nCoeffChanged += iCoeffChanged; nConRemove += iConRemove; nVarFixed += iVarFixed; nBoundTighten += iBoundTighten; nSOS += iSOS; nSum += iSum; iSum = iConRemove + iVarFixed + iBoundTighten + iCoeffChanged; if(iSum > 0) report(lp, NORMAL, "Presolve O:%d -> Reduced rows:%5d, cols:%5d --- changed bnds:%5d, Ab:%5d.\n", psdata->outerloops, iConRemove, iVarFixed, iBoundTighten, iCoeffChanged); /* Do the outermost loop again if we were successful in this presolve sequences */ } while(presolve_statuscheck(psdata, &status) && (psdata->forceupdate || (oSum < nSum)) && (psdata->outerloops < get_presolveloops(lp)) && (psdata->rows->varmap->count+psdata->cols->varmap->count > 0)); /* Finalize presolve */ #ifdef Paranoia i = presolve_debugcheck(lp, psdata->rows->varmap, psdata->cols->varmap); if(i > 0) report(lp, SEVERE, "presolve: %d internal consistency failure%s\n", i, my_plural_std(i)); if((SOS_count(lp) > 0) && !presolve_SOScheck(psdata)) report(lp, SEVERE, "presolve: SOS sparse member mapping problem - part 1\n"); #endif /* Perform bound relaxation to reduce chance of degeneracy. */ if((status == RUNNING) && !is_presolve(lp, PRESOLVE_IMPLIEDFREE)) jjx = presolve_makefree(psdata); else jjx = 0; /* Finalize the presolve */ if(!presolve_finalize(psdata)) report(lp, SEVERE, "presolve: Unable to construct internal data representation\n"); /* Report summary information */ i = NORMAL; iVarFixed = lp->presolve_undo->orig_columns - psdata->cols->varmap->count; iConRemove = lp->presolve_undo->orig_rows - psdata->rows->varmap->count; if(nSum > 0) report(lp, i, "PRESOLVE Elimination loops performed.......... O%d:M%d:I%d\n", psdata->outerloops, psdata->middleloops, psdata->innerloops); if(nVarFixed) report(lp, i, " %8d empty or fixed variables............. %s.\n", nVarFixed, "REMOVED"); if(nConRemove) report(lp, i, " %8d empty or redundant constraints....... %s.\n", nConRemove, "REMOVED"); if(nBoundTighten) report(lp, i, " %8d bounds............................... %s.\n", nBoundTighten, "TIGHTENED"); if(nCoeffChanged) report(lp, i, " %8d matrix coefficients.................. %s.\n", nCoeffChanged, "CHANGED"); if(jjx > 0) report(lp, i, " %8d variables' final bounds.............. %s.\n", jjx, "RELAXED"); if(nSOS) report(lp, i, " %8d constraints detected as SOS1......... %s.\n", nSOS, "CONVERTED"); /* Report optimality or infeasibility */ if(status == UNBOUNDED) report(lp, NORMAL, "%20s Solution status detected............. %s.\n", "", "UNBOUNDED"); else if(status == INFEASIBLE) report(lp, NORMAL, "%20s Solution status detected............. %s.\n", "", "INFEASIBLE"); else { if(psdata->cols->varmap->count == 0) Value1 = Value2 = lp->presolve_undo->fixed_rhs[0] -initrhs0; else presolve_rangeorig(lp, 0, psdata->rows, &Value1, &Value2, -initrhs0); if((fabs(Value1 - Value2) < psdata->epsvalue) || (fabs(my_reldiff(Value1, Value2)) < psdata->epsvalue)) { if((lp->rows == 0) && (lp->columns == 0)) { status = PRESOLVED; Value1 = my_chsign(is_maxim(lp), Value1); lp->solution[0] = Value1; lp->best_solution[0] = Value1; lp->full_solution[0] = Value1; } report(lp, NORMAL, "%20s OPTIMAL solution found............... %-g", "", Value1); } else if((status == RUNNING) && (i >= NORMAL)) { char lonum[20], upnum[20]; if(my_infinite(lp, Value1)) sprintf(lonum, "%13s", "-Inf"); else sprintf(lonum, "%+12g", Value1); if(my_infinite(lp, Value2)) sprintf(upnum, "%-13s", "Inf"); else sprintf(upnum, "%+-12g", Value2); report(lp, i, "%20s [ %s < Z < %s ]\n", "", lonum, upnum); } /* Update values for dual limit and best heuristic values */ if((MIP_count(lp) > 0) || (get_Lrows(lp) > 0)) { if(is_maxim(lp)) { SETMAX(lp->bb_heuristicOF, Value1); SETMIN(lp->bb_limitOF, Value2); } else { SETMIN(lp->bb_heuristicOF, Value2); SETMAX(lp->bb_limitOF, Value1); } } } report(lp, NORMAL, " \n"); /* Clean up (but save counts of constraint types for display later) */ j = psdata->LTmap->count; jx = psdata->EQmap->count; jjx = lp->rows - j - jx; presolve_free(&psdata); } /* Signal that we are done presolving */ if((lp->usermessage != NULL) && ((lp->do_presolve & PRESOLVE_LASTMASKMODE) != 0) && (lp->msgmask & MSG_PRESOLVE)) lp->usermessage(lp, lp->msghandle, MSG_PRESOLVE); /* Create master SOS variable list */ if(SOS_count(lp) > 0) { /*SOS_member_updatemap(lp->SOS); */ make_SOSchain(lp, (MYBOOL) ((lp->do_presolve & PRESOLVE_LASTMASKMODE) != PRESOLVE_NONE)); } /* Finalize model not identified as infeasible or unbounded */ if(status == RUNNING) { /* Resolve GUBs */ if(is_bb_mode(lp, NODE_GUBMODE)) identify_GUB(lp, TRUE); #if 0 /* Mark rows containing hidden identity matrices so that supporting factorization engines can use this structural information to boost efficiency */ if(is_algopt(lp, ALGOPT_COMPACTBPF)) lp->bfpoptimize = (MYBOOL) (assist_factorization(lp, ROWTYPE_LOGICAL, &lp->rowset1, &lp->rowno1) > 0); #endif /* Scale the model based on current settings */ auto_scale(lp); /* Crash the basis, if specified */ crash_basis(lp); /* Produce presolved model statistics */ if(nConRemove+nVarFixed+nBoundTighten+nVarFixed+nCoeffChanged > 0) { REPORT_modelinfo(lp, FALSE, "REDUCED"); if(nSum > 0) { report(lp, NORMAL, "Row-types: %7d LE, %7d GE, %7d EQ.\n", j, jjx, jx); report(lp, NORMAL, " \n"); } } } /* Optionally produce data on constraint classes */ if(lp->verbose > NORMAL) { report(lp, NORMAL, " \n"); REPORT_constraintinfo(lp, "CONSTRAINT CLASSES"); report(lp, NORMAL, " \n"); } Finish: lp->wasPresolved = TRUE; lp->timepresolved = timeNow(); #if 0 /* write_mps(lp, "test_out.mps"); */ /* Must put here due to variable name mapping */ write_lp(lp, "test_out.lp"); /* Must put here due to variable name mapping */ #endif #if 0 REPORT_debugdump(lp, "testint2.txt", FALSE); #endif return( status ); } STATIC MYBOOL postsolve(lprec *lp, int status) { /* Verify solution */ if(lp->lag_status != RUNNING) { int itemp; if(status == PRESOLVED) status = OPTIMAL; if((status == OPTIMAL) || (status == SUBOPTIMAL)) { itemp = check_solution(lp, lp->columns, lp->best_solution, lp->orig_upbo, lp->orig_lowbo, lp->epssolution); if((itemp != OPTIMAL) && (lp->spx_status == OPTIMAL)) lp->spx_status = itemp; else if((itemp == OPTIMAL) && ((status == SUBOPTIMAL) || (lp->spx_status == PRESOLVED))) lp->spx_status = status; } else if(status != PRESOLVED) { report(lp, NORMAL, "lp_solve unsuccessful after %.0f iter and a last best value of %g\n", (double) get_total_iter(lp), lp->best_solution[0]); if(lp->bb_totalnodes > 0) report(lp, NORMAL, "lp_solve explored %.0f nodes before termination\n", (double) get_total_nodes(lp)); } else lp->spx_status = OPTIMAL; /* Only rebuild primal solution here, since the dual is only computed on request */ presolve_rebuildUndo(lp, TRUE); } /* Check if we can clear the variable map */ if(varmap_canunlock(lp)) lp->varmap_locked = FALSE; #if 0 REPORT_mat_mmsave(lp, "basis.mtx", NULL, FALSE); /* Write the current basis matrix (no OF) */ #endif return( TRUE ); }