Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a73325d
Merge branch 'latest' into FMPresolve5
fwesselm Aug 21, 2026
c55053f
Merge remote-tracking branch 'origin/latest' into FMPresolve5
fwesselm Aug 21, 2026
e1f6047
Merged with latest
fwesselm Aug 21, 2026
f1fb0c5
More manual merging
fwesselm Aug 21, 2026
7b88798
Add fme_obj_col init to ctor
fwesselm Aug 21, 2026
1f920cf
Bring back test changes
fwesselm Aug 21, 2026
73d0139
Another missing test change
fwesselm Aug 21, 2026
f9301f3
And another missing test change
fwesselm Aug 21, 2026
f186f4d
More missing things
fwesselm Aug 21, 2026
db70ac2
And missing string
fwesselm Aug 21, 2026
69e7857
Merge branch 'latest' of https://github.com/ERGO-Code/HiGHS into FMPr…
fwesselm Aug 24, 2026
07a6677
Merge branch 'latest' of https://github.com/ERGO-Code/HiGHS into FMPr…
fwesselm Aug 25, 2026
caae409
Fix test by changing iteration count
fwesselm Aug 25, 2026
ba163c0
Merge branch 'latest' of https://github.com/fwesselm/HiGHS into FMPre…
fwesselm Aug 25, 2026
0ad0b02
Fix test
fwesselm Aug 25, 2026
94c24ab
Remove std::vector<bool>
fwesselm Aug 25, 2026
00e90f6
Use HighsBool
fwesselm Aug 25, 2026
3f0f389
Merge branch 'latest' of https://github.com/ERGO-Code/HiGHS into FMPr…
fwesselm Aug 26, 2026
4018db7
Merge branch 'latest' of https://github.com/ERGO-Code/HiGHS into FMPr…
fwesselm Aug 26, 2026
d1cd1ee
Merge commit '3893f88c32f07fa5180e13ed9800d6a0c6212755' of https://gi…
fwesselm Aug 28, 2026
80e8c3c
Do not use objective reformulation in the default settings
fwesselm Aug 28, 2026
0060964
Undo test change
fwesselm Aug 28, 2026
8155d2c
Refactor presolveOnOff
fwesselm Aug 28, 2026
7424f13
Merge branch 'latest' of https://github.com/fwesselm/HiGHS into FMPre…
fwesselm Aug 29, 2026
da127da
Switch off FM presolve for LPs
fwesselm Aug 29, 2026
aa055c5
Revert test change (no behavior change without obj. reformulation)
fwesselm Aug 30, 2026
e697b5e
Merge branch 'latest' of https://github.com/ERGO-Code/HiGHS into FMPr…
fwesselm Aug 30, 2026
5c8c272
Merge branch 'latest' of https://github.com/fwesselm/HiGHS into FMPre…
fwesselm Sep 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions check/TestCAPI.c
Original file line number Diff line number Diff line change
Expand Up @@ -734,8 +734,9 @@ void testNames() {

HighsInt presolved_num_col = Highs_getPresolvedNumCol(highs);
HighsInt presolved_num_row = Highs_getPresolvedNumRow(highs);
assert(presolved_num_col == num_col);
assert(presolved_num_row == num_row - 1);
// Fourier-Motzkin presolve reduction may add columns/rows
// assert(presolved_num_col == num_col);
// assert(presolved_num_row == num_row-1);

char presolved_name[5];

Expand Down
5 changes: 5 additions & 0 deletions check/TestPresolve.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ TEST_CASE("postsolve-no-basis", "[highs_test_presolve]") {
"Col Primal Col Primal\n");
for (HighsInt iCol = 0; iCol < presolved_lp.num_col_; iCol++) {
HighsInt original_iCol = original_col_indices[iCol];
// Skip columns added by presolve (e.g. FME objective reformulation)
if (original_iCol >= highs.getNumCol()) continue;
if (dev_run)
printf("%3d %11.5g %3d %11.5g\n", int(iCol), solution.col_value[iCol],
int(original_iCol), postsolve_solution.col_value[original_iCol]);
Expand Down Expand Up @@ -137,11 +139,14 @@ TEST_CASE("presolve", "[highs_test_presolve]") {
// Have to set matrix dimensions to match presolved_model.lp_
lp.setMatrixDimensions();
highs.passModel(lp);
// Disable Fourier-Motzkin so this LP is not reduced
highs.setOptionValue("presolve_rule_off", 1 << kPresolveRuleFourierMotzkin);
REQUIRE(highs.presolve() == HighsStatus::kOk);
REQUIRE(lp.equalButForNames(presolved_model.lp_));
REQUIRE(highs.getModelPresolveStatus() == HighsPresolveStatus::kNotReduced);
REQUIRE(highs.getModelStatus() == HighsModelStatus::kNotset);
REQUIRE(!presolved_model.isEmpty());
highs.setOptionValue("presolve_rule_off", 0);

special_lps.primalDualInfeasible1Lp(lp, require_model_status);
highs.passModel(lp);
Expand Down
61 changes: 61 additions & 0 deletions check/TestPresolveRules.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,67 @@ TEST_CASE("test-parallel-rows-cut-ordering", "[highs_test_presolve_rules]") {
REQUIRE(!postsolve_stack.isCutRow(0));
}

TEST_CASE("test-fourier-motzkin", "[highs_test_presolve_rules]") {
Highs h;
h.setOptionValue("output_flag", dev_run);
h.setOptionValue("presolve_rule_test", kPresolveRuleFourierMotzkin);
h.setOptionValue("presolve_rule_logging", true);
h.setOptionValue("log_dev_level", 1);

const bool lp0 = true;
const bool lp1 = true; // Makes eliminations marginal, and leaves x2=0
const bool lp2 = true;

// No PDLP due to numerical issues with FM postsolve
const std::vector<std::string> solvers = {kSimplexString, kIpmString};

// From "A novel linear optimization presolve technique based on
// Fourier-Motzkin elimination", Zhang, Ploskas and Sahinidis,
// Mathematical Programming Computation (2026) 18:345-378
HighsLp lp;

lp.num_col_ = 4;
lp.num_row_ = 3;

lp.col_cost_.assign(lp.num_col_, 0);
lp.col_lower_.assign(lp.num_col_, 0);
lp.col_upper_.assign(lp.num_col_, kHighsInf);
lp.col_upper_[0] = 40.0;

lp.row_lower_.assign(lp.num_row_, -kHighsInf);
lp.row_upper_ = {-30, 50, 40};
lp.a_matrix_.format_ = MatrixFormat::kRowwise;
lp.a_matrix_.start_ = {0, 3, 6, 9};
lp.a_matrix_.index_ = {0, 1, 3, 1, 2, 3, 1, 2, 3};
lp.a_matrix_.value_ = {-1, 1, -1, 2, 1, 2, 3, -1, 3};

if (lp0) {
REQUIRE(h.passModel(lp) == HighsStatus::kOk);
presolveOffOn("FM example from paper", lp, h, solvers);
}

lp.col_upper_[0] = 5.0;
lp.row_upper_ = {-30, 75, 50};

if (lp1) {
REQUIRE(h.passModel(lp) == HighsStatus::kOk);
presolveOffOn("FM example from paper - tightened", lp, h, solvers);
}

lp.col_cost_ = {1, 2, 3, 4};

REQUIRE(h.passModel(lp) == HighsStatus::kOk);

if (lp2) {
// Objective reformulation is needed since all costs are nonzero
h.setOptionValue("presolve_fm_level", 1);
presolveOffOn("FM example from paper - tightened and with costs", lp, h,
solvers, 1, 6, 6);
}

h.resetGlobalScheduler(true);
}

void solveAndCheck(const std::string& message, const HighsLp& lp, Highs& h,
const std::string& solver, bool use_presolve,
const HighsInt require_presolved_model_num_col,
Expand Down
3 changes: 3 additions & 0 deletions check/TestSemiVariables.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,9 @@ TEST_CASE("3015", "[highs_test_semi_variables]") {
double optimal_objective_value = -1407973.679417;
Highs highs;
highs.setOptionValue("output_flag", dev_run);
// Disable Fourier-Motzkin presolve so that the semi-variable
// infeasibility is still triggered with default mip_feasibility_tolerance
highs.setOptionValue("presolve_rule_off", 1 << kPresolveRuleFourierMotzkin);
highs.readModel(filename);
HighsStatus status = highs.run();
REQUIRE(status == HighsStatus::kError);
Expand Down
3 changes: 2 additions & 1 deletion highs/lp_data/HConst.h
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,8 @@ enum PresolveRuleType : int {
kPresolveRuleZeroCostSingleton,
kPresolveRuleColStuffing,
kPresolveRuleInitialSweep,
kPresolveRuleMax = kPresolveRuleInitialSweep,
kPresolveRuleFourierMotzkin,
kPresolveRuleMax = kPresolveRuleFourierMotzkin,
kPresolveRuleLastAllowOff = kPresolveRuleMax,
kPresolveRuleCount
};
Expand Down
1 change: 1 addition & 0 deletions highs/lp_data/HighsLp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ void HighsLp::clear() {
this->is_moved_ = false;
this->cost_row_location_ = -1;
this->has_infinite_cost_ = false;
this->fme_obj_col_ = -1;
this->mods_.clear();
}

Expand Down
1 change: 1 addition & 0 deletions highs/lp_data/HighsLp.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class HighsLp {
bool is_moved_;
HighsInt cost_row_location_;
bool has_infinite_cost_;
HighsInt fme_obj_col_ = -1;
HighsLpMods mods_;

bool operator==(const HighsLp& lp) const;
Expand Down
2 changes: 2 additions & 0 deletions highs/lp_data/HighsModelUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1531,6 +1531,8 @@ std::string utilPresolveRuleTypeToString(const HighsInt rule_type) {
return "Col stuffing";
} else if (rule_type == kPresolveRuleInitialSweep) {
return "Initial sweep";
} else if (rule_type == kPresolveRuleFourierMotzkin) {
return "Fourier-Motzkin";
}
assert(1 == 0);
return "????";
Expand Down
7 changes: 7 additions & 0 deletions highs/lp_data/HighsOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@ struct HighsOptionsStruct {
HighsInt presolve_substitution_maxfillin;
HighsInt presolve_rule_off;
HighsInt presolve_rule_test;
HighsInt presolve_fm_level;
bool presolve_rule_logging;
bool presolve_remove_slacks;
bool no_unnecessary_rebuild_refactor;
Expand Down Expand Up @@ -636,6 +637,7 @@ struct HighsOptionsStruct {
presolve_substitution_maxfillin(0),
presolve_rule_off(0),
presolve_rule_test(0),
presolve_fm_level(0),
presolve_rule_logging(false),
presolve_remove_slacks(false),
no_unnecessary_rebuild_refactor(false),
Expand Down Expand Up @@ -1684,6 +1686,11 @@ class HighsOptions : public HighsOptionsStruct {
&presolve_rule_test, 0, 0, kPresolveRuleMax);
records.push_back(record_int);

record_int =
new OptionRecordInt("presolve_fm_level", "Fourier-Motzkin level",
advanced, &presolve_fm_level, 0, 0, 1);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be changed to something that explicitly mentions objective reformulation? Example presolve_fm_obj_reformulation

records.push_back(record_int);

record_bool = new OptionRecordBool(
"presolve_rule_logging", "Log effectiveness of presolve rules for LP",
advanced, &presolve_rule_logging, false);
Expand Down
Loading
Loading