Originally posted by: GGR
Hi Jazzy
I understand far better your problem which appears quite interesting.
Another question about the shifts: From your drawing the shifts can start at any time. For an actual worker its shift can start at any time in the day. If I am right, the model with interval variable is certainly required. In fact the most there is contiguity and recuperation rules for the workers or flexibilities in start dates and duration of the shifts, the strongest the scheduling aspect is dominating.
The operational interval variable represent the production of a worker in its shift of a day. That is an optional interval variable per worker and per day temporally constrained to be in a possible shifts of the day. use fordidStart and forbidEnd constraints to sate the set of start points
Then the interval variables are hierarchically grouped in alternative and span in order to cover the workload and state some continuity constraints from the manner the workforce is managed.
Note you do not need Boolean integer variables as you can use the presenceOf(it) constraint on interval variable as Boolean expression. That is be itv1 and itv2 two interval variable, the constraint
presenceOf(itv1) + presenceOf(itv2) <= 1; // if itv1 is present, presenceOf(itv1) is 1 then presenceOf(itv2) is 0 then itv2 is absent.
Then you cumul all the shifts in order to cover the demand. Note that the demand curve should be a stepwise integer function: avoid having useless step for sake of efficiency. Note the following code is only indicative as I am not completely sure understanding all the features of your problem.
tuple UserDemand {
int start;
int end;
int height;
};
UserDemands = ...;
int maxH = max (ud in UserDemands) ud.height;
cumulfunction cover = sum (d in Days, s in Shifts) tasks[d][s];
minimize sum(d in Days, s in Shifts) presenceOf(tasks[d][s]);
subject to {
forall(d in Days, s in Shifts)
alternative(tasks[d][s], all (w in Workers) pulse(opers[d][s][w], 1);
forall (ud in UserDenands)
alwaysIn(cover, ud.start, ud.end, ud.height, maxH);
}
I am not completely sure of the efficiency of the optimization procedure. Try it. In any case think adding redundant constraints. E.g from the curve you know the ending points of all tasks.
In fact, you know that the work will be organized by contiguous blocks of task whose few last (or few ones of) being of free duration, all the others are regular length shifts.
By pre-processing the demand curve you can have a lower and upper bound of the optimal number of blocks: in the example you give it is 6 or 7 blocks.
You can use it by modeling each block a set of unary resource.
int delta = ...; // to have some slack in the size of the block.
dvar interval blocks[b in Blocks] in b.star, b.end + delta] size delta + b.end - b.start optional b.surelyPresent
//Pre affecting the tasks to a block
cumulFunction cumulBlock[b in Blocks] = sum (t in Tasks; t.block == b ) pulse(task[t.day][t.shift], 1);
cumulfunction cover = sum (b in Blocks) pulse(blocks[b], 1);
//minimize the slack in a second order of magnitude (eps << 1)
minimize sum(d in Days, s in Shifts) presenceOf(tasks[d][s]) + eps*delta;
forall(b in Blocks) {
alwaysIn(cumulBlocks[b], blocks[b], 1, 1); // no idle time in a block
span(blocks[b], all (t in Tasks; t.block == b ) task[t.day][t.shift]);
}
You may also pre-ordering some task using temporal constraints.
Finally you can also having a decomposed model that may simplify your problem: e.g first ignoring the worker assignment. That is a model without the alternative of worker. Then a second model is essentially an assignment problem.
Another idea could be starting to search for a solution with only regular shift and use it as starting point of a second pone that exchange regular workers by non regular ones to adjust the blocks.
Hope that helps
#DecisionOptimization#OPLusingCPOptimizer