Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
59ba433
Added clocks to presolve analysis
Mar 25, 2026
697a00e
Now to see why top presolve clock isn't recording/reporting
Mar 25, 2026
2d8f4b2
Added highs/presolve/PresolveTimer.h and unit test presolve-time
Mar 25, 2026
d9862d4
Logging temporary data in dependent equations reduction
Mar 25, 2026
4518f70
Aded heuristic to perform dependent equation presolve rule; removed p…
jajhall Mar 25, 2026
8bc59ca
Use of std::pow no unambiguous?
Mar 26, 2026
ff138f5
Reduced kMaxDependentEquationsTime to 10
Mar 26, 2026
ea86ff4
kMaxDependentEquationsTime=10 is too small: try kMaxDependentEquation…
Mar 26, 2026
447c66b
kMaxDependentEquationsTime back to 1000; formatted
Mar 26, 2026
3e58dde
Now to correct kernel_num_el
Mar 27, 2026
6ceeddb
Move timeout code down
Mar 27, 2026
8969c4c
Move timeout code down
Mar 27, 2026
1516fba
Use resizeIntDataShift
Mar 28, 2026
31b27b7
Now to tidy up
Mar 29, 2026
1ebb581
Formatted
Mar 29, 2026
25e2870
Cleaned up dev code
Mar 29, 2026
61ef98d
Merged latest into this branch
Apr 3, 2026
f235771
Removed cmake/sources-python.cmake
jajhall May 20, 2026
58d6fea
Need to move analysis_.setup for Presolve
jajhall May 20, 2026
2dc6b84
Moved start of presolve profiling to okSetInput, and accounting for a…
jajhall May 20, 2026
c7338ee
Cleaned up dev printing
jajhall May 20, 2026
c74e875
Now to time InitialCol: is fixed components
jajhall May 20, 2026
d904ce0
Formatted
jajhall May 20, 2026
281fde5
Merge branch 'latest' into presolve-profile
jajhall May 21, 2026
f8528d9
Introduced kPresolveRuleDualFixing and kPresolveRuleColStuffing
May 21, 2026
1e3a1df
Column stuffing and dual fixing can now be logged and switched off
May 21, 2026
f40838e
Now to add presolve_light option
May 21, 2026
0423519
Added presolve_light option to skip expensive rules
May 21, 2026
560c7aa
Merge branch 'latest' of https://github.com/ERGO-Code/HiGHS into pres…
fwesselm May 22, 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
17 changes: 17 additions & 0 deletions check/TestPresolve.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -910,3 +910,20 @@ TEST_CASE("presolve-issue-2874", "[highs_test_presolve]") {
REQUIRE(highs.presolve() == HighsStatus::kOk);
REQUIRE(highs.getModelPresolveStatus() == HighsPresolveStatus::kInfeasible);
}

TEST_CASE("presolve-light", "[highs_test_presolve]") {
std::string model_file =
std::string(HIGHS_DIR) + "/check/instances/afiro.mps";
Highs highs;
highs.setOptionValue("output_flag", dev_run);
highs.readModel(model_file);
for (HighsInt k = 0; k < 2; k++) {
REQUIRE(highs.presolve() == HighsStatus::kOk);
REQUIRE(highs.getModelPresolveStatus() == HighsPresolveStatus::kReduced);
const HighsLp& presolved_lp = highs.getPresolvedLp();
if (dev_run) printf("%s presolved LP has %d columns; %d rows and %d nonzeros\n",
k == 0 ? "Fully" : "Lightly", int(presolved_lp.num_col_),
int(presolved_lp.num_row_), int(presolved_lp.numNz()));
highs.setOptionValue("presolve_light", true);
}
}
1 change: 1 addition & 0 deletions cmake/sources.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,7 @@ set(highs_headers
presolve/ICrashUtil.h
presolve/ICrashX.h
presolve/PresolveComponent.h
presolve/PresolveTimer.h
qpsolver/a_asm.hpp
qpsolver/a_quass.hpp
qpsolver/basis.hpp
Expand Down
2 changes: 1 addition & 1 deletion highs/Highs.h
Original file line number Diff line number Diff line change
Expand Up @@ -717,7 +717,7 @@ class Highs {
* @brief Get the number of (constraint matrix) nonzeros in the incumbent
* model
*/
HighsInt getNumNz() const { return model_.lp_.a_matrix_.numNz(); }
HighsInt getNumNz() const { return model_.lp_.numNz(); }

/**
* @brief Get the number of Hessian matrix nonzeros in the incumbent model
Expand Down
4 changes: 2 additions & 2 deletions highs/interfaces/highs_c_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1184,7 +1184,7 @@ HighsInt Highs_getPresolvedNumRow(const void* highs) {
}

HighsInt Highs_getPresolvedNumNz(const void* highs) {
return ((Highs*)highs)->getPresolvedLp().a_matrix_.numNz();
return ((Highs*)highs)->getPresolvedLp().numNz();
}

// Gets pointers to all the public data members of HighsLp: avoids
Expand Down Expand Up @@ -1231,7 +1231,7 @@ static HighsInt Highs_getHighsLpData(const HighsLp& lp, const HighsInt a_format,
(desired_a_format == MatrixFormat::kRowwise &&
lp.a_matrix_.isRowwise())) {
// Incumbent format is OK
*num_nz = lp.a_matrix_.numNz();
*num_nz = lp.numNz();
if (a_start)
memcpy(a_start, lp.a_matrix_.start_.data(),
num_start_entries * sizeof(HighsInt));
Expand Down
5 changes: 5 additions & 0 deletions highs/io/HighsIO.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,11 @@ const std::string highsBoolToString(const bool b, const HighsInt field_width) {
return b ? " true" : "false";
}

const std::string highsIntToPlural(const HighsInt i, const bool y) {
if (y) return i == 1 ? "y" : "ies";
return i == 1 ? "" : "s";
}

const std::string highsTimeToString(const double time) {
return
#ifndef NDEBUG
Expand Down
1 change: 1 addition & 0 deletions highs/io/HighsIO.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ std::string highsFormatToString(const char* format, ...);
const std::string highsBoolToString(const bool b,
const HighsInt field_width = 2);

const std::string highsIntToPlural(const HighsInt i, const bool y = false);
const std::string highsInsertMdEscapes(const std::string& from_string);
const std::string highsInsertMdId(const std::string& from_string);
const std::string highsTimeToString(const double time);
Expand Down
8 changes: 6 additions & 2 deletions highs/lp_data/HConst.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,14 @@ enum HighsAnalysisLevel {
kHighsAnalysisLevelNlaTime = 32,
kHighsAnalysisLevelMipData = 64,
kHighsAnalysisLevelMipTime = 128,
kHighsAnalysisLevelPresolveTime = 256,
kHighsAnalysisLevelMin = kHighsAnalysisLevelNone,
kHighsAnalysisLevelMax =
kHighsAnalysisLevelModelData + kHighsAnalysisLevelSolverSummaryData +
kHighsAnalysisLevelSolverRuntimeData + kHighsAnalysisLevelSolverTime +
kHighsAnalysisLevelNlaData + kHighsAnalysisLevelNlaTime +
kHighsAnalysisLevelMipData + kHighsAnalysisLevelMipTime
kHighsAnalysisLevelMipData + kHighsAnalysisLevelMipTime +
kHighsAnalysisLevelPresolveTime
};

enum class HighsVarType : uint8_t {
Expand Down Expand Up @@ -275,7 +277,9 @@ enum PresolveRuleType : int {
kPresolveRuleSparsify,
kPresolveRuleProbing,
kPresolveRuleEnumeration,
kPresolveRuleMax = kPresolveRuleEnumeration,
kPresolveRuleDualFixing,
kPresolveRuleColStuffing,
kPresolveRuleMax = kPresolveRuleColStuffing,
kPresolveRuleLastAllowOff = kPresolveRuleMax,
kPresolveRuleCount
};
Expand Down
8 changes: 4 additions & 4 deletions highs/lp_data/Highs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1375,7 +1375,7 @@ HighsStatus Highs::calledOptimizeModel() {
time += timer_.read(timer_.solve_clock);
};

const bool unconstrained_lp = incumbent_lp.a_matrix_.numNz() == 0;
const bool unconstrained_lp = incumbent_lp.numNz() == 0;
assert(incumbent_lp.num_row_ || unconstrained_lp);
const bool has_basis = basis_.useful;
if (has_basis) {
Expand Down Expand Up @@ -3676,8 +3676,8 @@ HighsPresolveStatus Highs::runPresolve(const bool force_lp_presolve,
original_lp.num_col_ - reduced_lp.num_col_;
presolve_.info_.n_rows_removed =
original_lp.num_row_ - reduced_lp.num_row_;
presolve_.info_.n_nnz_removed = (HighsInt)original_lp.a_matrix_.numNz() -
(HighsInt)reduced_lp.a_matrix_.numNz();
presolve_.info_.n_nnz_removed =
(HighsInt)original_lp.numNz() - (HighsInt)reduced_lp.numNz();
// Clear any scaling information inherited by the reduced LP
reduced_lp.clearScale();
assert(lpDimensionsOk("RunPresolve: reduced_lp", reduced_lp,
Expand All @@ -3687,7 +3687,7 @@ HighsPresolveStatus Highs::runPresolve(const bool force_lp_presolve,
case HighsPresolveStatus::kReducedToEmpty: {
presolve_.info_.n_cols_removed = original_lp.num_col_;
presolve_.info_.n_rows_removed = original_lp.num_row_;
presolve_.info_.n_nnz_removed = (HighsInt)original_lp.a_matrix_.numNz();
presolve_.info_.n_nnz_removed = (HighsInt)original_lp.numNz();
break;
}
default:
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 @@ -64,6 +64,7 @@ class HighsLp {
bool equalNames(const HighsLp& lp) const;
bool equalScaling(const HighsLp& lp) const;
bool isMip() const;
HighsInt numNz() const { return this->a_matrix_.numNz(); }
bool hasSemiVariables() const;
bool hasInfiniteCost(const double infinite_cost) const;
bool hasMods() const;
Expand Down
8 changes: 4 additions & 4 deletions highs/lp_data/HighsLpUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ HighsStatus assessLp(HighsLp& lp, const HighsOptions& options) {
// If the LP has no columns the matrix must be empty and there is
// nothing left to test
if (lp.num_col_ == 0) {
assert(!lp.a_matrix_.numNz());
assert(!lp.numNz());
return HighsStatus::kOk;
}
// From here, any LP has lp.num_col_ > 0 and lp.a_matrix_.start_[lp.num_col_]
Expand All @@ -92,7 +92,7 @@ HighsStatus assessLp(HighsLp& lp, const HighsOptions& options) {
if (return_status == HighsStatus::kError) return return_status;
// If entries have been removed from the matrix, resize the index
// and value vectors to prevent bug in presolve
HighsInt lp_num_nz = lp.a_matrix_.numNz();
HighsInt lp_num_nz = lp.numNz();
if ((HighsInt)lp.a_matrix_.index_.size() > lp_num_nz)
lp.a_matrix_.index_.resize(lp_num_nz);
if ((HighsInt)lp.a_matrix_.value_.size() > lp_num_nz)
Expand Down Expand Up @@ -3102,7 +3102,7 @@ void reportPresolveReductions(const HighsLogOptions& log_options,
const HighsLp& lp, const HighsLp& presolved_lp) {
const HighsInt num_col_from = lp.num_col_;
const HighsInt num_row_from = lp.num_row_;
const HighsInt num_nz_from = lp.a_matrix_.numNz();
const HighsInt num_nz_from = lp.numNz();
HighsInt num_col_to = 0;
HighsInt num_row_to = 0;
HighsInt num_nz_to = 0;
Expand All @@ -3124,7 +3124,7 @@ void reportPresolveReductions(const HighsLogOptions& log_options,
case HighsPresolveStatus::kTimeout: {
num_col_to = presolved_lp.num_col_;
num_row_to = presolved_lp.num_row_;
num_nz_to = presolved_lp.a_matrix_.numNz();
num_nz_to = presolved_lp.numNz();
message =
presolve_status == HighsPresolveStatus::kTimeout ? "- Timeout" : "";
break;
Expand Down
10 changes: 7 additions & 3 deletions highs/lp_data/HighsModelUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -598,10 +598,10 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options,
assert(lp.row_names_.size() == static_cast<size_t>(lp.num_row_));
// Determine number of nonzeros including the objective function
// and, hence, determine whether there is an objective function
HighsInt num_nz = lp.a_matrix_.numNz();
HighsInt num_nz = lp.numNz();
for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++)
if (lp.col_cost_[iCol]) num_nz++;
const bool empty_cost_row = num_nz == lp.a_matrix_.numNz();
const bool empty_cost_row = num_nz == lp.numNz();
const bool has_objective = !empty_cost_row || model.hessian_.dim_;
// Writes the solution using the GLPK raw style (defined in
// api/wrsol.c) or pretty style (defined in api/prsol.c)
Expand Down Expand Up @@ -702,7 +702,7 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options,
const HighsInt glpsol_num_row = num_row + delta_num_row;
// If the cost row isn't reported, then the number of nonzeros is
// just the number in the constraint matrix
if (cost_row_location <= 0) num_nz = lp.a_matrix_.numNz();
if (cost_row_location <= 0) num_nz = lp.numNz();
// Record the discrete nature of the model
HighsInt num_integer = 0;
HighsInt num_binary = 0;
Expand Down Expand Up @@ -1515,6 +1515,10 @@ std::string utilPresolveRuleTypeToString(const HighsInt rule_type) {
return "Probing";
} else if (rule_type == kPresolveRuleEnumeration) {
return "Enumeration";
} else if (rule_type == kPresolveRuleDualFixing) {
return "Dual fixing";
} else if (rule_type == kPresolveRuleColStuffing) {
return "Col stuffing";
}
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 @@ -421,6 +421,7 @@ struct HighsOptionsStruct {
bool lp_presolve_requires_basis_postsolve;
bool mps_parser_type_free;
bool use_warm_start;
bool presolve_light;
HighsInt keep_n_rows;
HighsInt cost_scale_factor;
HighsInt allowed_matrix_scale_factor;
Expand Down Expand Up @@ -588,6 +589,7 @@ struct HighsOptionsStruct {
lp_presolve_requires_basis_postsolve(false),
mps_parser_type_free(false),
use_warm_start(true),
presolve_light(false),
keep_n_rows(0),
cost_scale_factor(0),
allowed_matrix_scale_factor(0),
Expand Down Expand Up @@ -1452,6 +1454,11 @@ class HighsOptions : public HighsOptionsStruct {
advanced, &use_warm_start, true);
records.push_back(record_bool);

record_bool = new OptionRecordBool("presolve_light",
"Use only low-cost presolve rules",
advanced, &presolve_light, false);
records.push_back(record_bool);

record_int =
new OptionRecordInt("keep_n_rows",
"For multiple N-rows in MPS files: delete rows / "
Expand Down
8 changes: 4 additions & 4 deletions highs/lp_data/HighsSolve.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ HighsStatus solveLp(HighsLpSolverObject& solver_object, const string message) {
}
return return_status;
};
if (!solver_object.lp_.num_row_ || solver_object.lp_.a_matrix_.numNz() == 0) {
if (!solver_object.lp_.num_row_ || solver_object.lp_.numNz() == 0) {
// LP is unconstrained due to having no rows or a zero constraint
// matrix, so solve directly
call_status = solveUnconstrainedLp(solver_object);
Expand Down Expand Up @@ -208,11 +208,11 @@ HighsStatus solveUnconstrainedLp(const HighsOptions& options, const HighsLp& lp,
resetModelStatusAndHighsInfo(model_status, highs_info);

// Check that the LP really is unconstrained!
assert(lp.num_row_ == 0 || lp.a_matrix_.numNz() == 0);
assert(lp.num_row_ == 0 || lp.numNz() == 0);
if (lp.num_row_ > 0) {
// LP has rows, but should only be here if the constraint matrix
// is zero
if (lp.a_matrix_.numNz() > 0) return HighsStatus::kError;
if (lp.numNz() > 0) return HighsStatus::kError;
}

highsLogUser(options.log_options, HighsLogType::kInfo,
Expand Down Expand Up @@ -465,7 +465,7 @@ void assessExcessiveObjectiveBoundScaling(const HighsLogOptions log_options,

double min_matrix_value = kHighsInf;
double max_matrix_value = -kHighsInf;
const HighsInt num_matrix_nz = lp.a_matrix_.numNz();
const HighsInt num_matrix_nz = lp.numNz();
for (HighsInt iEl = 0; iEl < num_matrix_nz; iEl++)
assessFiniteNonzero(lp.a_matrix_.value_[iEl], min_matrix_value,
max_matrix_value);
Expand Down
34 changes: 18 additions & 16 deletions highs/mip/MipTimer.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#ifndef MIP_MIPTIMER_H_
#define MIP_MIPTIMER_H_

// Clocks for profiling the MIP dual mip solver
// Clocks for profiling the MIP solver
enum iClockMip {
kMipClockTotal = 0,
kMipClockPresolve,
Expand Down Expand Up @@ -114,7 +114,7 @@ enum iClockMip {
kNumMipClock //!< Number of MIP clocks
};

const double tolerance_percent_report = 0.1;
static const double kMipClockTolerancePercentReport = 0.1;

class MipTimer {
public:
Expand Down Expand Up @@ -371,7 +371,7 @@ class MipTimer {
kMipClockSearch,
kMipClockPostsolve};
reportMipClockList("MipLevl1", mip_clock_list, mip_timer_clock,
kMipClockTotal, tolerance_percent_report);
kMipClockTotal, kMipClockTolerancePercentReport);
};

void reportMipSolveLpClock(const HighsTimerClock& mip_timer_clock) {
Expand All @@ -385,20 +385,20 @@ class MipTimer {
kMipClockHipoSolveLp,
kMipClockIpxSolveLp};
reportMipClockList("MipSlvLp", mip_clock_list, mip_timer_clock,
kMipClockTotal); //, tolerance_percent_report);
kMipClockTotal); //, kMipClockTolerancePercentReport);
};

void reportMipSubMipSolveClock(const HighsTimerClock& mip_timer_clock) {
const std::vector<HighsInt> mip_clock_list{kMipClockSubMipSolve};
reportMipClockList("MipSlvLp", mip_clock_list, mip_timer_clock,
kMipClockTotal); //, tolerance_percent_report);
kMipClockTotal); //, kMipClockTolerancePercentReport);
};

void reportMipPresolveClock(const HighsTimerClock& mip_timer_clock) {
const std::vector<HighsInt> mip_clock_list{kMipClockProbingPresolve,
kMipClockEnumerationPresolve};
reportMipClockList("MipPrslv", mip_clock_list, mip_timer_clock,
kMipClockRunPresolve, tolerance_percent_report);
kMipClockRunPresolve, kMipClockTolerancePercentReport);
};

void reportAltEvaluateRootNodeClock(const HighsTimerClock& mip_timer_clock) {
Expand All @@ -407,7 +407,7 @@ class MipTimer {
kMipClockEvaluateRootNode2};
reportMipClockList(
"AltEvaluateRootNode", mip_clock_list, mip_timer_clock,
kMipClockEvaluateRootNode); //, tolerance_percent_report);
kMipClockEvaluateRootNode); //, kMipClockTolerancePercentReport);
};

void reportMipEvaluateRootNodeClock(const HighsTimerClock& mip_timer_clock) {
Expand All @@ -433,7 +433,7 @@ class MipTimer {
};
reportMipClockList(
"MipEvaluateRootNode", mip_clock_list, mip_timer_clock,
kMipClockEvaluateRootNode); //, tolerance_percent_report);
kMipClockEvaluateRootNode); //, kMipClockTolerancePercentReport);
};

void reportMipRootSeparationClock(const HighsTimerClock& mip_timer_clock) {
Expand All @@ -442,8 +442,9 @@ class MipTimer {
kMipClockRootSeparationFinishAnalyticCentreComputation,
kMipClockRootSeparationCentralRounding,
kMipClockRootSeparationEvaluateRootLp};
reportMipClockList("MipRootSeparation", mip_clock_list, mip_timer_clock,
kMipClockRootSeparation); //, tolerance_percent_report);
reportMipClockList(
"MipRootSeparation", mip_clock_list, mip_timer_clock,
kMipClockRootSeparation); //, kMipClockTolerancePercentReport);
};

void reportMipSearchClock(const HighsTimerClock& mip_timer_clock) {
Expand All @@ -455,15 +456,15 @@ class MipTimer {
// kMipClock@
};
reportMipClockList("MipSerch", mip_clock_list, mip_timer_clock,
kMipClockSearch, tolerance_percent_report);
kMipClockSearch, kMipClockTolerancePercentReport);
};

void reportMipDiveClock(const HighsTimerClock& mip_timer_clock) {
const std::vector<HighsInt> mip_clock_list{
kMipClockDiveEvaluateNode, kMipClockDivePrimalHeuristics,
kMipClockTheDive, kMipClockBacktrackPlunge, kMipClockPerformAging2};
reportMipClockList("MipDive_", mip_clock_list, mip_timer_clock,
kMipClockDive, tolerance_percent_report);
kMipClockDive, kMipClockTolerancePercentReport);
};

void reportMipDivePrimalHeuristicsClock(
Expand All @@ -472,7 +473,7 @@ class MipTimer {
kMipClockDiveRandomizedRounding, kMipClockDiveRens, kMipClockDiveRins};
reportMipClockList("MipDivePrimalHeuristics", mip_clock_list,
mip_timer_clock, kMipClockDivePrimalHeuristics,
tolerance_percent_report);
kMipClockTolerancePercentReport);
};

void reportMipNodeSearchClock(const HighsTimerClock& mip_timer_clock) {
Expand All @@ -481,16 +482,17 @@ class MipTimer {
// kMipClockSearchBacktrack,
kMipClockOpenNodesToQueue1, kMipClockEvaluateNode1,
kMipClockNodeSearchSeparation}; //, kMipClockStoreBasis};
reportMipClockList("MipNodeSearch", mip_clock_list, mip_timer_clock,
kMipClockNodeSearch); //, tolerance_percent_report);
reportMipClockList(
"MipNodeSearch", mip_clock_list, mip_timer_clock,
kMipClockNodeSearch); //, kMipClockTolerancePercentReport);
};

void reportMipSeparationClock(const HighsTimerClock& mip_timer_clock) {
const std::vector<HighsInt> mip_clock_list{
kMipClockImplboundSepa, kMipClockCliqueSepa, kMipClockTableauSepa,
kMipClockPathAggrSepa, kMipClockModKSepa};
reportMipClockList("MipSeparation", mip_clock_list, mip_timer_clock,
kMipClockTotal); //, tolerance_percent_report);
kMipClockTotal); //, kMipClockTolerancePercentReport);
};

void csvMipClock(const std::string model_name,
Expand Down
Loading
Loading