Skip to content

Suboptimal solution reported as optimal on a small MIP with presolve enabled #3561

Closed

Description

@hodangkhoinguyen

What version of HiGHS and what language are you using?
Branch latest (17a99d4). Release 1.15.1 (04024d7) is not affected.
Using the standalone C++ command-line executable.

Which solver are you using (e.g. LP, MIP, QP)
MIP

What operating system (Linux, Windows, ...) and version?
Debian GNU/Linux 12, x86-64

What did you do?
Ran the attached model rand_numer_006_mr8.mps.txt (17 rows, 91 columns, 7 binary)

highs rand_numer_006_mr8.mps --threads 1 --time_limit 120

What did you expect to see
Optimal, objective ≈ 144.68

What did you see instead?
Optimal, objective 78863.54 (primal and dual bound both 78863.5419419, gap 0%)

Disabling presolve returns an optimal solution with objective 144.680849, and release 1.15.1 returns 144.680846 with default settings:

highs rand_numer_006_mr8.mps --threads 1 --time_limit 120 --presolve off

The attached witness.txt lists a verified feasible solution with objective 144.68085, disproving the reported optimality claim. Variables not listed are zero.

Anything else we should know about your project / environment
The model is a random MIP with every finite row side relaxed by 1e-6 * max(1, abs(side)). The unrelaxed model solves to 144.68 on the same build. HiGHS warns "Problem has some excessively small bounds on constraints" because zero row sides become ±1e-6. The pattern is similar to #3487, which is fixed on this build.

A second model, rand_numer_060_mr8.mps.txt (7 rows, 18 columns), shows the same behaviour: Optimal 80652.21 by default and 171.336426 with presolve off; witness_060.txt lists a verified feasible solution with objective 171.33643.

witness_060.txt
rand_numer_006_mr8.mps.txt
rand_numer_060_mr8.mps.txt
witness.txt

Activity

  1. jajhall commented on Nov 6, 2026

    @jajhall
    SponsorMember

    Reproduced with latest (9ba15c4)

  2. DoumarCharles commented on Nov 6, 2026

    @DoumarCharles

    I have been looking at this presolve code, and I found what causes this. Here is a proposed fix.

    The wrong fixing comes from dual-fixing probing (rule 20; presolve_rule_off = 1048576 avoids it). updateLhsRedundant and updateRhsRedundant in HighsDomain.cpp count a row side as redundant when its activity bound is within feastol of the side. On rand_numer_060_mr8, probing x1 = 0 leaves r0 1e-6 short of redundant, but it is marked redundant anyway, so x3 and x8 are dual-fixed down. That violates r0, the probe is declared infeasible, and x1 is fixed to 1.

    Without the tolerance, both models solve correctly (144.680846 and 171.336411):

    --- a/highs/mip/HighsDomain.cpp
    +++ b/highs/mip/HighsDomain.cpp
    @@ -733,8 +733,7 @@
           mipsolver->model_->row_upper_[row] == kHighsInf)
         return;
     
    -  if (domain->getMaxActivity(row) <=
    -      mipsolver->model_->row_upper_[row] + mipsolver->mipdata_->feastol) {
    +  if (domain->getMaxActivity(row) <= mipsolver->model_->row_upper_[row]) {
         redundantRowInds_.push_back(idx);
         redundantRowFlags_[idx] = 1;
         if (!isEnabled()) ++numGlobalRedundantRows_;
    @@ -747,8 +746,7 @@
           mipsolver->model_->row_lower_[row] == -kHighsInf)
         return;
     
    -  if (domain->getMinActivity(row) >=
    -      mipsolver->model_->row_lower_[row] - mipsolver->mipdata_->feastol) {
    +  if (domain->getMinActivity(row) >= mipsolver->model_->row_lower_[row]) {
         redundantRowInds_.push_back(idx);
         redundantRowFlags_[idx] = 1;
         if (!isEnabled()) ++numGlobalRedundantRows_;

    I used an AI assistant to help trace this.

  3. Opt-Mucca commented on Nov 6, 2026

    @Opt-Mucca
    Collaborator

    @DoumarCharles thanks for tracking down the issue! I've proposed a fix in #3575 that works for the problematic instance. In general I'd prefer to keep tolerances here as they're used constantly in this context throughout the code. It was some avoidable unlucky floating precision stuff that was causing this instance to produce incorrect results.

    @hodangkhoinguyen thanks for raising the issue!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions