2.1. Logical Modeling and Generalized Disjunctive Programs#

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

2.1.1. Introduction and Learning Objectives#

This notebook introduces generalized disjunctive programs through an example in Pyomo.GDP. Students will learn concepts related to Logical Modeling and Modeling Disjunctions in this notebook. These techniques will be applied to the Reactor Problem and then implemented into Pyomo. Critical thinking discussion questions will be included to connect concepts from CBE 60499.

2.1.2. Import Modules#

# Imports
import sys
if "google.colab" in sys.modules:
    !wget "https://raw.githubusercontent.com/ndcbe/CBE60499/main/notebooks/helper.py"
    import helper
    helper.install_idaes()
    helper.install_ipopt()
    # helper.install_cbc()
--2023-05-04 18:04:00--  https://raw.githubusercontent.com/ndcbe/CBE60499/main/notebooks/helper.py
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.109.133, 185.199.110.133, 185.199.108.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.109.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 7171 (7.0K) [text/plain]
Saving to: ‘helper.py’


helper.py             0%[                    ]       0  --.-KB/s               
helper.py           100%[===================>]   7.00K  --.-KB/s    in 0s      

2023-05-04 18:04:00 (43.5 MB/s) - ‘helper.py’ saved [7171/7171]

Installing idaes via pip...
idaes was successfully installed
Running idaes get-extensions to install Ipopt, k_aug, and more...
Ipopt 3.13.2 (x86_64-pc-linux-gnu), ASL(20190605)

[K_AUG] 0.1.0, Part of the IDAES PSE framework
Please visit https://idaes.org/ (x86_64-pc-linux-gnu), ASL(20190605)

Couenne 0.5.8 -- an Open-Source solver for Mixed Integer Nonlinear Optimization
Mailing list: couenne@list.coin-or.org
Instructions: http://www.coin-or.org/Couenne
couenne (x86_64-pc-linux-gnu), ASL(20190605)

Bonmin 1.8.8 using Cbc 2.10.8 and Ipopt 3.13.2
bonmin (x86_64-pc-linux-gnu), ASL(20190605)

Ipopt 3.13.3 (x86_64-pc-linux-gnu), ASL(20190605)

ipopt was successfully installed
k_aug was successfuly installed
cbc was successfuly installed
clp was successfuly installed
bonmin was successfuly installed
couenne was successfuly installed
ipopt_l1 was successfuly installed
 
Checking for ./figures/strip_packing.png
	Creating folder ./figures
	Downloading https://raw.githubusercontent.com/ndcbe/CBE60499/main/docs/figures/strip_packing.png
milp_solver = 'cbc'

2.1.3. Logical Modeling#

First we will look at important logic notation that is commonly used in logical modeling. This will enable us to convert logical expressions such as disjunctive clauses (\(Q_i = P_1 \lor P_2 \ \lor ... \lor \ P_r\)) into conjuctive normal form (\(Q_1 \land Q_2 \ \land ... \land \ Q_s\)).

2.1.3.1. Symbolic Logic Notation#

$\(\text{Logical Operation}\)$

$\(\text{Logical Symbol}\)$

$\(\text{OR}\)$

$\(\lor\)$

$\(\text{AND}\)$

$\(\land\)$

$\(\text{IMPLICATION}\)$

$\(\Rightarrow\)$

$\(\text{NEGATION}\)$

$\(\neg\)$

$\(\text{EQUIVALENCE}\)$

$\(\Leftrightarrow\)$

$\(\text{EXCLUSIVE OR}\)$

$\(\veebar\)$

2.1.3.2. Logical Statements and Conjuctive 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 conjuctive normal form:

  1. Replace the implication by its equivalent disjunction:
    (2.1)#\[\begin{equation} P_{1} \Rightarrow P_{2} \Leftrightarrow \neg P_{1} \lor P_{2} \tag{15.18} \end{equation}\]
  2. Distribute the negation by applying DeMorgan's Theorem:
    (2.2)#\[\begin{equation} \neg (P_{1} \land P_{2}) \Leftrightarrow \neg P_{1} \lor \neg P_{2}\tag{15.19} \end{equation}\]
    (2.3)#\[\begin{equation} \neg (P_{1} \lor P_{2}) \Leftrightarrow \neg P_{1} \land \neg P_{2}\tag{15.20} \end{equation}\]
  3. Recursively distribute the $\textbf{OR}$ over the $\textbf{AND}$ by using the following equivalence:
    (2.4)#\[\begin{equation} (P_{1} \land P_{2}) \lor P_{3} \Leftrightarrow (P_{1} \lor P_{3}) \land (P_{2} \lor P_{3})\tag{15.21} \end{equation}\]

Once in conjuctive normal form, we can apply the following rules:

$\(\text{Logical Relation}\)$

$\(\text{Comments}\)$

$\(\text{Boolean Expression}\)$

$\(\text{Representation as}\)\( \)\(\text{Linear Inequalities}\)$

Logical OR

\(P_{1}\) \(\lor\) \(P_{2}\) \(\lor \ .. \lor\) \(P_{r}\)

\(y_{1} + y_{2}\) \( + \ ..\ +\) \(y_{r} \geq 1\)

Logical AND

\(P_{1}\) \(\land\) \(P_{2}\) \(\land \ .. \land\) \(P_{r}\)

\(y_{1} \geq 1\\ y_{2} \geq 1\\ .. \\ y_{r} \geq 1\)

Implication

\(P_{1} \Rightarrow P_{2}\)

\(\neg P_{1} \lor P_{2}\)

\(1-y_{1} + y_{2} \geq 1\)

Equivalence

\(P_{1}\) iff \(P_{2} (P_{1} \Rightarrow P_{2}) \land (P_{2} \Rightarrow P_{1})\)

\((\neg P_{1} \lor P_{2})\) \(\land\) \((\neg P_{2} \lor P_{1})\)

\(y_{1} = y_{2}\)

Exclusive OR

Exactly one of the variables is true

\(P_{1} \veebar P_{2} \) \(\veebar \ .. \ \veebar\) \(P_{r}\)

\(y_{1} + y_{2}\) \(+ \ ..\ +\) \(y_{r} = 1\)

2.1.4. Modeling Disjunctions#

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

2.1.4.1. General Notation#

(2.5)#\[\begin{equation} \lor_{i \in D_{R}} \begin{bmatrix} Y_{ik}\\ n_{ik}(x)\leq0\\ c_{k} = \mu_{ik} \end{bmatrix} \ \ \ , \ \ \ \Omega(Y)= \text{True} \end{equation}\]

where: \

$\(\text{Notation}\)$

$\(\text{Definition}\)$

$\(\lor\)$

The OR operator that connects a finite collection of disjunctive clauses

$\(D_{R}\)$

The set of disjunctive terms

$\(Y_{ij}\)$

Boolean “indicator variable”

$\(n_{ik}(x)\leq0\)$

Constraint enforced when \(Y_{ik}\) is true

$\(\mu_{ik}\)$

Parameter values when indicator is true

$\(\Omega(Y)\)$

Additional logical constraints

2.1.5. Example: The Reactor Problem#

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

2.1.5.1. Problem Statement#

This modeling disjunctions example involves selecting between two reactors:

  • If reactor 1 is selected, then pressure \(P\) must be between \(5\) and \(10\) atmospheres.

  • If reactor 2 is selected, then pressure \(P\) must be between \(20\) and \(30\) atmospheres.

Linear Disjunction Form:

(2.6)#\[\begin{equation} \lor_{i \in D} \begin{bmatrix} A_{i}x \leq b_{i}(x) \end{bmatrix} \end{equation}\]

Applied to the Reactor Problem:

(2.7)#\[\begin{equation} \begin{bmatrix} y_1\\ P \leq 10\\ -P \leq -5 \end{bmatrix} \lor \begin{bmatrix} y_2\\ P \leq 30\\ -P \leq -20 \end{bmatrix} \end{equation}\]


where \(y_{1}\) represents reactor 1 and \(y_{2}\) represents reactor 2.

2.1.5.2. 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 (ConcreteModel, NonNegativeReals, Objective, Param,
                           Set, SolverFactory, TransformationFactory, Var, value)

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)
    model.P = Var(bounds=(5,30),doc='Reactor pressure (atm)')

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

    ## Disjunctions
    # Objective is bounded by the maximum and minimum pressure values
    @model.Disjunction(
        model.reactors,
        doc="Pressure bounds for different reactor selections")
    def pressure_bounds(m, r):
        return [
            m.P <= m.max_pressure[r],
            m.P >= m.min_pressure[r],
        ]

    return model

2.1.5.3. 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.

2.1.5.4. Big-M Relaxation Approach#

General Notation:

(2.8)#\[\begin{equation} A_{i}(x) \leq b_{i} + M_{i}(1-y_{i}) \ , \ \forall i \in D \end{equation}\]
(2.9)#\[\begin{equation} \sum_{i \in D} y_{i} = 1 \end{equation}\]
(2.10)#\[\begin{equation} y_{i} \in \{0,1) \ , \ \forall i \in D \end{equation}\]

Applied to the Reactor Problem:

(2.11)#\[\begin{equation} P \leq 10 + M_{1}(1-y_{1})\\ -P \leq -5 + M_{1}(1-y_{1})\\ P \leq 30 + M_{2}(1-y_{2})\\ -P \leq -20 + M_{2}(1-y_{2})\\ y_{1} + y_{2} = 1 \end{equation}\]

When the \(y\)’s are considered continuous variables, weak bounds for the objective function are formed for large values such as: \ $\(M_{1} = 100 \ \ \text{and} \ \ M_{2} = 100\)$

Main Idea:
Considering the special case where \(h_{i}(x) = A_{i}x - b_{i} \leq 0,\)
\(M_{i}\) is sufficiently large to relax \(h_{i}(x) \leq 0\) when \(y_{i}=0\)

Key Takeaways:

  • If \(M\) is too large, we can get a “weak relaxation” because integer programming algorithms need more iterations.

  • If \(M\) is too small, we can get unintended bounds.

Big-M is the best to use if the problem is small.

2.1.5.5. 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=4, Index=Any, Active=True
        pressure_bounds_disjuncts[0] : Active=True
            2 Set Declarations
                constraint_index : Size=1, Index=None, Ordered=Insertion
                    Key  : Dimen : Domain : Size : Members
                    None :     1 :    Any :    1 :    {1,}
                propositions_index : Size=1, Index=None, Ordered=Insertion
                    Key  : Dimen : Domain : Size : Members
                    None :    -- :    Any :    0 :      {}

            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=1, Index=pressure_bounds_disjuncts[0].constraint_index, Active=True
                    Key : Lower : Body : Upper : Active
                      1 :  -Inf :    P :  10.0 :   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=pressure_bounds_disjuncts[0].propositions_index, Active=True
                    Key : Body : Active

            6 Declarations: indicator_var binary_indicator_var constraint_index constraint propositions_index propositions
        pressure_bounds_disjuncts[1] : Active=True
            2 Set Declarations
                constraint_index : Size=1, Index=None, Ordered=Insertion
                    Key  : Dimen : Domain : Size : Members
                    None :     1 :    Any :    1 :    {1,}
                propositions_index : Size=1, Index=None, Ordered=Insertion
                    Key  : Dimen : Domain : Size : Members
                    None :    -- :    Any :    0 :      {}

            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=1, Index=pressure_bounds_disjuncts[1].constraint_index, Active=True
                    Key : Lower : Body : Upper : Active
                      1 :   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=pressure_bounds_disjuncts[1].propositions_index, Active=True
                    Key : Body : Active

            6 Declarations: indicator_var binary_indicator_var constraint_index constraint propositions_index propositions
        pressure_bounds_disjuncts[2] : Active=True
            2 Set Declarations
                constraint_index : Size=1, Index=None, Ordered=Insertion
                    Key  : Dimen : Domain : Size : Members
                    None :     1 :    Any :    1 :    {1,}
                propositions_index : Size=1, Index=None, Ordered=Insertion
                    Key  : Dimen : Domain : Size : Members
                    None :    -- :    Any :    0 :      {}

            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=1, Index=pressure_bounds_disjuncts[2].constraint_index, Active=True
                    Key : Lower : Body : Upper : Active
                      1 :  -Inf :    P :  30.0 :   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=pressure_bounds_disjuncts[2].propositions_index, Active=True
                    Key : Body : Active

            6 Declarations: indicator_var binary_indicator_var constraint_index constraint propositions_index propositions
        pressure_bounds_disjuncts[3] : Active=True
            2 Set Declarations
                constraint_index : Size=1, Index=None, Ordered=Insertion
                    Key  : Dimen : Domain : Size : Members
                    None :     1 :    Any :    1 :    {1,}
                propositions_index : Size=1, Index=None, Ordered=Insertion
                    Key  : Dimen : Domain : Size : Members
                    None :    -- :    Any :    0 :      {}

            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=1, Index=pressure_bounds_disjuncts[3].constraint_index, Active=True
                    Key : Lower : Body : Upper : Active
                      1 :  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=pressure_bounds_disjuncts[3].propositions_index, Active=True
                    Key : Body : Active

            6 Declarations: indicator_var binary_indicator_var constraint_index constraint propositions_index propositions

1 Disjunction Declarations
    pressure_bounds : Pressure bounds for different reactor selections
        Size=2, Index=reactors, Active=True
        Key : Disjuncts                                                        : Active : XOR
          1 : ['pressure_bounds_disjuncts[0]', 'pressure_bounds_disjuncts[1]'] :   True : True
          2 : ['pressure_bounds_disjuncts[2]', 'pressure_bounds_disjuncts[3]'] :   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()
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 Block Declarations
    _pyomo_gdp_bigm_reformulation : Size=1, Index=None, Active=True
        1 Constraint Declarations
            pressure_bounds_xor : Size=2, Index=Any, Active=True
                Key : Lower : Body                                                                                                  : Upper : Active
                  1 :   1.0 : pressure_bounds_disjuncts[0].binary_indicator_var + pressure_bounds_disjuncts[1].binary_indicator_var :   1.0 :   True
                  2 :   1.0 : pressure_bounds_disjuncts[2].binary_indicator_var + pressure_bounds_disjuncts[3].binary_indicator_var :   1.0 :   True

        1 Block Declarations
            relaxedDisjuncts : Size=4, Index=NonNegativeIntegers, Active=True
                _pyomo_gdp_bigm_reformulation.relaxedDisjuncts[0] : Active=True
                    1 Constraint Declarations
                        transformedConstraints : Size=1, Index=Any, Active=True
                            Key                          : Lower : Body                                                             : Upper : Active
                            ('constraint[1]_0', 1, 'ub') :  -Inf : P - 20.0*(1 - pressure_bounds_disjuncts[0].binary_indicator_var) :  10.0 :   True

                    1 Block Declarations
                        localVarReferences : Size=1, Index=None, Active=True
                            1 Var Declarations
                                binary_indicator_var : Size=1, Index=UnindexedComponent_ReferenceSet, ReferenceTo=pressure_bounds_disjuncts[0].binary_indicator_var
                                    Key  : Lower : Value : Upper : Fixed : Stale : Domain
                                    None :     0 :  None :     1 : False :  True : Binary

                            1 Declarations: binary_indicator_var

                    2 Declarations: transformedConstraints localVarReferences
                _pyomo_gdp_bigm_reformulation.relaxedDisjuncts[1] : Active=True
                    1 Constraint Declarations
                        transformedConstraints : Size=1, Index=Any, Active=True
                            Key                          : Lower : Body : Upper : Active
                            ('constraint[1]_0', 1, 'lb') :   5.0 :    P :  +Inf :   True

                    1 Block Declarations
                        localVarReferences : Size=1, Index=None, Active=True
                            1 Var Declarations
                                binary_indicator_var : Size=1, Index=UnindexedComponent_ReferenceSet, ReferenceTo=pressure_bounds_disjuncts[1].binary_indicator_var
                                    Key  : Lower : Value : Upper : Fixed : Stale : Domain
                                    None :     0 :  None :     1 : False :  True : Binary

                            1 Declarations: binary_indicator_var

                    2 Declarations: transformedConstraints localVarReferences
                _pyomo_gdp_bigm_reformulation.relaxedDisjuncts[2] : Active=True
                    1 Constraint Declarations
                        transformedConstraints : Size=1, Index=Any, Active=True
                            Key                          : Lower : Body : Upper : Active
                            ('constraint[1]_0', 1, 'ub') :  -Inf :    P :  30.0 :   True

                    1 Block Declarations
                        localVarReferences : Size=1, Index=None, Active=True
                            1 Var Declarations
                                binary_indicator_var : Size=1, Index=UnindexedComponent_ReferenceSet, ReferenceTo=pressure_bounds_disjuncts[2].binary_indicator_var
                                    Key  : Lower : Value : Upper : Fixed : Stale : Domain
                                    None :     0 :  None :     1 : False :  True : Binary

                            1 Declarations: binary_indicator_var

                    2 Declarations: transformedConstraints localVarReferences
                _pyomo_gdp_bigm_reformulation.relaxedDisjuncts[3] : Active=True
                    1 Constraint Declarations
                        transformedConstraints : Size=1, Index=Any, Active=True
                            Key                          : Lower : Body                                                             : Upper : Active
                            ('constraint[1]_0', 1, 'lb') :  20.0 : P + 15.0*(1 - pressure_bounds_disjuncts[3].binary_indicator_var) :  +Inf :   True

                    1 Block Declarations
                        localVarReferences : Size=1, Index=None, Active=True
                            1 Var Declarations
                                binary_indicator_var : Size=1, Index=UnindexedComponent_ReferenceSet, ReferenceTo=pressure_bounds_disjuncts[3].binary_indicator_var
                                    Key  : Lower : Value : Upper : Fixed : Stale : Domain
                                    None :     0 :  None :     1 : False :  True : Binary

                            1 Declarations: binary_indicator_var

                    2 Declarations: transformedConstraints localVarReferences

        2 Declarations: relaxedDisjuncts pressure_bounds_xor

1 Disjunct Declarations
    pressure_bounds_disjuncts : Size=4, Index=Any, Active=False
        pressure_bounds_disjuncts[0] : Active=False
            2 Set Declarations
                constraint_index : Size=1, Index=None, Ordered=Insertion
                    Key  : Dimen : Domain : Size : Members
                    None :     1 :    Any :    1 :    {1,}
                propositions_index : Size=1, Index=None, Ordered=Insertion
                    Key  : Dimen : Domain : Size : Members
                    None :    -- :    Any :    0 :      {}

            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=1, Index=pressure_bounds_disjuncts[0].constraint_index, Active=True
                    Key : Lower : Body : Upper : Active
                      1 :  -Inf :    P :  10.0 :  False

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

            1 LogicalConstraint Declarations
                propositions : Size=0, Index=pressure_bounds_disjuncts[0].propositions_index, Active=False
                    Key : Body : Active

            6 Declarations: indicator_var binary_indicator_var constraint_index constraint propositions_index propositions
        pressure_bounds_disjuncts[1] : Active=False
            2 Set Declarations
                constraint_index : Size=1, Index=None, Ordered=Insertion
                    Key  : Dimen : Domain : Size : Members
                    None :     1 :    Any :    1 :    {1,}
                propositions_index : Size=1, Index=None, Ordered=Insertion
                    Key  : Dimen : Domain : Size : Members
                    None :    -- :    Any :    0 :      {}

            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=1, Index=pressure_bounds_disjuncts[1].constraint_index, Active=True
                    Key : Lower : Body : Upper : Active
                      1 :   5.0 :    P :  +Inf :  False

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

            1 LogicalConstraint Declarations
                propositions : Size=0, Index=pressure_bounds_disjuncts[1].propositions_index, Active=False
                    Key : Body : Active

            6 Declarations: indicator_var binary_indicator_var constraint_index constraint propositions_index propositions
        pressure_bounds_disjuncts[2] : Active=False
            2 Set Declarations
                constraint_index : Size=1, Index=None, Ordered=Insertion
                    Key  : Dimen : Domain : Size : Members
                    None :     1 :    Any :    1 :    {1,}
                propositions_index : Size=1, Index=None, Ordered=Insertion
                    Key  : Dimen : Domain : Size : Members
                    None :    -- :    Any :    0 :      {}

            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=1, Index=pressure_bounds_disjuncts[2].constraint_index, Active=True
                    Key : Lower : Body : Upper : Active
                      1 :  -Inf :    P :  30.0 :  False

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

            1 LogicalConstraint Declarations
                propositions : Size=0, Index=pressure_bounds_disjuncts[2].propositions_index, Active=False
                    Key : Body : Active

            6 Declarations: indicator_var binary_indicator_var constraint_index constraint propositions_index propositions
        pressure_bounds_disjuncts[3] : Active=False
            2 Set Declarations
                constraint_index : Size=1, Index=None, Ordered=Insertion
                    Key  : Dimen : Domain : Size : Members
                    None :     1 :    Any :    1 :    {1,}
                propositions_index : Size=1, Index=None, Ordered=Insertion
                    Key  : Dimen : Domain : Size : Members
                    None :    -- :    Any :    0 :      {}

            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=1, Index=pressure_bounds_disjuncts[3].constraint_index, Active=True
                    Key : Lower : Body : Upper : Active
                      1 :  20.0 :    P :  +Inf :  False

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

            1 LogicalConstraint Declarations
                propositions : Size=0, Index=pressure_bounds_disjuncts[3].propositions_index, Active=False
                    Key : Body : Active

            6 Declarations: indicator_var binary_indicator_var constraint_index constraint propositions_index propositions

1 Disjunction Declarations
    pressure_bounds : Pressure bounds for different reactor selections
        Size=2, Index=reactors, Active=False
        Key : Disjuncts                                                        : Active : XOR
          1 : ['pressure_bounds_disjuncts[0]', 'pressure_bounds_disjuncts[1]'] :  False : True
          2 : ['pressure_bounds_disjuncts[2]', 'pressure_bounds_disjuncts[3]'] :  False : True

8 Declarations: reactors min_pressure max_pressure P objective pressure_bounds pressure_bounds_disjuncts _pyomo_gdp_bigm_reformulation

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

# Solve and print the solution
SolverFactory(milp_solver).solve(model, tee=True)

model.P.display()
Welcome to the CBC MILP Solver 
Version: 2.10.8 
Build Date: Feb  3 2023 

command line - /content/cbc -printingOptions all -import /tmp/tmpao0wxkyx.pyomo.lp -stat=1 -solve -solu /tmp/tmpao0wxkyx.pyomo.soln (default strategy 1)
Option for printingOptions changed from normal to all
Presolve 0 (-7) rows, 0 (-6) columns and 0 (-11) elements
Statistics for presolved model
Original problem has 4 integers (4 of which binary)


Problem has 0 rows, 0 columns (0 with objective) and 0 elements
There are 21915 singletons with no objective 
Column breakdown:
0 of type 0.0->inf, 0 of type 0.0->up, 0 of type lo->inf, 
0 of type lo->up, 0 of type free, 0 of type fixed, 
0 of type -inf->0.0, 0 of type -inf->up, 0 of type 0.0->1.0 
Row breakdown:
0 of type E 0.0, 0 of type E 1.0, 0 of type E -1.0, 
0 of type E other, 0 of type G 0.0, 0 of type G 1.0, 
0 of type G other, 0 of type L 0.0, 0 of type L 1.0, 
0 of type L other, 0 of type Range 0.0->1.0, 0 of type Range other, 
0 of type Free 
Continuous objective value is 5 - 0.00 seconds
Cgl0004I processed model has 0 rows, 0 columns (0 integer (0 of which binary)) and 0 elements
Cbc3007W No integer variables - nothing to do
Cuts at root node changed objective from 5 to -1.79769e+308
Probing was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
Gomory was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
Knapsack was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
Clique was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
MixedIntegerRounding2 was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
FlowCover was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
TwoMirCuts was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
ZeroHalf was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)

Result - Optimal solution found

Objective value:                5.00000000
Enumerated nodes:               0
Total iterations:               0
Time (CPU seconds):             0.00
Time (Wallclock seconds):       0.00

Total time (CPU seconds):       0.00   (Wallclock seconds):       0.00

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

2.1.5.6. Transform and Solve with Convex Hull Relaxation#

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

Convex hull can be used if we don’t want to implement Big-M parameters. This approach requires separating the continuous variables into its components.

2.1.5.7. Convex Hull Relaxation Approach#

General Notation:

(2.12)#\[\begin{equation} x = \sum_{i \in D} z_{i} \end{equation}\]
(2.13)#\[\begin{equation} A_{i} z_{i} \leq b_{i}y_{i} \ , \ \forall i \in D \end{equation}\]
(2.14)#\[\begin{equation} \sum_{i \in D} y_{i} = 1 \end{equation}\]
(2.15)#\[\begin{equation} 0 \leq z_{i} \leq Uy_{i} \ , \ \forall i \in D \end{equation}\]
(2.16)#\[\begin{equation} y_{i} = \{0,1) \ , \ \forall i \in D \end{equation}\]

\(z_{i}\): continuous variables separated into as many new variables as there are terms for the disjunctions.

Applied to the Reactor Problem:

(2.17)#\[\begin{equation} P = P_{1} + P_{2} \\ P_{1} \leq 10y_{1}\\ P_{2} \leq 30y_{2} \\ -P_{1} \leq -5y_{1}\\ -P_{2} \leq -20y_{2}\\ y_{1} + y_{2} = 1 \end{equation}\]

Key Takeaways:

(+) Constraints do not require Big-M parameters which produce a tight linear programming relaxation.
(–) A larger number of variables and constraints is required.

Convex hull is better to use over Big-M if the problem is large.

2.1.5.8. 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
SolverFactory(milp_solver).solve(model, tee=True)

model.P.display()
Welcome to the CBC MILP Solver 
Version: 2.10.8 
Build Date: Feb  3 2023 

command line - /content/cbc -printingOptions all -import /tmp/tmpqnfap997.pyomo.lp -stat=1 -solve -solu /tmp/tmpqnfap997.pyomo.soln (default strategy 1)
Option for printingOptions changed from normal to all
Presolve 10 (-7) rows, 5 (-5) columns and 24 (-11) elements
Statistics for presolved model
Original problem has 4 integers (4 of which binary)
Presolved problem has 2 integers (2 of which binary)
==== 4 zero objective 2 different
4 variables have objective of 0
1 variables have objective of 1
==== absolute objective values 2 different
4 variables have objective of 0
1 variables have objective of 1
==== for integers 2 zero objective 1 different
2 variables have objective of 0
==== for integers absolute objective values 1 different
2 variables have objective of 0
===== end objective counts


Problem has 10 rows, 5 columns (1 with objective) and 24 elements
Column breakdown:
0 of type 0.0->inf, 2 of type 0.0->up, 0 of type lo->inf, 
1 of type lo->up, 0 of type free, 0 of type fixed, 
0 of type -inf->0.0, 0 of type -inf->up, 2 of type 0.0->1.0 
Row breakdown:
0 of type E 0.0, 0 of type E 1.0, 0 of type E -1.0, 
0 of type E other, 0 of type G 0.0, 0 of type G 1.0, 
0 of type G other, 5 of type L 0.0, 0 of type L 1.0, 
5 of type L other, 0 of type Range 0.0->1.0, 0 of type Range other, 
0 of type Free 
Continuous objective value is 5 - 0.00 seconds
Cgl0003I 0 fixed, 0 tightened bounds, 1 strengthened rows, 0 substitutions
Cgl0003I 0 fixed, 0 tightened bounds, 1 strengthened rows, 0 substitutions
Cgl0004I processed model has 8 rows, 5 columns (2 integer (2 of which binary)) and 21 elements
Cbc0038I Initial state - 0 integers unsatisfied sum - 0
Cbc0038I Solution found of 5
Cbc0038I Relaxing continuous gives 5
Cbc0038I Before mini branch and bound, 2 integers at bound fixed and 3 continuous
Cbc0038I Mini branch and bound did not improve solution (0.00 seconds)
Cbc0038I After 0.00 seconds - Feasibility pump exiting with objective of 5 - took 0.00 seconds
Cbc0012I Integer solution of 5 found by feasibility pump after 0 iterations and 0 nodes (0.00 seconds)
Cbc0001I Search completed - best objective 5, took 0 iterations and 0 nodes (0.00 seconds)
Cbc0035I Maximum depth 0, 0 variables fixed on reduced cost
Cuts at root node changed objective from 5 to 5
Probing was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
Gomory was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
Knapsack was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
Clique was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
MixedIntegerRounding2 was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
FlowCover was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
TwoMirCuts was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
ZeroHalf was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)

Result - Optimal solution found

Objective value:                5.00000000
Enumerated nodes:               0
Total iterations:               0
Time (CPU seconds):             0.00
Time (Wallclock seconds):       0.01

Total time (CPU seconds):       0.01   (Wallclock seconds):       0.01

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

2.1.6. Discussion Questions#

  1. How do we create a system to go from logical expressions to linear constraints?

  2. Are conjuctive or disjunctive clauses more common? Why might this be the case?

  3. If \(y_{i}=0\) and \(y_{i}=1\), what happens when a Big-M parameter is introduced in the general notation?

  4. When will the convex hull formulation simplify?

# Discussion Questions
# Add your solution here
'\nAnswers:\n1. Using conjuctive normal form. \n2. Disjunctive clauses because the clause is true when at least one literal is true which occurs more often.\n3. The inequality becomes unnecessary when $y_{i}=0$ and the inequality is applied when $y_{i}=1$.\n4. If the disjunction only has two terms and one of the terms requires the variable to take a value at 0.\n'