Skip to content

MIP Worker Refactor#2886

Open
Opt-Mucca wants to merge 200 commits intolatestfrom
hmw-mt
Open

MIP Worker Refactor#2886
Opt-Mucca wants to merge 200 commits intolatestfrom
hmw-mt

Conversation

@Opt-Mucca
Copy link
Collaborator

No description provided.

galabovaa and others added 30 commits February 13, 2025 06:11
Copy link
Collaborator

@fwesselm fwesselm left a comment

Choose a reason for hiding this comment

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

Wow, @Opt-Mucca, looks like a lot of work! Good stuff.

I have added some minor comments (for the first 10 files). I will continue soon.

@Opt-Mucca
Copy link
Collaborator Author

@fwesselm I deleted the line in HighsCliqueTable and added const following all your suggestions

Copy link
Collaborator

@fwesselm fwesselm left a comment

Choose a reason for hiding this comment

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

Some more unqualified comments from me. I will continue in the afternoon.

@cmatraki
Copy link

cmatraki commented Mar 3, 2026

I can't reproduce it with the latest version, but commit 4e29a0d with default options, finds app1-2 from miplib infeasible:

Running HiGHS 1.12.0 (git hash: 4e29a0d95): Copyright (c) 2025 HiGHS under MIT licence terms
Set option log_file to "HiGHS.log"
Number of PL entries in BOUNDS section is 13571
Number of BV entries in BOUNDS section is 13300
MIP app1-2 has 53467 rows; 26871 cols; 199175 nonzeros; 13300 integer variables (13300 binary)
Coefficient ranges:
  Matrix  [1e-05, 1e+00]
  Cost    [1e+00, 1e+00]
  Bound   [1e+00, 1e+00]
  RHS     [1e-05, 1e+00]
WARNING: Problem has some excessively small row bounds
Presolving model
53461 rows, 26859 cols, 199133 nonzeros  0s
39849 rows, 13559 cols, 275633 nonzeros  0s
34782 rows, 11265 cols, 206079 nonzeros  18s
Presolve reductions: rows 34782(-18685); columns 11265(-15606); nonzeros 206079(+6904) 
Objective function is integral with scale 1

Solving MIP model with:
   34782 rows
   11265 cols (11000 binary, 0 integer, 0 implied int., 265 continuous, 0 domain fixed)
   206079 nonzeros

Src: B => Branching; C => Central rounding; F => Feasibility pump; H => Heuristic;
     I => Shifting; J => Feasibility jump; L => Sub-MIP; P => Empty MIP; R => Randomized rounding;
     S => Solve LP; T => Evaluate node; U => Unbounded; X => User solution; Y => HiGHS solution;
     Z => ZI Round; l => Trivial lower; p => Trivial point; u => Trivial upper; z => Trivial zero

        Nodes      |    B&B Tree     |            Objective Bounds              |  Dynamic Constraints |       Work      
Src  Proc. InQueue |  Leaves   Expl. | BestBound       BestSol              Gap |   Cuts   InLp Confl. | LpIters     Time

         0       0         0   0.00%   -238            inf                  inf        0      0      0         0    19.0s
         0       0         0   0.00%   -231.7553454    inf                  inf        0      0      2       817    20.2s
         0       0         0   0.00%   -215.3802548    inf                  inf    12422    709     99      3956    25.6s
         0       0         0   0.00%   -206.5307028    inf                  inf    11112    924    141      6686    30.7s
         0       0         0   0.00%   -198.3939772    inf                  inf    11604   1131    194      9142    36.0s
         0       0         0   0.00%   -186.7867751    inf                  inf    12614   1501    242     11571    41.1s
         0       0         0   0.00%   -182.4413041    inf                  inf    11793   1307    396     97428   233.9s

0.3% inactive integer columns, restarting
Model after restart has 34702 rows, 11228 cols (10963 bin., 0 int., 0 impl., 265 cont., 0 dom.fix.), and 205562 nonzeros

         0       0         0   0.00%   -182.4413041    inf                  inf     1307      0      0     97428   237.3s
         0       0         0   0.00%   -182.4368136    inf                  inf     1307   1152      4     99837   239.6s
         0       0         0   0.00%   -178.2681812    inf                  inf    13238   2059     69    101864   244.8s
         0       0         0   0.00%   -172.3725509    inf                  inf    19141   2120    129    105295   249.9s
         0       0         0   0.00%   -161.889637     inf                  inf    19369   2349    186    107863   254.9s
         0       0         0   0.00%   -156.2504424    inf                  inf    24243   2471    255    109553   260.2s
         0       0         0   0.00%   -156.2504424    inf                  inf    24257   2239    328    161721   368.1s
        31       5        13  99.77%   -156.2504424    inf                  inf    24015   2239    341    165970   373.3s
        48       6        21  99.82%   -156.2504424    inf                  inf    24015   2239    349    169966   378.6s
        69       7        31  99.82%   -156.2504424    inf                  inf    24018   2239    359    174356   384.2s
       100       0        46 100.00%   inf             inf                  inf     2326   2421    376    183365   395.2s
       100       0        46 100.00%   inf             inf                  inf     2326   2421    376    183365   395.2s

Solving report
  Model             app1-2
  Status            Infeasible
  Primal bound      inf
  Dual bound        inf
  Gap               inf
  P-D integral      0
  Solution status   -
  Timing            395.23
  Max sub-MIP depth 4
  Nodes             100
  Repair LPs        0
  LP iterations     183365
                    0 (strong br.)
                    25562 (separation)
                    136761 (heuristics)

Note the the presolved model is different than what is obtained with the latest code, but is consistent with the numbers shown by 1.12.0.

@Opt-Mucca
Copy link
Collaborator Author

I can't reproduce it with the latest version, but commit 4e29a0d with default options, finds app1-2 from miplib infeasible:
Note the the presolved model is different than what is obtained with the latest code, but is consistent with the numbers shown by 1.12.0.

@cmatraki christ you are on it. Did you test over the entire MIPLIB? If so can you share any observations of the change? (I haven't really focused on performance, only focusing on correctness, but am properly interested in any opinions and user insights)

For app1-2: I've got that instance written down and can reproduce the incorrect status on an older commit locally (before the v1.13.1 merge). It was causing me enough headaches that I've just decided to push ahead for now and hope it pops up on a smaller instance. If you're thinking of looking into it: There's an incorrect bound change from propagating a cut (cut is completely valid), which I suspect is coming from stale activities, although I've failed to prove this (history of issues with this and multiple cutpools, but I thought I'd finally managed to fix it).

@cmatraki
Copy link

cmatraki commented Mar 3, 2026

Did you test over the entire MIPLIB? If so can you share any observations of the change? (I haven't really focused on performance, only focusing on correctness, but am properly interested in any opinions and user insights)

Unfortunately no, I was just following along during development doing random tests for correctness. At some point everything started working but then I found this issue (and only this one) - just didn't know how to report it until now.

If you're thinking of looking into it

I'm not familiar enough with the code yet, I can only offer my own suspicion that the presolve changes since 1.12.0 mask the issue.

Copy link
Collaborator

@fwesselm fwesselm left a comment

Choose a reason for hiding this comment

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

Some more comments from me.

return std::make_pair(feasible, transformed_solobj);
}

bool HighsMipWorker::trySolution(const std::vector<double>& solution,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this a copy of HighsMipSolverData::trySolution?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes...... It is just calling a different addIncumbent at the end of feasibility check (which requires the HighsMipWorker object). I could pass HighsMipWorker* worker = nullptr as a default argument, and call the global HighsMipSolverData::addIncumbent vs HighsMipWorker::addIncumbent depending on whether or not it's a nullptr. Any strong opinion for or against (or a different approach)?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe another thing to look at after the initial submission.

return true;
}

std::pair<bool, double> HighsMipWorker::transformNewIntegerFeasibleSolution(
Copy link
Collaborator

Choose a reason for hiding this comment

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

This looks like a modified version of HighsMipSolverData::transformNewIntegerFeasibleSolution. I guess it is not possible to re-use the existing code? Should this live in HighsMipSolverData?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

After looking into this again, I guess it is now possible to reuse some of the existing code (I think I may have gotten lazy at this point). It would need:

  • Some nullptr default argument like in the above point
  • Some new statistics added to HighsMipWorker (repair LP iterations)
  • Some timer magic (delaying fixing all of this for now)

Would you want this to be unified into one function?

Copy link
Collaborator

Choose a reason for hiding this comment

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

We can make a note and look at it later.

HighsDomain& localdom = heur.getLocalDomain();
heur.setHeuristic(true);

std::vector<HighsInt> intcols_;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't understand this code segment (intcols_, intcols, this->intcols). What is it doing?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I have now done this a few times: I don't want to add boolean switches at all points in the code, i.e., wherever intcols is accessed, so I simply define it once as a reference, which involves creating the thing it potentially is referencing, e.g., intcols_.
There might be some obvious C++ mechanic I could be using here.

if (heur.getCurrentDepth() > targetdepth) {
if (!heur.backtrackUntilDepth(targetdepth)) {
lp_iterations += heur.getLocalLpIterations();
worker.heur_stats.lp_iterations += heur.getLocalLpIterations();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not for now, but maybe worth having a getHeurLpIterations method at some point.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That would return a reference to heur_stats.lp_iterations?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, I just saw similar updates in different places and wanted to ask.

void undo(const HighsOptions& options, HighsSolution& solution,
HighsBasis& basis, const HighsInt report_col = -1) {
reductionValues.resetPosition();
HighsBasis& basis, const HighsInt report_col = -1,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is this needed? Don't the workers have separate postsolve stacks?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The workers don't have separate postsolve stacks. The current undo function is changing some global data structures, so you can't call undo on two different threads simultaneously.
This change is adding an argument so it does everything on copied data.

mipsolver->mipdata_->implications.rebuild(model->num_col_, newColIndex,
newRowIndex);
mipsolver->mipdata_->cutpool =
// TODO: Find a sensible way to do this
Copy link
Collaborator

Choose a reason for hiding this comment

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

Are you deleting the cut pool?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yup! This was done before and I didn't see a need to change the workflow.

I had to get creative here because the std::atomic in HighsCutPool. I didn't want to change cutpool to a ptr in HighsMipData either (wanted to keep it as a reference). I'm happy with any ideas here as I think the current thing looks ugly (even if it works).

@jajhall
Copy link
Member

jajhall commented Mar 15, 2026

@Opt-Mucca I've merged latest into this branch to see whether all the CI tests pass

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants