job-shop-lib 0.3.0__py3-none-any.whl → 0.5.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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
|