Skip to content

Commit a8ba8aa

Browse files
authored
Merge pull request ERGO-Code#2494 from fwesselm/simplifyColPresolve
Minor changes to presolve
2 parents 5d45136 + c88d8a9 commit a8ba8aa

File tree

3 files changed

+127
-79
lines changed

3 files changed

+127
-79
lines changed

highs/lp_data/HighsLpUtils.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2564,7 +2564,8 @@ HighsStatus assessLpPrimalSolution(const std::string message,
25642564
HighsStatus return_status =
25652565
calculateRowValuesQuad(lp, solution.col_value, row_value);
25662566
if (return_status != HighsStatus::kOk) return return_status;
2567-
const bool have_row_names = lp.row_names_.size() >= lp.num_row_;
2567+
const bool have_row_names =
2568+
lp.row_names_.size() >= static_cast<size_t>(lp.num_row_);
25682569
for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) {
25692570
const double primal = solution.row_value[iRow];
25702571
const double lower = lp.row_lower_[iRow];

highs/presolve/HPresolve.cpp

Lines changed: 110 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
187194
bool 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+
192206
bool 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+
587647
void 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+
670733
void 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) {

highs/presolve/HPresolve.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,10 +174,21 @@ class HPresolve {
174174

175175
double getMaxAbsRowVal(HighsInt row) const;
176176

177+
bool checkUpdateColImpliedBounds(HighsInt row, double* rowLower = nullptr,
178+
double* rowUpper = nullptr) const;
179+
177180
void updateColImpliedBounds(HighsInt row, HighsInt col, double val);
178181

182+
void updateColImpliedBounds(HighsInt row);
183+
184+
bool checkUpdateRowDualImpliedBounds(HighsInt col,
185+
double* dualRowLower = nullptr,
186+
double* dualRowUpper = nullptr) const;
187+
179188
void updateRowDualImpliedBounds(HighsInt row, HighsInt col, double val);
180189

190+
void updateRowDualImpliedBounds(HighsInt col);
191+
181192
void resetColImpliedBounds(HighsInt col, HighsInt row = -1);
182193

183194
void resetRowDualImpliedBounds(HighsInt row, HighsInt col = -1);
@@ -211,8 +222,12 @@ class HPresolve {
211222

212223
bool isLowerImplied(HighsInt col) const;
213224

225+
bool isLowerStrictlyImplied(HighsInt col, double* tolerance = nullptr) const;
226+
214227
bool isUpperImplied(HighsInt col) const;
215228

229+
bool isUpperStrictlyImplied(HighsInt col, double* tolerance = nullptr) const;
230+
216231
HighsInt countFillin(HighsInt row);
217232

218233
bool checkFillin(HighsHashTable<HighsInt, HighsInt>& fillinCache,

0 commit comments

Comments
 (0)