@@ -3727,21 +3727,18 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack,
37273727 rowCoefs.reserve (rowsize[row]);
37283728 rowIndex.reserve (rowsize[row]);
37293729
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-
37373730 for (const HighsSliceNonzero& nonz : getStoredRow ()) {
37383731 assert (nonz.value () != 0.0 );
37393732 rowCoefs.push_back (nonz.value ());
37403733 rowIndex.push_back (nonz.index ());
37413734 }
37423735
3743- double intScale =
3744- HighsIntegers::integralScale (rowCoefs, deltaDown, deltaUp);
3736+ double intScale = HighsIntegers::integralScale (
3737+ rowCoefs,
3738+ model->row_lower_ [row] == -kHighsInf ? primal_feastol
3739+ : options->small_matrix_value ,
3740+ model->row_upper_ [row] == kHighsInf ? primal_feastol
3741+ : options->small_matrix_value );
37453742
37463743 auto roundRhs = [&](HighsCDouble rhs, HighsCDouble& roundedRhs,
37473744 HighsCDouble& fractionRhs, double minRhsTightening,
@@ -3904,26 +3901,32 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack,
39043901 }
39053902 } else if (model->row_lower_ [row] == -kHighsInf ||
39063903 model->row_upper_ [row] == kHighsInf ) {
3907- // try Chvatal-Gomory strengthening
3908- std::vector<HighsInt> complementedOrShifted;
3909- complementedOrShifted.resize (rowCoefs.size ());
3910-
3911- HighsInt direction =
3912- model->row_upper_ [row] != kHighsInf ? HighsInt{1 } : HighsInt{-1 };
3913- HighsCDouble rhs =
3914- direction > 0 ? model->row_upper_ [row] : -model->row_lower_ [row];
3915-
3916- auto complementOrShift = [&]() {
3904+ // Chvatal-Gomory strengthening
3905+ std::vector<double > roundedRowCoefs;
3906+ roundedRowCoefs.resize (rowsize[row]);
3907+ std::set<double > scalars;
3908+
3909+ // lambda for reformulating row to only contain variables with a lower
3910+ // bound of zero (if possible)
3911+ auto complementOrShift = [&](HighsInt direction, HighsCDouble& rhs,
3912+ double & minAbsCoef, double & maxAbsCoef) {
3913+ minAbsCoef = kHighsInf ;
3914+ maxAbsCoef = 0.0 ;
39173915 for (size_t i = 0 ; i < rowCoefs.size (); ++i) {
3916+ // get column index and (absolute) coefficient
39183917 HighsInt col = rowIndex[i];
39193918 double val = direction * rowCoefs[i];
3919+ double absval = std::abs (val);
3920+ // compute minimum and maximum absolute coefficients along the way
3921+ minAbsCoef = std::min (minAbsCoef, absval);
3922+ maxAbsCoef = std::max (maxAbsCoef, absval);
39203923 if (val < 0.0 && model->col_upper_ [col] != kHighsInf ) {
3924+ // complement
39213925 rhs -= val * static_cast <HighsCDouble>(model->col_upper_ [col]);
39223926 rowCoefs[i] = -rowCoefs[i];
3923- complementedOrShifted[i] = -1 ;
39243927 } else if (val > 0.0 && model->col_lower_ [col] != -kHighsInf ) {
3928+ // shift
39253929 rhs -= val * static_cast <HighsCDouble>(model->col_lower_ [col]);
3926- complementedOrShifted[i] = 1 ;
39273930 } else {
39283931 // unbounded variable; cannot shift or complement!
39293932 return false ;
@@ -3932,11 +3935,122 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack,
39323935 return true ;
39333936 };
39343937
3935- if (complementOrShift ()) {
3938+ // undo shifting and complementation
3939+ auto undoComplementOrShift = [&](HighsInt direction,
3940+ HighsCDouble& roundedRhs) {
3941+ for (size_t i = 0 ; i < rowCoefs.size (); ++i) {
3942+ HighsInt col = rowIndex[i];
3943+ double val = direction * rowCoefs[i];
3944+ if (val < 0.0 && model->col_upper_ [col] != kHighsInf ) {
3945+ // un-complement
3946+ roundedRowCoefs[i] = -roundedRowCoefs[i];
3947+ roundedRhs += roundedRowCoefs[i] *
3948+ static_cast <HighsCDouble>(model->col_upper_ [col]);
3949+ } else if (val > 0.0 && model->col_lower_ [col] != -kHighsInf ) {
3950+ // unshift
3951+ roundedRhs += roundedRowCoefs[i] *
3952+ static_cast <HighsCDouble>(model->col_lower_ [col]);
3953+ }
3954+ // flip coefficient sign for >= inequality
3955+ roundedRowCoefs[i] *= direction;
3956+ }
3957+ // flip rhs sign for >= inequality
3958+ roundedRhs *= direction;
3959+ };
3960+
3961+ // round row using given scalar
3962+ auto roundRow = [&](double s, const HighsCDouble& rhs,
3963+ HighsCDouble& roundedRhs) {
3964+ bool accept = false ;
3965+ // round rhs (using feasibility tolerance)
3966+ roundedRhs = floor (rhs * s + primal_feastol);
3967+ for (size_t i = 0 ; i < rowCoefs.size (); ++i) {
3968+ // round coefficient
3969+ roundedRowCoefs[i] = std::floor (rowCoefs[i] * s + kHighsTiny );
3970+ // compute "normalised" coefficients, i.e. coefficient divided by
3971+ // rhs
3972+ double normalisedCoef =
3973+ static_cast <double >(rowCoefs[i] * roundedRhs);
3974+ double normalisedRoundedCoef =
3975+ static_cast <double >(roundedRowCoefs[i] * rhs);
3976+ // return if coefficient is weaker
3977+ if (normalisedRoundedCoef <
3978+ normalisedCoef - options->small_matrix_value )
3979+ return false ;
3980+ // accept rounding if at least one coefficient is improved
3981+ accept = accept || (normalisedRoundedCoef >
3982+ normalisedCoef + options->small_matrix_value );
3983+ }
3984+ return accept;
3985+ };
3986+
3987+ // set up scalars suggested by Achterberg et al.
3988+ auto setScalars = [&](double minAbsCoef, double maxAbsCoef) {
3989+ scalars.clear ();
3990+ scalars.emplace (1.0 );
3991+ for (HighsInt t = 1 ; t <= 5 ; t++) {
3992+ scalars.emplace (t / maxAbsCoef);
3993+ scalars.emplace (t / minAbsCoef);
3994+ scalars.emplace ((2 * t - 1 ) / (2 * minAbsCoef));
3995+ }
3996+ };
3997+
3998+ // try different scalars and return an improving one
3999+ auto scalarToTightenRow = [&](const HighsCDouble& rhs,
4000+ HighsCDouble& roundedRhs) {
4001+ for (double s : scalars) {
4002+ if (roundRow (s, rhs, roundedRhs)) return s;
4003+ }
4004+ return 0.0 ;
4005+ };
4006+
4007+ // replace the model row by the rounded one
4008+ auto updateRow = [&](HighsInt row, HighsInt direction,
4009+ HighsCDouble roundedRhs) {
4010+ if (direction > 0 )
4011+ model->row_upper_ [row] = static_cast <double >(roundedRhs);
4012+ else
4013+ model->row_lower_ [row] = static_cast <double >(roundedRhs);
4014+ for (size_t i = 0 ; i < rowCoefs.size (); ++i) {
4015+ double delta = static_cast <double >(
4016+ static_cast <HighsCDouble>(roundedRowCoefs[i]) - rowCoefs[i]);
4017+ if (std::fabs (delta) > options->small_matrix_value )
4018+ addToMatrix (row, rowIndex[i], delta);
4019+ }
4020+ };
4021+
4022+ // direction = 1: <= constraint, direction = -1: >= constraint
4023+ HighsInt direction =
4024+ model->row_upper_ [row] != kHighsInf ? HighsInt{1 } : HighsInt{-1 };
4025+ // get rhs
4026+ HighsCDouble rhs =
4027+ direction > 0 ? model->row_upper_ [row] : -model->row_lower_ [row];
4028+
4029+ // initialise
4030+ HighsCDouble roundedRhs = 0.0 ;
4031+ double minAbsCoef = 0.0 ;
4032+ double maxAbsCoef = 0.0 ;
4033+
4034+ // complement or shift variables to have lower bounds of zero
4035+ if (complementOrShift (direction, rhs, minAbsCoef, maxAbsCoef)) {
4036+ // identify scalars for row
4037+ setScalars (minAbsCoef, maxAbsCoef);
4038+ // find a scalar that produces improved coefficients
4039+ double s = scalarToTightenRow (rhs, roundedRhs);
4040+ if (s != 0.0 ) {
4041+ // undo complementation and shifting
4042+ undoComplementOrShift (direction, roundedRhs);
4043+ // replace row by rounded one
4044+ updateRow (row, direction, roundedRhs);
4045+ }
39364046 }
39374047 }
39384048 }
39394049
4050+ // check for redundancy again
4051+ HPRESOLVE_CHECKED_CALL (checkRowRedundant (row));
4052+ if (rowDeleted[row]) return Result::kOk ;
4053+
39404054 auto strengthenCoefs = [&](HighsCDouble& rhs, HighsInt direction,
39414055 HighsCDouble maxAbsCoefValue) {
39424056 // iterate over non-zero positions instead of iterating over the
0 commit comments