job-shop-lib 1.1.0__py3-none-any.whl → 1.1.2__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 +1 -1
- job_shop_lib/_job_shop_instance.py +5 -5
- job_shop_lib/_schedule.py +2 -2
- job_shop_lib/dispatching/_dispatcher.py +12 -7
- job_shop_lib/dispatching/_optimal_operations_observer.py +6 -3
- job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py +11 -46
- job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py +2 -2
- job_shop_lib/dispatching/feature_observers/_factory.py +2 -4
- job_shop_lib/dispatching/rules/_dispatching_rule_solver.py +0 -46
- job_shop_lib/generation/_general_instance_generator.py +6 -4
- job_shop_lib/graphs/_build_resource_task_graphs.py +1 -0
- job_shop_lib/graphs/_job_shop_graph.py +2 -2
- job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +4 -3
- job_shop_lib/reinforcement_learning/_resource_task_graph_observation.py +5 -3
- job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +23 -29
- job_shop_lib/visualization/gantt/_gantt_chart_video_and_gif_creation.py +6 -4
- job_shop_lib/visualization/graphs/_plot_disjunctive_graph.py +18 -11
- job_shop_lib/visualization/graphs/_plot_resource_task_graph.py +1 -1
- {job_shop_lib-1.1.0.dist-info → job_shop_lib-1.1.2.dist-info}/METADATA +6 -4
- {job_shop_lib-1.1.0.dist-info → job_shop_lib-1.1.2.dist-info}/RECORD +22 -23
- job_shop_lib/generation/_transformations.py +0 -167
- {job_shop_lib-1.1.0.dist-info → job_shop_lib-1.1.2.dist-info}/LICENSE +0 -0
- {job_shop_lib-1.1.0.dist-info → job_shop_lib-1.1.2.dist-info}/WHEEL +0 -0
job_shop_lib/__init__.py
CHANGED
@@ -13,7 +13,7 @@ from job_shop_lib import Operation
|
|
13
13
|
|
14
14
|
|
15
15
|
class JobShopInstance:
|
16
|
-
"""Data structure to store a Job Shop Scheduling Problem instance.
|
16
|
+
r"""Data structure to store a Job Shop Scheduling Problem instance.
|
17
17
|
|
18
18
|
Additional attributes such as ``num_machines`` or ``durations_matrix`` can
|
19
19
|
be computed from the instance and are cached for performance since they
|
@@ -75,7 +75,7 @@ class JobShopInstance:
|
|
75
75
|
attributes of the operations are set when the instance is created.
|
76
76
|
See :meth:`set_operation_attributes` for more information. Defaults
|
77
77
|
to True.
|
78
|
-
|
78
|
+
\**metadata:
|
79
79
|
Additional information about the instance.
|
80
80
|
"""
|
81
81
|
|
@@ -131,7 +131,7 @@ class JobShopInstance:
|
|
131
131
|
name: str | None = None,
|
132
132
|
**metadata: Any,
|
133
133
|
) -> JobShopInstance:
|
134
|
-
"""Creates a JobShopInstance from a file following Taillard's format.
|
134
|
+
r"""Creates a JobShopInstance from a file following Taillard's format.
|
135
135
|
|
136
136
|
Args:
|
137
137
|
file_path:
|
@@ -144,7 +144,7 @@ class JobShopInstance:
|
|
144
144
|
name:
|
145
145
|
A string with the name of the instance. If not provided, the
|
146
146
|
name of the instance is set to the name of the file.
|
147
|
-
|
147
|
+
\**metadata:
|
148
148
|
Additional information about the instance.
|
149
149
|
|
150
150
|
Returns:
|
@@ -221,7 +221,7 @@ class JobShopInstance:
|
|
221
221
|
A list of lists of integers. The i-th list contains the
|
222
222
|
durations of the operations of the job with id i.
|
223
223
|
machines_matrix:
|
224
|
-
|
224
|
+
A list of lists of lists of integers if the
|
225
225
|
instance is flexible, or a list of lists of integers if the
|
226
226
|
instance is not flexible. The i-th list contains the machines
|
227
227
|
in which the operations of the job with id i can be processed.
|
job_shop_lib/_schedule.py
CHANGED
@@ -10,7 +10,7 @@ from job_shop_lib.exceptions import ValidationError
|
|
10
10
|
|
11
11
|
|
12
12
|
class Schedule:
|
13
|
-
"""Data structure to store a complete or partial solution for a particular
|
13
|
+
r"""Data structure to store a complete or partial solution for a particular
|
14
14
|
:class:`JobShopInstance`.
|
15
15
|
|
16
16
|
A schedule is a list of lists of :class:`ScheduledOperation` objects. Each
|
@@ -33,7 +33,7 @@ class Schedule:
|
|
33
33
|
A list of lists of :class:`ScheduledOperation` objects. Each
|
34
34
|
list represents the order of operations on a machine. If
|
35
35
|
not provided, the schedule is initialized as an empty schedule.
|
36
|
-
|
36
|
+
\**metadata:
|
37
37
|
Additional information about the schedule.
|
38
38
|
"""
|
39
39
|
|
@@ -30,9 +30,12 @@ def no_setup_time_calculator(
|
|
30
30
|
operation belongs.
|
31
31
|
|
32
32
|
Args:
|
33
|
-
dispatcher:
|
34
|
-
|
35
|
-
|
33
|
+
dispatcher:
|
34
|
+
The dispatcher instance.
|
35
|
+
operation:
|
36
|
+
The operation to be scheduled.
|
37
|
+
machine_id:
|
38
|
+
The id of the machine on which the operation is to be
|
36
39
|
scheduled.
|
37
40
|
|
38
41
|
Returns:
|
@@ -329,8 +332,10 @@ class Dispatcher:
|
|
329
332
|
:attr:`~job_shop_lib.Operation.machine_id` attribute is used.
|
330
333
|
|
331
334
|
Raises:
|
332
|
-
ValidationError:
|
333
|
-
|
335
|
+
ValidationError:
|
336
|
+
If the operation is not ready to be scheduled.
|
337
|
+
UninitializedAttributeError:
|
338
|
+
If the operation has multiple
|
334
339
|
machines in its list and no ``machine_id`` is provided.
|
335
340
|
"""
|
336
341
|
|
@@ -408,7 +413,7 @@ class Dispatcher:
|
|
408
413
|
condition: Callable[[DispatcherObserver], bool] = lambda _: True,
|
409
414
|
**kwargs,
|
410
415
|
) -> ObserverType:
|
411
|
-
"""Creates a new observer of the specified type or returns an existing
|
416
|
+
r"""Creates a new observer of the specified type or returns an existing
|
412
417
|
observer of the same type if it already exists in the dispatcher's list
|
413
418
|
of observers.
|
414
419
|
|
@@ -419,7 +424,7 @@ class Dispatcher:
|
|
419
424
|
A function that takes an observer and returns True if it is
|
420
425
|
the observer to be retrieved. By default, it returns True for
|
421
426
|
all observers.
|
422
|
-
|
427
|
+
\**kwargs:
|
423
428
|
Additional keyword arguments to be passed to the observer's
|
424
429
|
constructor.
|
425
430
|
"""
|
@@ -21,10 +21,13 @@ class OptimalOperationsObserver(DispatcherObserver):
|
|
21
21
|
operations.
|
22
22
|
|
23
23
|
Args:
|
24
|
-
dispatcher:
|
25
|
-
|
24
|
+
dispatcher:
|
25
|
+
The dispatcher instance to observe.
|
26
|
+
reference_schedule:
|
27
|
+
A complete schedule that represents the optimal
|
26
28
|
or reference solution.
|
27
|
-
subscribe:
|
29
|
+
subscribe:
|
30
|
+
If True, automatically subscribes to the dispatcher.
|
28
31
|
|
29
32
|
Raises:
|
30
33
|
ValidationError: If the reference schedule is incomplete or if it
|
@@ -11,7 +11,6 @@ import numpy as np
|
|
11
11
|
from numpy.typing import NDArray
|
12
12
|
import pandas as pd
|
13
13
|
|
14
|
-
from job_shop_lib.exceptions import ValidationError
|
15
14
|
from job_shop_lib.dispatching import Dispatcher
|
16
15
|
from job_shop_lib.dispatching.feature_observers import (
|
17
16
|
FeatureObserver,
|
@@ -86,19 +85,11 @@ class CompositeFeatureObserver(FeatureObserver):
|
|
86
85
|
for observer in dispatcher.subscribers
|
87
86
|
if isinstance(observer, FeatureObserver)
|
88
87
|
]
|
89
|
-
feature_types = self._get_feature_types_list(feature_types)
|
90
|
-
for observer in feature_observers:
|
91
|
-
if not set(observer.features.keys()).issubset(set(feature_types)):
|
92
|
-
raise ValidationError(
|
93
|
-
"The feature types observed by the feature observer "
|
94
|
-
f"{observer.__class__.__name__} are not a subset of the "
|
95
|
-
"feature types specified in the CompositeFeatureObserver."
|
96
|
-
f"Observer feature types: {observer.features.keys()}"
|
97
|
-
f"Composite feature types: {feature_types}"
|
98
|
-
)
|
99
88
|
self.feature_observers = feature_observers
|
100
89
|
self.column_names: dict[FeatureType, list[str]] = defaultdict(list)
|
101
|
-
super().__init__(
|
90
|
+
super().__init__(
|
91
|
+
dispatcher, subscribe=subscribe, feature_types=feature_types
|
92
|
+
)
|
102
93
|
self._set_column_names()
|
103
94
|
|
104
95
|
@classmethod
|
@@ -148,12 +139,17 @@ class CompositeFeatureObserver(FeatureObserver):
|
|
148
139
|
list
|
149
140
|
)
|
150
141
|
for observer in self.feature_observers:
|
151
|
-
for feature_type
|
142
|
+
for feature_type in self.supported_feature_types:
|
143
|
+
feature_matrix = observer.features.get(feature_type)
|
144
|
+
if feature_matrix is None:
|
145
|
+
continue
|
152
146
|
features[feature_type].append(feature_matrix)
|
153
147
|
|
154
148
|
self.features = {
|
155
|
-
feature_type: np.concatenate(
|
156
|
-
|
149
|
+
feature_type: np.concatenate(
|
150
|
+
feature_matrices, axis=1 # type: ignore[misc]
|
151
|
+
)
|
152
|
+
for feature_type, feature_matrices in features.items()
|
157
153
|
}
|
158
154
|
|
159
155
|
def _set_column_names(self):
|
@@ -177,34 +173,3 @@ class CompositeFeatureObserver(FeatureObserver):
|
|
177
173
|
out.append(f"{feature_type.value}:")
|
178
174
|
out.append(dataframe.to_string())
|
179
175
|
return "\n".join(out)
|
180
|
-
|
181
|
-
|
182
|
-
if __name__ == "__main__":
|
183
|
-
# from cProfile import Profile
|
184
|
-
import time
|
185
|
-
from job_shop_lib.benchmarking import load_benchmark_instance
|
186
|
-
from job_shop_lib.dispatching.rules import DispatchingRuleSolver
|
187
|
-
|
188
|
-
ta80 = load_benchmark_instance("ta80")
|
189
|
-
|
190
|
-
dispatcher_ = Dispatcher(ta80)
|
191
|
-
feature_observer_types_ = list(FeatureObserverType)
|
192
|
-
feature_observers_ = [
|
193
|
-
feature_observer_factory(
|
194
|
-
observer_type,
|
195
|
-
dispatcher=dispatcher_,
|
196
|
-
)
|
197
|
-
for observer_type in feature_observer_types_
|
198
|
-
# and not FeatureObserverType.EARLIEST_START_TIME
|
199
|
-
]
|
200
|
-
composite_observer_ = CompositeFeatureObserver(
|
201
|
-
dispatcher_, feature_observers=feature_observers_
|
202
|
-
)
|
203
|
-
solver = DispatchingRuleSolver(dispatching_rule="random")
|
204
|
-
# profiler = Profile()
|
205
|
-
# profiler.runcall(solver.solve, dispatcher_.instance, dispatcher_)
|
206
|
-
# profiler.print_stats("cumtime")
|
207
|
-
start = time.perf_counter()
|
208
|
-
solver.solve(dispatcher_.instance, dispatcher_)
|
209
|
-
end = time.perf_counter()
|
210
|
-
print(f"Time: {end - start}")
|
@@ -93,8 +93,8 @@ class EarliestStartTimeObserver(FeatureObserver):
|
|
93
93
|
# Cache:
|
94
94
|
operations_by_machine = dispatcher.instance.operations_by_machine
|
95
95
|
self._is_regular_instance = all(
|
96
|
-
len(
|
97
|
-
for
|
96
|
+
len(machine_ops) == len(operations_by_machine[0])
|
97
|
+
for machine_ops in operations_by_machine
|
98
98
|
)
|
99
99
|
if self._is_regular_instance:
|
100
100
|
self._job_ids = np.array(
|
@@ -1,5 +1,3 @@
|
|
1
|
-
"""Contains factory functions for creating :class:`FeatureObserver`s."""
|
2
|
-
|
3
1
|
from enum import Enum
|
4
2
|
|
5
3
|
from job_shop_lib.dispatching import DispatcherObserverConfig
|
@@ -57,13 +55,13 @@ def feature_observer_factory(
|
|
57
55
|
),
|
58
56
|
**kwargs,
|
59
57
|
) -> FeatureObserver:
|
60
|
-
"""Creates and returns a :class:`FeatureObserver` based on the specified
|
58
|
+
r"""Creates and returns a :class:`FeatureObserver` based on the specified
|
61
59
|
:class:`FeatureObserver` type.
|
62
60
|
|
63
61
|
Args:
|
64
62
|
feature_creator_type:
|
65
63
|
The type of :class:`FeatureObserver` to create.
|
66
|
-
|
64
|
+
\*\*kwargs:
|
67
65
|
Additional keyword arguments to pass to the
|
68
66
|
:class:`FeatureObserver` constructor.
|
69
67
|
|
@@ -148,49 +148,3 @@ class DispatchingRuleSolver(BaseSolver):
|
|
148
148
|
selected_operation = self.dispatching_rule(dispatcher)
|
149
149
|
machine_id = self.machine_chooser(dispatcher, selected_operation)
|
150
150
|
dispatcher.dispatch(selected_operation, machine_id)
|
151
|
-
|
152
|
-
|
153
|
-
if __name__ == "__main__":
|
154
|
-
import time
|
155
|
-
import cProfile
|
156
|
-
|
157
|
-
# import pstats
|
158
|
-
# from io import StringIO
|
159
|
-
from job_shop_lib.benchmarking import (
|
160
|
-
# load_benchmark_instance,
|
161
|
-
load_all_benchmark_instances,
|
162
|
-
)
|
163
|
-
|
164
|
-
# from job_shop_lib.dispatching.rules._dispatching_rules_functions import (
|
165
|
-
# most_work_remaining_rule_2,
|
166
|
-
# )
|
167
|
-
|
168
|
-
# ta_instances = [
|
169
|
-
# load_benchmark_instance(f"ta{i:02d}") for i in range(1, 81)
|
170
|
-
# ]
|
171
|
-
ta_instances = load_all_benchmark_instances().values()
|
172
|
-
solver = DispatchingRuleSolver(
|
173
|
-
dispatching_rule="most_work_remaining", ready_operations_filter=None
|
174
|
-
)
|
175
|
-
|
176
|
-
start = time.perf_counter()
|
177
|
-
|
178
|
-
# Create a Profile object
|
179
|
-
profiler = cProfile.Profile()
|
180
|
-
|
181
|
-
# Run the code under profiling
|
182
|
-
# profiler.enable()
|
183
|
-
for instance_ in ta_instances:
|
184
|
-
solver.solve(instance_)
|
185
|
-
# profiler.disable()
|
186
|
-
|
187
|
-
end = time.perf_counter()
|
188
|
-
|
189
|
-
# Print elapsed time
|
190
|
-
print(f"Elapsed time: {end - start:.2f} seconds.")
|
191
|
-
|
192
|
-
# Print profiling results
|
193
|
-
# s = StringIO()
|
194
|
-
# ps = pstats.Stats(profiler, stream=s).sort_stats("cumulative")
|
195
|
-
# profiler.print_stats("cumtime") # Print top 20 time-consuming functions
|
196
|
-
# print(s.getvalue())
|
@@ -129,16 +129,18 @@ class GeneralInstanceGenerator(InstanceGenerator):
|
|
129
129
|
num_machines: int | None = None,
|
130
130
|
) -> JobShopInstance:
|
131
131
|
if num_jobs is None:
|
132
|
-
num_jobs =
|
133
|
-
self.
|
132
|
+
num_jobs = int(
|
133
|
+
self.rng.integers(
|
134
|
+
self.num_jobs_range[0], self.num_jobs_range[1] + 1
|
135
|
+
)
|
134
136
|
)
|
135
137
|
|
136
138
|
if num_machines is None:
|
137
139
|
min_num_machines, max_num_machines = self.num_machines_range
|
138
140
|
if not self.allow_less_jobs_than_machines:
|
139
141
|
min_num_machines = min(num_jobs, max_num_machines)
|
140
|
-
num_machines =
|
141
|
-
min_num_machines, max_num_machines + 1
|
142
|
+
num_machines = int(
|
143
|
+
self.rng.integers(min_num_machines, max_num_machines + 1)
|
142
144
|
)
|
143
145
|
elif (
|
144
146
|
not self.allow_less_jobs_than_machines and num_jobs < num_machines
|
@@ -9,6 +9,7 @@ connected with machine ones. All machine nodes are connected between them, and
|
|
9
9
|
all operation nodes from the same job are connected by non-directed edges too.
|
10
10
|
|
11
11
|
References:
|
12
|
+
|
12
13
|
- Junyoung Park, Sanjar Bakhtiyar, and Jinkyoo Park. Schedulenet: Learn to
|
13
14
|
solve multi-agent scheduling problems with reinforcement learning. ArXiv,
|
14
15
|
abs/2106.03051, 2021.
|
@@ -166,7 +166,7 @@ class JobShopGraph:
|
|
166
166
|
v_of_edge: Node | int,
|
167
167
|
**attr,
|
168
168
|
) -> None:
|
169
|
-
"""Adds an edge to the graph.
|
169
|
+
r"""Adds an edge to the graph.
|
170
170
|
|
171
171
|
It automatically determines the edge type based on the source and
|
172
172
|
destination nodes unless explicitly provided in the ``attr`` argument
|
@@ -182,7 +182,7 @@ class JobShopGraph:
|
|
182
182
|
The destination node of the edge. If it is a :class:`Node`,
|
183
183
|
its ``node_id`` is used as the destination. Otherwise, it
|
184
184
|
is assumed to be the ``node_id`` of the destination.
|
185
|
-
|
185
|
+
\**attr:
|
186
186
|
Additional attributes to be added to the edge.
|
187
187
|
|
188
188
|
Raises:
|
@@ -277,7 +277,8 @@ class MultiJobShopGraphEnv(gym.Env):
|
|
277
277
|
options: Additional options for reset (currently unused).
|
278
278
|
|
279
279
|
Returns:
|
280
|
-
|
280
|
+
tuple[ObservationDict, dict[str, Any]]:
|
281
|
+
|
281
282
|
- ObservationDict: The initial observation of the environment.
|
282
283
|
- dict: An info dictionary containing additional information about
|
283
284
|
the reset state. This may include details about the generated
|
@@ -305,7 +306,7 @@ class MultiJobShopGraphEnv(gym.Env):
|
|
305
306
|
def step(
|
306
307
|
self, action: tuple[int, int]
|
307
308
|
) -> tuple[ObservationDict, float, bool, bool, dict[str, Any]]:
|
308
|
-
"""Takes a step in the environment.
|
309
|
+
r"""Takes a step in the environment.
|
309
310
|
|
310
311
|
Args:
|
311
312
|
action:
|
@@ -315,7 +316,7 @@ class MultiJobShopGraphEnv(gym.Env):
|
|
315
316
|
operation.
|
316
317
|
|
317
318
|
Returns:
|
318
|
-
|
319
|
+
tuple[ObservationDict, float, bool, bool, dict[str, Any]]:
|
319
320
|
|
320
321
|
- The observation of the environment.
|
321
322
|
- The reward obtained.
|
@@ -247,9 +247,11 @@ class ResourceTaskGraphObservation(ObservationWrapper, Generic[EnvType]):
|
|
247
247
|
def _get_start_from_zero_mappings(
|
248
248
|
original_indices_dict: dict[str, NDArray[np.int32]],
|
249
249
|
) -> dict[str, dict[int, int]]:
|
250
|
-
mappings = {}
|
250
|
+
mappings: dict[str, dict[int, int]] = {}
|
251
251
|
for key, indices in original_indices_dict.items():
|
252
|
-
mappings[key] = {
|
252
|
+
mappings[key] = {
|
253
|
+
idx: i for i, idx in enumerate(indices) # type: ignore[misc]
|
254
|
+
} # idx is an integer (false positive)
|
253
255
|
return mappings
|
254
256
|
|
255
257
|
def _create_node_features_dict(
|
@@ -318,7 +320,7 @@ class ResourceTaskGraphObservation(ObservationWrapper, Generic[EnvType]):
|
|
318
320
|
~removed_nodes_of_this_type
|
319
321
|
]
|
320
322
|
original_ids_dict[node_type] = np.where(
|
321
|
-
~removed_nodes_of_this_type
|
323
|
+
~removed_nodes_of_this_type # type: ignore[assignment]
|
322
324
|
)[0]
|
323
325
|
|
324
326
|
return removed_nodes_dict, original_ids_dict
|
@@ -3,10 +3,12 @@
|
|
3
3
|
from copy import deepcopy
|
4
4
|
from collections.abc import Callable, Sequence
|
5
5
|
from typing import Any
|
6
|
+
import warnings
|
6
7
|
|
7
8
|
import matplotlib.pyplot as plt
|
8
9
|
import gymnasium as gym
|
9
10
|
import numpy as np
|
11
|
+
|
10
12
|
from numpy.typing import NDArray
|
11
13
|
|
12
14
|
from job_shop_lib import JobShopInstance, Operation
|
@@ -207,17 +209,34 @@ class SingleJobShopGraphEnv(gym.Env):
|
|
207
209
|
"""Returns current makespan of partial schedule."""
|
208
210
|
return self.dispatcher.schedule.makespan()
|
209
211
|
|
210
|
-
def machine_utilization(
|
211
|
-
|
212
|
+
def machine_utilization( # noqa: DOC201,DOC203
|
213
|
+
self,
|
214
|
+
) -> NDArray[np.float32]:
|
215
|
+
"""Returns utilization percentage for each machine.
|
216
|
+
|
217
|
+
Returns:
|
218
|
+
Utilization percentage for each machine as a numpy array.
|
219
|
+
|
220
|
+
.. deprecated:: 1.1.2
|
221
|
+
This method is deprecated and will be removed in version 2.0.0.
|
222
|
+
"""
|
223
|
+
warnings.warn(
|
224
|
+
"machine_utilization is deprecated and will be removed in "
|
225
|
+
"version 2.0.0",
|
226
|
+
DeprecationWarning,
|
227
|
+
stacklevel=2,
|
228
|
+
)
|
212
229
|
total_time = max(1, self.current_makespan()) # Avoid division by zero
|
213
|
-
machine_busy_time = np.zeros(
|
230
|
+
machine_busy_time = np.zeros(
|
231
|
+
self.instance.num_machines, dtype=np.float32
|
232
|
+
)
|
214
233
|
|
215
234
|
for m_id, m_schedule in enumerate(self.dispatcher.schedule.schedule):
|
216
235
|
machine_busy_time[m_id] = sum(
|
217
236
|
op.operation.duration for op in m_schedule
|
218
237
|
)
|
219
238
|
|
220
|
-
return machine_busy_time / total_time
|
239
|
+
return machine_busy_time / total_time # type: ignore[return-value]
|
221
240
|
|
222
241
|
def _get_observation_space(self) -> gym.spaces.Dict:
|
223
242
|
"""Returns the observation space dictionary."""
|
@@ -414,28 +433,3 @@ class SingleJobShopGraphEnv(gym.Env):
|
|
414
433
|
raise ValidationError(
|
415
434
|
f"Operation {next_operation} requires a machine_id"
|
416
435
|
)
|
417
|
-
|
418
|
-
|
419
|
-
if __name__ == "__main__":
|
420
|
-
from job_shop_lib.dispatching.feature_observers import (
|
421
|
-
FeatureObserverType,
|
422
|
-
FeatureType,
|
423
|
-
)
|
424
|
-
from job_shop_lib.graphs import build_disjunctive_graph
|
425
|
-
from job_shop_lib.benchmarking import load_benchmark_instance
|
426
|
-
|
427
|
-
instance = load_benchmark_instance("ft06")
|
428
|
-
job_shop_graph_ = build_disjunctive_graph(instance)
|
429
|
-
feature_observer_configs_: list[DispatcherObserverConfig] = [
|
430
|
-
DispatcherObserverConfig(
|
431
|
-
FeatureObserverType.IS_READY,
|
432
|
-
kwargs={"feature_types": [FeatureType.JOBS]},
|
433
|
-
)
|
434
|
-
]
|
435
|
-
|
436
|
-
env = SingleJobShopGraphEnv(
|
437
|
-
job_shop_graph=job_shop_graph_,
|
438
|
-
feature_observer_configs=feature_observer_configs_,
|
439
|
-
render_mode="save_video",
|
440
|
-
render_config={"video_config": {"fps": 4}},
|
441
|
-
)
|
@@ -70,14 +70,16 @@ def get_partial_gantt_chart_plotter(
|
|
70
70
|
show_available_operations: bool = False,
|
71
71
|
**kwargs: Any,
|
72
72
|
) -> PartialGanttChartPlotter:
|
73
|
-
"""Returns a function that plots a Gantt chart for an unfinished schedule.
|
73
|
+
r"""Returns a function that plots a Gantt chart for an unfinished schedule.
|
74
74
|
|
75
75
|
Args:
|
76
|
-
title:
|
77
|
-
|
76
|
+
title:
|
77
|
+
The title of the Gantt chart.
|
78
|
+
cmap:
|
79
|
+
The name of the colormap to use.
|
78
80
|
show_available_operations:
|
79
81
|
Whether to show the available operations in the Gantt chart.
|
80
|
-
|
82
|
+
\*\*kwargs: Additional keyword arguments to pass to the
|
81
83
|
:func:`plot_gantt_chart` function.
|
82
84
|
|
83
85
|
Returns:
|
@@ -366,18 +366,25 @@ def plot_disjunctive_graph(
|
|
366
366
|
|
367
367
|
# Add machine colors to the legend
|
368
368
|
if show_machine_colors_in_legend:
|
369
|
+
label_color_pairs = []
|
370
|
+
sorted_machine_colors = sorted(
|
371
|
+
machine_colors.items(), key=lambda x: x[0]
|
372
|
+
)
|
373
|
+
for machine_id, color in sorted_machine_colors:
|
374
|
+
label = None
|
375
|
+
if machine_id == -1:
|
376
|
+
continue
|
377
|
+
if machine_labels is not None:
|
378
|
+
label = machine_labels[machine_id]
|
379
|
+
elif machine_id >= 0:
|
380
|
+
label = f"Machine {machine_id}"
|
381
|
+
|
382
|
+
if label: # Add patch if a label was determined
|
383
|
+
label_color_pairs.append((label, color))
|
384
|
+
|
369
385
|
machine_patches = [
|
370
|
-
matplotlib.patches.Patch(
|
371
|
-
|
372
|
-
label=(
|
373
|
-
machine_labels[machine_id]
|
374
|
-
if machine_labels is not None
|
375
|
-
else f"Machine {machine_id}"
|
376
|
-
),
|
377
|
-
)
|
378
|
-
for machine_id, color in sorted(
|
379
|
-
machine_colors.items(), key=lambda x: x[0]
|
380
|
-
)
|
386
|
+
matplotlib.patches.Patch(color=color, label=label)
|
387
|
+
for label, color in sorted(label_color_pairs)
|
381
388
|
]
|
382
389
|
handles.extend(machine_patches)
|
383
390
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: job-shop-lib
|
3
|
-
Version: 1.1.
|
3
|
+
Version: 1.1.2
|
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
|
@@ -16,9 +16,10 @@ Requires-Dist: gymnasium (>=1.0.0,<2.0.0)
|
|
16
16
|
Requires-Dist: imageio[ffmpeg] (>=2.34.1,<3.0.0)
|
17
17
|
Requires-Dist: matplotlib (>=3,<4)
|
18
18
|
Requires-Dist: networkx (>=3,<4)
|
19
|
-
Requires-Dist: numpy (>=1.26.4,<
|
20
|
-
Requires-Dist: ortools (>=9.9,<10.0)
|
21
|
-
Requires-Dist:
|
19
|
+
Requires-Dist: numpy (>=1.26.4,<3.0.0)
|
20
|
+
Requires-Dist: ortools (>=9.9,<10.0) ; sys_platform != "darwin"
|
21
|
+
Requires-Dist: ortools (>=9.9,<9.13) ; sys_platform == "darwin"
|
22
|
+
Requires-Dist: pyarrow (>=15,<21)
|
22
23
|
Requires-Dist: pygraphviz (>=1.12,<2.0) ; extra == "pygraphviz"
|
23
24
|
Description-Content-Type: text/markdown
|
24
25
|
|
@@ -30,6 +31,7 @@ Description-Content-Type: text/markdown
|
|
30
31
|
|
31
32
|
[](https://github.com/Pabloo22/job_shop_lib/actions/workflows/tests.yaml)
|
32
33
|
[](https://job-shop-lib.readthedocs.io/en/latest/?badge=latest)
|
34
|
+
[](https://codecov.io/gh/Pabloo22/job_shop_lib)
|
33
35
|

|
34
36
|
[](https://github.com/psf/black)
|
35
37
|
[](https://opensource.org/licenses/MIT)
|
@@ -1,8 +1,8 @@
|
|
1
|
-
job_shop_lib/__init__.py,sha256=
|
1
|
+
job_shop_lib/__init__.py,sha256=lVN0bvlkAFwi2T5vOu57gyXNOnwQftdNEwvgWJB-yQ0,639
|
2
2
|
job_shop_lib/_base_solver.py,sha256=p17XmtufNc9Y481cqZUT45pEkUmmW1HWG53dfhIBJH8,1363
|
3
|
-
job_shop_lib/_job_shop_instance.py,sha256=
|
3
|
+
job_shop_lib/_job_shop_instance.py,sha256=FkMBy9Yb8cNEGswI9vlN3Wh4mhtEX-QuDbKvSYUOXcM,18361
|
4
4
|
job_shop_lib/_operation.py,sha256=lwCjgXwWlgESFuV3Yh4SCVofPGCd3hJU4vnK7peREac,4235
|
5
|
-
job_shop_lib/_schedule.py,sha256=
|
5
|
+
job_shop_lib/_schedule.py,sha256=PX3wOv9Cw8NgjBLV3yDJW0mNl7a25nvoEV5Hdv7R_-g,11943
|
6
6
|
job_shop_lib/_scheduled_operation.py,sha256=czrGr87EOTlO2NPolIN5CDigeiCzvQEyra5IZPwSFZc,2801
|
7
7
|
job_shop_lib/benchmarking/__init__.py,sha256=JPnCw5mK7sADAW0HctVKHEDRw22afp9caNh2eUS36Ys,3290
|
8
8
|
job_shop_lib/benchmarking/_load_benchmark.py,sha256=-cgyx0Kn6uAc3KdGFSQb6eUVQjQggmpVKOH9qusNkXI,2930
|
@@ -10,19 +10,19 @@ job_shop_lib/benchmarking/benchmark_instances.json,sha256=F9EvyzFwVxiKAN6rQTsrMh
|
|
10
10
|
job_shop_lib/constraint_programming/__init__.py,sha256=kKQRUxxS_nVFUdXGnf4bQOD9mqrXxZZWElS753A4YiA,454
|
11
11
|
job_shop_lib/constraint_programming/_ortools_solver.py,sha256=oMPeA2VHoYX1ZvmygQ8kYew40ITLAQATmM4OhgVFuXM,10482
|
12
12
|
job_shop_lib/dispatching/__init__.py,sha256=eyiCpCWIsx3LDoSOtPImjYAkI6R8t93kz56hM03WODE,2558
|
13
|
-
job_shop_lib/dispatching/_dispatcher.py,sha256=
|
13
|
+
job_shop_lib/dispatching/_dispatcher.py,sha256=KnV_Kry3Ie81WbKhdpRQtOMsuFDNCuh5Kp2ZnelM-R8,23835
|
14
14
|
job_shop_lib/dispatching/_dispatcher_observer_config.py,sha256=QF2d3rJWwmvutQBAkKxzQ1toJs6eMelT404LGS2z9HQ,2467
|
15
15
|
job_shop_lib/dispatching/_factories.py,sha256=j3MhIwVXiq-B8JMit72ObvXSa2sdgWNhUD86gghL6Gg,4689
|
16
16
|
job_shop_lib/dispatching/_history_observer.py,sha256=Vl8rQaxekUeEB-AyNxyC3c76zQakeh-rdri2iDnZvXw,610
|
17
|
-
job_shop_lib/dispatching/_optimal_operations_observer.py,sha256=
|
17
|
+
job_shop_lib/dispatching/_optimal_operations_observer.py,sha256=2EYxevjpeGMP3do-m0ZmtmjIjmNcxrWOSKzN_bW37gQ,4247
|
18
18
|
job_shop_lib/dispatching/_ready_operation_filters.py,sha256=brhmhoyyoZ98wAEEfneZC-CD-aw9SerZHGMB1DpK8HY,5749
|
19
19
|
job_shop_lib/dispatching/_start_time_calculators.py,sha256=sEtInDnFW9gsKDUEDUGQBaIjDWgCkFYvBca46j8XYfE,6408
|
20
20
|
job_shop_lib/dispatching/_unscheduled_operations_observer.py,sha256=0he-j4OlvqtXAJZD5x1nuBnUKqZUfftVx9NT3CVxPyg,2708
|
21
21
|
job_shop_lib/dispatching/feature_observers/__init__.py,sha256=EuJLvSpJpoXUK8A4UuC2k6Mpa293ZR3oCnnvYivIBtU,2240
|
22
|
-
job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py,sha256=
|
22
|
+
job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py,sha256=tpvqTLIcNmbYROSFT62LiUZ_tI4fHWL_qCULKK43BU4,6429
|
23
23
|
job_shop_lib/dispatching/feature_observers/_duration_observer.py,sha256=fbkUIVScF1iNjdVCYr1ImQm53TfahvVnGXhsRAsgdzY,4129
|
24
|
-
job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py,sha256=
|
25
|
-
job_shop_lib/dispatching/feature_observers/_factory.py,sha256=
|
24
|
+
job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py,sha256=AQIjVp7VRDnb5GuYZlLUwk-xiXSqbsxJW-Ji7NjLoAw,11452
|
25
|
+
job_shop_lib/dispatching/feature_observers/_factory.py,sha256=NyXYK5A1hXsYEeEqngwVRNAFkevY95DglheeqyfFv8s,3217
|
26
26
|
job_shop_lib/dispatching/feature_observers/_feature_observer.py,sha256=a7COlMWd9XC_cpEgHlsj8LEKl8Ty1g77cBm-2goSMHU,8606
|
27
27
|
job_shop_lib/dispatching/feature_observers/_is_completed_observer.py,sha256=EYJOyWL8ApUElLucoHnFlt0g2Ior_1yO7Q8V3FU_Qog,3576
|
28
28
|
job_shop_lib/dispatching/feature_observers/_is_ready_observer.py,sha256=wy_pA-1wmnzVjhq92mdsT2JJHYbfsm79mcMgSgYUCOs,1264
|
@@ -31,21 +31,20 @@ job_shop_lib/dispatching/feature_observers/_position_in_job_observer.py,sha256=W
|
|
31
31
|
job_shop_lib/dispatching/feature_observers/_remaining_operations_observer.py,sha256=5V87lCrJUabEe8AkTGXPu5yS8OGxeN8L3-xNyHmdmLs,1441
|
32
32
|
job_shop_lib/dispatching/rules/__init__.py,sha256=0Nn9FBVmxVYeDeLsd7g7WkmKFBYJqOIDzArbqsC7FAI,2187
|
33
33
|
job_shop_lib/dispatching/rules/_dispatching_rule_factory.py,sha256=5fNpv90fAoR6rcE6NeJOWiB7ir-FVnoONIhHtKJ9H0E,2904
|
34
|
-
job_shop_lib/dispatching/rules/_dispatching_rule_solver.py,sha256=
|
34
|
+
job_shop_lib/dispatching/rules/_dispatching_rule_solver.py,sha256=1_canC1lXZATrQCZaHOY3JOLmTuT6U0Z_QWzgTOLwqI,5917
|
35
35
|
job_shop_lib/dispatching/rules/_dispatching_rules_functions.py,sha256=OldJH2ZcLq4a36yXFRiSICkKAh3RLYlvMIqKyT1OmPE,7606
|
36
36
|
job_shop_lib/dispatching/rules/_machine_chooser_factory.py,sha256=CJ74ujgWXgG8cuULWY6VJkD_b3arTcOjTNLZJTAf8xE,2346
|
37
37
|
job_shop_lib/dispatching/rules/_utils.py,sha256=m5qw4qyfaIvVrkmv51nuhreizr98-cg8AJKt2VTd48w,4603
|
38
38
|
job_shop_lib/exceptions.py,sha256=ARzpoZJCvRIvOesCiqqFSRxkv6w9WwEXx0aBP-l2IKA,1597
|
39
39
|
job_shop_lib/generation/__init__.py,sha256=QaWwuBfBNnOiG0OPiP_CV_flBu9dX7r2o_HwL47tREM,822
|
40
|
-
job_shop_lib/generation/_general_instance_generator.py,sha256=
|
40
|
+
job_shop_lib/generation/_general_instance_generator.py,sha256=b_tnyP4H_buoN7b6lKQRLvDkeZDdys0mpqS3thB5-SQ,6544
|
41
41
|
job_shop_lib/generation/_instance_generator.py,sha256=rT7CAJuv6E0zbmRFE_MFY6iaeZB06BshsBHl2_GyPzU,4567
|
42
|
-
job_shop_lib/generation/_transformations.py,sha256=ZigQTBsS3xgB2FhBu9MpsFs7A-_VY3840V_RtOIhCBk,5296
|
43
42
|
job_shop_lib/generation/_utils.py,sha256=TYBGt4Zjw94l6ukIjXBVAK3lmrrZXdyzyq_r1DMlL-E,3986
|
44
43
|
job_shop_lib/graphs/__init__.py,sha256=wlYIiXTuZRE6Kx3K0RpPUoZikzoegBuN2hcdqMODtGk,2433
|
45
44
|
job_shop_lib/graphs/_build_disjunctive_graph.py,sha256=UbUYdeQaaeEqLchcKJGHEFGl4wElfGLb1o_R-u8wqnA,5120
|
46
|
-
job_shop_lib/graphs/_build_resource_task_graphs.py,sha256=
|
45
|
+
job_shop_lib/graphs/_build_resource_task_graphs.py,sha256=vIy_EkQjgQAd5YyJxKAuGf7CLTjgCfhz-fYrObF4DTU,6962
|
47
46
|
job_shop_lib/graphs/_constants.py,sha256=K-GeVvh_DTWpo1KOX1clmxWS_pkUJbq19yOBmrCVIxI,1086
|
48
|
-
job_shop_lib/graphs/_job_shop_graph.py,sha256
|
47
|
+
job_shop_lib/graphs/_job_shop_graph.py,sha256=--9sbPpCiqC71kzmsPWFvMfqpx_gq4TL2x0HI2d-TEM,11427
|
49
48
|
job_shop_lib/graphs/_node.py,sha256=Ue3_BqVPU4w9S70kDChfsMJ09spnW7Dg83osSzi7fko,5990
|
50
49
|
job_shop_lib/graphs/graph_updaters/__init__.py,sha256=YOwb0RYypO9cEG-Nl3Ooj1yvAoyWDMNE_NAaUTyjzIw,658
|
51
50
|
job_shop_lib/graphs/graph_updaters/_disjunctive_graph_updater.py,sha256=-t0T8W-Jz9TJQR9-ljPkcDsDC4CwJAfs2nUF3zjEtuw,4369
|
@@ -54,21 +53,21 @@ job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py,sha256=9NG3pu7Z5h-
|
|
54
53
|
job_shop_lib/graphs/graph_updaters/_utils.py,sha256=sdw2Vo75P9c6Fy-YBlfgpXb9gPwHUluTB1E-9WINm_g,730
|
55
54
|
job_shop_lib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
56
55
|
job_shop_lib/reinforcement_learning/__init__.py,sha256=sAVgxylKfBnn2rrz0BFcab1kjvQQ1h-hgldfbkPF--E,1537
|
57
|
-
job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py,sha256=
|
58
|
-
job_shop_lib/reinforcement_learning/_resource_task_graph_observation.py,sha256=
|
56
|
+
job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py,sha256=6nXw67Tfmim3LqlSuQ9Cfg3mMY-VmbMHuXfyOL90jng,15740
|
57
|
+
job_shop_lib/reinforcement_learning/_resource_task_graph_observation.py,sha256=ZqN6kzuXbO0BdA1UKrTEHeiHAKzRNIeuH-dBt90ttEc,12914
|
59
58
|
job_shop_lib/reinforcement_learning/_reward_observers.py,sha256=4Kdyn9Jlp_1sBtVr6raF-ZFtcnKxwyCLykfX53TmuhU,2959
|
60
|
-
job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py,sha256=
|
59
|
+
job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py,sha256=MnQYCVpwX4WHiGhYguHziDUrPIrKmXsjOUDoTmuoCBc,16403
|
61
60
|
job_shop_lib/reinforcement_learning/_types_and_constants.py,sha256=6FpuQkZLV2H8_dXmax49OTgAw7dWQcUEWVWWdMLR7bs,1752
|
62
61
|
job_shop_lib/reinforcement_learning/_utils.py,sha256=aHgNdW7YvUH8QM3l7NGIfrgzfpzGoklyYm1jM2Isi6Q,6043
|
63
62
|
job_shop_lib/visualization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
64
63
|
job_shop_lib/visualization/gantt/__init__.py,sha256=xMvuNph6bfwulHYqqklCj_6SUQgRzvC92Yul75F3Zlg,1250
|
65
64
|
job_shop_lib/visualization/gantt/_gantt_chart_creator.py,sha256=FgE4SmKLYKnS7dfTFgnBklWhwGyIo0DKWVkmxusDmp8,8606
|
66
|
-
job_shop_lib/visualization/gantt/_gantt_chart_video_and_gif_creation.py,sha256
|
65
|
+
job_shop_lib/visualization/gantt/_gantt_chart_video_and_gif_creation.py,sha256=jessyvLFgS60HEEmO7o6imo2f0uEpCLkyo-p_SBpE7Y,14571
|
67
66
|
job_shop_lib/visualization/gantt/_plot_gantt_chart.py,sha256=_4UGUTRuIw0tLzsJD9Gcf10aIy2YkDzTGsNTzFb5r0Y,6809
|
68
67
|
job_shop_lib/visualization/graphs/__init__.py,sha256=HUWzfgQLeklNROtjnxeJX_FIySo_baTXO6klx0zUVpQ,630
|
69
|
-
job_shop_lib/visualization/graphs/_plot_disjunctive_graph.py,sha256=
|
70
|
-
job_shop_lib/visualization/graphs/_plot_resource_task_graph.py,sha256=
|
71
|
-
job_shop_lib-1.1.
|
72
|
-
job_shop_lib-1.1.
|
73
|
-
job_shop_lib-1.1.
|
74
|
-
job_shop_lib-1.1.
|
68
|
+
job_shop_lib/visualization/graphs/_plot_disjunctive_graph.py,sha256=L9_ZGgvCFpGc2rTOdZESdtydFQqShjqedimIOhqZx6Y,16209
|
69
|
+
job_shop_lib/visualization/graphs/_plot_resource_task_graph.py,sha256=nkkdZ-9_OBevw72Frecwzv1y3WyhGZ9r9lz0y9MXvZ8,13192
|
70
|
+
job_shop_lib-1.1.2.dist-info/LICENSE,sha256=9mggivMGd5taAu3xbmBway-VQZMBzurBGHofFopvUsQ,1069
|
71
|
+
job_shop_lib-1.1.2.dist-info/METADATA,sha256=i8mPexr3bUMJz7bKPC0D0VYKsFzYdMksnK4eOJFCQ20,16624
|
72
|
+
job_shop_lib-1.1.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
73
|
+
job_shop_lib-1.1.2.dist-info/RECORD,,
|
@@ -1,167 +0,0 @@
|
|
1
|
-
"""Classes for generating transformed JobShopInstance objects."""
|
2
|
-
|
3
|
-
import abc
|
4
|
-
import copy
|
5
|
-
import random
|
6
|
-
|
7
|
-
from job_shop_lib import JobShopInstance, Operation
|
8
|
-
|
9
|
-
|
10
|
-
class Transformation(abc.ABC):
|
11
|
-
"""Base class for transformations applied to JobShopInstance objects."""
|
12
|
-
|
13
|
-
def __init__(self, suffix: str = ""):
|
14
|
-
self.suffix = suffix
|
15
|
-
self.counter = 0
|
16
|
-
|
17
|
-
@abc.abstractmethod
|
18
|
-
def apply(self, instance: JobShopInstance) -> JobShopInstance:
|
19
|
-
"""Applies the transformation to a given JobShopInstance.
|
20
|
-
|
21
|
-
Args:
|
22
|
-
instance: The JobShopInstance to transform.
|
23
|
-
|
24
|
-
Returns:
|
25
|
-
A new JobShopInstance with the transformation applied.
|
26
|
-
"""
|
27
|
-
|
28
|
-
def __call__(self, instance: JobShopInstance) -> JobShopInstance:
|
29
|
-
instance = self.apply(instance)
|
30
|
-
suffix = f"{self.suffix}_id={self.counter}"
|
31
|
-
instance.name += suffix
|
32
|
-
self.counter += 1
|
33
|
-
return instance
|
34
|
-
|
35
|
-
|
36
|
-
# pylint: disable=too-few-public-methods
|
37
|
-
class RemoveMachines(Transformation):
|
38
|
-
"""Removes operations associated with randomly selected machines until
|
39
|
-
there are exactly num_machines machines left."""
|
40
|
-
|
41
|
-
def __init__(self, num_machines: int, suffix: str | None = None):
|
42
|
-
if suffix is None:
|
43
|
-
suffix = f"_machines={num_machines}"
|
44
|
-
super().__init__(suffix=suffix)
|
45
|
-
self.num_machines = num_machines
|
46
|
-
|
47
|
-
def apply(self, instance: JobShopInstance) -> JobShopInstance:
|
48
|
-
if instance.num_machines <= self.num_machines:
|
49
|
-
return instance # No need to remove machines
|
50
|
-
|
51
|
-
# Select machine indices to keep
|
52
|
-
machines_to_keep = set(
|
53
|
-
random.sample(range(instance.num_machines), self.num_machines)
|
54
|
-
)
|
55
|
-
|
56
|
-
# Re-index machines
|
57
|
-
machine_reindex_map = {
|
58
|
-
old_id: new_id
|
59
|
-
for new_id, old_id in enumerate(sorted(machines_to_keep))
|
60
|
-
}
|
61
|
-
|
62
|
-
new_jobs = []
|
63
|
-
for job in instance.jobs:
|
64
|
-
# Keep operations whose machine_id is in machines_to_keep and
|
65
|
-
# re-index them
|
66
|
-
new_jobs.append(
|
67
|
-
[
|
68
|
-
Operation(machine_reindex_map[op.machine_id], op.duration)
|
69
|
-
for op in job
|
70
|
-
if op.machine_id in machines_to_keep
|
71
|
-
]
|
72
|
-
)
|
73
|
-
|
74
|
-
return JobShopInstance(new_jobs, instance.name)
|
75
|
-
|
76
|
-
|
77
|
-
# pylint: disable=too-few-public-methods
|
78
|
-
class AddDurationNoise(Transformation):
|
79
|
-
"""Adds uniform integer noise to operation durations."""
|
80
|
-
|
81
|
-
def __init__(
|
82
|
-
self,
|
83
|
-
min_duration: int = 1,
|
84
|
-
max_duration: int = 100,
|
85
|
-
noise_level: int = 10,
|
86
|
-
suffix: str | None = None,
|
87
|
-
):
|
88
|
-
if suffix is None:
|
89
|
-
suffix = f"_noise={noise_level}"
|
90
|
-
super().__init__(suffix=suffix)
|
91
|
-
self.min_duration = min_duration
|
92
|
-
self.max_duration = max_duration
|
93
|
-
self.noise_level = noise_level
|
94
|
-
|
95
|
-
def apply(self, instance: JobShopInstance) -> JobShopInstance:
|
96
|
-
new_jobs = []
|
97
|
-
for job in instance.jobs:
|
98
|
-
new_job = []
|
99
|
-
for op in job:
|
100
|
-
noise = random.randint(-self.noise_level, self.noise_level)
|
101
|
-
new_duration = max(
|
102
|
-
self.min_duration,
|
103
|
-
min(self.max_duration, op.duration + noise),
|
104
|
-
)
|
105
|
-
|
106
|
-
new_job.append(Operation(op.machine_id, new_duration))
|
107
|
-
new_jobs.append(new_job)
|
108
|
-
|
109
|
-
return JobShopInstance(new_jobs, instance.name)
|
110
|
-
|
111
|
-
|
112
|
-
class RemoveJobs(Transformation):
|
113
|
-
"""Removes jobs randomly until the number of jobs is within a specified
|
114
|
-
range.
|
115
|
-
|
116
|
-
Args:
|
117
|
-
min_jobs:
|
118
|
-
The minimum number of jobs to remain in the instance.
|
119
|
-
max_jobs:
|
120
|
-
The maximum number of jobs to remain in the instance.
|
121
|
-
target_jobs:
|
122
|
-
If specified, the number of jobs to remain in the
|
123
|
-
instance. Overrides ``min_jobs`` and ``max_jobs``.
|
124
|
-
"""
|
125
|
-
|
126
|
-
def __init__(
|
127
|
-
self,
|
128
|
-
min_jobs: int,
|
129
|
-
max_jobs: int,
|
130
|
-
target_jobs: int | None = None,
|
131
|
-
suffix: str | None = None,
|
132
|
-
):
|
133
|
-
if suffix is None:
|
134
|
-
suffix = f"_jobs={min_jobs}-{max_jobs}"
|
135
|
-
super().__init__(suffix=suffix)
|
136
|
-
self.min_jobs = min_jobs
|
137
|
-
self.max_jobs = max_jobs
|
138
|
-
self.target_jobs = target_jobs
|
139
|
-
|
140
|
-
def apply(self, instance: JobShopInstance) -> JobShopInstance:
|
141
|
-
if self.target_jobs is None:
|
142
|
-
target_jobs = random.randint(self.min_jobs, self.max_jobs)
|
143
|
-
else:
|
144
|
-
target_jobs = self.target_jobs
|
145
|
-
new_jobs = copy.deepcopy(instance.jobs)
|
146
|
-
|
147
|
-
while len(new_jobs) > target_jobs:
|
148
|
-
new_jobs.pop(random.randint(0, len(new_jobs) - 1))
|
149
|
-
|
150
|
-
return JobShopInstance(new_jobs, instance.name)
|
151
|
-
|
152
|
-
@staticmethod
|
153
|
-
def remove_job(
|
154
|
-
instance: JobShopInstance, job_index: int
|
155
|
-
) -> JobShopInstance:
|
156
|
-
"""Removes a specific job from the instance.
|
157
|
-
|
158
|
-
Args:
|
159
|
-
instance: The JobShopInstance from which to remove the job.
|
160
|
-
job_index: The index of the job to remove.
|
161
|
-
|
162
|
-
Returns:
|
163
|
-
A new JobShopInstance with the specified job removed.
|
164
|
-
"""
|
165
|
-
new_jobs = copy.deepcopy(instance.jobs)
|
166
|
-
new_jobs.pop(job_index)
|
167
|
-
return JobShopInstance(new_jobs, instance.name)
|
File without changes
|
File without changes
|