Based on the "Iterated local search based on multi-type perturbation for single-machine earliness/tardiness scheduling" by Qin et al. 2015. Tailored to tackle Biskup's 2001 "Benchmarks for scheduling on a single machine against restrictive and unrestrictive common due dates".
Scheduling Problem with Weighted Tardiness and Earliness
E = earliness
T = tardiness
s = starting time
p = processing time
d = common due date
i, k = jobs
x = whether the job i preceds k
R = large constant
V-Shape property: the best solutions are ordered so that the ratios of the jobs
Run the main.ipynb
and change the path of the job_data_biskup.csv
for the job data or the result_heuristic99_BA.csv
for the initial sequence if needed. Run the cases and results will be saved in the main directory as a csv file containg the early and late job sets, as well as the costs and time to run the instances. Alternatively, it is possible to test a single case inputing the jobs list and the jobs sets on the BiskupILS.py
file.
According to the problem formulated, it is required a common due date which is the target time for completion of all jobs and a list of jobs containing the processing time, earliness penalty and lateness penalty.
This heuristic also admits as input lists of early (A) and late (B) jobs (with indexes corresponding to the order of the jobs in the jobs list), already sorted in the V-Shape. Thus, the algorithm ideally admits an initial solution generated by a constructive heuristic.
The following parameters must be decided before the run:
threshold_swaps: maximum neighborhood size for swaps
threshold_inserts: maximum neighborhood size for inserts
insert_probability: probability of applying insert move over swap
stop_iter: maximum iterations without improvement to stop
alpha_1: tabu list size deterministic parameter
alpha_2: tabu list size probabilistic parameter
L1: number of moves for tabu perturbation
L2: number of moves for construction perturbation
L3: number of moves for random perturbation
P: probability of applying tabu perturbation
Q: probability of applying construction over random perturbation
The ILS algorithm operates as evidenced on the following pseudocodes:
1. A_best, B_best -> A, B
2. best_cost = cost(A,B)
3. iter_no_improv -> 0
4. while iter_no_improv < stop_iter do:
5. A, B = local_search(A,B)
6. if cost(A,B) < best_cost:
7. A_best, B_best -> A, B
8. best_cost = cost(A,B)
9. iter_no_improv = 0
10. else:
11. iter_no_improv += 1
12. endif
13. x, y = random(0,1), random(0,1)
14. if x < P:
15. A, B = tabu_perturbation(A,B,L1)
16. else if y < (1 - P) * Q:
17. A, B = construction_perturbation(A,B,L2)
18. else:
19. A, B = random_perturbation(A,B,L3)
20. endif
21. endwhile
22. return A_best, B_best, best_cost, time
This function returns the feasible candidate(s) for insertion or swap, given a job, the early and late jobs sequences already in v-shape. To do so, it verifies whether:
(1) The position where the job is inserted does not break the v-shape property. (2) The sum of processing times in the early jobs set does not exceed the common due date. (3) In case of swaps, if the swapped job insertion doen not break the v-shape property.
Furthermore, to contemplate all possible insertions, the job -1
can outputed, which indicates insertion after the last element of the sequence.
The evaluate_move
function simply computest the total cost of the sequece after the move. This local search makes the best move until there are no improving moves left.
1. move_type -> random(insert,swap)
2. moves -> Ø
3. sequence -> A + B
4. best_cost = cost(A,B)
5. while True
6. for job in sequence do:
7. candidates = vshape_candidates(A,B,job,threshold)
8. cost = evaluate_move(job,candidates,move_type).cost
9. if cost < best_cost:
10. append evaluate_move(job,candidates,move_type) to moves
11. endif
12. endfor
13. if moves == Ø:
14. break
15. else:
16. best_move -> move[x], x with minimum cost
17. implement_move(best_move, move_type)
18. endif
19. endwhile
20. return A, B
Similar to local search, but the goal is to make the move that minimizes cost degradation, even if it leads to a worse solution. The search is conducted through L1
iterations and if there is improvement, the move is made and the solution is stored.
1. move_type -> random(insert,swap)
2. A_best, B_best = A, B
3. best_cost = cost(A,B)
4. moves -> Ø
5. improved -> False
6. tabu_list_size = int(alpha_1 * threshold + random(0,alpha_2*threshold))
7. sequence -> A + B
8. for i from 1 to L1:
9. for job in sequence do:
10. candidates = vshape_candidates(A,B,job,threshold)
11. cost = evaluate_move(job,candidates,move_type).cost
12. if cost < best_cost
13. A_best -> A
14. B_best -> B
15. best_cost -> cost
16. improved -> True
17. endif
18. append evaluate_move(job,candidates,move_type) to moves
19. endfor
20. if moves == Ø:
21. break
22. else:
23. best_move -> move[x], x with minimum cost
24. implement_move(best_move, move_type)
25. endif
26. endfor
27. if improved:
28. return A_best, B_best
29. else:
30. return A, B
31. endif
This perturbation ranks the feasible moves for all the jobs in increasing order of cost then chooses the job at the kth position following the harmonic probability function
1. move_type -> insert
2. sequence = A + B
3. moves -> Ø
4. for job in sequence:
5. candidates = vshape_candidates(A,B,job,threshold)
6. append evaluate_move(job,candidates,move_type) to moves
7. sort move in moves in increasing cost
8. chosen_move -> harmonic_select(moves)
9. implement_move(best_move, move_type)
10. return A, B
Perform random swap/insertion among the fesible candidates of a randomly selected job.