Linearize Pyomo model with BigM Constraint

Question:

I may have answered my own question posted below with the following.

I believe the construction is now appropriately linear and returns a feasible solution. Though I would appreciate any feedback as I am new to this.

In replacement of the model.bigM_cons I outlined below, I have now implemented the following which creates a new variable (model.new) to represent the product of model.auction*model.volume

Relevant constraints are then applied to model.new and the new variable is also used in the decision function:

New BigM Constraint

model.m = Param(initialize = 100)
model.new = Var(model.WEEK_PROD_flat, within = NonNegativeIntegers)

# force new to be 0 if auction is 0
# force new to be between 0 and M if auction is 1
def c1_rule(model,w,p):
    return model.new[w,p] <= model.auction[w,p]*model.m
model.c1 = Constraint(model.WEEK_PROD_flat, rule = c1_rule)

# force new to always be <= volume
def c2_rule(model,w,p):
    return(model.new[w,p] <= model.volume[w,p])
model.c2 = Constraint(model.WEEK_PROD_flat, rule = c2_rule)

# force new to equal volume if auction = 1
def c3_rule(model,w,p):
    return model.new[w,p] >= model.volume[w,p]-(1-model.auction[w,p])*model.m
model.c3 = Constraint(model.WEEK_PROD_flat, rule = c3_rule)

Original Post
I am constructing a model that involves 2 decision variables indexed over a sparse set of (week, product) indices (model.WEEK_PROD_flat):

model.volume is an non-negative integer variable that quantifies the amount of product to be sold.
model.auction is a binary variable that indicates if a particular (week, product) sale should occur.

As it stands, my problem is non-linear as several constraints and the objective function involve the product of model.auction * model.volume. Effectively, model.auction is being used to turn on/off model.volume.

I understand that I need to create a bigM constraint to avoid the multiplication of decision variables and to ensure that my problem formulation is linear.

I am trying to formulate a constraint that forces model.volume to equal zero if model.auction is equal to zero and assign a value to model.volume only if model.auction is equal to 1.

My current formulation of this constraint looks like this:

model.bigM = Param(initialize = 100) # define bigM value

def bigM_rule(model,w,p):
    return model.volume[w,p] <= model.auction[w,p] * model.bigM

model.bigM_cons = Constraint(model.WEEK_PROD_flat, rule = bigM_rule)  

When applied in a simple MWE (below), things seem to function correctly. However, when the new constraint is incorporated into my complete model, an infeasible solution is returned.

It should be noted that this complete model, when utilising the non-linear constraint and objective function DID reach a feasible solution (though I am aware this is unlikely to be a global max). The infeasible output is only returned when applying the bigM constraint and so leads me to believe that I may formulated it incorrectly?

I would appreciate some insight into where I may have gone wrong here.

Complete MWE

weekly_products = {
    1: ['Q24'],
    2: ['Q24', 'J24'],
    3: ['Q24', 'J24','F24'],
    4: ['J24', 'F24'],
    5: ['F24']
}

product_weeks = {'Q24': [1, 2, 3],
                 'J24': [2, 3, 4],
                 'F24': [3, 4, 5]}

prices = {(1, 'Q24'):43.42,
      (2, 'Q24'):43.73,
      (2, 'J24'):24.89,
      (3, 'Q24'):44.03,
      (3, 'J24'):25.54,
      (3, 'F24'):43.10,
      (4, 'J24'):26.15,
      (4, 'F24'):43.45,
      (5, 'F24'):43.77}

from pyomo.environ import *
model = ConcreteModel()

# define Sets
model.WEEKS = Set(initialize = [1,2,3,4,5])
model.PRODS = Set(initialize = ['Q24','J24','F24'])
model.WEEK_PROD = Set(model.WEEKS, initialize=weekly_products)
model.WEEK_PROD_flat = Set(initialize=[(w, p) for w in model.WEEKS for p in model.WEEK_PROD[w]])
model.PROD_WEEK = Set(model.PRODS, initialize = product_weeks)

# deine Vars
model.volume = Var(model.WEEK_PROD_flat, within = NonNegativeIntegers, bounds = (0,60))
model.auction = Var(model.WEEK_PROD_flat, within = Binary)

# Define Params
model.price = Param(model.WEEK_PROD_flat, initialize = prices)
model.weekMax = Param(initialize = 1)
model.prodMax = Param(initialize = 3)
model.bigM = Param(initialize = 100)

# Define Cons
def weekMax_rule(model,i):
    return sum(model.auction[i,j] for j in model.WEEK_PROD[i]) <= model.weekMax
model.weekMax_const = Constraint(model.WEEKS, rule = weekMax_rule)

def prodMax_rule(model,j):
    return sum(model.auction[i,j] for i in model.PROD_WEEK[j]) <=model.prodMax
model.prodMax_const = Constraint(model.PRODS, rule = prodMax_rule)

def bigM_rule(model,w,p):
    return model.volume[w,p] <= model.auction[w,p] * model.bigM
model.bigM_cons= Constraint(model.WEEK_PROD_flat, rule = bigM_rule)

# Objective function
def objective_rule(model):
    return sum(model.volume[w,p] * model.price[w,p] for p in model.PRODS for w in model.PROD_WEEK[p])
model.maximiseRev = Objective(rule = objective_rule, sense = maximize)

optimizer = SolverFactory('scip')
results = optimizer.solve(model)
model.display()
Asked By: r0bt

||

Answers:

In replacement of the model.bigM_cons I outlined below, I have now implemented the following which creates a new variable (model.new) to represent the product of model.auction*model.volume

Relevant constraints are then applied to model.new and the new variable is also used in the decision function:

New BigM Constraint

model.m = Param(initialize = 100)
model.new = Var(model.WEEK_PROD_flat, within = NonNegativeIntegers)

# force new to be 0 if auction is 0
# force new to be between 0 and M if auction is 1
def c1_rule(model,w,p):
    return model.new[w,p] <= model.auction[w,p]*model.m
model.c1 = Constraint(model.WEEK_PROD_flat, rule = c1_rule)

# force new to always be <= volume
def c2_rule(model,w,p):
    return(model.new[w,p] <= model.volume[w,p])
model.c2 = Constraint(model.WEEK_PROD_flat, rule = c2_rule)

# force new to equal volume if auction = 1
def c3_rule(model,w,p):
    return model.new[w,p] >= model.volume[w,p]-(1-model.auction[w,p])*model.m
model.c3 = Constraint(model.WEEK_PROD_flat, rule = c3_rule)
Answered By: r0bt
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.