Skip to content

Introduces presolve "light" - #3167

Merged
jajhall merged 72 commits into
latestfrom
fix-3040
Aug 9, 2026
Merged

Introduces presolve "light"#3167
jajhall merged 72 commits into
latestfrom
fix-3040

Conversation

@jajhall

@jajhall jajhall commented Jul 25, 2026

Copy link
Copy Markdown
Member

Presolve "light" performs an initial sweep removing empty and fixed columns, and empty, singleton and redundant rows. For some practical problems, this removes significant numbers of rows and columns, and avoids the overhead of forming the general presolve data structure for the whole original model.

After the initial sweep, presolve light avoids applying the following expensive presolve rules

  • DependentEquations
  • DependentFreeCols
  • Aggregator
  • ParallelRowsAndCols
  • Sparsify
  • Probing
  • Enumeration
  • DualFixing
  • ColStuffing

Julian Hall and others added 30 commits March 25, 2026 12:17
@jajhall
jajhall requested a review from fwesselm July 25, 2026 13:17
@jajhall jajhall self-assigned this Jul 25, 2026
@jajhall jajhall added Enhancement New feature or request Presolve labels Jul 25, 2026
@jajhall

jajhall commented Jul 25, 2026

Copy link
Copy Markdown
Member Author

This closes #3040

@codecov

codecov Bot commented Jul 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.11628% with 203 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.77%. Comparing base (d16cfda) to head (8ffae8f).
⚠️ Report is 92 commits behind head on latest.

Files with missing lines Patch % Lines
highs/presolve/PresolveTimer.h 0.00% 94 Missing ⚠️
highs/presolve/HPresolve.cpp 91.05% 61 Missing ⚠️
highs/presolve/HPresolveAnalysis.cpp 53.57% 13 Missing ⚠️
highs/lp_data/HighsInterface.cpp 30.76% 9 Missing ⚠️
highs/presolve/HighsPostsolveStack.h 76.92% 6 Missing ⚠️
highs/util/HFactor.cpp 92.06% 5 Missing ⚠️
highs/lp_data/HighsModelUtils.cpp 0.00% 3 Missing ⚠️
highs/mip/HighsImplications.cpp 25.00% 3 Missing ⚠️
highs/simplex/HighsSimplexAnalysis.cpp 0.00% 3 Missing ⚠️
highs/io/FilereaderLp.cpp 0.00% 2 Missing ⚠️
... and 3 more
Additional details and impacted files
@@            Coverage Diff             @@
##           latest    #3167      +/-   ##
==========================================
+ Coverage   72.72%   72.77%   +0.05%     
==========================================
  Files         442      444       +2     
  Lines      106631   107416     +785     
  Branches    17173    17283     +110     
==========================================
+ Hits        77544    78172     +628     
- Misses      28810    28967     +157     
  Partials      277      277              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@fwesselm fwesselm left a comment

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.

I can't really comment on the changes to HFactor etc.

The presolve changes look reasonable, although I am wondering whether it would be better to have a member variable (like bool in_initial_sweep_), thereby avoiding changes to function signatures.

std::copy(valuesNew.cbegin(), valuesNew.cend(), values.begin());
#else
for (size_t i = index.size(); i < static_cast<size_t>(origSize); i++)
values[i] = zero;

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.

values[i] = T{};

But, this would initialise the basis with HighsBasisStatus::kLower. I am not sure if this is problematic.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It's not problematic.

The method scatters "values" for the presolved problem into their position in the original problem. The values not assigned in this process are for the rows and columns removed. Their values are assigned during postsolve, without reference to what was stored in the particular location - so it doesn't matter what was stored in that location. Indeed, previously, what was in the particular location was the value for a row or column in the presolved problem. I found this very confusing when developing.

The "it doesn't matter" argument is also reflected in the use of std::numeric_limits<T>::signaling_NaN() to fill these locations when DEBUG_EXTRA is assigned. This NaN assignment ensures that a check can be made (with bool containsNanOrInf) that a value is assigned in postsolve for all the (double) locations corresponding to rows and columns removed.

In the case of T = HighsBasisStatus, ideally zero would yield HighsBasisStatus::kNonbasic as that's never assigned by the simplex solver or crossover, so would be clearly "not assigned". However, this would require the definition of HighsBasisStatus to be changed so kNonbasic = 1 and, even if the values of kHighsBasisStatus* were changed in interfaces/highs_c_api.h there are sure to be users who are assuming that HighsBasisStatus::kLower casts to 0, who would then be messed up, so I'll live with initialising to HighsBasisStatus::kLower.

Comment thread highs/presolve/HPresolve.cpp Outdated

double HPresolve::getMaxAbsColVal(HighsInt col) const {
double HPresolve::getMaxAbsColVal(HighsInt col,
const bool initial_sweep) const {

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.

Maybe add a member variable bool bool in_initial_sweep_?

Thereby, the signatures of HPresolve::getMaxAbsColVal etc. would not need to be changed.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, this is more elegant. The explicit change was helpful in development

Comment thread highs/presolve/HighsPostsolveStack.cpp Outdated
--numCol;
void HighsPostsolveStack::compressIndexMap(
const std::vector<HighsInt>& newIndex, std::vector<HighsInt>& origIndex) {
// loop over entries, decrease entry counter for deleted entriess

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.

entries?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Corrected

Comment thread highs/presolve/HighsPostsolveStack.h Outdated
size_t check_reduction = -35044;

auto solutionLogging = [&](const std::string& message) {
return;

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.

The lambda directly returns?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, I see that it's unnecessary. That said, I've commented out the debugging lambdas and the calls to them to improve code coverage! I think they are worth keeping for future debugging

@jajhall

jajhall commented Aug 7, 2026

Copy link
Copy Markdown
Member Author

One consequence of this PR (@fwesselm and @Opt-Mucca) is that the initial sweep is always performed, so the behaviour of any solve involving presolve (for a problem with empty columns, fixed columns, empty rows, singleton rows or redundant rows) will change. Clearly this will change MIP performance (in particular) but the initial sweep is of very low cost if it doesn't yield reductions, and it's more efficient than achieving these reductions in "standard" presolve. Hence I think that

  • We have to accept the volatility
  • We don't need the ability to switch off the initial sweep

@jajhall
jajhall merged commit 005710c into latest Aug 9, 2026
521 checks passed
@jajhall
jajhall deleted the fix-3040 branch August 9, 2026 14:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement New feature or request Presolve

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants