Beginner's guide on how to effectively make your optimization solver a pro
IBM ILOG CPLEX is one of the most powerful commercial solvers in the world widely used in industry and academia. However, as we all know, MIP problems could be extremely hard to solve especially when the problem size goes wild or the problem falls into some specific structures like near-singular constraint space. In these cases, using particular solver options (often referred to as solver tuning) could yield surprisingly efficient solution processes. This page offers a fundamental guidance for beginners facing excessively long solution times in CPLEX. The tuning procedures are also applicable to other solvers, such as FICO-Xpress and Gurobi, as they typically share similar solver options (arguably Gurobi might have the best performance, but still case dependent).
To be clear at the beginning, the tuning procedure provided in this article is very subjective. All solvers in fact largely depend on many sophisticated heuristics to initialize the algorithm, preprocess the searching, and polish the solutions. Refer to this documentation from Gurobi (yes, they get a far better and user-friendlier doc!) for more details regarding how solver works towards solving MIP. What we are technically doing for the solver tuning is to modify the heuristic methods (kind of adding another self-designed heuristic onto the existing ones). So it should be kept in mind that one good solver tuning result is with probability zero applicable to all scenarios, even if the scenarios are alike. But it could hold substantial merits in sequential or parallel problem solving in large quantities for identical problem structure (like how GPU works for us).
It should be noted that the example case we are using here is a MILP problem with millions of constraints & variables for unit commitment (UC). Compared to other MIP applications, the UC problem has the following unique features:
Those unique features give us an initial idea on how we should tell the solver to customize its solution procedure.
We should always start with the default solver setting. After obtaining the optimization log, it is very possible to gain a lot better understanding on the problem when checking the log. When doing so, ask the following questions:
This might be the most common issue we would face when solving large-scale UC problems. Incumbent is the best feasible solution found so far that could satisfy all constraints in the model, serving as an lower bound (in maximization problem) when calculating the MIP gap. So when the solver takes minutes or even hours finding the next improved incumbent, it means that the model is extremely near-infeasible or the LP relaxation is too weak. Then, there are two potential ways to speed up the searching if you don’t want to change (simplify) your model.
cpx.parameters.emphasis.mip.set(1)
.cpx.parameters.mip.strategy.variableselect.set(3)
.cpx.parameters.preprocessing.scaling.set(1)
.Presolve is a significant procedure that the solver usually first enters in the solving stage, where it prunes the model with redundant constraints & variables and prepares refined branches and nodes for further inspection. However, the abundance of tight physical constraints and model symmetry in large-scale UC problems could commonly jeopardize the Presolve stage. When the Presolve stage takes a majority of the total solution time, try the following solver options:
cpx.parameters.mip.strategy.presolvenode.set(3)
In fact, when finding the solver gets stuck in Presolve, the best practice should be to sit back and inspect the model again, trying to prune unnecessary constraints and variables manually.
Somestimes the solver could have difficulty quickly improving the objective value or finding good-quality feasible solutions early in the process. Usually, Presolve could significantly improve the early-stage solution hunting, but for large-scale problems it could be way insufficent for solver to crack the nutshell at the beginning due to model symmetry, weak LP relaxation, and large constraint space. This issue could be detrimental to solution time even rendering the solver time-out. Fortunately, there are several effective solver options that could speed up.
cpx.parameters.mip.tolerances.mipgappolish.set(0.9)
cpx.parameters.mip.start.set(2)
cpx.parameters.mip.strategy.symmetry.set(1)
More often in solving large-scale MIP problems, the solver could spend hours reducing the gap from 5% to 1%, for example. This is because the solver does a bad job with fine-tuning the solution due to the diminishing returns of exploring the solution space as it approaches optimality. This issue is de facto one of the most common bottlenecks for large-scale MIP applications, not just UC, because of the natures of branch-and-cut. It is also arguably the most difficult problems in tuning the solver. One might want to try with the following options:
cpx.parameters.mip.tolerances.mipgappolish.set(0.1)
cpx.parameters.mip.strategy.nodeselect.set(0)
Sometimes the following options are elixirs that setting them correctly could work like magic, especially for ill-conditioned MIP problems.
cpx.parameters.emphasis.numerical.set(1)
cpx.parameters.simplex.tolerances.markowitz.set(0.0001)
There are still a huge bunch of solver options out there for solvers like CPLEX that this guide hasn’t touch. But I believe we have covered the most impactful ones. There are also some built-in tuning tools of these solvers, like CPLEX automatic tuning tool, Gurobi parameter tuning tool, and FICO-Xpress tuner. Personally speaking, I don’t usually find these tools useful, since they would yield very specific and non-generalizable solver options that works particularly for the one instance you test. They are also frustratingly empirical.
Hope these recommendations give you a good start on tuning the solver. Leave me a comment below if you have any questions! Have fun 👻