job-shop-lib 0.5.1__py3-none-any.whl → 1.0.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/__init__.py +19 -8
- job_shop_lib/{base_solver.py → _base_solver.py} +1 -1
- job_shop_lib/{job_shop_instance.py → _job_shop_instance.py} +155 -81
- job_shop_lib/_operation.py +118 -0
- job_shop_lib/{schedule.py → _schedule.py} +102 -84
- job_shop_lib/{scheduled_operation.py → _scheduled_operation.py} +25 -49
- 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} +77 -22
- job_shop_lib/dispatching/__init__.py +51 -42
- job_shop_lib/dispatching/{dispatcher.py → _dispatcher.py} +223 -130
- job_shop_lib/dispatching/_dispatcher_observer_config.py +67 -0
- job_shop_lib/dispatching/_factories.py +135 -0
- job_shop_lib/dispatching/{history_tracker.py → _history_observer.py} +6 -7
- job_shop_lib/dispatching/_optimal_operations_observer.py +113 -0
- job_shop_lib/dispatching/_ready_operation_filters.py +168 -0
- job_shop_lib/dispatching/_unscheduled_operations_observer.py +70 -0
- job_shop_lib/dispatching/feature_observers/__init__.py +51 -13
- job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py +212 -0
- job_shop_lib/dispatching/feature_observers/{duration_observer.py → _duration_observer.py} +20 -18
- job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py +289 -0
- job_shop_lib/dispatching/feature_observers/_factory.py +95 -0
- job_shop_lib/dispatching/feature_observers/_feature_observer.py +228 -0
- job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +97 -0
- job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +35 -0
- job_shop_lib/dispatching/feature_observers/{is_scheduled_observer.py → _is_scheduled_observer.py} +9 -5
- job_shop_lib/dispatching/feature_observers/{position_in_job_observer.py → _position_in_job_observer.py} +8 -10
- job_shop_lib/dispatching/feature_observers/{remaining_operations_observer.py → _remaining_operations_observer.py} +8 -26
- job_shop_lib/dispatching/rules/__init__.py +87 -0
- job_shop_lib/dispatching/rules/_dispatching_rule_factory.py +84 -0
- job_shop_lib/dispatching/rules/_dispatching_rule_solver.py +201 -0
- job_shop_lib/dispatching/{dispatching_rules.py → rules/_dispatching_rules_functions.py} +70 -16
- job_shop_lib/dispatching/rules/_machine_chooser_factory.py +71 -0
- job_shop_lib/dispatching/rules/_utils.py +128 -0
- job_shop_lib/exceptions.py +18 -0
- job_shop_lib/generation/__init__.py +10 -2
- job_shop_lib/generation/_general_instance_generator.py +165 -0
- job_shop_lib/generation/{instance_generator.py → _instance_generator.py} +37 -26
- job_shop_lib/{generators/transformations.py → generation/_transformations.py} +16 -12
- job_shop_lib/generation/_utils.py +124 -0
- job_shop_lib/graphs/__init__.py +30 -12
- job_shop_lib/graphs/{build_disjunctive_graph.py → _build_disjunctive_graph.py} +41 -3
- job_shop_lib/graphs/{build_agent_task_graph.py → _build_resource_task_graphs.py} +28 -26
- job_shop_lib/graphs/_constants.py +38 -0
- job_shop_lib/graphs/_job_shop_graph.py +320 -0
- job_shop_lib/graphs/_node.py +182 -0
- job_shop_lib/graphs/graph_updaters/__init__.py +26 -0
- job_shop_lib/graphs/graph_updaters/_disjunctive_graph_updater.py +108 -0
- job_shop_lib/graphs/graph_updaters/_graph_updater.py +57 -0
- job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +155 -0
- job_shop_lib/graphs/graph_updaters/_utils.py +25 -0
- job_shop_lib/py.typed +0 -0
- job_shop_lib/reinforcement_learning/__init__.py +68 -0
- job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +398 -0
- job_shop_lib/reinforcement_learning/_resource_task_graph_observation.py +329 -0
- job_shop_lib/reinforcement_learning/_reward_observers.py +87 -0
- job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +443 -0
- job_shop_lib/reinforcement_learning/_types_and_constants.py +62 -0
- job_shop_lib/reinforcement_learning/_utils.py +199 -0
- job_shop_lib/visualization/__init__.py +0 -25
- job_shop_lib/visualization/gantt/__init__.py +48 -0
- job_shop_lib/visualization/gantt/_gantt_chart_creator.py +257 -0
- job_shop_lib/visualization/gantt/_gantt_chart_video_and_gif_creation.py +422 -0
- job_shop_lib/visualization/{gantt_chart.py → gantt/_plot_gantt_chart.py} +84 -21
- job_shop_lib/visualization/graphs/__init__.py +29 -0
- job_shop_lib/visualization/graphs/_plot_disjunctive_graph.py +418 -0
- job_shop_lib/visualization/graphs/_plot_resource_task_graph.py +389 -0
- {job_shop_lib-0.5.1.dist-info → job_shop_lib-1.0.0.dist-info}/METADATA +87 -55
- job_shop_lib-1.0.0.dist-info/RECORD +73 -0
- {job_shop_lib-0.5.1.dist-info → job_shop_lib-1.0.0.dist-info}/WHEEL +1 -1
- job_shop_lib/benchmarking/load_benchmark.py +0 -142
- job_shop_lib/cp_sat/__init__.py +0 -5
- job_shop_lib/dispatching/dispatching_rule_solver.py +0 -119
- job_shop_lib/dispatching/factories.py +0 -206
- job_shop_lib/dispatching/feature_observers/composite_feature_observer.py +0 -87
- job_shop_lib/dispatching/feature_observers/earliest_start_time_observer.py +0 -156
- job_shop_lib/dispatching/feature_observers/factory.py +0 -58
- job_shop_lib/dispatching/feature_observers/feature_observer.py +0 -113
- 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/dispatching/pruning_functions.py +0 -116
- job_shop_lib/generation/general_instance_generator.py +0 -169
- job_shop_lib/generation/transformations.py +0 -164
- job_shop_lib/generators/__init__.py +0 -8
- job_shop_lib/generators/basic_generator.py +0 -200
- job_shop_lib/graphs/constants.py +0 -21
- job_shop_lib/graphs/job_shop_graph.py +0 -202
- job_shop_lib/graphs/node.py +0 -166
- job_shop_lib/operation.py +0 -122
- job_shop_lib/visualization/agent_task_graph.py +0 -257
- job_shop_lib/visualization/create_gif.py +0 -209
- job_shop_lib/visualization/disjunctive_graph.py +0 -210
- job_shop_lib-0.5.1.dist-info/RECORD +0 -52
- {job_shop_lib-0.5.1.dist-info → job_shop_lib-1.0.0.dist-info}/LICENSE +0 -0
@@ -2,78 +2,88 @@
|
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
|
-
from typing import Any
|
5
|
+
from typing import Any, List, Union, Dict, Optional
|
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`.
|
13
15
|
|
14
|
-
|
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
|
28
|
+
|
29
|
+
Args:
|
15
30
|
instance:
|
16
|
-
The
|
31
|
+
The :class:`JobShopInstance` object that the schedule is for.
|
17
32
|
schedule:
|
18
|
-
A list of lists of
|
19
|
-
|
20
|
-
|
21
|
-
metadata:
|
22
|
-
|
23
|
-
can be used to store information about the algorithm that generated
|
24
|
-
the schedule, for example.
|
33
|
+
A list of lists of :class:`ScheduledOperation` objects. Each
|
34
|
+
list represents the order of operations on a machine. If
|
35
|
+
not provided, the schedule is initialized as an empty schedule.
|
36
|
+
**metadata:
|
37
|
+
Additional information about the schedule.
|
25
38
|
"""
|
26
39
|
|
27
|
-
__slots__ =
|
28
|
-
"instance"
|
29
|
-
|
30
|
-
|
31
|
-
|
40
|
+
__slots__ = {
|
41
|
+
"instance": (
|
42
|
+
"The :class:`JobShopInstance` object that the schedule is for."
|
43
|
+
),
|
44
|
+
"_schedule": (
|
45
|
+
"A list of lists of :class:`ScheduledOperation` objects. "
|
46
|
+
"Each list represents the order of operations on a machine."
|
47
|
+
),
|
48
|
+
"metadata": (
|
49
|
+
"A dictionary with additional information about the "
|
50
|
+
"schedule. It can be used to store information about the "
|
51
|
+
"algorithm that generated the schedule, for example."
|
52
|
+
),
|
53
|
+
}
|
32
54
|
|
33
55
|
def __init__(
|
34
56
|
self,
|
35
57
|
instance: JobShopInstance,
|
36
|
-
schedule:
|
37
|
-
**metadata,
|
58
|
+
schedule: Optional[List[List[ScheduledOperation]]] = None,
|
59
|
+
**metadata: Any,
|
38
60
|
):
|
39
|
-
"""Initializes the object with the given instance and schedule.
|
40
|
-
|
41
|
-
Args:
|
42
|
-
instance:
|
43
|
-
The `JobShopInstance` object that the schedule is for.
|
44
|
-
schedule:
|
45
|
-
A list of lists of `ScheduledOperation` objects. Each list of
|
46
|
-
`ScheduledOperation` objects represents the order of operations
|
47
|
-
on a machine. If not provided, the schedule is initialized as
|
48
|
-
an empty schedule.
|
49
|
-
**metadata:
|
50
|
-
Additional information about the schedule.
|
51
|
-
"""
|
52
61
|
if schedule is None:
|
53
62
|
schedule = [[] for _ in range(instance.num_machines)]
|
54
63
|
|
55
64
|
Schedule.check_schedule(schedule)
|
56
65
|
|
57
|
-
self.instance = instance
|
66
|
+
self.instance: JobShopInstance = instance
|
58
67
|
self._schedule = schedule
|
59
|
-
self.metadata = metadata
|
68
|
+
self.metadata: Dict[str, Any] = metadata
|
60
69
|
|
61
70
|
def __repr__(self) -> str:
|
62
71
|
return str(self.schedule)
|
63
72
|
|
64
73
|
@property
|
65
|
-
def schedule(self) ->
|
66
|
-
"""
|
74
|
+
def schedule(self) -> List[List[ScheduledOperation]]:
|
75
|
+
"""A list of lists of :class:`ScheduledOperation` objects. Each list
|
76
|
+
represents the order of operations on a machine."""
|
67
77
|
return self._schedule
|
68
78
|
|
69
79
|
@schedule.setter
|
70
|
-
def schedule(self, new_schedule:
|
80
|
+
def schedule(self, new_schedule: List[List[ScheduledOperation]]):
|
71
81
|
Schedule.check_schedule(new_schedule)
|
72
82
|
self._schedule = new_schedule
|
73
83
|
|
74
84
|
@property
|
75
85
|
def num_scheduled_operations(self) -> int:
|
76
|
-
"""
|
86
|
+
"""The number of operations that have been scheduled so far."""
|
77
87
|
return sum(len(machine_schedule) for machine_schedule in self.schedule)
|
78
88
|
|
79
89
|
def to_dict(self) -> dict:
|
@@ -84,15 +94,16 @@ class Schedule:
|
|
84
94
|
Returns:
|
85
95
|
A dictionary representation of the schedule with the following
|
86
96
|
keys:
|
87
|
-
|
88
|
-
- "
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
97
|
+
|
98
|
+
- **"instance"**: A dictionary representation of the instance.
|
99
|
+
- **"job_sequences"**: A list of lists of job ids. Each list
|
100
|
+
of job ids represents the order of operations on the machine.
|
101
|
+
The machine that the list corresponds to is determined by the
|
102
|
+
index of the list.
|
103
|
+
- **"metadata"**: A dictionary with additional information
|
104
|
+
about the schedule.
|
94
105
|
"""
|
95
|
-
job_sequences:
|
106
|
+
job_sequences: List[List[int]] = []
|
96
107
|
for machine_schedule in self.schedule:
|
97
108
|
job_sequences.append(
|
98
109
|
[operation.job_id for operation in machine_schedule]
|
@@ -106,9 +117,9 @@ class Schedule:
|
|
106
117
|
|
107
118
|
@staticmethod
|
108
119
|
def from_dict(
|
109
|
-
instance:
|
110
|
-
job_sequences:
|
111
|
-
metadata:
|
120
|
+
instance: Union[Dict[str, Any], JobShopInstance],
|
121
|
+
job_sequences: List[List[int]],
|
122
|
+
metadata: Optional[Dict[str, Any]] = None,
|
112
123
|
) -> Schedule:
|
113
124
|
"""Creates a schedule from a dictionary representation."""
|
114
125
|
if isinstance(instance, dict):
|
@@ -120,7 +131,7 @@ class Schedule:
|
|
120
131
|
@staticmethod
|
121
132
|
def from_job_sequences(
|
122
133
|
instance: JobShopInstance,
|
123
|
-
job_sequences:
|
134
|
+
job_sequences: List[List[int]],
|
124
135
|
) -> Schedule:
|
125
136
|
"""Creates an active schedule from a list of job sequences.
|
126
137
|
|
@@ -131,14 +142,14 @@ class Schedule:
|
|
131
142
|
|
132
143
|
Args:
|
133
144
|
instance:
|
134
|
-
The
|
145
|
+
The :class:`JobShopInstance` object that the schedule is for.
|
135
146
|
job_sequences:
|
136
147
|
A list of lists of job ids. Each list of job ids represents the
|
137
148
|
order of operations on the machine. The machine that the list
|
138
149
|
corresponds to is determined by the index of the list.
|
139
150
|
|
140
151
|
Returns:
|
141
|
-
A
|
152
|
+
A :class:`Schedule` object with the given job sequences.
|
142
153
|
"""
|
143
154
|
from job_shop_lib.dispatching import Dispatcher
|
144
155
|
|
@@ -161,7 +172,7 @@ class Schedule:
|
|
161
172
|
at_least_one_operation_scheduled = True
|
162
173
|
|
163
174
|
if not at_least_one_operation_scheduled:
|
164
|
-
raise
|
175
|
+
raise ValidationError(
|
165
176
|
"Invalid job sequences. No valid operation to schedule."
|
166
177
|
)
|
167
178
|
return dispatcher.schedule
|
@@ -182,18 +193,19 @@ class Schedule:
|
|
182
193
|
return max_end_time
|
183
194
|
|
184
195
|
def is_complete(self) -> bool:
|
185
|
-
"""Returns True if all operations have been scheduled."""
|
196
|
+
"""Returns ``True`` if all operations have been scheduled."""
|
186
197
|
return self.num_scheduled_operations == self.instance.num_operations
|
187
198
|
|
188
199
|
def add(self, scheduled_operation: ScheduledOperation):
|
189
|
-
"""Adds a new
|
200
|
+
"""Adds a new :class:`ScheduledOperation` to the schedule.
|
190
201
|
|
191
202
|
Args:
|
192
203
|
scheduled_operation:
|
193
|
-
The
|
204
|
+
The :class:`ScheduledOperation` to add to the schedule.
|
194
205
|
|
195
206
|
Raises:
|
196
|
-
|
207
|
+
ValidationError:
|
208
|
+
If the start time of the new operation is before the
|
197
209
|
end time of the last operation on the same machine. In favor of
|
198
210
|
performance, this method does not checks precedence
|
199
211
|
constraints.
|
@@ -212,49 +224,45 @@ class Schedule:
|
|
212
224
|
return
|
213
225
|
|
214
226
|
last_operation = self.schedule[new_operation.machine_id][-1]
|
215
|
-
self.
|
227
|
+
if not self._is_valid_start_time(new_operation, last_operation):
|
228
|
+
raise ValidationError(
|
229
|
+
"Operation cannot be scheduled before the last operation on "
|
230
|
+
"the same machine: end time of last operation "
|
231
|
+
f"({last_operation.end_time}) > start time of new operation "
|
232
|
+
f"({new_operation.start_time})."
|
233
|
+
)
|
216
234
|
|
217
235
|
@staticmethod
|
218
|
-
def
|
236
|
+
def _is_valid_start_time(
|
219
237
|
scheduled_operation: ScheduledOperation,
|
220
238
|
previous_operation: ScheduledOperation,
|
221
239
|
):
|
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
|
-
)
|
240
|
+
return previous_operation.end_time <= scheduled_operation.start_time
|
234
241
|
|
235
242
|
@staticmethod
|
236
|
-
def check_schedule(schedule:
|
237
|
-
"""Checks if a schedule is valid and raises a
|
243
|
+
def check_schedule(schedule: List[List[ScheduledOperation]]):
|
244
|
+
"""Checks if a schedule is valid and raises a
|
245
|
+
:class:`~exceptions.ValidationError` if it is not.
|
238
246
|
|
239
247
|
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.
|
248
|
+
- A :class:`ScheduledOperation` has a machine id that does not
|
249
|
+
match the machine id of the machine schedule (the list of
|
250
|
+
:class:`ScheduledOperation` objects) that it belongs to.
|
251
|
+
- The start time of a :class:`ScheduledOperation` is before the
|
252
|
+
end time of the last operation on the same machine.
|
245
253
|
|
246
254
|
Args:
|
247
255
|
schedule:
|
248
|
-
The schedule (a list of lists of
|
249
|
-
to check.
|
256
|
+
The schedule (a list of lists of :class:`ScheduledOperation`
|
257
|
+
objects) to check.
|
250
258
|
|
251
259
|
Raises:
|
252
|
-
|
260
|
+
ValidationError: If the schedule is invalid.
|
253
261
|
"""
|
254
262
|
for machine_id, scheduled_operations in enumerate(schedule):
|
255
263
|
for i, scheduled_operation in enumerate(scheduled_operations):
|
256
264
|
if scheduled_operation.machine_id != machine_id:
|
257
|
-
raise
|
265
|
+
raise ValidationError(
|
258
266
|
"The machine id of the scheduled operation "
|
259
267
|
f"({ScheduledOperation.machine_id}) does not match "
|
260
268
|
f"the machine id of the machine schedule ({machine_id}"
|
@@ -264,9 +272,19 @@ class Schedule:
|
|
264
272
|
if i == 0:
|
265
273
|
continue
|
266
274
|
|
267
|
-
Schedule.
|
275
|
+
if not Schedule._is_valid_start_time(
|
268
276
|
scheduled_operation, scheduled_operations[i - 1]
|
269
|
-
)
|
277
|
+
):
|
278
|
+
raise ValidationError(
|
279
|
+
"Invalid schedule. The start time of the new "
|
280
|
+
"operation is before the end time of the last "
|
281
|
+
"operation on the same machine."
|
282
|
+
"End time of last operation: "
|
283
|
+
f"{scheduled_operations[i - 1].end_time}. "
|
284
|
+
f"Start time of new operation: "
|
285
|
+
f"{scheduled_operation.start_time}. At index "
|
286
|
+
f"[{machine_id}][{i}]."
|
287
|
+
)
|
270
288
|
|
271
289
|
def __eq__(self, value: object) -> bool:
|
272
290
|
if not isinstance(value, Schedule):
|
@@ -1,42 +1,37 @@
|
|
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:
|
9
8
|
"""Data structure to store a scheduled operation.
|
10
9
|
|
11
|
-
|
10
|
+
Args:
|
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
15
|
machine_id:
|
17
16
|
The id of the machine on which the operation is scheduled.
|
17
|
+
|
18
|
+
Raises:
|
19
|
+
ValidationError:
|
20
|
+
If the given machine_id is not in the list of valid machines
|
21
|
+
for the operation.
|
18
22
|
"""
|
19
23
|
|
20
|
-
__slots__ =
|
24
|
+
__slots__ = {
|
25
|
+
"operation": "The :class:`Operation` object that is scheduled.",
|
26
|
+
"start_time": "The time at which the operation is scheduled to start.",
|
27
|
+
"_machine_id": (
|
28
|
+
"The id of the machine on which the operation is scheduled."
|
29
|
+
),
|
30
|
+
}
|
21
31
|
|
22
32
|
def __init__(self, operation: Operation, start_time: int, machine_id: int):
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
Args:
|
27
|
-
operation:
|
28
|
-
The `Operation` object that is scheduled.
|
29
|
-
start_time:
|
30
|
-
The time at which the operation is scheduled to start.
|
31
|
-
machine_id:
|
32
|
-
The id of the machine on which the operation is scheduled.
|
33
|
-
|
34
|
-
Raises:
|
35
|
-
ValueError:
|
36
|
-
If the machine_id is not valid for the operation.
|
37
|
-
"""
|
38
|
-
self.operation = operation
|
39
|
-
self.start_time = start_time
|
33
|
+
self.operation: Operation = operation
|
34
|
+
self.start_time: int = start_time
|
40
35
|
self._machine_id = machine_id
|
41
36
|
self.machine_id = machine_id # Validate machine_id
|
42
37
|
|
@@ -49,7 +44,7 @@ class ScheduledOperation:
|
|
49
44
|
@machine_id.setter
|
50
45
|
def machine_id(self, value: int):
|
51
46
|
if value not in self.operation.machines:
|
52
|
-
raise
|
47
|
+
raise ValidationError(
|
53
48
|
f"Operation cannot be scheduled on machine {value}. "
|
54
49
|
f"Valid machines are {self.operation.machines}."
|
55
50
|
)
|
@@ -57,35 +52,13 @@ class ScheduledOperation:
|
|
57
52
|
|
58
53
|
@property
|
59
54
|
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.")
|
55
|
+
"""Returns the id of the job that the operation belongs to."""
|
68
56
|
return self.operation.job_id
|
69
57
|
|
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
58
|
@property
|
81
59
|
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.")
|
60
|
+
"""Returns the position (starting at zero) of the operation in the
|
61
|
+
job."""
|
89
62
|
return self.operation.position_in_job
|
90
63
|
|
91
64
|
@property
|
@@ -107,3 +80,6 @@ class ScheduledOperation:
|
|
107
80
|
and self.start_time == value.start_time
|
108
81
|
and self.machine_id == value.machine_id
|
109
82
|
)
|
83
|
+
|
84
|
+
def __hash__(self) -> int:
|
85
|
+
return hash((self.operation, self.start_time, self.machine_id))
|
@@ -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,
|
@@ -0,0 +1,88 @@
|
|
1
|
+
"""Contains functions to load benchmark instances from a JSON file."""
|
2
|
+
|
3
|
+
from typing import Any, Dict
|
4
|
+
|
5
|
+
import functools
|
6
|
+
import json
|
7
|
+
from importlib import resources
|
8
|
+
|
9
|
+
from job_shop_lib import JobShopInstance
|
10
|
+
|
11
|
+
|
12
|
+
@functools.cache
|
13
|
+
def load_all_benchmark_instances() -> Dict[str, JobShopInstance]:
|
14
|
+
"""Loads all benchmark instances available.
|
15
|
+
|
16
|
+
Returns:
|
17
|
+
A dictionary containing the names of the benchmark instances as keys
|
18
|
+
and the corresponding :class:`JobShopInstance` objects as values.
|
19
|
+
|
20
|
+
"""
|
21
|
+
benchmark_instances_dict = load_benchmark_json()
|
22
|
+
return {
|
23
|
+
name: load_benchmark_instance(name)
|
24
|
+
for name in benchmark_instances_dict
|
25
|
+
}
|
26
|
+
|
27
|
+
|
28
|
+
def load_benchmark_instance(name: str) -> JobShopInstance:
|
29
|
+
"""Loads a specific benchmark instance.
|
30
|
+
|
31
|
+
Calls to :func:`load_benchmark_json` to load the benchmark instances from
|
32
|
+
the JSON file. The instance is then loaded from the dictionary using the
|
33
|
+
provided name. Since `load_benchmark_json` is cached, the file is only
|
34
|
+
read once.
|
35
|
+
|
36
|
+
Args:
|
37
|
+
name: The name of the benchmark instance to load. Can be one of the
|
38
|
+
following: "abz5-9", "ft06", "ft10", "ft20", "la01-40", "orb01-10",
|
39
|
+
"swb01-20", "yn1-4", or "ta01-80".
|
40
|
+
"""
|
41
|
+
benchmark_dict = load_benchmark_json()[name]
|
42
|
+
return JobShopInstance.from_matrices(
|
43
|
+
duration_matrix=benchmark_dict["duration_matrix"],
|
44
|
+
machines_matrix=benchmark_dict["machines_matrix"],
|
45
|
+
name=name,
|
46
|
+
metadata=benchmark_dict["metadata"],
|
47
|
+
)
|
48
|
+
|
49
|
+
|
50
|
+
@functools.cache
|
51
|
+
def load_benchmark_json() -> Dict[str, Dict[str, Any]]:
|
52
|
+
"""Loads the raw JSON file containing the benchmark instances.
|
53
|
+
|
54
|
+
Results are cached to avoid reading the file multiple times.
|
55
|
+
|
56
|
+
Each instance is represented as a dictionary with the following keys
|
57
|
+
and values:
|
58
|
+
|
59
|
+
* ``name`` (``str``): The name of the instance.
|
60
|
+
* ``duration_matrix`` (``list[list[int]]``): The matrix containing the
|
61
|
+
durations for each operation.
|
62
|
+
* ``machines_matrix`` (``list[list[int]]``): The matrix containing the
|
63
|
+
machines for each operation.
|
64
|
+
* ``metadata`` (``dict[str, Any]``): A dictionary containing metadata
|
65
|
+
about the instance. The keys are:
|
66
|
+
|
67
|
+
* ``optimum`` (``int | None``)
|
68
|
+
* ``lower_bound`` (``int``)
|
69
|
+
* ``upper_bound`` (``int``)
|
70
|
+
* ``reference`` (``str``)
|
71
|
+
|
72
|
+
Note:
|
73
|
+
This dictionary is the standard way to represent instances in
|
74
|
+
JobShopLib. You can obtain the dictionary of each instance with
|
75
|
+
:class:`~job_shop_lib.JobShopInstance`'s
|
76
|
+
:meth:`~job_shop_lib.JobShopInstance.to_dict` method.
|
77
|
+
|
78
|
+
Returns:
|
79
|
+
The dictionary containing the benchmark instances represented as
|
80
|
+
dictionaries.
|
81
|
+
"""
|
82
|
+
benchmark_file = (
|
83
|
+
resources.files("job_shop_lib.benchmarking")
|
84
|
+
/ "benchmark_instances.json"
|
85
|
+
)
|
86
|
+
|
87
|
+
with benchmark_file.open("r", encoding="utf-8") as f:
|
88
|
+
return json.load(f)
|
@@ -0,0 +1,13 @@
|
|
1
|
+
"""Contains solvers based on Constraint Programming (CP).
|
2
|
+
|
3
|
+
CP defines the Job Shop Scheduling Problem through constraints
|
4
|
+
and focuses on finding a feasible solution. It usually aims to minimize an
|
5
|
+
objective function, typically the makespan. One of the advantages of these
|
6
|
+
methods is that they are not restricted to linear constraints.
|
7
|
+
"""
|
8
|
+
|
9
|
+
from job_shop_lib.constraint_programming._ortools_solver import ORToolsSolver
|
10
|
+
|
11
|
+
__all__ = [
|
12
|
+
"ORToolsSolver",
|
13
|
+
]
|