Skip to content

Commit ee854e4

Browse files
authored
Merge pull request #2522 from fwesselm/chvatalGomoryStren
Chvátal-Gomory strengthening
2 parents eba2504 + d82cb80 commit ee854e4

File tree

2 files changed

+170
-15
lines changed

2 files changed

+170
-15
lines changed

highs/presolve/HPresolve.cpp

Lines changed: 168 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
241246
bool 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 =

highs/presolve/HPresolve.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,8 @@ class HPresolve {
209209

210210
bool isEquation(HighsInt row) const;
211211

212+
bool isRanged(HighsInt row) const;
213+
212214
bool isImpliedEquationAtLower(HighsInt row) const;
213215

214216
bool isImpliedEquationAtUpper(HighsInt row) const;

0 commit comments

Comments
 (0)