Retrieve Solution pool with AMPL and Gurobi#
Description: This notebook describes how to retrieve multiple solutions from the solver’s solution pool. Optimization problems usually have several optimal solutions, one is returned by the solver but the others are discarded. These alternative solutions can also be retrieved by AMPL.
Tags: solution pool, gurobi, cplex, xpress, mip, mixed-integer-linear, mp
Notebook author: Marcos Dominguez Velad <marcos@ampl.com>, Gleb Belov <gleb@ampl.com>
Model author: Marcos Dominguez Velad <marcos@ampl.com>
# Install dependencies
%pip install -q amplpy
# Google Colab & Kaggle integration
from amplpy import AMPL, ampl_notebook
ampl = ampl_notebook(
modules=["gurobi", "xpress", "cplex", "copt"], # modules to install
license_uuid="default", # license to use
) # instantiate AMPL object and register magics
Solution pool#
More often than not, optimization problems have more than one optimal solution; moreover, during the solution process, MIP solvers usually find sub-optimal solutions, which are normally discarded. They can be however be kept, and in most cases there are solver-specific options to control how the search for additional solutions is performed.
The main (and generic) options that controls the search are sol:stub
and sol:count
, which control respectively the base-name for the files where additional solution will be stored and if to count additional solutions and return them in the nsol problem suffix. Specifying a stub name automatically enables the solutions count; found solutions are written to files [solutionstub1.sol’, … solutionstub
This is one of the features of the MP drivers, see other advanced modeling in:
https://mp.ampl.com/features-guide.html
To illustrate this behavior, a simple model with random weights and multiple solutions is proposed. After that, different solvers supporting Solution Pool are used, and the different solutions are retrieved by AMPL once the problem is solved.
Solvers that allow multiple solutions: gurobi, xpress, cplex, copt, scip.
Model#
%%writefile weights.mod
param n;
set I := 1..n;
var x{I} integer >= 0 <= 2;
param w{i in I} := 10 + floor(Uniform(0,500)) mod 200;
maximize profit:
sum{i in I} w[i] * x[i];
s.t. capacity:
sum{i in I} x[i] <= 133;
Overwriting weights.mod
Activate solution pool#
Each solution will go into a separated “.sol” file.
sol:stub
option controls the base name of the solution files (synonym for ams_stub, solstub, solutionstub).sol:poollimit
is the maximum of solutions that can be retrieved (synonym for ams_limit, poollimit, solnlimit).sol:poolgap
is the relative tolerance for reporting alternate MIP solutions (default: Infinity, no limit) (synonym for ams_eps, poolgap).sol:poolgapabs
is absolute tolerance for reporting alternate MIP solutions (default: Infinity, no limit) (synonym for ams_epsabs, poolgapabs).sol:count
: 0*/1: Whether to count the number of solutions and return it in the “.nsol” problem suffix. The number and kind of solutions are controlled by the sol:pool… parameters. Value 1 implied by sol:stub.sol:poolmode
search mode for MIP solutions when sol:stub/sol:count are specified to request finding several alternative solutions:0 - Just collect solutions during normal solve, and sort them best-first
1 - Make some effort at finding additional solutions
2 - Seek “poollimit” best solutions (default). ‘Best solutions’ are defined by the poolgap(abs) parameters.
There may be more options to customize the behavior of the solution pool, please, check the specific options of your solver to know more:
Gurobi options: https://dev.ampl.com/solvers/gurobi/options.html
Xpress options: https://dev.ampl.com/solvers/xpress/options.html
Cplex options: https://dev.ampl.com/solvers/cplex/options.html
from amplpy import AMPL
def run_problem(solver):
ampl = AMPL()
# read model
ampl.read("weights.mod")
ampl.param["n"] = 1200
# enable solution pool
ampl.option[
"mp_options"
] = f"sol:stub=savesol_{solver} sol:poollimit=5 sol:poolmode=2"
# call the solver
ampl.solve(solver=solver)
# retrieve solutions as a dictionary
solution_pool = []
for i in range(1, ampl.get_value("profit.npool") + 1):
ampl.eval(f"solution savesol_{solver}{i}.sol;")
sol = ampl.get_solution(flat=False, zeros=False)
solution_pool.append(sol)
return solution_pool
Run with Gurobi#
solver = "gurobi"
solutions = run_problem(solver=solver)
for i, sol in enumerate(solutions):
print(f"Solution {i}:")
print(sol["x"])
Gurobi 12.0.1: sol:stub = savesol_gurobi
sol:poollimit = 5
sol:poolmode = 2
Gurobi 12.0.1: optimal solution; objective 27061
60 simplex iterations
551 branching nodes
5 alternative solution(s)
with objective values 27061..27059
written to 'savesol_gurobi1.sol' ... 'savesol_gurobi5.sol'.
suffix nsol OUT;
suffix npool OUT;
Gurobi 12.0.1: Alternative solution 1, objective 27061
Gurobi 12.0.1: Alternative solution 2, objective 27061
Gurobi 12.0.1: Alternative solution 3, objective 27061
Gurobi 12.0.1: Alternative solution 4, objective 27059
Gurobi 12.0.1: Alternative solution 5, objective 27059
Solution 0:
{19: 2, 37: 2, 45: 2, 48: 2, 61: 2, 71: 2, 97: 2, 107: 2, 145: 2, 167: 2, 183: 2, 206: 2, 256: 2, 263: 2, 266: 2, 272: 2, 305: 2, 369: 2, 379: 2, 399: 2, 401: 2, 416: 2, 434: 2, 444: 2, 449: 2, 452: 2, 496: 2, 507: 2, 520: 2, 563: 2, 568: 2, 583: 2, 585: 2, 623: 2, 630: 2, 644: 2, 646: 2, 649: 2, 661: 2, 680: 2, 681: 2, 712: 2, 758: 2, 771: 2, 810: 2, 827: 2, 829: 2, 831: 2, 858: 2, 860: 2, 871: 2, 925: 2, 926: 2, 928: 2, 931: 2, 949: 2, 993: 1, 999: 2, 1020: 2, 1088: 2, 1092: 2, 1104: 2, 1105: 2, 1142: 2, 1165: 2, 1176: 2, 1197: 2}
Solution 1:
{19: 2, 37: 2, 45: 2, 48: 2, 61: 2, 71: 2, 97: 1, 107: 2, 145: 2, 167: 2, 183: 2, 206: 2, 256: 2, 263: 2, 266: 2, 272: 2, 305: 2, 369: 2, 379: 2, 399: 2, 401: 2, 416: 2, 434: 2, 444: 2, 449: 2, 452: 2, 496: 2, 507: 2, 520: 2, 563: 2, 568: 2, 583: 2, 585: 2, 623: 2, 630: 2, 644: 2, 646: 2, 649: 2, 661: 2, 680: 2, 681: 2, 712: 2, 758: 2, 771: 2, 810: 2, 827: 2, 829: 2, 831: 2, 858: 2, 860: 2, 871: 2, 925: 2, 926: 2, 928: 2, 931: 2, 949: 2, 993: 2, 999: 2, 1020: 2, 1088: 2, 1092: 2, 1104: 2, 1105: 2, 1142: 2, 1165: 2, 1176: 2, 1197: 2}
Solution 2:
{19: 2, 37: 2, 45: 2, 48: 2, 61: 2, 71: 2, 107: 2, 145: 2, 167: 2, 183: 2, 206: 2, 256: 2, 263: 2, 266: 2, 272: 2, 305: 2, 369: 2, 379: 2, 399: 2, 401: 2, 416: 2, 434: 2, 444: 2, 449: 2, 452: 2, 496: 2, 507: 2, 520: 2, 563: 2, 568: 2, 583: 2, 585: 2, 623: 2, 630: 2, 644: 2, 646: 2, 649: 2, 661: 2, 680: 2, 681: 2, 712: 2, 758: 2, 771: 2, 810: 2, 827: 2, 829: 2, 831: 2, 858: 2, 860: 2, 871: 2, 889: 1, 925: 2, 926: 2, 928: 2, 931: 2, 949: 2, 993: 2, 999: 2, 1020: 2, 1088: 2, 1092: 2, 1104: 2, 1105: 2, 1142: 2, 1165: 2, 1176: 2, 1197: 2}
Solution 3:
{19: 2, 37: 2, 45: 2, 48: 2, 61: 2, 71: 2, 107: 2, 145: 2, 167: 2, 183: 1, 206: 2, 256: 2, 263: 2, 266: 2, 272: 2, 305: 2, 369: 2, 379: 2, 399: 2, 401: 2, 416: 2, 434: 2, 444: 2, 449: 2, 452: 2, 496: 2, 507: 2, 520: 2, 563: 2, 568: 2, 583: 2, 585: 2, 623: 2, 630: 2, 644: 2, 646: 2, 649: 2, 661: 2, 680: 2, 681: 2, 712: 2, 758: 2, 771: 2, 810: 2, 827: 2, 829: 2, 831: 2, 858: 2, 860: 2, 871: 2, 889: 2, 925: 2, 926: 2, 928: 2, 931: 2, 949: 2, 993: 2, 999: 2, 1020: 2, 1088: 2, 1092: 2, 1104: 2, 1105: 2, 1142: 2, 1165: 2, 1176: 2, 1197: 2}
Solution 4:
{19: 2, 37: 2, 45: 2, 48: 2, 61: 2, 71: 2, 97: 2, 107: 2, 145: 2, 167: 2, 183: 1, 206: 2, 256: 2, 263: 2, 266: 2, 272: 2, 305: 2, 369: 2, 379: 2, 399: 2, 401: 2, 416: 2, 434: 2, 444: 2, 449: 2, 452: 2, 496: 2, 507: 2, 520: 2, 563: 2, 568: 2, 583: 2, 585: 2, 623: 2, 630: 2, 644: 2, 646: 2, 649: 2, 661: 2, 680: 2, 681: 2, 712: 2, 758: 2, 771: 2, 810: 2, 827: 2, 829: 2, 831: 2, 858: 2, 860: 2, 871: 2, 925: 2, 926: 2, 928: 2, 931: 2, 949: 2, 993: 2, 999: 2, 1020: 2, 1088: 2, 1092: 2, 1104: 2, 1105: 2, 1142: 2, 1165: 2, 1176: 2, 1197: 2}
Run with Cplex#
solver = "cplex"
solutions = run_problem(solver=solver)
for i, sol in enumerate(solutions):
print(f"Solution {i}:")
if "x" in sol: # cplex may have the trivial solution in the pool, that it's empty
print(sol["x"])
else:
print("Trivial solution")
CPLEX 22.1.1: sol:stub = savesol_cplex
sol:poollimit = 5
sol:poolmode = 2
CPLEX 22.1.1: optimal solution; objective 27061
1 simplex iterations
5 alternative solution(s)
with objective values 27061..0
written to 'savesol_cplex1.sol' ... 'savesol_cplex5.sol'.
suffix nsol OUT;
suffix npool OUT;
CPLEX 22.1.1: Alternative solution 1, objective 27061
CPLEX 22.1.1: Alternative solution 2, objective 26538
CPLEX 22.1.1: Alternative solution 3, objective 26512
CPLEX 22.1.1: Alternative solution 4, objective 0
CPLEX 22.1.1: Alternative solution 5, objective 26680
Solution 0:
{19: 2, 37: 2, 45: 2, 48: 2, 61: 2, 71: 2, 97: 2, 107: 2, 145: 2, 167: 2, 183: 2, 206: 2, 256: 2, 263: 2, 266: 2, 272: 2, 274: 1, 305: 2, 369: 2, 379: 2, 399: 2, 401: 2, 416: 2, 434: 2, 444: 2, 449: 2, 452: 2, 496: 2, 507: 2, 520: 2, 563: 2, 568: 2, 583: 2, 585: 2, 623: 2, 630: 2, 644: 2, 646: 2, 649: 2, 661: 2, 680: 2, 681: 2, 712: 2, 758: 2, 771: 2, 810: 2, 827: 2, 829: 2, 831: 2, 858: 2, 860: 2, 871: 2, 925: 2, 926: 2, 928: 2, 931: 2, 949: 2, 999: 2, 1020: 2, 1088: 2, 1092: 2, 1104: 2, 1105: 2, 1142: 2, 1165: 2, 1176: 2, 1197: 2}
Solution 1:
{1: 1, 2: 2, 3: 2, 19: 2, 37: 2, 45: 2, 48: 2, 61: 2, 71: 2, 107: 2, 145: 2, 167: 2, 183: 2, 206: 2, 256: 2, 263: 2, 266: 2, 272: 2, 305: 2, 369: 2, 379: 2, 399: 2, 401: 2, 416: 2, 434: 2, 444: 2, 449: 2, 452: 2, 507: 2, 520: 2, 563: 2, 568: 2, 583: 2, 585: 2, 623: 2, 630: 2, 644: 2, 646: 2, 649: 2, 661: 2, 680: 2, 681: 2, 712: 2, 758: 2, 771: 2, 810: 2, 827: 2, 829: 2, 831: 2, 858: 2, 860: 2, 871: 2, 925: 2, 926: 2, 928: 2, 931: 2, 949: 2, 999: 2, 1020: 2, 1088: 2, 1092: 2, 1104: 2, 1105: 2, 1142: 2, 1165: 2, 1176: 2, 1197: 2}
Solution 2:
{2: 2, 3: 2, 4: 1, 19: 2, 37: 2, 45: 2, 48: 2, 61: 2, 71: 2, 107: 2, 145: 2, 167: 2, 183: 2, 206: 2, 256: 2, 263: 2, 266: 2, 272: 2, 305: 2, 369: 2, 379: 2, 399: 2, 401: 2, 416: 2, 434: 2, 444: 2, 449: 2, 452: 2, 507: 2, 520: 2, 563: 2, 568: 2, 583: 2, 585: 2, 623: 2, 630: 2, 644: 2, 646: 2, 649: 2, 661: 2, 680: 2, 681: 2, 712: 2, 758: 2, 771: 2, 810: 2, 827: 2, 829: 2, 831: 2, 858: 2, 860: 2, 871: 2, 925: 2, 926: 2, 928: 2, 931: 2, 949: 2, 999: 2, 1020: 2, 1088: 2, 1092: 2, 1104: 2, 1105: 2, 1142: 2, 1165: 2, 1176: 2, 1197: 2}
Solution 3:
Trivial solution
Solution 4:
{1: 1, 2: 2, 7: 1, 19: 2, 37: 2, 45: 2, 48: 2, 61: 2, 71: 2, 107: 2, 145: 2, 167: 2, 183: 2, 206: 2, 256: 2, 263: 2, 266: 2, 272: 2, 305: 2, 369: 2, 379: 2, 399: 2, 401: 2, 416: 2, 434: 2, 444: 2, 449: 2, 452: 2, 496: 1, 507: 2, 520: 2, 563: 2, 568: 2, 583: 2, 585: 2, 623: 2, 630: 2, 644: 2, 646: 2, 649: 2, 661: 2, 680: 2, 681: 2, 712: 2, 758: 2, 771: 2, 810: 2, 827: 2, 829: 2, 831: 2, 858: 2, 860: 2, 871: 2, 925: 2, 926: 2, 928: 2, 931: 2, 949: 2, 999: 2, 1020: 2, 1088: 2, 1092: 2, 1104: 2, 1105: 2, 1142: 2, 1165: 2, 1176: 2, 1197: 2}
AMPL Website | AMPL Colab | Community Edition | Twitter | LinkedIn