SHOW:
|
|
- or go back to the newest paste.
| 1 | ||
| 2 | ||
| 3 | #' internal function used by ds_user_parallel | |
| 4 | #' assign capacities to workload for one region and determines transferable workload | |
| 5 | ds_user <- function( | |
| 6 | workload, capacity, min_workertype, min_worktype, region, month_map, NbMonth, categ_map, worktype_map, NbWT, Nbtot, NbCat, dimsToIndex, | |
| 7 | no_work, max_offshore | |
| 8 | ) {
| |
| 9 | if(length(region)!=1) stop("region must be character of length 1.")
| |
| 10 | ||
| 11 | ### set up lp | |
| 12 | # the first Nbtot are the decision variables we are interested in (effort per month/work_type/categ/workplace/work_source). These do not | |
| 13 | # go into the objective (zero coefficients). They are, however, the main result. | |
| 14 | # the next NbCat variables are the "unmet workload" that is the work for which there is no capacity. Ideally, these would all be zero. | |
| 15 | # the next NbCat variables are "max effort over the months for a given category". This is used to evenly distribute the effort. | |
| 16 | # the next NbWT variables are "min capacity required" or "max effort over the months for a given worker type. | |
| 17 | lprec <- make.lp(0, ncol=Nbtot+NbCat+NbCat+NbWT+NbCat) | |
| 18 | set.objfn(lprec, obj=c(rep(0, Nbtot), rep(1000, NbCat), rep(1, NbCat), c(100, 80, 60), rep(500, NbCat))) | |
| 19 | ||
| 20 | ### constraints 1: no work in certain months for some types of work | |
| 21 | for (categ in names(no_work)) | |
| 22 | for (m in intersect(names(month_map), no_work[[categ]])) {
| |
| 23 | idx <- dimsToIndex(month=m, categ=categ) | |
| 24 | add.constraint(lprec, xt=rep(1, length(idx)), type="=", rhs=0, indices=idx) | |
| 25 | } | |
| 26 | ||
| 27 | ### constraints 2: workload must be met | |
| 28 | for (categ in names(categ_map)) {
| |
| 29 | add.constraint( | |
| 30 | lprec, xt=rep(1, NbMonth*NbWT+2), "=", rhs=workload[region, categ], | |
| 31 | indices=c( | |
| 32 | dimsToIndex(categ=categ), | |
| 33 | Nbtot+categ_map[categ], # unmet untransferable effort | |
| 34 | Nbtot+NbCat+NbCat+NbWT+categ_map[categ] # unmet transferable effort | |
| 35 | ) | |
| 36 | ) | |
| 37 | } | |
| 38 | ||
| 39 | ### constraints 3: effort <= capacity | |
| 40 | for (m in names(month_map)) {
| |
| 41 | idx <- which(capacity[,"mon"]==m) | |
| 42 | add.constraint( | |
| 43 | lprec, xt=rep(1, NbCat), type="<=", rhs=capacity[idx, "dcutil"], | |
| 44 | indices=dimsToIndex(month=m, worker_type="data_collector") | |
| 45 | ) | |
| 46 | add.constraint( | |
| 47 | lprec, xt=rep(1, NbCat), type="<=", rhs=capacity[idx, "regutil"], | |
| 48 | indices=dimsToIndex(month=m, worker_type="regular") | |
| 49 | ) | |
| 50 | add.constraint( | |
| 51 | lprec, xt=rep(1, NbCat), type="<=", rhs=capacity[idx, "nonregutil"], | |
| 52 | indices=dimsToIndex(month=m, worker_type="nonregular") | |
| 53 | ) | |
| 54 | } | |
| 55 | ||
| 56 | ### constraints 4: minimum worker type utilization | |
| 57 | ## for each month, region and category, we have two constraints: | |
| 58 | ## 1) effort_regular >= perc * (effort_dc + effort_nonreg + effort_regular) | |
| 59 | ## 2) effort_dc <= perc * (effort_dc + effort_nonreg + effort_regular) | |
| 60 | ## where perc are input constants given in the min_workertype data.frame | |
| 61 | ## By simple algebra, this normalizes to | |
| 62 | ## 1') -perc * effort_dc - perc * effort_nonreg + (1-perc) * effort_regular >= 0 | |
| 63 | ## 2') (1-perc) * effort_dc - perc * effort_nonreg - perc * effort_regular <= 0 | |
| 64 | ## the second constraint only applies if there is actually data_collector capacity in that region | |
| 65 | for (m in names(month_map)) | |
| 66 | for (categ in names(categ_map)) {
| |
| 67 | cap_idx <- which(capacity[,"mon"]==m) | |
| 68 | if(capacity[cap_idx, "regutil"]>0){
| |
| 69 | idx1 <- dimsToIndex(month=m, categ=categ, worker_type=c("data_collector", "nonregular"))
| |
| 70 | idx2 <- dimsToIndex(month=m, categ=categ, worker_type="regular") | |
| 71 | perc <- min_workertype[categ, "reg_min"] | |
| 72 | add.constraint(lprec, xt=c(rep(-perc, 2), (1-perc)), type=">=", rhs=0, indices=c(idx1, idx2)) | |
| 73 | } | |
| 74 | if(capacity[cap_idx, "dcutil"]>0) {
| |
| 75 | idx1 <- dimsToIndex(month=m, categ=categ, worker_type=c("regular", "nonregular"))
| |
| 76 | idx2 <- dimsToIndex(month=m, categ=categ, worker_type="data_collector") | |
| 77 | perc <- min_workertype[categ, "dc_min"] | |
| 78 | add.constraint(lprec, xt=c(rep(-perc, 2), (1-perc)), type="<=", rhs=0, indices=c(idx1, idx2)) | |
| 79 | } | |
| 80 | } | |
| 81 | ||
| 82 | ### constraints 5: maximum work that can be done offshore | |
| 83 | ## only a certain percentage can be done offshore | |
| 84 | for (m in names(month_map)) | |
| 85 | for (categ in names(categ_map)) | |
| 86 | add.constraint(lprec, xt=1/max_offshore[categ], "<=", rhs=workload[region, categ], indices=Nbtot+NbCat+NbCat+NbWT+categ_map[categ]) | |
| 87 | ||
| 88 | ### constraints 6: minimum work for some worktypes | |
| 89 | for (categ in names(min_worktype)) {
| |
| 90 | rhs <- workload[region, categ] * min_worktype[categ] | |
| 91 | for (m in names(month_map)) add.constraint(lprec, xt=rep(1, NbWT), ">=", rhs=rhs, indices=dimsToIndex(month=m, categ=categ)) | |
| 92 | } | |
| 93 | ||
| 94 | ### helper variable: maximum effort over all months for each category | |
| 95 | for (m in names(month_map)) | |
| 96 | for (categ in names(categ_map)) | |
| 97 | add.constraint( | |
| 98 | lprec, xt=c(rep(1, NbWT),-1), "<=", rhs=0, | |
| 99 | indices=c(dimsToIndex(month=m, categ=categ), Nbtot+NbCat+categ_map[categ]) | |
| 100 | ) | |
| 101 | ||
| 102 | ### helper variable: maximum effort over all months for each worker type | |
| 103 | for (m in names(month_map)) | |
| 104 | for (wt in names(worktype_map)) | |
| 105 | add.constraint( | |
| 106 | lprec, xt=c(rep(1, NbCat), -1), "<=", rhs=0, | |
| 107 | indices=c(dimsToIndex(month=m, worker_type=wt), Nbtot+NbCat+NbCat+worktype_map[wt]) | |
| 108 | ) | |
| 109 | ||
| 110 | # browser() | |
| 111 | ### solve | |
| 112 | status <- solve(lprec) | |
| 113 | ||
| 114 | ### no solution? halt | |
| 115 | if(status!=0) stop(paste("no solution found, error code ", status, "; region ", region))
| |
| 116 | ||
| 117 | # unmet, non-transferable. | |
| 118 | unmet <- matrix(get.variables(lprec)[seq(Nbtot+1, length.out=NbCat)], nrow=1, dimnames=list(region, names(categ_map))) | |
| 119 | ||
| 120 | # resulting effort | |
| 121 | effort <- get.variables(lprec)[1:Nbtot] | |
| 122 | ans <- expand.grid( | |
| 123 | Month=names(month_map), Category=names(categ_map), | |
| 124 | Region=region, Worker_Type=names(worktype_map), | |
| 125 | stringsAsFactors=FALSE | |
| 126 | ) | |
| 127 | lookup_one <- function(x) effort[dimsToIndex(month=x[["Month"]], categ=x[["Category"]], worker_type=x[["Worker_Type"]])] | |
| 128 | ans[,"Effort"] <- apply(ans, 1, lookup_one) | |
| 129 | ||
| 130 | # transferable | |
| 131 | transferable <- get.variables(lprec)[seq(Nbtot+NbCat+NbCat+NbWT+1, length.out=NbCat)] | |
| 132 | idx <- which(transferable>.Machine$double.eps^0.5) | |
| 133 | if(length(idx)>0) {
| |
| 134 | transferable_df <- data.frame( | |
| 135 | region=region, | |
| 136 | categ=names(categ_map)[idx], | |
| 137 | transferable=transferable[idx], | |
| 138 | stringsAsFactors=FALSE | |
| 139 | ) | |
| 140 | } else transferable_df <- data.frame() | |
| 141 | ||
| 142 | # if( nrow(transferable_df)>0) browser() | |
| 143 | return(list(effort=ans, unmet=unmet, transferable=transferable_df)) | |
| 144 | } | |
| 145 | ||
| 146 | #' internal function used by ds_user_parallel | |
| 147 | #' distributes transferable workload to regions with free capacities | |
| 148 | ds_user_transfer <- function(effort, capacity, transferable, min_workertype, month_map, NbMonth, categ_map, worktype_map, NbWT, NbCat, no_work) {
| |
| 149 | # calculate the capacity remaining after onshore work | |
| 150 | eff <- aggregate(Effort ~ Month + Region + Worker_Type, data=effort, sum) | |
| 151 | eff <- reshape(eff, timevar="Worker_Type", idvar=c("Month","Region"), direction="wide")
| |
| 152 | eff <- eff[, c("Month", "Region", "Effort.data_collector", "Effort.nonregular", "Effort.regular")]
| |
| 153 | colnames(eff) <- c("mon", "region", "dceff", "nonregeff", "regeff")
| |
| 154 | remaining_capa <- merge(eff, capacity) | |
| 155 | remaining_capa[, "regremain"] <- pmax(0, remaining_capa[, "regutil"] - remaining_capa[, "regeff"]) | |
| 156 | remaining_capa[, "nonregremain"] <- pmax(0, remaining_capa[, "nonregutil"] - remaining_capa[, "nonregeff"]) | |
| 157 | remaining_capa[, "dcremain"] <- pmax(0, remaining_capa[, "dcutil"] - remaining_capa[, "dceff"]) | |
| 158 | remaining_capa <- remaining_capa[,c("mon", "region", "regremain", "nonregremain", "dcremain")]
| |
| 159 | ||
| 160 | # from where to where can work be shifted? | |
| 161 | from_regions <- unique(transferable[,"region"]) | |
| 162 | NbShifts <- nrow(transferable) # region / categ combinations | |
| 163 | idx <- !(remaining_capa[,"region"] %in% from_regions) & | |
| 164 | (remaining_capa[,"regremain"]+remaining_capa[,"nonregremain"]+remaining_capa[,"dcremain"])>0 | |
| 165 | to_regions <- unique(remaining_capa[idx, "region"]) | |
| 166 | NbRegTo <- length(to_regions) | |
| 167 | ||
| 168 | # is there at least one region where we can transfer work to? | |
| 169 | if(length(to_regions)==0) {
| |
| 170 | unmet_df <- reshape(transferable, direction="wide", timevar="categ", idvar="region") | |
| 171 | rownames(unmet_df) <- unmet_df[,"region"] | |
| 172 | unmet_df[,"region"] <- NULL | |
| 173 | unmet_df <- as.matrix(unmet_df) | |
| 174 | unmet_df[is.na(unmet_df)] <- 0 | |
| 175 | colnames(unmet_df) <- substring(colnames(unmet_df), 14) | |
| 176 | return(list(transfers=data.frame(), still_unmet=unmet_df)) | |
| 177 | } | |
| 178 | ||
| 179 | # set up lp | |
| 180 | # first NbShifts*NbRegTo*NbWT*NbMonth entries: effort for the transfers | |
| 181 | # next NbRegTo*NbWT entries: capacity needed after the shifts (this is to minimize) | |
| 182 | # next NbShifts entries: unmet untransferable work (hopefully zero, this is penalized) | |
| 183 | lprec <- make.lp(0, ncol=NbShifts*NbRegTo*NbWT*NbMonth+NbRegTo*NbWT+NbShifts) | |
| 184 | set.objfn(lprec, obj=c(rep(0, NbShifts*NbRegTo*NbWT*NbMonth), rep(c(100, 80, 60), NbRegTo), rep(1000, NbShifts))) | |
| 185 | cat("#Vars: ", NbShifts*NbRegTo*NbWT*NbMonth+NbRegTo*NbWT+NbShifts, "\n")
| |
| 186 | ||
| 187 | ### constraints 1: no work in certain months for some types of work | |
| 188 | for (categ in names(no_work)) | |
| 189 | for (m in intersect(names(month_map), no_work[[categ]])) {
| |
| 190 | idx_shifts <- which(transferable[,"categ"]==categ) | |
| 191 | for (i in idx_shifts) | |
| 192 | for (reg in seq.int(NbRegTo)) | |
| 193 | for (wt in names(worktype_map)) {
| |
| 194 | add.constraint( | |
| 195 | lprec, | |
| 196 | xt=1, type="=", rhs=0, | |
| 197 | indices=(i-1)*NbRegTo*NbWT*NbMonth+(reg-1)*NbMonth + (worktype_map[wt]-1)*NbMonth + month_map[m] | |
| 198 | ) | |
| 199 | } | |
| 200 | } | |
| 201 | ||
| 202 | ### constraints 2: workload must be met | |
| 203 | for (i in seq.int(nrow(transferable))) {
| |
| 204 | add.constraint( | |
| 205 | lprec, xt=rep(1, NbRegTo*NbWT*NbMonth+1), "=", rhs=transferable[i, "transferable"] , | |
| 206 | indices=c(seq((i-1)*NbRegTo*NbWT*NbMonth+1, i*NbRegTo*NbWT*NbMonth), NbShifts*NbRegTo*NbWT*NbMonth+NbRegTo*NbWT+i) | |
| 207 | ) | |
| 208 | } | |
| 209 | ||
| 210 | ### constraints 3: transfered effort <= remaining capacity | |
| 211 | for (m in names(month_map)) | |
| 212 | for (reg in seq.int(NbRegTo)) {
| |
| 213 | idx <- which(remaining_capa[,"mon"]==m & remaining_capa[,"region"]==to_regions[reg]) | |
| 214 | add.constraint( | |
| 215 | lprec, xt=rep(1, NbShifts), type="<=", rhs=capacity[idx, "dcutil"], | |
| 216 | indices=(seq.int(NbShifts)-1)*NbRegTo*NbWT*NbMonth+(reg-1)*NbWT*NbMonth+(worktype_map["data_collector"]-1)*NbMonth+month_map[m] | |
| 217 | ) | |
| 218 | add.constraint( | |
| 219 | lprec, xt=rep(1, NbShifts), type="<=", rhs=capacity[idx, "regutil"], | |
| 220 | indices=(seq.int(NbShifts)-1)*NbRegTo*NbWT*NbMonth+(reg-1)*NbWT*NbMonth+(worktype_map["regular"]-1)*NbMonth+month_map[m] | |
| 221 | ) | |
| 222 | add.constraint( | |
| 223 | lprec, xt=rep(1, NbShifts), type="<=", rhs=capacity[idx, "nonregutil"], | |
| 224 | indices=(seq.int(NbShifts)-1)*NbRegTo*NbWT*NbMonth+(reg-1)*NbWT*NbMonth+(worktype_map["nonregular"]-1)*NbMonth+month_map[m] | |
| 225 | ) | |
| 226 | } | |
| 227 | ||
| 228 | ### constraints 4: minimum worker type utilization | |
| 229 | ## for each month, region and category, we have two constraints: | |
| 230 | ## 1) effort_regular_from_offshore + effort_regular_onsite >= perc * (effort_total_from_offshore + effort_total_onsite) | |
| 231 | ## 2) effort_dc_from_offshore + effort_dc_onsite <= perc * (effort_total_offshore + effort_total_onsite) | |
| 232 | ## where perc are input constants depending on the category given in the min_workertype data.frame | |
| 233 | ## By simple algebra, this normalizes to | |
| 234 | ## 1') (1-perc) effort_regular_offshore >= perc effort_total_onsite - effort_regular_onsite | |
| 235 | ## 2') (1-perc) effort_dc_offshore <= perc effort_total_onsite - effort_dc_onsite | |
| 236 | ## the second constraint only applies if there is actually data_collector capacity in that region | |
| 237 | for (reg in seq.int(NbRegTo)) | |
| 238 | for (m in names(month_map)) | |
| 239 | for (categ in names(categ_map)) {
| |
| 240 | # lookup onsite values | |
| 241 | idx <- which(effort[,"Month"]==m & effort[, "Category"]==categ & effort[, "Region"]==to_regions[reg]) | |
| 242 | onsite <- setNames(effort[idx, "Effort"], effort[idx, "Worker_Type"]) | |
| 243 | perc <- min_workertype[categ, "reg_min"] | |
| 244 | add.constraint( | |
| 245 | lprec, xt=rep((1-perc), NbShifts), type=">=", | |
| 246 | rhs=perc*sum(onsite)-onsite["regular"], | |
| 247 | indices=(seq.int(NbShifts)-1)*NbRegTo*NbWT*NbMonth+(reg-1)*NbWT*NbMonth+(worktype_map["regular"]-1)*NbMonth+month_map[m] | |
| 248 | ) | |
| 249 | perc <- min_workertype[categ, "dc_min"] | |
| 250 | add.constraint( | |
| 251 | lprec, xt=rep((1-perc), NbShifts), type="<=", | |
| 252 | rhs=perc*sum(onsite)-onsite["data_collector"], | |
| 253 | indices=(seq.int(NbShifts)-1)*NbRegTo*NbWT*NbMonth+(reg-1)*NbWT*NbMonth+(worktype_map["data_collector"]-1)*NbMonth+month_map[m] | |
| 254 | ) | |
| 255 | } | |
| 256 | ||
| 257 | ### helper variable: maximum effort over all months for each worker type | |
| 258 | for (reg in seq.int(NbRegTo)) | |
| 259 | for (m in names(month_map)) | |
| 260 | for (wt in names(worktype_map)) {
| |
| 261 | # lookup onsite values | |
| 262 | idx <- which(effort[,"Month"]==m & effort[, "Region"]==to_regions[reg] & effort[, "Worker_Type"]==wt) | |
| 263 | onsite <- sum(effort[idx, "Effort"]) # sum over categories | |
| 264 | add.constraint( | |
| 265 | lprec, xt=c(rep(1, NbShifts), -1), "<=", rhs=-onsite, | |
| 266 | indices=c((seq.int(NbShifts)-1)*NbRegTo*NbWT*NbMonth+(reg-1)*NbWT*NbMonth+(worktype_map[wt]-1)*NbMonth+month_map[m], | |
| 267 | NbShifts*NbRegTo*NbWT*NbMonth+(reg-1)*NbWT+worktype_map[wt]) | |
| 268 | ) | |
| 269 | } | |
| 270 | ||
| 271 | ### solve | |
| 272 | status <- solve(lprec) | |
| 273 | ||
| 274 | ### no solution? halt | |
| 275 | if(status!=0) stop(paste("no solution found, error code ", status))
| |
| 276 | ||
| 277 | ### gather results | |
| 278 | one_transfer <- function(i) {
| |
| 279 | vars <- get.variables(lprec)[seq((i-1)*NbRegTo*NbWT*NbMonth+1, i*NbRegTo*NbWT*NbMonth)] | |
| 280 | idx1 <- which(vars > .Machine$double.eps^0.5) | |
| 281 | if(length(idx1)>0) {
| |
| 282 | reg <- idx1 %/% (NbWT*NbMonth) + 1 | |
| 283 | idx <- idx1 - (reg - 1) * (NbWT*NbMonth) | |
| 284 | wt <- idx %/% NbMonth + 1 | |
| 285 | m <- idx - (wt - 1) * NbMonth + 1 | |
| 286 | # browser() | |
| 287 | ||
| 288 | ret <- data.frame( | |
| 289 | region_from=transferable[i, "region"], | |
| 290 | categ=transferable[i,"categ"], | |
| 291 | region_to=to_regions[reg], | |
| 292 | worker_type=names(worktype_map)[wt], | |
| 293 | mon=names(month_map)[m], | |
| 294 | effort=vars[idx1], | |
| 295 | stringsAsFactors=FALSE | |
| 296 | ) | |
| 297 | } else ret <- data.frame() | |
| 298 | return(ret) | |
| 299 | } | |
| 300 | ||
| 301 | transf <- lapply(seq.int(nrow(transferable)), FUN=one_transfer) | |
| 302 | transf <- do.call(rbind, transf) | |
| 303 | ||
| 304 | ### still unmet workload? | |
| 305 | unmet <- get.variables(lprec)[seq(NbShifts*NbRegTo*NbWT*NbMonth+NbRegTo*NbWT, length.out=NbShifts)] | |
| 306 | if(any(unmet > .Machine$double.eps^0.5)) {
| |
| 307 | unmet_df <- transferable | |
| 308 | unmet_df[, "transferable"] <- NULL | |
| 309 | unmet_df[, "unmet"] <- unmet | |
| 310 | unmet_df <- reshape(transferable, direction="wide", timevar="categ", idvar="region") | |
| 311 | rownames(unmet_df) <- unmet_df[,"region"] | |
| 312 | unmet_df[,"region"] <- NULL | |
| 313 | unmet_df <- as.matrix(unmet_df) | |
| 314 | unmet_df[is.na(unmet_df)] <- 0 | |
| 315 | colnames(unmet_df) <- substring(colnames(unmet_df), 14) | |
| 316 | } else unmet_df <- data.frame() | |
| 317 | ||
| 318 | return(list(transfers=transf, still_unmet=unmet_df)) | |
| 319 | } | |
| 320 | ||
| 321 | #' Solves a resource allocation problem | |
| 322 | #' | |
| 323 | #' @param workload workload data.frame (rows=regions, columns=worktype) | |
| 324 | #' @param capacity work capacity per month data.frame (columns: region, month, regutil, nonregutil, dcutil) | |
| 325 | #' @param min_workertype minimum requirements data.frame (columns=region, month, worktype, regmin, dcmin) | |
| 326 | #' @param min_worktype optional minimum requirement for work types. | |
| 327 | #' @param start_month start month: from here to Dec the workload needs to be processed (three letter month abbreviation). Default "Jan" | |
| 328 | #' @param no_work named list of worktype with the months (three letter month abbreviation) where no work on that worktype is possible | |
| 329 | #' @param max_offshore named vector with the percentages of work that can be done offshore | |
| 330 | #' | |
| 331 | #' @return a list with three elements named "effort" with the distributed workload | |
| 332 | #' (data.frame, columns: region, month, worktype, worker type, effort), | |
| 333 | #' "unmet", a (hopefully all zero) matrix (columns: category, rows: regions) where capacity problems arise, and | |
| 334 | #' (only if transfers==TRUE) "transfers" a data.frame with distributed offshore work | |
| 335 | #' (data.frame, columns: region_from, categ, region_to, worker_type, mon, effort) | |
| 336 | ds_user_parallel <- function( | |
| 337 | workload, | |
| 338 | capacity, | |
| 339 | min_workertype, | |
| 340 | min_worktype=c(Subdivisions=1/12, HomeValue=0.05), | |
| 341 | start_month="Jan", | |
| 342 | no_work=list( | |
| 343 | "Reval_Days"=c("Jan", "Feb", "Mar", "Apr", "May", "Jun"),
| |
| 344 | "Asset"=c("Sep", "Oct", "Nov", "Dec", "Jan"),
| |
| 345 | "Reval_Objections"=c("Jul", "Aug", "Sep", "Oct", "Nov", "Dec")
| |
| 346 | ), | |
| 347 | max_offshore=c( | |
| 348 | Building_Consents=0.5, Subdivisions=0.9, S12_Sales=0.5, RM_Objections=0.1, Reval_Days=0.3, Reval_Objections=0.1, | |
| 349 | Asset=0.1, HomeValue=0.2, Rural=0.2, Urgent_New_Imp=0.2 | |
| 350 | ), | |
| 351 | transfers=TRUE | |
| 352 | ){
| |
| 353 | ### helper functions | |
| 354 | month_map <- setNames( | |
| 355 | rev(seq.int(12)), | |
| 356 | c("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")
| |
| 357 | ) | |
| 358 | if(!start_month %in% names(month_map)) stop("invalid start_month, must be a three letter month abbreviation.")
| |
| 359 | NbMonth <- month_map[start_month] | |
| 360 | month_map <- tail(month_map, NbMonth) | |
| 361 | categ_map <- unique(rownames(min_workertype)) | |
| 362 | NbCat <- length(categ_map) | |
| 363 | categ_map <- setNames(seq.int(NbCat), categ_map) | |
| 364 | ||
| 365 | ### sanity check 1: consistent use of work category names | |
| 366 | if(length(intersect(colnames(workload), names(categ_map)))<max(c(length(colnames(workload)), length(categ_map)))) | |
| 367 | stop("work categories in workload and min_req argument do not match.")
| |
| 368 | if(length(setdiff(names(no_work), names(categ_map))) > 0) | |
| 369 | stop("work categories in no_work and min_req argument do not match.")
| |
| 370 | ||
| 371 | worktype_map <- c(regular=1, nonregular=2, data_collector=3) | |
| 372 | NbWT <- length(worktype_map) | |
| 373 | Nbtot <- (NbMonth * NbCat * NbWT) | |
| 374 | ||
| 375 | ### sanity check 2: need more capacity than workload | |
| 376 | capacity <- capacity[capacity[,"mon"] %in% names(month_map),] | |
| 377 | #if(sum(workload)>sum(capacity[, c("regutil", "nonregutil", "dcutil")])) stop("workload exceeds capacity")
| |
| 378 | ||
| 379 | ### map subscripts of the decision vars to index and vice versa | |
| 380 | dimsToIndex <- function(month=names(month_map), categ=names(categ_map), worker_type=names(worktype_map)) {
| |
| 381 | args <- expand.grid( | |
| 382 | month=month_map[month]-1, | |
| 383 | categ=categ_map[categ]-1, | |
| 384 | worker_type=worktype_map[worker_type], | |
| 385 | stringsAsFactors=FALSE | |
| 386 | ) | |
| 387 | w <- c(NbCat*NbWT, NbWT, 1) | |
| 388 | ret <- apply(args, 1, function(x) {ans <- x %*% w; dim(ans) <- NULL; return(ans)})
| |
| 389 | return(ret) | |
| 390 | } | |
| 391 | dimsToIndex <- cmpfun(dimsToIndex) | |
| 392 | ||
| 393 | subset_wl <- function(reg) matrix(workload[reg,], nrow=1, dimnames=list(reg, colnames(workload))) | |
| 394 | splitted_cap <- split(capacity, capacity[,"region"]) | |
| 395 | my_combine <- function(x, y) | |
| 396 | list( | |
| 397 | effort=rbind(x[["effort"]], y[["effort"]]), | |
| 398 | unmet=rbind(x[["unmet"]], y[["unmet"]]), | |
| 399 | transferable=rbind(x[["transferable"]], y[["transferable"]]) | |
| 400 | ) | |
| 401 | arg <- list( | |
| 402 | month_map=month_map, NbMonth=NbMonth, categ_map=categ_map, worktype_map=worktype_map, min_workertype=min_workertype, min_worktype=min_worktype, | |
| 403 | NbWT=NbWT, Nbtot=Nbtot, NbCat=NbCat, dimsToIndex=dimsToIndex, no_work=no_work, max_offshore=max_offshore | |
| 404 | ) | |
| 405 | ret <- foreach(reg=names(splitted_cap), .combine=my_combine) %do% {
| |
| 406 | arg[["workload"]] <- subset_wl(reg) | |
| 407 | arg[["capacity"]] <- splitted_cap[[reg]] | |
| 408 | arg[["region"]] <- reg | |
| 409 | do.call(ds_user, arg) | |
| 410 | } | |
| 411 | ||
| 412 | if(nrow(ret[["transferable"]])>0) | |
| 413 | if(transfers) {
| |
| 414 | transf_ret <- ds_user_transfer( | |
| 415 | effort=ret[["effort"]], capacity=capacity, transferable=ret[["transferable"]], min_workertype=min_workertype, | |
| 416 | month_map=month_map, NbMonth=NbMonth, categ_map=categ_map, worktype_map, NbWT=NbWT, NbCat, no_work=no_work | |
| 417 | ) | |
| 418 | still_unmet <- transf_ret[["still_unmet"]] | |
| 419 | if(nrow(still_unmet)>0) | |
| 420 | ret[["unmet"]][rownames(still_unmet), colnames(still_unmet)] <- | |
| 421 | ret[["unmet"]][rownames(still_unmet), colnames(still_unmet)] + still_unmet | |
| 422 | ret[["transferable"]] <- NULL | |
| 423 | ret[["transfers"]] <- transf_ret[["transfers"]] | |
| 424 | } else {
| |
| 425 | # no transfers wished -- add to unmet | |
| 426 | unmet_df <- reshape(ret[["transferable"]], direction="wide", timevar="categ", idvar="region") | |
| 427 | rownames(unmet_df) <- unmet_df[,"region"] | |
| 428 | unmet_df[,"region"] <- NULL | |
| 429 | unmet_df <- as.matrix(unmet_df) | |
| 430 | unmet_df[is.na(unmet_df)] <- 0 | |
| 431 | colnames(unmet_df) <- substring(colnames(unmet_df), 14) | |
| 432 | ret[["unmet"]][rownames(unmet_df), colnames(unmet_df)] <- ret[["unmet"]][rownames(unmet_df), colnames(unmet_df)] + unmet_df | |
| 433 | ret[["transferable"]] <- NULL | |
| 434 | } | |
| 435 | ||
| 436 | if(any(ret[["unmet"]]>.Machine$double.eps^0.5)) warning("there is unmet ", if(transfers) "untransferable", "workload")
| |
| 437 | ||
| 438 | # browser() | |
| 439 | return(ret) | |
| 440 | } |