Pyomo Solution Pools
The alternative-solutions library uses solution pools to filter and store solutions generated by an optimizer. The following types of solution pools are currently supported:
keep_all: This pool stores all solutions. No solutions are filtered out.keep_latest: This pool stores the latestmax_pool_sizesolutions that are added to the pool.max_pool_size(non-negative integer) : The maximum number of solutions that are stored.
keep_latest_unique: This pool stores the latestmax_pool_sizesolutions, ignoring duplicate solutions.max_pool_size(non-negative integer) : The maximum number of solutions that are stored.
keep_best: This pool stores the best solutions added to the pool.max_pool_size(non-negative integer) : The maximum number of solutions that are stored.objective(function) : A user-specified function that computes the objective value used for comparisons.abs_tolerance(non-negative float) : The absolute tolerance that is used to filter solutions.rel_tolernace(non-negative float) : The relative tolerance that is used to filter solutions.sense_is_min(bool) : If True, then the pool will keep solutions with the minimal objective values.best_value(float) : As solutions are added to this pool, it tracks the best solution value seen for tolerance comparisons. If specified, then this value provides an initial value for the best solution value.
A pool manager class is used to manage one-or-more solution pools. This
allows for flexible collection of solutions with different criteria. For
example, the the best solutions might be stored along with all
per-iteration solutions in an optimization solver. The solution
generation functions in the alternative-solutions library return
a PyomoPoolManager. By default, this pool manager uses
a solution pool that keeps the best solutions. However, the user can
provide a pool manager that is used to store solutions.
For example, we can explicitly create a pool manager that keeps the latest solutions. Consider the previous example, where all feasible solutions are generated:
>>> solns = aos.enumerate_binary_solutions(m, num_solutions=100, solver="glpk")
>>> print( [soln.objective().value for soln in solns] )
[70.0, 70.0, 60.0, 60.0, 50.0, 30.0, 20.0, 10.0, 0.0]
Each solution has a unique index:
>>> print( [soln.id for soln in solns] )
[0, 1, 2, 3, 4, 5, 6, 7, 8]
Now we create a PyomoPoolManager that is configured with a keep_latest pool:
>>> pool_manager = aos.PyomoPoolManager()
>>> context = pool_manager.add_pool(policy=aos.PoolPolicy.keep_latest, max_pool_size=3)
>>> solns = aos.enumerate_binary_solutions(m, num_solutions=100, solver="glpk", pool_manager=pool_manager)
>>> assert id(pool_manager) == id(solns)
>>> print( [soln.id for soln in solns] )
[6, 7, 8]
The default solution pool has policy keep_best with name None.
If a new Solution pool is added without a name, then the None
pool is replaced. Otherwise, if a solution pool is added with an
existing name an error occurs.
The pool manager always has an active pool. The pool manager has the same API as a solution pool, and the methods and data of the active pool are exposed to the user through the pool manager. The active pool defaults to the pool that was most recently added to the pool manager.
Solution Objects
Each Solution object contains information about the objective and
variables. Solutions can be sorted based on their variable values:
>>> solns = aos.enumerate_binary_solutions(m, num_solutions=100, solver="glpk", abs_opt_gap=0.0)
>>> print( [soln.objective().value for soln in solns] )
[70.0, 70.0]
>>> sorted_solns = list(sorted(solns))
>>> for soln in sorted_solns:
... print(soln)
{
"id": 1,
"objectives": [
{
"index": 0,
"name": "o",
"suffix": {},
"value": 70.0
}
],
"suffix": {},
"variables": [
{
"discrete": true,
"fixed": false,
"index": 0,
"name": "x[0]",
"suffix": {},
"value": 0
},
{
"discrete": true,
"fixed": false,
"index": 1,
"name": "x[1]",
"suffix": {},
"value": 1
},
{
"discrete": true,
"fixed": false,
"index": 2,
"name": "x[2]",
"suffix": {},
"value": 1
},
{
"discrete": true,
"fixed": false,
"index": 3,
"name": "x[3]",
"suffix": {},
"value": 0
}
]
}
{
"id": 0,
"objectives": [
{
"index": 0,
"name": "o",
"suffix": {},
"value": 70.0
}
],
"suffix": {},
"variables": [
{
"discrete": true,
"fixed": false,
"index": 0,
"name": "x[0]",
"suffix": {},
"value": 1
},
{
"discrete": true,
"fixed": false,
"index": 1,
"name": "x[1]",
"suffix": {},
"value": 0
},
{
"discrete": true,
"fixed": false,
"index": 2,
"name": "x[2]",
"suffix": {},
"value": 0
},
{
"discrete": true,
"fixed": false,
"index": 3,
"name": "x[3]",
"suffix": {},
"value": 1
}
]
}
Further, variable and objective values can be retrieved either using an integer index or the corresponding name:
>>> soln = sorted_solns[0]
>>> print(f"{soln.objective().value=} {soln.objective(0).value=} {soln.objective('o').value=}")
soln.objective().value=70.0 soln.objective(0).value=70.0 soln.objective('o').value=70.0
>>> print(f"{soln.variable(0).value=} {soln.variable('x[0]').value=}")
soln.variable(0).value=0 soln.variable('x[0]').value=0