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

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.

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.

Info

* 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?TerminationStatusResultCountPrimalStatusDualStatus
Proved optimalityOPTIMAL1FEASIBLE_POINTFEASIBLE_POINT
Proved infeasibleINFEASIBLE1NO_SOLUTIONINFEASIBILITY_CERTIFICATE
Optimal within relaxed tolerancesALMOST_OPTIMAL1FEASIBLE_POINTFEASIBLE_POINT
Optimal within relaxed tolerancesALMOST_OPTIMAL1ALMOST_FEASIBLE_POINTALMOST_FEASIBLE_POINT
Detected an unbounded ray of the primalDUAL_INFEASIBLE1INFEASIBILITY_CERTIFICATENO_SOLUTION
StallSLOW_PROGRESS1**

Global branch-and-bound solvers

Mixed-integer programming solvers fall into this category.

What happened?TerminationStatusResultCountPrimalStatusDualStatus
Proved optimalityOPTIMAL1FEASIBLE_POINTNO_SOLUTION
Presolve detected infeasibility or unboundednessINFEASIBLE_OR_UNBOUNDED0NO_SOLUTIONNO_SOLUTION
Proved infeasibilityINFEASIBLE0NO_SOLUTIONNO_SOLUTION
Timed out (no solution)TIME_LIMIT0NO_SOLUTIONNO_SOLUTION
Timed out (with a solution)TIME_LIMIT1FEASIBLE_POINTNO_SOLUTION
CPXMIP_OPTIMAL_INFEASALMOST_OPTIMAL1INFEASIBLE_POINTNO_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.

Local search solvers

Nonlinear programming solvers fall into this category. It also includes non-global tree search solvers like Juniper.

What happened?TerminationStatusResultCountPrimalStatusDualStatus
Converged to a stationary pointLOCALLY_SOLVED1FEASIBLE_POINTFEASIBLE_POINT
Completed a non-global tree search (with a solution)LOCALLY_SOLVED1FEASIBLE_POINTFEASIBLE_POINT
Converged to an infeasible pointLOCALLY_INFEASIBLE1INFEASIBLE_POINT*
Completed a non-global tree search (no solution found)LOCALLY_INFEASIBLE0NO_SOLUTIONNO_SOLUTION
Iteration limitITERATION_LIMIT1**
Diverging iteratesNORM_LIMIT or OBJECTIVE_LIMIT1**

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