model AssessorScheduling options noimplicit uses "mmxprs" parameters DATAFILE = "AssessorData.txt" !Data filename OUTFILE = "AssessorSchedule.txt" !Output filename ZERO_TOLERANCE = 0.001 !Tolerance used to define non-zero values end-parameters declarations DAYS: set of integer !set of all days SESSIONS: set of integer !set of all sessions ASSESSORS: set of string !set of all assessors SKILLS: set of string !set of all skills SessionDemand: dynamic array(DAYS, SESSIONS, SKILLS) of integer !Skill and proficiency demand Skill: dynamic array(ASSESSORS, SKILLS) of integer !Assessor skill matrix HourlyRate: array(ASSESSORS) of real !Assessor hourly rate OneOffCost: array(ASSESSORS) of real !Assessor one-off cost MinAssessorPerSession: integer !Minimum number of assessors per session MaxAssessorPerSession: real !Maximum number of assessors per session MaxNumHours: integer !Maximum working hours over appraisal period SessionLength: integer !Length of a session MaxNumSessions: integer !Maximum number of sessions that can be worked over appraisal period MaxNumSessionsPerDay: integer !Maximum number of sessions that can be worked each day !A set of restrictions on the availability of assessors RestrictedAvailability: array(NUMRESTRICTED: range, RESTRICTEDLIST:range) of string end-declarations initializations from DATAFILE SKILLS SessionDemand Skill HourlyRate OneOffCost MinAssessorPerSession MaxAssessorPerSession MaxNumHours SessionLength MaxNumSessionsPerDay RestrictedAvailability end-initializations finalize(DAYS) finalize(SESSIONS) finalize(ASSESSORS) finalize(SKILLS) !evaluate MaxNumSessions MaxNumSessions:= floor(MaxNumHours / SessionLength) !define constraints declarations assign_assessor: dynamic array(ASSESSORS, DAYS, SESSIONS) of mpvar use_assessor: dynamic array(ASSESSORS) of mpvar MinAssessorConstraint: dynamic array(DAYS, SESSIONS) of linctr MaxAssessorConstraint: dynamic array(DAYS, SESSIONS) of linctr UseOneSkill: dynamic array(ASSESSORS, DAYS, SESSIONS) of linctr NumSessionsMax: dynamic array(ASSESSORS) of linctr NumSessionsPerDayLimit: dynamic array(ASSESSORS, DAYS) of linctr NoConsecutiveSessions: dynamic array(ASSESSORS,DAYS,range) of linctr Restriction: dynamic array(range) of linctr TotalCost: linctr end-declarations !declare procedures forward procedure CreateVariables forward procedure PrintSchedule !create variables CreateVariables !Minimum number of assessors per session forall(t in DAYS, s in SESSIONS | sum(c in SKILLS) SessionDemand(t,s,c) > 0) do MinAssessorConstraint(t,s):= sum(a in ASSESSORS | exists(assign_assessor(a,t,s))) assign_assessor(a,t,s) >= MinAssessorPerSession end-do !Maximum number of assessors per session forall(t in DAYS, s in SESSIONS | sum(c in SKILLS) SessionDemand(t,s,c) > 0) do MaxAssessorConstraint(t,s):= sum(a in ASSESSORS | exists(assign_assessor(a,t,s))) assign_assessor(a,t,s) <= MaxAssessorPerSession end-do !Maximum number of sessions that can be worked, over appraisal period forall(a in ASSESSORS) do NumSessionsMax(a):= sum(t in DAYS, s in SESSIONS | exists(assign_assessor(a,t,s))) assign_assessor(a,t,s) <= MaxNumSessions * use_assessor(a) end-do !Maximum number of sessions that can be worked, on each day forall(a in ASSESSORS, t in DAYS) do NumSessionsPerDayLimit(a, t):= sum(s in SESSIONS | exists(assign_assessor(a,t,s))) assign_assessor(a,t,s) <= MaxNumSessionsPerDay * use_assessor(a) end-do !No two consecutive sessions forall( a in ASSESSORS, t in DAYS, k in 1..(getsize(SESSIONS)-1) | exists(assign_assessor(a,t,SESSIONS(k))) and exists(assign_assessor(a,t,SESSIONS(k+1)))) do NoConsecutiveSessions(a,t,k):= assign_assessor(a,t,SESSIONS(k)) + assign_assessor(a,t,SESSIONS(k+1)) <= use_assessor(a) end-do !Availability restriction forall(i in NUMRESTRICTED) do Restriction(i):= sum(r in RESTRICTEDLIST) use_assessor(RestrictedAvailability(i,r)) <= 1 end-do !Total assessor cost = OneOffCost + Costs due to hours worked TotalCost:= sum(a in ASSESSORS) OneOffCost(a) * use_assessor(a) + sum(a in ASSESSORS, t in DAYS, s in SESSIONS) HourlyRate(a) * SessionLength * assign_assessor(a,t,s) minimise(TotalCost) writeln("Total cost: $", getobjval) writeln("Total assessors in panel: ", sum(a in ASSESSORS) getsol(use_assessor(a))) writeln("Average hours per assessor: ", (sum(a in ASSESSORS, t in DAYS, s in SESSIONS) SessionLength * getsol(assign_assessor(a,t,s)))/sum(a in ASSESSORS) getsol(use_assessor(a))) writeln PrintSchedule fopen(OUTFILE, F_OUTPUT) PrintSchedule fclose(F_OUTPUT) !****PROCEDURES procedure CreateVariables forall( t in DAYS, s in SESSIONS, c in SKILLS | exists(SessionDemand(t,s,c))) do forall( a in ASSESSORS | Skill(a,c) >= SessionDemand(t,s,c)) do create(assign_assessor(a,t,s)) assign_assessor(a,t,s) is_binary if not exists(use_assessor(a)) then create(use_assessor(a)) use_assessor(a) is_binary end-if end-do end-do end-procedure procedure PrintSchedule declarations MyCost: real MyHours: real end-declarations write(strfmt("DAY",-4)) write(strfmt("SESSION",-8)) write(strfmt("SKILL",-25)) writeln forall(t in DAYS, s in SESSIONS, c in SKILLS | exists(SessionDemand(t,s,c))) do write(strfmt(t,-4)) write(strfmt(s,-8)) write(strfmt(c + "(Prof:" + SessionDemand(t,s,c) + ")",-30)) write("\t: ") forall(a in ASSESSORS) do if getsol(assign_assessor(a,t,s)) > ZERO_TOLERANCE then write(strfmt(a + "(Prof:" + Skill(a,c) + ")",-20)) end-if end-do writeln end-do writeln forall( a in ASSESSORS | getsol(use_assessor(a)) > ZERO_TOLERANCE) do MyHours:= sum(t in DAYS, s in SESSIONS) SessionLength * getsol(assign_assessor(a,t,s)) MyCost:= OneOffCost(a) + HourlyRate(a) * MyHours write(a, "(", MyHours, " hours, $", MyCost, "): ") (! forall(c in SKILLS | Skill(a,c) > 0) do write(c, "-", Skill(a,c), "; ") end-do !) writeln forall( t in DAYS, s in SESSIONS, c in SKILLS | getsol(assign_assessor(a,t,s)) > ZERO_TOLERANCE and exists(SessionDemand(t,s,c))) do writeln("\tDay ", t, " Session ", s, ": ", c, "(Prof:", SessionDemand(t,s,c), ")") end-do writeln end-do end-procedure end-model