This section includes source code for all of the Gurobi workforce examples.The same source code can be found in theexamples directory of theGurobi distribution.
/* Copyright 2025, Gurobi Optimization, LLC *//* Assign workers to shifts; each worker may or may not be available on a particular day. If the problem cannot be solved, use IIS to find a set of conflicting constraints. Note that there may be additional conflicts besides what is reported via IIS. */#include<stdlib.h>#include<stdio.h>#include<math.h>#include"gurobi_c.h"#define xcol(w,s) nShifts*w+s#define MAXSTR 128intmain(intargc,char*argv[]){GRBenv*env=NULL;GRBmodel*model=NULL;interror=0,status;ints,w,col;int*cbeg=NULL;int*cind=NULL;intidx;double*cval=NULL;char*sense=NULL;charvname[MAXSTR];doubleobj;inti,iis,numconstrs;char*cname;/* Sample data */constintnShifts=14;constintnWorkers=7;/* Sets of days and workers */char*Shifts[]={"Mon1","Tue2","Wed3","Thu4","Fri5","Sat6","Sun7","Mon8","Tue9","Wed10","Thu11","Fri12","Sat13","Sun14"};char*Workers[]={"Amy","Bob","Cathy","Dan","Ed","Fred","Gu"};/* Number of workers required for each shift */doubleshiftRequirements[]={3,2,4,4,5,6,5,2,2,3,4,6,7,5};/* Amount each worker is paid to work one shift */doublepay[]={10,12,10,8,8,9,11};/* Worker availability: 0 if the worker is unavailable for a shift */doubleavailability[][14]={{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},{1,1,1,0,1,1,1,1,1,1,1,1,1,1}};/* Create environment */error=GRBloadenv(&env,"workforce1.log");if(error)gotoQUIT;/* Create initial model */error=GRBnewmodel(env,&model,"workforce1",nWorkers*nShifts,NULL,NULL,NULL,NULL,NULL);if(error)gotoQUIT;/* Initialize assignment decision variables: x[w][s] == 1 if worker w is assigned to shift s. Since an assignment model always produces integer solutions, we use continuous variables and solve as an LP. */for(w=0;w<nWorkers;++w){for(s=0;s<nShifts;++s){col=xcol(w,s);sprintf(vname,"%s.%s",Workers[w],Shifts[s]);error=GRBsetdblattrelement(model,"UB",col,availability[w][s]);if(error)gotoQUIT;error=GRBsetdblattrelement(model,"Obj",col,pay[w]);if(error)gotoQUIT;error=GRBsetstrattrelement(model,"VarName",col,vname);if(error)gotoQUIT;}}/* The objective is to minimize the total pay costs */error=GRBsetintattr(model,"ModelSense",GRB_MINIMIZE);if(error)gotoQUIT;/* Make space for constraint data */cbeg=malloc(sizeof(int)*nShifts);if(!cbeg)gotoQUIT;cind=malloc(sizeof(int)*nShifts*nWorkers);if(!cind)gotoQUIT;cval=malloc(sizeof(double)*nShifts*nWorkers);if(!cval)gotoQUIT;sense=malloc(sizeof(char)*nShifts);if(!sense)gotoQUIT;/* Constraint: assign exactly shiftRequirements[s] workers to each shift s */idx=0;for(s=0;s<nShifts;++s){cbeg[s]=idx;sense[s]=GRB_EQUAL;for(w=0;w<nWorkers;++w){cind[idx]=xcol(w,s);cval[idx++]=1.0;}}error=GRBaddconstrs(model,nShifts,idx,cbeg,cind,cval,sense,shiftRequirements,Shifts);if(error)gotoQUIT;/* Optimize */error=GRBoptimize(model);if(error)gotoQUIT;error=GRBgetintattr(model,"Status",&status);if(error)gotoQUIT;if(status==GRB_UNBOUNDED){printf("The model cannot be solved because it is unbounded\n");gotoQUIT;}if(status==GRB_OPTIMAL){error=GRBgetdblattr(model,"ObjVal",&obj);if(error)gotoQUIT;printf("The optimal objective is %f\n",obj);gotoQUIT;}if((status!=GRB_INF_OR_UNBD)&&(status!=GRB_INFEASIBLE)){printf("Optimization was stopped with status %i\n",status);gotoQUIT;}/* do IIS */printf("The model is infeasible; computing IIS\n");error=GRBcomputeIIS(model);if(error)gotoQUIT;printf("\nThe following constraint(s) cannot be satisfied:\n");error=GRBgetintattr(model,"NumConstrs",&numconstrs);if(error)gotoQUIT;for(i=0;i<numconstrs;++i){error=GRBgetintattrelement(model,"IISConstr",i,&iis);if(error)gotoQUIT;if(iis){error=GRBgetstrattrelement(model,"ConstrName",i,&cname);if(error)gotoQUIT;printf("%s\n",cname);}}QUIT:/* Error reporting */if(error){printf("ERROR: %s\n",GRBgeterrormsg(env));exit(1);}/* Free data */free(cbeg);free(cind);free(cval);free(sense);/* Free model */GRBfreemodel(model);/* Free environment */GRBfreeenv(env);return0;}
/* Copyright 2025, Gurobi Optimization, LLC *//* Assign workers to shifts; each worker may or may not be available on a particular day. If the problem cannot be solved, use IIS to find a set of conflicting constraints. Note that there may be additional conflicts besides what is reported via IIS. */#include"gurobi_c++.h"#include<sstream>usingnamespacestd;intmain(intargc,char*argv[]){GRBEnv*env=0;GRBConstr*c=0;GRBVar**x=0;intxCt=0;try{// Sample dataconstintnShifts=14;constintnWorkers=7;// Sets of days and workersstringShifts[]={"Mon1","Tue2","Wed3","Thu4","Fri5","Sat6","Sun7","Mon8","Tue9","Wed10","Thu11","Fri12","Sat13","Sun14"};stringWorkers[]={"Amy","Bob","Cathy","Dan","Ed","Fred","Gu"};// Number of workers required for each shiftdoubleshiftRequirements[]={3,2,4,4,5,6,5,2,2,3,4,6,7,5};// Amount each worker is paid to work one shiftdoublepay[]={10,12,10,8,8,9,11};// Worker availability: 0 if the worker is unavailable for a shiftdoubleavailability[][nShifts]={{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},{1,1,1,0,1,1,1,1,1,1,1,1,1,1}};// Modelenv=newGRBEnv();GRBModelmodel=GRBModel(*env);model.set(GRB_StringAttr_ModelName,"assignment");// Assignment variables: x[w][s] == 1 if worker w is assigned// to shift s. Since an assignment model always produces integer// solutions, we use continuous variables and solve as an LP.x=newGRBVar*[nWorkers];for(intw=0;w<nWorkers;++w){x[w]=model.addVars(nShifts);xCt++;for(ints=0;s<nShifts;++s){ostringstreamvname;vname<<Workers[w]<<"."<<Shifts[s];x[w][s].set(GRB_DoubleAttr_UB,availability[w][s]);x[w][s].set(GRB_DoubleAttr_Obj,pay[w]);x[w][s].set(GRB_StringAttr_VarName,vname.str());}}// The objective is to minimize the total pay costsmodel.set(GRB_IntAttr_ModelSense,GRB_MINIMIZE);// Constraint: assign exactly shiftRequirements[s] workers// to each shift sfor(ints=0;s<nShifts;++s){GRBLinExprlhs=0;for(intw=0;w<nWorkers;++w){lhs+=x[w][s];}model.addConstr(lhs==shiftRequirements[s],Shifts[s]);}// Optimizemodel.optimize();intstatus=model.get(GRB_IntAttr_Status);if(status==GRB_UNBOUNDED){cout<<"The model cannot be solved "<<"because it is unbounded"<<endl;return1;}if(status==GRB_OPTIMAL){cout<<"The optimal objective is "<<model.get(GRB_DoubleAttr_ObjVal)<<endl;return0;}if((status!=GRB_INF_OR_UNBD)&&(status!=GRB_INFEASIBLE)){cout<<"Optimization was stopped with status "<<status<<endl;return1;}// do IIScout<<"The model is infeasible; computing IIS"<<endl;model.computeIIS();cout<<"\nThe following constraint(s) "<<"cannot be satisfied:"<<endl;c=model.getConstrs();for(inti=0;i<model.get(GRB_IntAttr_NumConstrs);++i){if(c[i].get(GRB_IntAttr_IISConstr)==1){cout<<c[i].get(GRB_StringAttr_ConstrName)<<endl;}}}catch(GRBExceptione){cout<<"Error code = "<<e.getErrorCode()<<endl;cout<<e.getMessage()<<endl;}catch(...){cout<<"Exception during optimization"<<endl;}delete[]c;for(inti=0;i<xCt;++i){delete[]x[i];}delete[]x;deleteenv;return0;}
/* Copyright 2025, Gurobi Optimization, LLC *//* Assign workers to shifts; each worker may or may not be available on a particular day. If the problem cannot be solved, use IIS to find a set of conflicting constraints. Note that there may be additional conflicts besides what is reported via IIS. */usingSystem;usingGurobi;classworkforce1_cs{staticvoidMain(){try{// Sample data// Sets of days and workersstring[]Shifts=newstring[]{"Mon1","Tue2","Wed3","Thu4","Fri5","Sat6","Sun7","Mon8","Tue9","Wed10","Thu11","Fri12","Sat13","Sun14"};string[]Workers=newstring[]{"Amy","Bob","Cathy","Dan","Ed","Fred","Gu"};intnShifts=Shifts.Length;intnWorkers=Workers.Length;// Number of workers required for each shiftdouble[]shiftRequirements=newdouble[]{3,2,4,4,5,6,5,2,2,3,4,6,7,5};// Amount each worker is paid to work one shiftdouble[]pay=newdouble[]{10,12,10,8,8,9,11};// Worker availability: 0 if the worker is unavailable for a shiftdouble[,]availability=newdouble[,]{{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},{1,1,1,0,1,1,1,1,1,1,1,1,1,1}};// ModelGRBEnvenv=newGRBEnv();GRBModelmodel=newGRBModel(env);model.ModelName="assignment";// Assignment variables: x[w][s] == 1 if worker w is assigned// to shift s. Since an assignment model always produces integer// solutions, we use continuous variables and solve as an LP.GRBVar[,]x=newGRBVar[nWorkers,nShifts];for(intw=0;w<nWorkers;++w){for(ints=0;s<nShifts;++s){x[w,s]=model.AddVar(0,availability[w,s],pay[w],GRB.CONTINUOUS,Workers[w]+"."+Shifts[s]);}}// The objective is to minimize the total pay costsmodel.ModelSense=GRB.MINIMIZE;// Constraint: assign exactly shiftRequirements[s] workers// to each shift sfor(ints=0;s<nShifts;++s){GRBLinExprlhs=0.0;for(intw=0;w<nWorkers;++w)lhs.AddTerm(1.0,x[w,s]);model.AddConstr(lhs==shiftRequirements[s],Shifts[s]);}// Optimizemodel.Optimize();intstatus=model.Status;if(status==GRB.Status.UNBOUNDED){Console.WriteLine("The model cannot be solved "+"because it is unbounded");return;}if(status==GRB.Status.OPTIMAL){Console.WriteLine("The optimal objective is "+model.ObjVal);return;}if((status!=GRB.Status.INF_OR_UNBD)&&(status!=GRB.Status.INFEASIBLE)){Console.WriteLine("Optimization was stopped with status "+status);return;}// Do IISConsole.WriteLine("The model is infeasible; computing IIS");model.ComputeIIS();Console.WriteLine("\nThe following constraint(s) "+"cannot be satisfied:");foreach(GRBConstrcinmodel.GetConstrs()){if(c.IISConstr==1){Console.WriteLine(c.ConstrName);}}// Dispose of model and envmodel.Dispose();env.Dispose();}catch(GRBExceptione){Console.WriteLine("Error code: "+e.ErrorCode+". "+e.Message);}}}
/* Copyright 2025, Gurobi Optimization, LLC *//* Assign workers to shifts; each worker may or may not be available on a particular day. If the problem cannot be solved, use IIS to find a set of conflicting constraints. Note that there may be additional conflicts besides what is reported via IIS. */importcom.gurobi.gurobi.*;publicclassWorkforce1{publicstaticvoidmain(String[]args){try{// Sample data// Sets of days and workersStringShifts[]=newString[]{"Mon1","Tue2","Wed3","Thu4","Fri5","Sat6","Sun7","Mon8","Tue9","Wed10","Thu11","Fri12","Sat13","Sun14"};StringWorkers[]=newString[]{"Amy","Bob","Cathy","Dan","Ed","Fred","Gu"};intnShifts=Shifts.length;intnWorkers=Workers.length;// Number of workers required for each shiftdoubleshiftRequirements[]=newdouble[]{3,2,4,4,5,6,5,2,2,3,4,6,7,5};// Amount each worker is paid to work one shiftdoublepay[]=newdouble[]{10,12,10,8,8,9,11};// Worker availability: 0 if the worker is unavailable for a shiftdoubleavailability[][]=newdouble[][]{{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},{1,1,1,0,1,1,1,1,1,1,1,1,1,1}};// ModelGRBEnvenv=newGRBEnv();GRBModelmodel=newGRBModel(env);model.set(GRB.StringAttr.ModelName,"assignment");// Assignment variables: x[w][s] == 1 if worker w is assigned// to shift s. Since an assignment model always produces integer// solutions, we use continuous variables and solve as an LP.GRBVar[][]x=newGRBVar[nWorkers][nShifts];for(intw=0;w<nWorkers;++w){for(ints=0;s<nShifts;++s){x[w][s]=model.addVar(0,availability[w][s],pay[w],GRB.CONTINUOUS,Workers[w]+"."+Shifts[s]);}}// The objective is to minimize the total pay costsmodel.set(GRB.IntAttr.ModelSense,GRB.MINIMIZE);// Constraint: assign exactly shiftRequirements[s] workers// to each shift sfor(ints=0;s<nShifts;++s){GRBLinExprlhs=newGRBLinExpr();for(intw=0;w<nWorkers;++w){lhs.addTerm(1.0,x[w][s]);}model.addConstr(lhs,GRB.EQUAL,shiftRequirements[s],Shifts[s]);}// Optimizemodel.optimize();intstatus=model.get(GRB.IntAttr.Status);if(status==GRB.Status.UNBOUNDED){System.out.println("The model cannot be solved "+"because it is unbounded");return;}if(status==GRB.Status.OPTIMAL){System.out.println("The optimal objective is "+model.get(GRB.DoubleAttr.ObjVal));return;}if(status!=GRB.Status.INF_OR_UNBD&&status!=GRB.Status.INFEASIBLE){System.out.println("Optimization was stopped with status "+status);return;}// Compute IISSystem.out.println("The model is infeasible; computing IIS");model.computeIIS();System.out.println("\nThe following constraint(s) "+"cannot be satisfied:");for(GRBConstrc:model.getConstrs()){if(c.get(GRB.IntAttr.IISConstr)==1){System.out.println(c.get(GRB.StringAttr.ConstrName));}}// Dispose of model and environmentmodel.dispose();env.dispose();}catch(GRBExceptione){System.out.println("Error code: "+e.getErrorCode()+". "+e.getMessage());}}}
functionworkforce1()% Copyright 2025, Gurobi Optimization, LLC%% Assign workers to shifts; each worker may or may not be available on a% particular day. If the problem cannot be solved, use IIS to find a set of% conflicting constraints. Note that there may be additional conflicts% besides what is reported via IIS.% define datanShifts=14;nWorkers=7;nVars=nShifts*nWorkers;Shifts={'Mon1';'Tue2';'Wed3';'Thu4';'Fri5';'Sat6';'Sun7';'Mon8';'Tue9';'Wed10';'Thu11';'Fri12';'Sat13';'Sun14'};Workers={'Amy';'Bob';'Cathy';'Dan';'Ed';'Fred';'Gu'};pay=[10;12;10;8;8;9;11];shiftRequirements=[3;2;4;4;5;6;5;2;2;3;4;6;7;5];availability=[01101010111111;11001101001010;00111011111111;01101101111111;11111011101011;11100101100111;11101111111111];% Build modelmodel.modelname='workforce1';model.modelsense='min';% Initialize assignment decision variables:% x[w][s] == 1 if worker w is assigned% to shift s. Since an assignment model always produces integer% solutions, we use continuous variables and solve as an LP.model.ub=ones(nVars,1);model.obj=zeros(nVars,1);forw=1:nWorkersfors=1:nShiftsmodel.varnames{s+(w-1)*nShifts}=sprintf('%s.%s',Workers{w},Shifts{s});model.obj(s+(w-1)*nShifts)=pay(w);ifavailability(w,s)==0model.ub(s+(w-1)*nShifts)=0;endendend% Set-up shift-requirements constraintsmodel.sense=repmat('=',nShifts,1);model.rhs=shiftRequirements;model.constrnames=Shifts;model.A=sparse(nShifts,nVars);fors=1:nShiftsforw=1:nWorkersmodel.A(s,s+(w-1)*nShifts)=1;endend% Save modelgurobi_write(model,'workforce1_m.lp');% Optimizeparams.logfile='workforce1_m.log';result=gurobi(model,params);% Display resultsifstrcmp(result.status,'OPTIMAL')% The code may enter here if you change some of the data... otherwise% this will never be executed.fprintf('The optimal objective is %g\n',result.objval);fprintf('Schedule:\n');fors=1:nShiftsfprintf('\t%s:',Shifts{s});forw=1:nWorkersifresult.x(s+(w-1)*nShifts)>0.9fprintf('%s ',Workers{w});endendfprintf('\n');endelseifstrcmp(result.status,'INFEASIBLE')fprintf('Problem is infeasible.... computing IIS\n');iis=gurobi_iis(model,params);ifiis.minimalfprintf('IIS is minimal\n');elsefprintf('IIS is not minimal\n');endifany(iis.Arows)fprintf('Rows in IIS: ');disp(strjoin(model.constrnames(iis.Arows)));endifany(iis.lb)fprintf('LB in IIS: ');disp(strjoin(model.varnames(iis.lb)));endifany(iis.ub)fprintf('UB in IIS: ');disp(strjoin(model.varnames(iis.ub)));endelse% Just to handle user interruptions or other problemsfprintf('Unexpected status %s\n',result.status);endend
#!/usr/bin/env python3# Copyright 2025, Gurobi Optimization, LLC# Assign workers to shifts; each worker may or may not be available on a# particular day. If the problem cannot be solved, use IIS to find a set of# conflicting constraints. Note that there may be additional conflicts besides# what is reported via IIS.importgurobipyasgpfromgurobipyimportGRBimportsys# Number of workers required for each shiftshifts,shiftRequirements=gp.multidict({"Mon1":3,"Tue2":2,"Wed3":4,"Thu4":4,"Fri5":5,"Sat6":6,"Sun7":5,"Mon8":2,"Tue9":2,"Wed10":3,"Thu11":4,"Fri12":6,"Sat13":7,"Sun14":5,})# Amount each worker is paid to work one shiftworkers,pay=gp.multidict({"Amy":10,"Bob":12,"Cathy":10,"Dan":8,"Ed":8,"Fred":9,"Gu":11,})# Worker availabilityavailability=gp.tuplelist([("Amy","Tue2"),("Amy","Wed3"),("Amy","Fri5"),("Amy","Sun7"),("Amy","Tue9"),("Amy","Wed10"),("Amy","Thu11"),("Amy","Fri12"),("Amy","Sat13"),("Amy","Sun14"),("Bob","Mon1"),("Bob","Tue2"),("Bob","Fri5"),("Bob","Sat6"),("Bob","Mon8"),("Bob","Thu11"),("Bob","Sat13"),("Cathy","Wed3"),("Cathy","Thu4"),("Cathy","Fri5"),("Cathy","Sun7"),("Cathy","Mon8"),("Cathy","Tue9"),("Cathy","Wed10"),("Cathy","Thu11"),("Cathy","Fri12"),("Cathy","Sat13"),("Cathy","Sun14"),("Dan","Tue2"),("Dan","Wed3"),("Dan","Fri5"),("Dan","Sat6"),("Dan","Mon8"),("Dan","Tue9"),("Dan","Wed10"),("Dan","Thu11"),("Dan","Fri12"),("Dan","Sat13"),("Dan","Sun14"),("Ed","Mon1"),("Ed","Tue2"),("Ed","Wed3"),("Ed","Thu4"),("Ed","Fri5"),("Ed","Sun7"),("Ed","Mon8"),("Ed","Tue9"),("Ed","Thu11"),("Ed","Sat13"),("Ed","Sun14"),("Fred","Mon1"),("Fred","Tue2"),("Fred","Wed3"),("Fred","Sat6"),("Fred","Mon8"),("Fred","Tue9"),("Fred","Fri12"),("Fred","Sat13"),("Fred","Sun14"),("Gu","Mon1"),("Gu","Tue2"),("Gu","Wed3"),("Gu","Fri5"),("Gu","Sat6"),("Gu","Sun7"),("Gu","Mon8"),("Gu","Tue9"),("Gu","Wed10"),("Gu","Thu11"),("Gu","Fri12"),("Gu","Sat13"),("Gu","Sun14"),])# Modelm=gp.Model("assignment")# Assignment variables: x[w,s] == 1 if worker w is assigned to shift s.# Since an assignment model always produces integer solutions, we use# continuous variables and solve as an LP.x=m.addVars(availability,ub=1,name="x")# The objective is to minimize the total pay costsm.setObjective(gp.quicksum(pay[w]*x[w,s]forw,sinavailability),GRB.MINIMIZE)# Constraints: assign exactly shiftRequirements[s] workers to each shift sreqCts=m.addConstrs((x.sum("*",s)==shiftRequirements[s]forsinshifts),"_")# Using Python looping constructs, the preceding statement would be...## reqCts = {}# for s in shifts:# reqCts[s] = m.addConstr(# gp.quicksum(x[w,s] for w,s in availability.select('*', s)) ==# shiftRequirements[s], s)# Save modelm.write("workforce1.lp")# Optimizem.optimize()status=m.Statusifstatus==GRB.UNBOUNDED:print("The model cannot be solved because it is unbounded")sys.exit(0)ifstatus==GRB.OPTIMAL:print(f"The optimal objective is{m.ObjVal:g}")sys.exit(0)ifstatus!=GRB.INF_OR_UNBDandstatus!=GRB.INFEASIBLE:print(f"Optimization was stopped with status{status}")sys.exit(0)# do IISprint("The model is infeasible; computing IIS")m.computeIIS()ifm.IISMinimal:print("IIS is minimal\n")else:print("IIS is not minimal\n")print("\nThe following constraint(s) cannot be satisfied:")forcinm.getConstrs():ifc.IISConstr:print(c.ConstrName)
# Copyright 2025, Gurobi Optimization, LLC## Assign workers to shifts; each worker may or may not be available on a# particular day. If the problem cannot be solved, use IIS to find a set of# conflicting constraints. Note that there may be additional conflicts# besides what is reported via IIS.library(Matrix)library(gurobi)# define datanShifts<-14nWorkers<-7nVars<-nShifts*nWorkersvarIdx<-function(w,s){s+(w-1)*nShifts}Shifts<-c('Mon1','Tue2','Wed3','Thu4','Fri5','Sat6','Sun7','Mon8','Tue9','Wed10','Thu11','Fri12','Sat13','Sun14')Workers<-c('Amy','Bob','Cathy','Dan','Ed','Fred','Gu')pay<-c(10,12,10,8,8,9,11)shiftRequirements<-c(3,2,4,4,5,6,5,2,2,3,4,6,7,5)availability<-list(c(0,1,1,0,1,0,1,0,1,1,1,1,1,1),c(1,1,0,0,1,1,0,1,0,0,1,0,1,0),c(0,0,1,1,1,0,1,1,1,1,1,1,1,1),c(0,1,1,0,1,1,0,1,1,1,1,1,1,1),c(1,1,1,1,1,0,1,1,1,0,1,0,1,1),c(1,1,1,0,0,1,0,1,1,0,0,1,1,1),c(1,1,1,0,1,1,1,1,1,1,1,1,1,1))# Set up parametersparams<-list()params$logfile<-'workforce1.log'# Build modelmodel<-list()model$modelname<-'workforce1'model$modelsense<-'min'# Initialize assignment decision variables:# x[w][s] == 1 if worker w is assigned# to shift s. Since an assignment model always produces integer# solutions, we use continuous variables and solve as an LP.model$lb<-0model$ub<-rep(1,nVars)model$obj<-rep(0,nVars)model$varnames<-rep('',nVars)for(win1:nWorkers){for(sin1:nShifts){model$varnames[varIdx(w,s)]=paste0(Workers[w],'.',Shifts[s])model$obj[varIdx(w,s)]=pay[w]if(availability[[w]][s]==0)model$ub[varIdx(w,s)]=0}}# Set up shift-requirements constraintsmodel$A<-spMatrix(nShifts,nVars,i=c(mapply(rep,1:nShifts,nWorkers)),j=mapply(varIdx,1:nWorkers,mapply(rep,1:nShifts,nWorkers)),x=rep(1,nShifts*nWorkers))model$sense<-rep('=',nShifts)model$rhs<-shiftRequirementsmodel$constrnames<-Shifts# Save modelgurobi_write(model,'workforce1.lp',params)# Optimizeresult<-gurobi(model,params=params)# Display resultsif(result$status=='OPTIMAL'){# The code may enter here if you change some of the data... otherwise# this will never be executed.cat('The optimal objective is',result$objval,'\n')cat('Schedule:\n')for(sin1:nShifts){cat('\t',Shifts[s],':')for(win1:nWorkers){if(result$x[varIdx(w,s)]>0.9)cat(Workers[w],' ')}cat('\n')}}elseif(result$status=='INFEASIBLE'){# Find ONE IIScat('Problem is infeasible.... computing IIS\n')iis<-gurobi_iis(model,params=params)if(iis$minimal)cat('IIS is minimal\n')elsecat('IIS is not minimal\n')cat('Rows in IIS: ',model$constrnames[iis$Arows])cat('\nLB in IIS: ',model$varnames[iis$lb])cat('\nUB in IIS: ',model$varnames[iis$ub])cat('\n')rm(iis)}else{# Just to handle user interruptions or other problemscat('Unexpected status',result$status,'\nEnding now\n')}#Clear spacerm(model,params,availability,Shifts,Workers,pay,shiftRequirements,result)
' Copyright 2025, Gurobi Optimization, LLC'' Assign workers to shifts; each worker may or may not be available on a' particular day. If the problem cannot be solved, use IIS to find a set of' conflicting constraints. Note that there may be additional conflicts' besides what is reported via IIS.ImportsSystemImportsGurobiClassworkforce1_vbSharedSubMain()Try' Sample data' Sets of days and workersDimShiftsAsString()=NewString(){"Mon1","Tue2","Wed3","Thu4",_"Fri5","Sat6","Sun7","Mon8",_"Tue9","Wed10","Thu11",_"Fri12","Sat13","Sun14"}DimWorkersAsString()=NewString(){"Amy","Bob","Cathy","Dan",_"Ed","Fred","Gu"}DimnShiftsAsInteger=Shifts.LengthDimnWorkersAsInteger=Workers.Length' Number of workers required for each shiftDimshiftRequirementsAsDouble()=NewDouble(){3,2,4,4,5,6,_5,2,2,3,4,6,_7,5}' Amount each worker is paid to work one shiftDimpayAsDouble()=NewDouble(){10,12,10,8,8,9,11}' Worker availability: 0 if the worker is unavailable for a shiftDimavailabilityAsDouble(,)=NewDouble(,){_{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},_{1,1,1,0,1,1,1,1,1,1,1,1,1,1}}' ModelDimenvAsNewGRBEnv()DimmodelAsNewGRBModel(env)model.ModelName="assignment"' Assignment variables: x(w)(s) == 1 if worker w is assigned' to shift s. Since an assignment model always produces integer' solutions, we use continuous variables and solve as an LP.DimxAsGRBVar(,)=NewGRBVar(nWorkers-1,nShifts-1){}ForwAsInteger=0TonWorkers-1ForsAsInteger=0TonShifts-1x(w,s)=model.AddVar(0,availability(w,s),pay(w),_GRB.CONTINUOUS,_Workers(w)&"."&Shifts(s))NextNext' The objective is to minimize the total pay costsmodel.ModelSense=GRB.MINIMIZE' Constraint: assign exactly shiftRequirements(s) workers' to each shift sForsAsInteger=0TonShifts-1DimlhsAsGRBLinExpr=0ForwAsInteger=0TonWorkers-1lhs.AddTerm(1.0,x(w,s))Nextmodel.AddConstr(lhs=shiftRequirements(s),Shifts(s))Next' Optimizemodel.Optimize()DimstatusAsInteger=model.StatusIfstatus=GRB.Status.UNBOUNDEDThenConsole.WriteLine("The model cannot be solved "&_"because it is unbounded")ExitSubEndIfIfstatus=GRB.Status.OPTIMALThenConsole.WriteLine("The optimal objective is "&model.ObjVal)ExitSubEndIfIf(status<>GRB.Status.INF_OR_UNBD)AndAlso_(status<>GRB.Status.INFEASIBLE)ThenConsole.WriteLine("Optimization was stopped with status "&status)ExitSubEndIf' Do IISConsole.WriteLine("The model is infeasible; computing IIS")model.ComputeIIS()Console.WriteLine(vbLf&"The following constraint(s) "&_"cannot be satisfied:")ForEachcAsGRBConstrInmodel.GetConstrs()Ifc.IISConstr=1ThenConsole.WriteLine(c.ConstrName)EndIfNext' Dispose of model and envmodel.Dispose()env.Dispose()CatcheAsGRBExceptionConsole.WriteLine("Error code: "&e.ErrorCode&". "&e.Message)EndTryEndSubEndClass
/* Copyright 2025, Gurobi Optimization, LLC *//* Assign workers to shifts; each worker may or may not be available on a particular day. If the problem cannot be solved, use IIS iteratively to find all conflicting constraints. */#include<stdlib.h>#include<stdio.h>#include<math.h>#include<string.h>#include"gurobi_c.h"#define xcol(w,s) nShifts*w+s#define MAXSTR 128intmain(intargc,char*argv[]){GRBenv*env=NULL;GRBmodel*model=NULL;interror=0,status;ints,w,col;int*cbeg=NULL;int*cind=NULL;intidx;double*cval=NULL;char*sense=NULL;charvname[MAXSTR];doubleobj;inti,iis,numconstrs,numremoved=0;char*cname;char**removed=NULL;/* Sample data */constintnShifts=14;constintnWorkers=7;/* Sets of days and workers */char*Shifts[]={"Mon1","Tue2","Wed3","Thu4","Fri5","Sat6","Sun7","Mon8","Tue9","Wed10","Thu11","Fri12","Sat13","Sun14"};char*Workers[]={"Amy","Bob","Cathy","Dan","Ed","Fred","Gu"};/* Number of workers required for each shift */doubleshiftRequirements[]={3,2,4,4,5,6,5,2,2,3,4,6,7,5};/* Amount each worker is paid to work one shift */doublepay[]={10,12,10,8,8,9,11};/* Worker availability: 0 if the worker is unavailable for a shift */doubleavailability[][14]={{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},{1,1,1,0,1,1,1,1,1,1,1,1,1,1}};/* Create environment */error=GRBloadenv(&env,"workforce2.log");if(error)gotoQUIT;/* Create initial model */error=GRBnewmodel(env,&model,"workforce2",nWorkers*nShifts,NULL,NULL,NULL,NULL,NULL);if(error)gotoQUIT;/* Initialize assignment decision variables: x[w][s] == 1 if worker w is assigned to shift s. Since an assignment model always produces integer solutions, we use continuous variables and solve as an LP. */for(w=0;w<nWorkers;++w){for(s=0;s<nShifts;++s){col=xcol(w,s);sprintf(vname,"%s.%s",Workers[w],Shifts[s]);error=GRBsetdblattrelement(model,"UB",col,availability[w][s]);if(error)gotoQUIT;error=GRBsetdblattrelement(model,"Obj",col,pay[w]);if(error)gotoQUIT;error=GRBsetstrattrelement(model,"VarName",col,vname);if(error)gotoQUIT;}}/* The objective is to minimize the total pay costs */error=GRBsetintattr(model,"ModelSense",GRB_MINIMIZE);if(error)gotoQUIT;/* Make space for constraint data */cbeg=malloc(sizeof(int)*nShifts);if(!cbeg)gotoQUIT;cind=malloc(sizeof(int)*nShifts*nWorkers);if(!cind)gotoQUIT;cval=malloc(sizeof(double)*nShifts*nWorkers);if(!cval)gotoQUIT;sense=malloc(sizeof(char)*nShifts);if(!sense)gotoQUIT;/* Constraint: assign exactly shiftRequirements[s] workers to each shift s */idx=0;for(s=0;s<nShifts;++s){cbeg[s]=idx;sense[s]=GRB_EQUAL;for(w=0;w<nWorkers;++w){cind[idx]=xcol(w,s);cval[idx++]=1.0;}}error=GRBaddconstrs(model,nShifts,idx,cbeg,cind,cval,sense,shiftRequirements,Shifts);if(error)gotoQUIT;/* Optimize */error=GRBoptimize(model);if(error)gotoQUIT;error=GRBgetintattr(model,"Status",&status);if(error)gotoQUIT;if(status==GRB_UNBOUNDED){printf("The model cannot be solved because it is unbounded\n");gotoQUIT;}if(status==GRB_OPTIMAL){error=GRBgetdblattr(model,"ObjVal",&obj);if(error)gotoQUIT;printf("The optimal objective is %f\n",obj);gotoQUIT;}if((status!=GRB_INF_OR_UNBD)&&(status!=GRB_INFEASIBLE)){printf("Optimization was stopped with status %i\n",status);gotoQUIT;}/* do IIS */printf("The model is infeasible; computing IIS\n");/* Loop until we reduce to a model that can be solved */error=GRBgetintattr(model,"NumConstrs",&numconstrs);if(error)gotoQUIT;removed=calloc(numconstrs,sizeof(char*));if(!removed)gotoQUIT;while(1){error=GRBcomputeIIS(model);if(error)gotoQUIT;printf("\nThe following constraint cannot be satisfied:\n");for(i=0;i<numconstrs;++i){error=GRBgetintattrelement(model,"IISConstr",i,&iis);if(error)gotoQUIT;if(iis){error=GRBgetstrattrelement(model,"ConstrName",i,&cname);if(error)gotoQUIT;printf("%s\n",cname);/* Remove a single constraint from the model */removed[numremoved]=malloc(sizeof(char)*(1+strlen(cname)));if(!removed[numremoved])gotoQUIT;strcpy(removed[numremoved++],cname);cind[0]=i;error=GRBdelconstrs(model,1,cind);if(error)gotoQUIT;break;}}printf("\n");error=GRBoptimize(model);if(error)gotoQUIT;error=GRBgetintattr(model,"Status",&status);if(error)gotoQUIT;if(status==GRB_UNBOUNDED){printf("The model cannot be solved because it is unbounded\n");gotoQUIT;}if(status==GRB_OPTIMAL){break;}if((status!=GRB_INF_OR_UNBD)&&(status!=GRB_INFEASIBLE)){printf("Optimization was stopped with status %i\n",status);gotoQUIT;}}printf("\nThe following constraints were removed to get a feasible LP:\n");for(i=0;i<numremoved;++i){printf("%s ",removed[i]);}printf("\n");QUIT:/* Error reporting */if(error){printf("ERROR: %s\n",GRBgeterrormsg(env));exit(1);}/* Free data */free(cbeg);free(cind);free(cval);free(sense);for(i=0;i<numremoved;++i){free(removed[i]);}free(removed);/* Free model */GRBfreemodel(model);/* Free environment */GRBfreeenv(env);return0;}
/* Copyright 2025, Gurobi Optimization, LLC *//* Assign workers to shifts; each worker may or may not be available on a particular day. If the problem cannot be solved, use IIS iteratively to find all conflicting constraints. */#include"gurobi_c++.h"#include<sstream>#include<deque>usingnamespacestd;intmain(intargc,char*argv[]){GRBEnv*env=0;GRBConstr*c=0;GRBVar**x=0;intxCt=0;try{// Sample dataconstintnShifts=14;constintnWorkers=7;// Sets of days and workersstringShifts[]={"Mon1","Tue2","Wed3","Thu4","Fri5","Sat6","Sun7","Mon8","Tue9","Wed10","Thu11","Fri12","Sat13","Sun14"};stringWorkers[]={"Amy","Bob","Cathy","Dan","Ed","Fred","Gu"};// Number of workers required for each shiftdoubleshiftRequirements[]={3,2,4,4,5,6,5,2,2,3,4,6,7,5};// Amount each worker is paid to work one shiftdoublepay[]={10,12,10,8,8,9,11};// Worker availability: 0 if the worker is unavailable for a shiftdoubleavailability[][nShifts]={{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},{1,1,1,0,1,1,1,1,1,1,1,1,1,1}};// Modelenv=newGRBEnv();GRBModelmodel=GRBModel(*env);model.set(GRB_StringAttr_ModelName,"assignment");// Assignment variables: x[w][s] == 1 if worker w is assigned// to shift s. Since an assignment model always produces integer// solutions, we use continuous variables and solve as an LP.x=newGRBVar*[nWorkers];for(intw=0;w<nWorkers;++w){x[w]=model.addVars(nShifts);xCt++;for(ints=0;s<nShifts;++s){ostringstreamvname;vname<<Workers[w]<<"."<<Shifts[s];x[w][s].set(GRB_DoubleAttr_UB,availability[w][s]);x[w][s].set(GRB_DoubleAttr_Obj,pay[w]);x[w][s].set(GRB_StringAttr_VarName,vname.str());}}// The objective is to minimize the total pay costsmodel.set(GRB_IntAttr_ModelSense,GRB_MINIMIZE);// Constraint: assign exactly shiftRequirements[s] workers// to each shift sfor(ints=0;s<nShifts;++s){GRBLinExprlhs=0;for(intw=0;w<nWorkers;++w){lhs+=x[w][s];}model.addConstr(lhs==shiftRequirements[s],Shifts[s]);}// Optimizemodel.optimize();intstatus=model.get(GRB_IntAttr_Status);if(status==GRB_UNBOUNDED){cout<<"The model cannot be solved "<<"because it is unbounded"<<endl;return1;}if(status==GRB_OPTIMAL){cout<<"The optimal objective is "<<model.get(GRB_DoubleAttr_ObjVal)<<endl;return0;}if((status!=GRB_INF_OR_UNBD)&&(status!=GRB_INFEASIBLE)){cout<<"Optimization was stopped with status "<<status<<endl;return1;}// do IIScout<<"The model is infeasible; computing IIS"<<endl;deque<string>removed;// Loop until we reduce to a model that can be solvedwhile(1){model.computeIIS();cout<<"\nThe following constraint cannot be satisfied:"<<endl;c=model.getConstrs();for(inti=0;i<model.get(GRB_IntAttr_NumConstrs);++i){if(c[i].get(GRB_IntAttr_IISConstr)==1){cout<<c[i].get(GRB_StringAttr_ConstrName)<<endl;// Remove a single constraint from the modelremoved.push_back(c[i].get(GRB_StringAttr_ConstrName));model.remove(c[i]);break;}}delete[]c;c=0;cout<<endl;model.optimize();status=model.get(GRB_IntAttr_Status);if(status==GRB_UNBOUNDED){cout<<"The model cannot be solved because it is unbounded"<<endl;return0;}if(status==GRB_OPTIMAL){break;}if((status!=GRB_INF_OR_UNBD)&&(status!=GRB_INFEASIBLE)){cout<<"Optimization was stopped with status "<<status<<endl;return1;}}cout<<"\nThe following constraints were removed "<<"to get a feasible LP:"<<endl;for(deque<string>::iteratorr=removed.begin();r!=removed.end();++r){cout<<*r<<" ";}cout<<endl;}catch(GRBExceptione){cout<<"Error code = "<<e.getErrorCode()<<endl;cout<<e.getMessage()<<endl;}catch(...){cout<<"Exception during optimization"<<endl;}delete[]c;for(inti=0;i<xCt;++i){delete[]x[i];}delete[]x;deleteenv;return0;}
/* Copyright 2025, Gurobi Optimization, LLC *//* Assign workers to shifts; each worker may or may not be available on a particular day. If the problem cannot be solved, use IIS iteratively to find all conflicting constraints. */usingSystem;usingSystem.Collections.Generic;usingGurobi;classworkforce2_cs{staticvoidMain(){try{// Sample data// Sets of days and workersstring[]Shifts=newstring[]{"Mon1","Tue2","Wed3","Thu4","Fri5","Sat6","Sun7","Mon8","Tue9","Wed10","Thu11","Fri12","Sat13","Sun14"};string[]Workers=newstring[]{"Amy","Bob","Cathy","Dan","Ed","Fred","Gu"};intnShifts=Shifts.Length;intnWorkers=Workers.Length;// Number of workers required for each shiftdouble[]shiftRequirements=newdouble[]{3,2,4,4,5,6,5,2,2,3,4,6,7,5};// Amount each worker is paid to work one shiftdouble[]pay=newdouble[]{10,12,10,8,8,9,11};// Worker availability: 0 if the worker is unavailable for a shiftdouble[,]availability=newdouble[,]{{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},{1,1,1,0,1,1,1,1,1,1,1,1,1,1}};// ModelGRBEnvenv=newGRBEnv();GRBModelmodel=newGRBModel(env);model.ModelName="assignment";// Assignment variables: x[w][s] == 1 if worker w is assigned// to shift s. Since an assignment model always produces integer// solutions, we use continuous variables and solve as an LP.GRBVar[,]x=newGRBVar[nWorkers,nShifts];for(intw=0;w<nWorkers;++w){for(ints=0;s<nShifts;++s){x[w,s]=model.AddVar(0,availability[w,s],pay[w],GRB.CONTINUOUS,Workers[w]+"."+Shifts[s]);}}// The objective is to minimize the total pay costsmodel.ModelSense=GRB.MINIMIZE;// Constraint: assign exactly shiftRequirements[s] workers// to each shift sfor(ints=0;s<nShifts;++s){GRBLinExprlhs=0.0;for(intw=0;w<nWorkers;++w)lhs.AddTerm(1.0,x[w,s]);model.AddConstr(lhs==shiftRequirements[s],Shifts[s]);}// Optimizemodel.Optimize();intstatus=model.Status;if(status==GRB.Status.UNBOUNDED){Console.WriteLine("The model cannot be solved "+"because it is unbounded");return;}if(status==GRB.Status.OPTIMAL){Console.WriteLine("The optimal objective is "+model.ObjVal);return;}if((status!=GRB.Status.INF_OR_UNBD)&&(status!=GRB.Status.INFEASIBLE)){Console.WriteLine("Optimization was stopped with status "+status);return;}// Do IISConsole.WriteLine("The model is infeasible; computing IIS");LinkedList<string>removed=newLinkedList<string>();// Loop until we reduce to a model that can be solvedwhile(true){model.ComputeIIS();Console.WriteLine("\nThe following constraint cannot be satisfied:");foreach(GRBConstrcinmodel.GetConstrs()){if(c.IISConstr==1){Console.WriteLine(c.ConstrName);// Remove a single constraint from the modelremoved.AddFirst(c.ConstrName);model.Remove(c);break;}}Console.WriteLine();model.Optimize();status=model.Status;if(status==GRB.Status.UNBOUNDED){Console.WriteLine("The model cannot be solved "+"because it is unbounded");return;}if(status==GRB.Status.OPTIMAL){break;}if((status!=GRB.Status.INF_OR_UNBD)&&(status!=GRB.Status.INFEASIBLE)){Console.WriteLine("Optimization was stopped with status "+status);return;}}Console.WriteLine("\nThe following constraints were removed "+"to get a feasible LP:");foreach(stringsinremoved){Console.Write(s+" ");}Console.WriteLine();// Dispose of model and envmodel.Dispose();env.Dispose();}catch(GRBExceptione){Console.WriteLine("Error code: "+e.ErrorCode+". "+e.Message);}}}
/* Copyright 2025, Gurobi Optimization, LLC *//* Assign workers to shifts; each worker may or may not be available on a particular day. If the problem cannot be solved, use IIS iteratively to find all conflicting constraints. */importcom.gurobi.gurobi.*;importjava.util.*;publicclassWorkforce2{publicstaticvoidmain(String[]args){try{// Sample data// Sets of days and workersStringShifts[]=newString[]{"Mon1","Tue2","Wed3","Thu4","Fri5","Sat6","Sun7","Mon8","Tue9","Wed10","Thu11","Fri12","Sat13","Sun14"};StringWorkers[]=newString[]{"Amy","Bob","Cathy","Dan","Ed","Fred","Gu"};intnShifts=Shifts.length;intnWorkers=Workers.length;// Number of workers required for each shiftdoubleshiftRequirements[]=newdouble[]{3,2,4,4,5,6,5,2,2,3,4,6,7,5};// Amount each worker is paid to work one shiftdoublepay[]=newdouble[]{10,12,10,8,8,9,11};// Worker availability: 0 if the worker is unavailable for a shiftdoubleavailability[][]=newdouble[][]{{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},{1,1,1,0,1,1,1,1,1,1,1,1,1,1}};// ModelGRBEnvenv=newGRBEnv();GRBModelmodel=newGRBModel(env);model.set(GRB.StringAttr.ModelName,"assignment");// Assignment variables: x[w][s] == 1 if worker w is assigned// to shift s. Since an assignment model always produces integer// solutions, we use continuous variables and solve as an LP.GRBVar[][]x=newGRBVar[nWorkers][nShifts];for(intw=0;w<nWorkers;++w){for(ints=0;s<nShifts;++s){x[w][s]=model.addVar(0,availability[w][s],pay[w],GRB.CONTINUOUS,Workers[w]+"."+Shifts[s]);}}// The objective is to minimize the total pay costsmodel.set(GRB.IntAttr.ModelSense,GRB.MINIMIZE);// Constraint: assign exactly shiftRequirements[s] workers// to each shift sfor(ints=0;s<nShifts;++s){GRBLinExprlhs=newGRBLinExpr();for(intw=0;w<nWorkers;++w){lhs.addTerm(1.0,x[w][s]);}model.addConstr(lhs,GRB.EQUAL,shiftRequirements[s],Shifts[s]);}// Optimizemodel.optimize();intstatus=model.get(GRB.IntAttr.Status);if(status==GRB.Status.UNBOUNDED){System.out.println("The model cannot be solved "+"because it is unbounded");return;}if(status==GRB.Status.OPTIMAL){System.out.println("The optimal objective is "+model.get(GRB.DoubleAttr.ObjVal));return;}if(status!=GRB.Status.INF_OR_UNBD&&status!=GRB.Status.INFEASIBLE){System.out.println("Optimization was stopped with status "+status);return;}// Do IISSystem.out.println("The model is infeasible; computing IIS");LinkedList<String>removed=newLinkedList<String>();// Loop until we reduce to a model that can be solvedwhile(true){model.computeIIS();System.out.println("\nThe following constraint cannot be satisfied:");for(GRBConstrc:model.getConstrs()){if(c.get(GRB.IntAttr.IISConstr)==1){System.out.println(c.get(GRB.StringAttr.ConstrName));// Remove a single constraint from the modelremoved.add(c.get(GRB.StringAttr.ConstrName));model.remove(c);break;}}System.out.println();model.optimize();status=model.get(GRB.IntAttr.Status);if(status==GRB.Status.UNBOUNDED){System.out.println("The model cannot be solved "+"because it is unbounded");return;}if(status==GRB.Status.OPTIMAL){break;}if(status!=GRB.Status.INF_OR_UNBD&&status!=GRB.Status.INFEASIBLE){System.out.println("Optimization was stopped with status "+status);return;}}System.out.println("\nThe following constraints were removed "+"to get a feasible LP:");for(Strings:removed){System.out.print(s+" ");}System.out.println();// Dispose of model and environmentmodel.dispose();env.dispose();}catch(GRBExceptione){System.out.println("Error code: "+e.getErrorCode()+". "+e.getMessage());}}}
functionworkforce2()% Copyright 2025, Gurobi Optimization, LLC%% Assign workers to shifts; each worker may or may not be available on a% particular day. If the problem cannot be solved, use IIS iteratively to% find all conflicting constraints.% define datanShifts=14;nWorkers=7;nVars=nShifts*nWorkers;Shifts={'Mon1';'Tue2';'Wed3';'Thu4';'Fri5';'Sat6';'Sun7';'Mon8';'Tue9';'Wed10';'Thu11';'Fri12';'Sat13';'Sun14'};Workers={'Amy';'Bob';'Cathy';'Dan';'Ed';'Fred';'Gu'};pay=[10;12;10;8;8;9;11];shiftRequirements=[3;2;4;4;5;6;5;2;2;3;4;6;7;5];availability=[01101010111111;11001101001010;00111011111111;01101101111111;11111011101011;11100101100111;11101111111111];% Build modelmodel.modelname='workforce2';model.modelsense='min';% Initialize assignment decision variables:% x[w][s] == 1 if worker w is assigned% to shift s. Since an assignment model always produces integer% solutions, we use continuous variables and solve as an LP.model.ub=ones(nVars,1);model.obj=zeros(nVars,1);forw=1:nWorkersfors=1:nShiftsmodel.varnames{s+(w-1)*nShifts}=sprintf('%s.%s',Workers{w},Shifts{s});model.obj(s+(w-1)*nShifts)=pay(w);ifavailability(w,s)==0model.ub(s+(w-1)*nShifts)=0;endendend% Set-up shift-requirements constraintsmodel.sense=repmat('=',nShifts,1);model.rhs=shiftRequirements;model.constrnames=Shifts;model.A=sparse(nShifts,nVars);fors=1:nShiftsforw=1:nWorkersmodel.A(s,s+(w-1)*nShifts)=1;endend% Save modelgurobi_write(model,'workforce2_m.lp');% Optimizeparams.logfile='workforce2_m.log';result=gurobi(model,params);% If infeasible, remove IIS rows until it becomes feasiblenumremoved=0;ifstrcmp(result.status,'INFEASIBLE')numremoved=0;whilestrcmp(result.status,'INFEASIBLE')iis=gurobi_iis(model,params);keep=find(~iis.Arows);fprintf('Removing rows: ');disp(model.constrnames{iis.Arows})model.A=model.A(keep,:);model.sense=model.sense(keep,:);model.rhs=model.rhs(keep,:);model.constrnames=model.constrnames(keep,:);numremoved=numremoved+1;result=gurobi(model,params);endend% Display resultsifstrcmp(result.status,'OPTIMAL')ifnumremoved>0fprintf('It becomes feasible after %d rounds of IIS row removing\n',numremoved);endprintsolution(result,Shifts,Workers)else% Just to handle user interruptions or other problemsfprintf('Unexpected status %s\n',result.status)endendfunctionprintsolution(result, Shifts, Workers)% Helper function to display resultsnShifts=length(Shifts);nWorkers=length(Workers);fprintf('The optimal objective is %g\n',result.objval);fprintf('Schedule:\n');fors=1:nShiftsfprintf('\t%s:',Shifts{s});forw=1:nWorkersifresult.x(s+(w-1)*nShifts)>0.9fprintf('%s ',Workers{w});endendfprintf('\n');endend
#!/usr/bin/env python3# Copyright 2025, Gurobi Optimization, LLC# Assign workers to shifts; each worker may or may not be available on a# particular day. If the problem cannot be solved, use IIS iteratively to# find all conflicting constraints.importgurobipyasgpfromgurobipyimportGRBimportsys# Number of workers required for each shiftshifts,shiftRequirements=gp.multidict({"Mon1":3,"Tue2":2,"Wed3":4,"Thu4":4,"Fri5":5,"Sat6":6,"Sun7":5,"Mon8":2,"Tue9":2,"Wed10":3,"Thu11":4,"Fri12":6,"Sat13":7,"Sun14":5,})# Amount each worker is paid to work one shiftworkers,pay=gp.multidict({"Amy":10,"Bob":12,"Cathy":10,"Dan":8,"Ed":8,"Fred":9,"Gu":11,})# Worker availabilityavailability=gp.tuplelist([("Amy","Tue2"),("Amy","Wed3"),("Amy","Fri5"),("Amy","Sun7"),("Amy","Tue9"),("Amy","Wed10"),("Amy","Thu11"),("Amy","Fri12"),("Amy","Sat13"),("Amy","Sun14"),("Bob","Mon1"),("Bob","Tue2"),("Bob","Fri5"),("Bob","Sat6"),("Bob","Mon8"),("Bob","Thu11"),("Bob","Sat13"),("Cathy","Wed3"),("Cathy","Thu4"),("Cathy","Fri5"),("Cathy","Sun7"),("Cathy","Mon8"),("Cathy","Tue9"),("Cathy","Wed10"),("Cathy","Thu11"),("Cathy","Fri12"),("Cathy","Sat13"),("Cathy","Sun14"),("Dan","Tue2"),("Dan","Wed3"),("Dan","Fri5"),("Dan","Sat6"),("Dan","Mon8"),("Dan","Tue9"),("Dan","Wed10"),("Dan","Thu11"),("Dan","Fri12"),("Dan","Sat13"),("Dan","Sun14"),("Ed","Mon1"),("Ed","Tue2"),("Ed","Wed3"),("Ed","Thu4"),("Ed","Fri5"),("Ed","Sun7"),("Ed","Mon8"),("Ed","Tue9"),("Ed","Thu11"),("Ed","Sat13"),("Ed","Sun14"),("Fred","Mon1"),("Fred","Tue2"),("Fred","Wed3"),("Fred","Sat6"),("Fred","Mon8"),("Fred","Tue9"),("Fred","Fri12"),("Fred","Sat13"),("Fred","Sun14"),("Gu","Mon1"),("Gu","Tue2"),("Gu","Wed3"),("Gu","Fri5"),("Gu","Sat6"),("Gu","Sun7"),("Gu","Mon8"),("Gu","Tue9"),("Gu","Wed10"),("Gu","Thu11"),("Gu","Fri12"),("Gu","Sat13"),("Gu","Sun14"),])# Modelm=gp.Model("assignment")# Assignment variables: x[w,s] == 1 if worker w is assigned to shift s.# Since an assignment model always produces integer solutions, we use# continuous variables and solve as an LP.x=m.addVars(availability,ub=1,name="x")# The objective is to minimize the total pay costsm.setObjective(gp.quicksum(pay[w]*x[w,s]forw,sinavailability),GRB.MINIMIZE)# Constraint: assign exactly shiftRequirements[s] workers to each shift sreqCts=m.addConstrs((x.sum("*",s)==shiftRequirements[s]forsinshifts),"_")# Optimizem.optimize()status=m.Statusifstatus==GRB.UNBOUNDED:print("The model cannot be solved because it is unbounded")sys.exit(0)ifstatus==GRB.OPTIMAL:print(f"The optimal objective is{m.ObjVal:g}")sys.exit(0)ifstatus!=GRB.INF_OR_UNBDandstatus!=GRB.INFEASIBLE:print(f"Optimization was stopped with status{status}")sys.exit(0)# do IISprint("The model is infeasible; computing IIS")removed=[]# Loop until we reduce to a model that can be solvedwhileTrue:m.computeIIS()print("\nThe following constraint cannot be satisfied:")forcinm.getConstrs():ifc.IISConstr:print(c.ConstrName)# Remove a single constraint from the modelremoved.append(str(c.ConstrName))m.remove(c)breakprint("")m.optimize()status=m.Statusifstatus==GRB.UNBOUNDED:print("The model cannot be solved because it is unbounded")sys.exit(0)ifstatus==GRB.OPTIMAL:breakifstatus!=GRB.INF_OR_UNBDandstatus!=GRB.INFEASIBLE:print(f"Optimization was stopped with status{status}")sys.exit(0)print("\nThe following constraints were removed to get a feasible LP:")print(removed)
# Copyright 2025, Gurobi Optimization, LLC## Assign workers to shifts; each worker may or may not be available on a# particular day. If the problem cannot be solved, use IIS iteratively to# find all conflicting constraints.library(Matrix)library(gurobi)# Function to display resultsprintsolution<-function(result){if(result$status=='OPTIMAL'){cat('The optimal objective is',result$objval,'\n')cat('Schedule:\n')for(sin1:nShifts){cat('\t',Shifts[s],':')for(win1:nWorkers){if(result$x[varIdx(w,s)]>0.9)cat(Workers[w],' ')}cat('\n')}}}# define datanShifts<-14nWorkers<-7nVars<-nShifts*nWorkersvarIdx<-function(w,s){s+(w-1)*nShifts}Shifts<-c('Mon1','Tue2','Wed3','Thu4','Fri5','Sat6','Sun7','Mon8','Tue9','Wed10','Thu11','Fri12','Sat13','Sun14')Workers<-c('Amy','Bob','Cathy','Dan','Ed','Fred','Gu')pay<-c(10,12,10,8,8,9,11)shiftRequirements<-c(3,2,4,4,5,6,5,2,2,3,4,6,7,5)availability<-list(c(0,1,1,0,1,0,1,0,1,1,1,1,1,1),c(1,1,0,0,1,1,0,1,0,0,1,0,1,0),c(0,0,1,1,1,0,1,1,1,1,1,1,1,1),c(0,1,1,0,1,1,0,1,1,1,1,1,1,1),c(1,1,1,1,1,0,1,1,1,0,1,0,1,1),c(1,1,1,0,0,1,0,1,1,0,0,1,1,1),c(1,1,1,0,1,1,1,1,1,1,1,1,1,1))# Set up parametersparams<-list()params$logfile<-'workforce2.log'# Build modelmodel<-list()model$modelname<-'workforce2'model$modelsense<-'min'# Initialize assignment decision variables:# x[w][s] == 1 if worker w is assigned# to shift s. Since an assignment model always produces integer# solutions, we use continuous variables and solve as an LP.model$lb<-0model$ub<-rep(1,nVars)model$obj<-rep(0,nVars)model$varnames<-rep('',nVars)for(win1:nWorkers){for(sin1:nShifts){model$varnames[varIdx(w,s)]=paste0(Workers[w],'.',Shifts[s])model$obj[varIdx(w,s)]=pay[w]if(availability[[w]][s]==0)model$ub[varIdx(w,s)]=0}}# Set up shift-requirements constraintsmodel$A<-spMatrix(nShifts,nVars,i=c(mapply(rep,1:nShifts,nWorkers)),j=mapply(varIdx,1:nWorkers,mapply(rep,1:nShifts,nWorkers)),x=rep(1,nShifts*nWorkers))model$sense<-rep('=',nShifts)model$rhs<-shiftRequirementsmodel$constrnames<-Shifts# Save modelgurobi_write(model,'workforce2.lp',params)# Optimizeresult<-gurobi(model,params=params)# Display resultsif(result$status=='OPTIMAL'){# The code may enter here if you change some of the data... otherwise# this will never be executed.printsolution(result);}elseif(result$status=='INFEASIBLE'){# We will loop until we reduce a model that can be solvednumremoved<-0while(result$status=='INFEASIBLE'){iis<-gurobi_iis(model,params=params)keep<-(!iis$Arows)cat('Removing rows',model$constrnames[iis$Arows],'...\n')model$A<-model$A[keep,,drop=FALSE]model$sense<-model$sense[keep]model$rhs<-model$rhs[keep]model$constrnames<-model$constrnames[keep]numremoved<-numremoved+1gurobi_write(model,paste0('workforce2-',numremoved,'.lp'),params)result<-gurobi(model,params=params)}printsolution(result)rm(iis)}else{# Just to handle user interruptions or other problemscat('Unexpected status',result$status,'\nEnding now\n')}#Clear spacerm(model,params,availability,Shifts,Workers,pay,shiftRequirements,result)
' Copyright 2025, Gurobi Optimization, LLC'' Assign workers to shifts; each worker may or may not be available on a' particular day. If the problem cannot be solved, use IIS iteratively to' find all conflicting constraints.ImportsSystemImportsSystem.Collections.GenericImportsGurobiClassworkforce2_vbSharedSubMain()Try' Sample data' Sets of days and workersDimShiftsAsString()=NewString(){"Mon1","Tue2","Wed3","Thu4",_"Fri5","Sat6","Sun7","Mon8",_"Tue9","Wed10","Thu11",_"Fri12","Sat13","Sun14"}DimWorkersAsString()=NewString(){"Amy","Bob","Cathy","Dan",_"Ed","Fred","Gu"}DimnShiftsAsInteger=Shifts.LengthDimnWorkersAsInteger=Workers.Length' Number of workers required for each shiftDimshiftRequirementsAsDouble()=NewDouble(){3,2,4,4,5,6,_5,2,2,3,4,6,_7,5}' Amount each worker is paid to work one shiftDimpayAsDouble()=NewDouble(){10,12,10,8,8,9,11}' Worker availability: 0 if the worker is unavailable for a shiftDimavailabilityAsDouble(,)=NewDouble(,){_{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},_{1,1,1,0,1,1,1,1,1,1,1,1,1,1}}' ModelDimenvAsNewGRBEnv()DimmodelAsNewGRBModel(env)model.ModelName="assignment"' Assignment variables: x(w)(s) == 1 if worker w is assigned' to shift s. Since an assignment model always produces integer' solutions, we use continuous variables and solve as an LP.DimxAsGRBVar(,)=NewGRBVar(nWorkers-1,nShifts-1){}ForwAsInteger=0TonWorkers-1ForsAsInteger=0TonShifts-1x(w,s)=model.AddVar(0,availability(w,s),pay(w),_GRB.CONTINUOUS,_Workers(w)&"."&Shifts(s))NextNext' The objective is to minimize the total pay costsmodel.ModelSense=GRB.MINIMIZE' Constraint: assign exactly shiftRequirements(s) workers' to each shift sForsAsInteger=0TonShifts-1DimlhsAsGRBLinExpr=0ForwAsInteger=0TonWorkers-1lhs.AddTerm(1.0,x(w,s))Nextmodel.AddConstr(lhs=shiftRequirements(s),Shifts(s))Next' Optimizemodel.Optimize()DimstatusAsInteger=model.StatusIfstatus=GRB.Status.UNBOUNDEDThenConsole.WriteLine("The model cannot be solved "&_"because it is unbounded")ExitSubEndIfIfstatus=GRB.Status.OPTIMALThenConsole.WriteLine("The optimal objective is "&model.ObjVal)ExitSubEndIfIf(status<>GRB.Status.INF_OR_UNBD)AndAlso_(status<>GRB.Status.INFEASIBLE)ThenConsole.WriteLine("Optimization was stopped with status "&status)ExitSubEndIf' Do IISConsole.WriteLine("The model is infeasible; computing IIS")DimremovedAsLinkedList(OfString)=NewLinkedList(OfString)()' Loop until we reduce to a model that can be solvedWhileTruemodel.ComputeIIS()Console.WriteLine(vbLf&"The following constraint cannot be satisfied:")ForEachcAsGRBConstrInmodel.GetConstrs()Ifc.IISConstr=1ThenConsole.WriteLine(c.ConstrName)' Remove a single constraint from the modelremoved.AddFirst(c.ConstrName)model.Remove(c)ExitForEndIfNextConsole.WriteLine()model.Optimize()status=model.StatusIfstatus=GRB.Status.UNBOUNDEDThenConsole.WriteLine("The model cannot be solved "&_"because it is unbounded")ExitSubEndIfIfstatus=GRB.Status.OPTIMALThenExitWhileEndIfIf(status<>GRB.Status.INF_OR_UNBD)AndAlso_(status<>GRB.Status.INFEASIBLE)ThenConsole.WriteLine("Optimization was stopped with status "&_status)ExitSubEndIfEndWhileConsole.WriteLine(vbLf&"The following constraints were removed "&_"to get a feasible LP:")ForEachsAsStringInremovedConsole.Write(s&" ")NextConsole.WriteLine()' Dispose of model and envmodel.Dispose()env.Dispose()CatcheAsGRBExceptionConsole.WriteLine("Error code: "&e.ErrorCode&". "&e.Message)EndTryEndSubEndClass
/* Copyright 2025, Gurobi Optimization, LLC *//* Assign workers to shifts; each worker may or may not be available on a particular day. If the problem cannot be solved, relax the model to determine which constraints cannot be satisfied, and how much they need to be relaxed. */#include<stdlib.h>#include<stdio.h>#include<math.h>#include<string.h>#include"gurobi_c.h"#define xcol(w,s) nShifts*w+s#define MAXSTR 128intmain(intargc,char*argv[]){GRBenv*env=NULL;GRBmodel*model=NULL;interror=0,status;ints,w,col;int*cbeg=NULL;int*cind=NULL;intidx;double*cval=NULL;char*sense=NULL;charvname[MAXSTR];doubleobj;inti,j,orignumvars,numvars,numconstrs;double*rhspen=NULL;doublesol;char*sname;/* Sample data */constintnShifts=14;constintnWorkers=7;/* Sets of days and workers */char*Shifts[]={"Mon1","Tue2","Wed3","Thu4","Fri5","Sat6","Sun7","Mon8","Tue9","Wed10","Thu11","Fri12","Sat13","Sun14"};char*Workers[]={"Amy","Bob","Cathy","Dan","Ed","Fred","Gu"};/* Number of workers required for each shift */doubleshiftRequirements[]={3,2,4,4,5,6,5,2,2,3,4,6,7,5};/* Amount each worker is paid to work one shift */doublepay[]={10,12,10,8,8,9,11};/* Worker availability: 0 if the worker is unavailable for a shift */doubleavailability[][14]={{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},{1,1,1,0,1,1,1,1,1,1,1,1,1,1}};/* Create environment */error=GRBloadenv(&env,"workforce3.log");if(error)gotoQUIT;/* Create initial model */error=GRBnewmodel(env,&model,"workforce3",nWorkers*nShifts,NULL,NULL,NULL,NULL,NULL);if(error)gotoQUIT;/* Initialize assignment decision variables: x[w][s] == 1 if worker w is assigned to shift s. Since an assignment model always produces integer solutions, we use continuous variables and solve as an LP. */for(w=0;w<nWorkers;++w){for(s=0;s<nShifts;++s){col=xcol(w,s);sprintf(vname,"%s.%s",Workers[w],Shifts[s]);error=GRBsetdblattrelement(model,"UB",col,availability[w][s]);if(error)gotoQUIT;error=GRBsetdblattrelement(model,"Obj",col,pay[w]);if(error)gotoQUIT;error=GRBsetstrattrelement(model,"VarName",col,vname);if(error)gotoQUIT;}}/* The objective is to minimize the total pay costs */error=GRBsetintattr(model,"ModelSense",GRB_MINIMIZE);if(error)gotoQUIT;/* Make space for constraint data */cbeg=malloc(sizeof(int)*nShifts);if(!cbeg)gotoQUIT;cind=malloc(sizeof(int)*nShifts*nWorkers);if(!cind)gotoQUIT;cval=malloc(sizeof(double)*nShifts*nWorkers);if(!cval)gotoQUIT;sense=malloc(sizeof(char)*nShifts);if(!sense)gotoQUIT;/* Constraint: assign exactly shiftRequirements[s] workers to each shift s */idx=0;for(s=0;s<nShifts;++s){cbeg[s]=idx;sense[s]=GRB_EQUAL;for(w=0;w<nWorkers;++w){cind[idx]=xcol(w,s);cval[idx++]=1.0;}}error=GRBaddconstrs(model,nShifts,idx,cbeg,cind,cval,sense,shiftRequirements,Shifts);if(error)gotoQUIT;/* Optimize */error=GRBoptimize(model);if(error)gotoQUIT;error=GRBgetintattr(model,"Status",&status);if(error)gotoQUIT;if(status==GRB_UNBOUNDED){printf("The model cannot be solved because it is unbounded\n");gotoQUIT;}if(status==GRB_OPTIMAL){error=GRBgetdblattr(model,"ObjVal",&obj);if(error)gotoQUIT;printf("The optimal objective is %f\n",obj);gotoQUIT;}if((status!=GRB_INF_OR_UNBD)&&(status!=GRB_INFEASIBLE)){printf("Optimization was stopped with status %i\n",status);gotoQUIT;}/* Relax the constraints to make the model feasible */printf("The model is infeasible; relaxing the constraints\n");/* Determine the matrix size before relaxing the constraints */error=GRBgetintattr(model,"NumVars",&orignumvars);if(error)gotoQUIT;error=GRBgetintattr(model,"NumConstrs",&numconstrs);if(error)gotoQUIT;/* Use FeasRelax feature with penalties for constraint violations */rhspen=malloc(sizeof(double)*numconstrs);if(!rhspen)gotoQUIT;for(i=0;i<numconstrs;i++)rhspen[i]=1;error=GRBfeasrelax(model,GRB_FEASRELAX_LINEAR,0,NULL,NULL,rhspen,NULL);if(error)gotoQUIT;error=GRBoptimize(model);if(error)gotoQUIT;error=GRBgetintattr(model,"Status",&status);if(error)gotoQUIT;if((status==GRB_INF_OR_UNBD)||(status==GRB_INFEASIBLE)||(status==GRB_UNBOUNDED)){printf("The relaxed model cannot be solved ""because it is infeasible or unbounded\n");gotoQUIT;}if(status!=GRB_OPTIMAL){printf("Optimization was stopped with status %i\n",status);gotoQUIT;}printf("\nSlack values:\n");error=GRBgetintattr(model,"NumVars",&numvars);if(error)gotoQUIT;for(j=orignumvars;j<numvars;++j){error=GRBgetdblattrelement(model,"X",j,&sol);if(error)gotoQUIT;if(sol>1e-6){error=GRBgetstrattrelement(model,"VarName",j,&sname);if(error)gotoQUIT;printf("%s = %f\n",sname,sol);}}QUIT:/* Error reporting */if(error){printf("ERROR: %s\n",GRBgeterrormsg(env));exit(1);}/* Free data */free(cbeg);free(cind);free(cval);free(sense);free(rhspen);/* Free model */GRBfreemodel(model);/* Free environment */GRBfreeenv(env);return0;}
/* Copyright 2025, Gurobi Optimization, LLC *//* Assign workers to shifts; each worker may or may not be available on a particular day. If the problem cannot be solved, relax the model to determine which constraints cannot be satisfied, and how much they need to be relaxed. */#include"gurobi_c++.h"#include<sstream>usingnamespacestd;intmain(intargc,char*argv[]){GRBEnv*env=0;GRBConstr*c=0;GRBVar**x=0;GRBVar*vars=0;intxCt=0;try{// Sample dataconstintnShifts=14;constintnWorkers=7;// Sets of days and workersstringShifts[]={"Mon1","Tue2","Wed3","Thu4","Fri5","Sat6","Sun7","Mon8","Tue9","Wed10","Thu11","Fri12","Sat13","Sun14"};stringWorkers[]={"Amy","Bob","Cathy","Dan","Ed","Fred","Gu"};// Number of workers required for each shiftdoubleshiftRequirements[]={3,2,4,4,5,6,5,2,2,3,4,6,7,5};// Amount each worker is paid to work one shiftdoublepay[]={10,12,10,8,8,9,11};// Worker availability: 0 if the worker is unavailable for a shiftdoubleavailability[][nShifts]={{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},{1,1,1,0,1,1,1,1,1,1,1,1,1,1}};// Modelenv=newGRBEnv();GRBModelmodel=GRBModel(*env);model.set(GRB_StringAttr_ModelName,"assignment");// Assignment variables: x[w][s] == 1 if worker w is assigned// to shift s. Since an assignment model always produces integer// solutions, we use continuous variables and solve as an LP.x=newGRBVar*[nWorkers];for(intw=0;w<nWorkers;++w){x[w]=model.addVars(nShifts);xCt++;for(ints=0;s<nShifts;++s){ostringstreamvname;vname<<Workers[w]<<"."<<Shifts[s];x[w][s].set(GRB_DoubleAttr_UB,availability[w][s]);x[w][s].set(GRB_DoubleAttr_Obj,pay[w]);x[w][s].set(GRB_StringAttr_VarName,vname.str());}}// The objective is to minimize the total pay costsmodel.set(GRB_IntAttr_ModelSense,GRB_MINIMIZE);// Constraint: assign exactly shiftRequirements[s] workers// to each shift sfor(ints=0;s<nShifts;++s){GRBLinExprlhs=0;for(intw=0;w<nWorkers;++w){lhs+=x[w][s];}model.addConstr(lhs==shiftRequirements[s],Shifts[s]);}// Optimizemodel.optimize();intstatus=model.get(GRB_IntAttr_Status);if(status==GRB_UNBOUNDED){cout<<"The model cannot be solved "<<"because it is unbounded"<<endl;return1;}if(status==GRB_OPTIMAL){cout<<"The optimal objective is "<<model.get(GRB_DoubleAttr_ObjVal)<<endl;return0;}if((status!=GRB_INF_OR_UNBD)&&(status!=GRB_INFEASIBLE)){cout<<"Optimization was stopped with status "<<status<<endl;return1;}// Relax the constraints to make the model feasiblecout<<"The model is infeasible; relaxing the constraints"<<endl;intorignumvars=model.get(GRB_IntAttr_NumVars);model.feasRelax(0,false,false,true);model.optimize();status=model.get(GRB_IntAttr_Status);if((status==GRB_INF_OR_UNBD)||(status==GRB_INFEASIBLE)||(status==GRB_UNBOUNDED)){cout<<"The relaxed model cannot be solved "<<"because it is infeasible or unbounded"<<endl;return1;}if(status!=GRB_OPTIMAL){cout<<"Optimization was stopped with status "<<status<<endl;return1;}cout<<"\nSlack values:"<<endl;vars=model.getVars();for(inti=orignumvars;i<model.get(GRB_IntAttr_NumVars);++i){GRBVarsv=vars[i];if(sv.get(GRB_DoubleAttr_X)>1e-6){cout<<sv.get(GRB_StringAttr_VarName)<<" = "<<sv.get(GRB_DoubleAttr_X)<<endl;}}}catch(GRBExceptione){cout<<"Error code = "<<e.getErrorCode()<<endl;cout<<e.getMessage()<<endl;}catch(...){cout<<"Exception during optimization"<<endl;}delete[]c;for(inti=0;i<xCt;++i){delete[]x[i];}delete[]x;delete[]vars;deleteenv;return0;}
/* Copyright 2025, Gurobi Optimization, LLC *//* Assign workers to shifts; each worker may or may not be available on a particular day. If the problem cannot be solved, relax the model to determine which constraints cannot be satisfied, and how much they need to be relaxed. */usingSystem;usingGurobi;classworkforce3_cs{staticvoidMain(){try{// Sample data// Sets of days and workersstring[]Shifts=newstring[]{"Mon1","Tue2","Wed3","Thu4","Fri5","Sat6","Sun7","Mon8","Tue9","Wed10","Thu11","Fri12","Sat13","Sun14"};string[]Workers=newstring[]{"Amy","Bob","Cathy","Dan","Ed","Fred","Gu"};intnShifts=Shifts.Length;intnWorkers=Workers.Length;// Number of workers required for each shiftdouble[]shiftRequirements=newdouble[]{3,2,4,4,5,6,5,2,2,3,4,6,7,5};// Amount each worker is paid to work one shiftdouble[]pay=newdouble[]{10,12,10,8,8,9,11};// Worker availability: 0 if the worker is unavailable for a shiftdouble[,]availability=newdouble[,]{{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},{1,1,1,0,1,1,1,1,1,1,1,1,1,1}};// ModelGRBEnvenv=newGRBEnv();GRBModelmodel=newGRBModel(env);model.ModelName="assignment";// Assignment variables: x[w][s] == 1 if worker w is assigned// to shift s. Since an assignment model always produces integer// solutions, we use continuous variables and solve as an LP.GRBVar[,]x=newGRBVar[nWorkers,nShifts];for(intw=0;w<nWorkers;++w){for(ints=0;s<nShifts;++s){x[w,s]=model.AddVar(0,availability[w,s],pay[w],GRB.CONTINUOUS,Workers[w]+"."+Shifts[s]);}}// The objective is to minimize the total pay costsmodel.ModelSense=GRB.MINIMIZE;// Constraint: assign exactly shiftRequirements[s] workers// to each shift sfor(ints=0;s<nShifts;++s){GRBLinExprlhs=0.0;for(intw=0;w<nWorkers;++w){lhs.AddTerm(1.0,x[w,s]);}model.AddConstr(lhs==shiftRequirements[s],Shifts[s]);}// Optimizemodel.Optimize();intstatus=model.Status;if(status==GRB.Status.UNBOUNDED){Console.WriteLine("The model cannot be solved "+"because it is unbounded");return;}if(status==GRB.Status.OPTIMAL){Console.WriteLine("The optimal objective is "+model.ObjVal);return;}if((status!=GRB.Status.INF_OR_UNBD)&&(status!=GRB.Status.INFEASIBLE)){Console.WriteLine("Optimization was stopped with status "+status);return;}// Relax the constraints to make the model feasibleConsole.WriteLine("The model is infeasible; relaxing the constraints");intorignumvars=model.NumVars;model.FeasRelax(0,false,false,true);model.Optimize();status=model.Status;if((status==GRB.Status.INF_OR_UNBD)||(status==GRB.Status.INFEASIBLE)||(status==GRB.Status.UNBOUNDED)){Console.WriteLine("The relaxed model cannot be solved "+"because it is infeasible or unbounded");return;}if(status!=GRB.Status.OPTIMAL){Console.WriteLine("Optimization was stopped with status "+status);return;}Console.WriteLine("\nSlack values:");GRBVar[]vars=model.GetVars();for(inti=orignumvars;i<model.NumVars;++i){GRBVarsv=vars[i];if(sv.X>1e-6){Console.WriteLine(sv.VarName+" = "+sv.X);}}// Dispose of model and environmentmodel.Dispose();env.Dispose();}catch(GRBExceptione){Console.WriteLine("Error code: "+e.ErrorCode+". "+e.Message);}}}
/* Copyright 2025, Gurobi Optimization, LLC *//* Assign workers to shifts; each worker may or may not be available on a particular day. If the problem cannot be solved, relax the model to determine which constraints cannot be satisfied, and how much they need to be relaxed. */importcom.gurobi.gurobi.*;publicclassWorkforce3{publicstaticvoidmain(String[]args){try{// Sample data// Sets of days and workersStringShifts[]=newString[]{"Mon1","Tue2","Wed3","Thu4","Fri5","Sat6","Sun7","Mon8","Tue9","Wed10","Thu11","Fri12","Sat13","Sun14"};StringWorkers[]=newString[]{"Amy","Bob","Cathy","Dan","Ed","Fred","Gu"};intnShifts=Shifts.length;intnWorkers=Workers.length;// Number of workers required for each shiftdoubleshiftRequirements[]=newdouble[]{3,2,4,4,5,6,5,2,2,3,4,6,7,5};// Amount each worker is paid to work one shiftdoublepay[]=newdouble[]{10,12,10,8,8,9,11};// Worker availability: 0 if the worker is unavailable for a shiftdoubleavailability[][]=newdouble[][]{{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},{1,1,1,0,1,1,1,1,1,1,1,1,1,1}};// ModelGRBEnvenv=newGRBEnv();GRBModelmodel=newGRBModel(env);model.set(GRB.StringAttr.ModelName,"assignment");// Assignment variables: x[w][s] == 1 if worker w is assigned// to shift s. Since an assignment model always produces integer// solutions, we use continuous variables and solve as an LP.GRBVar[][]x=newGRBVar[nWorkers][nShifts];for(intw=0;w<nWorkers;++w){for(ints=0;s<nShifts;++s){x[w][s]=model.addVar(0,availability[w][s],pay[w],GRB.CONTINUOUS,Workers[w]+"."+Shifts[s]);}}// The objective is to minimize the total pay costsmodel.set(GRB.IntAttr.ModelSense,GRB.MINIMIZE);// Constraint: assign exactly shiftRequirements[s] workers// to each shift sfor(ints=0;s<nShifts;++s){GRBLinExprlhs=newGRBLinExpr();for(intw=0;w<nWorkers;++w){lhs.addTerm(1.0,x[w][s]);}model.addConstr(lhs,GRB.EQUAL,shiftRequirements[s],Shifts[s]);}// Optimizemodel.optimize();intstatus=model.get(GRB.IntAttr.Status);if(status==GRB.UNBOUNDED){System.out.println("The model cannot be solved "+"because it is unbounded");return;}if(status==GRB.OPTIMAL){System.out.println("The optimal objective is "+model.get(GRB.DoubleAttr.ObjVal));return;}if(status!=GRB.INF_OR_UNBD&&status!=GRB.INFEASIBLE){System.out.println("Optimization was stopped with status "+status);return;}// Relax the constraints to make the model feasibleSystem.out.println("The model is infeasible; relaxing the constraints");intorignumvars=model.get(GRB.IntAttr.NumVars);model.feasRelax(0,false,false,true);model.optimize();status=model.get(GRB.IntAttr.Status);if(status==GRB.INF_OR_UNBD||status==GRB.INFEASIBLE||status==GRB.UNBOUNDED){System.out.println("The relaxed model cannot be solved "+"because it is infeasible or unbounded");return;}if(status!=GRB.OPTIMAL){System.out.println("Optimization was stopped with status "+status);return;}System.out.println("\nSlack values:");GRBVar[]vars=model.getVars();for(inti=orignumvars;i<model.get(GRB.IntAttr.NumVars);++i){GRBVarsv=vars[i];if(sv.get(GRB.DoubleAttr.X)>1e-6){System.out.println(sv.get(GRB.StringAttr.VarName)+" = "+sv.get(GRB.DoubleAttr.X));}}// Dispose of model and environmentmodel.dispose();env.dispose();}catch(GRBExceptione){System.out.println("Error code: "+e.getErrorCode()+". "+e.getMessage());}}}
functionworkforce3()% Copyright 2025, Gurobi Optimization, LLC%% Assign workers to shifts; each worker may or may not be available on a% particular day. If the problem cannot be solved, relax the model% to determine which constraints cannot be satisfied, and how much% they need to be relaxed.% define datanShifts=14;nWorkers=7;nVars=nShifts*nWorkers;Shifts={'Mon1';'Tue2';'Wed3';'Thu4';'Fri5';'Sat6';'Sun7';'Mon8';'Tue9';'Wed10';'Thu11';'Fri12';'Sat13';'Sun14'};Workers={'Amy';'Bob';'Cathy';'Dan';'Ed';'Fred';'Gu'};pay=[10;12;10;8;8;9;11];shiftRequirements=[3;2;4;4;5;6;5;2;2;3;4;6;7;5];availability=[01101010111111;11001101001010;00111011111111;01101101111111;11111011101011;11100101100111;11101111111111];% Build modelmodel.modelname='workforce3';model.modelsense='min';% Initialize assignment decision variables:% x[w][s] == 1 if worker w is assigned% to shift s. Since an assignment model always produces integer% solutions, we use continuous variables and solve as an LP.model.ub=ones(nVars,1);model.obj=zeros(nVars,1);forw=1:nWorkersfors=1:nShiftsmodel.varnames{s+(w-1)*nShifts}=sprintf('%s.%s',Workers{w},Shifts{s});model.obj(s+(w-1)*nShifts)=pay(w);ifavailability(w,s)==0model.ub(s+(w-1)*nShifts)=0;endendend% Set-up shift-requirements constraintsmodel.sense=repmat('=',nShifts,1);model.rhs=shiftRequirements;model.constrnames=Shifts;model.A=sparse(nShifts,nVars);fors=1:nShiftsforw=1:nWorkersmodel.A(s,s+(w-1)*nShifts)=1;endend% Save modelgurobi_write(model,'workforce3_m.lp');% Optimizeparams.logfile='workforce3_m.log';result=gurobi(model,params);% Display resultsifstrcmp(result.status,'OPTIMAL')% The code may enter here if you change some of the data... otherwise% this will never be executed.printsolution(result,Shifts,Workers)elseifstrcmp(result.status,'INFEASIBLE')penalties.lb=inf(nVars,1);penalties.ub=inf(nVars,1);penalties.rhs=ones(nShifts,1);feasrelax=gurobi_feasrelax(model,0,false,penalties,params);result=gurobi(feasrelax.model,params);ifstrcmp(result.status,'OPTIMAL')printsolution(result,Shifts,Workers);fprintf('Slack value:\n');forj=nVars+1:length(result.x)ifresult.x(j)>0.1fprintf('\t%s, %g\n',feasrelax.model.varnames{j},result.x(j));endendelsefprintf('Unexpected status %s\n',result.status);endelse% Just to handle user interruptions or other problemsfprintf('Unexpected status %s\n',result.status);endendendfunctionprintsolution(result, Shifts, Workers)% Helper function to display resultsnShifts=length(Shifts);nWorkers=length(Workers);fprintf('The optimal objective is %g\n',result.objval);fprintf('Schedule:\n');fors=1:nShiftsfprintf('\t%s:',Shifts{s});forw=1:nWorkersifresult.x(s+(w-1)*nShifts)>0.9fprintf('%s ',Workers{w});endendfprintf('\n');endend
#!/usr/bin/env python3# Copyright 2025, Gurobi Optimization, LLC# Assign workers to shifts; each worker may or may not be available on a# particular day. If the problem cannot be solved, relax the model# to determine which constraints cannot be satisfied, and how much# they need to be relaxed.importgurobipyasgpfromgurobipyimportGRBimportsys# Number of workers required for each shiftshifts,shiftRequirements=gp.multidict({"Mon1":3,"Tue2":2,"Wed3":4,"Thu4":4,"Fri5":5,"Sat6":6,"Sun7":5,"Mon8":2,"Tue9":2,"Wed10":3,"Thu11":4,"Fri12":6,"Sat13":7,"Sun14":5,})# Amount each worker is paid to work one shiftworkers,pay=gp.multidict({"Amy":10,"Bob":12,"Cathy":10,"Dan":8,"Ed":8,"Fred":9,"Gu":11})# Worker availabilityavailability=gp.tuplelist([("Amy","Tue2"),("Amy","Wed3"),("Amy","Fri5"),("Amy","Sun7"),("Amy","Tue9"),("Amy","Wed10"),("Amy","Thu11"),("Amy","Fri12"),("Amy","Sat13"),("Amy","Sun14"),("Bob","Mon1"),("Bob","Tue2"),("Bob","Fri5"),("Bob","Sat6"),("Bob","Mon8"),("Bob","Thu11"),("Bob","Sat13"),("Cathy","Wed3"),("Cathy","Thu4"),("Cathy","Fri5"),("Cathy","Sun7"),("Cathy","Mon8"),("Cathy","Tue9"),("Cathy","Wed10"),("Cathy","Thu11"),("Cathy","Fri12"),("Cathy","Sat13"),("Cathy","Sun14"),("Dan","Tue2"),("Dan","Wed3"),("Dan","Fri5"),("Dan","Sat6"),("Dan","Mon8"),("Dan","Tue9"),("Dan","Wed10"),("Dan","Thu11"),("Dan","Fri12"),("Dan","Sat13"),("Dan","Sun14"),("Ed","Mon1"),("Ed","Tue2"),("Ed","Wed3"),("Ed","Thu4"),("Ed","Fri5"),("Ed","Sun7"),("Ed","Mon8"),("Ed","Tue9"),("Ed","Thu11"),("Ed","Sat13"),("Ed","Sun14"),("Fred","Mon1"),("Fred","Tue2"),("Fred","Wed3"),("Fred","Sat6"),("Fred","Mon8"),("Fred","Tue9"),("Fred","Fri12"),("Fred","Sat13"),("Fred","Sun14"),("Gu","Mon1"),("Gu","Tue2"),("Gu","Wed3"),("Gu","Fri5"),("Gu","Sat6"),("Gu","Sun7"),("Gu","Mon8"),("Gu","Tue9"),("Gu","Wed10"),("Gu","Thu11"),("Gu","Fri12"),("Gu","Sat13"),("Gu","Sun14"),])# Modelm=gp.Model("assignment")# Assignment variables: x[w,s] == 1 if worker w is assigned to shift s.# Since an assignment model always produces integer solutions, we use# continuous variables and solve as an LP.x=m.addVars(availability,ub=1,name="x")# The objective is to minimize the total pay costsm.setObjective(gp.quicksum(pay[w]*x[w,s]forw,sinavailability),GRB.MINIMIZE)# Constraint: assign exactly shiftRequirements[s] workers to each shift sreqCts=m.addConstrs((x.sum("*",s)==shiftRequirements[s]forsinshifts),"_")# Optimizem.optimize()status=m.Statusifstatus==GRB.UNBOUNDED:print("The model cannot be solved because it is unbounded")sys.exit(0)ifstatus==GRB.OPTIMAL:print(f"The optimal objective is{m.ObjVal:g}")sys.exit(0)ifstatus!=GRB.INF_OR_UNBDandstatus!=GRB.INFEASIBLE:print(f"Optimization was stopped with status{status}")sys.exit(0)# Relax the constraints to make the model feasibleprint("The model is infeasible; relaxing the constraints")orignumvars=m.NumVarsm.feasRelaxS(0,False,False,True)m.optimize()status=m.Statusifstatusin(GRB.INF_OR_UNBD,GRB.INFEASIBLE,GRB.UNBOUNDED):print("The relaxed model cannot be solved\ because it is infeasible or unbounded")sys.exit(1)ifstatus!=GRB.OPTIMAL:print(f"Optimization was stopped with status{status}")sys.exit(1)print("\nSlack values:")slacks=m.getVars()[orignumvars:]forsvinslacks:ifsv.X>1e-6:print(f"{sv.VarName} ={sv.X:g}")
# Copyright 2025, Gurobi Optimization, LLC## Assign workers to shifts; each worker may or may not be available on a# particular day. If the problem cannot be solved, relax the model# to determine which constraints cannot be satisfied, and how much# they need to be relaxed.library(Matrix)library(gurobi)# Function to display resultsprintsolution<-function(result){if(result$status=='OPTIMAL'){cat('The optimal objective is',result$objval,'\n')cat('Schedule:\n')for(sin1:nShifts){cat('\t',Shifts[s],':')for(win1:nWorkers){if(result$x[varIdx(w,s)]>0.9)cat(Workers[w],' ')}cat('\n')}}}# define datanShifts<-14nWorkers<-7nVars<-nShifts*nWorkersvarIdx<-function(w,s){s+(w-1)*nShifts}Shifts<-c('Mon1','Tue2','Wed3','Thu4','Fri5','Sat6','Sun7','Mon8','Tue9','Wed10','Thu11','Fri12','Sat13','Sun14')Workers<-c('Amy','Bob','Cathy','Dan','Ed','Fred','Gu')pay<-c(10,12,10,8,8,9,11)shiftRequirements<-c(3,2,4,4,5,6,5,2,2,3,4,6,7,5)availability<-list(c(0,1,1,0,1,0,1,0,1,1,1,1,1,1),c(1,1,0,0,1,1,0,1,0,0,1,0,1,0),c(0,0,1,1,1,0,1,1,1,1,1,1,1,1),c(0,1,1,0,1,1,0,1,1,1,1,1,1,1),c(1,1,1,1,1,0,1,1,1,0,1,0,1,1),c(1,1,1,0,0,1,0,1,1,0,0,1,1,1),c(1,1,1,0,1,1,1,1,1,1,1,1,1,1))# Set up parametersparams<-list()params$logfile<-'workforce3.log'# Build modelmodel<-list()model$modelname<-'workforce3'model$modelsense<-'min'# Initialize assignment decision variables:# x[w][s] == 1 if worker w is assigned# to shift s. Since an assignment model always produces integer# solutions, we use continuous variables and solve as an LP.model$lb<-0model$ub<-rep(1,nVars)model$obj<-rep(0,nVars)model$varnames<-rep('',nVars)for(win1:nWorkers){for(sin1:nShifts){model$varnames[varIdx(w,s)]=paste0(Workers[w],'.',Shifts[s])model$obj[varIdx(w,s)]=pay[w]if(availability[[w]][s]==0)model$ub[varIdx(w,s)]=0}}# Set up shift-requirements constraintsmodel$A<-spMatrix(nShifts,nVars,i=c(mapply(rep,1:nShifts,nWorkers)),j=mapply(varIdx,1:nWorkers,mapply(rep,1:nShifts,nWorkers)),x=rep(1,nShifts*nWorkers))model$sense<-rep('=',nShifts)model$rhs<-shiftRequirementsmodel$constrnames<-Shifts# Save modelgurobi_write(model,'workforce3.lp',params)# Optimizeresult<-gurobi(model,params=params)# Display resultsif(result$status=='OPTIMAL'){# The code may enter here if you change some of the data... otherwise# this will never be executed.printsolution(result);}elseif(result$status=='INFEASIBLE'){# Use gurobi_feasrelax to find out which copnstraints should be relaxed# and by how much to make the problem feasible.penalties<-list()penalties$lb<-Infpenalties$ub<-Infpenalties$rhs<-rep(1,length(model$rhs))feasrelax<-gurobi_feasrelax(model,0,FALSE,penalties,params=params)result<-gurobi(feasrelax$model,params=params)if(result$status=='OPTIMAL'){printsolution(result)cat('Slack values:\n')for(jin(nVars+1):length(result$x)){if(result$x[j]>0.1)cat('\t',feasrelax$model$varnames[j],result$x[j],'\n')}}else{cat('Unexpected status',result$status,'\nEnding now\n')}rm(penalties,feasrelax)}else{# Just to handle user interruptions or other problemscat('Unexpected status',result$status,'\nEnding now\n')}#Clear spacerm(model,params,availability,Shifts,Workers,pay,shiftRequirements,result)
' Copyright 2025, Gurobi Optimization, LLC' Assign workers to shifts; each worker may or may not be available on a' particular day. If the problem cannot be solved, relax the model' to determine which constraints cannot be satisfied, and how much' they need to be relaxed.ImportsSystemImportsGurobiClassworkforce3_vbSharedSubMain()Try' Sample data' Sets of days and workersDimShiftsAsString()=NewString(){"Mon1","Tue2","Wed3","Thu4",_"Fri5","Sat6","Sun7","Mon8",_"Tue9","Wed10","Thu11",_"Fri12","Sat13","Sun14"}DimWorkersAsString()=NewString(){"Amy","Bob","Cathy","Dan",_"Ed","Fred","Gu"}DimnShiftsAsInteger=Shifts.LengthDimnWorkersAsInteger=Workers.Length' Number of workers required for each shiftDimshiftRequirementsAsDouble()=NewDouble(){3,2,4,4,5,6,_5,2,2,3,4,6,_7,5}' Amount each worker is paid to work one shiftDimpayAsDouble()=NewDouble(){10,12,10,8,8,9,11}' Worker availability: 0 if the worker is unavailable for a shiftDimavailabilityAsDouble(,)=NewDouble(,){_{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},_{1,1,1,0,1,1,1,1,1,1,1,1,1,1}}' ModelDimenvAsNewGRBEnv()DimmodelAsNewGRBModel(env)model.ModelName="assignment"' Assignment variables: x[w][s] == 1 if worker w is assigned' to shift s. Since an assignment model always produces integer' solutions, we use continuous variables and solve as an LP.DimxAsGRBVar(,)=NewGRBVar(nWorkers-1,nShifts-1){}ForwAsInteger=0TonWorkers-1ForsAsInteger=0TonShifts-1x(w,s)=model.AddVar(0,availability(w,s),pay(w),_GRB.CONTINUOUS,_Workers(w)&"."&Shifts(s))NextNext' The objective is to minimize the total pay costsmodel.ModelSense=GRB.MINIMIZE' Constraint: assign exactly shiftRequirements[s] workers' to each shift sForsAsInteger=0TonShifts-1DimlhsAsGRBLinExpr=0.0ForwAsInteger=0TonWorkers-1lhs.AddTerm(1.0,x(w,s))Nextmodel.AddConstr(lhs=shiftRequirements(s),Shifts(s))Next' Optimizemodel.Optimize()DimstatusAsInteger=model.StatusIfstatus=GRB.Status.UNBOUNDEDThenConsole.WriteLine("The model cannot be solved "&_"because it is unbounded")ReturnEndIfIfstatus=GRB.Status.OPTIMALThenConsole.WriteLine("The optimal objective is "&model.ObjVal)ReturnEndIfIf(status<>GRB.Status.INF_OR_UNBD)AndAlso_(status<>GRB.Status.INFEASIBLE)ThenConsole.WriteLine("Optimization was stopped with status "&_status)ReturnEndIf' Relax the constraints to make the model feasibleConsole.WriteLine("The model is infeasible; relaxing the constraints")DimorignumvarsAsInteger=model.NumVarsmodel.FeasRelax(0,False,False,True)model.Optimize()status=model.StatusIf(status=GRB.Status.INF_OR_UNBD)OrElse_(status=GRB.Status.INFEASIBLE)OrElse_(status=GRB.Status.UNBOUNDED)ThenConsole.WriteLine("The relaxed model cannot be solved "&_"because it is infeasible or unbounded")ReturnEndIfIfstatus<>GRB.Status.OPTIMALThenConsole.WriteLine("Optimization was stopped with status "&status)ReturnEndIfConsole.WriteLine(vbLf&"Slack values:")DimvarsAsGRBVar()=model.GetVars()ForiAsInteger=orignumvarsTomodel.NumVars-1DimsvAsGRBVar=vars(i)Ifsv.X>1E-06ThenConsole.WriteLine(sv.VarName&" = "&sv.X)EndIfNext' Dispose of model and environmentmodel.Dispose()env.Dispose()CatcheAsGRBExceptionConsole.WriteLine("Error code: "&e.ErrorCode&". "&e.Message)EndTryEndSubEndClass
/* Copyright 2025, Gurobi Optimization, LLC *//* Assign workers to shifts; each worker may or may not be available on a particular day. We use Pareto optimization to solve the model: first, we minimize the linear sum of the slacks. Then, we constrain the sum of the slacks, and we minimize a quadratic objective that tries to balance the workload among the workers. */#include<stdlib.h>#include<stdio.h>#include<math.h>#include<string.h>#include"gurobi_c.h"intsolveAndPrint(GRBmodel*model,intnShifts,intnWorkers,char**Workers,int*status);#define xcol(w,s) nShifts*w+s#define slackcol(s) nShifts*nWorkers+s#define totSlackcol nShifts*(nWorkers+1)#define totShiftscol(w) nShifts*(nWorkers+1)+1+w#define avgShiftscol (nShifts+1)*(nWorkers+1)#define diffShiftscol(w) (nShifts+1)*(nWorkers+1)+1+w#define MAXSTR 128intmain(intargc,char*argv[]){GRBenv*env=NULL;GRBmodel*model=NULL;interror=0,status;ints,w,col;int*cbeg=NULL;int*cind=NULL;intidx;double*cval=NULL;char*sense=NULL;charvname[MAXSTR],cname[MAXSTR];doubleval;/* Sample data */constintnShifts=14;constintnWorkers=7;/* Sets of days and workers */char*Shifts[]={"Mon1","Tue2","Wed3","Thu4","Fri5","Sat6","Sun7","Mon8","Tue9","Wed10","Thu11","Fri12","Sat13","Sun14"};char*Workers[]={"Amy","Bob","Cathy","Dan","Ed","Fred","Gu"};/* Number of workers required for each shift */doubleshiftRequirements[]={3,2,4,4,5,6,5,2,2,3,4,6,7,5};/* Worker availability: 0 if the worker is unavailable for a shift */doubleavailability[][14]={{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},{1,1,1,0,1,1,1,1,1,1,1,1,1,1}};/* Create environment */error=GRBloadenv(&env,"workforce4.log");if(error)gotoQUIT;/* Create initial model */error=GRBnewmodel(env,&model,"workforce4",(nShifts+1)*(nWorkers+1),NULL,NULL,NULL,NULL,NULL);if(error)gotoQUIT;/* 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. */for(w=0;w<nWorkers;++w){for(s=0;s<nShifts;++s){col=xcol(w,s);sprintf(vname,"%s.%s",Workers[w],Shifts[s]);error=GRBsetcharattrelement(model,"VType",col,GRB_BINARY);if(error)gotoQUIT;error=GRBsetdblattrelement(model,"UB",col,availability[w][s]);if(error)gotoQUIT;error=GRBsetstrattrelement(model,"VarName",col,vname);if(error)gotoQUIT;}}/* Initialize slack decision variables */for(s=0;s<nShifts;++s){sprintf(vname,"%sSlack",Shifts[s]);error=GRBsetstrattrelement(model,"VarName",slackcol(s),vname);if(error)gotoQUIT;}/* Initialize total slack decision variable */error=GRBsetstrattrelement(model,"VarName",totSlackcol,"totSlack");if(error)gotoQUIT;/* Initialize variables to count the total shifts worked by each worker */for(w=0;w<nWorkers;++w){sprintf(vname,"%sTotShifts",Workers[w]);error=GRBsetstrattrelement(model,"VarName",totShiftscol(w),vname);if(error)gotoQUIT;}/* The objective is to minimize the sum of the slacks */error=GRBsetintattr(model,"ModelSense",GRB_MINIMIZE);if(error)gotoQUIT;error=GRBsetdblattrelement(model,"Obj",totSlackcol,1.0);if(error)gotoQUIT;/* Make space for constraint data */cbeg=malloc(sizeof(int)*nShifts);if(!cbeg)gotoQUIT;cind=malloc(sizeof(int)*nShifts*(nWorkers+1));if(!cind)gotoQUIT;cval=malloc(sizeof(double)*nShifts*(nWorkers+1));if(!cval)gotoQUIT;sense=malloc(sizeof(char)*nShifts);if(!sense)gotoQUIT;/* Constraint: assign exactly shiftRequirements[s] workers to each shift s, plus the slack */idx=0;for(s=0;s<nShifts;++s){cbeg[s]=idx;sense[s]=GRB_EQUAL;for(w=0;w<nWorkers;++w){cind[idx]=xcol(w,s);cval[idx++]=1.0;}cind[idx]=slackcol(s);cval[idx++]=1.0;}error=GRBaddconstrs(model,nShifts,idx,cbeg,cind,cval,sense,shiftRequirements,Shifts);if(error)gotoQUIT;/* Constraint: set totSlack column equal to the total slack */idx=0;for(s=0;s<nShifts;++s){cind[idx]=slackcol(s);cval[idx++]=1.0;}cind[idx]=totSlackcol;cval[idx++]=-1.0;error=GRBaddconstr(model,idx,cind,cval,GRB_EQUAL,0.0,"totSlack");if(error)gotoQUIT;/* Constraint: compute the total number of shifts for each worker */for(w=0;w<nWorkers;++w){idx=0;for(s=0;s<nShifts;++s){cind[idx]=xcol(w,s);cval[idx++]=1.0;}sprintf(cname,"totShifts%s",Workers[w]);cind[idx]=totShiftscol(w);cval[idx++]=-1.0;error=GRBaddconstr(model,idx,cind,cval,GRB_EQUAL,0.0,cname);if(error)gotoQUIT;}/* Optimize */error=solveAndPrint(model,nShifts,nWorkers,Workers,&status);if(error)gotoQUIT;if(status!=GRB_OPTIMAL)gotoQUIT;/* Constrain the slack by setting its upper and lower bounds */error=GRBgetdblattrelement(model,"X",totSlackcol,&val);if(error)gotoQUIT;error=GRBsetdblattrelement(model,"UB",totSlackcol,val);if(error)gotoQUIT;error=GRBsetdblattrelement(model,"LB",totSlackcol,val);if(error)gotoQUIT;/* Variable to count the average number of shifts worked */error=GRBaddvar(model,0,NULL,NULL,0,0,GRB_INFINITY,GRB_CONTINUOUS,"avgShifts");if(error)gotoQUIT;/* Variables to count the difference from average for each worker; note that these variables can take negative values. */error=GRBaddvars(model,nWorkers,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);if(error)gotoQUIT;for(w=0;w<nWorkers;++w){sprintf(vname,"%sDiff",Workers[w]);error=GRBsetstrattrelement(model,"VarName",diffShiftscol(w),vname);if(error)gotoQUIT;error=GRBsetdblattrelement(model,"LB",diffShiftscol(w),-GRB_INFINITY);if(error)gotoQUIT;}/* Constraint: compute the average number of shifts worked */idx=0;for(w=0;w<nWorkers;++w){cind[idx]=totShiftscol(w);cval[idx++]=1.0;}cind[idx]=avgShiftscol;cval[idx++]=-nWorkers;error=GRBaddconstr(model,idx,cind,cval,GRB_EQUAL,0.0,"avgShifts");if(error)gotoQUIT;/* Constraint: compute the difference from the average number of shifts */for(w=0;w<nWorkers;++w){cind[0]=totShiftscol(w);cval[0]=1.0;cind[1]=avgShiftscol;cval[1]=-1.0;cind[2]=diffShiftscol(w);cval[2]=-1.0;sprintf(cname,"%sDiff",Workers[w]);error=GRBaddconstr(model,3,cind,cval,GRB_EQUAL,0.0,cname);if(error)gotoQUIT;}/* Objective: minimize the sum of the square of the difference from the average number of shifts worked */error=GRBsetdblattrelement(model,"Obj",totSlackcol,0.0);if(error)gotoQUIT;for(w=0;w<nWorkers;++w){cind[w]=diffShiftscol(w);cval[w]=1.0;}error=GRBaddqpterms(model,nWorkers,cind,cind,cval);if(error)gotoQUIT;/* Optimize */error=solveAndPrint(model,nShifts,nWorkers,Workers,&status);if(error)gotoQUIT;if(status!=GRB_OPTIMAL)gotoQUIT;QUIT:/* Error reporting */if(error){printf("ERROR: %s\n",GRBgeterrormsg(env));exit(1);}/* Free data */free(cbeg);free(cind);free(cval);free(sense);/* Free model */GRBfreemodel(model);/* Free environment */GRBfreeenv(env);return0;}intsolveAndPrint(GRBmodel*model,intnShifts,intnWorkers,char**Workers,int*status){interror,w;doubleval;error=GRBoptimize(model);if(error)returnerror;error=GRBgetintattr(model,"Status",status);if(error)returnerror;if((*status==GRB_INF_OR_UNBD)||(*status==GRB_INFEASIBLE)||(*status==GRB_UNBOUNDED)){printf("The model cannot be solved ""because it is infeasible or unbounded\n");return0;}if(*status!=GRB_OPTIMAL){printf("Optimization was stopped with status %i\n",*status);return0;}/* Print total slack and the number of shifts worked for each worker */error=GRBgetdblattrelement(model,"X",totSlackcol,&val);if(error)returnerror;printf("\nTotal slack required: %f\n",val);for(w=0;w<nWorkers;++w){error=GRBgetdblattrelement(model,"X",totShiftscol(w),&val);if(error)returnerror;printf("%s worked %f shifts\n",Workers[w],val);}printf("\n");return0;}
/* Copyright 2025, Gurobi Optimization, LLC *//* Assign workers to shifts; each worker may or may not be available on a * particular day. We use Pareto optimization to solve the model: * first, we minimize the linear sum of the slacks. Then, we constrain * the sum of the slacks, and we minimize a quadratic objective that * tries to balance the workload among the workers. */#include"gurobi_c++.h"#include<sstream>usingnamespacestd;intsolveAndPrint(GRBModel&model,GRBVar&totSlack,intnWorkers,string*Workers,GRBVar*totShifts);intmain(intargc,char*argv[]){GRBEnv*env=0;GRBVar**x=0;GRBVar*slacks=0;GRBVar*totShifts=0;GRBVar*diffShifts=0;intxCt=0;try{// Sample dataconstintnShifts=14;constintnWorkers=7;// Sets of days and workersstringShifts[]={"Mon1","Tue2","Wed3","Thu4","Fri5","Sat6","Sun7","Mon8","Tue9","Wed10","Thu11","Fri12","Sat13","Sun14"};stringWorkers[]={"Amy","Bob","Cathy","Dan","Ed","Fred","Gu"};// Number of workers required for each shiftdoubleshiftRequirements[]={3,2,4,4,5,6,5,2,2,3,4,6,7,5};// Worker availability: 0 if the worker is unavailable for a shiftdoubleavailability[][nShifts]={{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},{1,1,1,0,1,1,1,1,1,1,1,1,1,1}};// Modelenv=newGRBEnv();GRBModelmodel=GRBModel(*env);model.set(GRB_StringAttr_ModelName,"assignment");// Assignment 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=newGRBVar*[nWorkers];ints,w;for(w=0;w<nWorkers;++w){x[w]=model.addVars(nShifts);xCt++;for(s=0;s<nShifts;++s){ostringstreamvname;vname<<Workers[w]<<"."<<Shifts[s];x[w][s].set(GRB_DoubleAttr_UB,availability[w][s]);x[w][s].set(GRB_CharAttr_VType,GRB_BINARY);x[w][s].set(GRB_StringAttr_VarName,vname.str());}}// Slack variables for each shift constraint so that the shifts can// be satisfiedslacks=model.addVars(nShifts);for(s=0;s<nShifts;++s){ostringstreamvname;vname<<Shifts[s]<<"Slack";slacks[s].set(GRB_StringAttr_VarName,vname.str());}// Variable to represent the total slackGRBVartotSlack=model.addVar(0,GRB_INFINITY,0,GRB_CONTINUOUS,"totSlack");// Variables to count the total shifts worked by each workertotShifts=model.addVars(nWorkers);for(w=0;w<nWorkers;++w){ostringstreamvname;vname<<Workers[w]<<"TotShifts";totShifts[w].set(GRB_StringAttr_VarName,vname.str());}GRBLinExprlhs;// Constraint: assign exactly shiftRequirements[s] workers// to each shift sfor(s=0;s<nShifts;++s){lhs=0;lhs+=slacks[s];for(w=0;w<nWorkers;++w){lhs+=x[w][s];}model.addConstr(lhs==shiftRequirements[s],Shifts[s]);}// Constraint: set totSlack equal to the total slacklhs=0;for(s=0;s<nShifts;++s){lhs+=slacks[s];}model.addConstr(lhs==totSlack,"totSlack");// Constraint: compute the total number of shifts for each workerfor(w=0;w<nWorkers;++w){lhs=0;for(s=0;s<nShifts;++s){lhs+=x[w][s];}ostringstreamvname;vname<<"totShifts"<<Workers[w];model.addConstr(lhs==totShifts[w],vname.str());}// Objective: minimize the total slackGRBLinExprobj=0;obj+=totSlack;model.setObjective(obj);// Optimizeintstatus=solveAndPrint(model,totSlack,nWorkers,Workers,totShifts);if(status!=GRB_OPTIMAL)return1;// Constrain the slack by setting its upper and lower boundstotSlack.set(GRB_DoubleAttr_UB,totSlack.get(GRB_DoubleAttr_X));totSlack.set(GRB_DoubleAttr_LB,totSlack.get(GRB_DoubleAttr_X));// Variable to count the average number of shifts workedGRBVaravgShifts=model.addVar(0,GRB_INFINITY,0,GRB_CONTINUOUS,"avgShifts");// Variables to count the difference from average for each worker;// note that these variables can take negative values.diffShifts=model.addVars(nWorkers);for(w=0;w<nWorkers;++w){ostringstreamvname;vname<<Workers[w]<<"Diff";diffShifts[w].set(GRB_StringAttr_VarName,vname.str());diffShifts[w].set(GRB_DoubleAttr_LB,-GRB_INFINITY);}// Constraint: compute the average number of shifts workedlhs=0;for(w=0;w<nWorkers;++w){lhs+=totShifts[w];}model.addConstr(lhs==nWorkers*avgShifts,"avgShifts");// Constraint: compute the difference from the average number of shiftsfor(w=0;w<nWorkers;++w){lhs=0;lhs+=totShifts[w];lhs-=avgShifts;ostringstreamvname;vname<<Workers[w]<<"Diff";model.addConstr(lhs==diffShifts[w],vname.str());}// Objective: minimize the sum of the square of the difference from the// average number of shifts workedGRBQuadExprqobj;for(w=0;w<nWorkers;++w){qobj+=diffShifts[w]*diffShifts[w];}model.setObjective(qobj);// Optimizestatus=solveAndPrint(model,totSlack,nWorkers,Workers,totShifts);if(status!=GRB_OPTIMAL)return1;}catch(GRBExceptione){cout<<"Error code = "<<e.getErrorCode()<<endl;cout<<e.getMessage()<<endl;}catch(...){cout<<"Exception during optimization"<<endl;}for(inti=0;i<xCt;++i){delete[]x[i];}delete[]x;delete[]slacks;delete[]totShifts;delete[]diffShifts;deleteenv;return0;}intsolveAndPrint(GRBModel&model,GRBVar&totSlack,intnWorkers,string*Workers,GRBVar*totShifts){model.optimize();intstatus=model.get(GRB_IntAttr_Status);if((status==GRB_INF_OR_UNBD)||(status==GRB_INFEASIBLE)||(status==GRB_UNBOUNDED)){cout<<"The model cannot be solved "<<"because it is infeasible or unbounded"<<endl;returnstatus;}if(status!=GRB_OPTIMAL){cout<<"Optimization was stopped with status "<<status<<endl;returnstatus;}// Print total slack and the number of shifts worked for each workercout<<endl<<"Total slack required: "<<totSlack.get(GRB_DoubleAttr_X)<<endl;for(intw=0;w<nWorkers;++w){cout<<Workers[w]<<" worked "<<totShifts[w].get(GRB_DoubleAttr_X)<<" shifts"<<endl;}cout<<endl;returnstatus;}
/* Copyright 2025, Gurobi Optimization, LLC *//* Assign workers to shifts; each worker may or may not be available on a particular day. We use Pareto optimization to solve the model: first, we minimize the linear sum of the slacks. Then, we constrain the sum of the slacks, and we minimize a quadratic objective that tries to balance the workload among the workers. */usingSystem;usingGurobi;classworkforce4_cs{staticvoidMain(){try{// Sample data// Sets of days and workersstring[]Shifts=newstring[]{"Mon1","Tue2","Wed3","Thu4","Fri5","Sat6","Sun7","Mon8","Tue9","Wed10","Thu11","Fri12","Sat13","Sun14"};string[]Workers=newstring[]{"Amy","Bob","Cathy","Dan","Ed","Fred","Gu"};intnShifts=Shifts.Length;intnWorkers=Workers.Length;// Number of workers required for each shiftdouble[]shiftRequirements=newdouble[]{3,2,4,4,5,6,5,2,2,3,4,6,7,5};// Worker availability: 0 if the worker is unavailable for a shiftdouble[,]availability=newdouble[,]{{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},{1,1,1,0,1,1,1,1,1,1,1,1,1,1}};// ModelGRBEnvenv=newGRBEnv();GRBModelmodel=newGRBModel(env);model.ModelName="assignment";// Assignment 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.GRBVar[,]x=newGRBVar[nWorkers,nShifts];for(intw=0;w<nWorkers;++w){for(ints=0;s<nShifts;++s){x[w,s]=model.AddVar(0,availability[w,s],0,GRB.BINARY,Workers[w]+"."+Shifts[s]);}}// Slack variables for each shift constraint so that the shifts can// be satisfiedGRBVar[]slacks=newGRBVar[nShifts];for(ints=0;s<nShifts;++s){slacks[s]=model.AddVar(0,GRB.INFINITY,0,GRB.CONTINUOUS,Shifts[s]+"Slack");}// Variable to represent the total slackGRBVartotSlack=model.AddVar(0,GRB.INFINITY,0,GRB.CONTINUOUS,"totSlack");// Variables to count the total shifts worked by each workerGRBVar[]totShifts=newGRBVar[nWorkers];for(intw=0;w<nWorkers;++w){totShifts[w]=model.AddVar(0,GRB.INFINITY,0,GRB.CONTINUOUS,Workers[w]+"TotShifts");}GRBLinExprlhs;// Constraint: assign exactly shiftRequirements[s] workers// to each shift s, plus the slackfor(ints=0;s<nShifts;++s){lhs=newGRBLinExpr();lhs.AddTerm(1.0,slacks[s]);for(intw=0;w<nWorkers;++w){lhs.AddTerm(1.0,x[w,s]);}model.AddConstr(lhs==shiftRequirements[s],Shifts[s]);}// Constraint: set totSlack equal to the total slacklhs=newGRBLinExpr();for(ints=0;s<nShifts;++s){lhs.AddTerm(1.0,slacks[s]);}model.AddConstr(lhs==totSlack,"totSlack");// Constraint: compute the total number of shifts for each workerfor(intw=0;w<nWorkers;++w){lhs=newGRBLinExpr();for(ints=0;s<nShifts;++s){lhs.AddTerm(1.0,x[w,s]);}model.AddConstr(lhs==totShifts[w],"totShifts"+Workers[w]);}// Objective: minimize the total slackmodel.SetObjective(1.0*totSlack);// Optimizeintstatus=solveAndPrint(model,totSlack,nWorkers,Workers,totShifts);if(status!=GRB.Status.OPTIMAL){return;}// Constrain the slack by setting its upper and lower boundstotSlack.UB=totSlack.X;totSlack.LB=totSlack.X;// Variable to count the average number of shifts workedGRBVaravgShifts=model.AddVar(0,GRB.INFINITY,0,GRB.CONTINUOUS,"avgShifts");// Variables to count the difference from average for each worker;// note that these variables can take negative values.GRBVar[]diffShifts=newGRBVar[nWorkers];for(intw=0;w<nWorkers;++w){diffShifts[w]=model.AddVar(-GRB.INFINITY,GRB.INFINITY,0,GRB.CONTINUOUS,Workers[w]+"Diff");}// Constraint: compute the average number of shifts workedlhs=newGRBLinExpr();for(intw=0;w<nWorkers;++w){lhs.AddTerm(1.0,totShifts[w]);}model.AddConstr(lhs==nWorkers*avgShifts,"avgShifts");// Constraint: compute the difference from the average number of shiftsfor(intw=0;w<nWorkers;++w){model.AddConstr(totShifts[w]-avgShifts==diffShifts[w],Workers[w]+"Diff");}// Objective: minimize the sum of the square of the difference from the// average number of shifts workedGRBQuadExprqobj=newGRBQuadExpr();for(intw=0;w<nWorkers;++w){qobj.AddTerm(1.0,diffShifts[w],diffShifts[w]);}model.SetObjective(qobj);// Optimizestatus=solveAndPrint(model,totSlack,nWorkers,Workers,totShifts);if(status!=GRB.Status.OPTIMAL){return;}// Dispose of model and envmodel.Dispose();env.Dispose();}catch(GRBExceptione){Console.WriteLine("Error code: "+e.ErrorCode+". "+e.Message);}}privatestaticintsolveAndPrint(GRBModelmodel,GRBVartotSlack,intnWorkers,String[]Workers,GRBVar[]totShifts){model.Optimize();intstatus=model.Status;if((status==GRB.Status.INF_OR_UNBD)||(status==GRB.Status.INFEASIBLE)||(status==GRB.Status.UNBOUNDED)){Console.WriteLine("The model cannot be solved "+"because it is infeasible or unbounded");returnstatus;}if(status!=GRB.Status.OPTIMAL){Console.WriteLine("Optimization was stopped with status "+status);returnstatus;}// Print total slack and the number of shifts worked for each workerConsole.WriteLine("\nTotal slack required: "+totSlack.X);for(intw=0;w<nWorkers;++w){Console.WriteLine(Workers[w]+" worked "+totShifts[w].X+" shifts");}Console.WriteLine("\n");returnstatus;}}
/* Copyright 2025, Gurobi Optimization, LLC *//* Assign workers to shifts; each worker may or may not be available on a particular day. We use Pareto optimization to solve the model: first, we minimize the linear sum of the slacks. Then, we constrain the sum of the slacks, and we minimize a quadratic objective that tries to balance the workload among the workers. */importcom.gurobi.gurobi.*;publicclassWorkforce4{publicstaticvoidmain(String[]args){try{// Sample data// Sets of days and workersStringShifts[]=newString[]{"Mon1","Tue2","Wed3","Thu4","Fri5","Sat6","Sun7","Mon8","Tue9","Wed10","Thu11","Fri12","Sat13","Sun14"};StringWorkers[]=newString[]{"Amy","Bob","Cathy","Dan","Ed","Fred","Gu"};intnShifts=Shifts.length;intnWorkers=Workers.length;// Number of workers required for each shiftdoubleshiftRequirements[]=newdouble[]{3,2,4,4,5,6,5,2,2,3,4,6,7,5};// Worker availability: 0 if the worker is unavailable for a shiftdoubleavailability[][]=newdouble[][]{{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},{1,1,1,0,1,1,1,1,1,1,1,1,1,1}};// ModelGRBEnvenv=newGRBEnv();GRBModelmodel=newGRBModel(env);model.set(GRB.StringAttr.ModelName,"assignment");// Assignment 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.GRBVar[][]x=newGRBVar[nWorkers][nShifts];for(intw=0;w<nWorkers;++w){for(ints=0;s<nShifts;++s){x[w][s]=model.addVar(0,availability[w][s],0,GRB.BINARY,Workers[w]+"."+Shifts[s]);}}// Slack variables for each shift constraint so that the shifts can// be satisfiedGRBVar[]slacks=newGRBVar[nShifts];for(ints=0;s<nShifts;++s){slacks[s]=model.addVar(0,GRB.INFINITY,0,GRB.CONTINUOUS,Shifts[s]+"Slack");}// Variable to represent the total slackGRBVartotSlack=model.addVar(0,GRB.INFINITY,0,GRB.CONTINUOUS,"totSlack");// Variables to count the total shifts worked by each workerGRBVar[]totShifts=newGRBVar[nWorkers];for(intw=0;w<nWorkers;++w){totShifts[w]=model.addVar(0,GRB.INFINITY,0,GRB.CONTINUOUS,Workers[w]+"TotShifts");}GRBLinExprlhs;// Constraint: assign exactly shiftRequirements[s] workers// to each shift s, plus the slackfor(ints=0;s<nShifts;++s){lhs=newGRBLinExpr();lhs.addTerm(1.0,slacks[s]);for(intw=0;w<nWorkers;++w){lhs.addTerm(1.0,x[w][s]);}model.addConstr(lhs,GRB.EQUAL,shiftRequirements[s],Shifts[s]);}// Constraint: set totSlack equal to the total slacklhs=newGRBLinExpr();lhs.addTerm(-1.0,totSlack);for(ints=0;s<nShifts;++s){lhs.addTerm(1.0,slacks[s]);}model.addConstr(lhs,GRB.EQUAL,0,"totSlack");// Constraint: compute the total number of shifts for each workerfor(intw=0;w<nWorkers;++w){lhs=newGRBLinExpr();lhs.addTerm(-1.0,totShifts[w]);for(ints=0;s<nShifts;++s){lhs.addTerm(1.0,x[w][s]);}model.addConstr(lhs,GRB.EQUAL,0,"totShifts"+Workers[w]);}// Objective: minimize the total slackGRBLinExprobj=newGRBLinExpr();obj.addTerm(1.0,totSlack);model.setObjective(obj);// Optimizeintstatus=solveAndPrint(model,totSlack,nWorkers,Workers,totShifts);if(status!=GRB.Status.OPTIMAL){return;}// Constrain the slack by setting its upper and lower boundstotSlack.set(GRB.DoubleAttr.UB,totSlack.get(GRB.DoubleAttr.X));totSlack.set(GRB.DoubleAttr.LB,totSlack.get(GRB.DoubleAttr.X));// Variable to count the average number of shifts workedGRBVaravgShifts=model.addVar(0,GRB.INFINITY,0,GRB.CONTINUOUS,"avgShifts");// Variables to count the difference from average for each worker;// note that these variables can take negative values.GRBVar[]diffShifts=newGRBVar[nWorkers];for(intw=0;w<nWorkers;++w){diffShifts[w]=model.addVar(-GRB.INFINITY,GRB.INFINITY,0,GRB.CONTINUOUS,Workers[w]+"Diff");}// Constraint: compute the average number of shifts workedlhs=newGRBLinExpr();lhs.addTerm(-nWorkers,avgShifts);for(intw=0;w<nWorkers;++w){lhs.addTerm(1.0,totShifts[w]);}model.addConstr(lhs,GRB.EQUAL,0,"avgShifts");// Constraint: compute the difference from the average number of shiftsfor(intw=0;w<nWorkers;++w){lhs=newGRBLinExpr();lhs.addTerm(-1,diffShifts[w]);lhs.addTerm(-1,avgShifts);lhs.addTerm(1,totShifts[w]);model.addConstr(lhs,GRB.EQUAL,0,Workers[w]+"Diff");}// Objective: minimize the sum of the square of the difference from the// average number of shifts workedGRBQuadExprqobj=newGRBQuadExpr();for(intw=0;w<nWorkers;++w){qobj.addTerm(1.0,diffShifts[w],diffShifts[w]);}model.setObjective(qobj);// Optimizestatus=solveAndPrint(model,totSlack,nWorkers,Workers,totShifts);if(status!=GRB.Status.OPTIMAL){return;}// Dispose of model and environmentmodel.dispose();env.dispose();}catch(GRBExceptione){System.out.println("Error code: "+e.getErrorCode()+". "+e.getMessage());}}privatestaticintsolveAndPrint(GRBModelmodel,GRBVartotSlack,intnWorkers,String[]Workers,GRBVar[]totShifts)throwsGRBException{model.optimize();intstatus=model.get(GRB.IntAttr.Status);if(status==GRB.Status.INF_OR_UNBD||status==GRB.Status.INFEASIBLE||status==GRB.Status.UNBOUNDED){System.out.println("The model cannot be solved "+"because it is infeasible or unbounded");returnstatus;}if(status!=GRB.Status.OPTIMAL){System.out.println("Optimization was stopped with status "+status);returnstatus;}// Print total slack and the number of shifts worked for each workerSystem.out.println("\nTotal slack required: "+totSlack.get(GRB.DoubleAttr.X));for(intw=0;w<nWorkers;++w){System.out.println(Workers[w]+" worked "+totShifts[w].get(GRB.DoubleAttr.X)+" shifts");}System.out.println("\n");returnstatus;}}
functionworkforce4()% Copyright 2025, Gurobi Optimization, LLC%% Assign workers to shifts; each worker may or may not be available on a% particular day. We use Pareto optimization to solve the model:% first, we minimize the linear sum of the slacks. Then, we constrain% the sum of the slacks, and we minimize a quadratic objective that% tries to balance the workload among the workers.% define datanShifts=14;nWorkers=7;nVars=(nShifts+1)*(nWorkers+1)+nWorkers+1;avgShiftIdx=(nShifts+1)*(nWorkers+1);totalSlackIdx=nVars;Shifts={'Mon1';'Tue2';'Wed3';'Thu4';'Fri5';'Sat6';'Sun7';'Mon8';'Tue9';'Wed10';'Thu11';'Fri12';'Sat13';'Sun14'};Workers={'Amy';'Bob';'Cathy';'Dan';'Ed';'Fred';'Gu'};shiftRequirements=[3;2;4;4;5;6;5;2;2;3;4;6;7;5];availability=[01101010111111;11001101001010;00111011111111;01101101111111;11111011101011;11100101100111;11101111111111];% Build modelmodel.modelname='workforce4';model.modelsense='min';% Initialize assignment decision variables:% x[w][s] == 1 if worker w is assigned% to shift s. Since an assignment model always produces integer% solutions, we use continuous variables and solve as an LP.model.vtype=repmat('C',nVars,1);model.lb=zeros(nVars,1);model.ub=ones(nVars,1);model.obj=zeros(nVars,1);forw=1:nWorkersfors=1:nShiftsmodel.varnames{s+(w-1)*nShifts}=sprintf('%s.%s',Workers{w},Shifts{s});ifavailability(w,s)==0model.ub(s+(w-1)*nShifts)=0;endendend% Initialize shift slack variablesfors=1:nShiftsmodel.varnames{s+nShifts*nWorkers}=sprintf('ShiftSlack_%s',Shifts{s});model.ub(s+nShifts*nWorkers)=inf;end% Initialize worker slack and diff variablesforw=1:nWorkersmodel.varnames{w+nShifts*(nWorkers+1)}=sprintf('TotalShifts_%s',Workers{w});model.ub(w+nShifts*(nWorkers+1))=inf;model.varnames{w+avgShiftIdx}=sprintf('DiffShifts_%s',Workers{w});model.ub(w+avgShiftIdx)=inf;model.lb(w+avgShiftIdx)=-inf;end% Initialize average shift variablemodel.ub((nShifts+1)*(nWorkers+1))=inf;model.varnames{(nShifts+1)*(nWorkers+1)}='AvgShift';% Initialize total slack variablemodel.ub(totalSlackIdx)=inf;model.varnames{totalSlackIdx}='TotalSlack';model.obj(totalSlackIdx)=1;% Set-up shift-requirements constraints with shift slackmodel.sense=repmat('=',nShifts+1+nWorkers,1);model.rhs=[shiftRequirements;zeros(1+nWorkers,1)];model.constrnames=Shifts;model.A=sparse(nShifts+1+nWorkers,nVars);fors=1:nShiftsforw=1:nWorkersmodel.A(s,s+(w-1)*nShifts)=1;endmodel.A(s,s+nShifts*nWorkers)=1;end% Set TotalSlack equal to the sum of each shift slackfors=1:nShiftsmodel.A(nShifts+1,s+nShifts*nWorkers)=-1;endmodel.A(nShifts+1,totalSlackIdx)=1;model.constrnames{nShifts+1}='TotalSlack';% Set total number of shifts for each workerforw=1:nWorkersfors=1:nShiftsmodel.A(w+nShifts+1,s+(w-1)*nShifts)=-1;endmodel.A(w+nShifts+1,w+nShifts*(nWorkers+1))=1;model.constrnames{nShifts+1+w}=sprintf('totShifts_%s',Workers{w});end% Save modelgurobi_write(model,'workforce4a_m.lp');% Optimizeparams.logfile='workforce4_m.log';result=solveandprint(model,params,Shifts,Workers);if~strcmp(result.status,'OPTIMAL')fprintf('Quit now\n');return;end% Constraint the slack by setting its upper and lower boundstotalSlack=result.x(totalSlackIdx);model.lb(totalSlackIdx)=totalSlack;model.ub(totalSlackIdx)=totalSlack;Rows=nShifts+1+nWorkers;forw=1:nWorkersmodel.A(Rows+w,w+nShifts*(nWorkers+1))=1;model.A(Rows+w,w+avgShiftIdx)=-1;model.A(Rows+w,avgShiftIdx)=-1;model.A(Rows+1+nWorkers,w+nShifts*(nWorkers+1))=1;model.rhs(Rows+w)=0;model.sense(Rows+w)='=';model.constrnames{Rows+w}=sprintf('DiffShifts_%s_AvgShift',Workers{w});endmodel.A(Rows+1+nWorkers,avgShiftIdx)=-nWorkers;model.rhs(Rows+1+nWorkers)=0;model.sense(Rows+1+nWorkers)='=';model.constrnames{Rows+1+nWorkers}='AvgShift';% Objective: minimize the sum of the square of the difference from the% average number of shifts workedmodel.obj=zeros(nVars,1);model.Q=sparse(nVars,nVars);forw=1:nWorkersmodel.Q(avgShiftIdx+w,avgShiftIdx+w)=1;end% model is no longer an assignment problem, enforce binary constraints% on shift decision variables.model.vtype(1:(nWorkers*nShifts),1)='B';model.vtype((nWorkers*nShifts+1):nVars,1)='C';% Save modified modelgurobi_write(model,'workforce4b_m.lp');% Optimizeresult=solveandprint(model,params,Shifts,Workers);if~strcmp(result.status,'OPTIMAL')fprintf('Not optimal\n');endendfunctionresult=solveandprint(model, params, Shifts, Workers)% Helper function to solve and display resultsnShifts=length(Shifts);nWorkers=length(Workers);result=gurobi(model,params);ifstrcmp(result.status,'OPTIMAL')fprintf('The optimal objective is %g\n',result.objval);fprintf('Schedule:\n');fors=1:nShiftsfprintf('\t%s:',Shifts{s});forw=1:nWorkersifresult.x(s+(w-1)*nShifts)>0.9fprintf('%s ',Workers{w});endendfprintf('\n');endfprintf('Workload:\n');forw=1:nWorkersfprintf('\t%s: %g\n',Workers{w},result.x(w+nShifts*(nWorkers+1)));endelsefprintf('Optimization finished with status %s\n',result.status);endend
#!/usr/bin/env python3# Copyright 2025, Gurobi Optimization, LLC# Assign workers to shifts; each worker may or may not be available on a# particular day. We use lexicographic optimization to solve the model:# first, we minimize the linear sum of the slacks. Then, we constrain# the sum of the slacks, and we minimize a quadratic objective that# tries to balance the workload among the workers.importgurobipyasgpfromgurobipyimportGRBimportsys# Number of workers required for each shiftshifts,shiftRequirements=gp.multidict({"Mon1":3,"Tue2":2,"Wed3":4,"Thu4":4,"Fri5":5,"Sat6":6,"Sun7":5,"Mon8":2,"Tue9":2,"Wed10":3,"Thu11":4,"Fri12":6,"Sat13":7,"Sun14":5,})# Amount each worker is paid to work one shiftworkers,pay=gp.multidict({"Amy":10,"Bob":12,"Cathy":10,"Dan":8,"Ed":8,"Fred":9,"Gu":11,})# Worker availabilityavailability=gp.tuplelist([("Amy","Tue2"),("Amy","Wed3"),("Amy","Fri5"),("Amy","Sun7"),("Amy","Tue9"),("Amy","Wed10"),("Amy","Thu11"),("Amy","Fri12"),("Amy","Sat13"),("Amy","Sun14"),("Bob","Mon1"),("Bob","Tue2"),("Bob","Fri5"),("Bob","Sat6"),("Bob","Mon8"),("Bob","Thu11"),("Bob","Sat13"),("Cathy","Wed3"),("Cathy","Thu4"),("Cathy","Fri5"),("Cathy","Sun7"),("Cathy","Mon8"),("Cathy","Tue9"),("Cathy","Wed10"),("Cathy","Thu11"),("Cathy","Fri12"),("Cathy","Sat13"),("Cathy","Sun14"),("Dan","Tue2"),("Dan","Wed3"),("Dan","Fri5"),("Dan","Sat6"),("Dan","Mon8"),("Dan","Tue9"),("Dan","Wed10"),("Dan","Thu11"),("Dan","Fri12"),("Dan","Sat13"),("Dan","Sun14"),("Ed","Mon1"),("Ed","Tue2"),("Ed","Wed3"),("Ed","Thu4"),("Ed","Fri5"),("Ed","Sun7"),("Ed","Mon8"),("Ed","Tue9"),("Ed","Thu11"),("Ed","Sat13"),("Ed","Sun14"),("Fred","Mon1"),("Fred","Tue2"),("Fred","Wed3"),("Fred","Sat6"),("Fred","Mon8"),("Fred","Tue9"),("Fred","Fri12"),("Fred","Sat13"),("Fred","Sun14"),("Gu","Mon1"),("Gu","Tue2"),("Gu","Wed3"),("Gu","Fri5"),("Gu","Sat6"),("Gu","Sun7"),("Gu","Mon8"),("Gu","Tue9"),("Gu","Wed10"),("Gu","Thu11"),("Gu","Fri12"),("Gu","Sat13"),("Gu","Sun14"),])# Modelm=gp.Model("assignment")# Assignment 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=m.addVars(availability,vtype=GRB.BINARY,name="x")# Slack variables for each shift constraint so that the shifts can# be satisfiedslacks=m.addVars(shifts,name="Slack")# Variable to represent the total slacktotSlack=m.addVar(name="totSlack")# Variables to count the total shifts worked by each workertotShifts=m.addVars(workers,name="TotShifts")# Constraint: assign exactly shiftRequirements[s] workers to each shift s,# plus the slackreqCts=m.addConstrs((slacks[s]+x.sum("*",s)==shiftRequirements[s]forsinshifts),"_")# Constraint: set totSlack equal to the total slackm.addConstr(totSlack==slacks.sum(),"totSlack")# Constraint: compute the total number of shifts for each workerm.addConstrs((totShifts[w]==x.sum(w)forwinworkers),"totShifts")# Objective: minimize the total slack# Note that this replaces the previous 'pay' objective coefficientsm.setObjective(totSlack)# OptimizedefsolveAndPrint():m.optimize()status=m.statusifstatusin(GRB.INF_OR_UNBD,GRB.INFEASIBLE,GRB.UNBOUNDED):print("The model cannot be solved because it is infeasible or\ unbounded")sys.exit(1)ifstatus!=GRB.OPTIMAL:print(f"Optimization was stopped with status{status}")sys.exit(0)# Print total slack and the number of shifts worked for each workerprint("")print(f"Total slack required:{totSlack.X:g}")forwinworkers:print(f"{w} worked{totShifts[w].X:g} shifts")print("")solveAndPrint()# Constrain the slack by setting its upper and lower boundstotSlack.UB=totSlack.XtotSlack.LB=totSlack.X# Variable to count the average number of shifts workedavgShifts=m.addVar(name="avgShifts")# Variables to count the difference from average for each worker;# note that these variables can take negative values.diffShifts=m.addVars(workers,lb=-GRB.INFINITY,name="Diff")# Constraint: compute the average number of shifts workedm.addConstr(len(workers)*avgShifts==totShifts.sum(),"avgShifts")# Constraint: compute the difference from the average number of shiftsm.addConstrs((diffShifts[w]==totShifts[w]-avgShiftsforwinworkers),"Diff")# Objective: minimize the sum of the square of the difference from the# average number of shifts workedm.setObjective(gp.quicksum(diffShifts[w]*diffShifts[w]forwinworkers))# OptimizesolveAndPrint()
# Copyright 2025, Gurobi Optimization, LLC## Assign workers to shifts; each worker may or may not be available on a# particular day. We use Pareto optimization to solve the model:# first, we minimize the linear sum of the slacks. Then, we constrain# the sum of the slacks, and we minimize a quadratic objective that# tries to balance the workload among the workers.library(Matrix)library(gurobi)# define datanShifts<-14nWorkers<-7nVars<-(nShifts+1)*(nWorkers+1)+nWorkers+1varIdx<-function(w,s){s+(w-1)*nShifts}shiftSlackIdx<-function(s){s+nShifts*nWorkers}totShiftIdx<-function(w){w+nShifts*(nWorkers+1)}avgShiftIdx<-((nShifts+1)*(nWorkers+1))diffShiftIdx<-function(w){w+avgShiftIdx}totalSlackIdx<-nVarsShifts<-c('Mon1','Tue2','Wed3','Thu4','Fri5','Sat6','Sun7','Mon8','Tue9','Wed10','Thu11','Fri12','Sat13','Sun14')Workers<-c('Amy','Bob','Cathy','Dan','Ed','Fred','Gu')shiftRequirements<-c(3,2,4,4,5,6,5,2,2,3,4,6,7,5)availability<-list(c(0,1,1,0,1,0,1,0,1,1,1,1,1,1),c(1,1,0,0,1,1,0,1,0,0,1,0,1,0),c(0,0,1,1,1,0,1,1,1,1,1,1,1,1),c(0,1,1,0,1,1,0,1,1,1,1,1,1,1),c(1,1,1,1,1,0,1,1,1,0,1,0,1,1),c(1,1,1,0,0,1,0,1,1,0,0,1,1,1),c(1,1,1,0,1,1,1,1,1,1,1,1,1,1))# Function to display resultssolveandprint<-function(model,params){result<-gurobi(model,params=params)if(result$status=='OPTIMAL'){cat('The optimal objective is',result$objval,'\n')cat('Schedule:\n')for(sin1:nShifts){cat('\t',Shifts[s],':')for(win1:nWorkers){if(result$x[varIdx(w,s)]>0.9)cat(Workers[w],' ')}cat('\n')}cat('Workload:\n')for(win1:nWorkers){cat('\t',Workers[w],':',result$x[totShiftIdx(w)],'\n')}}else{cat('Optimization finished with status',result$status)}result}# Set up parametersparams<-list()params$logfile<-'workforce4.log'# Build modelmodel<-list()model$modelname<-'workforce4'model$modelsense<-'min'# 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.model$vtype<-rep('C',nVars)model$lb<-rep(0,nVars)model$ub<-rep(1,nVars)model$obj<-rep(0,nVars)model$varnames<-rep('',nVars)for(win1:nWorkers){for(sin1:nShifts){model$vtype[varIdx(w,s)]='B'model$varnames[varIdx(w,s)]=paste0(Workers[w],'.',Shifts[s])if(availability[[w]][s]==0)model$ub[varIdx(w,s)]=0}}# Initialize shift slack variablesfor(sin1:nShifts){model$varnames[shiftSlackIdx(s)]=paste0('ShiftSlack',Shifts[s])model$ub[shiftSlackIdx(s)]=Inf}# Initialize worker slack and diff variablesfor(win1:nWorkers){model$varnames[totShiftIdx(w)]=paste0('TotalShifts',Workers[w])model$ub[totShiftIdx(w)]=Infmodel$varnames[diffShiftIdx(w)]=paste0('DiffShifts',Workers[w])model$ub[diffShiftIdx(w)]=Infmodel$lb[diffShiftIdx(w)]=-Inf}#Initialize average shift variablemodel$ub[avgShiftIdx]=Infmodel$varnames[avgShiftIdx]='AvgShift'#Initialize total slack variablemodel$ub[totalSlackIdx]=Infmodel$varnames[totalSlackIdx]='TotalSlack'model$obj[totalSlackIdx]=1# Set up shift-requirements constraintsmodel$A<-spMatrix(nShifts,nVars,i=c(c(mapply(rep,1:nShifts,nWorkers)),c(1:nShifts)),j=c(mapply(varIdx,1:nWorkers,mapply(rep,1:nShifts,nWorkers)),shiftSlackIdx(1:nShifts)),x=rep(1,nShifts*(nWorkers+1)))model$sense<-rep('=',nShifts)model$rhs<-shiftRequirementsmodel$constrnames<-Shifts# Set TotalSlack equal to the sum of each shift slackB<-spMatrix(1,nVars,i=rep(1,nShifts+1),j=c(shiftSlackIdx(1:nShifts),totalSlackIdx),x=c(rep(1,nShifts),-1))model$A<-rbind(model$A,B)model$rhs<-c(model$rhs,0)model$sense<-c(model$sense,'=')model$constrnames<-c(model$constrnames,'TotalSlack')# Set total number of shifts for each workerB<-spMatrix(nWorkers,nVars,i=c(mapply(rep,1:nWorkers,nShifts),1:nWorkers),j=c(mapply(varIdx,c(mapply(rep,1:nWorkers,nShifts)),1:nShifts),totShiftIdx(1:nWorkers)),x=c(rep(1,nShifts*nWorkers),rep(-1,nWorkers)))model$A<-rbind(model$A,B)model$rhs<-c(model$rhs,rep(0,nWorkers))model$sense<-c(model$sense,rep('=',nWorkers))model$constrnames<-c(model$constrnames,sprintf('TotalShifts%s',Workers[1:nWorkers]))# Save initial modelgurobi_write(model,'workforce4.lp',params)# Optimizeresult<-solveandprint(model,params)if(result$status!='OPTIMAL')stop('Stop now\n')# Constraint the slack by setting its upper and lower boundstotalSlack<-result$x[totalSlackIdx]model$lb[totalSlackIdx]=totalSlackmodel$ub[totalSlackIdx]=totalSlack# Link average number of shifts worked and difference with averageB<-spMatrix(nWorkers+1,nVars,i=c(1:nWorkers,1:nWorkers,1:nWorkers,rep(nWorkers+1,nWorkers+1)),j=c(totShiftIdx(1:nWorkers),diffShiftIdx(1:nWorkers),rep(avgShiftIdx,nWorkers),totShiftIdx(1:nWorkers),avgShiftIdx),x=c(rep(1,nWorkers),rep(-1,nWorkers),rep(-1,nWorkers),rep(1,nWorkers),-nWorkers))model$A<-rbind(model$A,B)model$rhs<-c(model$rhs,rep(0,nWorkers+1))model$sense<-c(model$sense,rep('=',nWorkers+1))model$constrnames<-c(model$constrnames,sprintf('DiffShifts%s',Workers[1:nWorkers]),'AvgShift')# Objective: minimize the sum of the square of the difference from the# average number of shifts workedmodel$obj<-0model$Q<-spMatrix(nVars,nVars,i=c(diffShiftIdx(1:nWorkers)),j=c(diffShiftIdx(1:nWorkers)),x=rep(1,nWorkers))# Save modified modelgurobi_write(model,'workforce4b.lp',params)# Optimizeresult<-solveandprint(model,params)if(result$status!='OPTIMAL')stop('Stop now\n')#Clear spacerm(model,params,availability,Shifts,Workers,shiftRequirements,result)
' Copyright 2025, Gurobi Optimization, LLC'' Assign workers to shifts; each worker may or may not be available on a' particular day. We use Pareto optimization to solve the model:' first, we minimize the linear sum of the slacks. Then, we constrain' the sum of the slacks, and we minimize a quadratic objective that' tries to balance the workload among the workers.ImportsSystemImportsGurobiClassworkforce4_vbSharedSubMain()Try' Sample data' Sets of days and workersDimShiftsAsString()=NewString(){"Mon1","Tue2","Wed3","Thu4",_"Fri5","Sat6","Sun7","Mon8",_"Tue9","Wed10","Thu11",_"Fri12","Sat13","Sun14"}DimWorkersAsString()=NewString(){"Amy","Bob","Cathy","Dan",_"Ed","Fred","Gu"}DimnShiftsAsInteger=Shifts.LengthDimnWorkersAsInteger=Workers.Length' Number of workers required for each shiftDimshiftRequirementsAsDouble()=NewDouble(){3,2,4,4,5,6,_5,2,2,3,4,6,_7,5}' Worker availability: 0 if the worker is unavailable for a shiftDimavailabilityAsDouble(,)=NewDouble(,){_{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},_{1,1,1,0,1,1,1,1,1,1,1,1,1,1}}' ModelDimenvAsNewGRBEnv()DimmodelAsNewGRBModel(env)model.ModelName="assignment"' Assignment 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.DimxAsGRBVar(,)=NewGRBVar(nWorkers-1,nShifts-1){}ForwAsInteger=0TonWorkers-1ForsAsInteger=0TonShifts-1x(w,s)=model.AddVar(0,availability(w,s),0,_GRB.BINARY,_Workers(w)&"."&Shifts(s))NextNext' Add a new slack variable to each shift constraint so that the' shifts can be satisfiedDimslacksAsGRBVar()=NewGRBVar(nShifts-1){}ForsAsInteger=0TonShifts-1slacks(s)=_model.AddVar(0,GRB.INFINITY,0,GRB.CONTINUOUS,_Shifts(s)&"Slack")Next' Variable to represent the total slackDimtotSlackAsGRBVar=model.AddVar(0,GRB.INFINITY,0,_GRB.CONTINUOUS,"totSlack")' Variables to count the total shifts worked by each workerDimtotShiftsAsGRBVar()=NewGRBVar(nWorkers-1){}ForwAsInteger=0TonWorkers-1totShifts(w)=_model.AddVar(0,GRB.INFINITY,0,GRB.CONTINUOUS,_Workers(w)&"TotShifts")NextDimlhsAsGRBLinExpr' Constraint: assign exactly shiftRequirements(s) workers' to each shift s, plus the slackForsAsInteger=0TonShifts-1lhs=0lhs.AddTerm(1.0,slacks(s))ForwAsInteger=0TonWorkers-1lhs.AddTerm(1.0,x(w,s))Nextmodel.AddConstr(lhs=shiftRequirements(s),Shifts(s))Next' Constraint: set totSlack equal to the total slacklhs=0ForsAsInteger=0TonShifts-1lhs.AddTerm(1.0,slacks(s))Nextmodel.AddConstr(lhs=totSlack,"totSlack")' Constraint: compute the total number of shifts for each workerForwAsInteger=0TonWorkers-1lhs=0ForsAsInteger=0TonShifts-1lhs.AddTerm(1.0,x(w,s))Nextmodel.AddConstr(lhs=totShifts(w),"totShifts"&Workers(w))Next' Objective: minimize the total slackmodel.SetObjective(1.0*totSlack)' OptimizeDimstatusAsInteger=_solveAndPrint(model,totSlack,nWorkers,Workers,totShifts)Ifstatus<>GRB.Status.OPTIMALThenExitSubEndIf' Constrain the slack by setting its upper and lower boundstotSlack.UB=totSlack.XtotSlack.LB=totSlack.X' Variable to count the average number of shifts workedDimavgShiftsAsGRBVar=model.AddVar(0,GRB.INFINITY,0,_GRB.CONTINUOUS,"avgShifts")' Variables to count the difference from average for each worker;' note that these variables can take negative values.DimdiffShiftsAsGRBVar()=NewGRBVar(nWorkers-1){}ForwAsInteger=0TonWorkers-1diffShifts(w)=_model.AddVar(-GRB.INFINITY,GRB.INFINITY,0,_GRB.CONTINUOUS,Workers(w)&"Diff")Next' Constraint: compute the average number of shifts workedlhs=0ForwAsInteger=0TonWorkers-1lhs.AddTerm(1.0,totShifts(w))Nextmodel.AddConstr(lhs=nWorkers*avgShifts,"avgShifts")' Constraint: compute the difference from the average number of shiftsForwAsInteger=0TonWorkers-1model.AddConstr(totShifts(w)-avgShifts=diffShifts(w),_Workers(w)&"Diff")Next' Objective: minimize the sum of the square of the difference' from the average number of shifts workedDimqobjAsGRBQuadExpr=NewGRBQuadExprForwAsInteger=0TonWorkers-1qobj.AddTerm(1.0,diffShifts(w),diffShifts(w))Nextmodel.SetObjective(qobj)' Optimizestatus=solveAndPrint(model,totSlack,nWorkers,Workers,totShifts)Ifstatus<>GRB.Status.OPTIMALThenExitSubEndIf' Dispose of model and envmodel.Dispose()env.Dispose()CatcheAsGRBExceptionConsole.WriteLine("Error code: "&e.ErrorCode&". "&e.Message)EndTryEndSubPrivateSharedFunctionsolveAndPrint(ByValmodelAsGRBModel,_ByValtotSlackAsGRBVar,_ByValnWorkersAsInteger,_ByValWorkersAsString(),_ByValtotShiftsAsGRBVar())AsIntegermodel.Optimize()DimstatusAsInteger=model.StatussolveAndPrint=statusIf(status=GRB.Status.INF_OR_UNBD)OrElse_(status=GRB.Status.INFEASIBLE)OrElse_(status=GRB.Status.UNBOUNDED)ThenConsole.WriteLine("The model cannot be solved because "&_"it is infeasible or unbounded")ExitFunctionEndIfIfstatus<>GRB.Status.OPTIMALThenConsole.WriteLine("Optimization was stopped with status "_&status)ExitFunctionEndIf' Print total slack and the number of shifts worked for each workerConsole.WriteLine(vbLf&"Total slack required: "&totSlack.X)ForwAsInteger=0TonWorkers-1Console.WriteLine(Workers(w)&" worked "&_totShifts(w).X&" shifts")NextConsole.WriteLine(vbLf)EndFunctionEndClass
/* Copyright 2025, Gurobi Optimization, LLC *//* 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 */#include<stdlib.h>#include<stdio.h>#include<math.h>#include<string.h>#include"gurobi_c.h"intsolveAndPrint(GRBmodel*model,intnShifts,intnWorkers,char**Workers,int*status);#define xcol(w,s) nShifts*w+s#define slackcol(s) nShifts*nWorkers+s#define totSlackcol nShifts*(nWorkers+1)#define totShiftscol(w) nShifts*(nWorkers+1)+1+w#define minShiftcol (nShifts+1)*(nWorkers+1)#define maxShiftcol (nShifts+1)*(nWorkers+1)+1#define MAXSTR 128intmain(intargc,char*argv[]){GRBenv*env=NULL;GRBmodel*model=NULL;interror=0,status;ints,w,col;int*cbeg=NULL;int*cind=NULL;intidx;double*cval=NULL;char*sense=NULL;charvname[MAXSTR],cname[MAXSTR];/* Sample data */constintnShifts=14;constintnWorkers=8;/* Sets of days and workers */char*Shifts[]={"Mon1","Tue2","Wed3","Thu4","Fri5","Sat6","Sun7","Mon8","Tue9","Wed10","Thu11","Fri12","Sat13","Sun14"};char*Workers[]={"Amy","Bob","Cathy","Dan","Ed","Fred","Gu","Tobi"};/* Number of workers required for each shift */doubleshiftRequirements[]={3,2,4,4,5,6,5,2,2,3,4,6,7,5};/* Worker availability: 0 if the worker is unavailable for a shift */doubleavailability[][14]={{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}};/* Create environment */error=GRBloadenv(&env,"workforce5.log");if(error)gotoQUIT;/* Create initial model */error=GRBnewmodel(env,&model,"workforce5",(nShifts+1)*(nWorkers+1)+2,NULL,NULL,NULL,NULL,NULL);if(error)gotoQUIT;/* 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. */for(w=0;w<nWorkers;++w){for(s=0;s<nShifts;++s){col=xcol(w,s);sprintf(vname,"%s.%s",Workers[w],Shifts[s]);error=GRBsetcharattrelement(model,"VType",col,GRB_BINARY);if(error)gotoQUIT;error=GRBsetdblattrelement(model,"UB",col,availability[w][s]);if(error)gotoQUIT;error=GRBsetstrattrelement(model,"VarName",col,vname);if(error)gotoQUIT;}}/* Initialize slack decision variables */for(s=0;s<nShifts;++s){sprintf(vname,"%sSlack",Shifts[s]);error=GRBsetstrattrelement(model,"VarName",slackcol(s),vname);if(error)gotoQUIT;}/* Initialize total slack decision variable */error=GRBsetstrattrelement(model,"VarName",totSlackcol,"totSlack");if(error)gotoQUIT;/* Initialize variables to count the total shifts worked by each worker */for(w=0;w<nWorkers;++w){sprintf(vname,"%sTotShifts",Workers[w]);error=GRBsetstrattrelement(model,"VarName",totShiftscol(w),vname);if(error)gotoQUIT;}/* Initialize max and min #shifts variables */sprintf(vname,"minShifts");error=GRBsetstrattrelement(model,"VarName",minShiftcol,vname);sprintf(vname,"maxShifts");error=GRBsetstrattrelement(model,"VarName",maxShiftcol,vname);/* Make space for constraint data */cbeg=malloc(sizeof(int)*nShifts);if(!cbeg)gotoQUIT;cind=malloc(sizeof(int)*nShifts*(nWorkers+1));if(!cind)gotoQUIT;cval=malloc(sizeof(double)*nShifts*(nWorkers+1));if(!cval)gotoQUIT;sense=malloc(sizeof(char)*(nShifts+nWorkers));if(!sense)gotoQUIT;/* Constraint: assign exactly shiftRequirements[s] workers to each shift s, plus the slack */idx=0;for(s=0;s<nShifts;++s){cbeg[s]=idx;sense[s]=GRB_EQUAL;for(w=0;w<nWorkers;++w){cind[idx]=xcol(w,s);cval[idx++]=1.0;}cind[idx]=slackcol(s);cval[idx++]=1.0;}error=GRBaddconstrs(model,nShifts,idx,cbeg,cind,cval,sense,shiftRequirements,Shifts);if(error)gotoQUIT;/* Constraint: set totSlack column equal to the total slack */idx=0;for(s=0;s<nShifts;++s){cind[idx]=slackcol(s);cval[idx++]=1.0;}cind[idx]=totSlackcol;cval[idx++]=-1.0;error=GRBaddconstr(model,idx,cind,cval,GRB_EQUAL,0.0,"totSlack");if(error)gotoQUIT;/* Constraint: compute the total number of shifts for each worker */for(w=0;w<nWorkers;++w){idx=0;for(s=0;s<nShifts;++s){cind[idx]=xcol(w,s);cval[idx++]=1.0;}sprintf(cname,"totShifts%s",Workers[w]);cind[idx]=totShiftscol(w);cval[idx++]=-1.0;error=GRBaddconstr(model,idx,cind,cval,GRB_EQUAL,0.0,cname);if(error)gotoQUIT;}/* Constraint: set minShift/maxShift variable to less <=/>= to the * number of shifts among all workers */for(w=0;w<nWorkers;w++){cind[w]=totShiftscol(w);}error=GRBaddgenconstrMin(model,NULL,minShiftcol,nWorkers,cind,GRB_INFINITY);if(error)gotoQUIT;error=GRBaddgenconstrMax(model,NULL,maxShiftcol,nWorkers,cind,-GRB_INFINITY);if(error)gotoQUIT;/* Set global sense for ALL objectives */error=GRBsetintattr(model,GRB_INT_ATTR_MODELSENSE,GRB_MINIMIZE);if(error)gotoQUIT;/* Set primary objective */cind[0]=totSlackcol;cval[0]=1.0;error=GRBsetobjectiven(model,0,2,1.0,2.0,0.10,"TotalSlack",0.0,1,cind,cval);if(error)gotoQUIT;/* Set secondary objective */cind[0]=maxShiftcol;cval[0]=1.0;cind[1]=minShiftcol;cval[1]=-1.0;error=GRBsetobjectiven(model,1,1,1.0,0,0,"Fairness",0.0,2,cind,cval);if(error)gotoQUIT;/* Save problem */error=GRBwrite(model,"workforce5.lp");if(error)gotoQUIT;error=GRBwrite(model,"workforce5.mps");if(error)gotoQUIT;/* Optimize */error=solveAndPrint(model,nShifts,nWorkers,Workers,&status);if(error)gotoQUIT;if(status!=GRB_OPTIMAL)gotoQUIT;QUIT:/* Error reporting */if(error){printf("ERROR: %s\n",GRBgeterrormsg(env));exit(1);}/* Free data */free(cbeg);free(cind);free(cval);free(sense);/* Free model */GRBfreemodel(model);/* Free environment */GRBfreeenv(env);return0;}intsolveAndPrint(GRBmodel*model,intnShifts,intnWorkers,char**Workers,int*status){interror,w;doubleval;error=GRBoptimize(model);if(error)returnerror;error=GRBgetintattr(model,"Status",status);if(error)returnerror;if((*status==GRB_INF_OR_UNBD)||(*status==GRB_INFEASIBLE)||(*status==GRB_UNBOUNDED)){printf("The model cannot be solved ""because it is infeasible or unbounded\n");return0;}if(*status!=GRB_OPTIMAL){printf("Optimization was stopped with status %i\n",*status);return0;}/* Print total slack and the number of shifts worked for each worker */error=GRBgetdblattrelement(model,"X",totSlackcol,&val);if(error)returnerror;printf("\nTotal slack required: %f\n",val);for(w=0;w<nWorkers;++w){error=GRBgetdblattrelement(model,"X",totShiftscol(w),&val);if(error)returnerror;printf("%s worked %f shifts\n",Workers[w],val);}printf("\n");return0;}
/* Copyright 2025, Gurobi Optimization, LLC *//* 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 */#include"gurobi_c++.h"#include<sstream>usingnamespacestd;intsolveAndPrint(GRBModel&model,GRBVar&totSlack,intnWorkers,string*Workers,GRBVar*totShifts);intmain(intargc,char*argv[]){GRBEnv*env=0;GRBVar**x=0;GRBVar*slacks=0;GRBVar*totShifts=0;intxCt=0;ints,w;try{// Sample dataconstintnShifts=14;constintnWorkers=8;// Sets of days and workersstringShifts[]={"Mon1","Tue2","Wed3","Thu4","Fri5","Sat6","Sun7","Mon8","Tue9","Wed10","Thu11","Fri12","Sat13","Sun14"};stringWorkers[]={"Amy","Bob","Cathy","Dan","Ed","Fred","Gu","Tobi"};// Number of workers required for each shiftdoubleshiftRequirements[]={3,2,4,4,5,6,5,2,2,3,4,6,7,5};// Worker availability: 0 if the worker is unavailable for a shiftdoubleavailability[][14]={{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}};// Create environmentenv=newGRBEnv("workforce5_c++.log");// Create initial modelGRBModelmodel=GRBModel(*env);model.set(GRB_StringAttr_ModelName,"workforce5_c++");// 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=newGRBVar*[nWorkers];for(w=0;w<nWorkers;w++){x[w]=model.addVars(nShifts,GRB_BINARY);xCt++;for(s=0;s<nShifts;s++){ostringstreamvname;vname<<Workers[w]<<"."<<Shifts[s];x[w][s].set(GRB_DoubleAttr_UB,availability[w][s]);x[w][s].set(GRB_StringAttr_VarName,vname.str());}}// Initialize slack decision variablesslacks=model.addVars(nShifts);for(s=0;s<nShifts;s++){ostringstreamvname;vname<<Shifts[s]<<"Slack";slacks[s].set(GRB_StringAttr_VarName,vname.str());}// Variable to represent the total slackGRBVartotSlack=model.addVar(0,GRB_INFINITY,0,GRB_CONTINUOUS,"totSlack");// Initialize variables to count the total shifts worked by each workertotShifts=model.addVars(nWorkers);for(w=0;w<nWorkers;w++){ostringstreamvname;vname<<Workers[w]<<"TotShifts";totShifts[w].set(GRB_StringAttr_VarName,vname.str());}GRBLinExprlhs;// Constraint: assign exactly shiftRequirements[s] workers// to each shift s, plus the slackfor(s=0;s<nShifts;s++){lhs=0;lhs+=slacks[s];for(w=0;w<nWorkers;w++){lhs+=x[w][s];}model.addConstr(lhs==shiftRequirements[s],Shifts[s]);}// Constraint: set totSlack column equal to the total slacklhs=0;for(s=0;s<nShifts;s++){lhs+=slacks[s];}model.addConstr(lhs==totSlack,"totSlack");// Constraint: compute the total number of shifts for each workerfor(w=0;w<nWorkers;w++){lhs=0;for(s=0;s<nShifts;s++){lhs+=x[w][s];}ostringstreamvname;vname<<"totShifts"<<Workers[w];model.addConstr(lhs==totShifts[w],vname.str());}// Constraint: set minShift/maxShift variable to less <=/>= to the// number of shifts among all workersGRBVarminShift=model.addVar(0,GRB_INFINITY,0,GRB_CONTINUOUS,"minShift");GRBVarmaxShift=model.addVar(0,GRB_INFINITY,0,GRB_CONTINUOUS,"maxShift");model.addGenConstrMin(minShift,totShifts,nWorkers,GRB_INFINITY,"minShift");model.addGenConstrMax(maxShift,totShifts,nWorkers,-GRB_INFINITY,"maxShift");// Set global sense for ALL objectivesmodel.set(GRB_IntAttr_ModelSense,GRB_MINIMIZE);// Set primary objectivemodel.setObjectiveN(totSlack,0,2,1.0,2.0,0.1,"TotalSlack");// Set secondary objectivemodel.setObjectiveN(maxShift-minShift,1,1,1.0,0,0,"Fairness");// Save problemmodel.write("workforce5_c++.lp");// Optimizeintstatus=solveAndPrint(model,totSlack,nWorkers,Workers,totShifts);// Delete local variablesif(status!=GRB_OPTIMAL)return1;}catch(GRBExceptione){cout<<"Error code = "<<e.getErrorCode()<<endl;cout<<e.getMessage()<<endl;}catch(...){cout<<"Exception during optimization"<<endl;}for(s=0;s<xCt;s++)delete[]x[s];delete[]x;delete[]slacks;delete[]totShifts;deleteenv;return0;}intsolveAndPrint(GRBModel&model,GRBVar&totSlack,intnWorkers,string*Workers,GRBVar*totShifts){model.optimize();intstatus=model.get(GRB_IntAttr_Status);if((status==GRB_INF_OR_UNBD)||(status==GRB_INFEASIBLE)||(status==GRB_UNBOUNDED)){cout<<"The model cannot be solved "<<"because it is infeasible or unbounded"<<endl;returnstatus;}if(status!=GRB_OPTIMAL){cout<<"Optimization was stopped with status "<<status<<endl;returnstatus;}// Print total slack and the number of shifts worked for each workercout<<endl<<"Total slack required: "<<totSlack.get(GRB_DoubleAttr_X)<<endl;for(intw=0;w<nWorkers;++w){cout<<Workers[w]<<" worked "<<totShifts[w].get(GRB_DoubleAttr_X)<<" shifts"<<endl;}cout<<endl;returnstatus;}
/* Copyright 2025, Gurobi Optimization, LLC *//* 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 */usingSystem;usingGurobi;classworkforce5_cs{staticvoidMain(){try{// Sample data// Sets of days and workersstring[]Shifts=newstring[]{"Mon1","Tue2","Wed3","Thu4","Fri5","Sat6","Sun7","Mon8","Tue9","Wed10","Thu11","Fri12","Sat13","Sun14"};string[]Workers=newstring[]{"Amy","Bob","Cathy","Dan","Ed","Fred","Gu","Tobi"};intnShifts=Shifts.Length;intnWorkers=Workers.Length;// Number of workers required for each shiftdouble[]shiftRequirements=newdouble[]{3,2,4,4,5,6,5,2,2,3,4,6,7,5};// Worker availability: 0 if the worker is unavailable for a shiftdouble[,]availability=newdouble[,]{{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}};// Create environmentGRBEnvenv=newGRBEnv();// Create initial modelGRBModelmodel=newGRBModel(env);model.ModelName="workforce5_cs";// 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.GRBVar[,]x=newGRBVar[nWorkers,nShifts];for(intw=0;w<nWorkers;++w){for(ints=0;s<nShifts;++s){x[w,s]=model.AddVar(0,availability[w,s],0,GRB.BINARY,string.Format("{0}.{1}",Workers[w],Shifts[s]));}}// Slack variables for each shift constraint so that the shifts can// be satisfiedGRBVar[]slacks=newGRBVar[nShifts];for(ints=0;s<nShifts;++s){slacks[s]=model.AddVar(0,GRB.INFINITY,0,GRB.CONTINUOUS,string.Format("{0}Slack",Shifts[s]));}// Variable to represent the total slackGRBVartotSlack=model.AddVar(0,GRB.INFINITY,0,GRB.CONTINUOUS,"totSlack");// Variables to count the total shifts worked by each workerGRBVar[]totShifts=newGRBVar[nWorkers];for(intw=0;w<nWorkers;++w){totShifts[w]=model.AddVar(0,GRB.INFINITY,0,GRB.CONTINUOUS,string.Format("{0}TotShifts",Workers[w]));}GRBLinExprlhs;// Constraint: assign exactly shiftRequirements[s] workers// to each shift s, plus the slackfor(ints=0;s<nShifts;++s){lhs=newGRBLinExpr();lhs.AddTerm(1.0,slacks[s]);for(intw=0;w<nWorkers;++w){lhs.AddTerm(1.0,x[w,s]);}model.AddConstr(lhs,GRB.EQUAL,shiftRequirements[s],Shifts[s]);}// Constraint: set totSlack equal to the total slacklhs=newGRBLinExpr();lhs.AddTerm(-1.0,totSlack);for(ints=0;s<nShifts;++s){lhs.AddTerm(1.0,slacks[s]);}model.AddConstr(lhs,GRB.EQUAL,0,"totSlack");// Constraint: compute the total number of shifts for each workerfor(intw=0;w<nWorkers;++w){lhs=newGRBLinExpr();lhs.AddTerm(-1.0,totShifts[w]);for(ints=0;s<nShifts;++s){lhs.AddTerm(1.0,x[w,s]);}model.AddConstr(lhs,GRB.EQUAL,0,string.Format("totShifts{0}",Workers[w]));}// Constraint: set minShift/maxShift variable to less <=/>= to the// number of shifts among all workersGRBVarminShift=model.AddVar(0,GRB.INFINITY,0,GRB.CONTINUOUS,"minShift");GRBVarmaxShift=model.AddVar(0,GRB.INFINITY,0,GRB.CONTINUOUS,"maxShift");model.AddGenConstrMin(minShift,totShifts,GRB.INFINITY,"minShift");model.AddGenConstrMax(maxShift,totShifts,-GRB.INFINITY,"maxShift");// Set global sense for ALL objectivesmodel.ModelSense=GRB.MINIMIZE;// Set primary objectivemodel.SetObjectiveN(totSlack,0,2,1.0,2.0,0.1,"TotalSlack");// Set secondary objectivemodel.SetObjectiveN(maxShift-minShift,1,1,1.0,0,0,"Fairness");// Save problemmodel.Write("workforce5_cs.lp");// Optimizeintstatus=solveAndPrint(model,totSlack,nWorkers,Workers,totShifts);if(status!=GRB.Status.OPTIMAL)return;// Dispose of model and environmentmodel.Dispose();env.Dispose();}catch(GRBExceptione){Console.WriteLine("Error code: {0}. {1}",e.ErrorCode,e.Message);}}privatestaticintsolveAndPrint(GRBModelmodel,GRBVartotSlack,intnWorkers,String[]Workers,GRBVar[]totShifts){model.Optimize();intstatus=model.Status;if(status==GRB.Status.INF_OR_UNBD||status==GRB.Status.INFEASIBLE||status==GRB.Status.UNBOUNDED){Console.WriteLine("The model cannot be solved "+"because it is infeasible or unbounded");returnstatus;}if(status!=GRB.Status.OPTIMAL){Console.WriteLine("Optimization was stopped with status {0}",status);returnstatus;}// Print total slack and the number of shifts worked for each workerConsole.WriteLine("\nTotal slack required: {0}",totSlack.X);for(intw=0;w<nWorkers;++w){Console.WriteLine("{0} worked {1} shifts",Workers[w],totShifts[w].X);}Console.WriteLine("\n");returnstatus;}}
/* Copyright 2025, Gurobi Optimization, LLC *//* 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 */importcom.gurobi.gurobi.*;publicclassWorkforce5{publicstaticvoidmain(String[]args){try{// Sample data// Sets of days and workersStringShifts[]=newString[]{"Mon1","Tue2","Wed3","Thu4","Fri5","Sat6","Sun7","Mon8","Tue9","Wed10","Thu11","Fri12","Sat13","Sun14"};StringWorkers[]=newString[]{"Amy","Bob","Cathy","Dan","Ed","Fred","Gu","Tobi"};intnShifts=Shifts.length;intnWorkers=Workers.length;// Number of workers required for each shiftdoubleshiftRequirements[]=newdouble[]{3,2,4,4,5,6,5,2,2,3,4,6,7,5};// Worker availability: 0 if the worker is unavailable for a shiftdoubleavailability[][]=newdouble[][]{{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}};// Create environmentGRBEnvenv=newGRBEnv();// Create initial modelGRBModelmodel=newGRBModel(env);model.set(GRB.StringAttr.ModelName,"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.GRBVar[][]x=newGRBVar[nWorkers][nShifts];for(intw=0;w<nWorkers;++w){for(ints=0;s<nShifts;++s){x[w][s]=model.addVar(0,availability[w][s],0,GRB.BINARY,Workers[w]+"."+Shifts[s]);}}// Slack variables for each shift constraint so that the shifts can// be satisfiedGRBVar[]slacks=newGRBVar[nShifts];for(ints=0;s<nShifts;++s){slacks[s]=model.addVar(0,GRB.INFINITY,0,GRB.CONTINUOUS,Shifts[s]+"Slack");}// Variable to represent the total slackGRBVartotSlack=model.addVar(0,GRB.INFINITY,0,GRB.CONTINUOUS,"totSlack");// Variables to count the total shifts worked by each workerGRBVar[]totShifts=newGRBVar[nWorkers];for(intw=0;w<nWorkers;++w){totShifts[w]=model.addVar(0,GRB.INFINITY,0,GRB.CONTINUOUS,Workers[w]+"TotShifts");}GRBLinExprlhs;// Constraint: assign exactly shiftRequirements[s] workers// to each shift s, plus the slackfor(ints=0;s<nShifts;++s){lhs=newGRBLinExpr();lhs.addTerm(1.0,slacks[s]);for(intw=0;w<nWorkers;++w){lhs.addTerm(1.0,x[w][s]);}model.addConstr(lhs,GRB.EQUAL,shiftRequirements[s],Shifts[s]);}// Constraint: set totSlack equal to the total slacklhs=newGRBLinExpr();lhs.addTerm(-1.0,totSlack);for(ints=0;s<nShifts;++s){lhs.addTerm(1.0,slacks[s]);}model.addConstr(lhs,GRB.EQUAL,0,"totSlack");// Constraint: compute the total number of shifts for each workerfor(intw=0;w<nWorkers;++w){lhs=newGRBLinExpr();lhs.addTerm(-1.0,totShifts[w]);for(ints=0;s<nShifts;++s){lhs.addTerm(1.0,x[w][s]);}model.addConstr(lhs,GRB.EQUAL,0,"totShifts"+Workers[w]);}// Constraint: set minShift/maxShift variable to less <=/>= to the// number of shifts among all workersGRBVarminShift=model.addVar(0,GRB.INFINITY,0,GRB.CONTINUOUS,"minShift");GRBVarmaxShift=model.addVar(0,GRB.INFINITY,0,GRB.CONTINUOUS,"maxShift");model.addGenConstrMin(minShift,totShifts,GRB.INFINITY,"minShift");model.addGenConstrMax(maxShift,totShifts,-GRB.INFINITY,"maxShift");// Set global sense for ALL objectivesmodel.set(GRB.IntAttr.ModelSense,GRB.MINIMIZE);// Set primary objectiveGRBLinExprobj0=newGRBLinExpr();obj0.addTerm(1.0,totSlack);model.setObjectiveN(obj0,0,2,1.0,2.0,0.1,"TotalSlack");// Set secondary objectiveGRBLinExprobj1=newGRBLinExpr();obj1.addTerm(1.0,maxShift);obj1.addTerm(-1.0,minShift);model.setObjectiveN(obj1,1,1,1.0,0.0,0.0,"Fairness");// Save problemmodel.write("Workforce5.lp");// Optimizeintstatus=solveAndPrint(model,totSlack,nWorkers,Workers,totShifts);if(status!=GRB.OPTIMAL)return;// Dispose of model and environmentmodel.dispose();env.dispose();}catch(GRBExceptione){System.out.println("Error code: "+e.getErrorCode()+". "+e.getMessage());}}privatestaticintsolveAndPrint(GRBModelmodel,GRBVartotSlack,intnWorkers,String[]Workers,GRBVar[]totShifts)throwsGRBException{model.optimize();intstatus=model.get(GRB.IntAttr.Status);if(status==GRB.Status.INF_OR_UNBD||status==GRB.Status.INFEASIBLE||status==GRB.Status.UNBOUNDED){System.out.println("The model cannot be solved "+"because it is infeasible or unbounded");returnstatus;}if(status!=GRB.Status.OPTIMAL){System.out.println("Optimization was stopped with status "+status);returnstatus;}// Print total slack and the number of shifts worked for each workerSystem.out.println("\nTotal slack required: "+totSlack.get(GRB.DoubleAttr.X));for(intw=0;w<nWorkers;++w){System.out.println(Workers[w]+" worked "+totShifts[w].get(GRB.DoubleAttr.X)+" shifts");}System.out.println("\n");returnstatus;}}
functionworkforce5()% Copyright 2025, Gurobi Optimization, LLC%% 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% define datanShifts=14;nWorkers=8;nVars=(nShifts+1)*(nWorkers+1)+2;minShiftIdx=(nShifts+1)*(nWorkers+1);maxShiftIdx=minShiftIdx+1;totalSlackIdx=nVars;Shifts={'Mon1';'Tue2';'Wed3';'Thu4';'Fri5';'Sat6';'Sun7';'Mon8';'Tue9';'Wed10';'Thu11';'Fri12';'Sat13';'Sun14'};Workers={'Amy';'Bob';'Cathy';'Dan';'Ed';'Fred';'Gu';'Tobi'};shiftRequirements=[3;2;4;4;5;6;5;2;2;3;4;6;7;5];availability=[01101010111111;11001101001010;00111011111111;01101101111111;11111011101011;11100101100111;01110110111011;11101111111111];% Build modelmodel.modelname='workforce5';model.modelsense='min';% Initialize assignment decision variables:% x[w][s] == 1 if worker w is assigned% to shift s. Since an assignment model always produces integer% solutions, we use continuous variables and solve as an LP.model.vtype=repmat('C',nVars,1);model.lb=zeros(nVars,1);model.ub=ones(nVars,1);forw=1:nWorkersfors=1:nShiftsmodel.vtype(s+(w-1)*nShifts)='B';model.varnames{s+(w-1)*nShifts}=sprintf('%s.%s',Workers{w},Shifts{s});ifavailability(w,s)==0model.ub(s+(w-1)*nShifts)=0;endendend% Initialize shift slack variablesfors=1:nShiftsmodel.varnames{s+nShifts*nWorkers}=sprintf('ShiftSlack_%s',Shifts{s});model.ub(s+nShifts*nWorkers)=inf;end% Initialize worker slack and diff variablesforw=1:nWorkersmodel.varnames{w+nShifts*(nWorkers+1)}=sprintf('TotalShifts_%s',Workers{w});model.ub(w+nShifts*(nWorkers+1))=inf;end% Initialize min/max shift variablesmodel.ub(minShiftIdx)=inf;model.varnames{minShiftIdx}='MinShift';model.ub(maxShiftIdx)=inf;model.varnames{maxShiftIdx}='MaxShift';% Initialize total slack variablemodel.ub(totalSlackIdx)=inf;model.varnames{totalSlackIdx}='TotalSlack';% Set-up shift-requirements constraints with shift slackmodel.sense=repmat('=',nShifts+1+nWorkers,1);model.rhs=[shiftRequirements;zeros(1+nWorkers,1)];model.constrnames=Shifts;model.A=sparse(nShifts+1+nWorkers,nVars);fors=1:nShiftsforw=1:nWorkersmodel.A(s,s+(w-1)*nShifts)=1;endmodel.A(s,s+nShifts*nWorkers)=1;end% Set TotalSlack equal to the sum of each shift slackfors=1:nShiftsmodel.A(nShifts+1,s+nShifts*nWorkers)=-1;endmodel.A(nShifts+1,totalSlackIdx)=1;model.constrnames{nShifts+1}='TotalSlack';% Set total number of shifts for each workerforw=1:nWorkersfors=1:nShiftsmodel.A(w+nShifts+1,s+(w-1)*nShifts)=-1;endmodel.A(w+nShifts+1,w+nShifts*(nWorkers+1))=1;model.constrnames{nShifts+1+w}=sprintf('totShifts_%s',Workers{w});end% Set minShift / maxShift general constraintsmodel.genconmin.resvar=minShiftIdx;model.genconmin.name='MinShift';model.genconmax.resvar=maxShiftIdx;model.genconmax.name='MaxShift';forw=1:nWorkersmodel.genconmin.vars(w)=w+nShifts*(nWorkers+1);model.genconmax.vars(w)=w+nShifts*(nWorkers+1);end% Set multiobjectivemodel.multiobj(1).objn=zeros(nVars,1);model.multiobj(1).objn(totalSlackIdx)=1;model.multiobj(1).priority=2;model.multiobj(1).weight=1;model.multiobj(1).abstol=2;model.multiobj(1).reltol=0.1;model.multiobj(1).name='TotalSlack';model.multiobj(1).con=0.0;model.multiobj(2).objn=zeros(nVars,1);model.multiobj(2).objn(minShiftIdx)=-1;model.multiobj(2).objn(maxShiftIdx)=1;model.multiobj(2).priority=1;model.multiobj(2).weight=1;model.multiobj(2).abstol=0;model.multiobj(2).reltol=0;model.multiobj(2).name='Fairness';model.multiobj(2).con=0.0;% Save initial modelgurobi_write(model,'workforce5_m.lp');% Optimizeparams.logfile='workforce5_m.log';result=solveandprint(model,params,Shifts,Workers);if~strcmp(result.status,'OPTIMAL')fprintf('Not optimal\n');endendfunctionresult=solveandprint(model, params, Shifts, Workers)% Helper function to solve and display resultsnShifts=length(Shifts);nWorkers=length(Workers);result=gurobi(model,params);ifstrcmp(result.status,'OPTIMAL')fprintf('The optimal objective is %g\n',result.objval);fprintf('Schedule:\n');fors=1:nShiftsfprintf('\t%s:',Shifts{s});forw=1:nWorkersifresult.x(s+(w-1)*nShifts)>0.9fprintf('%s ',Workers{w});endendfprintf('\n');endfprintf('Workload:\n');forw=1:nWorkersfprintf('\t%s: %g\n',Workers{w},result.x(w+nShifts*(nWorkers+1)));endelsefprintf('Optimization finished with status %s\n',result.status);endend
#!/usr/bin/env python3# Copyright 2025, Gurobi Optimization, LLC# 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 */importgurobipyasgpfromgurobipyimportGRBimportsys# Sample data# Sets of days and workersShifts=["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 shiftS=[3,2,4,4,5,6,5,2,2,3,4,6,7,5]shiftRequirements={s:S[i]fori,sinenumerate(Shifts)}# Worker availability: 0 if the worker is unavailable for a shiftA=[[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]fori,sinenumerate(Shifts)forj,winenumerate(Workers)}try:# Create model with a context manager. Upon exit from this block,# model.dispose is called automatically, and memory consumed by the model# is released.## The model is created in the default environment, which will be created# automatically upon model construction. For safe release of resources# tied to the default environment, disposeDefaultEnv is called below.withgp.Model("workforce5")asmodel:# 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 satisfiedslacks=model.addVars(Shifts,name="Slack")# Variable to represent the total slacktotSlack=model.addVar(name="totSlack")# Variables to count the total shifts worked by each workertotShifts=model.addVars(Workers,name="TotShifts")# Constraint: assign exactly shiftRequirements[s] workers# to each shift s, plus the slackmodel.addConstrs((x.sum("*",s)+slacks[s]==shiftRequirements[s]forsinShifts),name="shiftRequirement",)# Constraint: set totSlack equal to the total slackmodel.addConstr(totSlack==slacks.sum(),name="totSlack")# Constraint: compute the total number of shifts for each workermodel.addConstrs((totShifts[w]==x.sum(w,"*")forwinWorkers),name="totShifts")# Constraint: set minShift/maxShift variable to less/greater than the# number of shifts among all workersminShift=model.addVar(name="minShift")maxShift=model.addVar(name="maxShift")model.addGenConstrMin(minShift,totShifts.values(),name="minShift")model.addGenConstrMax(maxShift,totShifts.values(),name="maxShift")# Set global sense for ALL objectivesmodel.ModelSense=GRB.MINIMIZE# Set up primary objectivemodel.setObjectiveN(totSlack,index=0,priority=2,abstol=2.0,reltol=0.1,name="TotalSlack")# Set up secondary objectivemodel.setObjectiveN(maxShift-minShift,index=1,priority=1,name="Fairness")# Save problemmodel.write("workforce5.lp")# Optimizemodel.optimize()status=model.Statusifstatusin(GRB.INF_OR_UNBD,GRB.INFEASIBLE,GRB.UNBOUNDED):print("Model cannot be solved because it is infeasible or unbounded")sys.exit(0)ifstatus!=GRB.OPTIMAL:print(f"Optimization was stopped with status{status}")sys.exit(0)# Print total slack and the number of shifts worked for each workerprint("")print(f"Total slack required:{totSlack.X}")forwinWorkers:print(f"{w} worked{totShifts[w].X} shifts")print("")exceptgp.GurobiErrorase:print(f"Error code{e.errno}:{e}")exceptAttributeErrorase:print(f"Encountered an attribute error:{e}")finally:# Safely release memory and/or server side resources consumed by# the default environment.gp.disposeDefaultEnv()
# Copyright 2025, Gurobi Optimization, LLC## 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 2library('Matrix')library('gurobi')# define datanShifts<-14nWorkers<-8nVars<-(nShifts+1)*(nWorkers+1)+2varIdx<-function(w,s){s+(w-1)*nShifts}shiftSlackIdx<-function(s){s+nShifts*nWorkers}totShiftIdx<-function(w){w+nShifts*(nWorkers+1)}minShiftIdx<-((nShifts+1)*(nWorkers+1))maxShiftIdx<-(minShiftIdx+1)totalSlackIdx<-nVarsShifts<-c('Mon1','Tue2','Wed3','Thu4','Fri5','Sat6','Sun7','Mon8','Tue9','Wed10','Thu11','Fri12','Sat13','Sun14')Workers<-c('Amy','Bob','Cathy','Dan','Ed','Fred','Gu','Tobi')shiftRequirements<-c(3,2,4,4,5,6,5,2,2,3,4,6,7,5)availability<-list(c(0,1,1,0,1,0,1,0,1,1,1,1,1,1),c(1,1,0,0,1,1,0,1,0,0,1,0,1,0),c(0,0,1,1,1,0,1,1,1,1,1,1,1,1),c(0,1,1,0,1,1,0,1,1,1,1,1,1,1),c(1,1,1,1,1,0,1,1,1,0,1,0,1,1),c(1,1,1,0,0,1,0,1,1,0,0,1,1,1),c(0,1,1,1,0,1,1,0,1,1,1,0,1,1),c(1,1,1,0,1,1,1,1,1,1,1,1,1,1))# Function to display resultssolveandprint<-function(model,params){result<-gurobi(model,params=params)if(result$status=='OPTIMAL'){cat('The optimal objective is',result$objval,'\n')cat('Schedule:\n')for(sin1:nShifts){cat('\t',Shifts[s],':')for(win1:nWorkers){if(result$x[varIdx(w,s)]>0.9)cat(Workers[w],' ')}cat('\n')}cat('Workload:\n')for(win1:nWorkers){cat('\t',Workers[w],':',result$x[totShiftIdx(w)],'\n')}}else{cat('Optimization finished with status',result$status)}result}# Set up parametersparams<-list()params$logfile<-'workforce5.log'# Build modelmodel<-list()model$modelname<-'workforce5'model$modelsense<-'min'# 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.model$vtype<-rep('C',nVars)model$lb<-rep(0,nVars)model$ub<-rep(1,nVars)model$varnames<-rep('',nVars)for(win1:nWorkers){for(sin1:nShifts){model$vtype[varIdx(w,s)]='B'model$varnames[varIdx(w,s)]=paste0(Workers[w],'.',Shifts[s])if(availability[[w]][s]==0)model$ub[varIdx(w,s)]=0}}# Initialize shift slack variablesfor(sin1:nShifts){model$varnames[shiftSlackIdx(s)]=paste0('ShiftSlack',Shifts[s])model$ub[shiftSlackIdx(s)]=Inf}# Initialize worker slack and diff variablesfor(win1:nWorkers){model$varnames[totShiftIdx(w)]=paste0('TotalShifts',Workers[w])model$ub[totShiftIdx(w)]=Inf}#Initialize min/max shift variablesmodel$ub[minShiftIdx]=Infmodel$varnames[minShiftIdx]='MinShift'model$ub[maxShiftIdx]=Infmodel$varnames[maxShiftIdx]='MaxShift'#Initialize total slack variablemodel$ub[totalSlackIdx]=Infmodel$varnames[totalSlackIdx]='TotalSlack'# Set up shift-requirements constraintsmodel$A<-spMatrix(nShifts,nVars,i=c(c(mapply(rep,1:nShifts,nWorkers)),c(1:nShifts)),j=c(mapply(varIdx,1:nWorkers,mapply(rep,1:nShifts,nWorkers)),shiftSlackIdx(1:nShifts)),x=rep(1,nShifts*(nWorkers+1)))model$sense<-rep('=',nShifts)model$rhs<-shiftRequirementsmodel$constrnames<-Shifts# Set TotalSlack equal to the sum of each shift slackB<-spMatrix(1,nVars,i=rep(1,nShifts+1),j=c(shiftSlackIdx(1:nShifts),totalSlackIdx),x=c(rep(1,nShifts),-1))model$A<-rbind(model$A,B)model$rhs<-c(model$rhs,0)model$sense<-c(model$sense,'=')model$constrnames<-c(model$constrnames,'TotalSlack')# Set total number of shifts for each workerB<-spMatrix(nWorkers,nVars,i=c(mapply(rep,1:nWorkers,nShifts),1:nWorkers),j=c(mapply(varIdx,c(mapply(rep,1:nWorkers,nShifts)),1:nShifts),totShiftIdx(1:nWorkers)),x=c(rep(1,nShifts*nWorkers),rep(-1,nWorkers)))model$A<-rbind(model$A,B)model$rhs<-c(model$rhs,rep(0,nWorkers))model$sense<-c(model$sense,rep('=',nWorkers))model$constrnames<-c(model$constrnames,sprintf('TotalShifts%s',Workers[1:nWorkers]))# Set minShift / maxShift general constraintsmodel$genconmin<-list(list(resvar=minShiftIdx,vars=c(totShiftIdx(1:nWorkers)),name='MinShift'))model$genconmax<-list(list(resvar=maxShiftIdx,vars=c(totShiftIdx(1:nWorkers)),name='MaxShift'))# Set multiobjectivemodel$multiobj<-list(1:2)model$multiobj[[1]]<-list()model$multiobj[[1]]$objn<-c(rep(0,nVars))model$multiobj[[1]]$objn[totalSlackIdx]=1model$multiobj[[1]]$priority<-2model$multiobj[[1]]$weight<-1model$multiobj[[1]]$abstol<-2model$multiobj[[1]]$reltol<-0.1model$multiobj[[1]]$name<-'TotalSlack'model$multiobj[[1]]$con<-0.0model$multiobj[[2]]<-list()model$multiobj[[2]]$objn<-c(rep(0,nVars))model$multiobj[[2]]$objn[minShiftIdx]=-1model$multiobj[[2]]$objn[maxShiftIdx]=1model$multiobj[[2]]$priority<-1model$multiobj[[2]]$weight<-1model$multiobj[[2]]$abstol<-0model$multiobj[[2]]$reltol<-0model$multiobj[[2]]$name<-'Fairness'model$multiobj[[2]]$con<-0.0# Save initial modelgurobi_write(model,'workforce5.lp',params)# Optimizeresult<-solveandprint(model,params)if(result$status!='OPTIMAL')stop('Stop now\n')#Clear spacerm(model,params,availability,Shifts,Workers,shiftRequirements,result)
' Copyright 2025, Gurobi Optimization, LLC'' 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 */ImportsSystemImportsGurobiClassworkforce5_vbSharedSubMain()Try' Sample data' Sets of days and workersDimShiftsAsString()=NewString(){_"Mon1","Tue2","Wed3","Thu4","Fri5","Sat6","Sun7",_"Mon8","Tue9","Wed10","Thu11","Fri12","Sat13","Sun14"}DimWorkersAsString()=NewString(){_"Amy","Bob","Cathy","Dan","Ed","Fred","Gu","Tobi"}DimnShiftsAsInteger=Shifts.LengthDimnWorkersAsInteger=Workers.Length' Number of workers required for each shiftDimshiftRequirementsAsDouble()=NewDouble(){_3,2,4,4,5,6,5,2,2,3,4,6,7,5}' Worker availability: 0 if the worker is unavailable for a shiftDimavailabilityAsDouble(,)=NewDouble(,){_{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}}' Create environmentDimenvAsNewGRBEnv()' Create initial modelDimmodelAsNewGRBModel(env)model.ModelName="workforce5_vb"' 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.DimxAsGRBVar(,)=NewGRBVar(nWorkers-1,nShifts-1){}ForwAsInteger=0TonWorkers-1ForsAsInteger=0TonShifts-1x(w,s)=model.AddVar(0,availability(w,s),0,GRB.BINARY,_String.Format("{0}.{1}",Workers(w),Shifts(s)))NextNext' Slack variables for each shift constraint so that the shifts can' be satisfiedDimslacksAsGRBVar()=NewGRBVar(nShifts-1){}ForsAsInteger=0TonShifts-1slacks(s)=model.AddVar(0,GRB.INFINITY,0,GRB.CONTINUOUS,_String.Format("{0}Slack",Shifts(s)))Next' Variable to represent the total slackDimtotSlackAsGRBVar=model.AddVar(0,GRB.INFINITY,0,GRB.CONTINUOUS,"totSlack")' Variables to count the total shifts worked by each workerDimtotShiftsAsGRBVar()=NewGRBVar(nWorkers-1){}ForwAsInteger=0TonWorkers-1totShifts(w)=model.AddVar(0,GRB.INFINITY,0,GRB.CONTINUOUS,_String.Format("{0}TotShifts",Workers(w)))NextDimlhsAsGRBLinExpr' Constraint: assign exactly shiftRequirements[s] workers' to each shift s, plus the slackForsAsInteger=0TonShifts-1lhs=NewGRBLinExpr()lhs.AddTerm(1.0,slacks(s))ForwAsInteger=0TonWorkers-1lhs.AddTerm(1.0,x(w,s))Nextmodel.AddConstr(lhs,GRB.EQUAL,shiftRequirements(s),Shifts(s))Next' Constraint: set totSlack equal to the total slacklhs=NewGRBLinExpr()lhs.AddTerm(-1.0,totSlack)ForsAsInteger=0TonShifts-1lhs.AddTerm(1.0,slacks(s))Nextmodel.AddConstr(lhs,GRB.EQUAL,0,"totSlack")' Constraint: compute the total number of shifts for each workerForwAsInteger=0TonWorkers-1lhs=NewGRBLinExpr()lhs.AddTerm(-1.0,totShifts(w))ForsAsInteger=0TonShifts-1lhs.AddTerm(1.0,x(w,s))Nextmodel.AddConstr(lhs,GRB.EQUAL,0,String.Format("totShifts{0}",Workers(w)))Next' Constraint: set minShift/maxShift variable to less <=/>= to the' number of shifts among all workersDimminShiftAsGRBVar=model.AddVar(0,GRB.INFINITY,0,GRB.CONTINUOUS,"minShift")DimmaxShiftAsGRBVar=model.AddVar(0,GRB.INFINITY,0,GRB.CONTINUOUS,"maxShift")model.AddGenConstrMin(minShift,totShifts,GRB.INFINITY,"minShift")model.AddGenConstrMax(maxShift,totShifts,-GRB.INFINITY,"maxShift")' Set global sense for ALL objectivesmodel.ModelSense=GRB.MINIMIZE' Set primary objectivemodel.SetObjectiveN(totSlack,0,2,1.0,2.0,0.1,"TotalSlack")' Set secondary objectivemodel.SetObjectiveN(maxShift-minShift,1,1,1.0,0,0,"Fairness")' Save problemmodel.Write("workforce5_vb.lp")' OptimizeDimstatusAsInteger=_solveAndPrint(model,totSlack,nWorkers,Workers,totShifts)Ifstatus<>GRB.Status.OPTIMALThenReturnEndIf' Dispose of model and environmentmodel.Dispose()env.Dispose()CatcheAsGRBExceptionConsole.WriteLine("Error code: {0}. {1}",e.ErrorCode,e.Message)EndTryEndSubPrivateSharedFunctionsolveAndPrint(ByValmodelAsGRBModel,_ByValtotSlackAsGRBVar,_ByValnWorkersAsInteger,_ByValWorkersAsString(),_ByValtotShiftsAsGRBVar())AsIntegermodel.Optimize()DimstatusAsInteger=model.StatusIfstatus=GRB.Status.INF_OR_UNBDOrElse_status=GRB.Status.INFEASIBLEOrElse_status=GRB.Status.UNBOUNDEDThenConsole.WriteLine("The model cannot be solved "&_"because it is infeasible or unbounded")ReturnstatusEndIfIfstatus<>GRB.Status.OPTIMALThenConsole.WriteLine("Optimization was stopped with status {0}",status)ReturnstatusEndIf' Print total slack and the number of shifts worked for each workerConsole.WriteLine(vbLf&"Total slack required: {0}",totSlack.X)ForwAsInteger=0TonWorkers-1Console.WriteLine("{0} worked {1} shifts",Workers(w),totShifts(w).X)NextConsole.WriteLine(vbLf)ReturnstatusEndFunctionEndClass
#!/usr/bin/env python3# Copyright 2025, Gurobi Optimization, LLC# Assign workers to shifts; each worker may or may not be available on a# particular day. The optimization problem is solved as a batch, and# the schedule constructed only from the meta data available in the solution# JSON.## NOTE: You'll need a license file configured to use a Cluster Manager# for this example to run.importtimeimportjsonimportsysimportgurobipyasgpfromgurobipyimportGRBfromcollectionsimportOrderedDict,defaultdict# For later pretty printing names for the shiftsshiftname=OrderedDict([("Mon1","Monday 8:00"),("Mon8","Monday 14:00"),("Tue2","Tuesday 8:00"),("Tue9","Tuesday 14:00"),("Wed3","Wednesday 8:00"),("Wed10","Wednesday 14:00"),("Thu4","Thursday 8:00"),("Thu11","Thursday 14:00"),("Fri5","Friday 8:00"),("Fri12","Friday 14:00"),("Sat6","Saturday 8:00"),("Sat13","Saturday 14:00"),("Sun7","Sunday 9:00"),("Sun14","Sunday 12:00"),])# Build the assignment problem in a Model, and submit it for batch optimization## Required input: A Cluster Manager environment setup for batch optimizationdefsubmit_assigment_problem(env):# Number of workers required for each shiftshifts,shiftRequirements=gp.multidict({"Mon1":3,"Tue2":2,"Wed3":4,"Thu4":4,"Fri5":5,"Sat6":5,"Sun7":3,"Mon8":2,"Tue9":2,"Wed10":3,"Thu11":4,"Fri12":5,"Sat13":7,"Sun14":5,})# Amount each worker is paid to work one shiftworkers,pay=gp.multidict({"Amy":10,"Bob":12,"Cathy":10,"Dan":8,"Ed":8,"Fred":9,"Gu":11,})# Worker availabilityavailability=gp.tuplelist([("Amy","Tue2"),("Amy","Wed3"),("Amy","Thu4"),("Amy","Sun7"),("Amy","Tue9"),("Amy","Wed10"),("Amy","Thu11"),("Amy","Fri12"),("Amy","Sat13"),("Amy","Sun14"),("Bob","Mon1"),("Bob","Tue2"),("Bob","Fri5"),("Bob","Sat6"),("Bob","Mon8"),("Bob","Thu11"),("Bob","Sat13"),("Cathy","Wed3"),("Cathy","Thu4"),("Cathy","Fri5"),("Cathy","Sun7"),("Cathy","Mon8"),("Cathy","Tue9"),("Cathy","Wed10"),("Cathy","Thu11"),("Cathy","Fri12"),("Cathy","Sat13"),("Cathy","Sun14"),("Dan","Tue2"),("Dan","Thu4"),("Dan","Fri5"),("Dan","Sat6"),("Dan","Mon8"),("Dan","Tue9"),("Dan","Wed10"),("Dan","Thu11"),("Dan","Fri12"),("Dan","Sat13"),("Dan","Sun14"),("Ed","Mon1"),("Ed","Tue2"),("Ed","Wed3"),("Ed","Thu4"),("Ed","Fri5"),("Ed","Sat6"),("Ed","Mon8"),("Ed","Tue9"),("Ed","Thu11"),("Ed","Sat13"),("Ed","Sun14"),("Fred","Mon1"),("Fred","Tue2"),("Fred","Wed3"),("Fred","Sat6"),("Fred","Mon8"),("Fred","Tue9"),("Fred","Fri12"),("Fred","Sat13"),("Fred","Sun14"),("Gu","Mon1"),("Gu","Tue2"),("Gu","Wed3"),("Gu","Fri5"),("Gu","Sat6"),("Gu","Sun7"),("Gu","Mon8"),("Gu","Tue9"),("Gu","Wed10"),("Gu","Thu11"),("Gu","Fri12"),("Gu","Sat13"),("Gu","Sun14"),])# Start environment, get model in this environmentwithgp.Model("assignment",env=env)asm:# Assignment variables: x[w,s] == 1 if worker w is assigned to shift s.# Since an assignment model always produces integer solutions, we use# continuous variables and solve as an LP.x=m.addVars(availability,ub=1,name="x")# Set tags encoding the assignments for later retrieval of the schedule.# Each tag is a JSON string of the format# {# "Worker": "<Name of the worker>",# "Shift": "String representation of the shift"# }#fork,vinx.items():name,timeslot=kd={"Worker":name,"Shift":shiftname[timeslot]}v.VTag=json.dumps(d)# The objective is to minimize the total pay costsm.setObjective(gp.quicksum(pay[w]*x[w,s]forw,sinavailability),GRB.MINIMIZE)# Constraints: assign exactly shiftRequirements[s] workers to each shiftreqCts=m.addConstrs((x.sum("*",s)==shiftRequirements[s]forsinshifts),"_")# Submit this model for batch optimization to the cluster manager# and return its batch ID for later querying the solutionbatchID=m.optimizeBatch()returnbatchID# Wait for the final status of the batch.# Initially the status of a batch is "submitted"; the status will change# once the batch has been processed (by a compute server).defwaitforfinalbatchstatus(batch):# Wait no longer than ten secondsmaxwaittime=10starttime=time.time()whilebatch.BatchStatus==GRB.BATCH_SUBMITTED:# Abort this batch if it is taking too longcurtime=time.time()ifcurtime-starttime>maxwaittime:batch.abort()break# Wait for one secondtime.sleep(1)# Update the resident attribute cache of the Batch object with the# latest values from the cluster manager.batch.update()# Print the schedule according to the solution in the given dictdefprint_shift_schedule(soldict):schedule=defaultdict(list)# Iterate over the variables that take a non-zero value (i.e.,# an assignment), and collect them per dayforvinsoldict["Vars"]:# There is only one VTag, the JSON dict of an assignment we passed# in as the VTagassignment=json.loads(v["VTag"][0])schedule[assignment["Shift"]].append(assignment["Worker"])# Print the scheduleforkinshiftname.values():day,time=k.split()workers=", ".join(schedule[k])print(f" -{day:10}{time:>5}:{workers}")if__name__=="__main__":# Create Cluster Manager environment in batch mode.env=gp.Env(empty=True)env.setParam("CSBatchMode",1)# env is a context manager; upon leaving, Env.dispose() is calledwithenv.start():# Submit the assignment problem to the cluster manager, get batch IDbatchID=submit_assigment_problem(env)# Create a batch object, wait for batch to complete, query solution JSONwithgp.Batch(batchID,env)asbatch:waitforfinalbatchstatus(batch)ifbatch.BatchStatus!=GRB.BATCH_COMPLETED:print("Batch request couldn't be completed")sys.exit(0)jsonsol=batch.getJSONSolution()# Dump JSON solution string into a dictsoldict=json.loads(jsonsol)# Has the assignment problem been solved as expected?ifsoldict["SolutionInfo"]["Status"]!=GRB.OPTIMAL:# Shouldn't happen...print("Assignment problem could not be solved to optimality")sys.exit(0)# Print shift schedule from solution JSONprint_shift_schedule(soldict)
Help and Feedback