job-shop-lib 0.3.0__py3-none-any.whl → 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- job_shop_lib/dispatching/dispatcher.py +220 -59
- job_shop_lib/dispatching/feature_observers/__init__.py +28 -0
- job_shop_lib/dispatching/feature_observers/composite_feature_observer.py +87 -0
- job_shop_lib/dispatching/feature_observers/duration_observer.py +95 -0
- job_shop_lib/dispatching/feature_observers/earliest_start_time_observer.py +156 -0
- job_shop_lib/dispatching/feature_observers/factory.py +58 -0
- job_shop_lib/dispatching/feature_observers/feature_observer.py +113 -0
- job_shop_lib/dispatching/feature_observers/is_completed_observer.py +98 -0
- job_shop_lib/dispatching/feature_observers/is_ready_observer.py +40 -0
- job_shop_lib/dispatching/feature_observers/is_scheduled_observer.py +34 -0
- job_shop_lib/dispatching/feature_observers/position_in_job_observer.py +39 -0
- job_shop_lib/dispatching/feature_observers/remaining_operations_observer.py +54 -0
- job_shop_lib/graphs/build_disjunctive_graph.py +20 -0
- job_shop_lib/job_shop_instance.py +101 -0
- job_shop_lib/operation.py +4 -4
- job_shop_lib/schedule.py +96 -1
- job_shop_lib/scheduled_operation.py +17 -5
- job_shop_lib/visualization/create_gif.py +47 -38
- job_shop_lib/visualization/gantt_chart.py +1 -1
- {job_shop_lib-0.3.0.dist-info → job_shop_lib-0.5.0.dist-info}/METADATA +9 -5
- {job_shop_lib-0.3.0.dist-info → job_shop_lib-0.5.0.dist-info}/RECORD +23 -12
- {job_shop_lib-0.3.0.dist-info → job_shop_lib-0.5.0.dist-info}/LICENSE +0 -0
- {job_shop_lib-0.3.0.dist-info → job_shop_lib-0.5.0.dist-info}/WHEEL +0 -0
@@ -6,6 +6,8 @@ import os
|
|
6
6
|
import functools
|
7
7
|
from typing import Any
|
8
8
|
|
9
|
+
import numpy as np
|
10
|
+
|
9
11
|
from job_shop_lib import Operation
|
10
12
|
|
11
13
|
|
@@ -264,6 +266,58 @@ class JobShopInstance:
|
|
264
266
|
[operation.machine_id for operation in job] for job in self.jobs
|
265
267
|
]
|
266
268
|
|
269
|
+
@functools.cached_property
|
270
|
+
def durations_matrix_array(self) -> np.ndarray:
|
271
|
+
"""Returns the duration matrix of the instance as a numpy array.
|
272
|
+
|
273
|
+
The returned array has shape (num_jobs, max_num_operations_per_job).
|
274
|
+
Non-existing operations are filled with np.nan.
|
275
|
+
|
276
|
+
Example:
|
277
|
+
>>> jobs = [[Operation(0, 2), Operation(1, 3)], [Operation(0, 4)]]
|
278
|
+
>>> instance = JobShopInstance(jobs)
|
279
|
+
>>> instance.durations_matrix_array
|
280
|
+
array([[ 2., 2.],
|
281
|
+
[ 4., nan]], dtype=float32)
|
282
|
+
"""
|
283
|
+
duration_matrix = self.durations_matrix
|
284
|
+
return self._fill_matrix_with_nans_2d(duration_matrix)
|
285
|
+
|
286
|
+
@functools.cached_property
|
287
|
+
def machines_matrix_array(self) -> np.ndarray:
|
288
|
+
"""Returns the machines matrix of the instance as a numpy array.
|
289
|
+
|
290
|
+
The returned array has shape (num_jobs, max_num_operations_per_job,
|
291
|
+
max_num_machines_per_operation). Non-existing machines are filled with
|
292
|
+
np.nan.
|
293
|
+
|
294
|
+
Example:
|
295
|
+
>>> jobs = [
|
296
|
+
... [Operation(machines=[0, 1], 2), Operation(machines=1, 3)],
|
297
|
+
... [Operation(machines=0, 6)],
|
298
|
+
... ]
|
299
|
+
>>> instance = JobShopInstance(jobs)
|
300
|
+
>>> instance.machines_matrix_array
|
301
|
+
array([[[ 0., 1.],
|
302
|
+
[ 1., nan]],
|
303
|
+
[[ 0., nan],
|
304
|
+
[nan, nan]]], dtype=float32)
|
305
|
+
"""
|
306
|
+
|
307
|
+
machines_matrix = self.machines_matrix
|
308
|
+
if self.is_flexible:
|
309
|
+
# False positive from mypy, the type of machines_matrix is
|
310
|
+
# list[list[list[int]]] here
|
311
|
+
return self._fill_matrix_with_nans_3d(
|
312
|
+
machines_matrix # type: ignore[arg-type]
|
313
|
+
)
|
314
|
+
|
315
|
+
# False positive from mypy, the type of machines_matrix is
|
316
|
+
# list[list[int]] here
|
317
|
+
return self._fill_matrix_with_nans_2d(
|
318
|
+
machines_matrix # type: ignore[arg-type]
|
319
|
+
)
|
320
|
+
|
267
321
|
@functools.cached_property
|
268
322
|
def operations_by_machine(self) -> list[list[Operation]]:
|
269
323
|
"""Returns a list of lists of operations.
|
@@ -353,3 +407,50 @@ class JobShopInstance:
|
|
353
407
|
def total_duration(self) -> int:
|
354
408
|
"""Returns the sum of the durations of all operations in all jobs."""
|
355
409
|
return sum(self.job_durations)
|
410
|
+
|
411
|
+
@staticmethod
|
412
|
+
def _fill_matrix_with_nans_2d(matrix: list[list[int]]) -> np.ndarray:
|
413
|
+
"""Fills a matrix with np.nan values.
|
414
|
+
|
415
|
+
Args:
|
416
|
+
matrix:
|
417
|
+
A list of lists of integers.
|
418
|
+
|
419
|
+
Returns:
|
420
|
+
A numpy array with the same shape as the input matrix, filled with
|
421
|
+
np.nan values.
|
422
|
+
"""
|
423
|
+
max_length = max(len(row) for row in matrix)
|
424
|
+
squared_matrix = np.full(
|
425
|
+
(len(matrix), max_length), np.nan, dtype=np.float32
|
426
|
+
)
|
427
|
+
for i, row in enumerate(matrix):
|
428
|
+
squared_matrix[i, : len(row)] = row
|
429
|
+
return squared_matrix
|
430
|
+
|
431
|
+
@staticmethod
|
432
|
+
def _fill_matrix_with_nans_3d(matrix: list[list[list[int]]]) -> np.ndarray:
|
433
|
+
"""Fills a 3D matrix with np.nan values.
|
434
|
+
|
435
|
+
Args:
|
436
|
+
matrix:
|
437
|
+
A list of lists of lists of integers.
|
438
|
+
|
439
|
+
Returns:
|
440
|
+
A numpy array with the same shape as the input matrix, filled with
|
441
|
+
np.nan values.
|
442
|
+
"""
|
443
|
+
max_length = max(len(row) for row in matrix)
|
444
|
+
max_inner_length = len(matrix[0][0])
|
445
|
+
for row in matrix:
|
446
|
+
for inner_row in row:
|
447
|
+
max_inner_length = max(max_inner_length, len(inner_row))
|
448
|
+
squared_matrix = np.full(
|
449
|
+
(len(matrix), max_length, max_inner_length),
|
450
|
+
np.nan,
|
451
|
+
dtype=np.float32,
|
452
|
+
)
|
453
|
+
for i, row in enumerate(matrix):
|
454
|
+
for j, inner_row in enumerate(row):
|
455
|
+
squared_matrix[i, j, : len(inner_row)] = inner_row
|
456
|
+
return squared_matrix
|
job_shop_lib/operation.py
CHANGED
@@ -107,10 +107,10 @@ class Operation:
|
|
107
107
|
def __hash__(self) -> int:
|
108
108
|
return hash(self.operation_id)
|
109
109
|
|
110
|
-
def __eq__(self,
|
111
|
-
if isinstance(
|
112
|
-
return
|
113
|
-
return
|
110
|
+
def __eq__(self, value: object) -> bool:
|
111
|
+
if not isinstance(value, Operation):
|
112
|
+
return False
|
113
|
+
return self.__slots__ == value.__slots__
|
114
114
|
|
115
115
|
def __repr__(self) -> str:
|
116
116
|
machines = (
|
job_shop_lib/schedule.py
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
"""Home of the `Schedule` class."""
|
2
2
|
|
3
|
-
from
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
from typing import Any
|
6
|
+
from collections import deque
|
7
|
+
|
8
|
+
from job_shop_lib import ScheduledOperation, JobShopInstance, JobShopLibError
|
4
9
|
|
5
10
|
|
6
11
|
class Schedule:
|
@@ -71,6 +76,96 @@ class Schedule:
|
|
71
76
|
"""Returns the number of operations that have been scheduled."""
|
72
77
|
return sum(len(machine_schedule) for machine_schedule in self.schedule)
|
73
78
|
|
79
|
+
def to_dict(self) -> dict:
|
80
|
+
"""Returns a dictionary representation of the schedule.
|
81
|
+
|
82
|
+
This representation is useful for saving the instance to a JSON file.
|
83
|
+
|
84
|
+
Returns:
|
85
|
+
A dictionary representation of the schedule with the following
|
86
|
+
keys:
|
87
|
+
- "instance": A dictionary representation of the instance.
|
88
|
+
- "job_sequences": A list of lists of job ids. Each list of job
|
89
|
+
ids represents the order of operations on the machine. The
|
90
|
+
machine that the list corresponds to is determined by the
|
91
|
+
index of the list.
|
92
|
+
- "metadata": A dictionary with additional information about
|
93
|
+
the schedule.
|
94
|
+
"""
|
95
|
+
job_sequences: list[list[int]] = []
|
96
|
+
for machine_schedule in self.schedule:
|
97
|
+
job_sequences.append(
|
98
|
+
[operation.job_id for operation in machine_schedule]
|
99
|
+
)
|
100
|
+
|
101
|
+
return {
|
102
|
+
"instance": self.instance.to_dict(),
|
103
|
+
"job_sequences": job_sequences,
|
104
|
+
"metadata": self.metadata,
|
105
|
+
}
|
106
|
+
|
107
|
+
@staticmethod
|
108
|
+
def from_dict(
|
109
|
+
instance: dict[str, Any] | JobShopInstance,
|
110
|
+
job_sequences: list[list[int]],
|
111
|
+
metadata: dict[str, Any] | None = None,
|
112
|
+
) -> Schedule:
|
113
|
+
"""Creates a schedule from a dictionary representation."""
|
114
|
+
if isinstance(instance, dict):
|
115
|
+
instance = JobShopInstance.from_matrices(**instance)
|
116
|
+
schedule = Schedule.from_job_sequences(instance, job_sequences)
|
117
|
+
schedule.metadata = metadata if metadata is not None else {}
|
118
|
+
return schedule
|
119
|
+
|
120
|
+
@staticmethod
|
121
|
+
def from_job_sequences(
|
122
|
+
instance: JobShopInstance,
|
123
|
+
job_sequences: list[list[int]],
|
124
|
+
) -> Schedule:
|
125
|
+
"""Creates an active schedule from a list of job sequences.
|
126
|
+
|
127
|
+
An active schedule is the optimal schedule for the given job sequences.
|
128
|
+
In other words, it is not possible to construct another schedule,
|
129
|
+
through changes in the order of processing on the machines, with at
|
130
|
+
least one operation finishing earlier and no operation finishing later.
|
131
|
+
|
132
|
+
Args:
|
133
|
+
instance:
|
134
|
+
The `JobShopInstance` object that the schedule is for.
|
135
|
+
job_sequences:
|
136
|
+
A list of lists of job ids. Each list of job ids represents the
|
137
|
+
order of operations on the machine. The machine that the list
|
138
|
+
corresponds to is determined by the index of the list.
|
139
|
+
|
140
|
+
Returns:
|
141
|
+
A `Schedule` object with the given job sequences.
|
142
|
+
"""
|
143
|
+
from job_shop_lib.dispatching import Dispatcher
|
144
|
+
|
145
|
+
dispatcher = Dispatcher(instance)
|
146
|
+
dispatcher.reset()
|
147
|
+
raw_solution_deques = [deque(job_ids) for job_ids in job_sequences]
|
148
|
+
|
149
|
+
while not dispatcher.schedule.is_complete():
|
150
|
+
at_least_one_operation_scheduled = False
|
151
|
+
for machine_id, job_ids in enumerate(raw_solution_deques):
|
152
|
+
if not job_ids:
|
153
|
+
continue
|
154
|
+
job_id = job_ids[0]
|
155
|
+
operation_index = dispatcher.job_next_operation_index[job_id]
|
156
|
+
operation = instance.jobs[job_id][operation_index]
|
157
|
+
is_ready = dispatcher.is_operation_ready(operation)
|
158
|
+
if is_ready and machine_id in operation.machines:
|
159
|
+
dispatcher.dispatch(operation, machine_id)
|
160
|
+
job_ids.popleft()
|
161
|
+
at_least_one_operation_scheduled = True
|
162
|
+
|
163
|
+
if not at_least_one_operation_scheduled:
|
164
|
+
raise JobShopLibError(
|
165
|
+
"Invalid job sequences. No valid operation to schedule."
|
166
|
+
)
|
167
|
+
return dispatcher.schedule
|
168
|
+
|
74
169
|
def reset(self):
|
75
170
|
"""Resets the schedule to an empty state."""
|
76
171
|
self.schedule = [[] for _ in range(self.instance.num_machines)]
|
@@ -1,6 +1,8 @@
|
|
1
1
|
"""Home of the `ScheduledOperation` class."""
|
2
2
|
|
3
|
-
from
|
3
|
+
from warnings import warn
|
4
|
+
|
5
|
+
from job_shop_lib import Operation, JobShopLibError
|
4
6
|
|
5
7
|
|
6
8
|
class ScheduledOperation:
|
@@ -47,7 +49,7 @@ class ScheduledOperation:
|
|
47
49
|
@machine_id.setter
|
48
50
|
def machine_id(self, value: int):
|
49
51
|
if value not in self.operation.machines:
|
50
|
-
raise
|
52
|
+
raise JobShopLibError(
|
51
53
|
f"Operation cannot be scheduled on machine {value}. "
|
52
54
|
f"Valid machines are {self.operation.machines}."
|
53
55
|
)
|
@@ -62,18 +64,28 @@ class ScheduledOperation:
|
|
62
64
|
"""
|
63
65
|
|
64
66
|
if self.operation.job_id is None:
|
65
|
-
raise
|
67
|
+
raise JobShopLibError("Operation has no job_id.")
|
66
68
|
return self.operation.job_id
|
67
69
|
|
68
70
|
@property
|
69
71
|
def position(self) -> int:
|
72
|
+
"""Deprecated. Use `position_in_job` instead."""
|
73
|
+
warn(
|
74
|
+
"The `position` attribute is deprecated. Use `position_in_job` "
|
75
|
+
"instead. It will be removed in version 1.0.0.",
|
76
|
+
DeprecationWarning,
|
77
|
+
)
|
78
|
+
return self.position_in_job
|
79
|
+
|
80
|
+
@property
|
81
|
+
def position_in_job(self) -> int:
|
70
82
|
"""Returns the position (starting at zero) of the operation in the job.
|
71
83
|
|
72
84
|
Raises:
|
73
85
|
ValueError: If the operation has no position_in_job.
|
74
86
|
"""
|
75
87
|
if self.operation.position_in_job is None:
|
76
|
-
raise
|
88
|
+
raise JobShopLibError("Operation has no position.")
|
77
89
|
return self.operation.position_in_job
|
78
90
|
|
79
91
|
@property
|
@@ -91,7 +103,7 @@ class ScheduledOperation:
|
|
91
103
|
if not isinstance(value, ScheduledOperation):
|
92
104
|
return False
|
93
105
|
return (
|
94
|
-
self.operation
|
106
|
+
self.operation == value.operation
|
95
107
|
and self.start_time == value.start_time
|
96
108
|
and self.machine_id == value.machine_id
|
97
109
|
)
|
@@ -27,7 +27,8 @@ def create_gif(
|
|
27
27
|
instance: JobShopInstance,
|
28
28
|
solver: DispatchingRuleSolver,
|
29
29
|
plot_function: (
|
30
|
-
Callable[[Schedule, int, list[Operation] | None
|
30
|
+
Callable[[Schedule, int, list[Operation] | None, int | None], Figure]
|
31
|
+
| None
|
31
32
|
) = None,
|
32
33
|
fps: int = 1,
|
33
34
|
remove_frames: bool = True,
|
@@ -80,50 +81,59 @@ def plot_gantt_chart_wrapper(
|
|
80
81
|
title: str | None = None,
|
81
82
|
cmap: str = "viridis",
|
82
83
|
show_available_operations: bool = False,
|
83
|
-
) -> Callable[[Schedule, int, list[Operation] | None], Figure]:
|
84
|
+
) -> Callable[[Schedule, int, list[Operation] | None, int | None], Figure]:
|
84
85
|
"""Returns a function that plots a Gantt chart for an unfinished schedule.
|
85
86
|
|
86
87
|
Args:
|
87
88
|
title: The title of the Gantt chart.
|
88
89
|
cmap: The name of the colormap to use.
|
90
|
+
show_available_operations:
|
91
|
+
Whether to show the available operations in the Gantt chart.
|
89
92
|
|
90
93
|
Returns:
|
91
94
|
A function that plots a Gantt chart for a schedule. The function takes
|
92
|
-
|
93
|
-
|
95
|
+
the following arguments:
|
96
|
+
- schedule: The schedule to plot.
|
97
|
+
- makespan: The makespan of the schedule.
|
98
|
+
- available_operations: A list of available operations. If None,
|
99
|
+
the available operations are not shown.
|
100
|
+
- current_time: The current time in the schedule. If provided, a
|
101
|
+
red vertical line is plotted at this time.
|
94
102
|
"""
|
95
103
|
|
96
104
|
def plot_function(
|
97
105
|
schedule: Schedule,
|
98
106
|
makespan: int,
|
99
107
|
available_operations: list | None = None,
|
108
|
+
current_time: int | None = None,
|
100
109
|
) -> Figure:
|
101
110
|
fig, ax = plot_gantt_chart(
|
102
111
|
schedule, title=title, cmap_name=cmap, xlim=makespan
|
103
112
|
)
|
104
113
|
|
105
|
-
if
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
114
|
+
if show_available_operations and available_operations is not None:
|
115
|
+
|
116
|
+
operations_text = "\n".join(
|
117
|
+
str(operation) for operation in available_operations
|
118
|
+
)
|
119
|
+
text = f"Available operations:\n{operations_text}"
|
120
|
+
# Print the available operations at the bottom right corner
|
121
|
+
# of the Gantt chart
|
122
|
+
fig.text(
|
123
|
+
1.25,
|
124
|
+
0.05,
|
125
|
+
text,
|
126
|
+
ha="right",
|
127
|
+
va="bottom",
|
128
|
+
transform=ax.transAxes,
|
129
|
+
bbox={
|
130
|
+
"facecolor": "white",
|
131
|
+
"alpha": 0.5,
|
132
|
+
"boxstyle": "round,pad=0.5",
|
133
|
+
},
|
134
|
+
)
|
135
|
+
if current_time is not None:
|
136
|
+
ax.axvline(current_time, color="red", linestyle="--")
|
127
137
|
return fig
|
128
138
|
|
129
139
|
return plot_function
|
@@ -133,7 +143,9 @@ def create_gantt_chart_frames(
|
|
133
143
|
frames_dir: str,
|
134
144
|
instance: JobShopInstance,
|
135
145
|
solver: DispatchingRuleSolver,
|
136
|
-
plot_function: Callable[
|
146
|
+
plot_function: Callable[
|
147
|
+
[Schedule, int, list[Operation] | None, int | None], Figure
|
148
|
+
],
|
137
149
|
plot_current_time: bool = True,
|
138
150
|
) -> None:
|
139
151
|
"""Creates frames of the Gantt chart for the schedule being built.
|
@@ -150,7 +162,8 @@ def create_gantt_chart_frames(
|
|
150
162
|
should take a `Schedule` object and the makespan of the schedule as
|
151
163
|
input and return a `Figure` object.
|
152
164
|
plot_current_time:
|
153
|
-
Whether to plot a vertical line at the current time.
|
165
|
+
Whether to plot a vertical line at the current time.
|
166
|
+
"""
|
154
167
|
dispatcher = Dispatcher(instance, pruning_function=solver.pruning_function)
|
155
168
|
history_tracker = HistoryTracker(dispatcher)
|
156
169
|
makespan = solver.solve(instance, dispatcher).makespan()
|
@@ -160,23 +173,19 @@ def create_gantt_chart_frames(
|
|
160
173
|
dispatcher.dispatch(
|
161
174
|
scheduled_operation.operation, scheduled_operation.machine_id
|
162
175
|
)
|
176
|
+
current_time = (
|
177
|
+
None if not plot_current_time else dispatcher.current_time()
|
178
|
+
)
|
163
179
|
fig = plot_function(
|
164
180
|
dispatcher.schedule,
|
165
181
|
makespan,
|
166
182
|
dispatcher.available_operations(),
|
183
|
+
current_time,
|
167
184
|
)
|
168
|
-
|
169
|
-
None if not plot_current_time else dispatcher.current_time()
|
170
|
-
)
|
171
|
-
_save_frame(fig, frames_dir, i, current_time)
|
172
|
-
|
185
|
+
_save_frame(fig, frames_dir, i)
|
173
186
|
|
174
|
-
def _save_frame(
|
175
|
-
figure: Figure, frames_dir: str, number: int, current_time: int | None
|
176
|
-
) -> None:
|
177
|
-
if current_time is not None:
|
178
|
-
figure.gca().axvline(current_time, color="red", linestyle="--")
|
179
187
|
|
188
|
+
def _save_frame(figure: Figure, frames_dir: str, number: int) -> None:
|
180
189
|
figure.savefig(f"{frames_dir}/frame_{number:02d}.png", bbox_inches="tight")
|
181
190
|
plt.close(figure)
|
182
191
|
|
@@ -65,7 +65,7 @@ def _plot_machine_schedules(
|
|
65
65
|
) -> dict[int, Patch]:
|
66
66
|
"""Plots the schedules for each machine."""
|
67
67
|
max_job_id = schedule.instance.num_jobs - 1
|
68
|
-
cmap = plt.
|
68
|
+
cmap = plt.get_cmap(cmap_name, max_job_id + 1)
|
69
69
|
norm = Normalize(vmin=0, vmax=max_job_id)
|
70
70
|
legend_handles = {}
|
71
71
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: job-shop-lib
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.5.0
|
4
4
|
Summary: An easy-to-use and modular Python library for the Job Shop Scheduling Problem (JSSP)
|
5
5
|
License: MIT
|
6
6
|
Author: Pabloo22
|
@@ -15,7 +15,8 @@ Provides-Extra: pygraphviz
|
|
15
15
|
Requires-Dist: imageio (>=2,<3)
|
16
16
|
Requires-Dist: matplotlib (>=3,<4)
|
17
17
|
Requires-Dist: networkx (>=3,<4)
|
18
|
-
Requires-Dist:
|
18
|
+
Requires-Dist: numpy (>=1.26.4,<2.0.0)
|
19
|
+
Requires-Dist: ortools (>=9.9,<9.10)
|
19
20
|
Requires-Dist: pyarrow (>=15.0.0,<16.0.0)
|
20
21
|
Requires-Dist: pygraphviz (>=1.12,<2.0) ; extra == "pygraphviz"
|
21
22
|
Description-Content-Type: text/markdown
|
@@ -83,7 +84,7 @@ ft06 = load_benchmark_instance("ft06")
|
|
83
84
|
```
|
84
85
|
|
85
86
|
The module `benchmarks` contains functions to load the instances from the file and return them as `JobShopInstance` objects without having to download them
|
86
|
-
manually.
|
87
|
+
manually.
|
87
88
|
|
88
89
|
The contributions to this benchmark dataset are as follows:
|
89
90
|
|
@@ -171,13 +172,15 @@ class DispatchingRule(str, Enum):
|
|
171
172
|
We can visualize the solution with a `DispatchingRuleSolver` as a gif:
|
172
173
|
|
173
174
|
```python
|
174
|
-
from job_shop_lib.visualization import create_gif,
|
175
|
+
from job_shop_lib.visualization import create_gif, plot_gantt_chart_wrapper
|
175
176
|
from job_shop_lib.dispatching import DispatchingRuleSolver, DispatchingRule
|
176
177
|
|
177
178
|
plt.style.use("ggplot")
|
178
179
|
|
179
180
|
mwkr_solver = DispatchingRuleSolver("most_work_remaining")
|
180
|
-
plot_function =
|
181
|
+
plot_function = plot_gantt_chart_wrapper(
|
182
|
+
title="Solution with Most Work Remaining Rule"
|
183
|
+
)
|
181
184
|
create_gif(
|
182
185
|
gif_path="ft06_optimized.gif",
|
183
186
|
instance=ft06,
|
@@ -350,3 +353,4 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
350
353
|
Journal of Operational Research, vol. 64, no. 2, pp. 278–285, 1993.
|
351
354
|
|
352
355
|
- Park, Junyoung, Sanjar Bakhtiyar, and Jinkyoo Park. "ScheduleNet: Learn to solve multi-agent scheduling problems with reinforcement learning." arXiv preprint arXiv:2106.03051, 2021.
|
356
|
+
|
@@ -6,10 +6,21 @@ job_shop_lib/benchmarking/load_benchmark.py,sha256=CjiSALutgWcfD-SDU6w9WO3udvPVp
|
|
6
6
|
job_shop_lib/cp_sat/__init__.py,sha256=DqrF9IewFMkVB5BhFOHhlJvG6w6BW4ecxBXySunGLoU,97
|
7
7
|
job_shop_lib/cp_sat/ortools_solver.py,sha256=zsISUQy0dQvn7bmUsAQBCe-V92CFskJHkSfngSP4KSg,8130
|
8
8
|
job_shop_lib/dispatching/__init__.py,sha256=xk6NjndZ4-EH5G_fGSEX4LQEXL53TRYn5dKEb5uFggI,1568
|
9
|
-
job_shop_lib/dispatching/dispatcher.py,sha256=
|
9
|
+
job_shop_lib/dispatching/dispatcher.py,sha256=3WdShJtVMP4ZBeoOIegelTXziJLpEjtonAku21yqr20,19299
|
10
10
|
job_shop_lib/dispatching/dispatching_rule_solver.py,sha256=fbNfSclH6Jw1F-QGY1oxAj9wm2hHhJHGnsF2HateXX8,4669
|
11
11
|
job_shop_lib/dispatching/dispatching_rules.py,sha256=SIDkPx_1uTkM0loEqGMqotLBBSaGi1gH0WS85GXrT_I,5557
|
12
12
|
job_shop_lib/dispatching/factories.py,sha256=ldyIbz3QuLuDkrqbgJXV6YoM6AV6CKyHu8z4hXLG2Vo,7267
|
13
|
+
job_shop_lib/dispatching/feature_observers/__init__.py,sha256=j7GXFCKK0mLjGc3M6bw2vePZaLyou_9BUjlHtAMriHc,1023
|
14
|
+
job_shop_lib/dispatching/feature_observers/composite_feature_observer.py,sha256=wLd5yirDekg_pFrBPWIyxKou8htimvKhtDbz0cJ8SKE,3302
|
15
|
+
job_shop_lib/dispatching/feature_observers/duration_observer.py,sha256=EMSfRsWkx94VeOF6v1f2s73hrEQ696vkFoWVVw5tWZ8,3804
|
16
|
+
job_shop_lib/dispatching/feature_observers/earliest_start_time_observer.py,sha256=pKrxaLOPHMnf-eniDhkPWVzzRpKLnpbXAeORqEmsrp0,6296
|
17
|
+
job_shop_lib/dispatching/feature_observers/factory.py,sha256=XF5spNp8T-dnPPnpWQ5dxuG7vrHLf-Wb8MP1tn4_BXA,1941
|
18
|
+
job_shop_lib/dispatching/feature_observers/feature_observer.py,sha256=q2i50LOhgjaQtu_0-3VlSH64uHcf73JddjcGijeKCVk,3646
|
19
|
+
job_shop_lib/dispatching/feature_observers/is_completed_observer.py,sha256=6UCxp-QLXxbw_1nppQJuG_qZZvjtGugYTxQdPTcIcgo,4086
|
20
|
+
job_shop_lib/dispatching/feature_observers/is_ready_observer.py,sha256=km75Pa8vd55grjvr7X-8e0K9vfxVWfms1ea6kAesKLo,1405
|
21
|
+
job_shop_lib/dispatching/feature_observers/is_scheduled_observer.py,sha256=PeLxPVLJX_TP4TG8ViEQFR8WS43wIp6CqyuapM8lIt8,1477
|
22
|
+
job_shop_lib/dispatching/feature_observers/position_in_job_observer.py,sha256=PEC-WwcyeHL8WOACElImDtAAoaf7MroaD9x5QlQddVE,1344
|
23
|
+
job_shop_lib/dispatching/feature_observers/remaining_operations_observer.py,sha256=yVde9gC0cheFNCoPa1lFgGSmzyew-zOGA98nZpBBw1I,1891
|
13
24
|
job_shop_lib/dispatching/history_tracker.py,sha256=3jSh7pKEGiOcEK6bXK8AQJK4NtASxTknRjmHRKenxt8,649
|
14
25
|
job_shop_lib/dispatching/pruning_functions.py,sha256=d94_uBHuESp4NSf_jBk1q8eBCfTPuU9meiL3StiqJiA,4378
|
15
26
|
job_shop_lib/exceptions.py,sha256=0Wla1lK6E2u1o3t2hJj9hUwyoJ-1ebkXd42GdXFAhV0,899
|
@@ -18,20 +29,20 @@ job_shop_lib/generators/basic_generator.py,sha256=pbRDQWC2mnHU0dbc-T8wkdwVeJPlRn
|
|
18
29
|
job_shop_lib/generators/transformations.py,sha256=FI2qHrETATJUrQP3-RYhZAQ5boyEZ0CF2StDbacBej8,5290
|
19
30
|
job_shop_lib/graphs/__init__.py,sha256=mWyF0MypyYfvFhy2F93BJkFIVsxS_0ZqvPuc29B7TJg,1454
|
20
31
|
job_shop_lib/graphs/build_agent_task_graph.py,sha256=ktj-oNLUPmWHfL81EVyaoF4hXClWYfnN7oG2Nn4pOsg,7128
|
21
|
-
job_shop_lib/graphs/build_disjunctive_graph.py,sha256=
|
32
|
+
job_shop_lib/graphs/build_disjunctive_graph.py,sha256=z1jiuTTaWPJZj-vSZdo064quGx4LEDKjtZIb1FieZW4,3705
|
22
33
|
job_shop_lib/graphs/constants.py,sha256=dqPF--okue5sF70Iv-YR14QKFx4pxPwT2dL1Rh5jylM,374
|
23
34
|
job_shop_lib/graphs/job_shop_graph.py,sha256=B0buqcg7US6UvIRWsoY8_FwqzPa_nVjnBu7hPIrygUo,7404
|
24
35
|
job_shop_lib/graphs/node.py,sha256=FrSndtvqgRbN69jIcU6q1TkBh-LOGg8sxxYjDZqCcf4,5613
|
25
|
-
job_shop_lib/job_shop_instance.py,sha256=
|
26
|
-
job_shop_lib/operation.py,sha256=
|
27
|
-
job_shop_lib/schedule.py,sha256=
|
28
|
-
job_shop_lib/scheduled_operation.py,sha256=
|
36
|
+
job_shop_lib/job_shop_instance.py,sha256=awEZ-xKM4yPlD4gE8SdfQdt68CWX_R3IebeVY8ST4bs,16376
|
37
|
+
job_shop_lib/operation.py,sha256=S61x0xgu09JLwrRp7syd1P2psbl0ByGuK_hHoHp4ng8,3916
|
38
|
+
job_shop_lib/schedule.py,sha256=aODGwMv9slFIqOTCz2hF_EIpXhddz8-iAH5gSzGO5G8,10393
|
39
|
+
job_shop_lib/scheduled_operation.py,sha256=qzXzat1dQBbQ-sLyoG1iXbF9eWbdFeZDFjhAFVavHPk,3526
|
29
40
|
job_shop_lib/visualization/__init__.py,sha256=Kxjk3ERYXPAHR72nkD92gFdJltSLA2kxLZrlZzZJS8o,693
|
30
41
|
job_shop_lib/visualization/agent_task_graph.py,sha256=G-c9eiawz6m9sdnDM1r-ZHz6K-gYDIAreHpb6pkYE7w,8284
|
31
|
-
job_shop_lib/visualization/create_gif.py,sha256=
|
42
|
+
job_shop_lib/visualization/create_gif.py,sha256=aUB_2ChyFNo4KuKiQl2ANYmVB5NFcGb7pxKeqr0CVJQ,7186
|
32
43
|
job_shop_lib/visualization/disjunctive_graph.py,sha256=pg4KG9BfQbnBPnXYgbyPGe0AuHSmhYqPeqWYAf_spWQ,5905
|
33
|
-
job_shop_lib/visualization/gantt_chart.py,sha256=
|
34
|
-
job_shop_lib-0.
|
35
|
-
job_shop_lib-0.
|
36
|
-
job_shop_lib-0.
|
37
|
-
job_shop_lib-0.
|
44
|
+
job_shop_lib/visualization/gantt_chart.py,sha256=B9sn4XrEUqgQhRKju-1VUG5R67AZXRu7jbrtA8VcndU,4412
|
45
|
+
job_shop_lib-0.5.0.dist-info/LICENSE,sha256=9mggivMGd5taAu3xbmBway-VQZMBzurBGHofFopvUsQ,1069
|
46
|
+
job_shop_lib-0.5.0.dist-info/METADATA,sha256=a_AtBz1LwmfrEoIVWXepewPFx2Pa1QiFJt857kCGncw,12582
|
47
|
+
job_shop_lib-0.5.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
48
|
+
job_shop_lib-0.5.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|