4

I am learning to build optimization models through Gurobi python and I am having some issues finding the pythonic way of defining decision variables and constraints:

Assuming I have these sets:

time={morning, afternoon, evening};
interval={early,late};
food={burger, banana, apple, orange};

and my decision variable is binary eat[time,interval,food]. However I only have a defined set of possible options as below and cannot enumerate all elements of my sets:

time      interval  food  number  value
morning   early   banana   2      500
morning   early   apple    3      600
afternoon early   burger   1      800
evening    late   orane    2      400

so my eat variables can only be the following:

eat[morning,early,banana]
eat[morning,early,apple]
eat[afternoon,early,burger]
eat[evening,late,orange]

and I cannot do:

eat = m.addVars(time, interval, food, name = "Eat", vtype=GRB.BINARY)

I can do something like:

eat = {}
for row in input.to_dict('records'):
         key = (row['time'], row['interval'],row['food'])
         eat[key] =  m.addVar(name = "Eat", vtype=GRB.BINARY)

But I still have trouble defining my objective which is multiplying number and value and eat and I am looking for a more consistent, elegant way:

obj = quicksum(number[i,j,k]*value[i, j, k] * eat[i, j, k] for i in time 
for j in interval for k in food)

The above will enumerate all which is wrong and I tried something like this:

obj = quicksum(number[key]*value[key] * eat[key] \
                 for key in eat)

which limits it to only defined combinations in the dictionary but then I am struggling with constraints when I have to separate elements of dictionary like below:

m.addConstrs(quicksum(eat[i,j,k] for k in food)==1 for i in time for j in interval)

or something like

m.addConstrs(quicksum(eat[morning,j,banana] ==1) for j in interval)

Sorry for the long questions. Any help from optimization/python experts would be great.

Nazanin Zinouri
  • 229
  • 3
  • 9

1 Answers1

3

It may help you to make use of the tupledict structure the Gurobi Python API has to store the variables. It has some convenient methods which allow you to sum, multiply or slice variables easily. I provide a complete example below.

from gurobipy import GRB, Model
import numpy as np

tuples = [('morning', 'early', 'banana'),('morning', 'early', 'apple'), 
('afternoon', 'early', 'burger'), ('evening', 'late', 'orane')]

numbers, values = [2, 3, 1, 2], [500, 600, 800, 400]

m = Model('SO52451928')

eat = m.addVars(tuples, name='eat', vtype=GRB.BINARY)

coeffs = np.array(numbers) * np.array(values) # Can be made with regular lists as well
coeffs = dict(zip(tuples, coeffs))

obj = eat.prod(coeffs)
m.setObjective(obj)

# This structure holds the unique combinations of (time, interval) that 
# appear in the data. They are necessary, because they form the set over which
# our constraints are defined
time_intervals = set(zip(*zip(*tuples)[:2]))

constrs = m.addConstrs((
    eat.sum(i, j, '*') == 1 for i, j in time_intervals), name='one_food')

m.write(m.ModelName+'.lp')
m.optimize()

if m.SolCount > 0:
    print(zip(m.getAttr(
    'VarName', m.getVars()), m.getAttr('x', m.getVars())))

I hope this helps!

Ioannis
  • 5,238
  • 2
  • 19
  • 31
  • 1
    Thank you. This was great help. – Nazanin Zinouri Sep 22 '18 at 16:47
  • how would you address constraints like these `m.addConstrs(quicksum(eat[morning,j,banana] ==1) for j in interval)`, creating another structure like time_intervals? – Nazanin Zinouri Sep 23 '18 at 02:40
  • 1
    If you would like to express that for each time and food combination you need exactly one interval then yes, it is necessary to extract the unique time x food combinations. It becomes somewhat more verbose though: `set(zip(zip(*tuples)[0], zip(*tuples)[2]))` – Ioannis Sep 23 '18 at 12:24