@@ -238,6 +238,11 @@ bool HPresolve::isEquation(HighsInt row) const {
238238 return (model->row_lower_ [row] == model->row_upper_ [row]);
239239}
240240
241+ bool HPresolve::isRanged (HighsInt row) const {
242+ return (model->row_lower_ [row] != -kHighsInf &&
243+ model->row_upper_ [row] != kHighsInf );
244+ }
245+
241246bool HPresolve::isImpliedEquationAtLower (HighsInt row) const {
242247 // if the implied lower bound on a row dual is strictly positive then the row
243248 // is an implied equation (using its lower bound) due to complementary
@@ -1608,12 +1613,20 @@ HPresolve::Result HPresolve::runProbing(HighsPostsolveStack& postsolve_stack) {
16081613 if (cliquetable.getSubstitution (i) != nullptr || !domain.isBinary (i))
16091614 continue ;
16101615
1616+ bool tightenLimits = (numProbed - oldNumProbed) >= 2500 ;
1617+
16111618 // when a large percentage of columns have been deleted, stop this round
16121619 // of probing
16131620 // if (numDel > std::max(model->num_col_ * 0.2, 1000.)) break;
1614- probingEarlyAbort =
1615- numDel >
1616- std::max (HighsInt{1000 }, (model->num_row_ + model->num_col_ ) / 20 );
1621+ if (!tightenLimits) {
1622+ probingEarlyAbort =
1623+ numDel >
1624+ std::max (HighsInt{1000 }, (model->num_row_ + model->num_col_ ) / 20 );
1625+ } else {
1626+ probingEarlyAbort =
1627+ numDel >
1628+ std::min (HighsInt{1000 }, (model->num_row_ + model->num_col_ ) / 20 );
1629+ }
16171630 if (probingEarlyAbort) break ;
16181631
16191632 // break in case of too many new implications to not spent ages in
@@ -1660,7 +1673,7 @@ HPresolve::Result HPresolve::runProbing(HighsPostsolveStack& postsolve_stack) {
16601673 numDel = newNumDel;
16611674 numFail = 0 ;
16621675 } else if (mipsolver->submip || numNewCliques == 0 ) {
1663- splayContingent -= 100 * numFail;
1676+ splayContingent -= (tightenLimits ? 250 : 100 ) * numFail;
16641677 ++numFail;
16651678 } else {
16661679 splayContingent += 1000 * numNewCliques;
@@ -3727,21 +3740,18 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack,
37273740 rowCoefs.reserve (rowsize[row]);
37283741 rowIndex.reserve (rowsize[row]);
37293742
3730- double deltaDown = model->row_lower_ [row] == -kHighsInf
3731- ? primal_feastol
3732- : options->small_matrix_value ;
3733- double deltaUp = model->row_upper_ [row] == kHighsInf
3734- ? primal_feastol
3735- : options->small_matrix_value ;
3736-
37373743 for (const HighsSliceNonzero& nonz : getStoredRow ()) {
37383744 assert (nonz.value () != 0.0 );
37393745 rowCoefs.push_back (nonz.value ());
37403746 rowIndex.push_back (nonz.index ());
37413747 }
37423748
3743- double intScale =
3744- HighsIntegers::integralScale (rowCoefs, deltaDown, deltaUp);
3749+ double intScale = HighsIntegers::integralScale (
3750+ rowCoefs,
3751+ model->row_lower_ [row] == -kHighsInf ? primal_feastol
3752+ : options->small_matrix_value ,
3753+ model->row_upper_ [row] == kHighsInf ? primal_feastol
3754+ : options->small_matrix_value );
37453755
37463756 auto roundRhs = [&](HighsCDouble rhs, HighsCDouble& roundedRhs,
37473757 HighsCDouble& fractionRhs, double minRhsTightening,
@@ -3902,9 +3912,164 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack,
39023912 }
39033913 }
39043914 }
3915+ } else if (!isRanged (row)) {
3916+ // Chvatal-Gomory strengthening
3917+ // See section 3.4 "Chvatal-Gomory strengthening of inequalities",
3918+ // Achterberg et al., Presolve Reductions in Mixed Integer
3919+ // Programming, INFORMS Journal on Computing 32(2):473-506.
3920+ std::vector<double > roundedRowCoefs;
3921+ roundedRowCoefs.resize (rowsize[row]);
3922+ std::set<double > scalars;
3923+
3924+ // lambda for reformulating row to only contain variables with lower
3925+ // bounds of zero (if possible)
3926+ auto complementOrShift = [&](HighsInt direction, HighsCDouble& rhs,
3927+ double & minAbsCoef, double & maxAbsCoef) {
3928+ minAbsCoef = kHighsInf ;
3929+ maxAbsCoef = 0.0 ;
3930+ for (size_t i = 0 ; i < rowCoefs.size (); ++i) {
3931+ // get column index and (absolute) coefficient
3932+ HighsInt col = rowIndex[i];
3933+ double val = direction * rowCoefs[i];
3934+ double absval = std::abs (val);
3935+ // compute minimum and maximum absolute coefficients along the way
3936+ minAbsCoef = std::min (minAbsCoef, absval);
3937+ maxAbsCoef = std::max (maxAbsCoef, absval);
3938+ if (val < 0.0 && model->col_upper_ [col] != kHighsInf ) {
3939+ // complement
3940+ rhs -= val * static_cast <HighsCDouble>(model->col_upper_ [col]);
3941+ } else if (val > 0.0 && model->col_lower_ [col] != -kHighsInf ) {
3942+ // shift
3943+ rhs -= val * static_cast <HighsCDouble>(model->col_lower_ [col]);
3944+ } else {
3945+ // unbounded variable; cannot shift or complement!
3946+ return false ;
3947+ }
3948+ }
3949+ return true ;
3950+ };
3951+
3952+ // undo shifting and complementation
3953+ auto undoComplementOrShift = [&](HighsInt direction,
3954+ HighsCDouble& roundedRhs) {
3955+ for (size_t i = 0 ; i < rowCoefs.size (); ++i) {
3956+ HighsInt col = rowIndex[i];
3957+ double val = direction * rowCoefs[i];
3958+ if (val < 0.0 && model->col_upper_ [col] != kHighsInf ) {
3959+ // uncomplement
3960+ roundedRowCoefs[i] = -roundedRowCoefs[i];
3961+ roundedRhs += roundedRowCoefs[i] *
3962+ static_cast <HighsCDouble>(model->col_upper_ [col]);
3963+ } else if (val > 0.0 && model->col_lower_ [col] != -kHighsInf ) {
3964+ // unshift
3965+ roundedRhs += roundedRowCoefs[i] *
3966+ static_cast <HighsCDouble>(model->col_lower_ [col]);
3967+ }
3968+ // flip coefficient sign for <= inequality
3969+ roundedRowCoefs[i] *= direction;
3970+ }
3971+ // flip rhs sign for <= inequality
3972+ roundedRhs *= direction;
3973+ };
3974+
3975+ // round row using given scalar
3976+ auto roundRow = [&](double s, const HighsCDouble& rhs,
3977+ HighsCDouble& roundedRhs) {
3978+ bool accept = false ;
3979+ // round rhs (using feasibility tolerance)
3980+ HighsCDouble scalar = static_cast <HighsCDouble>(s);
3981+ roundedRhs = ceil (rhs * scalar - primal_feastol);
3982+ assert (roundedRhs > 0.0 );
3983+ HighsCDouble rhsRatio = rhs / roundedRhs;
3984+
3985+ for (size_t i = 0 ; i < rowCoefs.size (); ++i) {
3986+ // coefficient sign has not been flipped for complemented
3987+ // variables; take absolute value of coefficient.
3988+ double absCoef = std::abs (rowCoefs[i]);
3989+ // round coefficient
3990+ roundedRowCoefs[i] =
3991+ static_cast <double >(ceil (absCoef * scalar - kHighsTiny ));
3992+ // compare "normalised" coefficients, i.e. coefficients divided by
3993+ // corresponding rhs.
3994+ double threshold =
3995+ static_cast <double >(roundedRowCoefs[i] * rhsRatio);
3996+ // return if coefficient is weaker
3997+ if (absCoef < threshold - options->small_matrix_value )
3998+ return false ;
3999+ // accept rounding if at least one coefficient is improved
4000+ accept =
4001+ accept || (absCoef > threshold + options->small_matrix_value );
4002+ }
4003+ return accept;
4004+ };
4005+
4006+ // set up scalars suggested by Achterberg et al.
4007+ auto setScalars = [&](double minAbsCoef, double maxAbsCoef) {
4008+ scalars.clear ();
4009+ scalars.emplace (1.0 );
4010+ for (HighsInt t = 1 ; t <= 5 ; t++) {
4011+ scalars.emplace (t / maxAbsCoef);
4012+ scalars.emplace (t / minAbsCoef);
4013+ scalars.emplace ((2 * t - 1 ) / (2 * minAbsCoef));
4014+ }
4015+ };
4016+
4017+ // try different scalars and return if an improving one was found
4018+ auto rowCanBeTightened = [&](const HighsCDouble& rhs,
4019+ HighsCDouble& roundedRhs) {
4020+ for (double s : scalars) {
4021+ if (roundRow (s, rhs, roundedRhs)) return true ;
4022+ }
4023+ return false ;
4024+ };
4025+
4026+ // replace the model row by the rounded one
4027+ auto updateRow = [&](HighsInt row, HighsInt direction,
4028+ const HighsCDouble& roundedRhs) {
4029+ if (direction < 0 )
4030+ model->row_upper_ [row] = static_cast <double >(roundedRhs);
4031+ else
4032+ model->row_lower_ [row] = static_cast <double >(roundedRhs);
4033+ for (size_t i = 0 ; i < rowCoefs.size (); ++i) {
4034+ double delta = static_cast <double >(
4035+ static_cast <HighsCDouble>(roundedRowCoefs[i]) - rowCoefs[i]);
4036+ if (std::fabs (delta) > options->small_matrix_value )
4037+ addToMatrix (row, rowIndex[i], delta);
4038+ }
4039+ };
4040+
4041+ // convert to >= inequality
4042+ // direction = -1: <= constraint, direction = 1: >= constraint
4043+ HighsInt direction =
4044+ model->row_upper_ [row] != kHighsInf ? HighsInt{-1 } : HighsInt{1 };
4045+ // get rhs
4046+ HighsCDouble rhs =
4047+ direction < 0 ? -model->row_upper_ [row] : model->row_lower_ [row];
4048+
4049+ // initialise
4050+ HighsCDouble roundedRhs = 0.0 ;
4051+ double minAbsCoef = 0.0 ;
4052+ double maxAbsCoef = 0.0 ;
4053+
4054+ // complement or shift variables to have lower bounds of zero
4055+ if (complementOrShift (direction, rhs, minAbsCoef, maxAbsCoef)) {
4056+ // identify scalars for row
4057+ setScalars (minAbsCoef, maxAbsCoef);
4058+ // find a scalar that produces improved coefficients
4059+ if (rowCanBeTightened (rhs, roundedRhs)) {
4060+ // undo complementation and shifting
4061+ undoComplementOrShift (direction, roundedRhs);
4062+ // replace row by rounded one
4063+ updateRow (row, direction, roundedRhs);
4064+ }
4065+ }
39054066 }
39064067 }
39074068
4069+ // check for redundancy again
4070+ HPRESOLVE_CHECKED_CALL (checkRowRedundant (row));
4071+ if (rowDeleted[row]) return Result::kOk ;
4072+
39084073 auto strengthenCoefs = [&](HighsCDouble& rhs, HighsInt direction,
39094074 HighsCDouble maxAbsCoefValue) {
39104075 // iterate over non-zero positions instead of iterating over the
@@ -4172,9 +4337,7 @@ HPresolve::Result HPresolve::colPresolve(HighsPostsolveStack& postsolve_stack,
41724337 HighsInt direction,
41734338 bool isBoundImplied, HighsInt numInf) {
41744339 if (isBoundImplied && row != -1 && numInf == 1 &&
4175- direction * model->col_cost_ [col] >= 0 &&
4176- (model->row_lower_ [row] == -kHighsInf ||
4177- model->row_upper_ [row] == kHighsInf )) {
4340+ direction * model->col_cost_ [col] >= 0 && !isRanged (row)) {
41784341 HighsInt nzPos = findNonzero (row, col);
41794342
41804343 if (model->integrality_ [col] != HighsVarType::kInteger ||
@@ -5551,9 +5714,7 @@ HPresolve::Result HPresolve::strengthenInequalities(
55515714
55525715 for (HighsInt row = 0 ; row != model->num_row_ ; ++row) {
55535716 if (rowsize[row] <= 1 ) continue ;
5554- if (model->row_lower_ [row] != -kHighsInf &&
5555- model->row_upper_ [row] != kHighsInf )
5556- continue ;
5717+ if (isRanged (row)) continue ;
55575718
55585719 // do not run on very dense rows as this could get expensive
55595720 HighsInt rowsize_limit =
0 commit comments