Originally posted by: andrea.taverna
Hello everyone,
I have started using OPL script for solving a scheduling problem. I have experience with MP with CPLEX + GAMS/AMPL/SCIP...but I have just had some exposure to CP with OR/tools and gecode for a summer school during the PhD a couple of years ago.
I am a bit confused on how to model the following problem
A set of jobs J is given. Each job j as one or more phases k_j. To be completed, the phases of each job have to be completed in order. There are earliest starting time for the whole job. Each phase has a duration, measured in equivalent man-hours and a minimum and maximum number of allowed operators working on it.
A set of internal workers is given. For each time period t in T, which corresponds to a 4 hour time slot, a worker has an "effort", expressed as a number of equivalent work-hours. The effort can be 0, to mean the worker is absent, 4, to mean the worker completes 4 man-hours of equivalent work, and 5, to represent 1 additional hour of over time.
The company can also employ external workers, which have an effort of 4 hours. They are not identified. WHat we want to know is how many external workers are used in each period for each phase.
Every worker, internal or external, can change his task at every time period. So worker o can be assigned to phase k_j at time t and to phase k'_j' at time t+1
The main goal is reducing the employment of external workers.
After some iterations, I wrote this model:
-
each phase is an interval. There are startBeforeStart, endBeforeStart and similar constraints to impose an ordering on the phases
-
to represent the choice of assigning an internal worker o to a phase k_j at a given period t I declared an optional interval int_worker[<o,j,k,t>] in t..t+1
-
I did the same for external workers.
-
for each phase I defined a cumulFunction (stepAtStaet) that counts the amount of work hours performed by each worker (stepAtStart(int_worker[<o,j,k,t>], e[<o,t>]) where e is the "efficiency"
-
same for the amount of operators for the phase in each time slot: a cumulFunction (pulse) for each worker, phase and period that increases by 1 when the worker starts the slot (at time t) and decreases when it ends it (at time t+1)
-
Then I put a span constraint that states that a phase needs to span the set of its assigned workers, and then I'll need an alternative constraints (for the internal workers only), that states for each internal worker in each period only one interval has to be present.
Here is a simplified sketch:
{int} ExtOperators = asSet(1..max(<j,k> in Jobs2Phase) PhaseData[<j,k>].max_operators);
/* for each time period have 1.. PhaseData[<j,k>].max_operators ids to assign to phase k of job j*/
{extopSlot} ExtOperatorsSlots = {<o,j,k,t>| ...}
tuple opSlot {
string operator;
string job;
string type;
string phase;
int t;
};
/* time periods in which worker o can be assigned to phase k of job j*/
{opSlot} OperatorsSlots = {<o,j,k,t>};
/*
* Variables
*/
dvar interval phase[<j,k> in Jobs2Phase];
dvar interval int_worker[<o,j,k,t> in OperatorsSlots] optional in t..t+1;
dvar interval ext_worker[<o,j,k,t> in ExtOperatorsSlots] optional in t..t+1;
/* OperatorEffort[<o,t>] is either 4 hours (base) or 5 hours (+1 overtime) */
cumulFunction internalEffort[<j,k> in Jobs2Phase] =
sum (o in Operators, t in Time: <o,j,k,t> in OperatorsSlots) stepAtStart(int_worker[<o,j,k,t>], OperatorEffort[<o,t>]);
cumulFunction internalOperators[<j,k> in Jobs2Phase][t in Time] =
sum (o in Operators: <o,j,k,t> in OperatorsSlots) pulse(int_worker[<o,j,k,t>],1);
cumulFunction externalEffort[<j,k> in Jobs2Phase] =
/* BaseEffort = 4 hours*/
sum(o in ExtOperators, t in Time : <o,j,k,t> in ExtOperatorsSlots) stepAtStart(ext_worker[<o,j,k,t>], BaseEffort);
cumulFunction externalOperators[<j,k> in Jobs2Phase][t in Time] =
sum(o in ExtOperators, t in Time : <o,j,k,t> in ExtOperatorsSlots) pulse(ext_worker[<o,j,k,t>],1);
/*
* Objectives
*
*
*/
...
/*
* Constraints
*/
subject to {
forall(<j,k> in Jobs2Phase) {
CompleteAll: internalEffort[<j,k>] + externalEffort[<j,k>] >= PhaseData[<j,k>].processing_time;
};
forall(<j,k> in Jobs2Phase, t in Time) {
MinOperators: internalOperators[<j,k>][t] + externalOperators[<j,k>][t] >= PhaseData[<j,k>].min_operators;
};
forall(<j,k> in Jobs2Phase, t in Time) {
MaxOperators: internalOperators[<j,k>][t] + externalOperators[<j,k>][t] <= PhaseData[<j,k>].max_operators;
};
forall(<o,j,k,t> in ExtOperatorsSlots: o < last(ExtOperators)) {
Sort: presenceOf(ext_worker[<o,j,k,t>]) >= presenceOf(ext_worker[<o+1,j,k,t>]);
};
forall(<j,k> in Jobs2Phase) {
Synch: span(phase[<j,k>],
append( (all(o in Operators, t in Time : <o,j,k,t> in OperatorsSlots) int_worker[<o,j,k,t>]),
(all(o in ExtOperators, t in Time : <o,j,k,t> in ExtOperatorsSlots) ext_worker[<o,j,k,t>])));
};
// Order phases for each job
forall(<j,k> in Jobs2Phase : ord(Phases,k) >= 1) {
PhaseStart: startBeforeStart(phase[<j,prev(Phases,k)>],phase[<j,k>],PhaseData[<j,k>].start_delay);
};
forall(<j,k> in Jobs2Phase : ord(Phases,k) >= 1) {
PhaseEnd: endBeforeEnd(phase[<j,prev(Phases,k)>],phase[<j,k>],PhaseData[<j,k>].end_delay);
};
forall(<j,k> in Jobs2Phase : ord(Phases,k) >= 1) {
PhaseConsist: endBeforeStart(phase[<j,prev(Phases,k)>], phase[<j,k>], PhaseData[<j,k>].inter_delay);
};
forall(j in Jobs) {
StartRelease: forbidStart(phase[<j,first(Phases)>], afterRelease[j]);
};
};
It currently does not work, as it returns infeasibility. I wrote a MP model before, with the same data, which did work, hence I think the data is correct, but the model isn't.
Before venturing further, however, I would like to have some suggestions. Is my approach correct? Am I missing something obvious?
TIA
#DecisionOptimization#OPLusingCPOptimizer