# 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
```