Skip to content

Commit 928448a

Browse files
committed
Merge branch 'latest' into fix-2521
2 parents 8c3a35b + dc27d34 commit 928448a

File tree

5 files changed

+213
-25
lines changed

5 files changed

+213
-25
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ linear optimization problems of the form
4646

4747
$$ \min \quad \dfrac{1}{2}x^TQx + c^Tx \qquad \textrm{s.t.}~ \quad L \leq Ax \leq U; \quad l \leq x \leq u $$
4848

49-
where Q must be positive semi-definite and, if Q is zero, there may be a requirement that some of the variables take integer values. Thus HiGHS can solve linear programming (LP) problems, convex quadratic programming (QP) problems, and mixed integer programming (MIP) problems. It is mainly written in C++, but also has some C. It has been developed and tested on various Linux, MacOS and Windows installations. No third-party dependencies are required.
49+
where $Q$ must be positive semi-definite and, if $Q$ is zero, there may be a requirement that some of the variables take integer values. Thus HiGHS can solve linear programming (LP) problems, convex quadratic programming (QP) problems, and mixed integer programming (MIP) problems. It is mainly written in C++, but also has some C. It has been developed and tested on various Linux, MacOS and Windows installations. No third-party dependencies are required.
5050

5151
HiGHS has primal and dual revised simplex solvers, originally written by Qi Huangfu and further developed by Julian Hall. It also has an interior point solver for LP written by Lukas Schork, an active set solver for QP written by Michael Feldmeier, and a MIP solver written by Leona Gottwald. Other features have been added by Julian Hall and Ivet Galabova, who manages the software engineering of HiGHS and interfaces to C, C#, FORTRAN, Julia and Python.
5252

highs/interfaces/highs_c_api.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,9 @@ HighsInt Highs_presolve(void* highs);
400400
HighsInt Highs_run(void* highs);
401401

402402
/**
403-
* Postsolve a model using a primal (and possibly dual) solution.
403+
* Postsolve a model using a primal (and possibly dual) solution. The
404+
* postsolved solution can be retrieved later by calling
405+
* `Highs_getSolution`.
404406
*
405407
* @param highs A pointer to the Highs instance.
406408
* @param col_value An array of length [num_col] with the column solution

highs/mip/HighsRedcostFixing.cpp

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,27 @@ void HighsRedcostFixing::addRootRedcost(const HighsMipSolver& mipsolver,
200200
mipsolver.mipdata_->lp.computeBasicDegenerateDuals(
201201
mipsolver.mipdata_->feastol);
202202

203+
// Compute maximum number of steps per column with large domain
204+
// max_steps = 2 ** k, k = max(5, min(10 ,round(log(|D| / 10)))),
205+
// D = {col : integral_cols | (ub - lb) >= 512}
206+
// This is to avoid doing 2**10 steps when there's many unbounded columns
207+
HighsInt numRedcostLargeDomainCols = 0;
208+
for (HighsInt col : mipsolver.mipdata_->integral_cols) {
209+
if ((mipsolver.mipdata_->domain.col_upper_[col] -
210+
mipsolver.mipdata_->domain.col_lower_[col]) >= 512 &&
211+
std::abs(lpredcost[col]) > mipsolver.mipdata_->feastol) {
212+
numRedcostLargeDomainCols++;
213+
}
214+
}
215+
HighsInt maxNumStepsExp = 10;
216+
int expshift = 0;
217+
std::frexp(numRedcostLargeDomainCols / 10, &expshift);
218+
if (expshift > 5) {
219+
expshift = std::min(expshift, static_cast<int>(maxNumStepsExp));
220+
maxNumStepsExp = maxNumStepsExp - expshift + 5;
221+
}
222+
HighsInt maxNumSteps = static_cast<HighsInt>(1ULL << maxNumStepsExp);
223+
203224
for (HighsInt col : mipsolver.mipdata_->integral_cols) {
204225
if (lpredcost[col] > mipsolver.mipdata_->feastol) {
205226
// col <= (cutoffbound - lpobj)/redcost + lb
@@ -218,13 +239,14 @@ void HighsRedcostFixing::addRootRedcost(const HighsMipSolver& mipsolver,
218239

219240
HighsInt maxub;
220241
if (mipsolver.mipdata_->domain.col_upper_[col] == kHighsInf)
221-
maxub = lb + 1024;
242+
maxub = lb + maxNumSteps;
222243
else
223244
maxub = (HighsInt)std::floor(
224245
mipsolver.mipdata_->domain.col_upper_[col] - 0.5);
225246

226247
HighsInt step = 1;
227-
if (maxub - lb > 1024) step = (maxub - lb + 1023) >> 10;
248+
if (maxub - lb > maxNumSteps)
249+
step = (maxub - lb + maxNumSteps - 1) >> maxNumStepsExp;
228250

229251
for (HighsInt lurkub = lb; lurkub <= maxub; lurkub += step) {
230252
double fracbound = (lurkub - lb + 1) - 10 * mipsolver.mipdata_->feastol;
@@ -276,12 +298,13 @@ void HighsRedcostFixing::addRootRedcost(const HighsMipSolver& mipsolver,
276298

277299
HighsInt minlb;
278300
if (mipsolver.mipdata_->domain.col_lower_[col] == -kHighsInf)
279-
minlb = ub - 1024;
301+
minlb = ub - maxNumSteps;
280302
else
281303
minlb = (HighsInt)(mipsolver.mipdata_->domain.col_lower_[col] + 1.5);
282304

283305
HighsInt step = 1;
284-
if (ub - minlb > 1024) step = (ub - minlb + 1023) >> 10;
306+
if (ub - minlb > maxNumSteps)
307+
step = (ub - minlb + maxNumSteps - 1) >> maxNumStepsExp;
285308

286309
for (HighsInt lurklb = minlb; lurklb <= ub; lurklb += step) {
287310
double fracbound = (lurklb - ub - 1) + 10 * mipsolver.mipdata_->feastol;

highs/presolve/HPresolve.cpp

Lines changed: 180 additions & 19 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
@@ -1608,12 +1613,20 @@ HPresolve::Result HPresolve::runProbing(HighsPostsolveStack& postsolve_stack) {
16081613
if (cliquetable.getSubstitution(i) != nullptr || !domain.isBinary(i))
16091614
continue;
16101615

1616+
bool tightenLimits = (numProbed - oldNumProbed) >= 2500;
1617+
16111618
// when a large percentage of columns have been deleted, stop this round
16121619
// of probing
16131620
// if (numDel > std::max(model->num_col_ * 0.2, 1000.)) break;
1614-
probingEarlyAbort =
1615-
numDel >
1616-
std::max(HighsInt{1000}, (model->num_row_ + model->num_col_) / 20);
1621+
if (!tightenLimits) {
1622+
probingEarlyAbort =
1623+
numDel >
1624+
std::max(HighsInt{1000}, (model->num_row_ + model->num_col_) / 20);
1625+
} else {
1626+
probingEarlyAbort =
1627+
numDel >
1628+
std::min(HighsInt{1000}, (model->num_row_ + model->num_col_) / 20);
1629+
}
16171630
if (probingEarlyAbort) break;
16181631

16191632
// break in case of too many new implications to not spent ages in
@@ -1660,7 +1673,7 @@ HPresolve::Result HPresolve::runProbing(HighsPostsolveStack& postsolve_stack) {
16601673
numDel = newNumDel;
16611674
numFail = 0;
16621675
} else if (mipsolver->submip || numNewCliques == 0) {
1663-
splayContingent -= 100 * numFail;
1676+
splayContingent -= (tightenLimits ? 250 : 100) * numFail;
16641677
++numFail;
16651678
} else {
16661679
splayContingent += 1000 * numNewCliques;
@@ -3727,21 +3740,18 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack,
37273740
rowCoefs.reserve(rowsize[row]);
37283741
rowIndex.reserve(rowsize[row]);
37293742

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-
37373743
for (const HighsSliceNonzero& nonz : getStoredRow()) {
37383744
assert(nonz.value() != 0.0);
37393745
rowCoefs.push_back(nonz.value());
37403746
rowIndex.push_back(nonz.index());
37413747
}
37423748

3743-
double intScale =
3744-
HighsIntegers::integralScale(rowCoefs, deltaDown, deltaUp);
3749+
double intScale = HighsIntegers::integralScale(
3750+
rowCoefs,
3751+
model->row_lower_[row] == -kHighsInf ? primal_feastol
3752+
: options->small_matrix_value,
3753+
model->row_upper_[row] == kHighsInf ? primal_feastol
3754+
: options->small_matrix_value);
37453755

37463756
auto roundRhs = [&](HighsCDouble rhs, HighsCDouble& roundedRhs,
37473757
HighsCDouble& fractionRhs, double minRhsTightening,
@@ -3902,9 +3912,164 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack,
39023912
}
39033913
}
39043914
}
3915+
} else if (!isRanged(row)) {
3916+
// Chvatal-Gomory strengthening
3917+
// See section 3.4 "Chvatal-Gomory strengthening of inequalities",
3918+
// Achterberg et al., Presolve Reductions in Mixed Integer
3919+
// Programming, INFORMS Journal on Computing 32(2):473-506.
3920+
std::vector<double> roundedRowCoefs;
3921+
roundedRowCoefs.resize(rowsize[row]);
3922+
std::set<double> scalars;
3923+
3924+
// lambda for reformulating row to only contain variables with lower
3925+
// bounds of zero (if possible)
3926+
auto complementOrShift = [&](HighsInt direction, HighsCDouble& rhs,
3927+
double& minAbsCoef, double& maxAbsCoef) {
3928+
minAbsCoef = kHighsInf;
3929+
maxAbsCoef = 0.0;
3930+
for (size_t i = 0; i < rowCoefs.size(); ++i) {
3931+
// get column index and (absolute) coefficient
3932+
HighsInt col = rowIndex[i];
3933+
double val = direction * rowCoefs[i];
3934+
double absval = std::abs(val);
3935+
// compute minimum and maximum absolute coefficients along the way
3936+
minAbsCoef = std::min(minAbsCoef, absval);
3937+
maxAbsCoef = std::max(maxAbsCoef, absval);
3938+
if (val < 0.0 && model->col_upper_[col] != kHighsInf) {
3939+
// complement
3940+
rhs -= val * static_cast<HighsCDouble>(model->col_upper_[col]);
3941+
} else if (val > 0.0 && model->col_lower_[col] != -kHighsInf) {
3942+
// shift
3943+
rhs -= val * static_cast<HighsCDouble>(model->col_lower_[col]);
3944+
} else {
3945+
// unbounded variable; cannot shift or complement!
3946+
return false;
3947+
}
3948+
}
3949+
return true;
3950+
};
3951+
3952+
// undo shifting and complementation
3953+
auto undoComplementOrShift = [&](HighsInt direction,
3954+
HighsCDouble& roundedRhs) {
3955+
for (size_t i = 0; i < rowCoefs.size(); ++i) {
3956+
HighsInt col = rowIndex[i];
3957+
double val = direction * rowCoefs[i];
3958+
if (val < 0.0 && model->col_upper_[col] != kHighsInf) {
3959+
// uncomplement
3960+
roundedRowCoefs[i] = -roundedRowCoefs[i];
3961+
roundedRhs += roundedRowCoefs[i] *
3962+
static_cast<HighsCDouble>(model->col_upper_[col]);
3963+
} else if (val > 0.0 && model->col_lower_[col] != -kHighsInf) {
3964+
// unshift
3965+
roundedRhs += roundedRowCoefs[i] *
3966+
static_cast<HighsCDouble>(model->col_lower_[col]);
3967+
}
3968+
// flip coefficient sign for <= inequality
3969+
roundedRowCoefs[i] *= direction;
3970+
}
3971+
// flip rhs sign for <= inequality
3972+
roundedRhs *= direction;
3973+
};
3974+
3975+
// round row using given scalar
3976+
auto roundRow = [&](double s, const HighsCDouble& rhs,
3977+
HighsCDouble& roundedRhs) {
3978+
bool accept = false;
3979+
// round rhs (using feasibility tolerance)
3980+
HighsCDouble scalar = static_cast<HighsCDouble>(s);
3981+
roundedRhs = ceil(rhs * scalar - primal_feastol);
3982+
assert(roundedRhs > 0.0);
3983+
HighsCDouble rhsRatio = rhs / roundedRhs;
3984+
3985+
for (size_t i = 0; i < rowCoefs.size(); ++i) {
3986+
// coefficient sign has not been flipped for complemented
3987+
// variables; take absolute value of coefficient.
3988+
double absCoef = std::abs(rowCoefs[i]);
3989+
// round coefficient
3990+
roundedRowCoefs[i] =
3991+
static_cast<double>(ceil(absCoef * scalar - kHighsTiny));
3992+
// compare "normalised" coefficients, i.e. coefficients divided by
3993+
// corresponding rhs.
3994+
double threshold =
3995+
static_cast<double>(roundedRowCoefs[i] * rhsRatio);
3996+
// return if coefficient is weaker
3997+
if (absCoef < threshold - options->small_matrix_value)
3998+
return false;
3999+
// accept rounding if at least one coefficient is improved
4000+
accept =
4001+
accept || (absCoef > threshold + options->small_matrix_value);
4002+
}
4003+
return accept;
4004+
};
4005+
4006+
// set up scalars suggested by Achterberg et al.
4007+
auto setScalars = [&](double minAbsCoef, double maxAbsCoef) {
4008+
scalars.clear();
4009+
scalars.emplace(1.0);
4010+
for (HighsInt t = 1; t <= 5; t++) {
4011+
scalars.emplace(t / maxAbsCoef);
4012+
scalars.emplace(t / minAbsCoef);
4013+
scalars.emplace((2 * t - 1) / (2 * minAbsCoef));
4014+
}
4015+
};
4016+
4017+
// try different scalars and return if an improving one was found
4018+
auto rowCanBeTightened = [&](const HighsCDouble& rhs,
4019+
HighsCDouble& roundedRhs) {
4020+
for (double s : scalars) {
4021+
if (roundRow(s, rhs, roundedRhs)) return true;
4022+
}
4023+
return false;
4024+
};
4025+
4026+
// replace the model row by the rounded one
4027+
auto updateRow = [&](HighsInt row, HighsInt direction,
4028+
const HighsCDouble& roundedRhs) {
4029+
if (direction < 0)
4030+
model->row_upper_[row] = static_cast<double>(roundedRhs);
4031+
else
4032+
model->row_lower_[row] = static_cast<double>(roundedRhs);
4033+
for (size_t i = 0; i < rowCoefs.size(); ++i) {
4034+
double delta = static_cast<double>(
4035+
static_cast<HighsCDouble>(roundedRowCoefs[i]) - rowCoefs[i]);
4036+
if (std::fabs(delta) > options->small_matrix_value)
4037+
addToMatrix(row, rowIndex[i], delta);
4038+
}
4039+
};
4040+
4041+
// convert to >= inequality
4042+
// direction = -1: <= constraint, direction = 1: >= constraint
4043+
HighsInt direction =
4044+
model->row_upper_[row] != kHighsInf ? HighsInt{-1} : HighsInt{1};
4045+
// get rhs
4046+
HighsCDouble rhs =
4047+
direction < 0 ? -model->row_upper_[row] : model->row_lower_[row];
4048+
4049+
// initialise
4050+
HighsCDouble roundedRhs = 0.0;
4051+
double minAbsCoef = 0.0;
4052+
double maxAbsCoef = 0.0;
4053+
4054+
// complement or shift variables to have lower bounds of zero
4055+
if (complementOrShift(direction, rhs, minAbsCoef, maxAbsCoef)) {
4056+
// identify scalars for row
4057+
setScalars(minAbsCoef, maxAbsCoef);
4058+
// find a scalar that produces improved coefficients
4059+
if (rowCanBeTightened(rhs, roundedRhs)) {
4060+
// undo complementation and shifting
4061+
undoComplementOrShift(direction, roundedRhs);
4062+
// replace row by rounded one
4063+
updateRow(row, direction, roundedRhs);
4064+
}
4065+
}
39054066
}
39064067
}
39074068

4069+
// check for redundancy again
4070+
HPRESOLVE_CHECKED_CALL(checkRowRedundant(row));
4071+
if (rowDeleted[row]) return Result::kOk;
4072+
39084073
auto strengthenCoefs = [&](HighsCDouble& rhs, HighsInt direction,
39094074
HighsCDouble maxAbsCoefValue) {
39104075
// iterate over non-zero positions instead of iterating over the
@@ -4172,9 +4337,7 @@ HPresolve::Result HPresolve::colPresolve(HighsPostsolveStack& postsolve_stack,
41724337
HighsInt direction,
41734338
bool isBoundImplied, HighsInt numInf) {
41744339
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)) {
4340+
direction * model->col_cost_[col] >= 0 && !isRanged(row)) {
41784341
HighsInt nzPos = findNonzero(row, col);
41794342

41804343
if (model->integrality_[col] != HighsVarType::kInteger ||
@@ -5551,9 +5714,7 @@ HPresolve::Result HPresolve::strengthenInequalities(
55515714

55525715
for (HighsInt row = 0; row != model->num_row_; ++row) {
55535716
if (rowsize[row] <= 1) continue;
5554-
if (model->row_lower_[row] != -kHighsInf &&
5555-
model->row_upper_[row] != kHighsInf)
5556-
continue;
5717+
if (isRanged(row)) continue;
55575718

55585719
// do not run on very dense rows as this could get expensive
55595720
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)