Skip to content

Commit 60b78de

Browse files
implementing FJSP_DRL to be applied in a dynamic/online env
1 parent 9a77218 commit 60b78de

18 files changed

+507
-994
lines changed

README.md

+19-18
Original file line numberDiff line numberDiff line change
@@ -9,29 +9,31 @@ This repository provides a comprehensive benchmarking environment for a variety
99
### 🛠 Solution Methods:
1010
The repository includes exact, heuristic and learning based solution methods, each compatible with one or more machine scheduline problem variants:
1111

12-
| Solution methods | Type | JSP | FSP | FJSP | FJSP SDST | FAJSP | Online (F)JSP |
12+
| Solution methods | Type | JSP | FSP | FJSP | SDST | AJSP | Dynamic JSP |
1313
| :----: | :---:| :---:| :---: | :---: | :---: | :---: | :---: |
14-
| MILP | Exact ||||| | |
15-
| CP-SAT | Exact ||||| | |
14+
| MILP | Exact ||||| | |
15+
| CP-SAT | Exact ||||| | |
1616
| Dispatching Rules | Heuristic |||||||
17-
| Genetic Algorithm | Heuristic |||||| |
18-
| FJSP-DRL | DRL | || | | | |
19-
| L2D | DRL ||| | | | |
20-
| DANIEL | DRL |||| | | |
17+
| Genetic Algorithm | Heuristic |||||| |
18+
| L2D | DRL ||| | | | |
19+
| FJSP-DRL | DRL | || ||| |
20+
| DANIEL | DRL |||| | | |
2121

2222
### 🚀 How to use:
2323

2424
Here we provide some short examples on how to use the solution methods in this repository. For more detailed information and more examples, please refer to the tutorials [here][2] and [here][3].
2525

26-
1. **Dispatching Rules:**
26+
1. **Dispatching Rules:**
27+
2728
```python
28-
from solution_methods.dispatching_rules import run_dispatching_rules
29-
from solution_methods.helper_functions import load_job_shop_env, load_parameters
30-
31-
parameters = load_parameters("configs/dispatching_rules.toml")
32-
jobShopEnv = load_job_shop_env(parameters['instance'].get('problem_instance'))
33-
34-
makespan, jobShopEnv = run_dispatching_rules(jobShopEnv, **parameters)
29+
30+
from solution_methods.L2D import run_dispatching_rules
31+
from solution_methods.helper_functions import load_job_shop_env, load_parameters
32+
33+
parameters = load_parameters("configs/dispatching_rules.toml")
34+
jobShopEnv = load_job_shop_env(parameters['instance'].get('problem_instance'))
35+
36+
makespan, jobShopEnv = run_dispatching_rules(jobShopEnv, **parameters)
3537
```
3638

3739
2. **Genetic Algorithm:**
@@ -68,9 +70,8 @@ We provide plotting functions to draw both the precedence relations between ope
6870
### 🏗️ Repository Structure
6971
The repository is structured to provide ease of use and flexibility:
7072
- **Configs**: Contains the configuration files for the solution methods.
71-
- **Data**: Contains the problem instances for benchmarking for different problem variants.
72-
- **Data Parsers**: Parsers for configuring the benchmarking instances in the scheduling environment.
73-
- **Plotting**: Contains the plotting functions for visualizing the results.
73+
- **Data**: Contains the problem instances for benchmarking for different problem variants and data parsers for configuring the benchmarking instances in the scheduling environment.
74+
- **Visualization**: Contains the plotting functions for visualizing the results.
7475
- **Scheduling Environment**: Defines the core environment components (`job`, `operation`, `machine`, and `jobShop`). Also contains the `simulationEnv` for dynamic scheduling problems with online job arrivals.
7576
- **Solution Methods**: Contains the solution methods, including exact, heuristic, and learning-based approaches.
7677

configs/FJSP_DRL.toml

+11
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
[test_parameters]
22
seed = 2024 # Set random seed for result replication
33
device = "cpu" # Device for testing ("cpu" or "cuda")
4+
online_arrivals = true # false for static instance (from data) or true for online job arrivals
45
problem_instance = "/fjsp/brandimarte/Mk02.fjs" # Problem instance for testing
56
trained_policy = "/saved_models/train_20240314_192906/song_10_5.pt" # Load pretrained policy
7+
68
sample = false # sample from policy if true, else use argmax (greedy)
79
show_precedences = true # draw precedence relations graph of the problem instance
810
show_gantt = true # draw ganttchart of found solution
@@ -13,6 +15,15 @@ exp_name = "" # name of the experiment, used for saving results.
1315
folder = "" # folder to save results, used for saving results.
1416
# If empty (""), the results are saved to the current working directory.
1517

18+
[online_arrival_details] # Only needed for_online arrivals = true
19+
number_total_machines = 5 # number of machines
20+
inter_arrival_time = 15 # inter_arrival_time between jobs
21+
simulation_time = 500 # simulation duration time
22+
min_nr_operations_per_job = 2 # min number of operations per online arrived job
23+
max_nr_operations_per_job = 7 # max number of operations per online arrived job
24+
min_duration_per_operation = 2 # min duration of online arrived operation
25+
max_duration_per_operation = 40 # max duration of online arrived operation
26+
1627
[train_parameters]
1728
lr = 0.0002 # Learning rate
1829
betas = [0.9, 0.999] # Beta values for Adam optimizer

configs/cp_sat.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[instance]
2-
problem_instance = "/fjsp/brandimarte/Mk01.fjs"
2+
problem_instance = "/fajsp/dafjs/DAFJS01"
33

44
[solver]
55
time_limit = 3600 # time limit for the OR-tools CP-SAT solver, in seconds

configs/milp.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[instance]
2-
problem_instance = "/fajsp/dafjs/DAFJS01" #"/jsp/adams/abz5" #
2+
problem_instance = "/fajsp/dafjs/DAFJS01"
33

44
[solver]
55
time_limit = 3600 # time limit for the gurobi solver, in seconds

scheduling_environment/simulationEnv.py

+1
Original file line numberDiff line numberDiff line change
@@ -98,5 +98,6 @@ def generate_online_job_arrivals(self):
9898
operation_id += 1
9999

100100
self.jobShopEnv.add_job(job)
101+
self.jobShopEnv.set_nr_of_jobs(self.jobShopEnv.nr_of_jobs + 1)
101102
# print(f"Job {job_id} generated with {num_operations} operations") # Debugging print statement
102103
job_id += 1

solution_methods/DANIEL/run_DANIEL.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import os
44
import torch
55

6-
from plotting import gantt_chart, precedence_chart
6+
from visualization import gantt_chart, precedence_chart
77
from solution_methods.helper_functions import load_job_shop_env, load_parameters, initialize_device, set_seeds
88
from solution_methods.DANIEL.src.common_utils import greedy_select_action, sample_action
99
from solution_methods.DANIEL.src.env_test import FJSPEnv_test

solution_methods/FJSP_DRL/run_FJSP_DRL.py

+36-16
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,15 @@
1111
import os
1212
import torch
1313

14-
from visualisation import gantt_chart, precedence_chart
14+
from visualization import gantt_chart, precedence_chart
1515
from solution_methods.helper_functions import load_job_shop_env, load_parameters, initialize_device, set_seeds
1616
from solution_methods.FJSP_DRL.src.env_test import FJSPEnv_test
17+
from scheduling_environment.simulationEnv import SimulationEnv
1718

1819
from solution_methods.FJSP_DRL.src.PPO import HGNNScheduler
1920
from solution_methods.FJSP_DRL.utils import output_dir_exp_name, results_saving
21+
from solution_methods.FJSP_DRL.src.online_FJSP_DRL import run_online_dispatcher
22+
2023

2124
PARAM_FILE = "../../configs/FJSP_DRL.toml"
2225
logging.basicConfig(level=logging.INFO)
@@ -32,9 +35,6 @@ def run_FJSP_DRL(jobShopEnv, **parameters):
3235
if device.type == 'cuda':
3336
torch.cuda.set_device(device)
3437

35-
# Configure environment and load instance
36-
env_test = FJSPEnv_test(jobShopEnv, parameters["test_parameters"])
37-
3838
# Load trained policy
3939
model_parameters = parameters["model_parameters"]
4040
test_parameters = parameters["test_parameters"]
@@ -52,18 +52,37 @@ def run_FJSP_DRL(jobShopEnv, **parameters):
5252
hgnn_model = HGNNScheduler(model_parameters).to(device)
5353
hgnn_model.load_state_dict(policy)
5454

55-
# Get state and completion signal
56-
state = env_test.state
57-
done = False
58-
59-
# Generate schedule for instance
60-
while not done:
61-
with torch.no_grad():
62-
actions = hgnn_model.act(state, [], done, flag_train=False, flag_sample=test_parameters['sample'])
63-
state, _, done = env_test.step(actions)
64-
65-
makespan = env_test.JSP_instance.makespan
66-
logging.info(f"Makespan: {makespan}")
55+
if not parameters['test_parameters']['online_arrivals']:
56+
57+
env_test = FJSPEnv_test(jobShopEnv, parameters["test_parameters"])
58+
state = env_test.state
59+
done = False
60+
61+
# Generate schedule for instance
62+
while not done:
63+
with torch.no_grad():
64+
actions = hgnn_model.act(state, [], done, flag_train=False, flag_sample=test_parameters['sample'])
65+
state, _, done = env_test.step(actions)
66+
makespan = env_test.JSP_instance.makespan
67+
68+
else:
69+
simulationEnv = SimulationEnv(
70+
online_arrivals=parameters["online_arrival_details"]
71+
)
72+
simulationEnv.set_online_arrival_details(parameters["online_arrival_details"])
73+
simulationEnv.jobShopEnv.set_nr_of_machines(
74+
parameters["online_arrival_details"]["number_total_machines"]
75+
)
76+
simulationEnv.simulator.process(
77+
run_online_dispatcher(
78+
simulationEnv, hgnn_model
79+
)
80+
)
81+
simulationEnv.simulator.run(
82+
until=parameters["online_arrival_details"]["simulation_time"]
83+
)
84+
makespan = simulationEnv.jobShopEnv.makespan
85+
jobShopEnv = simulationEnv.jobShopEnv
6786

6887
return makespan, jobShopEnv
6988

@@ -78,6 +97,7 @@ def main(param_file=PARAM_FILE):
7897
jobShopEnv = load_job_shop_env(parameters['test_parameters']['problem_instance'])
7998
makespan, jobShopEnv = run_FJSP_DRL(jobShopEnv, **parameters)
8099

100+
81101
if makespan is not None:
82102
# Check output configuration and prepare output paths if needed
83103
output_config = parameters['test_parameters']

solution_methods/FJSP_DRL/src/load_data.py

+42-8
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,37 @@ def load_fjs(path, num_mas, num_opes, num_jobs):
6060
return drl_tensors, jobShopEnv
6161

6262

63+
# def load_feats_from_sim(jobShopEnv: JobShop, num_mas, num_opes):
64+
# """convert scheduling_environment environment to DRL environment"""
65+
# matrix_proc_time = torch.zeros(size=(num_opes, num_mas))
66+
# matrix_ope_ma_adj = torch.zeros(size=(num_opes, num_mas)).int()
67+
# matrix_pre_proc = torch.full(size=(num_opes, num_opes), dtype=torch.bool, fill_value=False)
68+
# matrix_cal_cumul = torch.zeros(size=(num_opes, num_opes)).int()
69+
# opes_appertain = torch.zeros(size=(num_opes,)).int()
70+
# num_ope_biases = torch.zeros(size=(jobShopEnv.nr_of_jobs,)).int()
71+
# nums_ope = torch.zeros(size=(jobShopEnv.nr_of_jobs,)).int()
72+
#
73+
# for job in jobShopEnv.jobs:
74+
# num_ope_biases[job.job_id] = job.operations[0].operation_id
75+
# nums_ope[job.job_id] = len(job.operations)
76+
#
77+
# nr_remaining_ops = len(job.operations)
78+
# for operation in job.operations:
79+
# nr_remaining_ops -= 1
80+
# for op in range(1, nr_remaining_ops + 1):
81+
# matrix_cal_cumul[operation.operation_id][operation.operation_id + op] = 1
82+
# for operation in jobShopEnv.operations:
83+
# opes_appertain[operation.operation_id] = operation.job_id
84+
# for machine_id, duration in operation.processing_times.items():
85+
# matrix_proc_time[operation.operation_id][machine_id] = duration
86+
# matrix_ope_ma_adj[operation.operation_id][machine_id] = 1
87+
# for predecessor in operation.predecessors:
88+
# predecessor: Operation
89+
# matrix_pre_proc[predecessor.operation_id][operation.operation_id] = True
90+
#
91+
# return matrix_proc_time, matrix_ope_ma_adj, matrix_pre_proc, matrix_pre_proc.t(), opes_appertain, num_ope_biases, \
92+
# nums_ope, matrix_cal_cumul
93+
6394
def load_feats_from_sim(jobShopEnv: JobShop, num_mas, num_opes):
6495
"""convert scheduling_environment environment to DRL environment"""
6596
matrix_proc_time = torch.zeros(size=(num_opes, num_mas))
@@ -69,24 +100,27 @@ def load_feats_from_sim(jobShopEnv: JobShop, num_mas, num_opes):
69100
opes_appertain = torch.zeros(size=(num_opes,)).int()
70101
num_ope_biases = torch.zeros(size=(jobShopEnv.nr_of_jobs,)).int()
71102
nums_ope = torch.zeros(size=(jobShopEnv.nr_of_jobs,)).int()
72-
73103
for job in jobShopEnv.jobs:
74104
num_ope_biases[job.job_id] = job.operations[0].operation_id
75105
nums_ope[job.job_id] = len(job.operations)
76-
77106
nr_remaining_ops = len(job.operations)
78107
for operation in job.operations:
79108
nr_remaining_ops -= 1
80109
for op in range(1, nr_remaining_ops + 1):
81110
matrix_cal_cumul[operation.operation_id][operation.operation_id + op] = 1
82111
for operation in jobShopEnv.operations:
83112
opes_appertain[operation.operation_id] = operation.job_id
84-
for machine_id, duration in operation.processing_times.items():
85-
matrix_proc_time[operation.operation_id][machine_id] = duration
86-
matrix_ope_ma_adj[operation.operation_id][machine_id] = 1
87-
for predecessor in operation.predecessors:
88-
predecessor: Operation
89-
matrix_pre_proc[predecessor.operation_id][operation.operation_id] = True
113+
if operation.scheduling_information == {}: # If the operation has not been scheduled
114+
for machine_id, duration in operation.processing_times.items():
115+
matrix_proc_time[operation.operation_id][machine_id] = duration
116+
matrix_ope_ma_adj[operation.operation_id][machine_id] = 1
117+
else:
118+
scheduled_machine = operation.scheduling_information['machine_id']
119+
matrix_proc_time[operation.operation_id][scheduled_machine] = operation.scheduling_information['processing_time']
120+
matrix_ope_ma_adj[operation.operation_id][scheduled_machine] = 1
121+
for predecessor in operation.predecessors:
122+
predecessor: Operation
123+
matrix_pre_proc[predecessor.operation_id][operation.operation_id] = True
90124

91125
return matrix_proc_time, matrix_ope_ma_adj, matrix_pre_proc, matrix_pre_proc.t(), opes_appertain, num_ope_biases, \
92126
nums_ope, matrix_cal_cumul

0 commit comments

Comments
 (0)