element: Sequencing jobs on a single machine
The problem described in this section is taken from Section 7.4 `Sequencing jobs on a bottleneck machine' of the book `Applications of optimization with Xpress-MP'
The aim of this problem is to provide a model that may be used with different objective functions for scheduling operations on a single (bottleneck) machine. We shall see here how to minimize the total processing time, the average processing time, and the total tardiness.
A set of tasks (or jobs) is to be processed on a single machine. The execution of tasks is non-preemptive (that is, an operation may not be interrupted before its completion). For every task i its release date, duration, and due date are given in Table Task time windows and durations.
Job | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
Release date | 2 | 5 | 4 | 0 | 0 | 8 | 9 |
Duration | 5 | 6 | 8 | 4 | 2 | 4 | 2 |
Due date | 10 | 21 | 15 | 10 | 5 | 15 | 22 |
What is the optimal value for each of the objectives: minimizing the total duration of the schedule (makespan), the mean processing time or the total tardiness (that is, the amount of time by which the completion of jobs exceeds their respective due dates)?
Model formulation 1
We are going to present two alternative model formulations. The first is closer to the Mathematical Programming formulation in `Applications of optimization with Xpress-MP'. The second uses disjunctive constraints and branching on these. In both model formulations we are going deal with the different objective functions in sequence, but the body of the models will remain the same.
To represent the sequence of jobs we introduce variables rankk (k ∈ JOBS = {1,...,NJ}) that take as value the number of the job in position (rank) k. Every job j takes a single position. This constraint can be represented by an all-different on the rankk variables:
The processing time durk for the job in position k is given by DURrankk (where DUR_j denotes the duration given in the table in the previous section). Similarly, the release time relk is given by RELrankk (where REL_j denotes the given release date):
∀ k ∈ JOBS: relk = RELrankk
If startk is the start time of the job at position k, this value must be at least as great as the release date of the job assigned to this position. The completion time compk of this job is the sum of its start time plus its duration:
∀ k ∈ JOBS: compk = startk + durk
Another constraint is needed to specify that two jobs cannot be processed simultaneously. The job in position k+1 must start after the job in position k has finished, hence the following constraints:
Objective 1: The first objective is to minimize the makespan (completion time of the schedule), or, equivalently, to minimize the completion time of the last job (job with rank NJ). The complete model is then given by the following (where MAXTIME is a sufficiently large value, such as the sum of all release dates and all durations):
∀ k ∈ JOBS: rankk ∈ JOBS
∀ k ∈ JOBS: startk,compk ∈ {0,...,MAXTIME}
∀ k ∈ JOBS: durk ∈ {minj ∈ JOBS DURj,...,maxj ∈ JOBS DURj}
∀ k ∈ JOBS: relk ∈ {minj ∈ JOBS RELj,...,maxj ∈ JOBS RELj}
all-different(rank1,...,rankNJ)
∀ k ∈ JOBS: durk = DURrankk
∀ k ∈ JOBS: relk = RELrankk
∀ k ∈ JOBS: startk ≥ relk
∀ k ∈ JOBS: compk = startk + durk
∀ k ∈ {1,...,NJ-1}: startk+1 ≥ startk + durk
Objective 2: For minimizing the average processing time, we introduce an additional variable totComp representing the sum of the completion times of all jobs. We add the following constraint to the problem to calculate totComp:
∑ |
k ∈ JOBS |
The new objective consists of minimizing the average processing time, or equivalently, minimizing the sum of the job completion times:
Objective 3: If we now aim to minimize the total tardiness, we again introduce new variables—this time to measure the amount of time that jobs finish after their due date. We write latek for the variable that corresponds to the tardiness of the job with rank k. Its value is the difference between the completion time of a job j and its due date DUEj. If the job finishes before its due date, the value must be zero. We thus obtain the following constraints:
∀ k ∈ JOBS: latek ≥ compk - duek
For the formulation of the new objective function we introduce the variable totLate representing the total tardiness of all jobs. The objective now is to minimize the value of this variable:
totLate =
∑ |
k ∈ JOBS |
Implementation of model 1
The Mosel implementation below solves the same problem three times, each time with a different objective, and prints the resulting solutions by calling the procedures print_sol and print_sol3.
model "B-4 Sequencing (CP)" uses "kalis" forward procedure print_sol forward procedure print_sol3 declarations NJ = 7 ! Number of jobs JOBS=1..NJ REL: array(JOBS) of integer ! Release dates of jobs DUR: array(JOBS) of integer ! Durations of jobs DUE: array(JOBS) of integer ! Due dates of jobs rank: array(JOBS) of cpvar ! Number of job at position k start: array(JOBS) of cpvar ! Start time of job at position k dur: array(JOBS) of cpvar ! Duration of job at position k comp: array(JOBS) of cpvar ! Completion time of job at position k rel: array(JOBS) of cpvar ! Release date of job at position k end-declarations initializations from 'Data/b4seq.dat' DUR REL DUE end-initializations MAXTIME:= max(j in JOBS) REL(j) + sum(j in JOBS) DUR(j) MINDUR:= min(j in JOBS) DUR(j); MAXDUR:= max(j in JOBS) DUR(j) MINREL:= min(j in JOBS) REL(j); MAXREL:= max(j in JOBS) REL(j) forall(j in JOBS) do 1 <= rank(j); rank(j) <= NJ 0 <= start(j); start(j) <= MAXTIME MINDUR <= dur(j); dur(j) <= MAXDUR 0 <= comp(j); comp(j) <= MAXTIME MINREL <= rel(j); rel(j) <= MAXREL end-do ! One posistion per job all_different(rank) ! Duration of job at position k forall(k in JOBS) dur(k) = element(DUR, rank(k)) ! Release date of job at position k forall(k in JOBS) rel(k) = element(REL, rank(k)) ! Sequence of jobs forall(k in 1..NJ-1) start(k+1) >= start(k) + dur(k) ! Start times forall(k in JOBS) start(k) >= rel(k) ! Completion times forall(k in JOBS) comp(k) = start(k) + dur(k) ! Set the branching strategy cp_set_branching(split_domain(KALIS_SMALLEST_DOMAIN, KALIS_MIN_TO_MAX)) !**** Objective function 1: minimize latest completion time **** if cp_minimize(comp(NJ)) then print_sol end-if !**** Objective function 2: minimize average completion time **** declarations totComp: cpvar end-declarations totComp = sum(k in JOBS) comp(k) if cp_minimize(totComp) then print_sol end-if !**** Objective function 3: minimize total tardiness **** declarations late: array(JOBS) of cpvar ! Lateness of job at position k due: array(JOBS) of cpvar ! Due date of job at position k totLate: cpvar end-declarations MINDUE:= min(k in JOBS) DUE(k); MAXDUE:= max(k in JOBS) DUE(k) forall(k in JOBS) do MINDUE <= due(k); due(k) <= MAXDUE 0 <= late(k); late(k) <= MAXTIME end-do ! Due date of job at position k forall(k in JOBS) due(k) = element(DUE, rank(k)) ! Late jobs: completion time exceeds the due date forall(k in JOBS) late(k) >= comp(k) - due(k) totLate = sum(k in JOBS) late(k) if cp_minimize(totLate) then writeln("Tardiness: ", getsol(totLate)) print_sol print_sol3 end-if !----------------------------------------------------------------- ! Solution printing procedure print_sol writeln("Completion time: ", getsol(comp(NJ)) , " average: ", getsol(sum(k in JOBS) comp(k))) write("\t") forall(k in JOBS) write(strfmt(getsol(rank(k)),4)) write("\nRel\t") forall(k in JOBS) write(strfmt(getsol(rel(k)),4)) write("\nDur\t") forall(k in JOBS) write(strfmt(getsol(dur(k)),4)) write("\nStart\t") forall(k in JOBS) write(strfmt(getsol(start(k)),4)) write("\nEnd\t") forall(k in JOBS) write(strfmt(getsol(comp(k)),4)) writeln end-procedure procedure print_sol3 write("Due\t") forall(k in JOBS) write(strfmt(getsol(due(k)),4)) write("\nLate\t") forall(k in JOBS) write(strfmt(getsol(late(k)),4)) writeln end-procedure end-model
NB: The reader may have been wondering why we did not use the more obvious pair start-end for naming the variables in this example: end is a keyword of the Mosel language (see the list of reserved words in the Mosel language reference manual), which means that neither end nor END may be redefined by a Mosel program. It is possible though, to use versions combining lower and upper case letters, like End, but to prevent any possible confusion we do not recommend their use.
Results
The minimum makespan of the schedule is 31, the minimum sum of completion times is 103 (which gives an average of 103/7=14.71). A schedule with this objective value is 5→ 4→ 1→ 7→ 6→ 2→ 3. If we compare the completion times with the due dates we see that jobs 1, 2, 3, and 6 finish late (with a total tardiness of 21). The minimum tardiness is 18. A schedule with this tardiness is 5→ 1→ 4→ 6→ 2→ 7→ 3 where jobs 4 and 7 finish one time unit late and job 3 is late by 16 time units, and it terminates at time 31 instead of being ready at its due date, time 15. This schedule has an average completion time of 15.71.
Alternative formulation using disjunctions
Our second model formulation is possibly a more straightforward way of representing the problem. It introduces disjunctive constraints and branching on these.
Every job is now represented by its starting time, variables startj (j ∈ JOBS = {1,...,NJ}) that take their values in {RELj,...,MAXTIME} (where MAXTIME is a sufficiently large value, such as the sum of all release dates and all durations, and RELj the release date of job j). We state the disjunctions as a single disjunctive relation on the start times and durations of all jobs.
This constraint replaces the pair-wise disjunctions
for all pairs of jobs i < j ∈ JOBS.
The processing time durk for the job in position k is given by DURrankk (where DUR_j denotes the duration given in the table in the previous section). Similarly, its release time relk is given by RELrankk (where REL_j denotes the given release date).
∀ k ∈ JOBS: relk = RELrankk
The completion time compj of a job j is the sum of its start time plus its duration DURj.
Objective 1: The first objective is to minimize the makespan (completion time of the schedule) or, equivalently, to minimize the completion time finish of the last job. The complete model is then given by the following (where MAXTIME is a sufficiently large value, such as the sum of all release dates and all durations):
finish = maximumj ∈ JOBS(compj)
∀ j ∈ JOBS: compj ∈ {0,...,MAXTIME}
∀ j ∈ JOBS: startj ∈ {RELj,...,MAXTIME}
disjunctive([start1,...,startNJ], [DUR1,...,DURNJ])
∀ j ∈ JOBS: compj = startj + DURj
Objective 2: The formulation of the second objective (minimizing the average processing time or, equivalently, minimizing the sum of the job completion times) remains unchanged from the first model—we introduce an additional variable totComp representing the sum of the completion times of all jobs.
totComp =
∑ |
k ∈ JOBS |
Objective 3: To formulate the objective of minimizing the total tardiness, we introduce new variables latej to measure the amount of time that a job finishes after its due date. The value of these variables corresponds to the difference between the completion time of a job j and its due date DUEj. If the job finishes before its due date, the value must be zero. The objective now is to minimize the sum of these tardiness variables:
totLate =
∑ |
j ∈ JOBS |
∀ j ∈ JOBS: latej ∈ {0,...,MAXTIME}
∀ j ∈ JOBS: latej ≥ compj - DUEj
Implementation of model 2
As with model 1, the Mosel implementation below solves the same problem three times, each time with a different objective, and prints the resulting solutions by calling the procedures print_sol and print_sol3.
model "B-4 Sequencing (CP)" uses "kalis" forward procedure print_sol forward procedure print_sol3 declarations NJ = 7 ! Number of jobs JOBS=1..NJ REL: array(JOBS) of integer ! Release dates of jobs DUR: array(JOBS) of integer ! Durations of jobs DUE: array(JOBS) of integer ! Due dates of jobs DURS: array(set of cpvar) of integer ! Dur.s indexed by start variables start: array(JOBS) of cpvar ! Start time of jobs comp: array(JOBS) of cpvar ! Completion time of jobs finish: cpvar ! Completion time of the entire schedule Disj: set of cpctr ! Disjunction constraints Strategy: array(range) of cpbranching ! Branching strategy end-declarations initializations from 'Data/b4seq.dat' DUR REL DUE end-initializations MAXTIME:= max(j in JOBS) REL(j) + sum(j in JOBS) DUR(j) forall(j in JOBS) do 0 <= start(j); start(j) <= MAXTIME 0 <= comp(j); comp(j) <= MAXTIME end-do ! Disjunctions between jobs forall(j in JOBS) DURS(start(j)):= DUR(j) disjunctive(union(j in JOBS) {start(j)}, DURS, Disj, 1) ! Start times forall(j in JOBS) start(j) >= REL(j) ! Completion times forall(j in JOBS) comp(j) = start(j) + DUR(j) !**** Objective function 1: minimize latest completion time **** finish = maximum(comp) Strategy(1):= settle_disjunction(Disj) Strategy(2):= split_domain(KALIS_LARGEST_MAX, KALIS_MIN_TO_MAX) cp_set_branching(Strategy) if cp_minimize(finish) then print_sol end-if !**** Objective function 2: minimize average completion time **** declarations totComp: cpvar end-declarations totComp = sum(k in JOBS) comp(k) if cp_minimize(totComp) then print_sol end-if !**** Objective function 3: minimize total tardiness **** declarations late: array(JOBS) of cpvar ! Lateness of jobs totLate: cpvar end-declarations forall(k in JOBS) do 0 <= late(k); late(k) <= MAXTIME end-do ! Late jobs: completion time exceeds the due date forall(j in JOBS) late(j) >= comp(j) - DUE(j) totLate = sum(k in JOBS) late(k) if cp_minimize(totLate) then writeln("Tardiness: ", getsol(totLate)) print_sol print_sol3 end-if !----------------------------------------------------------------- ! Solution printing procedure print_sol writeln("Completion time: ", getsol(finish) , " average: ", getsol(sum(j in JOBS) comp(j))) write("Rel\t") forall(j in JOBS) write(strfmt(REL(j),4)) write("\nDur\t") forall(j in JOBS) write(strfmt(DUR(j),4)) write("\nStart\t") forall(j in JOBS) write(strfmt(getsol(start(j)),4)) write("\nEnd\t") forall(j in JOBS) write(strfmt(getsol(comp(j)),4)) writeln end-procedure procedure print_sol3 write("Due\t") forall(j in JOBS) write(strfmt(DUE(j),4)) write("\nLate\t") forall(j in JOBS) write(strfmt(getsol(late(j)),4)) writeln end-procedure end-model
This implementation introduces a new branching scheme, namely settle_disjunction. As opposed to the branching strategies we have seen so far this scheme defines a branching strategy over constraints, and not over variables. With this scheme a node is created by choosing a constraint from the given set and the branches from the node are obtained by adding one of the mutually exclusive constraints forming this disjunctive constraint to the constraint system.
NB: the disjunctive constraint of Xpress Kalis establishes pair-wise inequalities between the processing times of tasks. However, the definition of disjunctive constraints is not restricted to this case: a disjunction may have more than two components, and involve constraints of any type, including other logic relations obtained by combining constraints with and or or.
© 2001-2019 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.