Using an External Solver and Accessing the Solution

This page shows how to solve a model and access the solution. The reader should be familiar with the concept of optimizer and optimizer factories. If this is not the case, please refer to the page on Optimizers.

Modeling and Solving with an External Solver (e.g., GLPK)

Let us consider the following code.

using namespace idol;

const unsigned int n_items = 5;
const double[] profit = { 40., 50., 100., 95., 30., };
const double[] weight = { 2., 3.14, 1.98, 5., 3., };
const double capacity = 10.;

Env env;
Model model(env, Maximize);

auto x = model.add_vars(Dim<1>(n_items), 0., 1., Binary, 0, "x");
model.add(idol_Sum(j, Range(n_items), weight[j] * x[j] ) <= capacity);
model.set_obj_expr(idol_Sum(j, Range(n_items), profit[i] * x[i]);

This code creates a model for the knapsack problem.

As described in the page on this page, we will now set up an optimizer and solve the model. For this example, we use GLPK.

model.use(GLPK());

Solving the model is then done by calling the optimize method.

model.optimize();

Accessing the Solution

idol provides several methods to access the solution.

First, the status of the solution can be accessed using the get_status method. Here is a detailed list of possible statuses and reasons:

Status

Description

Loaded

The model has been loaded but not solved.

Optimal

An optimal solution has been found.

Feasible

A feasible solution has been found.

Infeasible

No feasible solution could be found.

InfOrUnbnd

The model is infeasible or unbounded.

Unbounded

The model is unbounded.

Fail

The solver failed to solve the model.

SubOptimal

A suboptimal solution has been found.

Related to a status, the reason for the status can be accessed using the get_reason method.

Reason

Description

NotSpecified

No specific reason is available.

Proved

The solver proved optimality.

TimeLimit

The solver reached the time limit.

IterLimit

The solver reached the iteration limit.

ObjLimit

The solver reached the objective limit.

Numerical

A numerical issue occurred during the solution process.

MemoryLimit

The solver ran out of memory.

Cycling

The solver encountered cycling (e.g., in simplex method).

SolutionLimit

The solver reached the solution limit (e.g., number of solutions).

Then, the following methods can be used to access the solution:

  • get_best_obj returns the best known objective value (this always refers to feasible solutions),

  • get_best_bound returns the best known objective value bound,

  • get_relative_gap returns the relative optimality gap,

  • get_absolute_gap returns the absolute optimality gap; see this page for more details about gaps and tolerances.

Accessing the primal and dual values can be done with the following methods:

  • get_var_primal returns the primal value of a given variable (Feasible and Optimal status only),

  • get_var_ray returns the primal ray value of a given variable (Unbounded status only),

  • get_ctr_dual returns the dual value of a given constraint (Continuous models only),

  • get_ctr_farkas returns the Farkas certificate value of a given constraint (Continuous models and Infeasible status only).

Saving a Solution

Sometimes, you will find it useful to save a solution to access it later.

idol provides the following functions to do so: save_primal, save_ray, save_dual and save_farkas. Each of these functions takes a model as argument and returns an object of the class Point with template parameter Var or Ctr depending on the function. The returned object stores the results of corresponding calls to get_var_primal, get_var_ray, get_ctr_dual or get_ctr_farkas methods.

Example

This example shows how to solve a model using HiGHS and retrieves some piece of information about its solution.

model.use(HiGHS());

model.optimize();

const auto status = model.get_status();

if (status == Optimal) {

    std::cout << "Optimal solution found!" << std::endl;

    const auto primal_values = save_primal(model);

    std::cout << primal_values << std::endl;

} else {

    std::cout << "An optimal solution could not be found." << std::endl;

    std::cout << "HiGHS returned status " << status << std::endl;

    std::cout << "The reason for this status is " << model.get_reason() << std::endl;

    if (status == Feasible) {

        std::cout << "The optimality gap is " << model.get_relative_gap() * 100 << " %" << std::endl;

    } else if (status == Unbounded) {

        std::cout << "An unbounded ray is" << std::endl;

        const auto primal_ray = save_ray(model);

        std::cout << primal_ray << std::endl;

    } else if (status == Infeasible) {

        std::cout << "A Farkas certificate is" << std::endl;

        const auto farkas = save_farkas(model);

        std::cout << farkas << std::endl;

    }

}

Saving a Projected Solution

In a more advanced solution scheme, you may deal with extended formulations of an original model, and may want to save the projected solution on the original problem space. In such a case, you can use the functions save_* with an additional argument to specify the original model. For instance.

const auto primal_values = save_primal(original_model, higher_dimensional_model);

This code will return an object of the class Point storing the results of corresponding calls to get_var_primal methods on the higher dimensional model for the original model variables.