I have a Pyomo model where I want to formulate constraints:
[Model.portrait.w, Model.portrait.l] OR [Model.landscape.w, Model.landscape.l]
I can use BigM constraints to achieve this, but I want to try using Pyomo Generalized Disjunctive Programming (GDP) as an alternative.
Code:
import pyomo.environ as pyo
import pyomo.gdp as gdp
def DefineModel(Model):
Model.Select = pyo.Var(Model.Candidate, domain = pyo.Binary)
Model.Allocation = pyo.Var(Model.Item, Model.Candidate, within = pyo.Binary, initialize = 0)
Model.portrait = gdp.Disjunct(Model.Item, Model.Candidate)
Model.landscape = gdp.Disjunct(Model.Item, Model.Candidate)
def rule_LBWidth1(Model, i):
return sum(Model.Allocation[i, c] * Model.CandidateWidth[c] for c in Model.Candidate) >= Model.Width[i]
Model.portrait.w = pyo.Constraint(Model.Item, rule = rule_LBWidth1)
def rule_LBLength1(Model, i):
return sum(Model.Allocation[i, c] * Model.CandidateLength[c] for c in Model.Candidate) >= Model.Length[i]
Model.portrait.l = pyo.Constraint(Model.Item, rule = rule_LBLength1)
def rule_LBWidth2(Model, i):
return sum(Model.Allocation[i, c] * Model.CandidateWidth[c] for c in Model.Candidate) >= Model.Length[i]
Model.landscape.w = pyo.Constraint(Model.Item, rule = rule_LBWidth2)
def rule_LBLength2(Model, i):
return sum(Model.Allocation[i, c] * Model.CandidateLength[c] for c in Model.Candidate) >= Model.Width[i]
Model.landscape.l = pyo.Constraint(Model.Item, rule = rule_LBLength2)
Model.rotate = gdp.Disjunction(expr = [Model.portrait, Model.landscape])
Error message:
ERROR: Constructing component 'rotate' from data=None failed: ValueError: Unexpected term for Disjunction rotate. Expected a Disjunct object, relational expression, or iterable of relational expressions but got <class 'tuple'> in <class 'pyomo.gdp.disjunct.IndexedDisjunct'>
The code follows the structure of the examples at https://pyomo.readthedocs.io/en/latest/modeling_extensions/gdp/modeling.html#disjunctions, except that it uses indexed functions - which seems to be a problem. I'd appreciate suggestions for how to fix the code.
-- EDIT with solution --
def DefineModel(Model):
Model.Select = pyo.Var(Model.Candidate, domain = pyo.Binary)
Model.Allocation = pyo.Var(Model.Item, Model.Candidate, within = pyo.Binary, initialize = 0)
def portrait_rule(d, i): # Original width|length order, as specified in the data
d.w = pyo.Constraint(expr=sum(Model.Allocation[i, c] * Model.CandidateWidth[c] for c in Model.Candidate) >= Model.Width[i])
d.l = pyo.Constraint(expr=sum(Model.Allocation[i, c] * Model.CandidateLength[c] for c in Model.Candidate) >= Model.Length[i])
Model.portrait = gdp.Disjunct(Model.Item, rule = portrait_rule)
def landscape_rule(d, i): # Rotated width|length order
d.w = pyo.Constraint(expr=sum(Model.Allocation[i, c] * Model.CandidateWidth[c] for c in Model.Candidate) >= Model.Length[i])
d.l = pyo.Constraint(expr=sum(Model.Allocation[i, c] * Model.CandidateLength[c] for c in Model.Candidate) >= Model.Width[i])
Model.landscape = gdp.Disjunct(Model.Item, rule = landscape_rule)
def rotate_rule(Model, i): # Use either portrait or landscape orientation for each item
return [Model.portrait[i], Model.landscape[i]]
Model.rotate = gdp.Disjunction(Model.Item, rule=rotate_rule)
# ... other constraints and objective function...
pyo.TransformationFactory('gdp.bigm').apply_to(Model) # Transform the disjunction rules into a form that the solver can work with