@@ -3827,9 +3827,19 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack,
38273827 // baseiRUpper);
38283828
38293829 auto checkForcingRow = [&](HighsInt row, HighsInt direction, double rowSide,
3830+ double impliedRowBound, double minAbsCoef,
38303831 HighsPostsolveStack::RowType rowType) {
3831- // store row
3832- storeRow (row);
3832+ // 1. direction = 1 (>=): forcing row if upper bound on constraint activity
3833+ // is equal to row's lower bound
3834+ // 2. direction = -1 (<=): forcing row if lower bound on constraint activity
3835+ // is equal to row's upper bound
3836+ // scale tolerance (equivalent to scaling row to have minimum absolute
3837+ // coefficient of 1)
3838+ if (direction * impliedRowBound >
3839+ direction * rowSide + primal_feastol * std::min (1.0 , minAbsCoef))
3840+ return Result::kOk ;
3841+
3842+ // get stored row
38333843 auto rowVector = getStoredRow ();
38343844
38353845 HighsInt nfixings = 0 ;
@@ -3905,29 +3915,25 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack,
39053915
39063916 if (analysis_.allow_rule_ [kPresolveRuleForcingRow ]) {
39073917 // Allow rule to consider forcing rows
3908- if (impliedRowUpper <= // check for forcing row on the row lower bound
3909- model->row_lower_ [row] + primal_feastol) {
3910- // the row upper bound that is implied by the column bounds is equal to
3911- // the row lower bound there for we can fix all columns at their bound
3912- // as this is the only feasible assignment for this row and then find a
3913- // suitable dual multiplier in postsolve. First we store the row on the
3914- // postsolve stack (forcingRow() call) afterwards we store each column
3915- // fixing on the postsolve stack. As the postsolve goes over the stack
3916- // in reverse, it will first restore the column primal and dual values
3917- // as the dual values are required to find the proper dual multiplier for
3918- // the row and the column that we put in the basis.
3919- HPRESOLVE_CHECKED_CALL (
3920- checkForcingRow (row, HighsInt{1 }, model->row_lower_ [row],
3921- HighsPostsolveStack::RowType::kGeq ));
3922- if (rowDeleted[row]) return Result::kOk ;
3923-
3924- } else if (impliedRowLower >= model->row_upper_ [row] - primal_feastol) {
3925- // forcing row in the other direction
3926- HPRESOLVE_CHECKED_CALL (
3927- checkForcingRow (row, HighsInt{-1 }, model->row_upper_ [row],
3928- HighsPostsolveStack::RowType::kLeq ));
3929- if (rowDeleted[row]) return Result::kOk ;
3918+
3919+ // store row and compute minimum absolute coefficient
3920+ storeRow (row);
3921+ double minAbsCoef = kHighsInf ;
3922+ for (const HighsSliceNonzero& nonzero : getStoredRow ()) {
3923+ minAbsCoef = std::min (minAbsCoef, std::abs (nonzero.value ()));
39303924 }
3925+
3926+ // >= inequality
3927+ HPRESOLVE_CHECKED_CALL (checkForcingRow (
3928+ row, HighsInt{1 }, model->row_lower_ [row], impliedRowUpper, minAbsCoef,
3929+ HighsPostsolveStack::RowType::kGeq ));
3930+ if (rowDeleted[row]) return Result::kOk ;
3931+
3932+ // <= inequality
3933+ HPRESOLVE_CHECKED_CALL (checkForcingRow (
3934+ row, HighsInt{-1 }, model->row_upper_ [row], impliedRowLower, minAbsCoef,
3935+ HighsPostsolveStack::RowType::kLeq ));
3936+ if (rowDeleted[row]) return Result::kOk ;
39313937 }
39323938
39333939 // implied bounds can only be computed when row bounds are available and
0 commit comments