job-shop-lib 1.0.0a4__py3-none-any.whl → 1.0.0b1__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 +3 -0
- job_shop_lib/_job_shop_instance.py +36 -31
- 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 -5
- job_shop_lib/dispatching/_dispatcher.py +58 -20
- job_shop_lib/dispatching/_dispatcher_observer_config.py +4 -4
- job_shop_lib/dispatching/_factories.py +8 -6
- 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.0a4.dist-info → job_shop_lib-1.0.0b1.dist-info}/METADATA +26 -24
- 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.0a4.dist-info/RECORD +0 -66
- {job_shop_lib-1.0.0a4.dist-info → job_shop_lib-1.0.0b1.dist-info}/LICENSE +0 -0
- {job_shop_lib-1.0.0a4.dist-info → job_shop_lib-1.0.0b1.dist-info}/WHEEL +0 -0
@@ -4,11 +4,13 @@ The factory functions create and return the appropriate functions based on the
|
|
4
4
|
specified names or enums.
|
5
5
|
"""
|
6
6
|
|
7
|
+
from typing import Union
|
7
8
|
from enum import Enum
|
8
9
|
from collections.abc import Iterable
|
9
10
|
|
10
11
|
from job_shop_lib import Operation
|
11
12
|
from job_shop_lib.exceptions import ValidationError
|
13
|
+
|
12
14
|
from job_shop_lib.dispatching import (
|
13
15
|
Dispatcher,
|
14
16
|
filter_dominated_operations,
|
@@ -35,14 +37,14 @@ class ReadyOperationsFilterType(str, Enum):
|
|
35
37
|
|
36
38
|
def create_composite_operation_filter(
|
37
39
|
ready_operations_filters: Iterable[
|
38
|
-
ReadyOperationsFilter
|
40
|
+
Union[ReadyOperationsFilter, str, ReadyOperationsFilterType]
|
39
41
|
],
|
40
42
|
) -> ReadyOperationsFilter:
|
41
|
-
"""Creates and returns a
|
42
|
-
|
43
|
+
"""Creates and returns a :class:`ReadyOperationsFilter` function by
|
44
|
+
combining multiple filter strategies.
|
43
45
|
|
44
|
-
The composite filter function
|
45
|
-
|
46
|
+
The composite filter function applies multiple filter strategies
|
47
|
+
iteratively in the order they are specified in the list.
|
46
48
|
|
47
49
|
Args:
|
48
50
|
ready_operations_filters:
|
@@ -83,7 +85,7 @@ def create_composite_operation_filter(
|
|
83
85
|
|
84
86
|
|
85
87
|
def ready_operations_filter_factory(
|
86
|
-
filter_name: str
|
88
|
+
filter_name: Union[str, ReadyOperationsFilterType, ReadyOperationsFilter]
|
87
89
|
) -> ReadyOperationsFilter:
|
88
90
|
"""Creates and returns a filter function based on the specified
|
89
91
|
filter strategy name.
|
@@ -1,5 +1,6 @@
|
|
1
1
|
"""Home of the `HistoryObserver` class."""
|
2
2
|
|
3
|
+
from typing import List
|
3
4
|
from job_shop_lib.dispatching import DispatcherObserver, Dispatcher
|
4
5
|
from job_shop_lib import ScheduledOperation
|
5
6
|
|
@@ -9,7 +10,7 @@ class HistoryObserver(DispatcherObserver):
|
|
9
10
|
|
10
11
|
def __init__(self, dispatcher: Dispatcher, *, subscribe: bool = True):
|
11
12
|
super().__init__(dispatcher, subscribe=subscribe)
|
12
|
-
self.history:
|
13
|
+
self.history: List[ScheduledOperation] = []
|
13
14
|
|
14
15
|
def update(self, scheduled_operation: ScheduledOperation):
|
15
16
|
self.history.append(scheduled_operation)
|
@@ -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,
|