Skip to content

Commit 3462023

Browse files
authored
Merge pull request #2623 from fwesselm/singletonColStuffing
Singleton column stuffing
2 parents 629509d + 113e9d1 commit 3462023

File tree

2 files changed

+190
-39
lines changed

2 files changed

+190
-39
lines changed

highs/presolve/HPresolve.cpp

Lines changed: 184 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3192,6 +3192,10 @@ HPresolve::Result HPresolve::singletonCol(HighsPostsolveStack& postsolve_stack,
31923192
HPRESOLVE_CHECKED_CALL(dualFixing(postsolve_stack, col));
31933193
if (colDeleted[col]) return Result::kOk;
31943194

3195+
// singleton column stuffing
3196+
HPRESOLVE_CHECKED_CALL(singletonColStuffing(postsolve_stack, col));
3197+
if (colDeleted[col]) return Result::kOk;
3198+
31953199
// update column implied bounds
31963200
updateColImpliedBounds(row, col, colCoef);
31973201

@@ -4335,6 +4339,10 @@ HPresolve::Result HPresolve::colPresolve(HighsPostsolveStack& postsolve_stack,
43354339
HPRESOLVE_CHECKED_CALL(dualFixing(postsolve_stack, col));
43364340
if (colDeleted[col]) return Result::kOk;
43374341

4342+
// singleton column stuffing
4343+
HPRESOLVE_CHECKED_CALL(singletonColStuffing(postsolve_stack, col));
4344+
if (colDeleted[col]) return Result::kOk;
4345+
43384346
// update dual implied bounds of all rows in given column
43394347
if (model->integrality_[col] != HighsVarType::kInteger)
43404348
updateRowDualImpliedBounds(col);
@@ -4460,6 +4468,35 @@ HPresolve::Result HPresolve::detectDominatedCol(
44604468
return Result::kOk;
44614469
}
44624470

4471+
void HPresolve::computeLocks(
4472+
HighsInt col, bool considerObjective,
4473+
std::function<bool(HighsInt, bool)> lockCallback) const {
4474+
// if the callback returns true, we stop examining the locks
4475+
if (considerObjective) {
4476+
// consider objective function
4477+
if (model->col_cost_[col] < 0) {
4478+
// downlock
4479+
if (lockCallback(-1, false)) return;
4480+
} else if (model->col_cost_[col] > 0) {
4481+
// uplock
4482+
if (lockCallback(-1, true)) return;
4483+
}
4484+
}
4485+
4486+
// check coefficients
4487+
for (const auto& nz : getColumnVector(col)) {
4488+
// implied lower bound -> downlock
4489+
if (yieldsImpliedLowerBound(nz.index(), nz.value()) &&
4490+
lockCallback(nz.index(), false))
4491+
break;
4492+
4493+
// implied upper bound -> uplock
4494+
if (yieldsImpliedUpperBound(nz.index(), nz.value()) &&
4495+
lockCallback(nz.index(), true))
4496+
break;
4497+
}
4498+
}
4499+
44634500
HPresolve::Result HPresolve::dualFixing(HighsPostsolveStack& postsolve_stack,
44644501
HighsInt col) {
44654502
// fix variables or tighten bounds using dual arguments
@@ -4471,43 +4508,6 @@ HPresolve::Result HPresolve::dualFixing(HighsPostsolveStack& postsolve_stack,
44714508
// return if variable is already fixed
44724509
if (model->col_lower_[col] == model->col_upper_[col]) return Result::kOk;
44734510

4474-
// lambda for computing locks
4475-
auto computeLocks = [&](HighsInt col, HighsInt& numDownLocks,
4476-
HighsInt& numUpLocks, HighsInt& downLockRow,
4477-
HighsInt& upLockRow) {
4478-
// initialise
4479-
numDownLocks = 0;
4480-
numUpLocks = 0;
4481-
downLockRow = -1;
4482-
upLockRow = -1;
4483-
4484-
// consider objective function
4485-
if (model->col_cost_[col] > 0)
4486-
numUpLocks++;
4487-
else if (model->col_cost_[col] < 0)
4488-
numDownLocks++;
4489-
4490-
// check coefficients
4491-
for (const auto& nz : getColumnVector(col)) {
4492-
// update number of locks
4493-
if (yieldsImpliedLowerBound(nz.index(), nz.value())) {
4494-
// implied lower bound -> downlock
4495-
numDownLocks++;
4496-
downLockRow = nz.index();
4497-
}
4498-
4499-
if (yieldsImpliedUpperBound(nz.index(), nz.value())) {
4500-
// implied upper bound -> uplock
4501-
numUpLocks++;
4502-
upLockRow = nz.index();
4503-
}
4504-
4505-
// stop early if there are locks in both directions, since the variable
4506-
// cannot be fixed in this case.
4507-
if (numDownLocks > 1 && numUpLocks > 1) break;
4508-
}
4509-
};
4510-
45114511
// lambda for variable substitution
45124512
auto substituteCol = [&](HighsInt col, HighsInt row, HighsInt direction,
45134513
double colBound, double otherColBound) {
@@ -4596,12 +4596,26 @@ HPresolve::Result HPresolve::dualFixing(HighsPostsolveStack& postsolve_stack,
45964596
return true;
45974597
};
45984598

4599-
// compute locks
4599+
// lambda callback for lock computation
46004600
HighsInt numDownLocks = 0;
46014601
HighsInt numUpLocks = 0;
46024602
HighsInt downLockRow = -1;
46034603
HighsInt upLockRow = -1;
4604-
computeLocks(col, numDownLocks, numUpLocks, downLockRow, upLockRow);
4604+
auto lockCallback = [&](HighsInt row, bool isUpLock) {
4605+
// count locks and remember row index
4606+
if (isUpLock) {
4607+
numUpLocks++;
4608+
upLockRow = row;
4609+
} else {
4610+
numDownLocks++;
4611+
downLockRow = row;
4612+
}
4613+
// stop early if there are locks in both directions, since the variable
4614+
// cannot be fixed in this case.
4615+
return numDownLocks > 1 && numUpLocks > 1;
4616+
};
4617+
// compute locks
4618+
computeLocks(col, true, lockCallback);
46054619

46064620
// check if variable can be fixed
46074621
if (numDownLocks == 0 || numUpLocks == 0) {
@@ -4655,6 +4669,137 @@ HPresolve::Result HPresolve::dualFixing(HighsPostsolveStack& postsolve_stack,
46554669
return Result::kOk;
46564670
}
46574671

4672+
HPresolve::Result HPresolve::singletonColStuffing(
4673+
HighsPostsolveStack& postsolve_stack, HighsInt col) {
4674+
// singleton column stuffing
4675+
// see Gamrath, G., Koch, T., Martin, A. et al., Progress in presolving
4676+
// for mixed integer programming, Math. Prog. Comp. 7, 367–398 (2015).
4677+
auto isContSingleton = [&](HighsInt col) {
4678+
return (!colDeleted[col] && colsize[col] == 1 &&
4679+
model->integrality_[col] != HighsVarType::kInteger &&
4680+
model->col_lower_[col] != model->col_upper_[col]);
4681+
};
4682+
4683+
auto sortCols =
4684+
[&](std::vector<std::tuple<HighsInt, double, HighsInt>>& vec) {
4685+
pdqsort(
4686+
vec.begin(), vec.end(),
4687+
[&](const std::tuple<HighsInt, double, HighsInt>& col1,
4688+
const std::tuple<HighsInt, double, HighsInt>& col2) {
4689+
return model->col_cost_[std::get<0>(col1)] / std::get<1>(col1) <
4690+
model->col_cost_[std::get<0>(col2)] / std::get<1>(col2);
4691+
});
4692+
};
4693+
4694+
// lambda for updating row activity bounds
4695+
auto updateActivityBounds = [&](HighsCDouble& sumLower,
4696+
HighsCDouble& sumUpper, bool& sumLowerFinite,
4697+
bool& sumUpperFinite, double aj,
4698+
double lowerSumBound, double upperSumBound) {
4699+
sumLowerFinite = sumLowerFinite && std::abs(lowerSumBound) != kHighsInf;
4700+
sumUpperFinite = sumUpperFinite && std::abs(upperSumBound) != kHighsInf;
4701+
if (sumLowerFinite)
4702+
sumLower += aj * static_cast<HighsCDouble>(lowerSumBound);
4703+
if (sumUpperFinite)
4704+
sumUpper += aj * static_cast<HighsCDouble>(upperSumBound);
4705+
};
4706+
4707+
// lambda for actual stuffing
4708+
auto checkRow = [&](HighsInt row, double rhs, HighsInt direction) {
4709+
// skip row if rhs is not finite
4710+
if (direction * rhs == kHighsInf) return Result::kOk;
4711+
4712+
// vectors for candidates and activity bounds
4713+
std::vector<std::tuple<HighsInt, double, HighsInt>> candidates;
4714+
HighsCDouble sumLower = 0.0;
4715+
HighsCDouble sumUpper = 0.0;
4716+
bool sumLowerFinite = true;
4717+
bool sumUpperFinite = true;
4718+
4719+
for (auto& nz : getRowVector(row)) {
4720+
// get column index, coefficient, cost and bounds
4721+
HighsInt j = nz.index();
4722+
double aj = direction * nz.value();
4723+
double cj = model->col_cost_[j];
4724+
double sumLowerBound = model->col_lower_[j];
4725+
double sumUpperBound = model->col_upper_[j];
4726+
if (isContSingleton(j)) {
4727+
// check singleton
4728+
if (aj > 0) {
4729+
// use lower bound
4730+
sumUpperBound = sumLowerBound;
4731+
// candidate for stuffing?
4732+
if (cj < 0) candidates.push_back(std::make_tuple(j, aj, HighsInt{1}));
4733+
} else {
4734+
// use upper bound
4735+
sumLowerBound = sumUpperBound;
4736+
// candidate for stuffing? multiply column with -1
4737+
if (cj > 0)
4738+
candidates.push_back(std::make_tuple(j, aj, HighsInt{-1}));
4739+
}
4740+
} else if (aj < 0)
4741+
std::swap(sumLowerBound, sumUpperBound);
4742+
// update activities
4743+
updateActivityBounds(sumLower, sumUpper, sumLowerFinite, sumUpperFinite,
4744+
aj, sumLowerBound, sumUpperBound);
4745+
if (!sumLowerFinite && !sumUpperFinite) return Result::kOk;
4746+
}
4747+
4748+
// sort candidates
4749+
sortCols(candidates);
4750+
4751+
// check candidates
4752+
for (const auto& t : candidates) {
4753+
// get variable index, coefficient and multiplier (-1 if sign was flipped)
4754+
HighsInt j = std::get<0>(t);
4755+
double aj = std::get<1>(t);
4756+
HighsInt multiplier = std::get<2>(t);
4757+
// both bounds have to be finite
4758+
if (model->col_lower_[j] == -kHighsInf ||
4759+
model->col_upper_[j] == kHighsInf)
4760+
break;
4761+
// compute delta (bound difference)
4762+
HighsCDouble delta = multiplier * aj *
4763+
(static_cast<HighsCDouble>(model->col_upper_[j]) -
4764+
static_cast<HighsCDouble>(model->col_lower_[j]));
4765+
// check if variable can be fixed
4766+
if (sumUpperFinite &&
4767+
delta <= direction * rhs - sumUpper + primal_feastol) {
4768+
if (multiplier < 0)
4769+
HPRESOLVE_CHECKED_CALL(fixColToLower(postsolve_stack, j));
4770+
else
4771+
HPRESOLVE_CHECKED_CALL(fixColToUpper(postsolve_stack, j));
4772+
} else if (sumLowerFinite &&
4773+
direction * rhs <= sumLower + primal_feastol) {
4774+
if (multiplier < 0)
4775+
HPRESOLVE_CHECKED_CALL(fixColToUpper(postsolve_stack, j));
4776+
else
4777+
HPRESOLVE_CHECKED_CALL(fixColToLower(postsolve_stack, j));
4778+
}
4779+
// update row activities
4780+
if (sumLowerFinite) sumLower += delta;
4781+
if (sumUpperFinite) sumUpper += delta;
4782+
}
4783+
4784+
return Result::kOk;
4785+
};
4786+
4787+
// consider only non-fixed singleton continuous columns
4788+
if (!isContSingleton(col)) return Result::kOk;
4789+
4790+
// get row index
4791+
HighsInt row = Arow[colhead[col]];
4792+
4793+
// return if we have an empty or singleton row or row is ranged
4794+
if (rowsize[row] <= 1 || isRanged(row)) return Result::kOk;
4795+
4796+
// check row
4797+
HPRESOLVE_CHECKED_CALL(checkRow(row, model->row_upper_[row], HighsInt{1}));
4798+
HPRESOLVE_CHECKED_CALL(checkRow(row, model->row_lower_[row], HighsInt{-1}));
4799+
4800+
return Result::kOk;
4801+
}
4802+
46584803
double HPresolve::computeImpliedLowerBound(HighsInt col, HighsInt boundCol,
46594804
double boundColValue,
46604805
HighsInt boundColCoeffPattern) {

highs/presolve/HPresolve.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,8 +389,14 @@ class HPresolve {
389389
Result detectDominatedCol(HighsPostsolveStack& postsolve_stack, HighsInt col,
390390
bool handleSingletonRows = true);
391391

392+
void computeLocks(HighsInt col, bool considerObjective,
393+
std::function<bool(HighsInt, bool)> lockCallback) const;
394+
392395
Result dualFixing(HighsPostsolveStack& postsolve_stack, HighsInt col);
393396

397+
Result singletonColStuffing(HighsPostsolveStack& postsolve_stack,
398+
HighsInt col);
399+
394400
double computeImpliedLowerBound(HighsInt col, HighsInt boundCol = -1,
395401
double boundColValue = kHighsInf,
396402
HighsInt boundColCoeffPattern = 0);

0 commit comments

Comments
 (0)