Skip to content

Commit 5293720

Browse files
authored
Merge pull request #3370 from viens-code/main
Alternative Solutions documentation add
2 parents 4e6ebbe + 43075cb commit 5293720

File tree

3 files changed

+142
-13
lines changed

3 files changed

+142
-13
lines changed

doc/OnlineDocs/explanation/analysis/alternative_solutions.rst

+132-11
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@ Optimization solvers are generally designed to return a feasible solution
66
to the user. However, there are many applications where a user needs
77
more context than this result. For example,
88

9-
* alternative solutions can support an assessment of trade-offs between competing objectives;
9+
* alternative solutions can support an assessment of trade-offs between
10+
competing objectives;
1011

11-
* if the optimization formulation may be inaccurate or untrustworthy, then comparisons amongst alternative solutions provide additional insights into the reliability of these model predictions; or
12+
* if the optimization formulation may be inaccurate or untrustworthy,
13+
then comparisons amongst alternative solutions provide additional
14+
insights into the reliability of these model predictions; or
1215

13-
* the user may have unexpressed objectives or constraints, which only are realized in later stages of model analysis.
16+
* the user may have unexpressed objectives or constraints, which only
17+
are realized in later stages of model analysis.
1418

1519
The *alternative-solutions library* provides a variety of functions that
1620
can be used to generate optimal or near-optimal solutions for a pyomo
@@ -31,21 +35,29 @@ The following functions are defined in the alternative-solutions library:
3135

3236
* ``enumerate_linear_solutions_soln_pool``
3337

34-
* Finds alternative optimal solutions for a (mixed-binary) linear program using Gurobi's solution pool feature.
38+
* Finds alternative optimal solutions for a (mixed-binary) linear
39+
program using Gurobi's solution pool feature.
3540

3641
* ``gurobi_generate_solutions``
3742

38-
* Finds alternative optimal solutions for discrete variables using Gurobi's built-in solution pool capability.
43+
* Finds alternative optimal solutions for discrete variables using
44+
Gurobi's built-in solution pool capability.
3945

4046
* ``obbt_analysis_bounds_and_solutions``
4147

42-
* Calculates the bounds on each variable by solving a series of min and max optimization problems where each variable is used as the objective function. This can be applied to any class of problem supported by the selected solver.
48+
* Calculates the bounds on each variable by solving a series of min
49+
and max optimization problems where each variable is used as the
50+
objective function. This can be applied to any class of problem
51+
supported by the selected solver.
4352

4453

45-
Usage Example
46-
-------------
54+
Basic Usage Example
55+
-------------------
4756

48-
Many of functions in the alternative-solutions library have similar options, so we simply illustrate the ``enumerate_binary_solutions`` function. We define a simple knapsack example whose alternative solutions have integer objective values ranging from 0 to 90.
57+
Many of the functions in the alternative-solutions library have similar
58+
options, so we simply illustrate the ``enumerate_binary_solutions``
59+
function. We define a simple knapsack example whose alternative
60+
solutions have integer objective values ranging from 0 to 90.
4961

5062
.. doctest::
5163

@@ -60,7 +72,9 @@ Many of functions in the alternative-solutions library have similar options, so
6072
>>> m.o = pyo.Objective(expr=sum(values[i] * m.x[i] for i in range(4)), sense=pyo.maximize)
6173
>>> m.c = pyo.Constraint(expr=sum(weights[i] * m.x[i] for i in range(4)) <= capacity)
6274

63-
We can execute the ``enumerate_binary_solutions`` function to generate a list of ``Solution`` objects that represent alternative optimal solutions:
75+
We can execute the ``enumerate_binary_solutions`` function to generate a
76+
list of ``Solution`` objects that represent alternative optimal
77+
solutions:
6478

6579
.. doctest::
6680
:skipif: not glpk_available
@@ -69,7 +83,9 @@ We can execute the ``enumerate_binary_solutions`` function to generate a list of
6983
>>> solns = aos.enumerate_binary_solutions(m, num_solutions=100, solver="glpk")
7084
>>> assert len(solns) == 10
7185

72-
Each ``Solution`` object contains information about the objective and variables, and it includes various methods to access this information. For example:
86+
Each ``Solution`` object contains information about the objective and
87+
variables, and it includes various methods to access this information.
88+
For example:
7389

7490
.. doctest::
7591
:skipif: not glpk_available
@@ -88,6 +104,111 @@ Each ``Solution`` object contains information about the objective and variables,
88104
}
89105

90106

107+
Gap Usage Example
108+
-----------------
109+
110+
When we only want some of the solutions based off a tolerance away from
111+
optimal, this can be done using the ``abs_opt_gap`` parameter. This is
112+
shown in the following simple knapsack examples where the weights and
113+
values are the same.
114+
115+
.. doctest::
116+
:skipif: not glpk_available
117+
118+
>>> import pyomo.environ as pyo
119+
>>> import pyomo.contrib.alternative_solutions as aos
120+
121+
>>> values = [10,9,2,1,1]
122+
>>> weights = [10,9,2,1,1]
123+
124+
>>> K = len(values)
125+
>>> capacity = 12
126+
127+
>>> m = pyo.ConcreteModel()
128+
>>> m.x = pyo.Var(range(K), within=pyo.Binary)
129+
>>> m.o = pyo.Objective(expr=sum(values[i] * m.x[i] for i in range(K)), sense=pyo.maximize)
130+
>>> m.c = pyo.Constraint(expr=sum(weights[i] * m.x[i] for i in range(K)) <= capacity)
131+
132+
>>> solns = aos.enumerate_binary_solutions(m, num_solutions=10, solver="glpk", abs_opt_gap = 0.0)
133+
>>> assert(len(solns) == 4)
134+
135+
In this example, we only get the four ``Solution`` objects that have an
136+
``objective_value`` of 12. Note that while we wanted only those four
137+
solutions with no optimality gap, using a gap of half the smallest value
138+
(in this case .5) will return the same solutions and avoids any machine
139+
precision issues.
140+
141+
.. doctest::
142+
:skipif: not glpk_available
143+
144+
>>> import pyomo.environ as pyo
145+
>>> import pyomo.contrib.alternative_solutions as aos
146+
147+
>>> values = [10,9,2,1,1]
148+
>>> weights = [10,9,2,1,1]
149+
150+
>>> K = len(values)
151+
>>> capacity = 12
152+
153+
>>> m = pyo.ConcreteModel()
154+
>>> m.x = pyo.Var(range(K), within=pyo.Binary)
155+
>>> m.o = pyo.Objective(expr=sum(values[i] * m.x[i] for i in range(K)), sense=pyo.maximize)
156+
>>> m.c = pyo.Constraint(expr=sum(weights[i] * m.x[i] for i in range(K)) <= capacity)
157+
158+
>>> solns = aos.enumerate_binary_solutions(m, num_solutions=10, solver="glpk", abs_opt_gap = 0.5)
159+
>>> assert(len(solns) == 4)
160+
>>> for soln in sorted(solns, key=lambda s: str(s.get_variable_name_values())):
161+
... print(soln)
162+
{
163+
"fixed_variables": [],
164+
"objective": "o",
165+
"objective_value": 12.0,
166+
"solution": {
167+
"x[0]": 0,
168+
"x[1]": 1,
169+
"x[2]": 1,
170+
"x[3]": 0,
171+
"x[4]": 1
172+
}
173+
}
174+
{
175+
"fixed_variables": [],
176+
"objective": "o",
177+
"objective_value": 12.0,
178+
"solution": {
179+
"x[0]": 0,
180+
"x[1]": 1,
181+
"x[2]": 1,
182+
"x[3]": 1,
183+
"x[4]": 0
184+
}
185+
}
186+
{
187+
"fixed_variables": [],
188+
"objective": "o",
189+
"objective_value": 12.0,
190+
"solution": {
191+
"x[0]": 1,
192+
"x[1]": 0,
193+
"x[2]": 0,
194+
"x[3]": 1,
195+
"x[4]": 1
196+
}
197+
}
198+
{
199+
"fixed_variables": [],
200+
"objective": "o",
201+
"objective_value": 12.0,
202+
"solution": {
203+
"x[0]": 1,
204+
"x[1]": 0,
205+
"x[2]": 1,
206+
"x[3]": 0,
207+
"x[4]": 0
208+
}
209+
}
210+
211+
91212
Interface Documentation
92213
-----------------------
93214

doc/OnlineDocs/reference/bibliography.rst

+6-2
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,13 @@ Bibliography
8585
Algebraic Discrete Methods*, 6(3), 466–486, 1985. DOI
8686
`10.1137/0606047 <https://doi.org/10.1137/0606047>`_
8787
88+
.. [BJ72] E. Balas and R. Jeroslow. "Canonical Cuts on the Unit Hypercube",
89+
*SIAM Journal on Applied Mathematics* 23(1), 61-19, 1972.
90+
DOI `10.1137/0123007 <https://doi.org/10.1137/0123007>`_
91+
8892
.. [FGK02] R. Fourer, D. M. Gay, and B. W. Kernighan. *AMPL: A Modeling
89-
Language for Mathematical Programming*, 2nd Edition. Duxbury
90-
Press. 2002.
93+
Language for Mathematical Programming*, 2nd Edition, Duxbury
94+
Press, 2002.
9195
9296
.. [GAMS] http://www.gams.com
9397

pyomo/contrib/alternative_solutions/balas.py

+4
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ def enumerate_binary_solutions(
3636
Finds alternative optimal solutions for a binary problem using no-good
3737
cuts.
3838
39+
This function implements a no-good cuts technique inheriting from
40+
Balas's work on Canonical Cuts [BJ72]_.
41+
3942
Parameters
4043
----------
4144
model : ConcreteModel
@@ -74,6 +77,7 @@ def enumerate_binary_solutions(
7477
solutions
7578
A list of Solution objects.
7679
[Solution]
80+
7781
"""
7882
logger.info("STARTING NO-GOOD CUT ANALYSIS")
7983

0 commit comments

Comments
 (0)