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
@@ -1,7 +1,11 @@
|
|
1
1
|
"""Home of the `Node` class."""
|
2
2
|
|
3
|
-
from job_shop_lib import Operation
|
4
|
-
from job_shop_lib.
|
3
|
+
from job_shop_lib import Operation
|
4
|
+
from job_shop_lib.exceptions import (
|
5
|
+
UninitializedAttributeError,
|
6
|
+
ValidationError,
|
7
|
+
)
|
8
|
+
from job_shop_lib.graphs._constants import NodeType
|
5
9
|
|
6
10
|
|
7
11
|
class Node:
|
@@ -75,21 +79,21 @@ class Node:
|
|
75
79
|
node_type is NodeType.JOB.
|
76
80
|
|
77
81
|
Raises:
|
78
|
-
|
82
|
+
ValidationError:
|
79
83
|
If the node_type is OPERATION and operation is None.
|
80
|
-
|
84
|
+
ValidationError:
|
81
85
|
If the node_type is MACHINE and machine_id is None.
|
82
|
-
|
86
|
+
ValidationError:
|
83
87
|
If the node_type is JOB and job_id is None.
|
84
88
|
"""
|
85
89
|
if node_type == NodeType.OPERATION and operation is None:
|
86
|
-
raise
|
90
|
+
raise ValidationError("Operation node must have an operation.")
|
87
91
|
|
88
92
|
if node_type == NodeType.MACHINE and machine_id is None:
|
89
|
-
raise
|
93
|
+
raise ValidationError("Machine node must have a machine_id.")
|
90
94
|
|
91
95
|
if node_type == NodeType.JOB and job_id is None:
|
92
|
-
raise
|
96
|
+
raise ValidationError("Job node must have a job_id.")
|
93
97
|
|
94
98
|
self.node_type = node_type
|
95
99
|
self._node_id: int | None = None
|
@@ -102,7 +106,9 @@ class Node:
|
|
102
106
|
def node_id(self) -> int:
|
103
107
|
"""Returns a unique identifier for the node."""
|
104
108
|
if self._node_id is None:
|
105
|
-
raise
|
109
|
+
raise UninitializedAttributeError(
|
110
|
+
"Node has not been assigned an id."
|
111
|
+
)
|
106
112
|
return self._node_id
|
107
113
|
|
108
114
|
@node_id.setter
|
@@ -116,7 +122,7 @@ class Node:
|
|
116
122
|
This property is mandatory for nodes of type `OPERATION`.
|
117
123
|
"""
|
118
124
|
if self._operation is None:
|
119
|
-
raise
|
125
|
+
raise UninitializedAttributeError("Node has no operation.")
|
120
126
|
return self._operation
|
121
127
|
|
122
128
|
@property
|
@@ -126,7 +132,7 @@ class Node:
|
|
126
132
|
This property is mandatory for nodes of type `MACHINE`.
|
127
133
|
"""
|
128
134
|
if self._machine_id is None:
|
129
|
-
raise
|
135
|
+
raise UninitializedAttributeError("Node has no `machine_id`.")
|
130
136
|
return self._machine_id
|
131
137
|
|
132
138
|
@property
|
@@ -136,7 +142,7 @@ class Node:
|
|
136
142
|
This property is mandatory for nodes of type `JOB`.
|
137
143
|
"""
|
138
144
|
if self._job_id is None:
|
139
|
-
raise
|
145
|
+
raise UninitializedAttributeError("Node has no `job_id`.")
|
140
146
|
return self._job_id
|
141
147
|
|
142
148
|
def __hash__(self) -> int:
|
@@ -0,0 +1,13 @@
|
|
1
|
+
"""Contains classes and functions for updating the graph representation of the
|
2
|
+
job shop scheduling problem."""
|
3
|
+
|
4
|
+
from ._graph_updater import GraphUpdater
|
5
|
+
from ._utils import remove_completed_operations
|
6
|
+
from ._residual_graph_updater import ResidualGraphUpdater
|
7
|
+
|
8
|
+
|
9
|
+
__all__ = [
|
10
|
+
"GraphUpdater",
|
11
|
+
"remove_completed_operations",
|
12
|
+
"ResidualGraphUpdater",
|
13
|
+
]
|
@@ -0,0 +1,59 @@
|
|
1
|
+
"""Home of the `GraphUpdater` class."""
|
2
|
+
|
3
|
+
from abc import abstractmethod
|
4
|
+
from copy import deepcopy
|
5
|
+
|
6
|
+
from job_shop_lib import ScheduledOperation
|
7
|
+
from job_shop_lib.dispatching import DispatcherObserver, Dispatcher
|
8
|
+
from job_shop_lib.graphs import JobShopGraph
|
9
|
+
|
10
|
+
|
11
|
+
class GraphUpdater(DispatcherObserver):
|
12
|
+
"""Observer that builds and updates a job shop graph.
|
13
|
+
|
14
|
+
This observer uses a provided graph builder function to initialize the
|
15
|
+
job shop graph and a graph updater function to update the graph after
|
16
|
+
each scheduled operation.
|
17
|
+
|
18
|
+
Attributes:
|
19
|
+
initial_job_shop_graph:
|
20
|
+
The initial job shop graph. This is a copy of the graph that was
|
21
|
+
received when the observer was created. It is used to reset the
|
22
|
+
graph to its initial state.
|
23
|
+
job_shop_graph:
|
24
|
+
The current job shop graph. This is the graph that is updated
|
25
|
+
after each scheduled operation.
|
26
|
+
"""
|
27
|
+
|
28
|
+
def __init__(
|
29
|
+
self,
|
30
|
+
dispatcher: Dispatcher,
|
31
|
+
job_shop_graph: JobShopGraph,
|
32
|
+
*,
|
33
|
+
subscribe: bool = True,
|
34
|
+
):
|
35
|
+
"""Initializes the class.
|
36
|
+
|
37
|
+
Args:
|
38
|
+
dispatcher:
|
39
|
+
The dispatcher instance to observe.
|
40
|
+
job_shop_graph:
|
41
|
+
The job shop graph to update.
|
42
|
+
subscribe:
|
43
|
+
Whether to subscribe to the dispatcher. If ``True``, the
|
44
|
+
observer will subscribe to the dispatcher when it is
|
45
|
+
initialized. If ``False``, the observer will not subscribe
|
46
|
+
to the dispatcher.
|
47
|
+
"""
|
48
|
+
super().__init__(dispatcher, subscribe=subscribe)
|
49
|
+
self.initial_job_shop_graph = deepcopy(job_shop_graph)
|
50
|
+
self.job_shop_graph = job_shop_graph
|
51
|
+
|
52
|
+
def reset(self) -> None:
|
53
|
+
"""Resets the job shop graph."""
|
54
|
+
self.job_shop_graph = deepcopy(self.initial_job_shop_graph)
|
55
|
+
|
56
|
+
@abstractmethod
|
57
|
+
def update(self, scheduled_operation: ScheduledOperation) -> None:
|
58
|
+
"""Updates the job shop graph after an operation has been
|
59
|
+
dispatched."""
|
@@ -0,0 +1,154 @@
|
|
1
|
+
"""Home of the `ResidualGraphUpdater` class."""
|
2
|
+
|
3
|
+
from job_shop_lib import ScheduledOperation
|
4
|
+
from job_shop_lib.exceptions import UninitializedAttributeError
|
5
|
+
from job_shop_lib.graphs import NodeType, JobShopGraph
|
6
|
+
from job_shop_lib.graphs.graph_updaters import (
|
7
|
+
GraphUpdater,
|
8
|
+
remove_completed_operations,
|
9
|
+
)
|
10
|
+
from job_shop_lib.dispatching import Dispatcher
|
11
|
+
from job_shop_lib.dispatching.feature_observers import (
|
12
|
+
IsCompletedObserver,
|
13
|
+
FeatureType,
|
14
|
+
)
|
15
|
+
from job_shop_lib.dispatching import DispatcherObserver
|
16
|
+
|
17
|
+
|
18
|
+
class ResidualGraphUpdater(GraphUpdater):
|
19
|
+
"""Updates the residual graph based on the completed operations.
|
20
|
+
|
21
|
+
This observer updates the residual graph by removing the completed
|
22
|
+
operation, machine and job nodes from the graph. It subscribes to the
|
23
|
+
:class:`~job_shop_lib.dispatching.feature_observers.IsCompletedObserver`
|
24
|
+
to determine which operations, machines and jobs have been completed.
|
25
|
+
|
26
|
+
Attributes:
|
27
|
+
remove_completed_machine_nodes:
|
28
|
+
If True, removes completed machine nodes from the graph.
|
29
|
+
remove_completed_job_nodes:
|
30
|
+
If True, removes completed job nodes from the graph.
|
31
|
+
"""
|
32
|
+
|
33
|
+
def __init__(
|
34
|
+
self,
|
35
|
+
dispatcher: Dispatcher,
|
36
|
+
job_shop_graph: JobShopGraph,
|
37
|
+
*,
|
38
|
+
subscribe: bool = True,
|
39
|
+
remove_completed_machine_nodes: bool = True,
|
40
|
+
remove_completed_job_nodes: bool = True,
|
41
|
+
):
|
42
|
+
"""Initializes the residual graph updater.
|
43
|
+
|
44
|
+
Args:
|
45
|
+
dispatcher:
|
46
|
+
The dispatcher instance to observe.
|
47
|
+
job_shop_graph:
|
48
|
+
The job shop graph to update.
|
49
|
+
subscribe:
|
50
|
+
If True, automatically subscribes the observer to the
|
51
|
+
dispatcher. Defaults to True.
|
52
|
+
remove_completed_machine_nodes:
|
53
|
+
If True, removes completed machine nodes from the graph.
|
54
|
+
Defaults to True.
|
55
|
+
remove_completed_job_nodes:
|
56
|
+
If True, removes completed job nodes from the graph.
|
57
|
+
Defaults to True.
|
58
|
+
"""
|
59
|
+
|
60
|
+
self._is_completed_observer: None | IsCompletedObserver = None
|
61
|
+
self.remove_completed_job_nodes = remove_completed_job_nodes
|
62
|
+
self.remove_completed_machine_nodes = remove_completed_machine_nodes
|
63
|
+
self._initialize_is_completed_observer_attribute(dispatcher)
|
64
|
+
|
65
|
+
# It is important to initialize the `IsCompletedObserver` before
|
66
|
+
# calling the parent class constructor to ensure the observer is
|
67
|
+
# updated before the `update` method of this class is called.
|
68
|
+
super().__init__(
|
69
|
+
dispatcher,
|
70
|
+
job_shop_graph,
|
71
|
+
subscribe=subscribe,
|
72
|
+
)
|
73
|
+
|
74
|
+
def _initialize_is_completed_observer_attribute(
|
75
|
+
self, dispatcher: Dispatcher
|
76
|
+
):
|
77
|
+
def has_all_features(observer: DispatcherObserver) -> bool:
|
78
|
+
if not isinstance(observer, IsCompletedObserver):
|
79
|
+
return False # Make the type checker happy.
|
80
|
+
|
81
|
+
for feature_type in feature_types:
|
82
|
+
if feature_type not in observer.features.keys():
|
83
|
+
return False
|
84
|
+
return True
|
85
|
+
|
86
|
+
feature_types: list[FeatureType] = []
|
87
|
+
if self.remove_completed_machine_nodes:
|
88
|
+
feature_types.append(FeatureType.MACHINES)
|
89
|
+
if self.remove_completed_job_nodes:
|
90
|
+
feature_types.append(FeatureType.JOBS)
|
91
|
+
if feature_types:
|
92
|
+
self._is_completed_observer = dispatcher.create_or_get_observer(
|
93
|
+
IsCompletedObserver,
|
94
|
+
condition=has_all_features,
|
95
|
+
feature_types=feature_types,
|
96
|
+
)
|
97
|
+
|
98
|
+
@property
|
99
|
+
def is_completed_observer(self) -> IsCompletedObserver:
|
100
|
+
"""Returns the :class:`~job_shop_lib.dispatching.feature_observers.
|
101
|
+
IsCompletedObserver` instance."""
|
102
|
+
|
103
|
+
if self._is_completed_observer is None:
|
104
|
+
raise UninitializedAttributeError(
|
105
|
+
"The `is_completed_observer` attribute has not been "
|
106
|
+
"initialized. Set `remove_completed_machine_nodes` or "
|
107
|
+
"remove_completed_job_nodes` to True when initializing the "
|
108
|
+
"ResidualGraphUpdater."
|
109
|
+
)
|
110
|
+
return self._is_completed_observer
|
111
|
+
|
112
|
+
def update(self, scheduled_operation: ScheduledOperation) -> None:
|
113
|
+
"""Updates the residual graph based on the completed operations."""
|
114
|
+
remove_completed_operations(
|
115
|
+
self.job_shop_graph,
|
116
|
+
completed_operations=self.dispatcher.completed_operations(),
|
117
|
+
)
|
118
|
+
graph_has_machine_nodes = bool(
|
119
|
+
self.job_shop_graph.nodes_by_type[NodeType.MACHINE]
|
120
|
+
)
|
121
|
+
if self.remove_completed_machine_nodes and graph_has_machine_nodes:
|
122
|
+
self._remove_completed_machine_nodes()
|
123
|
+
|
124
|
+
graph_has_machine_nodes = bool(
|
125
|
+
self.job_shop_graph.nodes_by_type[NodeType.JOB]
|
126
|
+
)
|
127
|
+
if self.remove_completed_job_nodes and graph_has_machine_nodes:
|
128
|
+
self._remove_completed_job_nodes()
|
129
|
+
|
130
|
+
def _remove_completed_machine_nodes(self):
|
131
|
+
"""Removes the completed machine nodes from the graph if they are
|
132
|
+
not already removed."""
|
133
|
+
|
134
|
+
for machine_id, is_completed in enumerate(
|
135
|
+
self.is_completed_observer.features[FeatureType.MACHINES].flatten()
|
136
|
+
):
|
137
|
+
if is_completed == 1 and not self.job_shop_graph.is_removed(
|
138
|
+
machine_node := self.job_shop_graph.get_machine_node(
|
139
|
+
machine_id
|
140
|
+
)
|
141
|
+
):
|
142
|
+
self.job_shop_graph.remove_node(machine_node.node_id)
|
143
|
+
|
144
|
+
def _remove_completed_job_nodes(self):
|
145
|
+
"""Removes the completed job nodes from the graph if they are not
|
146
|
+
already removed."""
|
147
|
+
|
148
|
+
for job_id, is_completed in enumerate(
|
149
|
+
self.is_completed_observer.features[FeatureType.JOBS]
|
150
|
+
):
|
151
|
+
if is_completed == 1 and not self.job_shop_graph.is_removed(
|
152
|
+
job_node := self.job_shop_graph.get_job_node(job_id)
|
153
|
+
):
|
154
|
+
self.job_shop_graph.remove_node(job_node.node_id)
|
@@ -0,0 +1,25 @@
|
|
1
|
+
"""Contains grah updater functions to update """
|
2
|
+
|
3
|
+
from collections.abc import Iterable
|
4
|
+
|
5
|
+
from job_shop_lib import Operation
|
6
|
+
from job_shop_lib.graphs import JobShopGraph
|
7
|
+
|
8
|
+
|
9
|
+
def remove_completed_operations(
|
10
|
+
job_shop_graph: JobShopGraph,
|
11
|
+
completed_operations: Iterable[Operation],
|
12
|
+
) -> None:
|
13
|
+
"""Removes the operation node of the scheduled operation from the graph.
|
14
|
+
|
15
|
+
Args:
|
16
|
+
graph:
|
17
|
+
The job shop graph to update.
|
18
|
+
dispatcher:
|
19
|
+
The dispatcher instance.
|
20
|
+
"""
|
21
|
+
for operation in completed_operations:
|
22
|
+
node_id = operation.operation_id
|
23
|
+
if job_shop_graph.removed_nodes[node_id]:
|
24
|
+
continue
|
25
|
+
job_shop_graph.remove_node(node_id)
|
@@ -0,0 +1,41 @@
|
|
1
|
+
"""Package for reinforcement learning components."""
|
2
|
+
|
3
|
+
from job_shop_lib.reinforcement_learning._types_and_constants import (
|
4
|
+
ObservationSpaceKey,
|
5
|
+
RenderConfig,
|
6
|
+
ObservationDict,
|
7
|
+
GanttChartWrapperConfig,
|
8
|
+
GifConfig,
|
9
|
+
VideoConfig,
|
10
|
+
)
|
11
|
+
|
12
|
+
from job_shop_lib.reinforcement_learning._reward_observers import (
|
13
|
+
RewardObserver,
|
14
|
+
MakespanReward,
|
15
|
+
IdleTimeReward,
|
16
|
+
)
|
17
|
+
|
18
|
+
from job_shop_lib.reinforcement_learning._utils import add_padding
|
19
|
+
|
20
|
+
from job_shop_lib.reinforcement_learning._single_job_shop_graph_env import (
|
21
|
+
SingleJobShopGraphEnv,
|
22
|
+
)
|
23
|
+
from job_shop_lib.reinforcement_learning._multi_job_shop_graph_env import (
|
24
|
+
MultiJobShopGraphEnv,
|
25
|
+
)
|
26
|
+
|
27
|
+
|
28
|
+
__all__ = [
|
29
|
+
"ObservationSpaceKey",
|
30
|
+
"RewardObserver",
|
31
|
+
"MakespanReward",
|
32
|
+
"IdleTimeReward",
|
33
|
+
"GanttChartWrapperConfig",
|
34
|
+
"GifConfig",
|
35
|
+
"VideoConfig",
|
36
|
+
"SingleJobShopGraphEnv",
|
37
|
+
"RenderConfig",
|
38
|
+
"ObservationDict",
|
39
|
+
"add_padding",
|
40
|
+
"MultiJobShopGraphEnv",
|
41
|
+
]
|