job-shop-lib 0.5.1__py3-none-any.whl → 1.0.0a1__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 +16 -8
- job_shop_lib/{base_solver.py → _base_solver.py} +1 -1
- job_shop_lib/{job_shop_instance.py → _job_shop_instance.py} +9 -4
- job_shop_lib/_operation.py +95 -0
- job_shop_lib/{schedule.py → _schedule.py} +73 -54
- job_shop_lib/{scheduled_operation.py → _scheduled_operation.py} +13 -37
- job_shop_lib/benchmarking/__init__.py +66 -43
- job_shop_lib/benchmarking/_load_benchmark.py +88 -0
- job_shop_lib/constraint_programming/__init__.py +13 -0
- job_shop_lib/{cp_sat/ortools_solver.py → constraint_programming/_ortools_solver.py} +57 -18
- job_shop_lib/dispatching/__init__.py +45 -41
- job_shop_lib/dispatching/{dispatcher.py → _dispatcher.py} +153 -80
- job_shop_lib/dispatching/_dispatcher_observer_config.py +54 -0
- job_shop_lib/dispatching/_factories.py +125 -0
- job_shop_lib/dispatching/{history_tracker.py → _history_observer.py} +4 -6
- job_shop_lib/dispatching/{pruning_functions.py → _ready_operation_filters.py} +6 -35
- job_shop_lib/dispatching/_unscheduled_operations_observer.py +69 -0
- job_shop_lib/dispatching/feature_observers/__init__.py +16 -10
- job_shop_lib/dispatching/feature_observers/{composite_feature_observer.py → _composite_feature_observer.py} +84 -2
- job_shop_lib/dispatching/feature_observers/{duration_observer.py → _duration_observer.py} +6 -17
- job_shop_lib/dispatching/feature_observers/{earliest_start_time_observer.py → _earliest_start_time_observer.py} +114 -35
- job_shop_lib/dispatching/feature_observers/{factory.py → _factory.py} +31 -5
- job_shop_lib/dispatching/feature_observers/{feature_observer.py → _feature_observer.py} +59 -16
- job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +97 -0
- job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +33 -0
- job_shop_lib/dispatching/feature_observers/{position_in_job_observer.py → _position_in_job_observer.py} +1 -8
- job_shop_lib/dispatching/feature_observers/{remaining_operations_observer.py → _remaining_operations_observer.py} +8 -26
- job_shop_lib/dispatching/rules/__init__.py +51 -0
- job_shop_lib/dispatching/rules/_dispatching_rule_factory.py +82 -0
- job_shop_lib/dispatching/{dispatching_rule_solver.py → rules/_dispatching_rule_solver.py} +44 -15
- job_shop_lib/dispatching/{dispatching_rules.py → rules/_dispatching_rules_functions.py} +74 -21
- job_shop_lib/dispatching/rules/_machine_chooser_factory.py +69 -0
- job_shop_lib/dispatching/rules/_utils.py +127 -0
- job_shop_lib/exceptions.py +18 -0
- job_shop_lib/generation/__init__.py +2 -2
- job_shop_lib/generation/{general_instance_generator.py → _general_instance_generator.py} +26 -7
- job_shop_lib/generation/{instance_generator.py → _instance_generator.py} +13 -3
- job_shop_lib/graphs/__init__.py +17 -6
- job_shop_lib/graphs/{job_shop_graph.py → _job_shop_graph.py} +81 -2
- job_shop_lib/graphs/{node.py → _node.py} +18 -12
- job_shop_lib/graphs/graph_updaters/__init__.py +13 -0
- job_shop_lib/graphs/graph_updaters/_graph_updater.py +59 -0
- job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +154 -0
- job_shop_lib/graphs/graph_updaters/_utils.py +25 -0
- job_shop_lib/reinforcement_learning/__init__.py +41 -0
- job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +366 -0
- job_shop_lib/reinforcement_learning/_reward_observers.py +85 -0
- job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +337 -0
- job_shop_lib/reinforcement_learning/_types_and_constants.py +61 -0
- job_shop_lib/reinforcement_learning/_utils.py +96 -0
- job_shop_lib/visualization/__init__.py +20 -4
- job_shop_lib/visualization/{agent_task_graph.py → _agent_task_graph.py} +28 -9
- job_shop_lib/visualization/_gantt_chart_creator.py +219 -0
- job_shop_lib/visualization/_gantt_chart_video_and_gif_creation.py +388 -0
- {job_shop_lib-0.5.1.dist-info → job_shop_lib-1.0.0a1.dist-info}/METADATA +68 -44
- job_shop_lib-1.0.0a1.dist-info/RECORD +66 -0
- job_shop_lib/benchmarking/load_benchmark.py +0 -142
- job_shop_lib/cp_sat/__init__.py +0 -5
- job_shop_lib/dispatching/factories.py +0 -206
- job_shop_lib/dispatching/feature_observers/is_completed_observer.py +0 -98
- job_shop_lib/dispatching/feature_observers/is_ready_observer.py +0 -40
- job_shop_lib/generators/__init__.py +0 -8
- job_shop_lib/generators/basic_generator.py +0 -200
- job_shop_lib/generators/transformations.py +0 -164
- job_shop_lib/operation.py +0 -122
- job_shop_lib/visualization/create_gif.py +0 -209
- job_shop_lib-0.5.1.dist-info/RECORD +0 -52
- /job_shop_lib/dispatching/feature_observers/{is_scheduled_observer.py → _is_scheduled_observer.py} +0 -0
- /job_shop_lib/generation/{transformations.py → _transformations.py} +0 -0
- /job_shop_lib/graphs/{build_agent_task_graph.py → _build_agent_task_graph.py} +0 -0
- /job_shop_lib/graphs/{build_disjunctive_graph.py → _build_disjunctive_graph.py} +0 -0
- /job_shop_lib/graphs/{constants.py → _constants.py} +0 -0
- /job_shop_lib/visualization/{disjunctive_graph.py → _disjunctive_graph.py} +0 -0
- /job_shop_lib/visualization/{gantt_chart.py → _gantt_chart.py} +0 -0
- {job_shop_lib-0.5.1.dist-info → job_shop_lib-1.0.0a1.dist-info}/LICENSE +0 -0
- {job_shop_lib-0.5.1.dist-info → job_shop_lib-1.0.0a1.dist-info}/WHEEL +0 -0
@@ -3,7 +3,9 @@
|
|
3
3
|
import enum
|
4
4
|
|
5
5
|
import numpy as np
|
6
|
+
|
6
7
|
from job_shop_lib import ScheduledOperation
|
8
|
+
from job_shop_lib.exceptions import ValidationError
|
7
9
|
from job_shop_lib.dispatching import Dispatcher, DispatcherObserver
|
8
10
|
|
9
11
|
|
@@ -16,22 +18,40 @@ class FeatureType(str, enum.Enum):
|
|
16
18
|
|
17
19
|
|
18
20
|
class FeatureObserver(DispatcherObserver):
|
19
|
-
"""Base class for feature observers.
|
21
|
+
"""Base class for feature observers.
|
22
|
+
|
23
|
+
Feature observers are not singleton by default. This means that more than
|
24
|
+
one instance of the same feature observer type can be subscribed to the
|
25
|
+
dispatcher. This is useful when the first subscriber only observes a subset
|
26
|
+
of the features, and the second subscriber observes a different subset of
|
27
|
+
them. For example, the first subscriber could observe only the
|
28
|
+
operation-related features, while the second subscriber could observe the
|
29
|
+
jobs.
|
30
|
+
"""
|
31
|
+
|
32
|
+
_is_singleton = False
|
33
|
+
_feature_size: dict[FeatureType, int] | int = 1
|
34
|
+
_supported_feature_types = list(FeatureType)
|
35
|
+
|
36
|
+
__slots__ = (
|
37
|
+
"features",
|
38
|
+
"feature_dimensions",
|
39
|
+
)
|
20
40
|
|
21
41
|
def __init__(
|
22
42
|
self,
|
23
43
|
dispatcher: Dispatcher,
|
24
|
-
|
25
|
-
feature_size: dict[FeatureType, int] | int = 1,
|
26
|
-
is_singleton: bool = True,
|
44
|
+
*,
|
27
45
|
subscribe: bool = True,
|
46
|
+
feature_types: list[FeatureType] | FeatureType | None = None,
|
28
47
|
):
|
29
|
-
feature_types = self.
|
30
|
-
if isinstance(
|
48
|
+
feature_types = self._get_feature_types_list(feature_types)
|
49
|
+
if isinstance(self._feature_size, int):
|
31
50
|
feature_size = {
|
32
|
-
feature_type:
|
51
|
+
feature_type: self._feature_size
|
52
|
+
for feature_type in feature_types
|
33
53
|
}
|
34
|
-
super().__init__(dispatcher,
|
54
|
+
super().__init__(dispatcher, subscribe=subscribe)
|
35
55
|
|
36
56
|
number_of_entities = {
|
37
57
|
FeatureType.OPERATIONS: dispatcher.instance.num_operations,
|
@@ -54,6 +74,21 @@ class FeatureObserver(DispatcherObserver):
|
|
54
74
|
}
|
55
75
|
self.initialize_features()
|
56
76
|
|
77
|
+
@property
|
78
|
+
def feature_size(self) -> dict[FeatureType, int]:
|
79
|
+
"""Returns the size of the features."""
|
80
|
+
if isinstance(self._feature_size, int):
|
81
|
+
return {
|
82
|
+
feature_type: self._feature_size
|
83
|
+
for feature_type in self.features
|
84
|
+
}
|
85
|
+
return self._feature_size
|
86
|
+
|
87
|
+
@property
|
88
|
+
def supported_feature_types(self) -> list[FeatureType]:
|
89
|
+
"""Returns the supported feature types."""
|
90
|
+
return self._supported_feature_types
|
91
|
+
|
57
92
|
def initialize_features(self):
|
58
93
|
"""Initializes the features based on the current state of the
|
59
94
|
dispatcher."""
|
@@ -61,16 +96,16 @@ class FeatureObserver(DispatcherObserver):
|
|
61
96
|
def update(self, scheduled_operation: ScheduledOperation):
|
62
97
|
"""Updates the features based on the scheduled operation.
|
63
98
|
|
64
|
-
By default, this method just calls
|
99
|
+
By default, this method just calls :meth:`initialize_features`.
|
65
100
|
|
66
101
|
Args:
|
67
|
-
|
102
|
+
ScheduledOperation:
|
68
103
|
The operation that has been scheduled.
|
69
104
|
"""
|
70
105
|
self.initialize_features()
|
71
106
|
|
72
107
|
def reset(self):
|
73
|
-
"""Sets features to zero and calls to
|
108
|
+
"""Sets features to zero and calls to :meth:``initialize_features``."""
|
74
109
|
self.set_features_to_zero()
|
75
110
|
self.initialize_features()
|
76
111
|
|
@@ -88,21 +123,29 @@ class FeatureObserver(DispatcherObserver):
|
|
88
123
|
continue
|
89
124
|
self.features[feature_type][:] = 0.0
|
90
125
|
|
91
|
-
|
92
|
-
|
126
|
+
def _get_feature_types_list(
|
127
|
+
self,
|
93
128
|
feature_types: list[FeatureType] | FeatureType | None,
|
94
129
|
) -> list[FeatureType]:
|
95
130
|
"""Returns a list of feature types.
|
96
131
|
|
97
132
|
Args:
|
98
133
|
feature_types:
|
99
|
-
A list of feature types or a single feature type. If
|
134
|
+
A list of feature types or a single feature type. If ``None``,
|
100
135
|
all feature types are returned.
|
101
136
|
"""
|
102
137
|
if isinstance(feature_types, FeatureType):
|
103
|
-
|
138
|
+
return [feature_types]
|
104
139
|
if feature_types is None:
|
105
|
-
|
140
|
+
return self._supported_feature_types
|
141
|
+
|
142
|
+
for feature_type in feature_types:
|
143
|
+
if feature_type not in self._supported_feature_types:
|
144
|
+
raise ValidationError(
|
145
|
+
f"Feature type {feature_type} is not supported."
|
146
|
+
" Supported feature types are: "
|
147
|
+
f"{self._supported_feature_types}"
|
148
|
+
)
|
106
149
|
return feature_types
|
107
150
|
|
108
151
|
def __str__(self):
|
@@ -0,0 +1,97 @@
|
|
1
|
+
"""Home of the `IsCompletedObserver` class."""
|
2
|
+
|
3
|
+
import numpy as np
|
4
|
+
|
5
|
+
from job_shop_lib import ScheduledOperation
|
6
|
+
from job_shop_lib.dispatching import (
|
7
|
+
Dispatcher,
|
8
|
+
DispatcherObserver,
|
9
|
+
)
|
10
|
+
from job_shop_lib.dispatching.feature_observers import (
|
11
|
+
FeatureObserver,
|
12
|
+
FeatureType,
|
13
|
+
RemainingOperationsObserver,
|
14
|
+
)
|
15
|
+
|
16
|
+
|
17
|
+
class IsCompletedObserver(FeatureObserver):
|
18
|
+
"""Observer that adds a binary feature indicating whether each operation,
|
19
|
+
machine, or job has been completed."""
|
20
|
+
|
21
|
+
def __init__(
|
22
|
+
self,
|
23
|
+
dispatcher: Dispatcher,
|
24
|
+
feature_types: list[FeatureType] | FeatureType | None = None,
|
25
|
+
subscribe: bool = True,
|
26
|
+
):
|
27
|
+
feature_types = self._get_feature_types_list(feature_types)
|
28
|
+
self.remaining_ops_per_machine = np.zeros(
|
29
|
+
(dispatcher.instance.num_machines, 1), dtype=int
|
30
|
+
)
|
31
|
+
self.remaining_ops_per_job = np.zeros(
|
32
|
+
(dispatcher.instance.num_jobs, 1), dtype=int
|
33
|
+
)
|
34
|
+
super().__init__(
|
35
|
+
dispatcher,
|
36
|
+
feature_types=feature_types,
|
37
|
+
subscribe=subscribe,
|
38
|
+
)
|
39
|
+
|
40
|
+
def initialize_features(self):
|
41
|
+
def _has_same_features(observer: DispatcherObserver) -> bool:
|
42
|
+
if not isinstance(observer, RemainingOperationsObserver):
|
43
|
+
return False
|
44
|
+
return all(
|
45
|
+
feature_type in observer.features
|
46
|
+
for feature_type in remaining_ops_feature_types
|
47
|
+
)
|
48
|
+
|
49
|
+
self.set_features_to_zero()
|
50
|
+
|
51
|
+
remaining_ops_feature_types = [
|
52
|
+
feature_type
|
53
|
+
for feature_type in self.features.keys()
|
54
|
+
if feature_type != FeatureType.OPERATIONS
|
55
|
+
]
|
56
|
+
remaining_ops_observer = self.dispatcher.create_or_get_observer(
|
57
|
+
RemainingOperationsObserver,
|
58
|
+
condition=_has_same_features,
|
59
|
+
feature_types=remaining_ops_feature_types,
|
60
|
+
)
|
61
|
+
if FeatureType.JOBS in self.features:
|
62
|
+
self.remaining_ops_per_job = remaining_ops_observer.features[
|
63
|
+
FeatureType.JOBS
|
64
|
+
].copy()
|
65
|
+
if FeatureType.MACHINES in self.features:
|
66
|
+
self.remaining_ops_per_machine = remaining_ops_observer.features[
|
67
|
+
FeatureType.MACHINES
|
68
|
+
].copy()
|
69
|
+
|
70
|
+
def reset(self):
|
71
|
+
self.initialize_features()
|
72
|
+
|
73
|
+
def update(self, scheduled_operation: ScheduledOperation):
|
74
|
+
if FeatureType.OPERATIONS in self.features:
|
75
|
+
completed_operations = [
|
76
|
+
op.operation_id
|
77
|
+
for op in self.dispatcher.completed_operations()
|
78
|
+
]
|
79
|
+
self.features[FeatureType.OPERATIONS][completed_operations, 0] = 1
|
80
|
+
if FeatureType.MACHINES in self.features:
|
81
|
+
self.remaining_ops_per_machine[
|
82
|
+
scheduled_operation.operation.machines, 0
|
83
|
+
] -= 1
|
84
|
+
is_completed = (
|
85
|
+
self.remaining_ops_per_machine[
|
86
|
+
scheduled_operation.operation.machines, 0
|
87
|
+
]
|
88
|
+
== 0
|
89
|
+
)
|
90
|
+
self.features[FeatureType.MACHINES][
|
91
|
+
scheduled_operation.operation.machines, 0
|
92
|
+
] = is_completed
|
93
|
+
if FeatureType.JOBS in self.features:
|
94
|
+
job_id = scheduled_operation.job_id
|
95
|
+
self.remaining_ops_per_job[job_id, 0] -= 1
|
96
|
+
is_completed = self.remaining_ops_per_job[job_id, 0] == 0
|
97
|
+
self.features[FeatureType.JOBS][job_id, 0] = is_completed
|
@@ -0,0 +1,33 @@
|
|
1
|
+
"""Home of the `IsReadyObserver` class."""
|
2
|
+
|
3
|
+
from job_shop_lib.dispatching.feature_observers import (
|
4
|
+
FeatureObserver,
|
5
|
+
FeatureType,
|
6
|
+
)
|
7
|
+
|
8
|
+
|
9
|
+
class IsReadyObserver(FeatureObserver):
|
10
|
+
"""Feature creator that adds a binary feature indicating if the operation
|
11
|
+
is ready to be dispatched."""
|
12
|
+
|
13
|
+
def initialize_features(self):
|
14
|
+
self.set_features_to_zero()
|
15
|
+
for feature_type, feature in self.features.items():
|
16
|
+
feature_ids = self._get_ready_feature_ids(feature_type)
|
17
|
+
feature[feature_ids, 0] = 1.0
|
18
|
+
|
19
|
+
def _get_ready_feature_ids(self, feature_type: FeatureType) -> list[int]:
|
20
|
+
if feature_type == FeatureType.OPERATIONS:
|
21
|
+
return self._get_ready_operations()
|
22
|
+
if feature_type == FeatureType.MACHINES:
|
23
|
+
return self.dispatcher.available_machines()
|
24
|
+
if feature_type == FeatureType.JOBS:
|
25
|
+
return self.dispatcher.available_jobs()
|
26
|
+
raise ValueError(f"Feature type {feature_type} is not supported.")
|
27
|
+
|
28
|
+
def reset(self):
|
29
|
+
self.initialize_features()
|
30
|
+
|
31
|
+
def _get_ready_operations(self) -> list[int]:
|
32
|
+
available_operations = self.dispatcher.ready_operations()
|
33
|
+
return [operation.operation_id for operation in available_operations]
|
@@ -1,6 +1,5 @@
|
|
1
1
|
"""Home of the `PositionInJobObserver` class."""
|
2
2
|
|
3
|
-
from job_shop_lib.dispatching import Dispatcher
|
4
3
|
from job_shop_lib import ScheduledOperation
|
5
4
|
from job_shop_lib.dispatching.feature_observers import (
|
6
5
|
FeatureObserver,
|
@@ -15,13 +14,7 @@ class PositionInJobObserver(FeatureObserver):
|
|
15
14
|
Positions are adjusted dynamically as operations are scheduled.
|
16
15
|
"""
|
17
16
|
|
18
|
-
|
19
|
-
super().__init__(
|
20
|
-
dispatcher,
|
21
|
-
feature_types=[FeatureType.OPERATIONS],
|
22
|
-
feature_size=1,
|
23
|
-
subscribe=subscribe,
|
24
|
-
)
|
17
|
+
_supported_feature_types = [FeatureType.OPERATIONS]
|
25
18
|
|
26
19
|
def initialize_features(self):
|
27
20
|
for operation in self.dispatcher.unscheduled_operations():
|
@@ -1,7 +1,7 @@
|
|
1
1
|
"""Home of the `RemainingOperationsObserver` class."""
|
2
2
|
|
3
3
|
from job_shop_lib import ScheduledOperation
|
4
|
-
from job_shop_lib.dispatching import
|
4
|
+
from job_shop_lib.dispatching import UnscheduledOperationsObserver
|
5
5
|
from job_shop_lib.dispatching.feature_observers import (
|
6
6
|
FeatureObserver,
|
7
7
|
FeatureType,
|
@@ -12,38 +12,20 @@ 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 FeatureType.OPERATIONS
|
15
|
+
It does not support :class:`FeatureType.OPERATIONS`.
|
16
16
|
"""
|
17
17
|
|
18
|
-
|
19
|
-
self,
|
20
|
-
dispatcher: Dispatcher,
|
21
|
-
feature_types: list[FeatureType] | FeatureType | None = None,
|
22
|
-
subscribe: bool = True,
|
23
|
-
):
|
24
|
-
if feature_types is None:
|
25
|
-
feature_types = [FeatureType.MACHINES, FeatureType.JOBS]
|
26
|
-
|
27
|
-
if (
|
28
|
-
feature_types == FeatureType.OPERATIONS
|
29
|
-
or FeatureType.OPERATIONS in feature_types
|
30
|
-
):
|
31
|
-
raise ValueError("FeatureType.OPERATIONS is not supported.")
|
32
|
-
super().__init__(
|
33
|
-
dispatcher,
|
34
|
-
feature_types=feature_types,
|
35
|
-
feature_size=1,
|
36
|
-
subscribe=subscribe,
|
37
|
-
)
|
18
|
+
_supported_feature_types = [FeatureType.MACHINES, FeatureType.JOBS]
|
38
19
|
|
39
20
|
def initialize_features(self):
|
40
|
-
|
21
|
+
unscheduled_ops_observer = self.dispatcher.create_or_get_observer(
|
22
|
+
UnscheduledOperationsObserver
|
23
|
+
)
|
24
|
+
for operation in unscheduled_ops_observer.unscheduled_operations:
|
41
25
|
if FeatureType.JOBS in self.features:
|
42
26
|
self.features[FeatureType.JOBS][operation.job_id, 0] += 1
|
43
27
|
if FeatureType.MACHINES in self.features:
|
44
|
-
self.features[FeatureType.MACHINES][
|
45
|
-
operation.machine_id, 0
|
46
|
-
] += 1
|
28
|
+
self.features[FeatureType.MACHINES][operation.machines, 0] += 1
|
47
29
|
|
48
30
|
def update(self, scheduled_operation: ScheduledOperation):
|
49
31
|
if FeatureType.JOBS in self.features:
|
@@ -0,0 +1,51 @@
|
|
1
|
+
"""Contains the dispatching rules for the job shop scheduling problem."""
|
2
|
+
|
3
|
+
from ._dispatching_rules_functions import (
|
4
|
+
shortest_processing_time_rule,
|
5
|
+
first_come_first_served_rule,
|
6
|
+
most_work_remaining_rule,
|
7
|
+
most_operations_remaining_rule,
|
8
|
+
random_operation_rule,
|
9
|
+
score_based_rule,
|
10
|
+
score_based_rule_with_tie_breaker,
|
11
|
+
shortest_processing_time_score,
|
12
|
+
first_come_first_served_score,
|
13
|
+
MostWorkRemainingScorer,
|
14
|
+
most_operations_remaining_score,
|
15
|
+
random_score,
|
16
|
+
observer_based_most_work_remaining_rule,
|
17
|
+
)
|
18
|
+
from ._machine_chooser_factory import (
|
19
|
+
MachineChooserType,
|
20
|
+
MachineChooser,
|
21
|
+
machine_chooser_factory,
|
22
|
+
)
|
23
|
+
|
24
|
+
from ._dispatching_rule_factory import (
|
25
|
+
dispatching_rule_factory,
|
26
|
+
DispatchingRuleType,
|
27
|
+
)
|
28
|
+
from ._dispatching_rule_solver import DispatchingRuleSolver
|
29
|
+
|
30
|
+
|
31
|
+
__all__ = [
|
32
|
+
"shortest_processing_time_rule",
|
33
|
+
"first_come_first_served_rule",
|
34
|
+
"most_work_remaining_rule",
|
35
|
+
"most_operations_remaining_rule",
|
36
|
+
"random_operation_rule",
|
37
|
+
"score_based_rule",
|
38
|
+
"score_based_rule_with_tie_breaker",
|
39
|
+
"shortest_processing_time_score",
|
40
|
+
"first_come_first_served_score",
|
41
|
+
"MostWorkRemainingScorer",
|
42
|
+
"most_operations_remaining_score",
|
43
|
+
"random_score",
|
44
|
+
"dispatching_rule_factory",
|
45
|
+
"DispatchingRuleType",
|
46
|
+
"MachineChooserType",
|
47
|
+
"machine_chooser_factory",
|
48
|
+
"MachineChooser",
|
49
|
+
"DispatchingRuleSolver",
|
50
|
+
"observer_based_most_work_remaining_rule",
|
51
|
+
]
|
@@ -0,0 +1,82 @@
|
|
1
|
+
"""Contains factory functions for creating dispatching rules, machine choosers,
|
2
|
+
and pruning functions for the job shop scheduling problem.
|
3
|
+
|
4
|
+
The factory functions create and return the appropriate functions based on the
|
5
|
+
specified names or enums.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from enum import Enum
|
9
|
+
from collections.abc import Callable
|
10
|
+
|
11
|
+
from job_shop_lib import Operation
|
12
|
+
from job_shop_lib.exceptions import ValidationError
|
13
|
+
from job_shop_lib.dispatching import Dispatcher
|
14
|
+
from job_shop_lib.dispatching.rules import (
|
15
|
+
shortest_processing_time_rule,
|
16
|
+
first_come_first_served_rule,
|
17
|
+
most_operations_remaining_rule,
|
18
|
+
random_operation_rule,
|
19
|
+
most_work_remaining_rule,
|
20
|
+
)
|
21
|
+
|
22
|
+
|
23
|
+
class DispatchingRuleType(str, Enum):
|
24
|
+
"""Enumeration of dispatching rules for the job shop scheduling problem."""
|
25
|
+
|
26
|
+
SHORTEST_PROCESSING_TIME = "shortest_processing_time"
|
27
|
+
FIRST_COME_FIRST_SERVED = "first_come_first_served"
|
28
|
+
MOST_WORK_REMAINING = "most_work_remaining"
|
29
|
+
MOST_OPERATIONS_REMAINING = "most_operations_remaining"
|
30
|
+
RANDOM = "random"
|
31
|
+
|
32
|
+
|
33
|
+
def dispatching_rule_factory(
|
34
|
+
dispatching_rule: str | DispatchingRuleType,
|
35
|
+
) -> Callable[[Dispatcher], Operation]:
|
36
|
+
"""Creates and returns a dispatching rule function based on the specified
|
37
|
+
dispatching rule name.
|
38
|
+
|
39
|
+
The dispatching rule function determines the order in which operations are
|
40
|
+
selected for execution based on certain criteria such as shortest
|
41
|
+
processing time, first come first served, etc.
|
42
|
+
|
43
|
+
Args:
|
44
|
+
dispatching_rule: The name of the dispatching rule to be used.
|
45
|
+
Supported values are 'shortest_processing_time',
|
46
|
+
'first_come_first_served', 'most_work_remaining',
|
47
|
+
and 'random'.
|
48
|
+
|
49
|
+
Returns:
|
50
|
+
A function that takes a Dispatcher instance as input and returns an
|
51
|
+
Operation based on the specified dispatching rule.
|
52
|
+
|
53
|
+
Raises:
|
54
|
+
ValidationError:
|
55
|
+
If the dispatching_rule argument is not recognized or it is
|
56
|
+
not supported.
|
57
|
+
"""
|
58
|
+
dispatching_rules: dict[
|
59
|
+
DispatchingRuleType,
|
60
|
+
Callable[[Dispatcher], Operation],
|
61
|
+
] = {
|
62
|
+
DispatchingRuleType.SHORTEST_PROCESSING_TIME: (
|
63
|
+
shortest_processing_time_rule
|
64
|
+
),
|
65
|
+
DispatchingRuleType.FIRST_COME_FIRST_SERVED: (
|
66
|
+
first_come_first_served_rule
|
67
|
+
),
|
68
|
+
DispatchingRuleType.MOST_WORK_REMAINING: most_work_remaining_rule,
|
69
|
+
DispatchingRuleType.MOST_OPERATIONS_REMAINING: (
|
70
|
+
most_operations_remaining_rule
|
71
|
+
),
|
72
|
+
DispatchingRuleType.RANDOM: random_operation_rule,
|
73
|
+
}
|
74
|
+
|
75
|
+
dispatching_rule = dispatching_rule.lower()
|
76
|
+
if dispatching_rule not in dispatching_rules:
|
77
|
+
raise ValidationError(
|
78
|
+
f"Dispatching rule {dispatching_rule} not recognized. Available "
|
79
|
+
f"dispatching rules: {', '.join(dispatching_rules)}."
|
80
|
+
)
|
81
|
+
|
82
|
+
return dispatching_rules[dispatching_rule] # type: ignore[index]
|
@@ -4,13 +4,15 @@ from collections.abc import Callable
|
|
4
4
|
|
5
5
|
from job_shop_lib import JobShopInstance, Schedule, Operation, BaseSolver
|
6
6
|
from job_shop_lib.dispatching import (
|
7
|
+
ready_operations_filter_factory,
|
8
|
+
Dispatcher,
|
9
|
+
ReadyOperationsFilterType,
|
10
|
+
)
|
11
|
+
from job_shop_lib.dispatching.rules import (
|
7
12
|
dispatching_rule_factory,
|
8
13
|
machine_chooser_factory,
|
9
|
-
|
10
|
-
|
11
|
-
MachineChooser,
|
12
|
-
Dispatcher,
|
13
|
-
PruningFunction,
|
14
|
+
DispatchingRuleType,
|
15
|
+
MachineChooserType,
|
14
16
|
)
|
15
17
|
|
16
18
|
|
@@ -34,15 +36,15 @@ class DispatchingRuleSolver(BaseSolver):
|
|
34
36
|
self,
|
35
37
|
dispatching_rule: (
|
36
38
|
str | Callable[[Dispatcher], Operation]
|
37
|
-
) =
|
39
|
+
) = DispatchingRuleType.MOST_WORK_REMAINING,
|
38
40
|
machine_chooser: (
|
39
41
|
str | Callable[[Dispatcher, Operation], int]
|
40
|
-
) =
|
42
|
+
) = MachineChooserType.FIRST,
|
41
43
|
pruning_function: (
|
42
44
|
str
|
43
45
|
| Callable[[Dispatcher, list[Operation]], list[Operation]]
|
44
46
|
| None
|
45
|
-
) =
|
47
|
+
) = ReadyOperationsFilterType.DOMINATED_OPERATIONS,
|
46
48
|
):
|
47
49
|
"""Initializes the solver with the given dispatching rule, machine
|
48
50
|
chooser and pruning function.
|
@@ -70,7 +72,9 @@ class DispatchingRuleSolver(BaseSolver):
|
|
70
72
|
if isinstance(machine_chooser, str):
|
71
73
|
machine_chooser = machine_chooser_factory(machine_chooser)
|
72
74
|
if isinstance(pruning_function, str):
|
73
|
-
pruning_function =
|
75
|
+
pruning_function = ready_operations_filter_factory(
|
76
|
+
pruning_function
|
77
|
+
)
|
74
78
|
|
75
79
|
self.dispatching_rule = dispatching_rule
|
76
80
|
self.machine_chooser = machine_chooser
|
@@ -83,7 +87,7 @@ class DispatchingRuleSolver(BaseSolver):
|
|
83
87
|
dispatching rule algorithm."""
|
84
88
|
if dispatcher is None:
|
85
89
|
dispatcher = Dispatcher(
|
86
|
-
instance,
|
90
|
+
instance, ready_operations_filter=self.pruning_function
|
87
91
|
)
|
88
92
|
while not dispatcher.schedule.is_complete():
|
89
93
|
self.step(dispatcher)
|
@@ -105,15 +109,40 @@ class DispatchingRuleSolver(BaseSolver):
|
|
105
109
|
|
106
110
|
if __name__ == "__main__":
|
107
111
|
import time
|
112
|
+
import cProfile
|
113
|
+
import pstats
|
114
|
+
from io import StringIO
|
108
115
|
from job_shop_lib.benchmarking import load_benchmark_instance
|
109
116
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
117
|
+
# from job_shop_lib.dispatching.rules._dispatching_rules_functions import (
|
118
|
+
# most_work_remaining_rule_2,
|
119
|
+
# )
|
120
|
+
|
121
|
+
ta_instances = [
|
122
|
+
load_benchmark_instance(f"ta{i:02d}") for i in range(1, 81)
|
123
|
+
]
|
124
|
+
solver = DispatchingRuleSolver(
|
125
|
+
dispatching_rule="most_work_remaining", pruning_function=None
|
126
|
+
)
|
127
|
+
|
115
128
|
start = time.perf_counter()
|
129
|
+
|
130
|
+
# Create a Profile object
|
131
|
+
profiler = cProfile.Profile()
|
132
|
+
|
133
|
+
# Run the code under profiling
|
134
|
+
profiler.enable()
|
116
135
|
for instance_ in ta_instances:
|
117
136
|
solver.solve(instance_)
|
137
|
+
profiler.disable()
|
138
|
+
|
118
139
|
end = time.perf_counter()
|
140
|
+
|
141
|
+
# Print elapsed time
|
119
142
|
print(f"Elapsed time: {end - start:.2f} seconds.")
|
143
|
+
|
144
|
+
# 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
|
148
|
+
# print(s.getvalue())
|