I am currently writing code in python to solve a workforce planning problem. Now, I have some problems with implementing the concept of worker productivity in this problem. I express the probability in a number from one to 100 (so that it is an integer, instead of a continuous variable). Still when I try to run my program, following error message is seen: "GurobiError: Unable to retrieve attribute 'X'". In my opinion the problem takes place in the first constraint. Below you find the code.
#define sets
periods = ("Period1", "Period2", "Period3", "Period4", "Period5", "Period6", "Period7")
skillPositions = ("SP1", "SP2", "SP3", "SP4", "SP5", "SP6", "SP7", "SP8")
tasks = ("T1", "T2", "T3", "T4", "T5", "T6", "T7", "T8", "T9", "T10", "T11", "T12", "T13", "T14", "T15")
#define parameters
initialProductivity = {"SP1": {"T1":0, "T2":80,"T3":0, "T4":0, "T5":0, "T6":0, "T7":70, "T8":0, "T9":0, "T10":0, "T11":0, "T12":0, "T13":0, "T14":0, "T15":0},
"SP2": {"T1":0, "T2":0,"T3":0, "T4":0, "T5":80, "T6":0, "T7":0, "T8":0, "T9":70, "T10":0, "T11":0, "T12":0, "T13":0, "T14":0, "T15":0},
"SP3": {"T1":90, "T2":0,"T3":0, "T4":0, "T5":0, "T6":0, "T7":0, "T8":0, "T9":0, "T10":0, "T11":0, "T12":0, "T13":60, "T14":0, "T15":0},
"SP4": {"T1":0, "T2":0,"T3":0, "T4":0, "T5":0, "T6":90, "T7":0, "T8":0, "T9":0, "T10":0, "T11":0, "T12":0, "T13":0, "T14":0, "T15":0},
"SP5": {"T1":0, "T2":0,"T3":60, "T4":0, "T5":0, "T6":0, "T7":0, "T8":70, "T9":0, "T10":0, "T11":0, "T12":0, "T13":0, "T14":0, "T15":0},
"SP6": {"T1":0, "T2":0,"T3":0, "T4":0, "T5":0, "T6":0, "T7":0, "T8":0, "T9":0, "T10":80, "T11":0, "T12":0, "T13":0, "T14":0, "T15":80},
"SP7": {"T1":0, "T2":0,"T3":0, "T4":70, "T5":0, "T6":0, "T7":0, "T8":0, "T9":0, "T10":0, "T11":0, "T12":0, "T13":0, "T14":80, "T15":0},
"SP8": {"T1":0, "T2":0,"T3":0, "T4":0, "T5":0, "T6":0, "T7":0, "T8":0, "T9":0, "T10":0, "T11":70, "T12":60, "T13":0, "T14":0, "T15":0}}
learningParameter = {"SP1": {"T1":0, "T2":5,"T3":0, "T4":0, "T5":0, "T6":0, "T7":4, "T8":0, "T9":0, "T10":0, "T11":0, "T12":0, "T13":0, "T14":0, "T15":0},
"SP2": {"T1":0, "T2":0,"T3":0, "T4":0, "T5":7, "T6":0, "T7":0, "T8":0, "T9":5, "T10":0, "T11":0, "T12":0, "T13":0, "T14":0, "T15":0},
"SP3": {"T1":1, "T2":0,"T3":0, "T4":0, "T5":0, "T6":0, "T7":0, "T8":0, "T9":0, "T10":0, "T11":0, "T12":0, "T13":2, "T14":0, "T15":0},
"SP4": {"T1":0, "T2":0,"T3":0, "T4":0, "T5":0, "T6":6, "T7":0, "T8":0, "T9":0, "T10":0, "T11":0, "T12":0, "T13":0, "T14":0, "T15":0},
"SP5": {"T1":0, "T2":0,"T3":6, "T4":0, "T5":0, "T6":0, "T7":0, "T8":7, "T9":0, "T10":0, "T11":0, "T12":0, "T13":0, "T14":0, "T15":0},
"SP6": {"T1":0, "T2":0,"T3":0, "T4":0, "T5":0, "T6":0, "T7":0, "T8":0, "T9":0, "T10":5, "T11":0, "T12":0, "T13":0, "T14":0, "T15":5},
"SP7": {"T1":0, "T2":0,"T3":0, "T4":6, "T5":0, "T6":0, "T7":0, "T8":0, "T9":0, "T10":0, "T11":0, "T12":0, "T13":0, "T14":6, "T15":0},
"SP8": {"T1":0, "T2":0,"T3":0, "T4":0, "T5":0, "T6":0, "T7":0, "T8":0, "T9":0, "T10":0, "T11":2, "T12":2, "T13":0, "T14":0, "T15":0}}
demand = {"Period1": {"T1":3, "T2":1386,"T3":1169, "T4":650, "T5":2923, "T6":693, "T7":4482, "T8":130, "T9":346, "T10":87, "T11":346, "T12":346, "T13":693, "T14":173, "T15":1},
"Period2": {"T1":1, "T2":1252,"T3":1120, "T4":645, "T5":2788, "T6":670, "T7":4348, "T8":80, "T9":212, "T10":69, "T11":325, "T12":325, "T13":687, "T14":168, "T15":1},
"Period3": {"T1":1, "T2":1302,"T3":1095, "T4":650, "T5":2838, "T6":650, "T7":4398, "T8":55, "T9":262, "T10":39, "T11":330, "T12":330, "T13":689, "T14":173, "T15":1},
"Period4": {"T1":1, "T2":1297,"T3":1169, "T4":645, "T5":2788, "T6":693, "T7":4393, "T8":130, "T9":212, "T10":77, "T11":335, "T12":335, "T13":669, "T14":168, "T15":1},
"Period5": {"T1":1, "T2":1452,"T3":1170, "T4":650, "T5":3038, "T6":700, "T7":4548, "T8":130, "T9":462, "T10":87, "T11":340, "T12":340, "T13":689, "T14":173, "T15":1},
"Period6": {"T1":1, "T2":1502,"T3":1220, "T4":660, "T5":3088, "T6":700, "T7":4598, "T8":180, "T9":512, "T10":89, "T11":345, "T12":344, "T13":689, "T14":183, "T15":1},
"Period7": {"T1":1, "T2":1602,"T3":1270, "T4":670, "T5":3188, "T6":700, "T7":4698, "T8":230, "T9":612, "T10":99, "T11":349, "T12":350, "T13":689, "T14":193, "T15":1}}
salaryCost = {"Period1": {"SP1":2296, "SP2":2207, "SP3":1900, "SP4":2199, "SP5":2586, "SP6":2276, "SP7":2390, "SP8":2200},
"Period2": {"SP1":2296, "SP2":2207, "SP3":1900, "SP4":2199, "SP5":2586, "SP6":2276, "SP7":2390, "SP8":2200},
"Period3": {"SP1":2296, "SP2":2207, "SP3":1900, "SP4":2199, "SP5":2586, "SP6":2276, "SP7":2390, "SP8":2200},
"Period4": {"SP1":2296, "SP2":2207, "SP3":1900, "SP4":2199, "SP5":2586, "SP6":2276, "SP7":2390, "SP8":2200},
"Period5": {"SP1":2296, "SP2":2207, "SP3":1900, "SP4":2199, "SP5":2586, "SP6":2276, "SP7":2390, "SP8":2200},
"Period6": {"SP1":2296, "SP2":2207, "SP3":1900, "SP4":2199, "SP5":2586, "SP6":2276, "SP7":2390, "SP8":2200},
"Period7": {"SP1":2296, "SP2":2207, "SP3":1900, "SP4":2199, "SP5":2586, "SP6":2276, "SP7":2390, "SP8":2200}}
#define decision variables
numberRequired = model.addVars(periods, skillPositions, tasks, vtype=GRB.INTEGER, name = "numberRequired")
productivity = model.addVars(periods, skillPositions, tasks, vtype=GRB.INTEGER, lb=0, ub=100, name= "productivity")
differenceProductivity = model.addVars(periods, skillPositions, tasks, vtype= GRB.INTEGER, lb=0, ub=100, name= "differenceProductivity")
#define constraints
#THIS FIRST CONSTRAINT IS WHERE THE ERROR MESSAGE COMES FROM
model.addConstrs(demand[period][taskx] <= quicksum(math.floor((numberRequired[period, skillPositionJ, taskx]*availableRegularHours*productivity[period, skillPositionJ, taskx])/100) for skillPositionJ in skillPositions) for period in periods for taskx in tasks)
model.addConstrs(productivity[period, skillPosition, task] == initialProductivity[period][skillPosition][task] + differenceProductivity[period, skillPosition, task] * (1-math.exp(-(sum(possibleCombinations[currentP][skillPosition][task] for currentP in periods[:int(period[-1])])/learningParameter[period][skillPosition][task])) if learningParameter[period][skillPosition][task] > 0 else 0) for period in periods for skillPosition in skillPositions for task in tasks)
model.addConstrs(differenceProductivity[period, skillPosition, task] == 100-initialProductivity[period][skillPosition][task] for period in periods for skillPosition in skillPositions for task in tasks)
#define objective
obj = (quicksum(salaryCost[period][skillPositionJ]*numberRequired[periods[period_index-1], skillPositionJ, taskx] for period_index, period in enumerate(periods) for skillPositionJ in skillPositions for taskx in tasks)
model.setObjective(obj, GRB.MINIMIZE)
#solving the model
model.optimize()
model.printAttr('X')
Can anybody find my error in this code? It has something to do with the productivity variable, since it runs without this. Thank you in advance!
Kind regards
One thing I see is the use of math.floor. First, Gurobi does not know about this function. But also: it would make the model nonlinear in a nasty fashion (non-differentiable).
The construct y=floor(x)
can be linearized as:
y <= x
y >= x-1+0.0001
y integer
There are still quadratic terms in the model. Gurobi can handle convex and non-convex quadratic models, but usually it is better to stick to linear models whenever possible.
It is also important to keep the constraints as simple as possible. That means that I would precompute things like:
(1-math.exp(-(sum(possibleCombinations[currentP][skillPosition][task] for currentP in periods[:int(period[-1])])/learningParameter[period] [skillPosition][task])) if learningParameter[period][skillPosition][task] > 0 else 0)
If I am correct, the constraint is simply:
productivity=initialProductivity+differenceProductivity*coefficient
but we get buried in complexity making the constraint unreadable.
Finally, my advice is to first develop a mathematical model and discuss that with your supervisor before you start coding in Python. It is much more efficient to reason about reformulations for a compact mathematical model than for a bunch of convoluted Python code.