job-shop-lib 1.0.2__py3-none-any.whl → 1.0.3__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 +26 -26
- job_shop_lib/_operation.py +2 -4
- job_shop_lib/_schedule.py +11 -11
- job_shop_lib/benchmarking/_load_benchmark.py +3 -3
- job_shop_lib/constraint_programming/_ortools_solver.py +6 -6
- job_shop_lib/dispatching/_dispatcher.py +19 -19
- job_shop_lib/dispatching/_dispatcher_observer_config.py +3 -3
- job_shop_lib/dispatching/_factories.py +2 -3
- job_shop_lib/dispatching/_history_observer.py +1 -2
- job_shop_lib/dispatching/_optimal_operations_observer.py +3 -4
- job_shop_lib/dispatching/_ready_operation_filters.py +18 -19
- job_shop_lib/dispatching/_unscheduled_operations_observer.py +3 -2
- job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py +10 -12
- job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py +1 -3
- job_shop_lib/dispatching/feature_observers/_factory.py +11 -12
- job_shop_lib/dispatching/feature_observers/_feature_observer.py +8 -9
- job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +2 -4
- job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +2 -4
- job_shop_lib/dispatching/rules/_dispatching_rule_factory.py +2 -4
- job_shop_lib/dispatching/rules/_dispatching_rule_solver.py +15 -20
- job_shop_lib/dispatching/rules/_dispatching_rules_functions.py +9 -10
- job_shop_lib/dispatching/rules/_machine_chooser_factory.py +2 -3
- job_shop_lib/dispatching/rules/_utils.py +7 -8
- job_shop_lib/generation/_general_instance_generator.py +22 -20
- job_shop_lib/generation/_instance_generator.py +8 -8
- job_shop_lib/generation/_transformations.py +4 -5
- job_shop_lib/generation/_utils.py +16 -8
- job_shop_lib/graphs/_job_shop_graph.py +13 -14
- job_shop_lib/graphs/_node.py +6 -12
- job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +2 -4
- job_shop_lib/reinforcement_learning/__init__.py +2 -1
- job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +17 -17
- job_shop_lib/reinforcement_learning/_resource_task_graph_observation.py +1 -1
- job_shop_lib/reinforcement_learning/_reward_observers.py +1 -3
- job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +22 -24
- job_shop_lib/reinforcement_learning/_utils.py +2 -2
- job_shop_lib/visualization/gantt/_gantt_chart_creator.py +11 -11
- job_shop_lib/visualization/gantt/_gantt_chart_video_and_gif_creation.py +21 -21
- job_shop_lib/visualization/gantt/_plot_gantt_chart.py +12 -14
- job_shop_lib/visualization/graphs/_plot_disjunctive_graph.py +15 -17
- job_shop_lib/visualization/graphs/_plot_resource_task_graph.py +22 -24
- {job_shop_lib-1.0.2.dist-info → job_shop_lib-1.0.3.dist-info}/METADATA +1 -1
- {job_shop_lib-1.0.2.dist-info → job_shop_lib-1.0.3.dist-info}/RECORD +46 -46
- {job_shop_lib-1.0.2.dist-info → job_shop_lib-1.0.3.dist-info}/LICENSE +0 -0
- {job_shop_lib-1.0.2.dist-info → job_shop_lib-1.0.3.dist-info}/WHEEL +0 -0
@@ -4,7 +4,6 @@ This functions are used by the `Dispatcher` class to reduce the
|
|
4
4
|
amount of available operations to choose from.
|
5
5
|
"""
|
6
6
|
|
7
|
-
from typing import List, Set
|
8
7
|
from collections.abc import Callable
|
9
8
|
|
10
9
|
from job_shop_lib import Operation
|
@@ -12,13 +11,13 @@ from job_shop_lib.dispatching import Dispatcher
|
|
12
11
|
|
13
12
|
|
14
13
|
ReadyOperationsFilter = Callable[
|
15
|
-
[Dispatcher,
|
14
|
+
[Dispatcher, list[Operation]], list[Operation]
|
16
15
|
]
|
17
16
|
|
18
17
|
|
19
18
|
def filter_non_idle_machines(
|
20
|
-
dispatcher: Dispatcher, operations:
|
21
|
-
) ->
|
19
|
+
dispatcher: Dispatcher, operations: list[Operation]
|
20
|
+
) -> list[Operation]:
|
22
21
|
"""Filters out all the operations associated with non-idle machines.
|
23
22
|
|
24
23
|
A machine is considered idle if there are no ongoing operations
|
@@ -41,7 +40,7 @@ def filter_non_idle_machines(
|
|
41
40
|
|
42
41
|
# Filter operations to keep those that are associated with at least one
|
43
42
|
# idle machine
|
44
|
-
filtered_operations:
|
43
|
+
filtered_operations: list[Operation] = []
|
45
44
|
for operation in operations:
|
46
45
|
if all(
|
47
46
|
machine_id in non_idle_machines
|
@@ -55,7 +54,7 @@ def filter_non_idle_machines(
|
|
55
54
|
|
56
55
|
def _get_non_idle_machines(
|
57
56
|
dispatcher: Dispatcher, current_time: int
|
58
|
-
) ->
|
57
|
+
) -> set[int]:
|
59
58
|
"""Returns the set of machine ids that are currently busy (i.e., have at
|
60
59
|
least one uncompleted operation)."""
|
61
60
|
|
@@ -71,8 +70,8 @@ def _get_non_idle_machines(
|
|
71
70
|
|
72
71
|
|
73
72
|
def filter_non_immediate_operations(
|
74
|
-
dispatcher: Dispatcher, operations:
|
75
|
-
) ->
|
73
|
+
dispatcher: Dispatcher, operations: list[Operation]
|
74
|
+
) -> list[Operation]:
|
76
75
|
"""Filters out all the operations that can't start immediately.
|
77
76
|
|
78
77
|
An operation can start immediately if its earliest start time is the
|
@@ -87,7 +86,7 @@ def filter_non_immediate_operations(
|
|
87
86
|
"""
|
88
87
|
|
89
88
|
min_start_time = dispatcher.min_start_time(operations)
|
90
|
-
immediate_operations:
|
89
|
+
immediate_operations: list[Operation] = []
|
91
90
|
for operation in operations:
|
92
91
|
start_time = dispatcher.earliest_start_time(operation)
|
93
92
|
if start_time == min_start_time:
|
@@ -97,8 +96,8 @@ def filter_non_immediate_operations(
|
|
97
96
|
|
98
97
|
|
99
98
|
def filter_dominated_operations(
|
100
|
-
dispatcher: Dispatcher, operations:
|
101
|
-
) ->
|
99
|
+
dispatcher: Dispatcher, operations: list[Operation]
|
100
|
+
) -> list[Operation]:
|
102
101
|
"""Filters out all the operations that are dominated.
|
103
102
|
An operation is dominated if there is another operation that ends before
|
104
103
|
it starts on the same machine.
|
@@ -106,7 +105,7 @@ def filter_dominated_operations(
|
|
106
105
|
|
107
106
|
min_machine_end_times = _get_min_machine_end_times(dispatcher, operations)
|
108
107
|
|
109
|
-
non_dominated_operations:
|
108
|
+
non_dominated_operations: list[Operation] = []
|
110
109
|
for operation in operations:
|
111
110
|
# One benchmark instance has an operation with duration 0
|
112
111
|
if operation.duration == 0:
|
@@ -122,13 +121,13 @@ def filter_dominated_operations(
|
|
122
121
|
|
123
122
|
|
124
123
|
def filter_non_immediate_machines(
|
125
|
-
dispatcher: Dispatcher, operations:
|
126
|
-
) ->
|
124
|
+
dispatcher: Dispatcher, operations: list[Operation]
|
125
|
+
) -> list[Operation]:
|
127
126
|
"""Filters out all the operations associated with machines which earliest
|
128
127
|
operation is not the current time."""
|
129
128
|
|
130
129
|
is_immediate_machine = _get_immediate_machines(dispatcher, operations)
|
131
|
-
non_dominated_operations:
|
130
|
+
non_dominated_operations: list[Operation] = []
|
132
131
|
for operation in operations:
|
133
132
|
if any(
|
134
133
|
is_immediate_machine[machine_id]
|
@@ -140,8 +139,8 @@ def filter_non_immediate_machines(
|
|
140
139
|
|
141
140
|
|
142
141
|
def _get_min_machine_end_times(
|
143
|
-
dispatcher: Dispatcher, available_operations:
|
144
|
-
) ->
|
142
|
+
dispatcher: Dispatcher, available_operations: list[Operation]
|
143
|
+
) -> list[float]:
|
145
144
|
end_times_per_machine = [float("inf")] * dispatcher.instance.num_machines
|
146
145
|
for op in available_operations:
|
147
146
|
for machine_id in op.machines:
|
@@ -153,8 +152,8 @@ def _get_min_machine_end_times(
|
|
153
152
|
|
154
153
|
|
155
154
|
def _get_immediate_machines(
|
156
|
-
dispatcher: Dispatcher, available_operations:
|
157
|
-
) ->
|
155
|
+
dispatcher: Dispatcher, available_operations: list[Operation]
|
156
|
+
) -> list[bool]:
|
158
157
|
"""Returns the machine ids of the machines that have at least one
|
159
158
|
operation with the lowest start time (i.e. the start time)."""
|
160
159
|
working_machines = [False] * dispatcher.instance.num_machines
|
@@ -3,7 +3,6 @@
|
|
3
3
|
import collections
|
4
4
|
from collections.abc import Iterable
|
5
5
|
import itertools
|
6
|
-
from typing import Deque, List
|
7
6
|
|
8
7
|
from job_shop_lib import Operation, ScheduledOperation
|
9
8
|
from job_shop_lib.dispatching import DispatcherObserver, Dispatcher
|
@@ -19,7 +18,9 @@ class UnscheduledOperationsObserver(DispatcherObserver):
|
|
19
18
|
|
20
19
|
def __init__(self, dispatcher: Dispatcher, *, subscribe: bool = True):
|
21
20
|
super().__init__(dispatcher, subscribe=subscribe)
|
22
|
-
self.unscheduled_operations_per_job:
|
21
|
+
self.unscheduled_operations_per_job: list[
|
22
|
+
collections.deque[Operation]
|
23
|
+
] = []
|
23
24
|
self.reset()
|
24
25
|
# In case the dispatcher has already scheduled some operations,
|
25
26
|
# we need to remove them.
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
from collections import defaultdict
|
4
4
|
from collections.abc import Sequence
|
5
|
-
|
5
|
+
|
6
6
|
# The Self type can be imported directly from Python’s typing module in
|
7
7
|
# version 3.11 and beyond. We use the typing_extensions module to support
|
8
8
|
# python >=3.10
|
@@ -77,8 +77,8 @@ class CompositeFeatureObserver(FeatureObserver):
|
|
77
77
|
dispatcher: Dispatcher,
|
78
78
|
*,
|
79
79
|
subscribe: bool = True,
|
80
|
-
feature_types:
|
81
|
-
feature_observers:
|
80
|
+
feature_types: list[FeatureType] | FeatureType | None = None,
|
81
|
+
feature_observers: list[FeatureObserver] | None = None,
|
82
82
|
):
|
83
83
|
if feature_observers is None:
|
84
84
|
feature_observers = [
|
@@ -97,7 +97,7 @@ class CompositeFeatureObserver(FeatureObserver):
|
|
97
97
|
f"Composite feature types: {feature_types}"
|
98
98
|
)
|
99
99
|
self.feature_observers = feature_observers
|
100
|
-
self.column_names:
|
100
|
+
self.column_names: dict[FeatureType, list[str]] = defaultdict(list)
|
101
101
|
super().__init__(dispatcher, subscribe=subscribe)
|
102
102
|
self._set_column_names()
|
103
103
|
|
@@ -106,12 +106,10 @@ class CompositeFeatureObserver(FeatureObserver):
|
|
106
106
|
cls,
|
107
107
|
dispatcher: Dispatcher,
|
108
108
|
feature_observer_configs: Sequence[
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
FeatureObserverConfig,
|
114
|
-
],
|
109
|
+
str
|
110
|
+
| FeatureObserverType
|
111
|
+
| type[FeatureObserver]
|
112
|
+
| FeatureObserverConfig
|
115
113
|
],
|
116
114
|
subscribe: bool = True,
|
117
115
|
) -> Self:
|
@@ -136,7 +134,7 @@ class CompositeFeatureObserver(FeatureObserver):
|
|
136
134
|
return composite_observer
|
137
135
|
|
138
136
|
@property
|
139
|
-
def features_as_dataframe(self) ->
|
137
|
+
def features_as_dataframe(self) -> dict[FeatureType, pd.DataFrame]:
|
140
138
|
"""Returns the features as a dictionary of `pd.DataFrame` instances."""
|
141
139
|
return {
|
142
140
|
feature_type: pd.DataFrame(
|
@@ -146,7 +144,7 @@ class CompositeFeatureObserver(FeatureObserver):
|
|
146
144
|
}
|
147
145
|
|
148
146
|
def initialize_features(self):
|
149
|
-
features:
|
147
|
+
features: dict[FeatureType, list[NDArray[np.float32]]] = defaultdict(
|
150
148
|
list
|
151
149
|
)
|
152
150
|
for observer in self.feature_observers:
|
@@ -1,7 +1,5 @@
|
|
1
1
|
"""Home of the `EarliestStartTimeObserver` class."""
|
2
2
|
|
3
|
-
from typing import List, Optional, Union
|
4
|
-
|
5
3
|
import numpy as np
|
6
4
|
from numpy.typing import NDArray
|
7
5
|
|
@@ -77,7 +75,7 @@ class EarliestStartTimeObserver(FeatureObserver):
|
|
77
75
|
dispatcher: Dispatcher,
|
78
76
|
*,
|
79
77
|
subscribe: bool = True,
|
80
|
-
feature_types:
|
78
|
+
feature_types: list[FeatureType] | FeatureType | None = None,
|
81
79
|
):
|
82
80
|
|
83
81
|
# Earliest start times initialization
|
@@ -1,7 +1,6 @@
|
|
1
1
|
"""Contains factory functions for creating :class:`FeatureObserver`s."""
|
2
2
|
|
3
3
|
from enum import Enum
|
4
|
-
from typing import Union, Type
|
5
4
|
|
6
5
|
from job_shop_lib.dispatching import DispatcherObserverConfig
|
7
6
|
from job_shop_lib.dispatching.feature_observers import (
|
@@ -37,25 +36,25 @@ class FeatureObserverType(str, Enum):
|
|
37
36
|
|
38
37
|
|
39
38
|
# FeatureObserverConfig = DispatcherObserverConfig[
|
40
|
-
#
|
39
|
+
# type[FeatureObserver] | FeatureObserverType | str
|
41
40
|
# ]
|
42
41
|
# FeatureObserverConfig = DispatcherObserverConfig[
|
43
|
-
# Union[
|
42
|
+
# Union[type[FeatureObserver], FeatureObserverType, str]
|
44
43
|
# ]
|
45
44
|
FeatureObserverConfig = (
|
46
|
-
DispatcherObserverConfig[
|
45
|
+
DispatcherObserverConfig[type[FeatureObserver]]
|
47
46
|
| DispatcherObserverConfig[FeatureObserverType]
|
48
47
|
| DispatcherObserverConfig[str]
|
49
48
|
)
|
50
49
|
|
51
50
|
|
52
51
|
def feature_observer_factory(
|
53
|
-
feature_observer_type:
|
54
|
-
str
|
55
|
-
FeatureObserverType
|
56
|
-
|
57
|
-
FeatureObserverConfig
|
58
|
-
|
52
|
+
feature_observer_type: (
|
53
|
+
str
|
54
|
+
| FeatureObserverType
|
55
|
+
| type[FeatureObserver]
|
56
|
+
| FeatureObserverConfig
|
57
|
+
),
|
59
58
|
**kwargs,
|
60
59
|
) -> FeatureObserver:
|
61
60
|
"""Creates and returns a :class:`FeatureObserver` based on the specified
|
@@ -77,12 +76,12 @@ def feature_observer_factory(
|
|
77
76
|
**feature_observer_type.kwargs,
|
78
77
|
**kwargs,
|
79
78
|
)
|
80
|
-
# if the instance is of type
|
79
|
+
# if the instance is of type type[FeatureObserver] we can just
|
81
80
|
# call the object constructor with the keyword arguments
|
82
81
|
if isinstance(feature_observer_type, type):
|
83
82
|
return feature_observer_type(**kwargs)
|
84
83
|
|
85
|
-
mapping: dict[FeatureObserverType,
|
84
|
+
mapping: dict[FeatureObserverType, type[FeatureObserver]] = {
|
86
85
|
FeatureObserverType.IS_READY: IsReadyObserver,
|
87
86
|
FeatureObserverType.EARLIEST_START_TIME: EarliestStartTimeObserver,
|
88
87
|
FeatureObserverType.DURATION: DurationObserver,
|
@@ -1,7 +1,6 @@
|
|
1
1
|
"""Home of the `FeatureObserver` class and `FeatureType` enum."""
|
2
2
|
|
3
3
|
import enum
|
4
|
-
from typing import Optional, Union, Dict, List, Tuple
|
5
4
|
|
6
5
|
import numpy as np
|
7
6
|
|
@@ -65,7 +64,7 @@ class FeatureObserver(DispatcherObserver):
|
|
65
64
|
"""
|
66
65
|
|
67
66
|
_is_singleton = False
|
68
|
-
_feature_sizes:
|
67
|
+
_feature_sizes: dict[FeatureType, int] | int = 1
|
69
68
|
_supported_feature_types = list(FeatureType)
|
70
69
|
|
71
70
|
__slots__ = {
|
@@ -85,7 +84,7 @@ class FeatureObserver(DispatcherObserver):
|
|
85
84
|
dispatcher: Dispatcher,
|
86
85
|
*,
|
87
86
|
subscribe: bool = True,
|
88
|
-
feature_types:
|
87
|
+
feature_types: list[FeatureType] | FeatureType | None = None,
|
89
88
|
):
|
90
89
|
feature_types = self._get_feature_types_list(feature_types)
|
91
90
|
if isinstance(self._feature_sizes, int):
|
@@ -117,7 +116,7 @@ class FeatureObserver(DispatcherObserver):
|
|
117
116
|
self.initialize_features()
|
118
117
|
|
119
118
|
@property
|
120
|
-
def feature_sizes(self) ->
|
119
|
+
def feature_sizes(self) -> dict[FeatureType, int]:
|
121
120
|
"""Returns the size of the features.
|
122
121
|
|
123
122
|
The size of the features is the number of values being observed for
|
@@ -135,12 +134,12 @@ class FeatureObserver(DispatcherObserver):
|
|
135
134
|
return self._feature_sizes
|
136
135
|
|
137
136
|
@property
|
138
|
-
def supported_feature_types(self) ->
|
137
|
+
def supported_feature_types(self) -> list[FeatureType]:
|
139
138
|
"""Returns the supported feature types."""
|
140
139
|
return self._supported_feature_types
|
141
140
|
|
142
141
|
@property
|
143
|
-
def feature_dimensions(self) ->
|
142
|
+
def feature_dimensions(self) -> dict[FeatureType, tuple[int, int]]:
|
144
143
|
"""A dictionary containing the shape of each :class:`FeatureType`."""
|
145
144
|
feature_dimensions = {}
|
146
145
|
for feature_type, array in self.features.items():
|
@@ -171,7 +170,7 @@ class FeatureObserver(DispatcherObserver):
|
|
171
170
|
self.initialize_features()
|
172
171
|
|
173
172
|
def set_features_to_zero(
|
174
|
-
self, exclude:
|
173
|
+
self, exclude: FeatureType | list[FeatureType] | None = None
|
175
174
|
):
|
176
175
|
"""Sets all features to zero except for the ones specified in
|
177
176
|
``exclude``.
|
@@ -197,8 +196,8 @@ class FeatureObserver(DispatcherObserver):
|
|
197
196
|
|
198
197
|
def _get_feature_types_list(
|
199
198
|
self,
|
200
|
-
feature_types:
|
201
|
-
) ->
|
199
|
+
feature_types: list[FeatureType] | FeatureType | None,
|
200
|
+
) -> list[FeatureType]:
|
202
201
|
"""Returns a list of feature types.
|
203
202
|
|
204
203
|
Args:
|
@@ -1,6 +1,5 @@
|
|
1
1
|
"""Home of the `IsCompletedObserver` class."""
|
2
2
|
|
3
|
-
from typing import Optional, Union, List
|
4
3
|
import numpy as np
|
5
4
|
|
6
5
|
from job_shop_lib import ScheduledOperation
|
@@ -40,7 +39,7 @@ class IsCompletedObserver(FeatureObserver):
|
|
40
39
|
self,
|
41
40
|
dispatcher: Dispatcher,
|
42
41
|
*,
|
43
|
-
feature_types:
|
42
|
+
feature_types: list[FeatureType] | FeatureType | None = None,
|
44
43
|
subscribe: bool = True,
|
45
44
|
):
|
46
45
|
feature_types = self._get_feature_types_list(feature_types)
|
@@ -86,8 +85,7 @@ class IsCompletedObserver(FeatureObserver):
|
|
86
85
|
for op in self.dispatcher.completed_operations():
|
87
86
|
num_completed_ops_per_job[op.operation.job_id] += 1
|
88
87
|
self.features[FeatureType.JOBS][:, 0] = (
|
89
|
-
num_completed_ops_per_job
|
90
|
-
== self._num_of_operations_per_job
|
88
|
+
num_completed_ops_per_job == self._num_of_operations_per_job
|
91
89
|
).astype(np.float32)
|
92
90
|
|
93
91
|
def reset(self):
|
@@ -1,7 +1,5 @@
|
|
1
1
|
"""Home of the `IsReadyObserver` class."""
|
2
2
|
|
3
|
-
from typing import List
|
4
|
-
|
5
3
|
from job_shop_lib.dispatching.feature_observers import (
|
6
4
|
FeatureObserver,
|
7
5
|
FeatureType,
|
@@ -18,7 +16,7 @@ class IsReadyObserver(FeatureObserver):
|
|
18
16
|
feature_ids = self._get_ready_feature_ids(feature_type)
|
19
17
|
feature[feature_ids, 0] = 1.0
|
20
18
|
|
21
|
-
def _get_ready_feature_ids(self, feature_type: FeatureType) ->
|
19
|
+
def _get_ready_feature_ids(self, feature_type: FeatureType) -> list[int]:
|
22
20
|
if feature_type == FeatureType.OPERATIONS:
|
23
21
|
return self._get_ready_operations()
|
24
22
|
if feature_type == FeatureType.MACHINES:
|
@@ -30,6 +28,6 @@ class IsReadyObserver(FeatureObserver):
|
|
30
28
|
def reset(self):
|
31
29
|
self.initialize_features()
|
32
30
|
|
33
|
-
def _get_ready_operations(self) ->
|
31
|
+
def _get_ready_operations(self) -> list[int]:
|
34
32
|
available_operations = self.dispatcher.available_operations()
|
35
33
|
return [operation.operation_id for operation in available_operations]
|
@@ -5,8 +5,6 @@ The factory functions create and return the appropriate functions based on the
|
|
5
5
|
specified names or enums.
|
6
6
|
"""
|
7
7
|
|
8
|
-
from typing import Dict, Union
|
9
|
-
|
10
8
|
from enum import Enum
|
11
9
|
from collections.abc import Callable
|
12
10
|
|
@@ -33,7 +31,7 @@ class DispatchingRuleType(str, Enum):
|
|
33
31
|
|
34
32
|
|
35
33
|
def dispatching_rule_factory(
|
36
|
-
dispatching_rule:
|
34
|
+
dispatching_rule: str | DispatchingRuleType,
|
37
35
|
) -> Callable[[Dispatcher], Operation]:
|
38
36
|
"""Creates and returns a dispatching rule function based on the specified
|
39
37
|
dispatching rule name.
|
@@ -57,7 +55,7 @@ def dispatching_rule_factory(
|
|
57
55
|
If the dispatching_rule argument is not recognized or it is
|
58
56
|
not supported.
|
59
57
|
"""
|
60
|
-
dispatching_rules:
|
58
|
+
dispatching_rules: dict[
|
61
59
|
DispatchingRuleType,
|
62
60
|
Callable[[Dispatcher], Operation],
|
63
61
|
] = {
|
@@ -1,6 +1,5 @@
|
|
1
1
|
"""Home of the `DispatchingRuleSolver` class."""
|
2
2
|
|
3
|
-
from typing import Optional, Union
|
4
3
|
from collections.abc import Callable, Iterable
|
5
4
|
|
6
5
|
from job_shop_lib import JobShopInstance, Schedule, Operation, BaseSolver
|
@@ -66,24 +65,19 @@ class DispatchingRuleSolver(BaseSolver):
|
|
66
65
|
|
67
66
|
def __init__(
|
68
67
|
self,
|
69
|
-
dispatching_rule:
|
70
|
-
str
|
71
|
-
|
72
|
-
machine_chooser:
|
73
|
-
str
|
74
|
-
|
75
|
-
ready_operations_filter:
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
str,
|
83
|
-
ReadyOperationsFilterType,
|
84
|
-
ReadyOperationsFilter,
|
85
|
-
]
|
86
|
-
] = (
|
68
|
+
dispatching_rule: (
|
69
|
+
str | Callable[[Dispatcher], Operation]
|
70
|
+
) = DispatchingRuleType.MOST_WORK_REMAINING,
|
71
|
+
machine_chooser: (
|
72
|
+
str | Callable[[Dispatcher, Operation], int]
|
73
|
+
) = MachineChooserType.FIRST,
|
74
|
+
ready_operations_filter: (
|
75
|
+
Iterable[ReadyOperationsFilter | str | ReadyOperationsFilterType]
|
76
|
+
| str
|
77
|
+
| ReadyOperationsFilterType
|
78
|
+
| ReadyOperationsFilter
|
79
|
+
| None
|
80
|
+
) = (
|
87
81
|
ReadyOperationsFilterType.DOMINATED_OPERATIONS,
|
88
82
|
ReadyOperationsFilterType.NON_IMMEDIATE_OPERATIONS,
|
89
83
|
),
|
@@ -108,7 +102,7 @@ class DispatchingRuleSolver(BaseSolver):
|
|
108
102
|
def solve(
|
109
103
|
self,
|
110
104
|
instance: JobShopInstance,
|
111
|
-
dispatcher:
|
105
|
+
dispatcher: Dispatcher | None = None,
|
112
106
|
) -> Schedule:
|
113
107
|
"""Solves the instance using the dispatching rule and machine chooser
|
114
108
|
algorithms.
|
@@ -159,6 +153,7 @@ class DispatchingRuleSolver(BaseSolver):
|
|
159
153
|
if __name__ == "__main__":
|
160
154
|
import time
|
161
155
|
import cProfile
|
156
|
+
|
162
157
|
# import pstats
|
163
158
|
# from io import StringIO
|
164
159
|
from job_shop_lib.benchmarking import (
|
@@ -6,7 +6,6 @@ which operations are selected for execution based on certain criteria such as
|
|
6
6
|
shortest processing time, first come first served, etc.
|
7
7
|
"""
|
8
8
|
|
9
|
-
from typing import List, Optional
|
10
9
|
from collections.abc import Callable, Sequence
|
11
10
|
import random
|
12
11
|
|
@@ -65,7 +64,7 @@ def random_operation_rule(dispatcher: Dispatcher) -> Operation:
|
|
65
64
|
|
66
65
|
|
67
66
|
def score_based_rule(
|
68
|
-
score_function: Callable[[Dispatcher], Sequence[float]]
|
67
|
+
score_function: Callable[[Dispatcher], Sequence[float]],
|
69
68
|
) -> Callable[[Dispatcher], Operation]:
|
70
69
|
"""Creates a dispatching rule based on a scoring function.
|
71
70
|
|
@@ -89,7 +88,7 @@ def score_based_rule(
|
|
89
88
|
|
90
89
|
|
91
90
|
def score_based_rule_with_tie_breaker(
|
92
|
-
score_functions:
|
91
|
+
score_functions: list[Callable[[Dispatcher], Sequence[int]]],
|
93
92
|
) -> Callable[[Dispatcher], Operation]:
|
94
93
|
"""Creates a dispatching rule based on multiple scoring functions.
|
95
94
|
|
@@ -123,7 +122,7 @@ def score_based_rule_with_tie_breaker(
|
|
123
122
|
# -----------------
|
124
123
|
|
125
124
|
|
126
|
-
def shortest_processing_time_score(dispatcher: Dispatcher) ->
|
125
|
+
def shortest_processing_time_score(dispatcher: Dispatcher) -> list[int]:
|
127
126
|
"""Scores each job based on the duration of the next operation."""
|
128
127
|
num_jobs = dispatcher.instance.num_jobs
|
129
128
|
scores = [0] * num_jobs
|
@@ -132,7 +131,7 @@ def shortest_processing_time_score(dispatcher: Dispatcher) -> List[int]:
|
|
132
131
|
return scores
|
133
132
|
|
134
133
|
|
135
|
-
def first_come_first_served_score(dispatcher: Dispatcher) ->
|
134
|
+
def first_come_first_served_score(dispatcher: Dispatcher) -> list[int]:
|
136
135
|
"""Scores each job based on the position of the next operation."""
|
137
136
|
num_jobs = dispatcher.instance.num_jobs
|
138
137
|
scores = [0] * num_jobs
|
@@ -154,9 +153,9 @@ class MostWorkRemainingScorer: # pylint: disable=too-few-public-methods
|
|
154
153
|
"""
|
155
154
|
|
156
155
|
def __init__(self) -> None:
|
157
|
-
self._duration_observer:
|
158
|
-
self._is_ready_observer:
|
159
|
-
self._current_dispatcher:
|
156
|
+
self._duration_observer: DurationObserver | None = None
|
157
|
+
self._is_ready_observer: IsReadyObserver | None = None
|
158
|
+
self._current_dispatcher: Dispatcher | None = None
|
160
159
|
|
161
160
|
def __call__(self, dispatcher: Dispatcher) -> Sequence[int]:
|
162
161
|
"""Scores each job based on the remaining work in the job."""
|
@@ -198,7 +197,7 @@ observer_based_most_work_remaining_rule = score_based_rule(
|
|
198
197
|
)
|
199
198
|
|
200
199
|
|
201
|
-
def most_operations_remaining_score(dispatcher: Dispatcher) ->
|
200
|
+
def most_operations_remaining_score(dispatcher: Dispatcher) -> list[int]:
|
202
201
|
"""Scores each job based on the remaining operations in the job."""
|
203
202
|
num_jobs = dispatcher.instance.num_jobs
|
204
203
|
scores = [0] * num_jobs
|
@@ -207,7 +206,7 @@ def most_operations_remaining_score(dispatcher: Dispatcher) -> List[int]:
|
|
207
206
|
return scores
|
208
207
|
|
209
208
|
|
210
|
-
def random_score(dispatcher: Dispatcher) ->
|
209
|
+
def random_score(dispatcher: Dispatcher) -> list[int]:
|
211
210
|
"""Scores each job randomly."""
|
212
211
|
return [
|
213
212
|
random.randint(0, 100) for _ in range(dispatcher.instance.num_jobs)
|
@@ -5,7 +5,6 @@ The factory functions create and return the appropriate functions based on the
|
|
5
5
|
specified names or enums.
|
6
6
|
"""
|
7
7
|
|
8
|
-
from typing import Union, Dict
|
9
8
|
from enum import Enum
|
10
9
|
from collections.abc import Callable
|
11
10
|
import random
|
@@ -26,7 +25,7 @@ MachineChooser = Callable[[Dispatcher, Operation], int]
|
|
26
25
|
|
27
26
|
|
28
27
|
def machine_chooser_factory(
|
29
|
-
machine_chooser:
|
28
|
+
machine_chooser: str | MachineChooser,
|
30
29
|
) -> MachineChooser:
|
31
30
|
"""Creates and returns a machine chooser function based on the specified
|
32
31
|
machine chooser strategy name.
|
@@ -51,7 +50,7 @@ def machine_chooser_factory(
|
|
51
50
|
If the ``machine_chooser`` argument is not recognized or
|
52
51
|
is not supported.
|
53
52
|
"""
|
54
|
-
machine_choosers:
|
53
|
+
machine_choosers: dict[str, Callable[[Dispatcher, Operation], int]] = {
|
55
54
|
MachineChooserType.FIRST: lambda _, operation: operation.machines[0],
|
56
55
|
MachineChooserType.RANDOM: lambda _, operation: random.choice(
|
57
56
|
operation.machines
|
@@ -1,6 +1,5 @@
|
|
1
1
|
"""Utility functions."""
|
2
2
|
|
3
|
-
from typing import Union, List
|
4
3
|
import time
|
5
4
|
from collections.abc import Callable
|
6
5
|
import pandas as pd
|
@@ -11,12 +10,12 @@ from job_shop_lib.dispatching import Dispatcher
|
|
11
10
|
|
12
11
|
|
13
12
|
def benchmark_dispatching_rules(
|
14
|
-
dispatching_rules:
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
instances:
|
13
|
+
dispatching_rules: (
|
14
|
+
list[str | Callable[[Dispatcher], Operation]]
|
15
|
+
| list[str]
|
16
|
+
| list[Callable[[Dispatcher], Operation]]
|
17
|
+
),
|
18
|
+
instances: list[JobShopInstance],
|
20
19
|
) -> pd.DataFrame:
|
21
20
|
"""Benchmark multiple dispatching rules on multiple JobShopInstances.
|
22
21
|
|
@@ -112,7 +111,7 @@ if __name__ == "__main__":
|
|
112
111
|
instances_ = [load_benchmark_instance(f"ta{i:02d}") for i in range(1, 3)]
|
113
112
|
|
114
113
|
# Define rules
|
115
|
-
rules_:
|
114
|
+
rules_: list[str | Callable[[Dispatcher], Operation]] = [
|
116
115
|
"most_work_remaining",
|
117
116
|
"shortest_processing_time",
|
118
117
|
most_work_remaining_rule,
|