def test_get_processing_time(self, idx_job, idx_machine): frame = JobSchedulingFrame(self.processing_times) fst_time = self.processing_times[idx_job][idx_machine] scnd_time = frame.get_processing_time(idx_job, idx_machine) assert fst_time == scnd_time
def test_set_processing_times(self): frame = JobSchedulingFrame([[]]) frame.set_processing_times(self.processing_times) expected_frame = JobSchedulingFrame(self.processing_times) tm.assert_js_frame(frame, expected_frame)
def slope_index_func(frame: JobSchedulingFrame, idx_job: int) -> int: """ Compute slope index for `idx_job` using the method invented by Palmer, D.S. Parameters ---------- frame: JobSchedulingFrame idx_job: int Returns ------- slope index: int Notes ----- Journal Paper: Palmer, D.S., 1965. Sequencing jobs through a multi-stage process in the minimum total time a quick method of obtaining a near optimum. Operations Research Quarterly 16(1), 101-107 """ count_machines = frame.count_machines slope_index = 0 for idx_machine in range(count_machines): slope_index -= (count_machines - (2 * (idx_machine + 1) - 1)) * \ frame.get_processing_time(idx_job, idx_machine) return slope_index
def neh_heuristics(frame: JobSchedulingFrame) -> list: """ Compute approximate solution for instance of Flow Job problem by NEH heuristic. Parameters ---------- frame: JobSchedulingFrame Returns ------- solution: list sequence of job index Notes ----- Journal Paper: Nawaz,M., Enscore,Jr.E.E, and Ham,I. (1983) A Heuristics Algorithm for the m Machine, n Job Flowshop Sequencing Problem. Omega-International Journal of Management Science 11(1), 91-95 """ count_jobs = frame.count_jobs all_processing_times = [0] * count_jobs for j in range(count_jobs): all_processing_times[j] = frame.get_sum_processing_time(j) init_jobs = [j for j in range(count_jobs)] init_jobs.sort(key=lambda x: all_processing_times[x], reverse=True) solution, _ = local_search_partitial_sequence(frame, init_jobs) return solution
def fgh_heuristic(frame: JobSchedulingFrame, count_alpha: int = 1) -> list: init_jobs = [idx_job for idx_job in range(frame.count_jobs)] sum_times = [ frame.get_sum_processing_time(idx_job) for idx_job in init_jobs ] tetta = sum(sum_times) / frame.count_jobs time_min = min(sum_times) time_max = max(sum_times) alpha_min = time_min / (time_min + tetta) alpha_max = time_max / (time_max + tetta) period = (alpha_max - alpha_min) / count_alpha solutions = [] for i in range(count_alpha): alpha = alpha_max - period * i init_jobs.sort( key=lambda idx_job: fgh_index(sum_times[idx_job], alpha, tetta), reverse=True) solutions.append(copy(init_jobs)) for i in range(count_alpha): solutions[i], _ = local_search_partitial_sequence(frame, solutions[i]) solutions.sort(key=lambda solution: compute_end_time(frame, solution)) return solutions[0]
def setup_method(self): self.frame1 = JobSchedulingFrame([[17, 19, 13], [15, 11, 12], [14, 21, 16], [20, 16, 20], [16, 17, 17]]) self.frame1_solution1 = [2, 4, 3, 0, 1] self.end_time_f1_s1 = 114 self.str_f1_s1 = ('2: (0, 14), (14, 35), (35, 51),\n' '4: (14, 30), (35, 52), (52, 69),\n' '3: (30, 50), (52, 68), (69, 89),\n' '0: (50, 67), (68, 87), (89, 102),\n' '1: (67, 82), (87, 98), (102, 114),\n') self.str_f1_s1_1machine = ('2: (0, 14),\n' '4: (14, 30),\n' '3: (30, 50),\n' '0: (50, 67),\n' '1: (67, 82),\n') self.frame1_solution2 = [4, 2, 3, 0, 1] self.end_time_f1_s2 = 115 self.str_f1_s2 = ('4: (0, 16), (16, 33), (33, 50),\n' '2: (16, 30), (33, 54), (54, 70),\n' '3: (30, 50), (54, 70), (70, 90),\n' '0: (50, 67), (70, 89), (90, 103),\n' '1: (67, 82), (89, 100), (103, 115),\n')
def johnson_algorithm(frame: JobSchedulingFrame) -> list: """ Compute solution for case of 2 machines of flow job problem by Johnson's algorithm. Parameters ---------- frame: JobSchedulingFrame frame with exactly 2 machines Returns ------- exact_solution: list list of job index """ if frame.count_machines != 2: raise ValueError('count machines must be 2') # init job indexes exact_solution = [i for i in range(frame.count_jobs)] # sorting by increasing the minimum processing time exact_solution.sort(key=lambda idx_job: min( frame.get_processing_time(idx_job, idx_machine=0), frame.get_processing_time(idx_job, idx_machine=1))) jobs_on_first_machine = [] jobs_on_second_machine = [] # distribution of jobs on machines for idx_job in exact_solution: frst = frame.get_processing_time(idx_job, idx_machine=0) scnd = frame.get_processing_time(idx_job, idx_machine=1) if frst < scnd: jobs_on_first_machine.append(idx_job) else: jobs_on_second_machine.append(idx_job) exact_solution = jobs_on_first_machine # simulating the installation of jobs that are processed faster on the # second machine to the end of the processing queue on the first machine jobs_on_second_machine.reverse() exact_solution.extend(jobs_on_second_machine) return exact_solution
def test_count_jobs_and_machines(self): frame = JobSchedulingFrame(self.processing_times) count_jobs = len(self.processing_times) count_machines = len(self.processing_times[0]) assert frame.count_jobs == count_jobs assert frame.count_machines == count_machines
def test_johnson_algorithm(self): frame = JobSchedulingFrame([[2, 3], [8, 3], [4, 6], [9, 5], [6, 8], [9, 7]]) solution = johnson_algorithm(frame) assert solution == [0, 2, 4, 5, 3, 1] solution_end_time = compute_end_time(frame, solution) assert solution_end_time == 41
def cds_create_proc_times(frame: JobSchedulingFrame, sub_problem: int) -> list: """ Create processing time matrix with 2 machines from matrix with M machines according to the CDS heuristic rule. Parameters ---------- frame: JobSchedulingFrame sub_problem: int `sub_problem` values start with 1 Returns ------- matrix of processing times: list Notes ----- Developed by Campbell, Dudek, and Smith in 1970. """ processing_times = [] count_machines = frame.count_machines for idx_job in range(frame.count_jobs): first_machine_time = 0 second_machine_time = 0 # From `sub_problem` count machines will make one artificial, # summing up the processing times on each of them. for idx_machine in range(sub_problem): first_machine_time += frame.get_processing_time( idx_job, idx_machine) # From `sub_problem` machines will make one artificial, # summing up the processing times on each of them. for idx_machine in range(count_machines - sub_problem, count_machines): second_machine_time += frame.get_processing_time( idx_job, idx_machine) processing_times.append([first_machine_time, second_machine_time]) return processing_times
def test_str(self): taillard_str = ("number of jobs, number of machines, " "initial seed, upper bound and lower bound :\n" " 5 3" " NaN NaN NaN\n" "processing times :\n" " 17 15 14 20 16\n" " 19 11 21 16 17\n" " 13 12 16 20 17\n") assert taillard_str == str(JobSchedulingFrame(self.processing_times))
def cds_heuristics(frame: JobSchedulingFrame) -> list: """ Compute approximate solution for instance of Flow Job problem by Campbell, Dudek, and Smith (CDS) heuristic. Parameters ---------- frame: JobSchedulingFrame Returns ------- solution: list sequence of job index Notes ----- Developed by Campbell, Dudek, and Smith in 1970. """ johnson_frame = JobSchedulingFrame([[]]) johnson_solutions_with_end_time = [] # Create `count_machines - 1` sub-problems # which will be solved by Johnson's algorithm for sub_problem in range(1, frame.count_machines): # Create processing times matrix for all jobs on only 2 machines proc_times = cds_create_proc_times(frame, sub_problem) johnson_frame.set_processing_times(proc_times) johnson_solution = johnson_algorithm(johnson_frame) # end time compute for the original task, that is `frame` end_time = compute_end_time(frame, johnson_solution) johnson_solutions_with_end_time.append((johnson_solution, end_time)) johnson_solutions_with_end_time.sort(key=lambda elem: elem[1]) # return only solution with minimum makespan (end_time) return johnson_solutions_with_end_time[0][0]
def test_cds_create_proc_times(): processing_times = [[17, 19, 13], [15, 11, 12], [14, 21, 16], [20, 16, 20], [16, 17, 17]] frame = JobSchedulingFrame(processing_times) johnson_frame = JobSchedulingFrame([[]]) # sub problem index begin with 1 processing_times_first = cds_create_proc_times(frame, 1) johnson_frame.set_processing_times(processing_times_first) assert_js_frame( johnson_frame, JobSchedulingFrame([[17, 13], [15, 12], [14, 16], [20, 20], [16, 17]])) processing_times_second = cds_create_proc_times(frame, 2) johnson_frame.set_processing_times(processing_times_second) assert_js_frame( johnson_frame, JobSchedulingFrame([[36, 32], [26, 23], [35, 37], [36, 36], [33, 34]]))
def _artificial_time(frame: JobSchedulingFrame, jobs: list, unscheduled_jobs: list) -> int: # copy processing time matrix jobs x machines from frame processing_times = frame.copy_proc_time # creating processing times for artificial job as average of # the processing times of jobs from `unscheduled_jobs` artificial_prc_times = [] for idx_machine in range(frame.count_machines): average_time = 0. for idx_job in range(len(unscheduled_jobs)): average_time += processing_times[idx_job][idx_machine] average_time /= len(unscheduled_jobs) artificial_prc_times.append(round(average_time)) processing_times.append(artificial_prc_times) assert frame.count_jobs + 1 == len(processing_times) assert frame.count_machines == len(processing_times[0]) frame_with_artificial_job = JobSchedulingFrame(processing_times) jobs.append(frame.count_jobs - 1) # added index of artificial job if len(jobs) != 1: end_time_sec_last_job = compute_end_time(frame_with_artificial_job, jobs, len(jobs) - 1, frame.count_machines - 1) else: end_time_sec_last_job = 0 end_time_last_job = compute_end_time(frame_with_artificial_job, jobs, len(jobs), frame.count_machines - 1) result_time = end_time_sec_last_job + end_time_last_job jobs.pop() return result_time
def test_bad_processing_times(self, processing_times): msg = 'processing_times must be list of lists of integers; same length' with pytest.raises(ValueError, match=msg): JobSchedulingFrame(processing_times)
def read_flow_shop_instances(file_name): """ Read from file with Tailard's instances to JobSchedulingFrames Parameters ---------- file_name: str Returns ------- Sequence of JobSchedulingFrame objects: list Notes ----- http://mistic.heig-vd.ch/taillard/problemes.dir/ordonnancement.dir/ordonnancement.html file format: number of jobs, number of machines, initial seed, upper bound and lower bound : 20 5 873654221 1278 1232 processing times : 54 83 15 71 77 36 53 38 27 87 76 91 14 29 12 77 32 87 68 94 79 3 11 99 56 70 99 60 5 56 3 61 73 75 47 14 21 86 5 77 16 89 49 15 89 45 60 23 57 64 7 1 63 41 63 47 26 75 77 40 66 58 31 68 78 91 13 59 49 85 85 9 39 41 56 40 54 77 51 31 58 56 20 85 53 35 53 41 69 13 86 72 8 49 47 87 58 18 68 28 """ file = open(file_name) frames = [] counter_lines = 0 for string in iter(file): counter_lines += 1 try: if string.strip().startswith('number'): string = next(file) counter_lines += 1 params = re.findall(r'\d+', string)[:4] if len(params) != 4: raise FlowShopFormatError(file_name, counter_lines) count_jobs, count_machines, \ _, upper_bound = (int(param) for param in params) string = next(file) counter_lines += 1 if string.strip().startswith('processing'): processing_time = [] for _ in range(count_machines): string = next(file) counter_lines += 1 times = re.findall(r'\d+', string) if len(times) != count_jobs: raise FlowShopFormatError(file_name, counter_lines) processing_time.append((int(time) for time in times)) processing_time = list(zip(*processing_time)) # transpose assert count_jobs == len(processing_time) assert count_machines == len(processing_time[0]) frames.append( JobSchedulingFrame(processing_time, upper_bound=upper_bound)) else: raise FlowShopFormatError(file_name, counter_lines) except StopIteration: raise FlowShopFormatError(file_name, counter_lines) if len(frames) == 0: raise FlowShopFormatError(file_name, None) file.close() return frames
def test_bad_upper_bound(self, upper_bound): msg = 'upper_bound must be the NaN or int' with pytest.raises(ValueError, match=msg): JobSchedulingFrame(self.processing_times, upper_bound=upper_bound)
def test_upper_bound(self, upper_bound): frame = JobSchedulingFrame(self.processing_times, upper_bound=upper_bound) assert upper_bound == frame.upper_bound
def test_bad_initial_seed(self, seed): msg = 'initial_seed must be the NaN or int' with pytest.raises(ValueError, match=msg): JobSchedulingFrame(self.processing_times, initial_seed=seed)
def test_initial_seed(self, seed): frame = JobSchedulingFrame(self.processing_times, initial_seed=seed) assert seed == frame.initial_seed
def test_bad_frame(self): msg = 'count machines must be 2' frame = JobSchedulingFrame([[1, 2, 3], [4, 5, 6]]) with pytest.raises(ValueError, match=msg): johnson_algorithm(frame)
def test_slope_index(idx_job, slope_index): processing_times = [[17, 19, 13], [15, 11, 12], [14, 21, 16], [20, 16, 20], [16, 17, 17]] frame = JobSchedulingFrame(processing_times) assert slope_index == slope_index_func(frame, idx_job)