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