Skip to content

Commit 02aeb70

Browse files
authored
Merge pull request ERGO-Code#2034 from ERGO-Code/fix-2001
Added code to remove explicit slacks
2 parents 7aede2a + 0eda282 commit 02aeb70

File tree

8 files changed

+213
-1
lines changed

8 files changed

+213
-1
lines changed

check/TestPresolve.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,3 +601,41 @@ TEST_CASE("write-presolved-model", "[highs_test_presolve]") {
601601
REQUIRE(highs.getInfo().simplex_iteration_count == -1);
602602
std::remove(presolved_model_file.c_str());
603603
}
604+
605+
TEST_CASE("presolve-slacks", "[highs_test_presolve]") {
606+
// This LP reduces to empty, because the equation is a doubleton
607+
HighsLp lp;
608+
lp.num_col_ = 2;
609+
lp.num_row_ = 1;
610+
lp.col_cost_ = {1, 0};
611+
lp.col_lower_ = {0, 0};
612+
lp.col_upper_ = {kHighsInf, kHighsInf};
613+
lp.row_lower_ = {1};
614+
lp.row_upper_ = {1};
615+
lp.a_matrix_.start_ = {0, 1, 2};
616+
lp.a_matrix_.index_ = {0, 0};
617+
lp.a_matrix_.value_ = {1, 1};
618+
Highs h;
619+
// h.setOptionValue("output_flag", dev_run);
620+
REQUIRE(h.passModel(lp) == HighsStatus::kOk);
621+
REQUIRE(h.presolve() == HighsStatus::kOk);
622+
REQUIRE(h.getPresolvedLp().num_col_ == 0);
623+
REQUIRE(h.getPresolvedLp().num_row_ == 0);
624+
625+
lp.num_col_ = 4;
626+
lp.num_row_ = 2;
627+
lp.col_cost_ = {-10, -25, 0, 0};
628+
lp.col_lower_ = {0, 0, 0, 0};
629+
lp.col_upper_ = {kHighsInf, kHighsInf, kHighsInf, kHighsInf};
630+
lp.row_lower_ = {80, 120};
631+
lp.row_upper_ = {80, 120};
632+
lp.a_matrix_.start_ = {0, 2, 4, 5, 6};
633+
lp.a_matrix_.index_ = {0, 1, 0, 1, 0, 1};
634+
lp.a_matrix_.value_ = {1, 1, 2, 4, 1, 1};
635+
REQUIRE(h.setOptionValue("presolve_remove_slacks", true) == HighsStatus::kOk);
636+
REQUIRE(h.passModel(lp) == HighsStatus::kOk);
637+
REQUIRE(h.run() == HighsStatus::kOk);
638+
REQUIRE(h.presolve() == HighsStatus::kOk);
639+
REQUIRE(h.getPresolvedLp().num_col_ == 2);
640+
REQUIRE(h.getPresolvedLp().num_row_ == 2);
641+
}

src/lp_data/Highs.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1252,6 +1252,7 @@ HighsStatus Highs::run() {
12521252
// Run solver.
12531253
bool have_optimal_solution = false;
12541254
// ToDo Put solution of presolved problem in a separate method
1255+
12551256
switch (model_presolve_status_) {
12561257
case HighsPresolveStatus::kNotPresolved: {
12571258
ekk_instance_.lp_name_ = "Original LP";
@@ -1527,6 +1528,10 @@ HighsStatus Highs::run() {
15271528
options_ = save_options;
15281529
if (return_status == HighsStatus::kError)
15291530
return returnFromRun(return_status, undo_mods);
1531+
if (postsolve_iteration_count > 0)
1532+
highsLogUser(options_.log_options, HighsLogType::kInfo,
1533+
"Required %d simplex iterations after postsolve\n",
1534+
int(postsolve_iteration_count));
15301535
}
15311536
} else {
15321537
highsLogUser(log_options, HighsLogType::kError,

src/lp_data/HighsOptions.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,7 @@ struct HighsOptionsStruct {
372372
HighsInt presolve_substitution_maxfillin;
373373
HighsInt presolve_rule_off;
374374
bool presolve_rule_logging;
375+
bool presolve_remove_slacks;
375376
bool simplex_initial_condition_check;
376377
bool no_unnecessary_rebuild_refactor;
377378
double simplex_initial_condition_tolerance;
@@ -507,6 +508,7 @@ struct HighsOptionsStruct {
507508
presolve_substitution_maxfillin(0),
508509
presolve_rule_off(0),
509510
presolve_rule_logging(false),
511+
presolve_remove_slacks(false),
510512
simplex_initial_condition_check(false),
511513
no_unnecessary_rebuild_refactor(false),
512514
simplex_initial_condition_tolerance(0.0),
@@ -1324,6 +1326,11 @@ class HighsOptions : public HighsOptionsStruct {
13241326
advanced, &presolve_rule_logging, false);
13251327
records.push_back(record_bool);
13261328

1329+
record_bool = new OptionRecordBool("presolve_remove_slacks",
1330+
"Remove slacks after presolve", advanced,
1331+
&presolve_remove_slacks, false);
1332+
records.push_back(record_bool);
1333+
13271334
record_int = new OptionRecordInt(
13281335
"presolve_substitution_maxfillin",
13291336
"Maximal fillin allowed for substitutions in presolve", advanced,

src/presolve/HPresolve.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4307,6 +4307,10 @@ HPresolve::Result HPresolve::presolve(HighsPostsolveStack& postsolve_stack) {
43074307
break;
43084308
}
43094309

4310+
// Now consider removing slacks
4311+
if (options->presolve_remove_slacks)
4312+
HPRESOLVE_CHECKED_CALL(removeSlacks(postsolve_stack));
4313+
43104314
report();
43114315
} else {
43124316
highsLogUser(options->log_options, HighsLogType::kInfo,
@@ -4322,6 +4326,56 @@ HPresolve::Result HPresolve::presolve(HighsPostsolveStack& postsolve_stack) {
43224326
return Result::kOk;
43234327
}
43244328

4329+
HPresolve::Result HPresolve::removeSlacks(
4330+
HighsPostsolveStack& postsolve_stack) {
4331+
// SingletonColumns data structure appears not to be retained
4332+
// throughout presolve
4333+
for (HighsInt iCol = 0; iCol != model->num_col_; ++iCol) {
4334+
if (colDeleted[iCol]) continue;
4335+
if (colsize[iCol] != 1) continue;
4336+
if (model->integrality_[iCol] == HighsVarType::kInteger) continue;
4337+
HighsInt coliter = colhead[iCol];
4338+
HighsInt iRow = Arow[coliter];
4339+
assert(Acol[coliter] == iCol);
4340+
assert(!rowDeleted[iRow]);
4341+
if (model->row_lower_[iRow] != model->row_upper_[iRow]) continue;
4342+
double lower = model->col_lower_[iCol];
4343+
double upper = model->col_upper_[iCol];
4344+
double cost = model->col_cost_[iCol];
4345+
double rhs = model->row_lower_[iRow];
4346+
double coeff = Avalue[coliter];
4347+
assert(coeff);
4348+
// Slack is s = (rhs - a^Tx)/coeff
4349+
//
4350+
// Constraint bounds become:
4351+
//
4352+
// For coeff > 0 [rhs - coeff * upper, rhs - coeff * lower]
4353+
//
4354+
// For coeff < 0 [rhs - coeff * lower, rhs - coeff * upper]
4355+
model->row_lower_[iRow] =
4356+
coeff > 0 ? rhs - coeff * upper : rhs - coeff * lower;
4357+
model->row_upper_[iRow] =
4358+
coeff > 0 ? rhs - coeff * lower : rhs - coeff * upper;
4359+
if (cost) {
4360+
// Cost is (cost * rhs / coeff) + (col_cost - (cost/coeff) row_values)^Tx
4361+
double multiplier = cost / coeff;
4362+
for (const HighsSliceNonzero& nonzero : getRowVector(iRow)) {
4363+
HighsInt local_iCol = nonzero.index();
4364+
double local_value = nonzero.value();
4365+
model->col_cost_[local_iCol] -= multiplier * local_value;
4366+
}
4367+
model->offset_ += multiplier * rhs;
4368+
}
4369+
//
4370+
postsolve_stack.slackColSubstitution(iRow, iCol, rhs, getRowVector(iRow));
4371+
4372+
markColDeleted(iCol);
4373+
4374+
unlink(coliter);
4375+
}
4376+
return Result::kOk;
4377+
}
4378+
43254379
HPresolve::Result HPresolve::checkTimeLimit() {
43264380
assert(timer);
43274381
if (options->time_limit < kHighsInf && timer->read() >= options->time_limit)

src/presolve/HPresolve.h

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

269269
Result presolve(HighsPostsolveStack& postsolve_stack);
270270

271+
Result removeSlacks(HighsPostsolveStack& postsolve_stack);
272+
271273
Result checkTimeLimit();
272274

273275
Result checkLimits(HighsPostsolveStack& postsolve_stack);

src/presolve/HighsPostsolveStack.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <numeric>
1414

1515
#include "lp_data/HConst.h"
16+
#include "lp_data/HighsModelUtils.h" // For debugging #2001
1617
#include "lp_data/HighsOptions.h"
1718
#include "util/HighsCDouble.h"
1819
#include "util/HighsUtils.h"
@@ -1351,4 +1352,72 @@ void HighsPostsolveStack::DuplicateColumn::transformToPresolvedSpace(
13511352
primalSol[col] = primalSol[col] + colScale * primalSol[duplicateCol];
13521353
}
13531354

1355+
void HighsPostsolveStack::SlackColSubstitution::undo(
1356+
const HighsOptions& options, const std::vector<Nonzero>& rowValues,
1357+
HighsSolution& solution, HighsBasis& basis) {
1358+
bool debug_print = false;
1359+
// May have to determine row dual and basis status unless doing
1360+
// primal-only transformation in MIP solver, in which case row may
1361+
// no longer exist if it corresponds to a removed cut, so have to
1362+
// avoid exceeding array bounds of solution.row_value
1363+
bool isModelRow = static_cast<size_t>(row) < solution.row_value.size();
1364+
1365+
// compute primal values
1366+
double colCoef = 0;
1367+
HighsCDouble rowValue = 0;
1368+
for (const auto& rowVal : rowValues) {
1369+
if (rowVal.index == col)
1370+
colCoef = rowVal.value;
1371+
else
1372+
rowValue += rowVal.value * solution.col_value[rowVal.index];
1373+
}
1374+
1375+
assert(colCoef != 0);
1376+
// Row values aren't fully postsolved, so why do this?
1377+
if (isModelRow)
1378+
solution.row_value[row] =
1379+
double(rowValue + colCoef * solution.col_value[col]);
1380+
1381+
solution.col_value[col] = double((rhs - rowValue) / colCoef);
1382+
1383+
// If no dual values requested, return here
1384+
if (!solution.dual_valid) return;
1385+
1386+
// Row retains its dual value, and column has this dual value scaled by coeff
1387+
if (isModelRow) solution.col_dual[col] = -solution.row_dual[row] / colCoef;
1388+
1389+
// Set basis status if necessary
1390+
if (!basis.valid) return;
1391+
1392+
// If row is basic, then slack is basic, otherwise row retains its status
1393+
if (isModelRow) {
1394+
HighsBasisStatus save_row_basis_status = basis.row_status[row];
1395+
if (basis.row_status[row] == HighsBasisStatus::kBasic) {
1396+
basis.col_status[col] = HighsBasisStatus::kBasic;
1397+
basis.row_status[row] =
1398+
computeRowStatus(solution.row_dual[row], RowType::kEq);
1399+
} else if (basis.row_status[row] == HighsBasisStatus::kLower) {
1400+
basis.col_status[col] =
1401+
colCoef > 0 ? HighsBasisStatus::kUpper : HighsBasisStatus::kLower;
1402+
} else {
1403+
basis.col_status[col] =
1404+
colCoef > 0 ? HighsBasisStatus::kLower : HighsBasisStatus::kUpper;
1405+
}
1406+
if (debug_print)
1407+
printf(
1408+
"HighsPostsolveStack::SlackColSubstitution::undo OgRowStatus = %s; "
1409+
"RowStatus = %s; ColStatus = %s\n",
1410+
utilBasisStatusToString(save_row_basis_status).c_str(),
1411+
utilBasisStatusToString(basis.row_status[row]).c_str(),
1412+
utilBasisStatusToString(basis.col_status[col]).c_str());
1413+
if (basis.col_status[col] == HighsBasisStatus::kLower) {
1414+
assert(solution.col_dual[col] > -options.dual_feasibility_tolerance);
1415+
} else if (basis.col_status[col] == HighsBasisStatus::kUpper) {
1416+
assert(solution.col_dual[col] < options.dual_feasibility_tolerance);
1417+
}
1418+
} else {
1419+
basis.col_status[col] = HighsBasisStatus::kNonbasic;
1420+
}
1421+
}
1422+
13541423
} // namespace presolve

src/presolve/HighsPostsolveStack.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,16 @@ class HighsPostsolveStack {
219219
void transformToPresolvedSpace(std::vector<double>& primalSol) const;
220220
};
221221

222+
struct SlackColSubstitution {
223+
double rhs;
224+
HighsInt row;
225+
HighsInt col;
226+
227+
void undo(const HighsOptions& options,
228+
const std::vector<Nonzero>& rowValues, HighsSolution& solution,
229+
HighsBasis& basis);
230+
};
231+
222232
/// tags for reduction
223233
enum class ReductionType : uint8_t {
224234
kLinearTransform,
@@ -234,6 +244,7 @@ class HighsPostsolveStack {
234244
kForcingColumnRemovedRow,
235245
kDuplicateRow,
236246
kDuplicateColumn,
247+
kSlackColSubstitution,
237248
};
238249

239250
HighsDataStack reductionValues;
@@ -323,6 +334,19 @@ class HighsPostsolveStack {
323334
reductionAdded(ReductionType::kFreeColSubstitution);
324335
}
325336

337+
template <typename RowStorageFormat>
338+
void slackColSubstitution(HighsInt row, HighsInt col, double rhs,
339+
const HighsMatrixSlice<RowStorageFormat>& rowVec) {
340+
rowValues.clear();
341+
for (const HighsSliceNonzero& rowVal : rowVec)
342+
rowValues.emplace_back(origColIndex[rowVal.index()], rowVal.value());
343+
344+
reductionValues.push(
345+
SlackColSubstitution{rhs, origRowIndex[row], origColIndex[col]});
346+
reductionValues.push(rowValues);
347+
reductionAdded(ReductionType::kSlackColSubstitution);
348+
}
349+
326350
template <typename ColStorageFormat>
327351
void doubletonEquation(HighsInt row, HighsInt colSubst, HighsInt col,
328352
double coefSubst, double coef, double rhs,
@@ -710,6 +734,13 @@ class HighsPostsolveStack {
710734
reduction.undo(options, solution, basis);
711735
break;
712736
}
737+
case ReductionType::kSlackColSubstitution: {
738+
SlackColSubstitution reduction;
739+
reductionValues.pop(rowValues);
740+
reductionValues.pop(reduction);
741+
reduction.undo(options, rowValues, solution, basis);
742+
break;
743+
}
713744
default:
714745
printf("Reduction case %d not handled\n",
715746
int(reductions[i - 1].first));
@@ -887,6 +918,13 @@ class HighsPostsolveStack {
887918
reductionValues.pop(reduction);
888919
reduction.undo(options, solution, basis);
889920
}
921+
case ReductionType::kSlackColSubstitution: {
922+
SlackColSubstitution reduction;
923+
reductionValues.pop(rowValues);
924+
reductionValues.pop(reduction);
925+
reduction.undo(options, rowValues, solution, basis);
926+
break;
927+
}
890928
}
891929
}
892930
#ifdef DEBUG_EXTRA

src/qpsolver/dantzigpricing.hpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ class DantzigPricing : public Pricing {
5555
// clang-format off
5656
: runtime(rt), basis(bas), redcosts(rc) {};
5757
// clang-format on
58-
5958
HighsInt price(const QpVector& x, const QpVector& gradient) {
6059
HighsInt minidx = chooseconstrainttodrop(redcosts.getReducedCosts());
6160
return minidx;

0 commit comments

Comments
 (0)