@@ -12,11 +12,12 @@ The main advantage of these techniques is their ability to solve subproblems
12
12
in a reduced space, including nonlinear constraints only for ``True `` logical blocks.
13
13
As a result, GDPopt is most effective for nonlinear GDP models.
14
14
15
- Three algorithms are available in GDPopt:
15
+ Four algorithms are available in GDPopt:
16
16
17
17
1. Logic-based outer approximation (LOA) [`Turkay & Grossmann, 1996 `_]
18
18
2. Global logic-based outer approximation (GLOA) [`Lee & Grossmann, 2001 `_]
19
19
3. Logic-based branch-and-bound (LBB) [`Lee & Grossmann, 2001 `_]
20
+ 4. Logic-based discrete steepest descent algorithm (LD-SDA) [`Ovalle et al., 2025 `_]
20
21
21
22
Usage and implementation details for GDPopt can be found in the PSE 2018 paper
22
23
(`Chen et al., 2018 `_), or via its
@@ -28,6 +29,7 @@ Credit for prototyping and development can be found in the ``GDPopt`` class docu
28
29
.. _Lee & Grossmann, 2001 : https://doi.org/10.1016/S0098-1354(01)00732-3
29
30
.. _Lee & Grossmann, 2000 : https://doi.org/10.1016/S0098-1354(00)00581-0
30
31
.. _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
31
33
32
34
GDPopt can be used to solve a Pyomo.GDP concrete model in two ways.
33
35
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.
63
65
:skipif: not glpk_available
64
66
65
67
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
68
70
69
71
Create a simple model
70
- >>> model = ConcreteModel(name = ' LOA example' )
72
+ >>> model = pyo. ConcreteModel(name = ' LOA example' )
71
73
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 )
75
77
76
78
>>> 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 )
78
80
79
81
>>> 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 )
81
83
82
84
>>> 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)
84
86
85
87
Solve the model using GDPopt
86
- >>> results = SolverFactory(' gdpopt.loa' ).solve(
88
+ >>> results = pyo. SolverFactory(' gdpopt.loa' ).solve(
87
89
... model, mip_solver= ' glpk' ) # doctest: +IGNORE_RESULT
88
90
89
91
Display the final solution
@@ -158,25 +160,25 @@ To use the GDPopt-LBB solver, define your Pyomo GDP model as usual:
158
160
:skipif: not baron_available
159
161
160
162
Required imports
161
- >>> from pyomo.environ import *
163
+ >>> import pyomo.environ as pyo
162
164
>>> from pyomo.gdp import Disjunct, Disjunction
163
165
164
166
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)
169
171
>>> m.y1 = Disjunct()
170
172
>>> 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 )
175
177
>>> m.djn = Disjunction(expr = [m.y1, m.y2])
176
178
177
179
Invoke the GDPopt-LBB solver
178
180
179
- >>> results = SolverFactory(' gdpopt.lbb' ).solve(m)
181
+ >>> results = pyo. SolverFactory(' gdpopt.lbb' ).solve(m)
180
182
WARNING: 09/06/22: The GDPopt LBB algorithm currently has known issues. Please
181
183
use the results with caution and report any bugs!
182
184
@@ -186,9 +188,70 @@ To use the GDPopt-LBB solver, define your Pyomo GDP model as usual:
186
188
>>> print (results.solver.termination_condition)
187
189
optimal
188
190
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)])
190
192
[True, False]
191
193
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
+ ... )
192
255
GDPopt implementation and optional arguments
193
256
--------------------------------------------
194
257
@@ -204,4 +267,5 @@ GDPopt implementation and optional arguments
204
267
~pyomo.contrib.gdpopt.gloa.GDP_GLOA_Solver
205
268
~pyomo.contrib.gdpopt.ric.GDP_RIC_Solver
206
269
~pyomo.contrib.gdpopt.branch_and_bound.GDP_LBB_Solver
270
+ ~pyomo.contrib.gdpopt.ldsda.GDP_LDSDA_Solver
207
271
0 commit comments