Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Logical Modeling and Generalized Disjunctive Programs

Prepared by: Prof. Alexander Dowling (adowling@nd.edu), Hailey Lynch (hlynch@nd.edu, 2023)

Introduction and Learning Objectives

Translate logical statements into binary constraints, model continuous alternatives with Pyomo.GDP, and compare Big-M and hull reformulations. The separator and parts examples develop the logic; the reactor example shows how bounds affect the reformulation. Full derivations and reproducible diagnostics supplement the lecture activities.

Import Modules

# Imports
import sys

if "google.colab" in sys.modules:
    !wget "https://raw.githubusercontent.com/ndcbe/optimization/main/notebooks/helper.py"
    import helper

    helper.easy_install()
else:
    sys.path.insert(0, "../")
    import helper
helper.set_plotting_style()

milp_solver = "appsi_highs"

Motivating Example: Separation

The following excerpts are from Section 15.7 in Biegler, Grossmann, and Westerberg (1997).

Pairwise and aggregate separator relaxations in the unit cube and their common slice.

Black circles mark the common binary vertices; squares mark the two extra aggregate vertices. The slice exposes the printed fractional witness.

If the absorber is selected or the membrane is selected, do not use cryogenic separation. Let yA,yM,yCS∈{0,1}y_A,y_M,y_{CS}\in\{0,1\} indicate these selections. The OR is inclusive: selecting both absorber and membrane is permitted.

Translate each way the antecedent can be true first:

yA+yCS≤1,yM+yCS≤1.y_A+y_{CS}\le1,\qquad y_M+y_{CS}\le1.

Adding the two rows gives the aggregate:

yA+yM+2yCS≤2.y_A+y_M+2y_{CS}\le2.

The pair implies the aggregate even for fractional variables. Conversely, for binary variables the aggregate enforces both rows: if yCS=1y_{CS}=1, it forces yA=yM=0y_A=y_M=0; if yCS=0y_{CS}=0, it imposes no further restriction. Thus both formulations describe the same binary choices.

They differ on the unit cube [0,1]3[0,1]^3. At (yA,yM,yCS)=(1,0,1/2)(y_A,y_M,y_{CS})=(1,0,1/2), the aggregate holds with equality, but the first pairwise row reads 3/2≤13/2\le1 and fails. The pair therefore has a strictly tighter LP relaxation. The all-zero choice remains feasible: the implication does not require installing a separator.

Source: Biegler, Grossmann, and Westerberg (1997), §15.7, pp. 514–517, Example 15.4.

Logical Modeling

An atomic statement can represent a selection, such as “use the absorber.” Logical connectives combine these statements. Conjunctive normal form separates the resulting formula into disjunctive clauses, each of which becomes one linear inequality.

Symbolic Logic Notation

Logical OperationLogical Symbol
OR\text{OR}∨\lor
AND\text{AND}∧\land
IMPLICATION\text{IMPLICATION}⇒\Rightarrow
NEGATION\text{NEGATION}¬\neg
EQUIVALENCE\text{EQUIVALENCE}⇔\Leftrightarrow
EXCLUSIVE OR\text{EXCLUSIVE OR}⊻\veebar

Vocabulary

An atom PiP_i has no logical connective inside it. Associate the binary variable yi=1y_i=1 with a true atom and yi=0y_i=0 with a false atom. A literal is an atom PiP_i or its negation ¬Pi\neg P_i; their numerical truth values are yiy_i and 1−yi1-y_i.

A disjunctive clause is an OR of finitely many literals. It is true when at least one literal is true. A conjunctive clause is an AND of literals; all must be true. Conjunctive normal form (CNF) is an AND of disjunctive clauses, not an arbitrary sequence of clauses.

For example,

Q=(P1∨¬P2∨P3)∧(¬P1∨P4)Q=(P_1\lor\neg P_2\lor P_3)\land(\neg P_1\lor P_4)

is CNF and translates to

y1+(1−y2)+y3≥1,(1−y1)+y4≥1.y_1+(1-y_2)+y_3\ge1,\qquad(1-y_1)+y_4\ge1.

More generally, if a clause has positive-literal indices J+J^+ and negative-literal indices J−J^-, its row is

∑j∈J+yj+∑j∈J−(1−yj)≥1.\sum_{j\in J^+}y_j+\sum_{j\in J^-}(1-y_j)\ge1.

Each literal is 0 or 1, so this sum is at least one exactly when the clause is true. Retain binary domains: this equivalence concerns truth assignments, not fractional truth values.

Atoms become literals, disjunctive clauses, and one constraint per clause in CNF.

Logical Statements and Conjunctive Normal Form

The following excerpts are from Section 15.7 in Biegler, Grossmann, and Westerberg (1997).

The three step procedure below shows how to convert logical statements into conjunctive normal form:

Step 1: Replace the implication by its equivalent disjunction. Example:

P1⇒P2⇔¬P1∨P2(15.18)P_{1} \Rightarrow P_{2} \Leftrightarrow \neg P_{1} \lor P_{2} \tag{15.18}
Click to further explore this example

Let’s enumerate to better understand this example. We will look at all possible outcomes for P1P_1 and P2P_2 and then assess if the left and right statements are true or false.

P1P_1P2P_2P1⇒P2P_{1} \Rightarrow P_{2}¬P1∨P2\neg P_{1} \lor P_{2}
truetruetruetrue
truefalsefalsefalse
falsetruetruetrue
falsefalsetruetrue
The two De Morgan evaluation paths exchange AND and OR when negating.

Step 2: Distribute the negation by applying DeMorgan’s Theorem. Examples:

¬(P1∧P2)⇔¬P1∨¬P2(15.19)\neg (P_{1} \land P_{2}) \Leftrightarrow \neg P_{1} \lor \neg P_{2}\tag{15.19}
Click to further explore this example

Let’s enumerate to better understand this example

P1P_1P2P_2¬(P1∧P2)\neg (P_{1} \land P_{2})¬P1∨¬P2\neg P_{1} \lor \neg P_{2}
truetruefalsefalse
truefalsetruetrue
falsetruetruetrue
falsefalsetruetrue

The example holds.

¬(P1∨P2)⇔¬P1∧¬P2(15.20)\neg (P_{1} \lor P_{2}) \Leftrightarrow \neg P_{1} \land \neg P_{2}\tag{15.20}
Click to further explore this example

Let’s enumerate to better understand this example

P1P_1P2P_2¬(P1∨P2)\neg (P_{1} \lor P_{2})¬P1∧¬P2\neg P_{1} \land \neg P_{2}
truetruefalsefalse
truefalsefalsefalse
falsetruefalsefalse
falsefalsetruetrue

The example holds.

Distributing P3 over the AND produces two clauses that must both hold.

Step 3: Recursively distribute the OR\textbf{OR} over the AND\textbf{AND} by using the following equivalence. Example:

(P1∧P2)∨P3⇔(P1∨P3)∧(P2∨P3)(15.21)(P_{1} \land P_{2}) \lor P_{3} \Leftrightarrow (P_{1} \lor P_{3}) \land (P_{2} \lor P_{3})\tag{15.21}
Click to further explore this example

Let’s enumerate to better understand this example

P1P_1P2P_2P3P_3(P1∧P2)∨P3(P_{1} \land P_{2}) \lor P_{3}(P1∨P3)∧(P2∨P3)(P_{1} \lor P_{3}) \land (P_{2} \lor P_{3})
truetruetruetruetrue
truetruefalsetruetrue
truefalsetruetruetrue
truefalsefalsefalsefalse
falsetruetruetruetrue
falsetruefalsefalsefalse
falsefalsetruetruetrue
falsefalsefalsefalsefalse

Again, the example holds.

Permitted binary assignments for implication, equivalence, and two-input XOR.

Once in conjunctive normal form, translate each clause separately. The following dictionary also handles common direct relations.

RelationBoolean statementBinary representation
Inclusive ORP1∨⋯∨PrP_1\lor\cdots\lor P_r∑iyi≥1\sum_i y_i\ge1
ANDP1∧⋯∧PrP_1\land\cdots\land P_ryi=1y_i=1 for every ii
ImplicationP1⇒P2P_1\Rightarrow P_2y1≤y2y_1\le y_2
EquivalenceP1⇔P2P_1\Leftrightarrow P_2y1=y2y_1=y_2
Two-input exclusive ORP1⊻P2P_1\veebar P_2y1+y2=1y_1+y_2=1
Exactly one of rr statementsexactly one PiP_i is true∑iyi=1\sum_i y_i=1
At most oneat most one PiP_i is true∑iyi≤1\sum_i y_i\le1

A chain of ordinary two-input XORs is a parity test: for three true inputs, (P1⊻P2)⊻P3(P_1\veebar P_2)\veebar P_3 is true. It is not the exactly-one relation for three or more inputs. In GDP below, “exclusive selection” means exactly one selected indicator, as in Pyomo’s default disjunction semantics.

Source: BGW, Table 15.1, p. 515, and Eqs. (15.18)–(15.21), pp. 515–516. The exactly-one row above states the intended cardinality directly rather than interpreting a repeated XOR as cardinality.

Derived AND- and OR-implication rows

For (P1∧P2)⇒P3(P_1\land P_2)\Rightarrow P_3, implication removal and De Morgan give ¬P1∨¬P2∨P3\neg P_1\lor\neg P_2\lor P_3. Its single clause is (1−y1)+(1−y2)+y3≥1(1-y_1)+(1-y_2)+y_3\ge1, or y1+y2−y3≤1y_1+y_2-y_3\le1.

For (P1∨P2)⇒P3(P_1\lor P_2)\Rightarrow P_3, the same first steps give (¬P1∧¬P2)∨P3(\neg P_1\land\neg P_2)\lor P_3. Distributing yields (¬P1∨P3)∧(¬P2∨P3)(\neg P_1\lor P_3)\land(\neg P_2\lor P_3). The two rows are (1−y1)+y3≥1(1-y_1)+y_3\ge1 and (1−y2)+y3≥1(1-y_2)+y_3\ge1, equivalently y1≤y3y_1\le y_3 and y2≤y3y_2\le y_3. Adding them gives the weaker aggregate y1+y2≤2y3y_1+y_2\le2y_3 on fractional variables. The separator example substitutes the consequence ¬PCS\neg P_{CS} and thus y3=1−yCSy_3=1-y_{CS}.

Example: Separation Sequence

Let’s revisit the example from the top of the notebook. Reformulate:

PA∨PM  ⟹  ¬PCSP_A \lor P_M \implies \neg P_{CS}

Step 1:

¬(PA∨PM)∨¬PCS\neg (P_A \lor P_M) \lor \neg P_{CS}

Step 2:

(¬PA∧¬PM)∨¬PCS(\neg P_A \land \neg P_M) \lor \neg P_{CS}

Step 3:

(¬PA∨¬PCS)∧(¬PM∨¬PCS)(\neg P_A \lor \neg P_{CS}) \land (\neg P_M \lor \neg P_{CS})

Now substituting:

¬PA→1−yA,¬PCS→1−yCS \neg P_A \rightarrow 1 - y_A, \quad \neg P_{CS} \rightarrow 1 - y_{CS}

¬PM→1−yM,¬PCS→1−yCS \neg P_M \rightarrow 1 - y_M, \quad \neg P_{CS} \rightarrow 1 - y_{CS}

We get:

(1−yA)+(1−yCS)≥1 (1 - y_A) + (1 - y_{CS}) \geq 1

(1−yM)+(1−yCS)≥1 (1 - y_M) + (1 - y_{CS}) \geq 1

Rearrange:

yA+yCS≤1 y_A + y_{CS} \leq 1

yM+yCS≤1 y_M + y_{CS} \leq 1

Example: Assembling Components

If (parts 1 and 2) or part 3 is used, then parts 4 or 5 must also be used. Here PiP_i means “use part ii,” and both ORs are inclusive.

((P1∧P2)∨P3)⇒(P4∨P5).((P_1\land P_2)\lor P_3)\Rightarrow(P_4\lor P_5).

Step 1: replace the implication. Apply A⇒B⇔¬A∨BA\Rightarrow B\Leftrightarrow\neg A\lor B with the whole antecedent A=((P1∧P2)∨P3)A=((P_1\land P_2)\lor P_3) and B=(P4∨P5)B=(P_4\lor P_5):

¬((P1∧P2)∨P3)∨(P4∨P5).\neg((P_1\land P_2)\lor P_3)\lor(P_4\lor P_5).

Step 2: move the negations inward. De Morgan’s laws first negate the outer OR and then the inner AND:

[¬(P1∧P2)∧¬P3]∨(P4∨P5)[\neg(P_1\land P_2)\land\neg P_3]\lor(P_4\lor P_5)
[(¬P1∨¬P2)∧¬P3]∨(P4∨P5).[(\neg P_1\lor\neg P_2)\land\neg P_3]\lor(P_4\lor P_5).

Step 3: distribute OR over AND. With C=(P4∨P5)C=(P_4\lor P_5), distribute (A∧B)∨C(A\land B)\lor C into (A∨C)∧(B∨C)(A\lor C)\land(B\lor C):

(¬P1∨¬P2∨P4∨P5)∧(¬P3∨P4∨P5).(\neg P_1\lor\neg P_2\lor P_4\lor P_5)\land(\neg P_3\lor P_4\lor P_5).

Translate the two clauses separately:

(1−y1)+(1−y2)+y4+y5≥1,(1-y_1)+(1-y_2)+y_4+y_5\ge1,
(1−y3)+y4+y5≥1.(1-y_3)+y_4+y_5\ge1.

Equivalently,

y1+y2−y4−y5≤1,y3−y4−y5≤0.y_1+y_2-y_4-y_5\le1,\qquad y_3-y_4-y_5\le0.

The first row has y4y_4, not y3y_3, in its negative terms. For example, parts 1 and 2 without 4 or 5 must be rejected even when part 3 is absent. Source: BGW, Example 15.5, Eqs. (15.28)–(15.33), pp. 516–517.

The entire parts-selection antecedent implies the required consequence.

Modeling Disjunctions

When modeling disjunctions, we will have to represent logical constraints that involve continuous variables.

General Notation

A generalized disjunctive program combines an objective, global algebraic constraints, disjunctions, and logical constraints. One common form is

min⁡x,c,Yf(x)+∑k∈Kcksubject tog(x)≤0,L≤x≤U,⋁i∈Dk[Yikgik(x)≤0ck=γik],k∈K,Ω(Y)=True,Yik∈{False,True}.\begin{aligned} \min_{\mathbf{x},\mathbf{c},Y}\quad & f(\mathbf{x})+\sum_{k\in K}c_k\\ \text{subject to}\quad &\mathbf{g}(\mathbf{x})\le\mathbf{0},\qquad \mathbf{L}\le\mathbf{x}\le\mathbf{U},\\ &\bigvee_{i\in D_k}\begin{bmatrix}Y_{ik}\\\mathbf{g}_{ik}(\mathbf{x})\le\mathbf{0}\\c_k=\gamma_{ik}\end{bmatrix},\quad k\in K,\\ &\Omega(Y)=\mathrm{True},\qquad Y_{ik}\in\{\mathrm{False},\mathrm{True}\}. \end{aligned}

Here KK indexes disjunctions and DkD_k indexes the alternatives in disjunction kk. The cost variable ckc_k is assigned the given cost γik\gamma_{ik} when term ii is selected. Each selected term enforces all its constraints together. Global rows hold regardless of the choice. The Boolean indicators YikY_{ik} are distinct from their algebraic binary representatives yiky_{ik}.

The displayed ⋁\bigvee denotes inclusive OR. Our reactor and packing models additionally impose exactly one selected indicator per disjunction, which is Pyomo’s default. A false indicator removes that term’s requirements; it does not assert the negation of every inequality in the term. Thus more than one alternative’s algebraic inequalities can happen to hold even though only one indicator is selected.

Sources: Bynum et al. (2021), §11.1, Eqs. (11.13)–(11.17), p. 173; Pyomo’s modeling documentation. Pyomo exposes indicator_var as Boolean and binary_indicator_var as its associated binary. Use BooleanVar for logic and a Var(domain=Binary) for numeric binary algebra.

Example: The Reactor Problem

The following excerpts are from Section 15.8 in Biegler, Grossmann, and Westerberg (1997).

This modeling disjunctions example involves selecting between two reactors:

  • If reactor 1 is selected, then pressure PP must be between 5 and 10 atmospheres.

  • If reactor 2 is selected, then pressure PP must be between 20 and 30 atmospheres.

A bounded linear disjunction has term sets

Si={x:Aix≤bi, L≤x≤U},i∈D.S_i=\{\mathbf{x}:\mathbf{A}_i\mathbf{x}\le\mathbf{b}_i,\ \mathbf{L}\le\mathbf{x}\le\mathbf{U}\},\qquad i\in D.

Selecting a term enforces all of its rows. With exactly-one selection, eliminating the indicators leaves the union ⋃i∈DSi\bigcup_{i\in D}S_i. The same continuous union can also be represented by inclusive selection; overlapping terms can then have several true indicators.

Applied to the reactor problem:

[Y15≤P≤10]∨[Y220≤P≤30],y1+y2=1.\begin{bmatrix}Y_1\\5\le P\le10\end{bmatrix}\lor\begin{bmatrix}Y_2\\20\le P\le30\end{bmatrix},\qquad y_1+y_2=1.

The continuous feasible set is [5,10]∪[20,30][5,10]\cup[20,30] atm. We minimize PP to obtain a simple test objective. The analytic Big-M illustration below uses the declared common box 0≤P≤300\le P\le30. The canonical Pyomo builder uses the stronger valid bound 5≤P≤305\le P\le30. This difference affects automatic Big-M values and LP bounds; it does not change the original disjunctive feasible set.

The two disjoint reactor pressure windows and the infeasible pressure gap.

Define Model in Pyomo with GDP

First we will define the model (including disjunctions) for the Reactor Problem in Pyomo.

"""
Instead of using
# import pyomo.environ as pyo
We can import specific functions/objects
"""

from pyomo.environ import (
    check_optimal_termination,
    ConcreteModel,
    Param,
    Set,
    SolverFactory,
    TransformationFactory,
    Var,
)
def add_pressure_windows(model):
    # One exactly-one disjunction, with both pressure rows in each term.
    # GDP creates the indicators; reformulation supplies the algebraic rows.
    @model.Disjunction(
        doc="Select exactly one reactor; each brings its own pressure window"
    )
    def pressure_bounds(b):
        # Two inner lists: one simultaneous pressure window per reactor.
        return [
            [b.P <= b.max_pressure[r], b.P >= b.min_pressure[r]] for r in b.reactors
        ]

def create_model():
    """
    Build the reactor problem model.

    Return:
    model: Pyomo model

    """
    ## Model
    model = ConcreteModel(name="Selecting a reactor")

    ## Sets
    # Initialized for reactor 1 (1) and reactor 2 (2)
    model.reactors = Set(initialize=[1, 2])

    ## Parameters
    # Initialized with a dictionary where the keys are 1 and 2 (the reactors)
    # for the minimum and maximum pressure values (atm)
    model.min_pressure = Param(model.reactors, initialize={1: 5, 2: 20})
    model.max_pressure = Param(model.reactors, initialize={1: 10, 2: 30})

    ## Variables
    # Reactor pressure bounded between the lower bound (5 atm) and upper bound (30 atm).
    # These bounds are what gdp.bigm reads to compute M per row -- here 20, 0, 0, 15.
    # Automatic M estimation needs finite expression bounds; valid supplied M values
    # are an alternative. Hull requires finite bounds on variables in its disjuncts.
    model.P = Var(bounds=(5, 30), doc="Reactor pressure (atm)")

    ## Adding an objective for the example
    @model.Objective()
    def objective(b):
        return b.P

    add_pressure_windows(model)

    return model

Pyomo creates the Boolean and associated binary indicators when the disjuncts are declared, as the pre-transformation model printout shows. Reformulation then supplies the algebraic selection and constraint rows.

Transform and Solve with Big-M Relaxation

The following excerpts are from Section 15.8 in Biegler, Grossmann, and Westerberg (1997).

Use “Big-M” constraints to convert linear disjunctions into mixed-integer constraints to represent logic with continuous variables.

Big-M Relaxation Approach

For each row aijTx≤bija_{ij}^{\mathsf T}\mathbf{x}\le b_{ij} in term ii, choose a nonnegative constant MijM_{ij} large enough that the row imposes no restriction on the relevant off-state domain:

aijTx≤bij+Mij(1−yi),∑i∈Dyi=1,yi∈{0,1}.a_{ij}^{\mathsf T}\mathbf{x}\le b_{ij}+M_{ij}(1-y_i),\qquad\sum_{i\in D}y_i=1,\qquad y_i\in\{0,1\}.

When yi=1y_i=1, the original row is enforced. When yi=0y_i=0, a valid MijM_{ij} deactivates it. A conservative choice is

Mij≥max⁡{0, max⁡L≤x≤U(aijTx−bij)}.M_{ij}\ge\max\left\{0,\ \max_{\mathbf{L}\le\mathbf{x}\le\mathbf{U}}(a_{ij}^{\mathsf T}\mathbf{x}-b_{ij})\right\}.

A common constant for all rows of a term can be the maximum of their row constants. Bounds or constraints known to hold in the other selected terms may yield smaller valid constants. State which off-state domain was used.

For the reactor, common constants give

P≤10+M1(1−y1),−P≤−5+M1(1−y1),P≤30+M2(1−y2),−P≤−20+M2(1−y2),y1+y2=1,y1,y2∈{0,1}.\begin{aligned} P&\le10+M_1(1-y_1),& -P&\le-5+M_1(1-y_1),\\ P&\le30+M_2(1-y_2),& -P&\le-20+M_2(1-y_2),\\ y_1+y_2&=1,& y_1,y_2&\in\{0,1\}. \end{aligned}

Over the entire box [0,30][0,30], maximum row violations are 20,5,0,2020,5,0,20, so M1=M2=20M_1=M_2=20 deactivates every row over that box. These are the smallest common per-term constants for that deactivation policy. They are not the smallest constants that can preserve this particular union: when term 2 is off, term 1 supplies P≥5P\ge5, so M2=15M_2=15 already suffices.

A too-small constant may remove feasible points. For example, M1=10M_1=10 wrongly caps P≤20P\le20 when reactor 2 is selected, cutting out its valid pressures above 20. An unnecessarily large constant can weaken the LP relaxation and worsen scaling; it does not guarantee a particular iteration count. Neither problem size alone nor a blanket preference selects the better reformulation.

Source: BGW, §15.8, Eqs. (15.43)–(15.44), pp. 519–520. The two printing errors in (15.44) are corrected above: the fourth row starts with -20, and the selection row is y1+y2=1y_1+y_2=1.

One upper pressure row is enforced or deactivated according to its indicator and M.

Big-M Implementation in Pyomo

First we will create and print the model.

# Creating the model
model = create_model()

# Printing the model
model.pprint()
1 Set Declarations
    reactors : Size=1, Index=None, Ordered=Insertion
        Key  : Dimen : Domain : Size : Members
        None :     1 :    Any :    2 : {1, 2}

2 Param Declarations
    max_pressure : Size=2, Index=reactors, Domain=Any, Default=None, Mutable=False
        Key : Value
          1 :    10
          2 :    30
    min_pressure : Size=2, Index=reactors, Domain=Any, Default=None, Mutable=False
        Key : Value
          1 :     5
          2 :    20

1 Var Declarations
    P : Reactor pressure (atm)
        Size=1, Index=None
        Key  : Lower : Value : Upper : Fixed : Stale : Domain
        None :     5 :  None :    30 : False :  True :  Reals

1 Objective Declarations
    objective : Size=1, Index=None, Active=True
        Key  : Active : Sense    : Expression
        None :   True : minimize :          P

1 Disjunct Declarations
    pressure_bounds_disjuncts : Size=2, Index=Any, Active=True
        pressure_bounds_disjuncts[0] : Active=True
            1 Var Declarations
                binary_indicator_var : Size=1, Index=None
                    Key  : Lower : Value : Upper : Fixed : Stale : Domain
                    None :     0 :  None :     1 : False :  True : Binary

            1 Constraint Declarations
                constraint : Size=2, Index={1, 2}, Active=True
                    Key : Lower : Body : Upper : Active
                      1 :  -Inf :    P :  10.0 :   True
                      2 :   5.0 :    P :  +Inf :   True

            1 BooleanVar Declarations
                indicator_var : Size=1, Index=None
                    Key  : Value : Fixed : Stale
                    None :  None : False :  True

            1 LogicalConstraint Declarations
                propositions : Size=0, Index={}, Active=True
                    Key : Body : Active

            4 Declarations: indicator_var binary_indicator_var constraint propositions
        pressure_bounds_disjuncts[1] : Active=True
            1 Var Declarations
                binary_indicator_var : Size=1, Index=None
                    Key  : Lower : Value : Upper : Fixed : Stale : Domain
                    None :     0 :  None :     1 : False :  True : Binary

            1 Constraint Declarations
                constraint : Size=2, Index={1, 2}, Active=True
                    Key : Lower : Body : Upper : Active
                      1 :  -Inf :    P :  30.0 :   True
                      2 :  20.0 :    P :  +Inf :   True

            1 BooleanVar Declarations
                indicator_var : Size=1, Index=None
                    Key  : Value : Fixed : Stale
                    None :  None : False :  True

            1 LogicalConstraint Declarations
                propositions : Size=0, Index={}, Active=True
                    Key : Body : Active

            4 Declarations: indicator_var binary_indicator_var constraint propositions

1 Disjunction Declarations
    pressure_bounds : Select exactly one reactor; each brings its own pressure window
        Size=1, Index=None, Active=True
        Key  : Disjuncts                                                        : Active : XOR
        None : ['pressure_bounds_disjuncts[0]', 'pressure_bounds_disjuncts[1]'] :   True : True

7 Declarations: reactors min_pressure max_pressure P objective pressure_bounds pressure_bounds_disjuncts

Next, let’s transform using Big-M and print the model again.

# Applying Big-M relaxation to the model
# Add your solution here

# Printing
model.pprint()
Click to see the solution to the activity
TransformationFactory("gdp.bigm").apply_to(model)

Finally, we’ll solve the model and examine the solution.

# Solve and print the solution
results = SolverFactory(milp_solver).solve(model, tee=True)
assert check_optimal_termination(results), (
    f"Solve failed: status={results.solver.status}, "
    f"termination={results.solver.termination_condition}"
)

model.P.display()
P : Reactor pressure (atm)
    Size=1, Index=None
    Key  : Lower : Value : Upper : Fixed : Stale : Domain
    None :     5 :   5.0 :    30 : False : False :  Reals

Transform and Solve with Convex Hull Relaxation

The following excerpts are from Section 15.8 in Biegler, Grossmann, and Westerberg (1997).

The linear hull construction disaggregates each continuous variable into one scaled copy per term. The copies express a convex combination of feasible points, giving the convex hull of one bounded linear disjunction when the indicators are relaxed. This avoids choosing Big-M constants, at the cost of additional variables and rows.

Convex Hull Relaxation Approach

Manual M-equals-20 reactor relaxation and linear hull.

This analytic illustration uses the manual box-wide constants, not the automatic transformation of the builder with P bounded below by 5. The hatched difference is entirely at the lower boundary.

For the nonempty bounded linear term sets SiS_i defined above, introduce zi\mathbf{z}_i and impose

x=∑i∈Dzi,Aizi≤biyi,i∈D,Lyi≤zi≤Uyi,i∈D,∑i∈Dyi=1,yi∈{0,1}.\begin{aligned} \mathbf{x}&=\sum_{i\in D}\mathbf{z}_i,\\ \mathbf{A}_i\mathbf{z}_i&\le\mathbf{b}_i y_i,\qquad i\in D,\\ \mathbf{L}y_i&\le\mathbf{z}_i\le\mathbf{U}y_i,\qquad i\in D,\\ \sum_{i\in D}y_i&=1,\qquad y_i\in\{0,1\}. \end{aligned}

The bounds are signed: L\mathbf{L} need not be zero. For example, a valid selected value x=−1x=-1 would be lost if 0≤zi0\le z_i were incorrectly imposed. At yi=0y_i=0, the scaled bounds force zi=0\mathbf{z}_i=\mathbf{0}. The row Aizi≤0\mathbf{A}_i\mathbf{z}_i\le\mathbf{0} alone need not do so; −zi≤0-z_i\le0 allows any nonnegative ziz_i.

Why the relaxed formulation gives the hull. Replace binary domains by yi≥0y_i\ge0. For any positive weight yiy_i, divide by that weight to obtain zi/yi∈Si\mathbf{z}_i/y_i\in S_i. The zero-weight copies vanish by the scaled bounds. Hence x=∑i:yi>0yi(zi/yi)\mathbf{x}=\sum_{i:y_i>0}y_i(\mathbf{z}_i/y_i) is a convex combination of points in the union. Conversely, combine any convex combination’s contributions from the same term, which is convex; assigning its total weight to yiy_i and its weighted point to zi\mathbf{z}_i satisfies every row. This proves both inclusions.

With binary weights, exactly one copy remains and equals x\mathbf{x}, proving exactness for the original union. The bounded linear assumptions matter: nonlinear hull reformulation needs additional theory. Also, intersecting separately convexified disjunctions with global constraints need not give the convex hull of the entire GDP feasible set.

Reactor hull:

P=P1+P2,5y1≤P1≤10y1,20y2≤P2≤30y2,y1+y2=1,y1,y2∈{0,1}.\begin{aligned} P&=P_1+P_2,\\ 5y_1\le P_1&\le10y_1,\qquad20y_2\le P_2\le30y_2,\\ y_1+y_2&=1,\qquad y_1,y_2\in\{0,1\}. \end{aligned}

Here each term already bounds its copy on both sides, forcing an off-copy to zero. With t=y1t=y_1, y2=1−ty_2=1-t, the relaxed pressure interval is

20−15t≤P≤30−20t,0≤t≤1.20-15t\le P\le30-20t,\qquad0\le t\le1.

Every value in this interval is a sum of points in [5t,10t][5t,10t] and [20(1−t),30(1−t)][20(1-t),30(1-t)]. Thus these projected bounds are necessary and sufficient. Minimizing PP gives 5.

For the manual [0,30][0,30], M1=M2=20M_1=M_2=20 model, the corresponding lower bound is max⁡{20t−15,20−20t}\max\{20t-15,20-20t\} and the upper bound is 30−20t30-20t. The lower lines meet at t=7/8t=7/8, P=5/2P=5/2, whereas the hull’s lower bound there is 55/855/8. At t=1/2t=1/2, the respective intervals are [10,20][10,20] and [12.5,20][12.5,20]. The upper boundaries agree; the lower boundaries explain the gap.

Sources: BGW, Eqs. (15.45)–(15.46), pp. 520–521; Balas (1985). Pyomo’s hull transformation requires finite lower and upper bounds on the variables appearing in disjuncts, including when automatic Big-M estimation could instead be replaced by supplied valid constants.

Scaled copies of feasible points add to the convex combination in a two-term hull.

Convex Hull Implementation in Pyomo

We will repeat the procedure above but using Convex Hull now.

# Creating the model
model = create_model()

# Applying convex hull relaxation to the model
# Add your solution here

# Solve and print the solution
results = SolverFactory(milp_solver).solve(model, tee=True)
assert check_optimal_termination(results), (
    f"Solve failed: status={results.solver.status}, "
    f"termination={results.solver.termination_condition}"
)

model.P.display()
Click to see the solution to the activity
TransformationFactory("gdp.hull").apply_to(model)

Discussion Questions

  1. Why does CNF allow one linear row per clause?

  2. Does the separator implication require selecting any separator?

  3. What changes when an indicator is zero in Big-M? Does it assert the opposite inequality?

  4. Is hull always strictly stronger than a valid Big-M formulation?

  5. Under what conditions can the zero-term hull be simplified?

Answers
  1. Each clause is an OR of literals. Its sum of binary truth values must be at least one; an AND of clauses requires every such row.

  2. No. The all-zero selection makes the antecedent false and satisfies the implication. Add a separate selection requirement if the process needs one.

  3. A valid constant makes the term’s row nonrestrictive on the relevant off-state domain. The false indicator does not enforce the negation of that row.

  4. No. It can be stronger, or the LP relaxations can agree. The following diagnostics show equality for this notebook’s automatically bounded reactor model.

  5. The particular two-term on/off construction below eliminates the off-copy. This is not a theorem that every two-term GDP simplifies in the same way.

Check the model and its relaxations

The following calculation constructs fresh copies of the same builder used above, inspects Pyomo’s signed lower/upper M values, and solves their MIP and LP versions. It also checks a pressure cap of 19: the correct model must return 10, not 19. Fixing P=15P=15 must be infeasible. Both checks detect an error that the minimum P=5P=5 would miss.

For this builder’s box [5,30][5,30], automatic row magnitudes are 20,0,0,1520,0,0,15. Substitution yields 20−15t≤P≤30−20t20-15t\le P\le30-20t, the same projection as the hull. A zero M means the bound is already globally implied. This example therefore illustrates equality, rather than a strict automatic-Big-M/hull gap. The manual M=20M=20 illustration deliberately uses a different deactivation policy.

import json
import pyomo.environ as pyo
from pyomo.gdp import Disjunction
import pyomo

def model_size(m):
    variables = list(m.component_data_objects(pyo.Var, active=True))
    rows = list(m.component_data_objects(pyo.Constraint, active=True))
    return {"continuous_variables": sum(not v.is_binary() for v in variables),
            "binary_variables": sum(v.is_binary() for v in variables),
            "equalities": sum(c.equality for c in rows),
            "inequalities": sum(not c.equality for c in rows)}

def reactor_check(transform, relax=False, cap=None, fixed_pressure=None):
    m = create_model()
    if cap is not None:
        m.cap = pyo.Constraint(expr=m.P <= cap)
        m.objective.sense = pyo.maximize
    if fixed_pressure is not None:
        m.P.fix(fixed_pressure)
    reformulation = pyo.TransformationFactory(transform)
    reformulation.apply_to(m)
    size = model_size(m)
    M_values = None
    if transform == "gdp.bigm":
        M_values = {c.name: list(pair) for c, pair in
                    reformulation.get_all_M_values_by_constraint(m).items()}
    if relax:
        pyo.TransformationFactory("core.relax_integer_vars").apply_to(m)
    solver = pyo.SolverFactory(milp_solver)
    solved = solver.solve(m, load_solutions=False)
    termination = str(solved.solver.termination_condition)
    pressure = None
    if pyo.check_optimal_termination(solved):
        m.solutions.load_from(solved)
        pressure = pyo.value(m.P)
    return {"transformation": transform, "LP": relax, "cap": cap,
            "fixed_pressure": fixed_pressure, "termination": termination,
            "pressure": pressure, "size_before_relaxing": size, "signed_M": M_values}

reactor_diagnostics = [reactor_check(t, relax=r)
                       for t in ("gdp.bigm", "gdp.hull") for r in (False, True)]
reactor_diagnostics += [reactor_check("gdp.bigm", cap=19),
                        reactor_check("gdp.bigm", fixed_pressure=15)]
assert all(abs(row["pressure"] - 5) < 1e-8 for row in reactor_diagnostics[:4])
assert abs(reactor_diagnostics[4]["pressure"] - 10) < 1e-8
assert reactor_diagnostics[5]["termination"] == "infeasible"
print("Pyomo", pyomo.__version__)
print(json.dumps(reactor_diagnostics, indent=2))
Pyomo 6.10.1
[
  {
    "transformation": "gdp.bigm",
    "LP": false,
    "cap": null,
    "fixed_pressure": null,
    "termination": "optimal",
    "pressure": 5.0,
    "size_before_relaxing": {
      "continuous_variables": 1,
      "binary_variables": 2,
      "equalities": 1,
      "inequalities": 4
    },
    "signed_M": {
      "pressure_bounds_disjuncts[0].constraint[1]": [
        null,
        20.0
      ],
      "pressure_bounds_disjuncts[0].constraint[2]": [
        0.0,
        null
      ],
      "pressure_bounds_disjuncts[1].constraint[1]": [
        null,
        0.0
      ],
      "pressure_bounds_disjuncts[1].constraint[2]": [
        -15.0,
        null
      ]
    }
  },
  {
    "transformation": "gdp.bigm",
    "LP": true,
    "cap": null,
    "fixed_pressure": null,
    "termination": "optimal",
    "pressure": 5.0,
    "size_before_relaxing": {
      "continuous_variables": 1,
      "binary_variables": 2,
      "equalities": 1,
      "inequalities": 4
    },
    "signed_M": {
      "pressure_bounds_disjuncts[0].constraint[1]": [
        null,
        20.0
      ],
      "pressure_bounds_disjuncts[0].constraint[2]": [
        0.0,
        null
      ],
      "pressure_bounds_disjuncts[1].constraint[1]": [
        null,
        0.0
      ],
      "pressure_bounds_disjuncts[1].constraint[2]": [
        -15.0,
        null
      ]
    }
  },
  {
    "transformation": "gdp.hull",
    "LP": false,
    "cap": null,
    "fixed_pressure": null,
    "termination": "optimal",
    "pressure": 5.0,
    "size_before_relaxing": {
      "continuous_variables": 3,
      "binary_variables": 2,
      "equalities": 2,
      "inequalities": 8
    },
    "signed_M": null
  },
  {
    "transformation": "gdp.hull",
    "LP": true,
    "cap": null,
    "fixed_pressure": null,
    "termination": "optimal",
    "pressure": 5.0,
    "size_before_relaxing": {
      "continuous_variables": 3,
      "binary_variables": 2,
      "equalities": 2,
      "inequalities": 8
    },
    "signed_M": null
  },
  {
    "transformation": "gdp.bigm",
    "LP": false,
    "cap": 19,
    "fixed_pressure": null,
    "termination": "optimal",
    "pressure": 10.0,
    "size_before_relaxing": {
      "continuous_variables": 1,
      "binary_variables": 2,
      "equalities": 1,
      "inequalities": 5
    },
    "signed_M": {
      "pressure_bounds_disjuncts[0].constraint[1]": [
        null,
        20.0
      ],
      "pressure_bounds_disjuncts[0].constraint[2]": [
        0.0,
        null
      ],
      "pressure_bounds_disjuncts[1].constraint[1]": [
        null,
        0.0
      ],
      "pressure_bounds_disjuncts[1].constraint[2]": [
        -15.0,
        null
      ]
    }
  },
  {
    "transformation": "gdp.bigm",
    "LP": false,
    "cap": null,
    "fixed_pressure": 15,
    "termination": "infeasible",
    "pressure": null,
    "size_before_relaxing": {
      "continuous_variables": 1,
      "binary_variables": 2,
      "equalities": 1,
      "inequalities": 4
    },
    "signed_M": {
      "pressure_bounds_disjuncts[0].constraint[1]": [
        null,
        20.0
      ],
      "pressure_bounds_disjuncts[0].constraint[2]": [
        0.0,
        null
      ],
      "pressure_bounds_disjuncts[1].constraint[1]": [
        null,
        0.0
      ],
      "pressure_bounds_disjuncts[1].constraint[2]": [
        -15.0,
        null
      ]
    }
  }
]

A test that rejects the toothless indexed model

The incorrect formulation below creates two independent exactly-one choices between overlapping half-lines. At P=15P=15, each pair has a satisfied alternative. Even exclusive indicator selection cannot repair a wrongly grouped model: the unselected half-line is deactivated, rather than negated.

wrong = pyo.ConcreteModel()
wrong.P = pyo.Var(bounds=(5, 30))
@wrong.Disjunction([1, 2])
def pressure_bounds(b, r):
    lo, hi = {1: (5, 10), 2: (20, 30)}[r]
    return [b.P <= hi, b.P >= lo]
wrong.cap = pyo.Constraint(expr=wrong.P <= 19)
wrong.objective = pyo.Objective(expr=wrong.P, sense=pyo.maximize)
pyo.TransformationFactory("gdp.bigm").apply_to(wrong)
wrong_result = pyo.SolverFactory(milp_solver).solve(wrong)
assert pyo.check_optimal_termination(wrong_result)
assert abs(pyo.value(wrong.P) - 19) < 1e-8
wrong.P.fix(15)
wrong_result = pyo.SolverFactory(milp_solver).solve(wrong)
assert pyo.check_optimal_termination(wrong_result)
print("Incorrect indexed model: max P with P <= 19 is 19; P = 15 is feasible.")
Incorrect indexed model: max P with P <= 19 is 19; P = 15 is feasible.

Reproduce the manual LP bounds

The following LPs use the same four reactor rows with explicit nonnegative row constants. Their continuous variables are (P,t)(P,t), with y1=ty_1=t and y2=1−ty_2=1-t. Keeping the box and constant policy in the output makes the comparison reproducible.

For [0,30][0,30], row-specific box maxima (20,5,0,20)(20,5,0,20) improve the LP bound from 2.5 to 4. Using the other branch to justify common constants (M1,M2)=(20,15)(M_1,M_2)=(20,15) gives the hull projection and bound 5. The canonical [5,30][5,30] automatic constants (20,0,0,15)(20,0,0,15) also give 5. These choices are all valid for integer selections; their fractional behavior differs.

from scipy.optimize import linprog

manual_lp_bounds = []
for label, lower, M in (
    ("box [0,30], common M=20", 0, (20,20,20,20)),
    ("box [0,30], row-specific box maxima", 0, (20,5,0,20)),
    ("box [0,30], other-branch common M", 0, (20,20,15,15)),
    ("box [5,30], automatic row magnitudes", 5, (20,0,0,15)),
):
    u1,l1,u2,l2 = M
    # Columns are (P,t): upper/lower reactor-1 rows, then reactor-2 rows.
    A = [[1,u1],[-1,l1],[1,-u2],[-1,-l2]]
    b = [10+u1,-5+l1,30,-20]
    lp = linprog([1,0], A_ub=A, b_ub=b, bounds=[(lower,30),(0,1)], method="highs")
    assert lp.success
    manual_lp_bounds.append({"case": label, "row_M": list(M),
                             "minimum_P": float(lp.fun), "t": float(lp.x[1])})
assert all(abs(row["minimum_P"]-expected)<1e-8
           for row,expected in zip(manual_lp_bounds,(2.5,4,5,5)))
print(json.dumps(manual_lp_bounds, indent=2))
[
  {
    "case": "box [0,30], common M=20",
    "row_M": [
      20,
      20,
      20,
      20
    ],
    "minimum_P": 2.5,
    "t": 0.875
  },
  {
    "case": "box [0,30], row-specific box maxima",
    "row_M": [
      20,
      5,
      0,
      20
    ],
    "minimum_P": 4.0,
    "t": 0.8
  },
  {
    "case": "box [0,30], other-branch common M",
    "row_M": [
      20,
      20,
      15,
      15
    ],
    "minimum_P": 5.0,
    "t": 1.0
  },
  {
    "case": "box [5,30], automatic row magnitudes",
    "row_M": [
      20,
      0,
      0,
      15
    ],
    "minimum_P": 5.0,
    "t": 1.0
  }
]

Formulation size and choice

For one disjunction with dd terms, nn continuous components, and rir_i inequalities in term ii, count the algebraic core as follows. These are numbers of declared components, not a Jacobian rank or degrees-of-freedom calculation.

ComponentCommon-per-term Big-MLinear hull
Continuous variablesnnn+ndn+nd
Binary indicatorsdddd
Equalities1 selection row1 selection row and nn linking rows
Term inequalities∑iri\sum_i r_i∑iri\sum_i r_i

This table excludes domain bounds and scaled-copy bound rows. The explicit general hull also has 2nd2nd scaled bound inequalities, although some can be redundant or subsumed by term rows. Global constraints add to both formulations. Pyomo can eliminate or reuse components, so compare the actual counts printed above under a stated convention rather than asserting they must equal the simplified table.

Tighter relaxations can improve lower bounds for minimization, while larger models cost more per node. Bounds, formulation structure, presolve, objective, and solver all matter. Compare actual relaxations and solve evidence; “Big-M for small problems, hull for large problems” is not a valid selection rule. This notebook reports optimality and mathematical bounds rather than treating an iteration count as a universal performance result.

Source: BGW, §15.8, p. 521. Nonlinear convex hull theory is treated by Grossmann & Lee (2003); it does not follow from the bounded linear proof above.

A two-term on/off simplification

Consider [Y; Ax≤b, L≤x≤U][Y;\ \mathbf{A}\mathbf{x}\le\mathbf{b},\ \mathbf{L}\le\mathbf{x}\le\mathbf{U}] or [¬Y; x=0][\neg Y;\ \mathbf{x}=\mathbf{0}]. The hull copies satisfy x=zon+zoff\mathbf{x}=\mathbf{z}_{on}+\mathbf{z}_{off} and zoff=0\mathbf{z}_{off}=\mathbf{0}, so eliminate both copies to obtain

Ax≤by,Ly≤x≤Uy,y∈{0,1}.\mathbf{A}\mathbf{x}\le\mathbf{b}y,\qquad\mathbf{L}y\le\mathbf{x}\le\mathbf{U}y,\qquad y\in\{0,1\}.

At y=1y=1 these are the original on-term rows; at y=0y=0 the scaled bounds force zero. Relaxing yy represents convex combinations with the zero point by the same proof as above. The signed bounds are essential in general. Merely having two alternatives, without an explicit zero off-term, does not justify this elimination. Source: BGW, Eqs. (15.47)–(15.48), p. 521.

Cost assignment and logical scope

When exactly one indicator is selected, the disjunctive assignments c=γic=\gamma_i are equivalently expressed by c=∑iγiyic=\sum_i\gamma_i y_i. The equality takes the selected alternative’s cost. With several selected alternatives this expression instead adds their costs, so it must not be substituted into an inclusive-cost model without checking the intended semantics.

A separate complementarity warning concerns NLP solution methods. Biegler (2010), §11.3, p. 337, Eq. (11.18), gives 0≤x⊥(1−x)≥00\le x\perp(1-x)\ge0: the product condition x(1−x)=0x(1-x)=0 and the nonnegativity bounds leave only x=0x=0 or x=1x=1. The book warns that this disconnected feasible set can cause convergence difficulties and advises against using such MPCC constructions for exclusive choices. This is a warning about formulation and solution behavior, not a proof that complementarity cannot represent those two points. Inclusive alternatives can sometimes be modeled successfully with MPCCs, but the construction and its assumptions are problem dependent. The present lecture uses explicit binary/GDP selection instead. See MINLP Algorithms for the later discrete-continuous algorithm material.

Further reading and applications

The CNF procedure here follows BGW §15.7, pp. 515–516. BGW attribute its formalization to Clocksin & Mellish (1981). Raman & Grossmann (1991) connect logical inference with MILP process-synthesis modeling; the 1993 paper by Raman & Grossmann (1993) develops symbolic integration of logic, including CNF/DNF representations, in mixed-integer methods. These historical notes explain the relationship between the logic and algebraic models; they do not add another conversion algorithm to learn.

Start with Balas (1985) for disjunctive relaxations, Raman & Grossmann (1994) for the logic-based GDP framework, and Grossmann & Ruiz (2012) for formulations and alternative MINLP algorithms. Grossmann (2002) and Trespalacios & Grossmann (2014) are broader reviews; Kronqvist et al. (2026) give a historical overview. Grossmann & Trespalacios (2013) explain systematic discrete-continuous modeling, and Chen & Grossmann (2019) discuss model variants and hierarchy. Perez & Grossmann (2024) extend GDP to hierarchical structures and first-order logic. These are extension readings, not prerequisites for the bounded linear hull proof.

Representative applications include power/desalination superstructures (Mussati et al. (2008)), thermally linked distillation (Caballero & Grossmann (2001)), production scheduling (Castro & Grossmann (2012)), maintenance scheduling (Castro et al. (2014)), solvent-mixture design (Jonuzaj et al. (2016)), modular process synthesis (Chen & Grossmann (2019)), and discrete-continuous energy-system model predictive control (Bhattacharya et al. (2021)).

Nonlinear solution methods are developed by Lee & Grossmann (2005); Türkay & Grossmann (1996) treat discontinuous investment costs and multiple size regions. The current Pyomo modeling ecosystem is described by Chen et al. (2022).

Textbooks and software

Biegler, Grossmann, and Westerberg (1997), Systematic Methods of Chemical Process Design, Chapter 15, §§15.7–15.8, pp. 514–521. Bynum et al. (2021), Pyomo—Optimization Modeling in Python, 3rd ed., Chapter 11, pp. 173–179, DOI. Biegler (2010), Nonlinear Programming: Concepts, Algorithms, and Applications, §11.3, p. 337 (complementarity warning). Postek et al. (2025), Hands-On Mathematical Optimization with Python, §3.4: Production model using disjunctions. Current Pyomo.GDP modeling and reformulation documentation supplement the book’s syntax; diagnostics above ran with Pyomo 6.10.1.

References
  1. Balas, E. (1985). Disjunctive Programming and a Hierarchy of Relaxations for Discrete Optimization Problems. SIAM Journal on Algebraic and Discrete Methods, 6(3), 466–486. 10.1137/0606047
  2. Grossmann, I. E., & Lee, S. (2003). Generalized Convex Disjunctive Programming: Nonlinear Convex Hull Relaxation. Computational Optimization and Applications, 26(1), 83–100. 10.1023/A:1025154322278
  3. Clocksin, W. F., & Mellish, C. S. (1981). Programming in Prolog. Springer-Verlag.
  4. Raman, R., & Grossmann, I. E. (1991). Relation between MILP Modelling and Logical Inference for Chemical Process Synthesis. Computers & Chemical Engineering, 15(2), 73–84. 10.1016/0098-1354(91)87007-V
  5. Raman, R., & Grossmann, I. E. (1993). Symbolic Integration of Logic in Mixed-Integer Linear Programming Techniques for Process Synthesis. Computers & Chemical Engineering, 17(9), 909–927. 10.1016/0098-1354(93)80073-V
  6. Raman, R., & Grossmann, I. E. (1994). Modelling and Computational Techniques for Logic Based Integer Programming. Computers & Chemical Engineering, 18(7), 563–578. 10.1016/0098-1354(93)E0010-7
  7. Grossmann, I. E., & Ruiz, J. P. (2012). Generalized Disjunctive Programming: A Framework for Formulation and Alternative Algorithms for MINLP Optimization. In J. Lee & S. Leyffer (Eds.), Mixed Integer Nonlinear Programming (Vol. 154, pp. 93–115). Springer. 10.1007/978-1-4614-1927-3_4
  8. Grossmann, I. E. (2002). Review of Nonlinear Mixed-Integer and Disjunctive Programming Techniques. Optimization and Engineering, 3, 227–252. 10.1023/A:1021039126272
  9. Trespalacios, F., & Grossmann, I. E. (2014). Review of Mixed-Integer Nonlinear and Generalized Disjunctive Programming Methods. Chemie Ingenieur Technik, 86(7), 991–1012. 10.1002/cite.201400037
  10. Kronqvist, J., Neira, D. E. B., & Grossmann, I. E. (2026). 50 Years of Mixed-Integer Nonlinear and Disjunctive Programming. European Journal of Operational Research, 331, 687–705. 10.1016/j.ejor.2025.07.016
  11. Grossmann, I. E., & Trespalacios, F. (2013). Systematic Modeling of Discrete-Continuous Optimization Models through Generalized Disjunctive Programming. AIChE Journal, 59(9), 3276–3295. 10.1002/aic.14088
  12. Chen, Q., & Grossmann, I. E. (2019). Modern Modeling Paradigms Using Generalized Disjunctive Programming. Processes, 7(11), 839. 10.3390/pr7110839
  13. Perez, H. D., & Grossmann, I. E. (2024). Extensions to Generalized Disjunctive Programming: Hierarchical Structures and First-Order Logic. Optimization and Engineering, 25(2), 959–998. 10.1007/s11081-023-09831-x
  14. Mussati, S. F., Barttfeld, M., Aguirre, P. A., & Scenna, N. J. (2008). A Disjunctive Programming Model for Superstructure Optimization of Power and Desalting Plants. Desalination, 222, 457–465. 10.1016/j.desal.2007.01.162
  15. Caballero, J. A., & Grossmann, I. E. (2001). Generalized Disjunctive Programming Model for the Optimal Synthesis of Thermally Linked Distillation Columns. Industrial & Engineering Chemistry Research, 40(10), 2260–2274. 10.1021/ie000761a