Skip to content

Commit 259663f

Browse files
include custom_instance_parser and totorial files and further finetuning
1 parent e68cb19 commit 259663f

28 files changed

+829
-159
lines changed

LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2023 AI for Decision Making @TU/e
3+
Copyright (c) 2024 AI for Decision Making @TU/e
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

configs/cp_sat.toml

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ problem_instance = "/fjsp/1_Brandimarte/Mk01.fjs"
33

44
[solver]
55
time_limit = 3600 # time limit for the OR-tools CP-SAT solver, in seconds
6+
model = "fjsp" # model to use, either 'jsp', 'fsp', fjsp' or 'fjsp_sdst'
67

78
[output]
89
show_gantt = true # draw ganttchart of found solution
+136
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
from scheduling_environment.job import Job
2+
from scheduling_environment.machine import Machine
3+
from scheduling_environment.operation import Operation
4+
from scheduling_environment.jobShop import JobShop
5+
6+
7+
def parse(processing_info, instance_name="custom_problem_instance"):
8+
9+
# Initialize JobShop
10+
jobShop = JobShop()
11+
jobShop.set_instance_name(instance_name)
12+
13+
# Configure Machines based on nr_machines in processing_info
14+
number_total_machines = processing_info["nr_machines"]
15+
for machine_id in range(0, number_total_machines):
16+
jobShop.add_machine(Machine(machine_id))
17+
jobShop.set_nr_of_machines(number_total_machines)
18+
19+
# Configure jobs, operations, and processing times
20+
for job_info in processing_info["jobs"]:
21+
job = Job(job_id=job_info["job_id"])
22+
23+
for operation_info in job_info["operations"]:
24+
operation = Operation(job, job_info["job_id"], operation_info["operation_id"])
25+
26+
# Convert machine names (e.g., "machine_1") to numeric IDs for compatibility
27+
for machine_key, processing_time in operation_info["processing_times"].items():
28+
machine_id = int(machine_key.split("_")[1])-1
29+
operation.add_operation_option(machine_id, processing_time)
30+
31+
job.add_operation(operation)
32+
jobShop.add_operation(operation)
33+
jobShop.add_job(job)
34+
jobShop.set_nr_of_jobs(len(processing_info["jobs"]))
35+
36+
# Configure precedence relations between operations
37+
precedence_relations = {}
38+
for job_info in processing_info["jobs"]:
39+
for op_info in job_info["operations"]:
40+
if op_info["predecessor"] is not None:
41+
operation = jobShop.get_operation(op_info["operation_id"])
42+
predecessor_operation = jobShop.get_operation(op_info["predecessor"])
43+
operation.add_predecessors([predecessor_operation])
44+
precedence_relations[op_info["operation_id"]] = [predecessor_operation]
45+
else:
46+
precedence_relations[op_info["operation_id"]] = []
47+
48+
# Configure sequence-dependent setup times for each machine and operation pair
49+
setup_times = processing_info["sequence_dependent_setup_times"]
50+
sequence_dependent_setup_times = {}
51+
52+
for machine_key, setup_matrix in setup_times.items():
53+
machine_id = int(machine_key.split("_")[1])-1 # Convert machine_1 to machine ID 1
54+
machine_setup_times = {}
55+
56+
# Map the setup times for all pairs of operations
57+
for i in range(len(setup_matrix)):
58+
for j in range(len(setup_matrix[i])):
59+
if i != j: # Ignore setup times for the same operation (i == j)
60+
if i not in machine_setup_times:
61+
machine_setup_times[i] = {}
62+
machine_setup_times[i][j] = setup_matrix[i][j]
63+
64+
sequence_dependent_setup_times[machine_id] = machine_setup_times
65+
66+
# Add the precedence relations and sequence-dependent setup times to the JobShop
67+
jobShop.add_precedence_relations_operations(precedence_relations)
68+
jobShop.add_sequence_dependent_setup_times(sequence_dependent_setup_times)
69+
70+
return jobShop
71+
72+
73+
if __name__ == "__main__":
74+
processing_info = {
75+
"instance_name": "custom_problem_instance",
76+
"nr_machines": 2,
77+
"jobs": [
78+
{"job_id": 0, "operations": [
79+
{"operation_id": 0, "processing_times": {"machine_1": 10, "machine_2": 20}, "predecessor": None},
80+
{"operation_id": 1, "processing_times": {"machine_1": 25, "machine_2": 19}, "predecessor": 0}
81+
]},
82+
{"job_id": 1, "operations": [
83+
{"operation_id": 2, "processing_times": {"machine_1": 23, "machine_2": 21}, "predecessor": None},
84+
{"operation_id": 3, "processing_times": {"machine_1": 12, "machine_2": 24}, "predecessor": 2}
85+
]},
86+
{"job_id": 2, "operations": [
87+
{"operation_id": 4, "processing_times": {"machine_1": 37, "machine_2": 21}, "predecessor": None},
88+
{"operation_id": 5, "processing_times": {"machine_1": 23, "machine_2": 34}, "predecessor": 4}
89+
]}
90+
],
91+
"sequence_dependent_setup_times": {
92+
"machine_1": [
93+
[0, 25, 30, 35, 40, 45],
94+
[25, 0, 20, 30, 40, 50],
95+
[30, 20, 0, 10, 15, 25],
96+
[35, 30, 10, 0, 5, 10],
97+
[40, 40, 15, 5, 0, 20],
98+
[45, 50, 25, 10, 20, 0]
99+
],
100+
"machine_2": [
101+
[0, 21, 30, 35, 40, 45],
102+
[21, 0, 10, 25, 30, 40],
103+
[30, 10, 0, 5, 15, 25],
104+
[35, 25, 5, 0, 10, 20],
105+
[40, 30, 15, 10, 0, 25],
106+
[45, 40, 25, 20, 25, 0]
107+
]
108+
}
109+
}
110+
111+
jobShopEnv = parse(processing_info)
112+
print('Job shop setup complete')
113+
114+
# TEST GA:
115+
# from solution_methods.GA.src.initialization import initialize_run
116+
# from solution_methods.GA.run_GA import run_GA
117+
# import multiprocessing
118+
#
119+
# parameters = {"instance": {"problem_instance": "custom_problem_instance"},
120+
# "algorithm": {"population_size": 8, "ngen": 10, "seed": 5, "indpb": 0.2, "cr": 0.7, "mutiprocessing": True},
121+
# "output": {"logbook": True}
122+
# }
123+
#
124+
# pool = multiprocessing.Pool()
125+
# population, toolbox, stats, hof = initialize_run(jobShopEnv, pool, **parameters)
126+
# makespan, jobShopEnv = run_GA(jobShopEnv, population, toolbox, stats, hof, **parameters)
127+
128+
# TEST CP_SAT:
129+
from solution_methods.CP_SAT.run_cp_sat import run_CP_SAT
130+
parameters = {"instance": {"problem_instance": "custom_fjsp_sdst"},
131+
"solver": {"time_limit": 3600},
132+
"output": {"logbook": True}
133+
}
134+
135+
jobShopEnv = parse(processing_info)
136+
results, jobShopEnv = run_CP_SAT(jobShopEnv, **parameters)

data_parsers/parser_fajsp.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
from scheduling_environment.operation import Operation
77

88

9-
def parse(JobShop, instance, from_absolute_path=False):
9+
def parse_fajsp(JobShop, instance, from_absolute_path=False):
10+
JobShop.set_instance_name(instance)
1011
if not from_absolute_path:
1112
base_path = Path(__file__).parent.parent.absolute()
1213
data_path = base_path.joinpath('data' + instance)

data_parsers/parser_fjsp.py

+10-11
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
from scheduling_environment.operation import Operation
77

88

9-
def parse(JobShop, instance, from_absolute_path=False):
9+
def parse_fjsp(JobShop, instance, from_absolute_path=False):
10+
JobShop.set_instance_name(instance)
11+
1012
if not from_absolute_path:
1113
base_path = Path(__file__).parent.parent.absolute()
1214
data_path = base_path.joinpath('data' + instance)
@@ -60,7 +62,7 @@ def parse(JobShop, instance, from_absolute_path=False):
6062
JobShop.add_job(job)
6163
job_id += 1
6264

63-
# add also the operations without precedence operations to the precendence relations dictionary
65+
# add also the operations without precedence operations to the precedence relations dictionary
6466
for operation in JobShop.operations:
6567
if operation.operation_id not in precedence_relations.keys():
6668
precedence_relations[operation.operation_id] = []
@@ -81,12 +83,9 @@ def parse(JobShop, instance, from_absolute_path=False):
8183
return JobShop
8284

8385

84-
if __name__ == "__main__":
85-
# add the following to top:
86-
87-
# for running this code locally
88-
import sys
89-
90-
sys.path.append("../Machine_scheduling_benchmarking/")
91-
92-
JobShop = parse(r"\fjsp\6_Fattahi\MFJS1.fjs", )
86+
# if __name__ == "__main__":
87+
# from scheduling_environment.jobShop import JobShop
88+
# jobShopEnv = JobShop()
89+
# jobShopEnv = parse_fjsp(jobShopEnv, '/fjsp/1_brandimarte/Mk01.fjs')
90+
# jobShopEnv.update_operations_available_for_scheduling()
91+
# print(jobShopEnv.operations_available_for_scheduling)

data_parsers/parser_fjsp_sdst.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
from scheduling_environment.operation import Operation
66

77

8-
def parse(JobShop, instance, from_absolute_path=False):
8+
def parse_fjsp_sdst(JobShop, instance, from_absolute_path=False):
9+
JobShop.set_instance_name(instance)
910
if not from_absolute_path:
1011
base_path = Path(__file__).parent.parent.absolute()
1112
data_path = base_path.joinpath('data' + instance)

data_parsers/parser_jsp_fsp.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
from scheduling_environment.operation import Operation
77

88

9-
def parse(JobShop, instance, from_absolute_path=False):
9+
def parse_jsp_fsp(JobShop, instance, from_absolute_path=False):
10+
JobShop.set_instance_name(instance)
1011
if not from_absolute_path:
1112
base_path = Path(__file__).parent.parent.absolute()
1213
data_path = base_path.joinpath('data' + instance)

plotting/drawer.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def create_colormap():
3939

4040

4141
# Plot the Gantt chart of the job shop schedule
42-
def draw_gantt_chart(JobShop, save=False, filename=None):
42+
def plot_gantt_chart(JobShop, save=False, filename=None):
4343
fig, ax = plt.subplots()
4444
colormap = create_colormap()
4545

@@ -84,10 +84,10 @@ def draw_gantt_chart(JobShop, save=False, filename=None):
8484
)
8585

8686
fig = ax.figure
87-
fig.set_size_inches(16, 8)
87+
fig.set_size_inches(12, 6)
8888

8989
ax.set_yticks(range(JobShop.nr_of_machines))
90-
ax.set_yticklabels([f'M{machine_id}' for machine_id in range(JobShop.nr_of_machines)])
90+
ax.set_yticklabels([f'M{machine_id+1}' for machine_id in range(JobShop.nr_of_machines)])
9191
ax.set_xlabel('Time')
9292
ax.set_ylabel('Machine')
9393
ax.set_title('Job Shop Scheduling Gantt Chart')

scheduling_environment/job.py

+5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ def __init__(self, job_id: int):
77
self._job_id: int = job_id
88
self._operations: List[Operation] = []
99

10+
def __repr__(self):
11+
return (
12+
f"<Job(job_id={self._job_id})>"
13+
)
14+
1015
def add_operation(self, operation: Operation):
1116
"""Add an operation to the job."""
1217
self._operations.append(operation)

scheduling_environment/jobShop.py

+50-21
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,14 @@ def __init__(self) -> None:
1919
self._operations_to_be_scheduled: List[Operation] = []
2020
self._operations_available_for_scheduling: List[Operation] = []
2121
self._scheduled_operations: List[Operation] = []
22-
self._name: str = ""
22+
self._instance_name: str = ""
23+
24+
def __repr__(self):
25+
return (
26+
f"<JobShop(instance={self._instance_name!r}, "
27+
f"jobs={self.nr_of_jobs}, operations={self.nr_of_operations}, "
28+
f"machines={self.nr_of_machines})>"
29+
)
2330

2431
def reset(self):
2532
self._scheduled_operations = []
@@ -33,8 +40,7 @@ def reset(self):
3340
for operation in self._operations:
3441
operation.reset()
3542

36-
def __str__(self):
37-
return f"Instance {self._name}, {self.nr_of_jobs} jobs, {len(self.operations)} operations, {len(self.machines)} machines"
43+
self.update_operations_available_for_scheduling()
3844

3945
def set_nr_of_jobs(self, nr_of_jobs: int) -> None:
4046
"""Set the number of jobs."""
@@ -44,6 +50,10 @@ def set_nr_of_machines(self, nr_of_machines: int) -> None:
4450
"""Set the number of jobs."""
4551
self._nr_of_machines = nr_of_machines
4652

53+
def set_instance_name(self, name: str) -> None:
54+
"""Set the name of the instance."""
55+
self._instance_name = name
56+
4757
def add_operation(self, operation) -> None:
4858
"""Add an operation to the environment."""
4959
self._operations_to_be_scheduled.append(operation)
@@ -74,18 +84,25 @@ def set_operations_available_for_scheduling(self, operations_available_for_sched
7484
self._operations_available_for_scheduling = operations_available_for_scheduling
7585

7686
def get_job(self, job_id):
77-
"""Return operation object with operation id."""
78-
return next((job for job in self.jobs if job.job_id == job_id), None)
87+
"""Return operation object with operation id, or None if not found."""
88+
job = next((job for job in self.jobs if job.job_id == job_id), None)
89+
if job is None:
90+
raise ValueError(f"No job found with job_id: {job_id}")
91+
return job
7992

8093
def get_operation(self, operation_id):
81-
"""Return operation object with operation id."""
82-
return next((operation for operation in self.operations if operation.operation_id == operation_id), None)
94+
"""Return operation object with operation id, or None if not found."""
95+
operation = next((operation for operation in self.operations if operation.operation_id == operation_id), None)
96+
if operation is None:
97+
raise ValueError(f"No operation found with operation_id: {operation_id}")
98+
return operation
8399

84100
def get_machine(self, machine_id):
85-
"""Return machine object with machine id."""
86-
for machine in self._machines:
87-
if machine.machine_id == machine_id:
88-
return machine
101+
"""Return machine object with machine id, or None if not found."""
102+
machine = next((machine for machine in self._machines if machine.machine_id == machine_id), None)
103+
if machine is None:
104+
raise ValueError(f"No machine found with machine_id: {machine_id}")
105+
return machine
89106

90107
@property
91108
def jobs(self) -> List[Job]:
@@ -145,7 +162,7 @@ def precedence_relations_jobs(self) -> Dict[int, List[int]]:
145162
@property
146163
def instance_name(self) -> str:
147164
"""Return the name of the instance."""
148-
return self._name
165+
return self._instance_name
149166

150167
@property
151168
def makespan(self) -> float:
@@ -190,6 +207,14 @@ def max_flowtime(self) -> float:
190207
max_flowtime = flow_time
191208
return max_flowtime
192209

210+
def schedule_operation_on_machine(self, operation: Operation, machine_id, duration) -> None:
211+
"""Schedule an operation on a specific machine."""
212+
machine = self.get_machine(machine_id)
213+
if machine is None:
214+
raise ValueError(
215+
f"Invalid machine ID {machine_id}")
216+
machine.add_operation_to_schedule(operation, duration, self._sequence_dependent_setup_times)
217+
193218
def schedule_operation_with_backfilling(self, operation: Operation, machine_id, duration) -> None:
194219
"""Schedule an operation"""
195220
if operation not in self.operations_available_for_scheduling:
@@ -208,15 +233,6 @@ def unschedule_operation(self, operation: Operation) -> None:
208233
machine.unschedule_operation(operation)
209234
self.mark_operation_as_available(operation)
210235

211-
def schedule_operation_on_machine(self, operation: Operation, machine_id, duration) -> None:
212-
"""Schedule an operation on a specific machine."""
213-
machine = self.get_machine(machine_id)
214-
if machine is None:
215-
raise ValueError(
216-
f"Invalid machine ID {machine_id}")
217-
218-
machine.add_operation_to_schedule(operation, duration, self._sequence_dependent_setup_times)
219-
220236
def mark_operation_as_scheduled(self, operation: Operation) -> None:
221237
"""Mark an operation as scheduled."""
222238
if operation not in self.operations_available_for_scheduling:
@@ -234,3 +250,16 @@ def mark_operation_as_available(self, operation: Operation) -> None:
234250
self.scheduled_operations.remove(operation)
235251
self.operations_available_for_scheduling.append(operation)
236252
self.operations_to_be_scheduled.append(operation)
253+
254+
def update_operations_available_for_scheduling(self) -> None:
255+
"""Update the list of operations available for scheduling."""
256+
scheduled_operations = set(self.scheduled_operations)
257+
operations_available = [
258+
operation
259+
for operation in self.operations
260+
if operation not in scheduled_operations and all(
261+
prec_operation in scheduled_operations
262+
for prec_operation in self._precedence_relations_operations[operation.operation_id]
263+
)
264+
]
265+
self.set_operations_available_for_scheduling(operations_available)

0 commit comments

Comments
 (0)