CurrentModule = MathOptInterface
DocTestSetup = quote
import MathOptInterface as MOI
end
DocTestFilters = [r"MathOptInterface|MOI"]
Once an optimizer is loaded with the objective function and all of the
constraints, we can ask the solver to solve the model by calling
optimize!
.
MOI.optimize!(optimizer)
The optimization procedure may stop for a number of reasons. The
TerminationStatus
attribute of the optimizer returns a
TerminationStatusCode
object which explains why the solver stopped.
The termination statuses distinguish between proofs of optimality, infeasibility, local convergence, limits, and termination because of something unexpected like invalid problem data or failure to converge.
A typical usage of the TerminationStatus
attribute is as follows:
status = MOI.get(optimizer, TerminationStatus())
if status == MOI.OPTIMAL
# Ok, we solved the problem!
else
# Handle other cases.
end
After checking the TerminationStatus
, check
ResultCount
. This attribute returns the number of results that the
solver has available to return. A result is defined as a primal-dual pair,
but either the primal or the dual may be missing from the result. While the
OPTIMAL
termination status normally implies that at least one result is
available, other statuses do not. For example, in the case of infeasibility, a
solver may return no result or a proof of infeasibility. The ResultCount
attribute distinguishes between these two cases.
Use the PrimalStatus
optimizer attribute to return a
ResultStatusCode
describing the status of the primal solution.
Common returns are described below in the Common status situations section.
Query the primal solution using the VariablePrimal
and
ConstraintPrimal
attributes.
Query the objective function value using the ObjectiveValue
attribute.
!!! warning See Duality for a discussion of the MOI conventions for primal-dual pairs and certificates.
Use the DualStatus
optimizer attribute to return a
ResultStatusCode
describing the status of the dual solution.
Query the dual solution using the ConstraintDual
attribute.
Query the dual objective function value using the DualObjectiveValue
attribute.
The sections below describe how to interpret typical or interesting status cases for three common classes of solvers. The example cases are illustrative, not comprehensive. Solver wrappers may provide additional information on how the solver's statuses map to MOI statuses.
!!! info
*
in the tables indicate that multiple different values are possible.
Linear programming and conic optimization solvers fall into this category.
What happened? | TerminationStatus |
ResultCount |
PrimalStatus |
DualStatus |
---|---|---|---|---|
Proved optimality | OPTIMAL |
1 | FEASIBLE_POINT |
FEASIBLE_POINT |
Proved infeasible | INFEASIBLE |
1 | NO_SOLUTION |
INFEASIBILITY_CERTIFICATE |
Optimal within relaxed tolerances | ALMOST_OPTIMAL |
1 | FEASIBLE_POINT |
FEASIBLE_POINT |
Optimal within relaxed tolerances | ALMOST_OPTIMAL |
1 | ALMOST_FEASIBLE_POINT |
ALMOST_FEASIBLE_POINT |
Detected an unbounded ray of the primal | DUAL_INFEASIBLE |
1 | INFEASIBILITY_CERTIFICATE |
NO_SOLUTION |
Stall | SLOW_PROGRESS |
1 | * | * |
Mixed-integer programming solvers fall into this category.
What happened? | TerminationStatus |
ResultCount |
PrimalStatus |
DualStatus |
---|---|---|---|---|
Proved optimality | OPTIMAL |
1 | FEASIBLE_POINT |
NO_SOLUTION |
Presolve detected infeasibility or unboundedness | INFEASIBLE_OR_UNBOUNDED |
0 | NO_SOLUTION |
NO_SOLUTION |
Proved infeasibility | INFEASIBLE |
0 | NO_SOLUTION |
NO_SOLUTION |
Timed out (no solution) | TIME_LIMIT |
0 | NO_SOLUTION |
NO_SOLUTION |
Timed out (with a solution) | TIME_LIMIT |
1 | FEASIBLE_POINT |
NO_SOLUTION |
CPXMIP_OPTIMAL_INFEAS |
ALMOST_OPTIMAL |
1 | INFEASIBLE_POINT |
NO_SOLUTION |
!!! info
CPXMIP_OPTIMAL_INFEAS
is a CPLEX status that indicates that a preprocessed problem was solved to
optimality, but the solver was unable to recover a feasible solution to the
original problem. Handling this status was one of the motivating drivers
behind the design of MOI.
Nonlinear programming solvers fall into this category. It also includes non-global tree search solvers like Juniper.
What happened? | TerminationStatus |
ResultCount |
PrimalStatus |
DualStatus |
---|---|---|---|---|
Converged to a stationary point | LOCALLY_SOLVED |
1 | FEASIBLE_POINT |
FEASIBLE_POINT |
Completed a non-global tree search (with a solution) | LOCALLY_SOLVED |
1 | FEASIBLE_POINT |
FEASIBLE_POINT |
Converged to an infeasible point | LOCALLY_INFEASIBLE |
1 | INFEASIBLE_POINT |
* |
Completed a non-global tree search (no solution found) | LOCALLY_INFEASIBLE |
0 | NO_SOLUTION |
NO_SOLUTION |
Iteration limit | ITERATION_LIMIT |
1 | * | * |
Diverging iterates | NORM_LIMIT or OBJECTIVE_LIMIT |
1 | * | * |
Some solvers will not implement every solution attribute. Therefore, a call like
MOI.get(model, MOI.SolveTimeSec())
may throw an UnsupportedAttribute
error.
If you need to write code that is agnostic to the solver (for example, you are
writing a library that an end-user passes their choice of solver to), you can
work-around this problem using a try-catch
:
function get_solve_time(model)
try
return MOI.get(model, MOI.SolveTimeSec())
catch err
if err isa MOI.UnsupportedAttribute
return NaN # Solver doesn't support. Return a placeholder value.
end
rethrow(err) # Something else went wrong. Rethrow the error
end
end
If, after careful profiling, you find that the try-catch
is taking a
significant portion of your runtime, you can improve performance by caching the
result of the try-catch
:
mutable struct CachedSolveTime{M}
model::M
supports_solve_time::Bool
CachedSolveTime(model::M) where {M} = new(model, true)
end
function get_solve_time(model::CachedSolveTime)
if !model.supports_solve_time
return NaN
end
try
return MOI.get(model, MOI.SolveTimeSec())
catch err
if err isa MOI.UnsupportedAttribute
model.supports_solve_time = false
return NaN
end
rethrow(err) # Something else went wrong. Rethrow the error
end
end