@@ -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
@@ -3727,21 +3732,18 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack,
37273732 rowCoefs.reserve (rowsize[row]);
37283733 rowIndex.reserve (rowsize[row]);
37293734
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-
37373735 for (const HighsSliceNonzero& nonz : getStoredRow ()) {
37383736 assert (nonz.value () != 0.0 );
37393737 rowCoefs.push_back (nonz.value ());
37403738 rowIndex.push_back (nonz.index ());
37413739 }
37423740
3743- double intScale =
3744- HighsIntegers::integralScale (rowCoefs, deltaDown, deltaUp);
3741+ double intScale = HighsIntegers::integralScale (
3742+ rowCoefs,
3743+ model->row_lower_ [row] == -kHighsInf ? primal_feastol
3744+ : options->small_matrix_value ,
3745+ model->row_upper_ [row] == kHighsInf ? primal_feastol
3746+ : options->small_matrix_value );
37453747
37463748 auto roundRhs = [&](HighsCDouble rhs, HighsCDouble& roundedRhs,
37473749 HighsCDouble& fractionRhs, double minRhsTightening,
@@ -3902,9 +3904,164 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack,
39023904 }
39033905 }
39043906 }
3907+ } else if (!isRanged (row)) {
3908+ // Chvatal-Gomory strengthening
3909+ // See section 3.4 "Chvatal-Gomory strengthening of inequalities",
3910+ // Achterberg et al., Presolve Reductions in Mixed Integer
3911+ // Programming, INFORMS Journal on Computing 32(2):473-506.
3912+ std::vector<double > roundedRowCoefs;
3913+ roundedRowCoefs.resize (rowsize[row]);
3914+ std::set<double > scalars;
3915+
3916+ // lambda for reformulating row to only contain variables with lower
3917+ // bounds of zero (if possible)
3918+ auto complementOrShift = [&](HighsInt direction, HighsCDouble& rhs,
3919+ double & minAbsCoef, double & maxAbsCoef) {
3920+ minAbsCoef = kHighsInf ;
3921+ maxAbsCoef = 0.0 ;
3922+ for (size_t i = 0 ; i < rowCoefs.size (); ++i) {
3923+ // get column index and (absolute) coefficient
3924+ HighsInt col = rowIndex[i];
3925+ double val = direction * rowCoefs[i];
3926+ double absval = std::abs (val);
3927+ // compute minimum and maximum absolute coefficients along the way
3928+ minAbsCoef = std::min (minAbsCoef, absval);
3929+ maxAbsCoef = std::max (maxAbsCoef, absval);
3930+ if (val < 0.0 && model->col_upper_ [col] != kHighsInf ) {
3931+ // complement
3932+ rhs -= val * static_cast <HighsCDouble>(model->col_upper_ [col]);
3933+ } else if (val > 0.0 && model->col_lower_ [col] != -kHighsInf ) {
3934+ // shift
3935+ rhs -= val * static_cast <HighsCDouble>(model->col_lower_ [col]);
3936+ } else {
3937+ // unbounded variable; cannot shift or complement!
3938+ return false ;
3939+ }
3940+ }
3941+ return true ;
3942+ };
3943+
3944+ // undo shifting and complementation
3945+ auto undoComplementOrShift = [&](HighsInt direction,
3946+ HighsCDouble& roundedRhs) {
3947+ for (size_t i = 0 ; i < rowCoefs.size (); ++i) {
3948+ HighsInt col = rowIndex[i];
3949+ double val = direction * rowCoefs[i];
3950+ if (val < 0.0 && model->col_upper_ [col] != kHighsInf ) {
3951+ // uncomplement
3952+ roundedRowCoefs[i] = -roundedRowCoefs[i];
3953+ roundedRhs += roundedRowCoefs[i] *
3954+ static_cast <HighsCDouble>(model->col_upper_ [col]);
3955+ } else if (val > 0.0 && model->col_lower_ [col] != -kHighsInf ) {
3956+ // unshift
3957+ roundedRhs += roundedRowCoefs[i] *
3958+ static_cast <HighsCDouble>(model->col_lower_ [col]);
3959+ }
3960+ // flip coefficient sign for <= inequality
3961+ roundedRowCoefs[i] *= direction;
3962+ }
3963+ // flip rhs sign for <= inequality
3964+ roundedRhs *= direction;
3965+ };
3966+
3967+ // round row using given scalar
3968+ auto roundRow = [&](double s, const HighsCDouble& rhs,
3969+ HighsCDouble& roundedRhs) {
3970+ bool accept = false ;
3971+ // round rhs (using feasibility tolerance)
3972+ HighsCDouble scalar = static_cast <HighsCDouble>(s);
3973+ roundedRhs = ceil (rhs * scalar - primal_feastol);
3974+ assert (roundedRhs > 0.0 );
3975+ HighsCDouble rhsRatio = rhs / roundedRhs;
3976+
3977+ for (size_t i = 0 ; i < rowCoefs.size (); ++i) {
3978+ // coefficient sign has not been flipped for complemented
3979+ // variables; take absolute value of coefficient.
3980+ double absCoef = std::abs (rowCoefs[i]);
3981+ // round coefficient
3982+ roundedRowCoefs[i] =
3983+ static_cast <double >(ceil (absCoef * scalar - kHighsTiny ));
3984+ // compare "normalised" coefficients, i.e. coefficients divided by
3985+ // corresponding rhs.
3986+ double threshold =
3987+ static_cast <double >(roundedRowCoefs[i] * rhsRatio);
3988+ // return if coefficient is weaker
3989+ if (absCoef < threshold - options->small_matrix_value )
3990+ return false ;
3991+ // accept rounding if at least one coefficient is improved
3992+ accept =
3993+ accept || (absCoef > threshold + options->small_matrix_value );
3994+ }
3995+ return accept;
3996+ };
3997+
3998+ // set up scalars suggested by Achterberg et al.
3999+ auto setScalars = [&](double minAbsCoef, double maxAbsCoef) {
4000+ scalars.clear ();
4001+ scalars.emplace (1.0 );
4002+ for (HighsInt t = 1 ; t <= 5 ; t++) {
4003+ scalars.emplace (t / maxAbsCoef);
4004+ scalars.emplace (t / minAbsCoef);
4005+ scalars.emplace ((2 * t - 1 ) / (2 * minAbsCoef));
4006+ }
4007+ };
4008+
4009+ // try different scalars and return if an improving one was found
4010+ auto rowCanBeTightened = [&](const HighsCDouble& rhs,
4011+ HighsCDouble& roundedRhs) {
4012+ for (double s : scalars) {
4013+ if (roundRow (s, rhs, roundedRhs)) return true ;
4014+ }
4015+ return false ;
4016+ };
4017+
4018+ // replace the model row by the rounded one
4019+ auto updateRow = [&](HighsInt row, HighsInt direction,
4020+ const HighsCDouble& roundedRhs) {
4021+ if (direction < 0 )
4022+ model->row_upper_ [row] = static_cast <double >(roundedRhs);
4023+ else
4024+ model->row_lower_ [row] = static_cast <double >(roundedRhs);
4025+ for (size_t i = 0 ; i < rowCoefs.size (); ++i) {
4026+ double delta = static_cast <double >(
4027+ static_cast <HighsCDouble>(roundedRowCoefs[i]) - rowCoefs[i]);
4028+ if (std::fabs (delta) > options->small_matrix_value )
4029+ addToMatrix (row, rowIndex[i], delta);
4030+ }
4031+ };
4032+
4033+ // convert to >= inequality
4034+ // direction = -1: <= constraint, direction = 1: >= constraint
4035+ HighsInt direction =
4036+ model->row_upper_ [row] != kHighsInf ? HighsInt{-1 } : HighsInt{1 };
4037+ // get rhs
4038+ HighsCDouble rhs =
4039+ direction < 0 ? -model->row_upper_ [row] : model->row_lower_ [row];
4040+
4041+ // initialise
4042+ HighsCDouble roundedRhs = 0.0 ;
4043+ double minAbsCoef = 0.0 ;
4044+ double maxAbsCoef = 0.0 ;
4045+
4046+ // complement or shift variables to have lower bounds of zero
4047+ if (complementOrShift (direction, rhs, minAbsCoef, maxAbsCoef)) {
4048+ // identify scalars for row
4049+ setScalars (minAbsCoef, maxAbsCoef);
4050+ // find a scalar that produces improved coefficients
4051+ if (rowCanBeTightened (rhs, roundedRhs)) {
4052+ // undo complementation and shifting
4053+ undoComplementOrShift (direction, roundedRhs);
4054+ // replace row by rounded one
4055+ updateRow (row, direction, roundedRhs);
4056+ }
4057+ }
39054058 }
39064059 }
39074060
4061+ // check for redundancy again
4062+ HPRESOLVE_CHECKED_CALL (checkRowRedundant (row));
4063+ if (rowDeleted[row]) return Result::kOk ;
4064+
39084065 auto strengthenCoefs = [&](HighsCDouble& rhs, HighsInt direction,
39094066 HighsCDouble maxAbsCoefValue) {
39104067 // iterate over non-zero positions instead of iterating over the
@@ -4172,9 +4329,7 @@ HPresolve::Result HPresolve::colPresolve(HighsPostsolveStack& postsolve_stack,
41724329 HighsInt direction,
41734330 bool isBoundImplied, HighsInt numInf) {
41744331 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 )) {
4332+ direction * model->col_cost_ [col] >= 0 && !isRanged (row)) {
41784333 HighsInt nzPos = findNonzero (row, col);
41794334
41804335 if (model->integrality_ [col] != HighsVarType::kInteger ||
@@ -5551,9 +5706,7 @@ HPresolve::Result HPresolve::strengthenInequalities(
55515706
55525707 for (HighsInt row = 0 ; row != model->num_row_ ; ++row) {
55535708 if (rowsize[row] <= 1 ) continue ;
5554- if (model->row_lower_ [row] != -kHighsInf &&
5555- model->row_upper_ [row] != kHighsInf )
5556- continue ;
5709+ if (isRanged (row)) continue ;
55575710
55585711 // do not run on very dense rows as this could get expensive
55595712 HighsInt rowsize_limit =
0 commit comments