Solve a timetabling problem for college courses under various constraints
|
|
Type: | Programming |
Rating: | 2 (easy-medium) |
Description: | A timetabling problem for college courses under various constraints. |
File(s): | college_timetable.R |
|
college_timetable.R |
##################################### # This file is part of the # # Xpress-R interface examples # # # # (c) 2022-2025 Fair Isaac Corporation # ##################################### #' --- #' title: "Establishing A College Timetable" #' author: Y. Gu #' date: Jul.2021 #' --- #' #' #' ## ----setup, include=FALSE----------------------------------------------------- knitr::opts_chunk$set(echo = TRUE) knitr::opts_chunk$set(results = "hold") knitr::opts_chunk$set(warning = FALSE, message = FALSE) #' #' #' ## Brief Introduction To The Problem #' #' [This is a conversion of the Mosel example 'Establishing A College Timetable'](https://www.fico.com/fico-xpress-optimization/docs/latest/examples/mosel/ApplBook/I_TimePers/i3school.mos). #' A brief introduction to this problem is given below, and to see the full #' mathematical modeling of this problem you may refer to section 14.3, page 218 of the #' book 'Applications of optimization with Xpress'. #' #' This example is a timetabling problem for college courses. Optimization can provide #' useful help for this kind of problem, while since many subtle sociological and #' psychological constraints are hard to consider in a mathematical model, the solution #' should always be used interactively: the optimization model makes suggestions #' and the human being plans by adjusting the solution to exact needs. #' #' In our example, 9 teachers will give lessons to 2 classes for 5 days per week, and #' each day contains 4 time slots for scheduling lessons. We set binary variables #' 'teach(t,c,l,d)' that take value 1 if the teacher t gives a lesson to class c in #' period l of day d. Our goal is to find a feasible timetable that satisfies all the #' constraints, but we can set objective as minimizing the 'holes' in the timetable, #' i.e., we minimize the sum of courses taught during slots 1 and 4 of every day. #' #' To schedule a feasible timetable, many constraints should be satisfied. Firstly, all #' lessons taught by each teacher to each class must be scheduled. Secondly, a class #' could have at most one course at any time. Similarly, a teacher must not teach more #' than one course at a time slot. To prevent students from getting bored, another #' constraint is specified, where at most one lesson per subject could be taught on the #' same day . #' #' There are also some specific constraints for this example. For example, sport lessons #' have to take place on Thursday afternoon from 14:00 to 16:00. Other specific conditions #' will be discussed when we add rows to the problem in section 'Add Rows'. #' #' The solution to this problem is not unique, and as mentioned before, the one that #' matches the real situation most should be chosen and adjustments could always be made #' if necessary. For mathematical formulations, please refer to the book #' 'Applications of optimization with Xpress' #' #' #' For this example, we need packages 'xpress' and 'dplyr'. Besides, we use the #' function `pretty_name` to give the variables and constraints concise names. #' ## ----Load The Packages And The Function To Give Names------------------------- library(xpress) library(dplyr) pretty_name <- function(prefix, y) { "%s_%s" %>% sprintf(prefix, paste(lapply(names(y), function(name) paste(name, y[name], sep = "_")), collapse = "_")) } #' #' #' Create a new empty problem and give the problem a suitable name. #' ## ----Create The Problem------------------------------------------------------- # create a new problem prob <- createprob() # set the problem name setprobname(prob, "CollegeTimetable") #' #' #' Add the values we need for this example. #' ## ----Data--------------------------------------------------------------------- # 1. classes CLASS <- 1:2 # 2. periods per day Periods <- 1:4 NP <- length(Periods) # 3. days per week Days <- c("Monday", "Tuesday", "Wednesday", "Thursday", "Friday") ND <- length(Days) # 4. teachers Teachers <- c( "Mr Cheese", "Mrs Insulin", "Mr Map", "Mr Effofecks", "Mrs Derivate", "Mrs Electron", "Mr Wise", "Mr Muscle", "Mrs Biceps" ) # 5. data frame for storing variable indices index.df <- expand.grid(Teachers, CLASS, Periods, Days) names(index.df) <- c("teacher", "class", "period", "day") # 6. lessons per teacher and class Courses <- rep(c(c(1, 3, 2, 0, 4, 3, 1, 1, 0), # lessons for class 1 c(1, 3, 2, 4, 0, 3, 1, 0, 1) # lessons for class 2 ), NP * ND) index.df$courses <- Courses # add Courses as a vector 'courses' to 'index.df' for future use #' #' #' Create binary variables 'teach' and set objective as described in introduction. #' ## ----Add Columns-------------------------------------------------------------- # create binary variables 'teach' for each teacher, each class and each period. Also # create a vector 'teach' in 'index.df' to store the indices of 'teach' index.df$teach <- index.df %>% apply(1, function(x) xprs_newcol( prob, lb = 0, ub = 1, coltype = "B", objcoef = NULL, name = paste0(x[c("teacher", "class", "period", "day")], collapse = "_") )) # set objective: minimize the number of "holes" in the class timetables, i.e., minimize the courses # being placed in periods 1 and 4. colidx <- (index.df %>% filter(period == 1 | period == 4))$teach chgobj(prob, colind = colidx, objcoef = rep(1, length(colidx))) #' #' #' Add the constraints described in introduction and some specific constraints related to #' the teachers in this example. #' ## ----Add Rows, results='hide'------------------------------------------------- # 1. all lessons taught by the teacher t to class c must be scheduled index.df %>% group_by(teacher, class) %>% group_map( ~ xprs_newrow( prob, colind = .x$teach, rowcoef = rep(1, nrow(.x)), rowtype = 'E', rhs = .x$courses[1], name = pretty_name("allcourses", .y) ) ) # 2. a class has at most one course at any time index.df %>% group_by(class, period, day) %>% group_map( ~ xprs_newrow( prob, colind = .x$teach, rowcoef = rep(1, nrow(.x)), rowtype = 'L', rhs = 1, name = pretty_name("onecourse", .y) ) ) # 3. a teacher must not teach more than one lesson at a time index.df %>% group_by(teacher, period, day) %>% group_map( ~ xprs_newrow( prob, colind = .x$teach, rowcoef = rep(1, nrow(.x)), rowtype = 'L', rhs = 1, name = pretty_name("oneteacher", .y) ) ) # 4. at most one two-hour lesson per subject is taught on the same day index.df %>% group_by(teacher, class, day) %>% group_map( ~ xprs_newrow( prob, colind = .x$teach, rowcoef = rep(1, nrow(.x)), rowtype = 'L', rhs = 1, name = pretty_name("samesubject", .y) ) ) # 5. the specific conditions for this example # Note that these constraints require us to fix some 'teach' variables to 0 or 1, and # to realize this we change the bounds of these variables. An equivalent way to # achieve this is to set additional constraints such as: teach(t,c,l,d) = 0. # 5.1 the sport lessons taught by Mr Muscle and Mrs Biceps have to take place on Thursday afternoon # from 14:00 to 16:00 (time slot 3 on Thursday) colidx <- c((index.df %>% filter(period==3 & teacher=="Mr Muscle" & class==1 & day=="Thursday"))$teach, (index.df %>% filter(period==3 & teacher=="Mrs Biceps" & class==2 & day=="Thursday"))$teach) chgbounds( prob, colind = colidx, bndtype = rep("L", length(colidx)), bndval = rep(1, length(colidx)) ) # 5.2 no course may be scheduled during Monday period 1 colidx <- (index.df %>% filter(period==1 & day=="Monday"))$teach chgbounds( prob, colind = colidx, bndtype = rep("U", length(colidx)), bndval = rep(0, length(colidx)) ) # 5.3 Mr Effofecks does not teach on Monday morning (time slots 1 & 2 on Monday) colidx <- (index.df %>% filter(teacher=="Mr Effofecks" & (period==1 | period==2 ) & day=="Monday"))$teach chgbounds( prob, colind = colidx, bndtype = rep("U", length(colidx)), bndval = rep(0, length(colidx)) ) # 5.4 Mrs Insulin does not teach on Wednesday colidx <- (index.df %>% filter(teacher=="Mrs Insulin" & day=="Wednesday"))$teach chgbounds( prob, colind = colidx, bndtype = rep("U", length(colidx)), bndval = rep(0, length(colidx)) ) #' #' #' Now we can solve the problem. #' ## ----Solve The Problem-------------------------------------------------------- setoutput(prob) summary(xprs_optimize(prob)) #' #' #' Display the solutions here. #' ## ----The Solutions------------------------------------------------------------ index.df$solution <- getsolution(prob)$x # timetable for class 1 c1_timetable <- as.data.frame(matrix("-", nrow = NP, ncol = ND)) names(c1_timetable) <- Days c1_lessons <- index.df %>% filter(class == 1 & solution == 1) for (i in 1:nrow(c1_lessons)) { day <- as.character(c1_lessons$day[i]) period <- c1_lessons$period[i] c1_timetable[period, day] <- as.character(c1_lessons$teacher[i]) } print("The timetable of class 1 is:") c1_timetable cat("\n") # timetable for class 2 c2_timetable <- as.data.frame(matrix("-", nrow = NP, ncol = ND)) names(c2_timetable) <- Days c2_lessons <- index.df %>% filter(class == 2 & solution == 1) for (i in 1:nrow(c2_lessons)) { day <- as.character(c2_lessons$day[i]) period <- c2_lessons$period[i] c2_timetable[period, day] <- as.character(c2_lessons$teacher[i]) } print("The timetable of class 2 is:") c2_timetable #' #' |
© 2001-2025 Fair Isaac Corporation. All rights reserved. This documentation is the property of Fair Isaac Corporation (“FICO”). Receipt or possession of this documentation does not convey rights to disclose, reproduce, make derivative works, use, or allow others to use it except solely for internal evaluation purposes to determine whether to purchase a license to the software described in this documentation, or as otherwise set forth in a written software license agreement between you and FICO (or a FICO affiliate). Use of this documentation and the software described in it must conform strictly to the foregoing permitted uses, and no other use is permitted.