@@ -184,11 +184,25 @@ bool HPresolve::isLowerImplied(HighsInt col) const {
184184 implColLower[col] >= model->col_lower_ [col] - primal_feastol);
185185}
186186
187+ bool HPresolve::isLowerStrictlyImplied (HighsInt col, double * tolerance) const {
188+ return (model->col_lower_ [col] == -kHighsInf ||
189+ implColLower[col] >
190+ model->col_lower_ [col] +
191+ (tolerance != nullptr ? *tolerance : primal_feastol));
192+ }
193+
187194bool HPresolve::isUpperImplied (HighsInt col) const {
188195 return (model->col_upper_ [col] == kHighsInf ||
189196 implColUpper[col] <= model->col_upper_ [col] + primal_feastol);
190197}
191198
199+ bool HPresolve::isUpperStrictlyImplied (HighsInt col, double * tolerance) const {
200+ return (model->col_upper_ [col] == kHighsInf ||
201+ implColUpper[col] <
202+ model->col_upper_ [col] -
203+ (tolerance != nullptr ? *tolerance : primal_feastol));
204+ }
205+
192206bool HPresolve::isImpliedFree (HighsInt col) const {
193207 return isLowerImplied (col) && isUpperImplied (col);
194208}
@@ -527,26 +541,43 @@ double HPresolve::getMaxAbsRowVal(HighsInt row) const {
527541 return maxVal;
528542}
529543
530- void HPresolve::updateRowDualImpliedBounds (HighsInt row, HighsInt col,
531- double val) {
532- // propagate implied row dual bound
544+ bool HPresolve::checkUpdateRowDualImpliedBounds (HighsInt col,
545+ double * dualRowLower,
546+ double * dualRowUpper) const {
547+ // check if implied bounds of row duals in given column can be updated (i.e.
548+ // dual row has finite bounds and number of infinite contributions to
549+ // corresponding activity bounds is at most one)
550+
533551 // if the column has an infinite lower bound the reduced cost cannot be
534552 // positive, i.e. the column corresponds to a <= constraint in the dual with
535553 // right hand side -cost which becomes a >= constraint with side +cost.
536554 // Furthermore, we can ignore strictly redundant primal
537555 // column bounds and treat them as if they are infinite
538556 double impliedMargin = colsize[col] != 1 ? primal_feastol : -primal_feastol;
539- double dualRowLower =
540- (model->col_lower_ [col] == -kHighsInf ) ||
541- (implColLower[col] > model->col_lower_ [col] + impliedMargin)
542- ? model->col_cost_ [col]
543- : -kHighsInf ;
544-
545- double dualRowUpper =
546- (model->col_upper_ [col] == kHighsInf ) ||
547- (implColUpper[col] < model->col_upper_ [col] - impliedMargin)
548- ? model->col_cost_ [col]
549- : kHighsInf ;
557+
558+ double myDualRowLower = isLowerStrictlyImplied (col, &impliedMargin)
559+ ? model->col_cost_ [col]
560+ : -kHighsInf ;
561+
562+ double myDualRowUpper = isUpperStrictlyImplied (col, &impliedMargin)
563+ ? model->col_cost_ [col]
564+ : kHighsInf ;
565+
566+ if (dualRowLower != nullptr ) *dualRowLower = myDualRowLower;
567+ if (dualRowUpper != nullptr ) *dualRowUpper = myDualRowUpper;
568+
569+ return (myDualRowLower != -kHighsInf &&
570+ impliedDualRowBounds.getNumInfSumUpperOrig (col) <= 1 ) ||
571+ (myDualRowUpper != kHighsInf &&
572+ impliedDualRowBounds.getNumInfSumLowerOrig (col) <= 1 );
573+ }
574+
575+ void HPresolve::updateRowDualImpliedBounds (HighsInt row, HighsInt col,
576+ double val) {
577+ // propagate implied row dual bound
578+ double dualRowLower, dualRowUpper;
579+ if (!checkUpdateRowDualImpliedBounds (col, &dualRowLower, &dualRowUpper))
580+ return ;
550581
551582 const double threshold = 1000 * options->dual_feasibility_tolerance ;
552583
@@ -584,15 +615,39 @@ void HPresolve::updateRowDualImpliedBounds(HighsInt row, HighsInt col,
584615 HighsInt{-1 });
585616}
586617
618+ void HPresolve::updateRowDualImpliedBounds (HighsInt col) {
619+ // update dual implied bounds of all rows in given column
620+ assert (col >= 0 && col < model->num_col_ );
621+ if (!checkUpdateRowDualImpliedBounds (col)) return ;
622+ for (const HighsSliceNonzero& nonzero : getColumnVector (col))
623+ updateRowDualImpliedBounds (nonzero.index (), col, nonzero.value ());
624+ }
625+
626+ bool HPresolve::checkUpdateColImpliedBounds (HighsInt row, double * rowLower,
627+ double * rowUpper) const {
628+ // check if implied bounds of columns in given row can be updated (i.e. if
629+ // row's left-hand or right-hand side is finite and number of infinite
630+ // contributions to corresponding activity bounds is at most one)
631+ double myRowLower = isImpliedEquationAtUpper (row) ? model->row_upper_ [row]
632+ : model->row_lower_ [row];
633+ double myRowUpper = isImpliedEquationAtLower (row) ? model->row_lower_ [row]
634+ : model->row_upper_ [row];
635+ assert (myRowLower != kHighsInf );
636+ assert (myRowUpper != -kHighsInf );
637+
638+ if (rowLower != nullptr ) *rowLower = myRowLower;
639+ if (rowUpper != nullptr ) *rowUpper = myRowUpper;
640+
641+ return (myRowLower != -kHighsInf &&
642+ impliedRowBounds.getNumInfSumUpperOrig (row) <= 1 ) ||
643+ (myRowUpper != kHighsInf &&
644+ impliedRowBounds.getNumInfSumLowerOrig (row) <= 1 );
645+ }
646+
587647void HPresolve::updateColImpliedBounds (HighsInt row, HighsInt col, double val) {
588648 // propagate implied column bound upper bound if row has an upper bound
589- double rowUpper = isImpliedEquationAtLower (row) ? model->row_lower_ [row]
590- : model->row_upper_ [row];
591- double rowLower = isImpliedEquationAtUpper (row) ? model->row_upper_ [row]
592- : model->row_lower_ [row];
593-
594- assert (rowLower != kHighsInf );
595- assert (rowUpper != -kHighsInf );
649+ double rowLower, rowUpper;
650+ if (!checkUpdateColImpliedBounds (row, &rowLower, &rowUpper)) return ;
596651
597652 const double threshold = 1000 * primal_feastol;
598653
@@ -667,6 +722,14 @@ void HPresolve::updateColImpliedBounds(HighsInt row, HighsInt col, double val) {
667722 HighsInt{-1 });
668723}
669724
725+ void HPresolve::updateColImpliedBounds (HighsInt row) {
726+ // update implied bounds of all columns in given row
727+ assert (row >= 0 && row < model->num_row_ );
728+ if (!checkUpdateColImpliedBounds (row)) return ;
729+ for (const HighsSliceNonzero& nonzero : getRowVector (row))
730+ updateColImpliedBounds (row, nonzero.index (), nonzero.value ());
731+ }
732+
670733void HPresolve::resetColImpliedBounds (HighsInt col, HighsInt row) {
671734 assert (row == -1 || colLowerSource[col] == row || colUpperSource[col] == row);
672735 if (!colDeleted[col]) {
@@ -4023,16 +4086,8 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack,
40234086 if (rowDeleted[row]) return Result::kOk ;
40244087 }
40254088
4026- // implied bounds can only be computed when row bounds are available and
4027- // bounds on activity contain at most one infinite bound
4028- if (((model->row_upper_ [row] != kHighsInf || isImpliedEquationAtLower (row)) &&
4029- impliedRowBounds.getNumInfSumLowerOrig (row) <= 1 ) ||
4030- ((model->row_lower_ [row] != -kHighsInf ||
4031- isImpliedEquationAtUpper (row)) &&
4032- impliedRowBounds.getNumInfSumUpperOrig (row) <= 1 )) {
4033- for (const HighsSliceNonzero& nonzero : getRowVector (row))
4034- updateColImpliedBounds (row, nonzero.index (), nonzero.value ());
4035- }
4089+ // update implied bounds of all columns in given row
4090+ updateColImpliedBounds (row);
40364091
40374092 return checkLimits (postsolve_stack);
40384093}
@@ -4110,53 +4165,42 @@ HPresolve::Result HPresolve::colPresolve(HighsPostsolveStack& postsolve_stack,
41104165
41114166 // column is not (weakly) dominated
41124167
4113- // the associated dual constraint has an upper bound if there is an infinite
4114- // or redundant column lower bound as then the reduced cost of the column must
4115- // not be positive i.e. <= 0
4116- bool dualConsHasUpper = isUpperImplied (col);
4117- bool dualConsHasLower = isLowerImplied (col);
4118-
41194168 // integer columns cannot be used to tighten bounds on dual multipliers
41204169 if (mipsolver != nullptr ) {
4121- if (dualConsHasLower && colLowerSource[col] != -1 &&
4122- impliedDualRowBounds.getNumInfSumUpperOrig (col) == 1 &&
4123- model->col_cost_ [col] >= 0 ) {
4124- HighsInt row = colLowerSource[col];
4125-
4126- if (model->row_lower_ [row] == -kHighsInf ||
4127- model->row_upper_ [row] == kHighsInf ) {
4170+ // lambda for changing implied row dual
4171+ auto modifyImpliedRowDualBound = [&](HighsInt col, HighsInt row,
4172+ HighsInt direction,
4173+ bool isBoundImplied, HighsInt numInf) {
4174+ 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 )) {
41284178 HighsInt nzPos = findNonzero (row, col);
41294179
41304180 if (model->integrality_ [col] != HighsVarType::kInteger ||
41314181 (rowsizeInteger[row] == rowsize[row] &&
41324182 rowCoefficientsIntegral (row, 1.0 / Avalue[nzPos]))) {
4133- if (Avalue[nzPos] > 0 )
4183+ if (direction * Avalue[nzPos] > 0 )
41344184 changeImplRowDualLower (row, 0.0 , col);
41354185 else
41364186 changeImplRowDualUpper (row, 0.0 , col);
41374187 }
41384188 }
4139- }
4189+ };
41404190
4141- if (dualConsHasUpper && colUpperSource[col] != -1 &&
4142- impliedDualRowBounds.getNumInfSumLowerOrig (col) == 1 &&
4143- model->col_cost_ [col] <= 0 ) {
4144- HighsInt row = colUpperSource[col];
4191+ // if there is an infinite or redundant column lower bound, the reduced
4192+ // cost of the column must not be positive (i.e. <= 0; the upper bound on
4193+ // the reduced cost is zero).
4194+ modifyImpliedRowDualBound (col, colLowerSource[col], HighsInt{1 },
4195+ isLowerImplied (col),
4196+ impliedDualRowBounds.getNumInfSumUpperOrig (col));
41454197
4146- if (model->row_lower_ [row] == -kHighsInf ||
4147- model->row_upper_ [row] == kHighsInf ) {
4148- HighsInt nzPos = findNonzero (row, col);
4149-
4150- if (model->integrality_ [col] != HighsVarType::kInteger ||
4151- (rowsizeInteger[row] == rowsize[row] &&
4152- rowCoefficientsIntegral (row, 1.0 / Avalue[nzPos]))) {
4153- if (Avalue[nzPos] > 0 )
4154- changeImplRowDualUpper (row, 0.0 , col);
4155- else
4156- changeImplRowDualLower (row, 0.0 , col);
4157- }
4158- }
4159- }
4198+ // if there is an infinite or redundant column upper bound, the reduced
4199+ // cost of the column must not be negative (i.e. >= 0; the lower bound on
4200+ // the reduced cost is zero).
4201+ modifyImpliedRowDualBound (col, colUpperSource[col], HighsInt{-1 },
4202+ isUpperImplied (col),
4203+ impliedDualRowBounds.getNumInfSumLowerOrig (col));
41604204
41614205 HPRESOLVE_CHECKED_CALL (static_cast <Result>(convertImpliedInteger (col)));
41624206
@@ -4180,12 +4224,8 @@ HPresolve::Result HPresolve::colPresolve(HighsPostsolveStack& postsolve_stack,
41804224 if (model->integrality_ [col] == HighsVarType::kInteger ) return Result::kOk ;
41814225 }
41824226
4183- // now check if we can expect to tighten at least one bound
4184- if ((dualConsHasLower && impliedDualRowBounds.getNumInfSumUpper (col) <= 1 ) ||
4185- (dualConsHasUpper && impliedDualRowBounds.getNumInfSumLower (col) <= 1 )) {
4186- for (const HighsSliceNonzero& nonzero : getColumnVector (col))
4187- updateRowDualImpliedBounds (nonzero.index (), col, nonzero.value ());
4188- }
4227+ // update dual implied bounds of all rows in given column
4228+ updateRowDualImpliedBounds (col);
41894229
41904230 return Result::kOk ;
41914231}
@@ -5872,14 +5912,6 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols(
58725912 // compensating column is integral
58735913 bool checkColImplBounds = true ;
58745914 bool checkDuplicateColImplBounds = true ;
5875- auto isLowerStrictlyImplied = [&](HighsInt col) {
5876- return (model->col_lower_ [col] == -kHighsInf ||
5877- implColLower[col] > model->col_lower_ [col] + primal_feastol);
5878- };
5879- auto isUpperStrictlyImplied = [&](HighsInt col) {
5880- return (model->col_upper_ [col] == kHighsInf ||
5881- implColUpper[col] < model->col_upper_ [col] - primal_feastol);
5882- };
58835915 auto colUpperInf = [&]() {
58845916 if (!checkColImplBounds) return false ;
58855917 if (mipsolver == nullptr ) {
0 commit comments