View difference between Paste ID: 29Ur4ZyF and 9j0pUVHr
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
}