job-shop-lib 1.0.0a1__py3-none-any.whl → 1.0.0a3__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/_job_shop_instance.py +18 -20
- job_shop_lib/_operation.py +30 -24
- job_shop_lib/_schedule.py +17 -16
- job_shop_lib/_scheduled_operation.py +10 -12
- job_shop_lib/constraint_programming/_ortools_solver.py +31 -16
- job_shop_lib/dispatching/__init__.py +4 -0
- job_shop_lib/dispatching/_dispatcher.py +24 -32
- job_shop_lib/dispatching/_factories.py +8 -0
- job_shop_lib/dispatching/_ready_operation_filters.py +80 -0
- job_shop_lib/dispatching/feature_observers/__init__.py +34 -2
- job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py +54 -14
- job_shop_lib/dispatching/feature_observers/_duration_observer.py +15 -2
- job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py +62 -10
- job_shop_lib/dispatching/feature_observers/_factory.py +5 -1
- job_shop_lib/dispatching/feature_observers/_feature_observer.py +87 -16
- job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +32 -2
- job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +3 -3
- job_shop_lib/dispatching/feature_observers/_is_scheduled_observer.py +9 -5
- job_shop_lib/dispatching/feature_observers/_position_in_job_observer.py +7 -2
- job_shop_lib/dispatching/feature_observers/_remaining_operations_observer.py +1 -1
- job_shop_lib/dispatching/rules/_dispatching_rule_solver.py +66 -43
- job_shop_lib/dispatching/rules/_dispatching_rules_functions.py +9 -9
- job_shop_lib/graphs/__init__.py +2 -0
- job_shop_lib/graphs/_build_agent_task_graph.py +2 -2
- job_shop_lib/graphs/_constants.py +18 -1
- job_shop_lib/graphs/_job_shop_graph.py +36 -20
- job_shop_lib/graphs/_node.py +60 -52
- job_shop_lib/graphs/graph_updaters/__init__.py +11 -1
- job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +1 -1
- job_shop_lib/visualization/__init__.py +5 -5
- job_shop_lib/visualization/_gantt_chart_creator.py +5 -5
- job_shop_lib/visualization/_gantt_chart_video_and_gif_creation.py +63 -36
- job_shop_lib/visualization/{_gantt_chart.py → _plot_gantt_chart.py} +78 -14
- {job_shop_lib-1.0.0a1.dist-info → job_shop_lib-1.0.0a3.dist-info}/METADATA +15 -3
- {job_shop_lib-1.0.0a1.dist-info → job_shop_lib-1.0.0a3.dist-info}/RECORD +37 -37
- {job_shop_lib-1.0.0a1.dist-info → job_shop_lib-1.0.0a3.dist-info}/WHEEL +1 -1
- {job_shop_lib-1.0.0a1.dist-info → job_shop_lib-1.0.0a3.dist-info}/LICENSE +0 -0
@@ -12,7 +12,7 @@ from job_shop_lib.dispatching.feature_observers import (
|
|
12
12
|
class DurationObserver(FeatureObserver):
|
13
13
|
"""Measures the remaining duration of operations, machines, and jobs.
|
14
14
|
|
15
|
-
The duration of an :class
|
15
|
+
The duration of an :class:`~job_shop_lib.Operation` is:
|
16
16
|
- if the operation has not been scheduled, it is the duration of the
|
17
17
|
operation.
|
18
18
|
- if the operation has been scheduled, it is the remaining duration of
|
@@ -22,8 +22,21 @@ class DurationObserver(FeatureObserver):
|
|
22
22
|
manually if needed. We do not update the duration of completed
|
23
23
|
operations to save computation time.
|
24
24
|
|
25
|
-
The duration of a
|
25
|
+
The duration of a machine or job is the sum of the durations of the
|
26
26
|
unscheduled operations that belong to the machine or job.
|
27
|
+
|
28
|
+
Args:
|
29
|
+
dispatcher:
|
30
|
+
The :class:`~job_shop_lib.dispatching.Dispatcher` to observe.
|
31
|
+
subscribe:
|
32
|
+
If ``True``, the observer is subscribed to the dispatcher upon
|
33
|
+
initialization. Otherwise, the observer must be subscribed later
|
34
|
+
or manually updated.
|
35
|
+
feature_types:
|
36
|
+
A list of :class:`FeatureType` or a single :class:`FeatureType`
|
37
|
+
that specifies the types of features to observe. They must be a
|
38
|
+
subset of the class attribute :attr:`supported_feature_types`.
|
39
|
+
If ``None``, all supported feature types are tracked.
|
27
40
|
"""
|
28
41
|
|
29
42
|
def initialize_features(self):
|
@@ -1,6 +1,7 @@
|
|
1
1
|
"""Home of the `EarliestStartTimeObserver` class."""
|
2
2
|
|
3
3
|
import numpy as np
|
4
|
+
from numpy.typing import NDArray
|
4
5
|
|
5
6
|
from job_shop_lib.dispatching import Dispatcher
|
6
7
|
from job_shop_lib.dispatching.feature_observers import (
|
@@ -12,14 +13,62 @@ from job_shop_lib import ScheduledOperation
|
|
12
13
|
|
13
14
|
class EarliestStartTimeObserver(FeatureObserver):
|
14
15
|
"""Observer that adds a feature indicating the earliest start time of
|
15
|
-
each operation, machine, and job in the graph.
|
16
|
+
each operation, machine, and job in the graph.
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
18
|
+
The earliest start time of an operation refers to the earliest time at
|
19
|
+
which the operation could potentially start without violating any
|
20
|
+
constraints. This time is normalized by the current time (i.e., the
|
21
|
+
difference between the earliest start time and the current time).
|
22
|
+
|
23
|
+
The earliest start time of a machine is the earliest start time of the
|
24
|
+
next operation that can be scheduled on that machine.
|
25
|
+
|
26
|
+
Finally, the earliest start time of a job is the earliest start time of the
|
27
|
+
next operation in the job.
|
28
|
+
|
29
|
+
Args:
|
30
|
+
dispatcher:
|
31
|
+
The :class:`~job_shop_lib.dispatching.Dispatcher` to observe.
|
32
|
+
subscribe:
|
33
|
+
If ``True``, the observer is subscribed to the dispatcher upon
|
34
|
+
initialization. Otherwise, the observer must be subscribed later
|
35
|
+
or manually updated.
|
36
|
+
feature_types:
|
37
|
+
A list of :class:`FeatureType` or a single :class:`FeatureType`
|
38
|
+
that specifies the types of features to observe. They must be a
|
39
|
+
subset of the class attribute :attr:`supported_feature_types`.
|
40
|
+
If ``None``, all supported feature types are tracked.
|
41
|
+
"""
|
42
|
+
|
43
|
+
__slots__ = {
|
44
|
+
"earliest_start_times": (
|
45
|
+
"A 2D numpy array with the earliest start "
|
46
|
+
"times of each operation. The array has "
|
47
|
+
"shape (``num_jobs``, ``max_operations_per_job``). "
|
48
|
+
"The value at index (i, j) is the earliest start "
|
49
|
+
"time of the j-th operation in the i-th job. "
|
50
|
+
"If a job has fewer than the maximum number of "
|
51
|
+
"operations in a job, the remaining values are "
|
52
|
+
"set to ``np.nan``. Similarly to "
|
53
|
+
":class:`~job_shop_lib.JobShopInstance`'s "
|
54
|
+
":meth:`~job_shop_lib.JobShopInstance.durations_matrix_array` "
|
55
|
+
"method."
|
56
|
+
),
|
57
|
+
"_job_ids": (
|
58
|
+
"An array that stores the job IDs for each operation in the "
|
59
|
+
"dispatcher's instance. The array has shape "
|
60
|
+
"(``num_machines``, ``max_operations_per_machine``)."
|
61
|
+
),
|
62
|
+
"_positions": (
|
63
|
+
"An array that stores the positions of each operation in their "
|
64
|
+
"respective jobs. The array has shape "
|
65
|
+
"(``num_machines``, ``max_operations_per_machine``)."
|
66
|
+
),
|
67
|
+
"_is_regular_instance": (
|
68
|
+
"Whether the dispatcher's instance is a regular "
|
69
|
+
"instance, where each job has the same number of operations."
|
70
|
+
),
|
71
|
+
}
|
23
72
|
|
24
73
|
def __init__(
|
25
74
|
self,
|
@@ -32,9 +81,9 @@ class EarliestStartTimeObserver(FeatureObserver):
|
|
32
81
|
# Earliest start times initialization
|
33
82
|
# -------------------------------
|
34
83
|
squared_duration_matrix = dispatcher.instance.durations_matrix_array
|
35
|
-
self.earliest_start_times = np.hstack(
|
84
|
+
self.earliest_start_times: NDArray[np.float32] = np.hstack(
|
36
85
|
(
|
37
|
-
np.zeros((squared_duration_matrix.shape[0], 1)),
|
86
|
+
np.zeros((squared_duration_matrix.shape[0], 1), dtype=float),
|
38
87
|
np.cumsum(squared_duration_matrix[:, :-1], axis=1),
|
39
88
|
)
|
40
89
|
)
|
@@ -70,7 +119,7 @@ class EarliestStartTimeObserver(FeatureObserver):
|
|
70
119
|
|
71
120
|
def update(self, scheduled_operation: ScheduledOperation):
|
72
121
|
"""Recomputes the earliest start times and calls the
|
73
|
-
|
122
|
+
``initialize_features`` method.
|
74
123
|
|
75
124
|
The earliest start times is computed as the cumulative sum of the
|
76
125
|
previous unscheduled operations in the job plus the maximum of the
|
@@ -78,6 +127,9 @@ class EarliestStartTimeObserver(FeatureObserver):
|
|
78
127
|
time of the machine(s) the operation is assigned.
|
79
128
|
|
80
129
|
After that, we substract the current time.
|
130
|
+
|
131
|
+
Args:
|
132
|
+
scheduled_operation: The operation that has been scheduled.
|
81
133
|
"""
|
82
134
|
# We compute the gap that the current scheduled operation could be
|
83
135
|
# adding to each job.
|
@@ -16,7 +16,11 @@ from job_shop_lib.dispatching.feature_observers import (
|
|
16
16
|
|
17
17
|
|
18
18
|
class FeatureObserverType(str, Enum):
|
19
|
-
"""Enumeration of the different feature observers.
|
19
|
+
"""Enumeration of the different feature observers.
|
20
|
+
|
21
|
+
Each feature observer is associated with a string value that can be used
|
22
|
+
to create the feature observer using the factory function.
|
23
|
+
"""
|
20
24
|
|
21
25
|
IS_READY = "is_ready"
|
22
26
|
EARLIEST_START_TIME = "earliest_start_time"
|
@@ -20,6 +20,27 @@ class FeatureType(str, enum.Enum):
|
|
20
20
|
class FeatureObserver(DispatcherObserver):
|
21
21
|
"""Base class for feature observers.
|
22
22
|
|
23
|
+
A :class:`FeatureObserver` is a
|
24
|
+
a subclass of :class:`~job_shop_lib.dispatching.DispatcherObserver` that
|
25
|
+
observes features related to operations, machines, or jobs in the
|
26
|
+
:class:`~job_shop_lib.dispatching.Dispatcher`.
|
27
|
+
|
28
|
+
Attributes are stored in numpy arrays with a shape of (``num_entities``,
|
29
|
+
``feature_size``), where ``num_entities`` is the number of entities being
|
30
|
+
observed (e.g., operations, machines, or jobs) and ``feature_size`` is the
|
31
|
+
number of values being observed for each entity.
|
32
|
+
|
33
|
+
The advantage of using arrays is that they can be easily updated in a
|
34
|
+
vectorized manner, which is more efficient than updating each attribute
|
35
|
+
individually. Furthermore, machine learning models can be trained on these
|
36
|
+
arrays to predict the best dispatching decisions.
|
37
|
+
|
38
|
+
Arrays use the data type ``np.float32``. This is because most machine
|
39
|
+
|
40
|
+
New :class:`FeatureObservers` must inherit from this class, and re-define
|
41
|
+
the class attributes ``_singleton`` (defualt ), ``_feature_size``
|
42
|
+
(default 1) and ``_supported_feature_types`` (default all feature types).
|
43
|
+
|
23
44
|
Feature observers are not singleton by default. This means that more than
|
24
45
|
one instance of the same feature observer type can be subscribed to the
|
25
46
|
dispatcher. This is useful when the first subscriber only observes a subset
|
@@ -27,16 +48,36 @@ class FeatureObserver(DispatcherObserver):
|
|
27
48
|
them. For example, the first subscriber could observe only the
|
28
49
|
operation-related features, while the second subscriber could observe the
|
29
50
|
jobs.
|
51
|
+
|
52
|
+
Args:
|
53
|
+
dispatcher:
|
54
|
+
The :class:`~job_shop_lib.dispatching.Dispatcher` to observe.
|
55
|
+
subscribe:
|
56
|
+
If ``True``, the observer is subscribed to the dispatcher upon
|
57
|
+
initialization. Otherwise, the observer must be subscribed later
|
58
|
+
or manually updated.
|
59
|
+
feature_types:
|
60
|
+
A list of :class:`FeatureType` or a single :class:`FeatureType`
|
61
|
+
that specifies the types of features to observe. They must be a
|
62
|
+
subset of the class attribute :attr:`supported_feature_types`.
|
63
|
+
If ``None``, all supported feature types are tracked.
|
30
64
|
"""
|
31
65
|
|
32
66
|
_is_singleton = False
|
33
|
-
|
67
|
+
_feature_sizes: dict[FeatureType, int] | int = 1
|
34
68
|
_supported_feature_types = list(FeatureType)
|
35
69
|
|
36
|
-
__slots__ =
|
37
|
-
"features"
|
38
|
-
|
39
|
-
|
70
|
+
__slots__ = {
|
71
|
+
"features": (
|
72
|
+
"A dictionary of numpy arrays with the features. "
|
73
|
+
"Each key is a :class:`FeatureType` and each value is a numpy "
|
74
|
+
"array with the features. The array has shape (``num_entities``, "
|
75
|
+
"``feature_size``), where ``num_entities`` is the number of "
|
76
|
+
"entities being observed (e.g., operations, machines, or jobs) and"
|
77
|
+
" ``feature_size`` is the number of values being observed for each"
|
78
|
+
" entity."
|
79
|
+
)
|
80
|
+
}
|
40
81
|
|
41
82
|
def __init__(
|
42
83
|
self,
|
@@ -46,9 +87,9 @@ class FeatureObserver(DispatcherObserver):
|
|
46
87
|
feature_types: list[FeatureType] | FeatureType | None = None,
|
47
88
|
):
|
48
89
|
feature_types = self._get_feature_types_list(feature_types)
|
49
|
-
if isinstance(self.
|
90
|
+
if isinstance(self._feature_sizes, int):
|
50
91
|
feature_size = {
|
51
|
-
feature_type: self.
|
92
|
+
feature_type: self._feature_sizes
|
52
93
|
for feature_type in feature_types
|
53
94
|
}
|
54
95
|
super().__init__(dispatcher, subscribe=subscribe)
|
@@ -58,7 +99,7 @@ class FeatureObserver(DispatcherObserver):
|
|
58
99
|
FeatureType.MACHINES: dispatcher.instance.num_machines,
|
59
100
|
FeatureType.JOBS: dispatcher.instance.num_jobs,
|
60
101
|
}
|
61
|
-
|
102
|
+
feature_dimensions = {
|
62
103
|
feature_type: (
|
63
104
|
number_of_entities[feature_type],
|
64
105
|
feature_size[feature_type],
|
@@ -67,7 +108,7 @@ class FeatureObserver(DispatcherObserver):
|
|
67
108
|
}
|
68
109
|
self.features = {
|
69
110
|
feature_type: np.zeros(
|
70
|
-
|
111
|
+
feature_dimensions[feature_type],
|
71
112
|
dtype=np.float32,
|
72
113
|
)
|
73
114
|
for feature_type in feature_types
|
@@ -75,23 +116,42 @@ class FeatureObserver(DispatcherObserver):
|
|
75
116
|
self.initialize_features()
|
76
117
|
|
77
118
|
@property
|
78
|
-
def
|
79
|
-
"""Returns the size of the features.
|
80
|
-
|
119
|
+
def feature_sizes(self) -> dict[FeatureType, int]:
|
120
|
+
"""Returns the size of the features.
|
121
|
+
|
122
|
+
The size of the features is the number of values being observed for
|
123
|
+
each entity. This corresponds to the second dimension of each array.
|
124
|
+
|
125
|
+
This number is typically one (e.g. measuring the duration
|
126
|
+
of each operation), but some feature observers like the
|
127
|
+
:class:`CompositeFeatureObserver` may track more than one value.
|
128
|
+
"""
|
129
|
+
if isinstance(self._feature_sizes, int):
|
81
130
|
return {
|
82
|
-
feature_type: self.
|
131
|
+
feature_type: self._feature_sizes
|
83
132
|
for feature_type in self.features
|
84
133
|
}
|
85
|
-
return self.
|
134
|
+
return self._feature_sizes
|
86
135
|
|
87
136
|
@property
|
88
137
|
def supported_feature_types(self) -> list[FeatureType]:
|
89
138
|
"""Returns the supported feature types."""
|
90
139
|
return self._supported_feature_types
|
91
140
|
|
141
|
+
@property
|
142
|
+
def feature_dimensions(self) -> dict[FeatureType, tuple[int, int]]:
|
143
|
+
"""A dictionary containing the shape of each :class:`FeatureType`."""
|
144
|
+
feature_dimensions = {}
|
145
|
+
for feature_type, array in self.features.items():
|
146
|
+
feature_dimensions[feature_type] = array.shape
|
147
|
+
return feature_dimensions # type: ignore[return-value]
|
148
|
+
|
92
149
|
def initialize_features(self):
|
93
150
|
"""Initializes the features based on the current state of the
|
94
|
-
dispatcher.
|
151
|
+
dispatcher.
|
152
|
+
|
153
|
+
This method is automatically called after initializing the observer.
|
154
|
+
"""
|
95
155
|
|
96
156
|
def update(self, scheduled_operation: ScheduledOperation):
|
97
157
|
"""Updates the features based on the scheduled operation.
|
@@ -112,7 +172,18 @@ class FeatureObserver(DispatcherObserver):
|
|
112
172
|
def set_features_to_zero(
|
113
173
|
self, exclude: FeatureType | list[FeatureType] | None = None
|
114
174
|
):
|
115
|
-
"""Sets features to zero
|
175
|
+
"""Sets all features to zero except for the ones specified in
|
176
|
+
``exclude``.
|
177
|
+
|
178
|
+
Setting a feature to zero means that all values in the feature array
|
179
|
+
are set to this value.
|
180
|
+
|
181
|
+
Args:
|
182
|
+
exclude:
|
183
|
+
A single :class:`FeatureType` or a list of :class:`FeatureType`
|
184
|
+
that specifies the features that should not be set to zero. If
|
185
|
+
``None``, all currently used features are set to zero.
|
186
|
+
"""
|
116
187
|
if exclude is None:
|
117
188
|
exclude = []
|
118
189
|
if isinstance(exclude, FeatureType):
|
@@ -15,8 +15,38 @@ from job_shop_lib.dispatching.feature_observers import (
|
|
15
15
|
|
16
16
|
|
17
17
|
class IsCompletedObserver(FeatureObserver):
|
18
|
-
"""
|
19
|
-
machine, or job has been completed.
|
18
|
+
"""Adds a binary feature indicating whether each operation,
|
19
|
+
machine, or job has been completed.
|
20
|
+
|
21
|
+
An operation is considered completed if it has been scheduled and the
|
22
|
+
current time is greater than or equal to the sum of the operation's start
|
23
|
+
time and duration.
|
24
|
+
|
25
|
+
A machine or job is considered completed if all of its operations have been
|
26
|
+
completed.
|
27
|
+
|
28
|
+
Args:
|
29
|
+
dispatcher:
|
30
|
+
The :class:`~job_shop_lib.dispatching.Dispatcher` to observe.
|
31
|
+
feature_types:
|
32
|
+
A list of :class:`FeatureType` or a single :class:`FeatureType`
|
33
|
+
that specifies the types of features to observe. They must be a
|
34
|
+
subset of the class attribute :attr:`supported_feature_types`.
|
35
|
+
If ``None``, all supported feature types are tracked.
|
36
|
+
subscribe:
|
37
|
+
If ``True``, the observer is subscribed to the dispatcher upon
|
38
|
+
initialization. Otherwise, the observer must be subscribed later
|
39
|
+
or manually updated.
|
40
|
+
"""
|
41
|
+
|
42
|
+
__slots__ = {
|
43
|
+
"remaining_ops_per_machine": (
|
44
|
+
"The number of unscheduled operations per machine."
|
45
|
+
),
|
46
|
+
"remaining_ops_per_job": (
|
47
|
+
"The number of unscheduled operations per job."
|
48
|
+
),
|
49
|
+
}
|
20
50
|
|
21
51
|
def __init__(
|
22
52
|
self,
|
@@ -7,8 +7,8 @@ from job_shop_lib.dispatching.feature_observers import (
|
|
7
7
|
|
8
8
|
|
9
9
|
class IsReadyObserver(FeatureObserver):
|
10
|
-
"""Feature creator that adds a binary feature indicating if the operation
|
11
|
-
is ready to be dispatched."""
|
10
|
+
"""Feature creator that adds a binary feature indicating if the operation,
|
11
|
+
machine or job is ready to be dispatched."""
|
12
12
|
|
13
13
|
def initialize_features(self):
|
14
14
|
self.set_features_to_zero()
|
@@ -29,5 +29,5 @@ class IsReadyObserver(FeatureObserver):
|
|
29
29
|
self.initialize_features()
|
30
30
|
|
31
31
|
def _get_ready_operations(self) -> list[int]:
|
32
|
-
available_operations = self.dispatcher.
|
32
|
+
available_operations = self.dispatcher.available_operations()
|
33
33
|
return [operation.operation_id for operation in available_operations]
|
@@ -8,14 +8,18 @@ from job_shop_lib.dispatching.feature_observers import (
|
|
8
8
|
|
9
9
|
|
10
10
|
class IsScheduledObserver(FeatureObserver):
|
11
|
-
"""
|
11
|
+
"""Updates features based on scheduling operations.
|
12
12
|
|
13
13
|
This observer tracks which operations have been scheduled and updates
|
14
|
-
feature matrices accordingly.
|
15
|
-
|
16
|
-
|
14
|
+
feature matrices accordingly.
|
15
|
+
|
16
|
+
It updates a feature in the
|
17
|
+
:meth:`FeatureType.OPERATIONS` matrix to indicate that an operation has
|
18
|
+
been scheduled.
|
19
|
+
|
20
|
+
Additionally, it counts the number of uncompleted but
|
17
21
|
scheduled operations for each machine and job, updating the respective
|
18
|
-
|
22
|
+
:meth:`FeatureType.MACHINES` and :meth:`FeatureType.JOBS` feature matrices.
|
19
23
|
"""
|
20
24
|
|
21
25
|
def update(self, scheduled_operation: ScheduledOperation):
|
@@ -8,10 +8,15 @@ from job_shop_lib.dispatching.feature_observers import (
|
|
8
8
|
|
9
9
|
|
10
10
|
class PositionInJobObserver(FeatureObserver):
|
11
|
-
"""
|
11
|
+
"""Adds a feature indicating the position of
|
12
12
|
operations in their respective jobs.
|
13
13
|
|
14
|
-
Positions are adjusted dynamically as operations are scheduled.
|
14
|
+
Positions are adjusted dynamically as operations are scheduled. In other
|
15
|
+
words, the position of an operation is the number of unscheduled operations
|
16
|
+
that precede it in the job.
|
17
|
+
|
18
|
+
It only supports the :meth:`~job_shop_lib.FeatureType.OPERATIONS` feature
|
19
|
+
type.
|
15
20
|
"""
|
16
21
|
|
17
22
|
_supported_feature_types = [FeatureType.OPERATIONS]
|
@@ -12,7 +12,7 @@ class RemainingOperationsObserver(FeatureObserver):
|
|
12
12
|
"""Adds a feature indicating the number of remaining operations for each
|
13
13
|
job and machine.
|
14
14
|
|
15
|
-
It does not support :
|
15
|
+
It does not support :meth:`FeatureType.OPERATIONS`.
|
16
16
|
"""
|
17
17
|
|
18
18
|
_supported_feature_types = [FeatureType.MACHINES, FeatureType.JOBS]
|
@@ -1,12 +1,14 @@
|
|
1
1
|
"""Home of the `DispatchingRuleSolver` class."""
|
2
2
|
|
3
|
-
from collections.abc import Callable
|
3
|
+
from collections.abc import Callable, Iterable
|
4
4
|
|
5
5
|
from job_shop_lib import JobShopInstance, Schedule, Operation, BaseSolver
|
6
6
|
from job_shop_lib.dispatching import (
|
7
7
|
ready_operations_filter_factory,
|
8
8
|
Dispatcher,
|
9
9
|
ReadyOperationsFilterType,
|
10
|
+
ReadyOperationsFilter,
|
11
|
+
create_composite_operation_filter,
|
10
12
|
)
|
11
13
|
from job_shop_lib.dispatching.rules import (
|
12
14
|
dispatching_rule_factory,
|
@@ -30,6 +32,35 @@ class DispatchingRuleSolver(BaseSolver):
|
|
30
32
|
pruning_function:
|
31
33
|
The pruning function to use. It is used to initialize the
|
32
34
|
dispatcher object internally when calling the solve method.
|
35
|
+
|
36
|
+
Args:
|
37
|
+
dispatching_rule:
|
38
|
+
The dispatching rule to use. It can be a string with the name
|
39
|
+
of the dispatching rule, a class`DispatchingRuleType` enum member,
|
40
|
+
or a callable that takes a dispatcher and returns the operation to
|
41
|
+
be dispatched next.
|
42
|
+
machine_chooser:
|
43
|
+
The machine chooser to use. It can be a string with the name
|
44
|
+
of the machine chooser, a :class:`MachineChooserType` member, or a
|
45
|
+
callable that takes a dispatcher and an operation and returns
|
46
|
+
the machine id where the operation will be dispatched.
|
47
|
+
ready_operations_filter:
|
48
|
+
The ready operations filter to use. It can be either:
|
49
|
+
|
50
|
+
- a string with the name of the pruning function
|
51
|
+
- a :class`ReadyOperationsFilterType` enum member.
|
52
|
+
- a callable that takes a dispatcher and a list of operations
|
53
|
+
and returns a list of operations that should be considered
|
54
|
+
for dispatching,
|
55
|
+
- a list with names or actual ready operations filters to be used.
|
56
|
+
If a list is provided, a composite filter will be created
|
57
|
+
using the specified filters.
|
58
|
+
|
59
|
+
.. seealso::
|
60
|
+
- :func:`job_shop_lib.dispatching.rules.dispatching_rule_factory`
|
61
|
+
- :func:`job_shop_lib.dispatching.rules.machine_chooser_factory`
|
62
|
+
- :func:`~job_shop_lib.dispatching.ready_operations_filter_factory`
|
63
|
+
- :func:`~job_shop_lib.dispatching.create_composite_operation_filter`
|
33
64
|
"""
|
34
65
|
|
35
66
|
def __init__(
|
@@ -40,45 +71,33 @@ class DispatchingRuleSolver(BaseSolver):
|
|
40
71
|
machine_chooser: (
|
41
72
|
str | Callable[[Dispatcher, Operation], int]
|
42
73
|
) = MachineChooserType.FIRST,
|
43
|
-
|
44
|
-
str
|
45
|
-
|
|
74
|
+
ready_operations_filter: (
|
75
|
+
Iterable[ReadyOperationsFilter | str | ReadyOperationsFilterType]
|
76
|
+
| str
|
77
|
+
| ReadyOperationsFilterType
|
78
|
+
| ReadyOperationsFilter
|
46
79
|
| None
|
47
|
-
) =
|
80
|
+
) = (
|
81
|
+
ReadyOperationsFilterType.DOMINATED_OPERATIONS,
|
82
|
+
ReadyOperationsFilterType.NON_IDLE_MACHINES,
|
83
|
+
),
|
48
84
|
):
|
49
|
-
"""Initializes the solver with the given dispatching rule, machine
|
50
|
-
chooser and pruning function.
|
51
|
-
|
52
|
-
Args:
|
53
|
-
dispatching_rule:
|
54
|
-
The dispatching rule to use. It can be a string with the name
|
55
|
-
of the dispatching rule, a DispatchingRule enum member, or a
|
56
|
-
callable that takes a dispatcher and returns the operation to
|
57
|
-
be dispatched next.
|
58
|
-
machine_chooser:
|
59
|
-
The machine chooser to use. It can be a string with the name
|
60
|
-
of the machine chooser, a MachineChooser enum member, or a
|
61
|
-
callable that takes a dispatcher and an operation and returns
|
62
|
-
the machine id where the operation will be dispatched.
|
63
|
-
pruning_function:
|
64
|
-
The pruning function to use. It can be a string with the name
|
65
|
-
of the pruning function, a PruningFunction enum member, or a
|
66
|
-
callable that takes a dispatcher and a list of operations and
|
67
|
-
returns a list of operations that should be considered for
|
68
|
-
dispatching.
|
69
|
-
"""
|
70
85
|
if isinstance(dispatching_rule, str):
|
71
86
|
dispatching_rule = dispatching_rule_factory(dispatching_rule)
|
72
87
|
if isinstance(machine_chooser, str):
|
73
88
|
machine_chooser = machine_chooser_factory(machine_chooser)
|
74
|
-
if isinstance(
|
75
|
-
|
76
|
-
|
89
|
+
if isinstance(ready_operations_filter, str):
|
90
|
+
ready_operations_filter = ready_operations_filter_factory(
|
91
|
+
ready_operations_filter
|
92
|
+
)
|
93
|
+
if isinstance(ready_operations_filter, Iterable):
|
94
|
+
ready_operations_filter = create_composite_operation_filter(
|
95
|
+
ready_operations_filter
|
77
96
|
)
|
78
97
|
|
79
98
|
self.dispatching_rule = dispatching_rule
|
80
99
|
self.machine_chooser = machine_chooser
|
81
|
-
self.
|
100
|
+
self.ready_operations_filter = ready_operations_filter
|
82
101
|
|
83
102
|
def solve(
|
84
103
|
self, instance: JobShopInstance, dispatcher: Dispatcher | None = None
|
@@ -87,7 +106,7 @@ class DispatchingRuleSolver(BaseSolver):
|
|
87
106
|
dispatching rule algorithm."""
|
88
107
|
if dispatcher is None:
|
89
108
|
dispatcher = Dispatcher(
|
90
|
-
instance, ready_operations_filter=self.
|
109
|
+
instance, ready_operations_filter=self.ready_operations_filter
|
91
110
|
)
|
92
111
|
while not dispatcher.schedule.is_complete():
|
93
112
|
self.step(dispatcher)
|
@@ -110,19 +129,23 @@ class DispatchingRuleSolver(BaseSolver):
|
|
110
129
|
if __name__ == "__main__":
|
111
130
|
import time
|
112
131
|
import cProfile
|
113
|
-
import pstats
|
114
|
-
from io import StringIO
|
115
|
-
from job_shop_lib.benchmarking import
|
132
|
+
# import pstats
|
133
|
+
# from io import StringIO
|
134
|
+
from job_shop_lib.benchmarking import (
|
135
|
+
# load_benchmark_instance,
|
136
|
+
load_all_benchmark_instances,
|
137
|
+
)
|
116
138
|
|
117
139
|
# from job_shop_lib.dispatching.rules._dispatching_rules_functions import (
|
118
140
|
# most_work_remaining_rule_2,
|
119
141
|
# )
|
120
142
|
|
121
|
-
ta_instances = [
|
122
|
-
|
123
|
-
]
|
143
|
+
# ta_instances = [
|
144
|
+
# load_benchmark_instance(f"ta{i:02d}") for i in range(1, 81)
|
145
|
+
# ]
|
146
|
+
ta_instances = load_all_benchmark_instances().values()
|
124
147
|
solver = DispatchingRuleSolver(
|
125
|
-
dispatching_rule="most_work_remaining",
|
148
|
+
dispatching_rule="most_work_remaining", ready_operations_filter=None
|
126
149
|
)
|
127
150
|
|
128
151
|
start = time.perf_counter()
|
@@ -131,10 +154,10 @@ if __name__ == "__main__":
|
|
131
154
|
profiler = cProfile.Profile()
|
132
155
|
|
133
156
|
# Run the code under profiling
|
134
|
-
profiler.enable()
|
157
|
+
# profiler.enable()
|
135
158
|
for instance_ in ta_instances:
|
136
159
|
solver.solve(instance_)
|
137
|
-
profiler.disable()
|
160
|
+
# profiler.disable()
|
138
161
|
|
139
162
|
end = time.perf_counter()
|
140
163
|
|
@@ -142,7 +165,7 @@ if __name__ == "__main__":
|
|
142
165
|
print(f"Elapsed time: {end - start:.2f} seconds.")
|
143
166
|
|
144
167
|
# Print profiling results
|
145
|
-
s = StringIO()
|
146
|
-
ps = pstats.Stats(profiler, stream=s).sort_stats("cumulative")
|
147
|
-
profiler.print_stats("cumtime") # Print top 20 time-consuming functions
|
168
|
+
# s = StringIO()
|
169
|
+
# ps = pstats.Stats(profiler, stream=s).sort_stats("cumulative")
|
170
|
+
# profiler.print_stats("cumtime") # Print top 20 time-consuming functions
|
148
171
|
# print(s.getvalue())
|