or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-extensions.mdcore-modeling.mddae.mddata-management.mddomain-sets.mdgdp.mdindex.mdmathematical-functions.mdmpec.mdoptimization-interface.md

gdp.mddocs/

0

# Generalized Disjunctive Programming

1

2

Components for modeling logical relationships and disjunctive constraints in optimization problems. GDP enables the formulation of problems with logical decision-making and alternative process paths through disjuncts and disjunctions.

3

4

## Capabilities

5

6

### Disjunct Components

7

8

Components for defining disjunctive blocks containing variables, constraints, and logical relationships.

9

10

```python { .api }

11

class Disjunct:

12

"""

13

Disjunct component for GDP modeling.

14

15

A disjunct represents a logical block that is either active or inactive.

16

When active, all constraints within the disjunct must be satisfied.

17

"""

18

def __init__(self, *args, **kwargs): ...

19

20

def activate(self):

21

"""Activate this disjunct."""

22

23

def deactivate(self):

24

"""Deactivate this disjunct."""

25

26

def is_active(self):

27

"""Check if disjunct is active."""

28

29

@property

30

def indicator_var(self):

31

"""Get the binary indicator variable for this disjunct."""

32

33

class DisjunctData:

34

"""

35

Data container for disjunct components.

36

37

Stores the actual constraint and variable data associated with

38

a specific disjunct instance.

39

"""

40

def __init__(self, component=None): ...

41

42

def activate(self): ...

43

def deactivate(self): ...

44

def is_active(self): ...

45

```

46

47

### Disjunction Components

48

49

Components for defining logical disjunctions (OR relationships) between disjuncts.

50

51

```python { .api }

52

class Disjunction:

53

"""

54

Disjunction component representing logical OR relationships.

55

56

A disjunction requires that exactly one of its constituent

57

disjuncts must be active (true).

58

"""

59

def __init__(self, *args, **kwargs): ...

60

61

def activate(self):

62

"""Activate this disjunction."""

63

64

def deactivate(self):

65

"""Deactivate this disjunction."""

66

67

def is_active(self):

68

"""Check if disjunction is active."""

69

70

class DisjunctionData:

71

"""

72

Data container for disjunction components.

73

74

Stores the disjunct references and logical relationships

75

for a specific disjunction instance.

76

"""

77

def __init__(self, component=None): ...

78

79

def activate(self): ...

80

def deactivate(self): ...

81

def is_active(self): ...

82

```

83

84

### GDP Error Handling

85

86

Exception class for GDP-specific errors and validation issues.

87

88

```python { .api }

89

class GDP_Error(Exception):

90

"""GDP-specific error class for logical modeling issues."""

91

def __init__(self, message): ...

92

```

93

94

## Usage Examples

95

96

### Basic Disjunctive Model

97

98

```python

99

from pyomo.environ import *

100

from pyomo.gdp import Disjunct, Disjunction

101

102

model = ConcreteModel()

103

104

# Decision variables

105

model.x = Var(bounds=(0, 10))

106

model.y = Var(bounds=(0, 10))

107

108

# Create disjuncts for alternative process paths

109

model.path1 = Disjunct()

110

model.path2 = Disjunct()

111

112

# Path 1: High-temperature, low-pressure process

113

model.path1.temp_constraint = Constraint(expr=model.x >= 8)

114

model.path1.pressure_constraint = Constraint(expr=model.y <= 3)

115

model.path1.cost = Var(bounds=(0, 100))

116

model.path1.cost_def = Constraint(expr=model.path1.cost == 2*model.x + model.y)

117

118

# Path 2: Low-temperature, high-pressure process

119

model.path2.temp_constraint = Constraint(expr=model.x <= 5)

120

model.path2.pressure_constraint = Constraint(expr=model.y >= 6)

121

model.path2.cost = Var(bounds=(0, 100))

122

model.path2.cost_def = Constraint(expr=model.path2.cost == model.x + 3*model.y)

123

124

# Disjunction: exactly one path must be chosen

125

model.path_choice = Disjunction(expr=[model.path1, model.path2])

126

127

# Objective: minimize total cost

128

model.obj = Objective(

129

expr=model.path1.cost + model.path2.cost,

130

sense=minimize

131

)

132

```

133

134

### Indexed Disjuncts and Disjunctions

135

136

```python

137

from pyomo.environ import *

138

from pyomo.gdp import Disjunct, Disjunction

139

140

model = ConcreteModel()

141

142

# Sets for indexing

143

model.UNITS = Set(initialize=[1, 2, 3])

144

model.MODES = Set(initialize=['low', 'med', 'high'])

145

146

# Variables

147

model.flow = Var(model.UNITS, bounds=(0, 100))

148

model.operating = Var(model.UNITS, domain=Binary)

149

150

# Indexed disjuncts for operating modes

151

model.mode_disjunct = Disjunct(model.UNITS, model.MODES)

152

153

# Define constraints for each mode

154

for unit in model.UNITS:

155

# Low mode: 0-30% capacity

156

model.mode_disjunct[unit, 'low'].flow_lower = Constraint(

157

expr=model.flow[unit] >= 0

158

)

159

model.mode_disjunct[unit, 'low'].flow_upper = Constraint(

160

expr=model.flow[unit] <= 30

161

)

162

163

# Medium mode: 30-70% capacity

164

model.mode_disjunct[unit, 'med'].flow_lower = Constraint(

165

expr=model.flow[unit] >= 30

166

)

167

model.mode_disjunct[unit, 'med'].flow_upper = Constraint(

168

expr=model.flow[unit] <= 70

169

)

170

171

# High mode: 70-100% capacity

172

model.mode_disjunct[unit, 'high'].flow_lower = Constraint(

173

expr=model.flow[unit] >= 70

174

)

175

model.mode_disjunct[unit, 'high'].flow_upper = Constraint(

176

expr=model.flow[unit] <= 100

177

)

178

179

# Each unit must operate in exactly one mode

180

def mode_disjunction_rule(model, unit):

181

return [model.mode_disjunct[unit, mode] for mode in model.MODES]

182

183

model.mode_choice = Disjunction(model.UNITS, rule=mode_disjunction_rule)

184

```

185

186

### Hierarchical Disjunctions

187

188

```python

189

from pyomo.environ import *

190

from pyomo.gdp import Disjunct, Disjunction

191

192

model = ConcreteModel()

193

194

# Variables

195

model.x = Var(bounds=(0, 20))

196

model.y = Var(bounds=(0, 20))

197

198

# Top-level choice: Process A or Process B

199

model.process_A = Disjunct()

200

model.process_B = Disjunct()

201

202

# Within Process A: two sub-options

203

model.process_A.option_A1 = Disjunct()

204

model.process_A.option_A2 = Disjunct()

205

206

# Process A, Option 1

207

model.process_A.option_A1.con1 = Constraint(expr=model.x <= 8)

208

model.process_A.option_A1.con2 = Constraint(expr=model.y >= 5)

209

210

# Process A, Option 2

211

model.process_A.option_A2.con1 = Constraint(expr=model.x >= 12)

212

model.process_A.option_A2.con2 = Constraint(expr=model.y <= 10)

213

214

# Process A must choose one sub-option

215

model.process_A.sub_choice = Disjunction(

216

expr=[model.process_A.option_A1, model.process_A.option_A2]

217

)

218

219

# Process B (single option)

220

model.process_B.con1 = Constraint(expr=model.x + model.y <= 15)

221

model.process_B.con2 = Constraint(expr=model.x - model.y >= 2)

222

223

# Top-level disjunction

224

model.main_choice = Disjunction(expr=[model.process_A, model.process_B])

225

226

# Objective

227

model.obj = Objective(expr=model.x + model.y, sense=maximize)

228

```

229

230

### Solving GDP Models

231

232

```python

233

from pyomo.environ import *

234

from pyomo.gdp import Disjunct, Disjunction

235

import pyomo.contrib.gdpopt as gdpopt

236

237

# Create GDP model (using previous example)

238

model = create_gdp_model() # Your GDP model creation function

239

240

# Method 1: Use GDP-specific solver

241

solver = SolverFactory('gdpopt')

242

results = solver.solve(model, tee=True)

243

244

# Method 2: Transform to MILP and solve

245

from pyomo.gdp import TransformationFactory

246

247

# Big-M transformation

248

bigm = TransformationFactory('gdp.bigm')

249

bigm.apply_to(model)

250

251

# Solve transformed model

252

milp_solver = SolverFactory('gurobi') # or 'cplex', 'glpk', etc.

253

results = milp_solver.solve(model, tee=True)

254

255

# Method 3: Hull reformulation

256

model_hull = model.clone()

257

hull = TransformationFactory('gdp.hull')

258

hull.apply_to(model_hull)

259

results = milp_solver.solve(model_hull, tee=True)

260

```

261

262

### Accessing Solution Information

263

264

```python

265

from pyomo.environ import *

266

from pyomo.gdp import Disjunct, Disjunction

267

268

# After solving GDP model

269

if results.solver.termination_condition == TerminationCondition.optimal:

270

print("Solution found!")

271

272

# Check which disjuncts are active

273

for disjunct in [model.path1, model.path2]:

274

if disjunct.indicator_var.value >= 0.5: # Binary variable ≈ 1

275

print(f"{disjunct.name} is active")

276

277

# Access variables within active disjunct

278

if hasattr(disjunct, 'cost'):

279

print(f" Cost: {value(disjunct.cost)}")

280

281

# Access main variables

282

print(f"x = {value(model.x)}")

283

print(f"y = {value(model.y)}")

284

print(f"Objective = {value(model.obj)}")

285

```

286

287

### Conditional Constraints with GDP

288

289

```python

290

from pyomo.environ import *

291

from pyomo.gdp import Disjunct, Disjunction

292

293

model = ConcreteModel()

294

295

# Variables

296

model.x = Var(bounds=(0, 100))

297

model.setup_cost = Var(bounds=(0, 1000))

298

model.operating_cost = Var(bounds=(0, 500))

299

300

# Disjunct for "facility not built"

301

model.not_built = Disjunct()

302

model.not_built.no_production = Constraint(expr=model.x == 0)

303

model.not_built.no_setup = Constraint(expr=model.setup_cost == 0)

304

model.not_built.no_operating = Constraint(expr=model.operating_cost == 0)

305

306

# Disjunct for "facility built"

307

model.built = Disjunct()

308

model.built.min_production = Constraint(expr=model.x >= 20) # Minimum viable scale

309

model.built.setup_cost_def = Constraint(expr=model.setup_cost == 200)

310

model.built.operating_cost_def = Constraint(expr=model.operating_cost == 5 * model.x)

311

312

# Either build or don't build

313

model.build_decision = Disjunction(expr=[model.not_built, model.built])

314

315

# Objective: maximize profit (revenue - costs)

316

model.revenue = Expression(expr=10 * model.x)

317

model.total_cost = Expression(expr=model.setup_cost + model.operating_cost)

318

model.obj = Objective(expr=model.revenue - model.total_cost, sense=maximize)

319

```