/* It may be the case that the bounds on the variables in a constraint are such that no matter what feasible value the variables take, the constraint cannot be violated. In this case we can drop the constraint as useless. On the other hand, it may be that the only way to satisfy a constraint is to jam all the variables in the constraint to one of their bounds, fixing the variables. This is a forcing constraint, the primary target of this transform. Detection of both useless and forcing constraints requires calculation of bounds on the row activity (often referred to as lhs bounds, from the common form ax <= b). This routine will remember useless constraints as it finds them and invoke useless_constraint_action to deal with them. The transform applied here simply tightens the bounds on the variables. Other transforms will remove the fixed variables, leaving an empty row which is ultimately dropped. A reasonable question to ask is ``If a variable is already fixed, why do we need a record in the postsolve object?'' The answer is that in postsolve we'll be dealing with a column-major representation and we may need to scan the row (see postsolve comments). So it's useful to record all variables in the constraint. On the other hand, it's definitely harmful to ask remove_fixed_action to process a variable more than once (causes problems in remove_fixed_action::postsolve). Original comments: It looks like these checks could be performed in parallel, that is, the tests could be carried out for all rows in parallel, and then the rows deleted and columns tightened afterward. Obviously, this is true for useless rows. By doing it in parallel rather than sequentially, we may miss transformations due to variables that were fixed by forcing constraints, though. Note that both of these operations will cause problems if the variables in question really need to exceed their bounds in order to make the problem feasible. */ const CoinPresolveAction* forcing_constraint_action::presolve (CoinPresolveMatrix *prob, const CoinPresolveAction *next) { # if PRESOLVE_DEBUG > 0 || COIN_PRESOLVE_TUNING int startEmptyRows = 0 ; int startEmptyColumns = 0 ; startEmptyRows = prob->countEmptyRows() ; startEmptyColumns = prob->countEmptyCols() ; # endif # if PRESOLVE_DEBUG > 0 || PRESOLVE_CONSISTENCY > 0 # if PRESOLVE_DEBUG > 0 std::cout << "Entering forcing_constraint_action::presolve, considering " << prob->numberRowsToDo_ << " rows." << std::endl ; # endif presolve_check_sol(prob) ; presolve_check_nbasic(prob) ; # endif # if COIN_PRESOLVE_TUNING double startTime = 0.0 ; if (prob->tuning_) { startTime = CoinCpuTime() ; } # endif // Column solution and bounds double *clo = prob->clo_ ; double *cup = prob->cup_ ; double *csol = prob->sol_ ; // Row-major representation const CoinBigIndex *mrstrt = prob->mrstrt_ ; const double *rowels = prob->rowels_ ; const int *hcol = prob->hcol_ ; const int *hinrow = prob->hinrow_ ; const int nrows = prob->nrows_ ; const double *rlo = prob->rlo_ ; const double *rup = prob->rup_ ; const double tol = ZTOLDP ; const double inftol = prob->feasibilityTolerance_ ; // for redundant rows be safe const double inftol2 = 0.01*prob->feasibilityTolerance_ ; const int ncols = prob->ncols_ ; int *fixed_cols = new int[ncols] ; int nfixed_cols = 0 ; action *actions = new action [nrows] ; int nactions = 0 ; int *useless_rows = new int[nrows] ; int nuseless_rows = 0 ; const int numberLook = prob->numberRowsToDo_ ; int *look = prob->rowsToDo_ ; bool fixInfeasibility = ((prob->presolveOptions_&0x4000) != 0) ; /* Open a loop to scan the constraints of interest. There must be variables left in the row. */ for (int iLook = 0 ; iLook < numberLook ; iLook++) { int irow = look[iLook] ; if (hinrow[irow] <= 0) continue ; const CoinBigIndex krs = mrstrt[irow] ; const CoinBigIndex kre = krs+hinrow[irow] ; /* Calculate upper and lower bounds on the row activity based on upper and lower bounds on the variables. If these are finite and incompatible with the given row bounds, we have infeasibility. */ double maxup, maxdown ; implied_row_bounds(rowels,clo,cup,hcol,krs,kre,maxup,maxdown) ; # if PRESOLVE_DEBUG > 2 std::cout << " considering row " << irow << ", rlo " << rlo[irow] << " LB " << maxdown << " UB " << maxup << " rup " << rup[irow] ; # endif /* If the maximum lhs value is less than L(i) or the minimum lhs value is greater than U(i), we're infeasible. */ if (maxup < PRESOLVE_INF && maxup+inftol < rlo[irow] && !fixInfeasibility) { CoinMessageHandler *hdlr = prob->messageHandler() ; prob->status_|= 1 ; hdlr->message(COIN_PRESOLVE_ROWINFEAS,prob->messages()) << irow << rlo[irow] << rup[irow] << CoinMessageEol ; # if PRESOLVE_DEBUG > 2 std::cout << "; infeasible." << std::endl ; # endif break ; } if (-PRESOLVE_INF < maxdown && rup[irow] < maxdown-inftol && !fixInfeasibility) { CoinMessageHandler *hdlr = prob->messageHandler() ; prob->status_|= 1 ; hdlr->message(COIN_PRESOLVE_ROWINFEAS,prob->messages()) << irow << rlo[irow] << rup[irow] << CoinMessageEol ; # if PRESOLVE_DEBUG > 2 std::cout << "; infeasible." << std::endl ; # endif break ; } /* We've dealt with prima facie infeasibility. Now check if the constraint is trivially satisfied. If so, add it to the list of useless rows and move on. The reason we require maxdown and maxup to be finite if the row bound is finite is to guard against some subsequent transform changing a column bound from infinite to finite. Once finite, bounds continue to tighten, so we're safe. */ /* Test changed to use +small tolerance rather than -tolerance as test fails often */ if (((rlo[irow] <= -PRESOLVE_INF) || (-PRESOLVE_INF < maxdown && rlo[irow] <= maxdown+inftol2)) && ((rup[irow] >= PRESOLVE_INF) || (maxup < PRESOLVE_INF && rup[irow] >= maxup-inftol2))) { // check none prohibited if (prob->anyProhibited_) { bool anyProhibited=false; for (int k=krs; k<kre; k++) { int jcol = hcol[k]; if (prob->colProhibited(jcol)) { anyProhibited=true; break; } } if (anyProhibited) continue; // skip row } useless_rows[nuseless_rows++] = irow ; # if PRESOLVE_DEBUG > 2 std::cout << "; useless." << std::endl ; # endif continue ; } /* Is it the case that we can just barely attain L(i) or U(i)? If so, we have a forcing constraint. As explained above, we need maxup and maxdown to be finite in order for the test to be valid. */ const bool tightAtLower = ((maxup < PRESOLVE_INF) && (fabs(rlo[irow]-maxup) < tol)) ; const bool tightAtUpper = ((-PRESOLVE_INF < maxdown) && (fabs(rup[irow]-maxdown) < tol)) ; # if PRESOLVE_DEBUG > 2 if (tightAtLower || tightAtUpper) std::cout << "; forcing." ; std::cout << std::endl ; # endif if (!(tightAtLower || tightAtUpper)) continue ; // check none prohibited if (prob->anyProhibited_) { bool anyProhibited=false; for (int k=krs; k<kre; k++) { int jcol = hcol[k]; if (prob->colProhibited(jcol)) { anyProhibited=true; break; } } if (anyProhibited) continue; // skip row } /* We have a forcing constraint. Get down to the business of fixing the variables at the appropriate bound. We need to remember the original value of the bound we're tightening. Allocate a pair of arrays the size of the row. Load variables fixed at l<j> from the start, variables fixed at u<j> from the end. Add the column to the list of columns to be processed further. */ double *bounds = new double[hinrow[irow]] ; int *rowcols = new int[hinrow[irow]] ; CoinBigIndex lk = krs ; CoinBigIndex uk = kre ; for (CoinBigIndex k = krs ; k < kre ; k++) { const int j = hcol[k] ; const double lj = clo[j] ; const double uj = cup[j] ; const double coeff = rowels[k] ; PRESOLVEASSERT(fabs(coeff) > ZTOLDP) ; /* If maxup is tight at L(i), then we want to force variables x<j> to the bound that produced maxup: u<j> if a<ij> > 0, l<j> if a<ij> < 0. If maxdown is tight at U(i), it'll be just the opposite. */ if (tightAtLower == (coeff > 0.0)) { --uk ; bounds[uk-krs] = lj ; rowcols[uk-krs] = j ; if (csol != 0) { csol[j] = uj ; } clo[j] = uj ; } else { bounds[lk-krs] = uj ; rowcols[lk-krs] = j ; ++lk ; if (csol != 0) { csol[j] = lj ; } cup[j] = lj ; } /* Only add a column to the list of fixed columns the first time it's fixed. */ if (lj != uj) { fixed_cols[nfixed_cols++] = j ; prob->addCol(j) ; } } PRESOLVEASSERT(uk == lk) ; PRESOLVE_DETAIL_PRINT(printf("pre_forcing %dR E\n",irow)) ; # if PRESOLVE_DEBUG > 1 std::cout << "FORCING: row(" << irow << "), " << (kre-krs) << " variables." << std::endl ; # endif /* Done with this row. Remember the changes in a postsolve action. */ action *f = &actions[nactions] ; nactions++ ; f->row = irow ; f->nlo = lk-krs ; f->nup = kre-uk ; f->rowcols = rowcols ; f->bounds = bounds ; } /* Done processing the rows of interest. No sense doing any additional work unless we're feasible. */ if (prob->status_ == 0) { # if PRESOLVE_DEBUG > 0 std::cout << "FORCING: " << nactions << " forcing, " << nuseless_rows << " useless." << std::endl ; # endif /* Trim the actions array to size and create a postsolve object. */ if (nactions) { next = new forcing_constraint_action(nactions, CoinCopyOfArray(actions,nactions),next) ; } /* Hand off the job of dealing with the useless rows to a specialist. */ if (nuseless_rows) { next = useless_constraint_action::presolve(prob, useless_rows,nuseless_rows,next) ; } /* Hand off the job of dealing with the fixed columns to a specialist. Note that there *cannot* be duplicates in this list or we'll get in trouble `unfixing' a column multiple times. The code above now adds a variable to fixed_cols only if it's not already fixed. If that ever changes, the disabled code (sort, unique) will need to be reenabled. */ if (nfixed_cols) { if (false && nfixed_cols > 1) { std::sort(fixed_cols,fixed_cols+nfixed_cols) ; int *end = std::unique(fixed_cols,fixed_cols+nfixed_cols) ; nfixed_cols = static_cast<int>(end-fixed_cols) ; } next = remove_fixed_action::presolve(prob,fixed_cols,nfixed_cols,next) ; } } else { // delete arrays for (int i=0;i<nactions;i++) { deleteAction(actions[i].rowcols,int *) ; deleteAction(actions[i].bounds,double *) ; } } deleteAction(actions,action*) ; delete [] useless_rows ; delete [] fixed_cols ; # if COIN_PRESOLVE_TUNING if (prob->tuning_) double thisTime = CoinCpuTime() ; # endif # if PRESOLVE_DEBUG > 0 || PRESOLVE_CONSISTENCY > 0 presolve_check_sol(prob) ; presolve_check_nbasic(prob) ; # endif # if PRESOLVE_DEBUG > 0 || COIN_PRESOLVE_TUNING int droppedRows = prob->countEmptyRows()-startEmptyRows ; int droppedColumns = prob->countEmptyCols()-startEmptyColumns ; std::cout << "Leaving forcing_constraint_action::presolve: removed " << droppedRows << " rows, " << droppedColumns << " columns" ; # if COIN_PRESOLVE_TUNING > 0 if (prob->tuning_) std::cout << " in " << (thisTime-prob->startTime_) << "s" ; # endif std::cout << "." << std::endl ; # endif return (next) ; }
const CoinPresolveAction *forcing_constraint_action::presolve(CoinPresolveMatrix *prob, const CoinPresolveAction *next) { double startTime = 0.0; int startEmptyRows=0; int startEmptyColumns = 0; if (prob->tuning_) { startTime = CoinCpuTime(); startEmptyRows = prob->countEmptyRows(); startEmptyColumns = prob->countEmptyCols(); } double *clo = prob->clo_; double *cup = prob->cup_; double *csol = prob->sol_ ; const double *rowels = prob->rowels_; const int *hcol = prob->hcol_; const CoinBigIndex *mrstrt = prob->mrstrt_; const int *hinrow = prob->hinrow_; const int nrows = prob->nrows_; const double *rlo = prob->rlo_; const double *rup = prob->rup_; // const char *integerType = prob->integerType_; const double tol = ZTOLDP; const double inftol = prob->feasibilityTolerance_; const int ncols = prob->ncols_; int *fixed_cols = new int[ncols]; int nfixed_cols = 0; action *actions = new action [nrows]; int nactions = 0; int *useless_rows = new int[nrows]; int nuseless_rows = 0; int numberLook = prob->numberRowsToDo_; int iLook; int * look = prob->rowsToDo_; bool fixInfeasibility = (prob->presolveOptions_&16384)!=0; # if PRESOLVE_DEBUG std::cout << "Entering forcing_constraint_action::presolve." << std::endl ; presolve_check_sol(prob) ; # endif /* Open a loop to scan the constraints of interest. There must be variables left in the row. */ for (iLook=0;iLook<numberLook;iLook++) { int irow = look[iLook]; if (hinrow[irow] > 0) { CoinBigIndex krs = mrstrt[irow]; CoinBigIndex kre = krs + hinrow[irow]; /* Calculate upper and lower bounds on the row activity based on upper and lower bounds on the variables. If these are finite and incompatible with the given row bounds, we have infeasibility. */ double maxup, maxdown; implied_row_bounds(rowels, clo, cup, hcol, krs, kre, &maxup, &maxdown); if (maxup < PRESOLVE_INF && maxup + inftol < rlo[irow]&&!fixInfeasibility) { /* max row activity below the row lower bound */ prob->status_|= 1; prob->messageHandler()->message(COIN_PRESOLVE_ROWINFEAS, prob->messages()) <<irow <<rlo[irow] <<rup[irow] <<CoinMessageEol; break; } else if (-PRESOLVE_INF < maxdown && rup[irow] < maxdown - inftol&&!fixInfeasibility) { /* min row activity above the row upper bound */ prob->status_|= 1; prob->messageHandler()->message(COIN_PRESOLVE_ROWINFEAS, prob->messages()) <<irow <<rlo[irow] <<rup[irow] <<CoinMessageEol; break; } // ADD TOLERANCE TO THESE TESTS else if ((rlo[irow] <= -PRESOLVE_INF || (-PRESOLVE_INF < maxdown && rlo[irow] <= maxdown)) && (rup[irow] >= PRESOLVE_INF || (maxup < PRESOLVE_INF && rup[irow] >= maxup))) { /* Original comment: I'm not sure that these transforms don't intefere with each other. We can get it next time. Well, I'll argue that bounds are never really loosened (at worst, they're transferred onto some other variable, or inferred to be unnecessary. Once useless, always useless. Leaving this hook in place allows for a sort of infinite loop where this routine keeps queuing the same constraints over and over. -- lh, 040901 -- if (some_col_was_fixed(hcol, krs, kre, clo, cup)) { prob->addRow(irow); continue; } */ // this constraint must always be satisfied - drop it useless_rows[nuseless_rows++] = irow; } else if ((maxup < PRESOLVE_INF && fabs(rlo[irow] - maxup) < tol) || (-PRESOLVE_INF < maxdown && fabs(rup[irow] - maxdown) < tol)) { // the lower bound can just be reached, or // the upper bound can just be reached; // called a "forcing constraint" in the paper (p. 226) const int lbound_tight = (maxup < PRESOLVE_INF && fabs(rlo[irow] - maxup) < tol); /* Original comment and rebuttal as above. if (some_col_was_fixed(hcol, krs, kre, clo, cup)) { // make sure on next time prob->addRow(irow); continue; } */ // out of space - this probably never happens (but this routine will // often put duplicates in the fixed column list) if (nfixed_cols + (kre-krs) >= ncols) break; double *bounds = new double[hinrow[irow]]; int *rowcols = new int[hinrow[irow]]; int lk = krs; // load fix-to-down in front int uk = kre; // load fix-to-up in back CoinBigIndex k; for ( k=krs; k<kre; k++) { int jcol = hcol[k]; prob->addCol(jcol); double coeff = rowels[k]; PRESOLVEASSERT(fabs(coeff) > ZTOLDP); // one of the two contributed to maxup - set the other to that if (lbound_tight == (coeff > 0.0)) { --uk; bounds[uk-krs] = clo[jcol]; rowcols[uk-krs] = jcol; if (csol != 0) { csol[jcol] = cup[jcol] ; } clo[jcol] = cup[jcol]; } else { bounds[lk-krs] = cup[jcol]; rowcols[lk-krs] = jcol; ++lk; if (csol != 0) { csol[jcol] = clo[jcol] ; } cup[jcol] = clo[jcol]; } fixed_cols[nfixed_cols++] = jcol; } PRESOLVEASSERT(uk == lk); action *f = &actions[nactions]; nactions++; f->row = irow; f->nlo = lk-krs; f->nup = kre-uk; f->rowcols = rowcols; f->bounds = bounds; } } } if (nactions) { #if PRESOLVE_SUMMARY printf("NFORCED: %d\n", nactions); #endif next = new forcing_constraint_action(nactions, CoinCopyOfArray(actions,nactions), next); } deleteAction(actions,action*); if (nuseless_rows) { next = useless_constraint_action::presolve(prob, useless_rows, nuseless_rows, next); } delete[]useless_rows; /* We need to remove duplicates here, or we get into trouble in remove_fixed_action::postsolve when we try to reinstate a column multiple times. */ if (nfixed_cols) { if (nfixed_cols > 1) { std::sort(fixed_cols,fixed_cols+nfixed_cols) ; int *end = std::unique(fixed_cols,fixed_cols+nfixed_cols) ; nfixed_cols = static_cast<int>(end-fixed_cols) ; } next = remove_fixed_action::presolve(prob,fixed_cols,nfixed_cols,next) ; } delete[]fixed_cols ; if (prob->tuning_) { double thisTime=CoinCpuTime(); int droppedRows = prob->countEmptyRows() - startEmptyRows ; int droppedColumns = prob->countEmptyCols() - startEmptyColumns; printf("CoinPresolveForcing(32) - %d rows, %d columns dropped in time %g, total %g\n", droppedRows,droppedColumns,thisTime-startTime,thisTime-prob->startTime_); } # if PRESOLVE_DEBUG presolve_check_sol(prob) ; std::cout << "Leaving forcing_constraint_action::presolve." << std::endl ; # endif return (next); }