job-shop-lib 0.5.1__py3-none-any.whl → 1.0.0a1__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- job_shop_lib/__init__.py +16 -8
- job_shop_lib/{base_solver.py → _base_solver.py} +1 -1
- job_shop_lib/{job_shop_instance.py → _job_shop_instance.py} +9 -4
- job_shop_lib/_operation.py +95 -0
- job_shop_lib/{schedule.py → _schedule.py} +73 -54
- job_shop_lib/{scheduled_operation.py → _scheduled_operation.py} +13 -37
- job_shop_lib/benchmarking/__init__.py +66 -43
- job_shop_lib/benchmarking/_load_benchmark.py +88 -0
- job_shop_lib/constraint_programming/__init__.py +13 -0
- job_shop_lib/{cp_sat/ortools_solver.py → constraint_programming/_ortools_solver.py} +57 -18
- job_shop_lib/dispatching/__init__.py +45 -41
- job_shop_lib/dispatching/{dispatcher.py → _dispatcher.py} +153 -80
- job_shop_lib/dispatching/_dispatcher_observer_config.py +54 -0
- job_shop_lib/dispatching/_factories.py +125 -0
- job_shop_lib/dispatching/{history_tracker.py → _history_observer.py} +4 -6
- job_shop_lib/dispatching/{pruning_functions.py → _ready_operation_filters.py} +6 -35
- job_shop_lib/dispatching/_unscheduled_operations_observer.py +69 -0
- job_shop_lib/dispatching/feature_observers/__init__.py +16 -10
- job_shop_lib/dispatching/feature_observers/{composite_feature_observer.py → _composite_feature_observer.py} +84 -2
- job_shop_lib/dispatching/feature_observers/{duration_observer.py → _duration_observer.py} +6 -17
- job_shop_lib/dispatching/feature_observers/{earliest_start_time_observer.py → _earliest_start_time_observer.py} +114 -35
- job_shop_lib/dispatching/feature_observers/{factory.py → _factory.py} +31 -5
- job_shop_lib/dispatching/feature_observers/{feature_observer.py → _feature_observer.py} +59 -16
- job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +97 -0
- job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +33 -0
- job_shop_lib/dispatching/feature_observers/{position_in_job_observer.py → _position_in_job_observer.py} +1 -8
- job_shop_lib/dispatching/feature_observers/{remaining_operations_observer.py → _remaining_operations_observer.py} +8 -26
- job_shop_lib/dispatching/rules/__init__.py +51 -0
- job_shop_lib/dispatching/rules/_dispatching_rule_factory.py +82 -0
- job_shop_lib/dispatching/{dispatching_rule_solver.py → rules/_dispatching_rule_solver.py} +44 -15
- job_shop_lib/dispatching/{dispatching_rules.py → rules/_dispatching_rules_functions.py} +74 -21
- job_shop_lib/dispatching/rules/_machine_chooser_factory.py +69 -0
- job_shop_lib/dispatching/rules/_utils.py +127 -0
- job_shop_lib/exceptions.py +18 -0
- job_shop_lib/generation/__init__.py +2 -2
- job_shop_lib/generation/{general_instance_generator.py → _general_instance_generator.py} +26 -7
- job_shop_lib/generation/{instance_generator.py → _instance_generator.py} +13 -3
- job_shop_lib/graphs/__init__.py +17 -6
- job_shop_lib/graphs/{job_shop_graph.py → _job_shop_graph.py} +81 -2
- job_shop_lib/graphs/{node.py → _node.py} +18 -12
- job_shop_lib/graphs/graph_updaters/__init__.py +13 -0
- job_shop_lib/graphs/graph_updaters/_graph_updater.py +59 -0
- job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +154 -0
- job_shop_lib/graphs/graph_updaters/_utils.py +25 -0
- job_shop_lib/reinforcement_learning/__init__.py +41 -0
- job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +366 -0
- job_shop_lib/reinforcement_learning/_reward_observers.py +85 -0
- job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +337 -0
- job_shop_lib/reinforcement_learning/_types_and_constants.py +61 -0
- job_shop_lib/reinforcement_learning/_utils.py +96 -0
- job_shop_lib/visualization/__init__.py +20 -4
- job_shop_lib/visualization/{agent_task_graph.py → _agent_task_graph.py} +28 -9
- job_shop_lib/visualization/_gantt_chart_creator.py +219 -0
- job_shop_lib/visualization/_gantt_chart_video_and_gif_creation.py +388 -0
- {job_shop_lib-0.5.1.dist-info → job_shop_lib-1.0.0a1.dist-info}/METADATA +68 -44
- job_shop_lib-1.0.0a1.dist-info/RECORD +66 -0
- job_shop_lib/benchmarking/load_benchmark.py +0 -142
- job_shop_lib/cp_sat/__init__.py +0 -5
- job_shop_lib/dispatching/factories.py +0 -206
- job_shop_lib/dispatching/feature_observers/is_completed_observer.py +0 -98
- job_shop_lib/dispatching/feature_observers/is_ready_observer.py +0 -40
- job_shop_lib/generators/__init__.py +0 -8
- job_shop_lib/generators/basic_generator.py +0 -200
- job_shop_lib/generators/transformations.py +0 -164
- job_shop_lib/operation.py +0 -122
- job_shop_lib/visualization/create_gif.py +0 -209
- job_shop_lib-0.5.1.dist-info/RECORD +0 -52
- /job_shop_lib/dispatching/feature_observers/{is_scheduled_observer.py → _is_scheduled_observer.py} +0 -0
- /job_shop_lib/generation/{transformations.py → _transformations.py} +0 -0
- /job_shop_lib/graphs/{build_agent_task_graph.py → _build_agent_task_graph.py} +0 -0
- /job_shop_lib/graphs/{build_disjunctive_graph.py → _build_disjunctive_graph.py} +0 -0
- /job_shop_lib/graphs/{constants.py → _constants.py} +0 -0
- /job_shop_lib/visualization/{disjunctive_graph.py → _disjunctive_graph.py} +0 -0
- /job_shop_lib/visualization/{gantt_chart.py → _gantt_chart.py} +0 -0
- {job_shop_lib-0.5.1.dist-info → job_shop_lib-1.0.0a1.dist-info}/LICENSE +0 -0
- {job_shop_lib-0.5.1.dist-info → job_shop_lib-1.0.0a1.dist-info}/WHEEL +0 -0
job_shop_lib/__init__.py
CHANGED
@@ -1,12 +1,22 @@
|
|
1
1
|
"""Contains the main data structures and base classes.
|
2
|
+
|
3
|
+
.. autosummary::
|
4
|
+
:nosignatures:
|
5
|
+
|
6
|
+
Operation
|
7
|
+
JobShopInstance
|
8
|
+
ScheduledOperation
|
9
|
+
Schedule
|
10
|
+
Solver
|
11
|
+
BaseSolver
|
12
|
+
|
2
13
|
"""
|
3
14
|
|
4
|
-
from job_shop_lib.
|
5
|
-
from job_shop_lib.
|
6
|
-
from job_shop_lib.
|
7
|
-
from job_shop_lib.
|
8
|
-
from job_shop_lib.
|
9
|
-
from job_shop_lib.base_solver import BaseSolver, Solver
|
15
|
+
from job_shop_lib._operation import Operation
|
16
|
+
from job_shop_lib._job_shop_instance import JobShopInstance
|
17
|
+
from job_shop_lib._scheduled_operation import ScheduledOperation
|
18
|
+
from job_shop_lib._schedule import Schedule
|
19
|
+
from job_shop_lib._base_solver import BaseSolver, Solver
|
10
20
|
|
11
21
|
|
12
22
|
__all__ = [
|
@@ -16,6 +26,4 @@ __all__ = [
|
|
16
26
|
"Schedule",
|
17
27
|
"Solver",
|
18
28
|
"BaseSolver",
|
19
|
-
"JobShopLibError",
|
20
|
-
"NoSolutionFoundError",
|
21
29
|
]
|
@@ -33,5 +33,5 @@ class BaseSolver(abc.ABC):
|
|
33
33
|
schedule = self.solve(instance)
|
34
34
|
elapsed_time = time_start - time.perf_counter()
|
35
35
|
schedule.metadata["elapsed_time"] = elapsed_time
|
36
|
-
schedule.metadata["solved_by"] =
|
36
|
+
schedule.metadata["solved_by"] = self.__class__.__name__
|
37
37
|
return schedule
|
@@ -7,6 +7,7 @@ import functools
|
|
7
7
|
from typing import Any
|
8
8
|
|
9
9
|
import numpy as np
|
10
|
+
from numpy.typing import NDArray
|
10
11
|
|
11
12
|
from job_shop_lib import Operation
|
12
13
|
|
@@ -267,7 +268,7 @@ class JobShopInstance:
|
|
267
268
|
]
|
268
269
|
|
269
270
|
@functools.cached_property
|
270
|
-
def durations_matrix_array(self) -> np.
|
271
|
+
def durations_matrix_array(self) -> NDArray[np.float32]:
|
271
272
|
"""Returns the duration matrix of the instance as a numpy array.
|
272
273
|
|
273
274
|
The returned array has shape (num_jobs, max_num_operations_per_job).
|
@@ -284,7 +285,7 @@ class JobShopInstance:
|
|
284
285
|
return self._fill_matrix_with_nans_2d(duration_matrix)
|
285
286
|
|
286
287
|
@functools.cached_property
|
287
|
-
def machines_matrix_array(self) -> np.
|
288
|
+
def machines_matrix_array(self) -> NDArray[np.float32]:
|
288
289
|
"""Returns the machines matrix of the instance as a numpy array.
|
289
290
|
|
290
291
|
The returned array has shape (num_jobs, max_num_operations_per_job,
|
@@ -409,7 +410,9 @@ class JobShopInstance:
|
|
409
410
|
return sum(self.job_durations)
|
410
411
|
|
411
412
|
@staticmethod
|
412
|
-
def _fill_matrix_with_nans_2d(
|
413
|
+
def _fill_matrix_with_nans_2d(
|
414
|
+
matrix: list[list[int]],
|
415
|
+
) -> NDArray[np.float32]:
|
413
416
|
"""Fills a matrix with np.nan values.
|
414
417
|
|
415
418
|
Args:
|
@@ -429,7 +432,9 @@ class JobShopInstance:
|
|
429
432
|
return squared_matrix
|
430
433
|
|
431
434
|
@staticmethod
|
432
|
-
def _fill_matrix_with_nans_3d(
|
435
|
+
def _fill_matrix_with_nans_3d(
|
436
|
+
matrix: list[list[list[int]]],
|
437
|
+
) -> NDArray[np.float32]:
|
433
438
|
"""Fills a 3D matrix with np.nan values.
|
434
439
|
|
435
440
|
Args:
|
@@ -0,0 +1,95 @@
|
|
1
|
+
"""Home of the `Operation` class."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
from job_shop_lib.exceptions import UninitializedAttributeError
|
6
|
+
|
7
|
+
|
8
|
+
class Operation:
|
9
|
+
"""Stores machine and duration information for a job operation.
|
10
|
+
|
11
|
+
An operation is a task that must be performed on a machine. It is part of a
|
12
|
+
job and has a duration that represents the time it takes to complete the
|
13
|
+
task.
|
14
|
+
|
15
|
+
Tip:
|
16
|
+
To use custom attributes, such as due dates or priorities, subclass
|
17
|
+
this class and add the desired attributes.
|
18
|
+
|
19
|
+
Note:
|
20
|
+
To increase performance, some solvers such as the CP-SAT solver use
|
21
|
+
only integers to represent the operation's attributes. Should a
|
22
|
+
problem involve operations with non-integer durations, it would be
|
23
|
+
necessary to multiply all durations by a sufficiently large integer so
|
24
|
+
that every duration is an integer.
|
25
|
+
|
26
|
+
Attributes:
|
27
|
+
machines: A list of machine ids that can perform the operation.
|
28
|
+
duration: The time it takes to perform the operation.
|
29
|
+
"""
|
30
|
+
|
31
|
+
__slots__ = (
|
32
|
+
"machines",
|
33
|
+
"duration",
|
34
|
+
"job_id",
|
35
|
+
"position_in_job",
|
36
|
+
"operation_id",
|
37
|
+
)
|
38
|
+
|
39
|
+
def __init__(self, machines: int | list[int], duration: int):
|
40
|
+
"""Initializes the object with the given machines and duration.
|
41
|
+
|
42
|
+
Args:
|
43
|
+
machines:
|
44
|
+
A list of machine ids that can perform the operation. If
|
45
|
+
only one machine can perform the operation, it can be passed as
|
46
|
+
an integer.
|
47
|
+
duration:
|
48
|
+
The time it takes to perform the operation.
|
49
|
+
"""
|
50
|
+
self.machines = [machines] if isinstance(machines, int) else machines
|
51
|
+
self.duration = duration
|
52
|
+
|
53
|
+
# Defined outside the class by the JobShopInstance class:
|
54
|
+
self.job_id: int = -1
|
55
|
+
self.position_in_job: int = -1
|
56
|
+
self.operation_id: int = -1
|
57
|
+
|
58
|
+
@property
|
59
|
+
def machine_id(self) -> int:
|
60
|
+
"""Returns the id of the machine associated with the operation.
|
61
|
+
|
62
|
+
Raises:
|
63
|
+
UninitializedAttributeError: If the operation has multiple machines
|
64
|
+
in its list.
|
65
|
+
"""
|
66
|
+
if len(self.machines) > 1:
|
67
|
+
raise UninitializedAttributeError(
|
68
|
+
"Operation has multiple machines."
|
69
|
+
)
|
70
|
+
return self.machines[0]
|
71
|
+
|
72
|
+
def is_initialized(self) -> bool:
|
73
|
+
"""Returns whether the operation has been initialized."""
|
74
|
+
return (
|
75
|
+
self.job_id == -1
|
76
|
+
or self.position_in_job == -1
|
77
|
+
or self.operation_id == -1
|
78
|
+
)
|
79
|
+
|
80
|
+
def __hash__(self) -> int:
|
81
|
+
return hash(self.operation_id)
|
82
|
+
|
83
|
+
def __eq__(self, value: object) -> bool:
|
84
|
+
if not isinstance(value, Operation):
|
85
|
+
return False
|
86
|
+
return self.__slots__ == value.__slots__
|
87
|
+
|
88
|
+
def __repr__(self) -> str:
|
89
|
+
machines = (
|
90
|
+
self.machines[0] if len(self.machines) == 1 else self.machines
|
91
|
+
)
|
92
|
+
return (
|
93
|
+
f"O(m={machines}, d={self.duration}, "
|
94
|
+
f"j={self.job_id}, p={self.position_in_job})"
|
95
|
+
)
|
@@ -5,19 +5,30 @@ from __future__ import annotations
|
|
5
5
|
from typing import Any
|
6
6
|
from collections import deque
|
7
7
|
|
8
|
-
from job_shop_lib import ScheduledOperation, JobShopInstance
|
8
|
+
from job_shop_lib import ScheduledOperation, JobShopInstance
|
9
|
+
from job_shop_lib.exceptions import ValidationError
|
9
10
|
|
10
11
|
|
11
12
|
class Schedule:
|
12
|
-
"""Data structure to store a
|
13
|
+
"""Data structure to store a complete or partial solution for a particular
|
14
|
+
:class:`JobShopInstance`.
|
15
|
+
|
16
|
+
A schedule is a list of lists of :class:`ScheduledOperation` objects. Each
|
17
|
+
list represents the order of operations on a machine.
|
18
|
+
|
19
|
+
The main methods of this class are:
|
20
|
+
|
21
|
+
.. autosummary::
|
22
|
+
:nosignatures:
|
23
|
+
|
24
|
+
makespan
|
25
|
+
is_complete
|
26
|
+
add
|
27
|
+
reset
|
13
28
|
|
14
29
|
Attributes:
|
15
30
|
instance:
|
16
|
-
The
|
17
|
-
schedule:
|
18
|
-
A list of lists of `ScheduledOperation` objects. Each list of
|
19
|
-
`ScheduledOperation` objects represents the order of operations
|
20
|
-
on a machine.
|
31
|
+
The :class:`JobShopInstance` object that the schedule is for.
|
21
32
|
metadata:
|
22
33
|
A dictionary with additional information about the schedule. It
|
23
34
|
can be used to store information about the algorithm that generated
|
@@ -40,12 +51,11 @@ class Schedule:
|
|
40
51
|
|
41
52
|
Args:
|
42
53
|
instance:
|
43
|
-
The
|
54
|
+
The :class:`JobShopInstance` object that the schedule is for.
|
44
55
|
schedule:
|
45
|
-
A list of lists of
|
46
|
-
|
47
|
-
|
48
|
-
an empty schedule.
|
56
|
+
A list of lists of :class:`ScheduledOperation` objects. Each
|
57
|
+
list represents the order of operations on a machine. If
|
58
|
+
not provided, the schedule is initialized as an empty schedule.
|
49
59
|
**metadata:
|
50
60
|
Additional information about the schedule.
|
51
61
|
"""
|
@@ -63,7 +73,8 @@ class Schedule:
|
|
63
73
|
|
64
74
|
@property
|
65
75
|
def schedule(self) -> list[list[ScheduledOperation]]:
|
66
|
-
"""
|
76
|
+
"""A list of lists of :class:`ScheduledOperation` objects. Each list
|
77
|
+
represents the order of operations on a machine."""
|
67
78
|
return self._schedule
|
68
79
|
|
69
80
|
@schedule.setter
|
@@ -73,7 +84,7 @@ class Schedule:
|
|
73
84
|
|
74
85
|
@property
|
75
86
|
def num_scheduled_operations(self) -> int:
|
76
|
-
"""
|
87
|
+
"""The number of operations that have been scheduled so far."""
|
77
88
|
return sum(len(machine_schedule) for machine_schedule in self.schedule)
|
78
89
|
|
79
90
|
def to_dict(self) -> dict:
|
@@ -84,13 +95,14 @@ class Schedule:
|
|
84
95
|
Returns:
|
85
96
|
A dictionary representation of the schedule with the following
|
86
97
|
keys:
|
87
|
-
|
88
|
-
- "
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
98
|
+
|
99
|
+
- **"instance"**: A dictionary representation of the instance.
|
100
|
+
- **"job_sequences"**: A list of lists of job ids. Each list
|
101
|
+
of job ids represents the order of operations on the machine.
|
102
|
+
The machine that the list corresponds to is determined by the
|
103
|
+
index of the list.
|
104
|
+
- **"metadata"**: A dictionary with additional information
|
105
|
+
about the schedule.
|
94
106
|
"""
|
95
107
|
job_sequences: list[list[int]] = []
|
96
108
|
for machine_schedule in self.schedule:
|
@@ -131,14 +143,14 @@ class Schedule:
|
|
131
143
|
|
132
144
|
Args:
|
133
145
|
instance:
|
134
|
-
The
|
146
|
+
The :class:`JobShopInstance` object that the schedule is for.
|
135
147
|
job_sequences:
|
136
148
|
A list of lists of job ids. Each list of job ids represents the
|
137
149
|
order of operations on the machine. The machine that the list
|
138
150
|
corresponds to is determined by the index of the list.
|
139
151
|
|
140
152
|
Returns:
|
141
|
-
A
|
153
|
+
A :class:`Schedule` object with the given job sequences.
|
142
154
|
"""
|
143
155
|
from job_shop_lib.dispatching import Dispatcher
|
144
156
|
|
@@ -161,7 +173,7 @@ class Schedule:
|
|
161
173
|
at_least_one_operation_scheduled = True
|
162
174
|
|
163
175
|
if not at_least_one_operation_scheduled:
|
164
|
-
raise
|
176
|
+
raise ValidationError(
|
165
177
|
"Invalid job sequences. No valid operation to schedule."
|
166
178
|
)
|
167
179
|
return dispatcher.schedule
|
@@ -182,18 +194,19 @@ class Schedule:
|
|
182
194
|
return max_end_time
|
183
195
|
|
184
196
|
def is_complete(self) -> bool:
|
185
|
-
"""Returns True if all operations have been scheduled."""
|
197
|
+
"""Returns ``True`` if all operations have been scheduled."""
|
186
198
|
return self.num_scheduled_operations == self.instance.num_operations
|
187
199
|
|
188
200
|
def add(self, scheduled_operation: ScheduledOperation):
|
189
|
-
"""Adds a new
|
201
|
+
"""Adds a new :class:`ScheduledOperation` to the schedule.
|
190
202
|
|
191
203
|
Args:
|
192
204
|
scheduled_operation:
|
193
|
-
The
|
205
|
+
The :class:`ScheduledOperation` to add to the schedule.
|
194
206
|
|
195
207
|
Raises:
|
196
|
-
|
208
|
+
ValidationError:
|
209
|
+
If the start time of the new operation is before the
|
197
210
|
end time of the last operation on the same machine. In favor of
|
198
211
|
performance, this method does not checks precedence
|
199
212
|
constraints.
|
@@ -212,49 +225,45 @@ class Schedule:
|
|
212
225
|
return
|
213
226
|
|
214
227
|
last_operation = self.schedule[new_operation.machine_id][-1]
|
215
|
-
self.
|
228
|
+
if not self._is_valid_start_time(new_operation, last_operation):
|
229
|
+
raise ValidationError(
|
230
|
+
"Operation cannot be scheduled before the last operation on "
|
231
|
+
"the same machine: end time of last operation "
|
232
|
+
f"({last_operation.end_time}) > start time of new operation "
|
233
|
+
f"({new_operation.start_time})."
|
234
|
+
)
|
216
235
|
|
217
236
|
@staticmethod
|
218
|
-
def
|
237
|
+
def _is_valid_start_time(
|
219
238
|
scheduled_operation: ScheduledOperation,
|
220
239
|
previous_operation: ScheduledOperation,
|
221
240
|
):
|
222
|
-
|
223
|
-
the end time of the last operation on the same machine."""
|
224
|
-
|
225
|
-
if previous_operation.end_time <= scheduled_operation.start_time:
|
226
|
-
return
|
227
|
-
|
228
|
-
raise ValueError(
|
229
|
-
"Operation cannot be scheduled before the last operation on "
|
230
|
-
"the same machine: end time of last operation "
|
231
|
-
f"({previous_operation.end_time}) > start time of new operation "
|
232
|
-
f"({scheduled_operation.start_time})."
|
233
|
-
)
|
241
|
+
return previous_operation.end_time <= scheduled_operation.start_time
|
234
242
|
|
235
243
|
@staticmethod
|
236
244
|
def check_schedule(schedule: list[list[ScheduledOperation]]):
|
237
|
-
"""Checks if a schedule is valid and raises a
|
245
|
+
"""Checks if a schedule is valid and raises a
|
246
|
+
:class:`~exceptions.ValidationError` if it is not.
|
238
247
|
|
239
248
|
A schedule is considered invalid if:
|
240
|
-
- A
|
241
|
-
machine id of the machine schedule (the list of
|
242
|
-
|
243
|
-
- The start time of a
|
244
|
-
of the last operation on the same machine.
|
249
|
+
- A :class:`ScheduledOperation` has a machine id that does not
|
250
|
+
match the machine id of the machine schedule (the list of
|
251
|
+
:class:`ScheduledOperation` objects) that it belongs to.
|
252
|
+
- The start time of a :class:`ScheduledOperation` is before the
|
253
|
+
end time of the last operation on the same machine.
|
245
254
|
|
246
255
|
Args:
|
247
256
|
schedule:
|
248
|
-
The schedule (a list of lists of
|
249
|
-
to check.
|
257
|
+
The schedule (a list of lists of :class:`ScheduledOperation`
|
258
|
+
objects) to check.
|
250
259
|
|
251
260
|
Raises:
|
252
|
-
|
261
|
+
ValidationError: If the schedule is invalid.
|
253
262
|
"""
|
254
263
|
for machine_id, scheduled_operations in enumerate(schedule):
|
255
264
|
for i, scheduled_operation in enumerate(scheduled_operations):
|
256
265
|
if scheduled_operation.machine_id != machine_id:
|
257
|
-
raise
|
266
|
+
raise ValidationError(
|
258
267
|
"The machine id of the scheduled operation "
|
259
268
|
f"({ScheduledOperation.machine_id}) does not match "
|
260
269
|
f"the machine id of the machine schedule ({machine_id}"
|
@@ -264,9 +273,19 @@ class Schedule:
|
|
264
273
|
if i == 0:
|
265
274
|
continue
|
266
275
|
|
267
|
-
Schedule.
|
276
|
+
if not Schedule._is_valid_start_time(
|
268
277
|
scheduled_operation, scheduled_operations[i - 1]
|
269
|
-
)
|
278
|
+
):
|
279
|
+
raise ValidationError(
|
280
|
+
"Invalid schedule. The start time of the new "
|
281
|
+
"operation is before the end time of the last "
|
282
|
+
"operation on the same machine."
|
283
|
+
"End time of last operation: "
|
284
|
+
f"{scheduled_operations[i - 1].end_time}. "
|
285
|
+
f"Start time of new operation: "
|
286
|
+
f"{scheduled_operation.start_time}. At index "
|
287
|
+
f"[{machine_id}][{i}]."
|
288
|
+
)
|
270
289
|
|
271
290
|
def __eq__(self, value: object) -> bool:
|
272
291
|
if not isinstance(value, Schedule):
|
@@ -1,8 +1,7 @@
|
|
1
1
|
"""Home of the `ScheduledOperation` class."""
|
2
2
|
|
3
|
-
from
|
4
|
-
|
5
|
-
from job_shop_lib import Operation, JobShopLibError
|
3
|
+
from job_shop_lib import Operation
|
4
|
+
from job_shop_lib.exceptions import ValidationError
|
6
5
|
|
7
6
|
|
8
7
|
class ScheduledOperation:
|
@@ -10,30 +9,29 @@ class ScheduledOperation:
|
|
10
9
|
|
11
10
|
Attributes:
|
12
11
|
operation:
|
13
|
-
The
|
12
|
+
The :class:`Operation` object that is scheduled.
|
14
13
|
start_time:
|
15
14
|
The time at which the operation is scheduled to start.
|
16
|
-
|
17
|
-
The id of the machine on which the operation is scheduled.
|
15
|
+
|
18
16
|
"""
|
19
17
|
|
20
18
|
__slots__ = ("operation", "start_time", "_machine_id")
|
21
19
|
|
22
20
|
def __init__(self, operation: Operation, start_time: int, machine_id: int):
|
23
|
-
"""Initializes
|
24
|
-
machine id.
|
21
|
+
"""Initializes a new instance of the :class:`ScheduledOperation` class.
|
25
22
|
|
26
23
|
Args:
|
27
24
|
operation:
|
28
|
-
The
|
25
|
+
The :class:`Operation` object that is scheduled.
|
29
26
|
start_time:
|
30
27
|
The time at which the operation is scheduled to start.
|
31
28
|
machine_id:
|
32
29
|
The id of the machine on which the operation is scheduled.
|
33
30
|
|
34
31
|
Raises:
|
35
|
-
|
36
|
-
If the machine_id is not
|
32
|
+
ValidationError:
|
33
|
+
If the given machine_id is not in the list of valid machines
|
34
|
+
for the operation.
|
37
35
|
"""
|
38
36
|
self.operation = operation
|
39
37
|
self.start_time = start_time
|
@@ -49,7 +47,7 @@ class ScheduledOperation:
|
|
49
47
|
@machine_id.setter
|
50
48
|
def machine_id(self, value: int):
|
51
49
|
if value not in self.operation.machines:
|
52
|
-
raise
|
50
|
+
raise ValidationError(
|
53
51
|
f"Operation cannot be scheduled on machine {value}. "
|
54
52
|
f"Valid machines are {self.operation.machines}."
|
55
53
|
)
|
@@ -57,35 +55,13 @@ class ScheduledOperation:
|
|
57
55
|
|
58
56
|
@property
|
59
57
|
def job_id(self) -> int:
|
60
|
-
"""Returns the id of the job that the operation belongs to.
|
61
|
-
|
62
|
-
Raises:
|
63
|
-
ValueError: If the operation has no job_id.
|
64
|
-
"""
|
65
|
-
|
66
|
-
if self.operation.job_id is None:
|
67
|
-
raise JobShopLibError("Operation has no job_id.")
|
58
|
+
"""Returns the id of the job that the operation belongs to."""
|
68
59
|
return self.operation.job_id
|
69
60
|
|
70
|
-
@property
|
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
61
|
@property
|
81
62
|
def position_in_job(self) -> int:
|
82
|
-
"""Returns the position (starting at zero) of the operation in the
|
83
|
-
|
84
|
-
Raises:
|
85
|
-
ValueError: If the operation has no position_in_job.
|
86
|
-
"""
|
87
|
-
if self.operation.position_in_job is None:
|
88
|
-
raise JobShopLibError("Operation has no position.")
|
63
|
+
"""Returns the position (starting at zero) of the operation in the
|
64
|
+
job."""
|
89
65
|
return self.operation.position_in_job
|
90
66
|
|
91
67
|
@property
|
@@ -1,71 +1,94 @@
|
|
1
|
-
"""
|
1
|
+
"""Contains functions to load benchmark instances.
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
.. autosummary::
|
4
|
+
|
5
|
+
load_all_benchmark_instances
|
6
|
+
load_benchmark_instance
|
7
|
+
load_benchmark_json
|
8
|
+
|
9
|
+
You can load a benchmark instance from the library:
|
10
|
+
|
11
|
+
.. code-block:: python
|
12
|
+
|
13
|
+
from job_shop_lib.benchmarking import load_benchmark_instance
|
14
|
+
|
15
|
+
ft06 = load_benchmark_instance("ft06")
|
16
|
+
|
17
|
+
|
18
|
+
This package contains functions to load the instances from the file
|
19
|
+
and return them as :class:`JobShopInstance` objects without having to download
|
20
|
+
them manually.
|
6
21
|
|
7
22
|
The contributions to this benchmark dataset are as follows:
|
8
23
|
|
9
|
-
abz5-9
|
10
|
-
al. (1988).
|
11
|
-
ft06, ft10, ft20: These three instances are attributed to the work of
|
12
|
-
Fisher and Thompson, as detailed in their 1963 work.
|
13
|
-
la01-40: A collection of forty instances, this group was contributed by
|
14
|
-
Lawrence, as referenced in his 1984 report.
|
15
|
-
orb01-10: Ten instances in this category were provided by Applegate and
|
16
|
-
Cook, as seen in their 1991 study.
|
17
|
-
swb01-20: This segment, encompassing twenty instances, was contributed by
|
18
|
-
Storer et al., as per their 1992 article.
|
19
|
-
yn1-4: Yamada and Nakano are credited with the addition of four instances
|
20
|
-
in this group, as found in their 1992 paper.
|
21
|
-
ta01-80: The largest contribution, consisting of eighty instances, was
|
22
|
-
made by Taillard, as documented in his 1993 paper.
|
24
|
+
- ``abz5-9``: by Adams et al. (1988).
|
23
25
|
|
24
|
-
|
26
|
+
- ``ft06``, ``ft10``, ``ft20``: by Fisher and Thompson (1963).
|
27
|
+
|
28
|
+
- ``la01-40``: by Lawrence (1984)
|
25
29
|
|
26
|
-
|
27
|
-
|
30
|
+
- ``orb01-10``: by Applegate and Cook (1991).
|
31
|
+
|
32
|
+
- ``swb01-20``: by Storer et al. (1992).
|
33
|
+
|
34
|
+
- ``yn1-4``: by Yamada and Nakano (1992).
|
35
|
+
|
36
|
+
- ``ta01-80``: by Taillard (1993).
|
37
|
+
|
38
|
+
The metadata from these instances has been updated using data from:
|
39
|
+
https://github.com/thomasWeise/jsspInstancesAndResults
|
28
40
|
|
29
41
|
It includes the following information:
|
30
|
-
- "optimum" (int | None): The optimal makespan for the instance.
|
31
|
-
- "lower_bound" (int): The lower bound for the makespan. If
|
32
|
-
|
33
|
-
- "upper_bound" (int): The upper bound for the makespan. If
|
34
|
-
|
35
|
-
- "reference" (str): The paper or source where the instance was first
|
36
|
-
|
42
|
+
- "optimum" (``int | None``): The optimal makespan for the instance.
|
43
|
+
- "lower_bound" (``int``): The lower bound for the makespan. If
|
44
|
+
optimality is known, it is equal to the optimum.
|
45
|
+
- "upper_bound" (``int``): The upper bound for the makespan. If
|
46
|
+
optimality is known, it is equal to the optimum.
|
47
|
+
- "reference" (``str``): The paper or source where the instance was first
|
48
|
+
introduced.
|
49
|
+
|
50
|
+
.. code-block:: python
|
51
|
+
|
52
|
+
>>> ft06.metadata
|
53
|
+
{'optimum': 55,
|
54
|
+
'upper_bound': 55,
|
55
|
+
'lower_bound': 55,
|
56
|
+
'reference': "J.F. Muth, G.L. Thompson. 'Industrial scheduling.',
|
57
|
+
Englewood Cliffs, NJ, Prentice-Hall, 1963."}
|
37
58
|
|
38
59
|
References:
|
60
|
+
|
39
61
|
- J. Adams, E. Balas, and D. Zawack, "The shifting bottleneck procedure
|
40
|
-
|
41
|
-
|
62
|
+
for job shop scheduling," Management Science, vol. 34, no. 3,
|
63
|
+
pp. 391–401, 1988.
|
42
64
|
|
43
65
|
- J.F. Muth and G.L. Thompson, Industrial scheduling. Englewood Cliffs,
|
44
|
-
|
66
|
+
NJ: Prentice-Hall, 1963.
|
45
67
|
|
46
68
|
- S. Lawrence, "Resource constrained project scheduling: An experimental
|
47
|
-
|
48
|
-
|
49
|
-
|
69
|
+
investigation of heuristic scheduling techniques (Supplement),"
|
70
|
+
Carnegie-Mellon University, Graduate School of Industrial
|
71
|
+
Administration, Pittsburgh, Pennsylvania, 1984.
|
50
72
|
|
51
73
|
- D. Applegate and W. Cook, "A computational study of job-shop
|
52
|
-
|
53
|
-
|
74
|
+
scheduling," ORSA Journal on Computer, vol. 3, no. 2, pp. 149–156,
|
75
|
+
1991.
|
54
76
|
|
55
77
|
- R.H. Storer, S.D. Wu, and R. Vaccari, "New search spaces for
|
56
|
-
|
57
|
-
|
78
|
+
sequencing problems with applications to job-shop scheduling,"
|
79
|
+
Management Science, vol. 38, no. 10, pp. 1495–1509, 1992.
|
58
80
|
|
59
81
|
- T. Yamada and R. Nakano, "A genetic algorithm applicable to
|
60
|
-
|
61
|
-
|
62
|
-
|
82
|
+
large-scale job-shop problems," in Proceedings of the Second
|
83
|
+
International Workshop on Parallel Problem Solving from Nature
|
84
|
+
(PPSN'2), Brussels, Belgium, pp. 281–290, 1992.
|
63
85
|
|
64
86
|
- E. Taillard, "Benchmarks for basic scheduling problems," European
|
65
|
-
|
87
|
+
Journal of Operational Research, vol. 64, no. 2, pp. 278–285, 1993.
|
88
|
+
|
66
89
|
"""
|
67
90
|
|
68
|
-
from job_shop_lib.benchmarking.
|
91
|
+
from job_shop_lib.benchmarking._load_benchmark import (
|
69
92
|
load_all_benchmark_instances,
|
70
93
|
load_benchmark_instance,
|
71
94
|
load_benchmark_json,
|