Solutions
Solving and retrieving the results
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)
Why did the solver stop?
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.
Primal solutions
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.
Dual solutions
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.
Common status situations
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.
*
in the tables indicate that multiple different values are possible.
Primal-dual convex solver
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 | * | * |
Global branch-and-bound solvers
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 |
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.
Local search solvers
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 | * | * |
Querying solution attributes
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