Skip to content

Commit 1f5e1e7

Browse files
authored
Merge branch 'main' into relational-multiple-dispatch
2 parents ac5d88d + cfc0850 commit 1f5e1e7

File tree

1 file changed

+86
-22
lines changed

1 file changed

+86
-22
lines changed

doc/OnlineDocs/explanation/solvers/gdpopt.rst

+86-22
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ The main advantage of these techniques is their ability to solve subproblems
1212
in a reduced space, including nonlinear constraints only for ``True`` logical blocks.
1313
As a result, GDPopt is most effective for nonlinear GDP models.
1414

15-
Three algorithms are available in GDPopt:
15+
Four algorithms are available in GDPopt:
1616

1717
1. Logic-based outer approximation (LOA) [`Turkay & Grossmann, 1996`_]
1818
2. Global logic-based outer approximation (GLOA) [`Lee & Grossmann, 2001`_]
1919
3. Logic-based branch-and-bound (LBB) [`Lee & Grossmann, 2001`_]
20+
4. Logic-based discrete steepest descent algorithm (LD-SDA) [`Ovalle et al., 2025`_]
2021

2122
Usage and implementation details for GDPopt can be found in the PSE 2018 paper
2223
(`Chen et al., 2018`_), or via its
@@ -28,6 +29,7 @@ Credit for prototyping and development can be found in the ``GDPopt`` class docu
2829
.. _Lee & Grossmann, 2001: https://doi.org/10.1016/S0098-1354(01)00732-3
2930
.. _Lee & Grossmann, 2000: https://doi.org/10.1016/S0098-1354(00)00581-0
3031
.. _Chen et al., 2018: https://doi.org/10.1016/B978-0-444-64241-7.50143-9
32+
.. _Ovalle et al., 2025: https://doi.org/10.1016/j.compchemeng.2024.108993
3133

3234
GDPopt can be used to solve a Pyomo.GDP concrete model in two ways.
3335
The simplest is to instantiate the generic GDPopt solver and specify the desired algorithm as an argument to the ``solve`` method:
@@ -63,27 +65,27 @@ An example that includes the modeling approach may be found below.
6365
:skipif: not glpk_available
6466

6567
Required imports
66-
>>> from pyomo.environ import *
67-
>>> from pyomo.gdp import *
68+
>>> import pyomo.environ as pyo
69+
>>> from pyomo.gdp import Disjunct, Disjunction
6870

6971
Create a simple model
70-
>>> model = ConcreteModel(name='LOA example')
72+
>>> model = pyo.ConcreteModel(name='LOA example')
7173

72-
>>> model.x = Var(bounds=(-1.2, 2))
73-
>>> model.y = Var(bounds=(-10,10))
74-
>>> model.c = Constraint(expr= model.x + model.y == 1)
74+
>>> model.x = pyo.Var(bounds=(-1.2, 2))
75+
>>> model.y = pyo.Var(bounds=(-10,10))
76+
>>> model.c = pyo.Constraint(expr= model.x + model.y == 1)
7577

7678
>>> model.fix_x = Disjunct()
77-
>>> model.fix_x.c = Constraint(expr=model.x == 0)
79+
>>> model.fix_x.c = pyo.Constraint(expr=model.x == 0)
7880

7981
>>> model.fix_y = Disjunct()
80-
>>> model.fix_y.c = Constraint(expr=model.y == 0)
82+
>>> model.fix_y.c = pyo.Constraint(expr=model.y == 0)
8183

8284
>>> model.d = Disjunction(expr=[model.fix_x, model.fix_y])
83-
>>> model.objective = Objective(expr=model.x + 0.1*model.y, sense=minimize)
85+
>>> model.objective = pyo.Objective(expr=model.x + 0.1*model.y, sense=pyo.minimize)
8486

8587
Solve the model using GDPopt
86-
>>> results = SolverFactory('gdpopt.loa').solve(
88+
>>> results = pyo.SolverFactory('gdpopt.loa').solve(
8789
... model, mip_solver='glpk') # doctest: +IGNORE_RESULT
8890

8991
Display the final solution
@@ -158,25 +160,25 @@ To use the GDPopt-LBB solver, define your Pyomo GDP model as usual:
158160
:skipif: not baron_available
159161

160162
Required imports
161-
>>> from pyomo.environ import *
163+
>>> import pyomo.environ as pyo
162164
>>> from pyomo.gdp import Disjunct, Disjunction
163165

164166
Create a simple model
165-
>>> m = ConcreteModel()
166-
>>> m.x1 = Var(bounds = (0,8))
167-
>>> m.x2 = Var(bounds = (0,8))
168-
>>> m.obj = Objective(expr=m.x1 + m.x2, sense=minimize)
167+
>>> m = pyo.ConcreteModel()
168+
>>> m.x1 = pyo.Var(bounds = (0,8))
169+
>>> m.x2 = pyo.Var(bounds = (0,8))
170+
>>> m.obj = pyo.Objective(expr=m.x1 + m.x2, sense=pyo.minimize)
169171
>>> m.y1 = Disjunct()
170172
>>> m.y2 = Disjunct()
171-
>>> m.y1.c1 = Constraint(expr=m.x1 >= 2)
172-
>>> m.y1.c2 = Constraint(expr=m.x2 >= 2)
173-
>>> m.y2.c1 = Constraint(expr=m.x1 >= 3)
174-
>>> m.y2.c2 = Constraint(expr=m.x2 >= 3)
173+
>>> m.y1.c1 = pyo.Constraint(expr=m.x1 >= 2)
174+
>>> m.y1.c2 = pyo.Constraint(expr=m.x2 >= 2)
175+
>>> m.y2.c1 = pyo.Constraint(expr=m.x1 >= 3)
176+
>>> m.y2.c2 = pyo.Constraint(expr=m.x2 >= 3)
175177
>>> m.djn = Disjunction(expr=[m.y1, m.y2])
176178

177179
Invoke the GDPopt-LBB solver
178180

179-
>>> results = SolverFactory('gdpopt.lbb').solve(m)
181+
>>> results = pyo.SolverFactory('gdpopt.lbb').solve(m)
180182
WARNING: 09/06/22: The GDPopt LBB algorithm currently has known issues. Please
181183
use the results with caution and report any bugs!
182184

@@ -186,9 +188,70 @@ To use the GDPopt-LBB solver, define your Pyomo GDP model as usual:
186188
>>> print(results.solver.termination_condition)
187189
optimal
188190

189-
>>> print([value(m.y1.indicator_var), value(m.y2.indicator_var)])
191+
>>> print([pyo.value(m.y1.indicator_var), pyo.value(m.y2.indicator_var)])
190192
[True, False]
191193

194+
Logic-based Discrete-Steepest Descent Algorithm (LD-SDA)
195+
--------------------------------------------------------
196+
197+
The GDPopt-LDSDA solver exploits the ordered Boolean variables in the disjunctions to solve the GDP model.
198+
It requires an **exclusive OR (XOR) logical constraint** to ensure that exactly one disjunct is active in each disjunction.
199+
The solver also requires a **starting point** for the discrete variables and allows users to choose between two **direction norms**, `'L2'` and `'Linf'`, to guide the search process.
200+
201+
.. note::
202+
203+
The current implementation of the GDPopt-LDSDA requires an explicit LogicalConstraint to enforce the exclusive OR condition for each disjunction.
204+
205+
To use the GDPopt-LDSDA solver, define your Pyomo GDP model as usual:
206+
207+
.. doctest::
208+
:skipif: not baron_available
209+
210+
Required imports
211+
>>> import pyomo.environ as pyo
212+
>>> from pyomo.gdp import Disjunct, Disjunction
213+
214+
Create a simple model
215+
>>> m = pyo.ConcreteModel()
216+
217+
Define sets
218+
>>> I = [1, 2, 3, 4, 5]
219+
>>> J = [1, 2, 3, 4, 5]
220+
221+
Define variables
222+
>>> m.a = pyo.Var(bounds=(-0.3, 0.2))
223+
>>> m.b = pyo.Var(bounds=(-0.9, -0.5))
224+
225+
Define disjuncts for Y1
226+
>>> m.Y1_disjuncts = Disjunct(I)
227+
>>> for i in I:
228+
... m.Y1_disjuncts[i].y1_constraint = pyo.Constraint(expr=m.a == -0.3 + 0.1 * (i - 1))
229+
230+
Define disjuncts for Y2
231+
>>> m.Y2_disjuncts = Disjunct(J)
232+
>>> for j in J:
233+
... m.Y2_disjuncts[j].y2_constraint = pyo.Constraint(expr=m.b == -0.9 + 0.1 * (j - 1))
234+
235+
Define disjunctions
236+
>>> m.y1_disjunction = Disjunction(expr=[m.Y1_disjuncts[i] for i in I])
237+
>>> m.y2_disjunction = Disjunction(expr=[m.Y2_disjuncts[j] for j in J])
238+
239+
Logical constraints to enforce exactly one selection
240+
>>> m.Y1_limit = pyo.LogicalConstraint(expr=pyo.exactly(1, [m.Y1_disjuncts[i].indicator_var for i in I]))
241+
>>> m.Y2_limit = pyo.LogicalConstraint(expr=pyo.exactly(1, [m.Y2_disjuncts[j].indicator_var for j in J]))
242+
243+
Define objective function
244+
>>> m.obj = pyo.Objective(
245+
... expr=4 * m.a**2 - 2.1 * m.a**4 + (1 / 3) * m.a**6 + m.a * m.b - 4 * m.b**2 + 4 * m.b**4,
246+
... sense=pyo.minimize
247+
... )
248+
249+
Invoke the GDPopt-LDSDA solver
250+
>>> results = pyo.SolverFactory('gdpopt.ldsda').solve(m,
251+
... starting_point=[1,1],
252+
... logical_constraint_list=[m.Y1_limit, m.Y2_limit],
253+
... direction_norm='Linf',
254+
... )
192255
GDPopt implementation and optional arguments
193256
--------------------------------------------
194257

@@ -204,4 +267,5 @@ GDPopt implementation and optional arguments
204267
~pyomo.contrib.gdpopt.gloa.GDP_GLOA_Solver
205268
~pyomo.contrib.gdpopt.ric.GDP_RIC_Solver
206269
~pyomo.contrib.gdpopt.branch_and_bound.GDP_LBB_Solver
270+
~pyomo.contrib.gdpopt.ldsda.GDP_LDSDA_Solver
207271

0 commit comments

Comments
 (0)