Filter Content By
Version
Text Search
workforce5.py
#!/usr/bin/python # Copyright 2016, Gurobi Optimization, Inc. # Assign workers to shifts; each worker may or may not be available on a # particular day. We use multi-objective optimization to solve the model. # The highest-priority objective minimizes the sum of the slacks # (i.e., the total number of uncovered shifts). The secondary objective # minimizes the difference between the maximum and minimum number of # shifts worked among all workers. The second optimization is allowed # to degrade the first objective by up to the smaller value of 10% and 2 */ from gurobipy import * try: # Sample data # Sets of days and workers Shifts = [ "Mon1", "Tue2", "Wed3", "Thu4", "Fri5", "Sat6", "Sun7", "Mon8", "Tue9", "Wed10", "Thu11", "Fri12", "Sat13", "Sun14" ] Workers = [ "Amy", "Bob", "Cathy", "Dan", "Ed", "Fred", "Gu", "Tobi" ] # Number of workers required for each shift S = [ 3, 2, 4, 4, 5, 6, 5, 2, 2, 3, 4, 6, 7, 5 ] shiftRequirements = { s : S[i] for i,s in enumerate(Shifts) } # Worker availability: 0 if the worker is unavailable for a shift A = [ [ 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1 ], [ 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0 ], [ 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1 ], [ 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1 ], [ 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1 ], [ 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1 ], [ 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1 ], [ 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ] ] availability = { (w,s) : A[j][i] for i,s in enumerate(Shifts) for j,w in enumerate(Workers) } # Create initial model model = Model("workforce5") # Initialize assignment decision variables: # x[w][s] == 1 if worker w is assigned to shift s. # This is no longer a pure assignment model, so we must # use binary variables. x = model.addVars(availability.keys(), ub=availability, vtype=GRB.BINARY, name='x') # Slack variables for each shift constraint so that the shifts can # be satisfied slacks = model.addVars(Shifts, name='Slack') # Variable to represent the total slack totSlack = model.addVar(name='totSlack') # Variables to count the total shifts worked by each worker totShifts = model.addVars(Workers, name='TotShifts') # Constraint: assign exactly shiftRequirements[s] workers # to each shift s, plus the slack model.addConstrs((x.sum('*',s) + slacks[s] == shiftRequirements[s] for s in Shifts), name='shiftRequirement') # Constraint: set totSlack equal to the total slack model.addConstr(totSlack == slacks.sum(), name='totSlack') # Constraint: compute the total number of shifts for each worker model.addConstrs((totShifts[w] == x.sum(w,'*') for w in Workers), name='totShifts') # Constraint: set minShift/maxShift variable to less/greater than the # number of shifts among all workers minShift = model.addVar(name='minShift') maxShift = model.addVar(name='maxShift') model.addGenConstrMin(minShift, totShifts, name='minShift') model.addGenConstrMax(maxShift, totShifts, name='maxShift') # Set global sense for ALL objectives model.ModelSense = GRB.MINIMIZE # Set number of objectives model.NumObj = 2 # Set and configure objective 0 model.setParam(GRB.Param.ObjNumber, 0) model.ObjNPriority = 2 model.ObjNWeight = 1.0 model.ObjNName = 'TotalSlack' model.ObjNRelTol = 0.1 model.ObjNAbsTol = 2.0 totSlack.ObjN = 1.0 # set and configure objective 1 model.setParam(GRB.Param.ObjNumber, 1) model.ObjNPriority = 1 model.ObjNWeight = 1.0 model.ObjNName = 'Fairness' maxShift.ObjN = 1.0 minShift.ObjN = -1.0 # Save problem model.write('workforce5.lp') # Optimize model.optimize() status = model.Status if status == GRB.Status.INF_OR_UNBD or \ status == GRB.Status.INFEASIBLE or \ status == GRB.Status.UNBOUNDED: print('The model cannot be solved because it is infeasible or unbounded') sys.exit(0) if status != GRB.Status.OPTIMAL: print('Optimization was stopped with status ' + str(status)) sys.exit(0) # Print total slack and the number of shifts worked for each worker print('') print('Total slack required: ' + str(totSlack.X)) for w in Workers: print(w + ' worked ' + str(totShifts[w].X) + ' shifts') print('') except GurobiError as e: print('Error code ' + str(e.errno) + ": " + str(e)) except AttributeError as e: print('Encountered an attribute error: ' + str(e))