job-shop-lib 0.5.1__py3-none-any.whl → 1.0.0__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 +19 -8
- job_shop_lib/{base_solver.py → _base_solver.py} +1 -1
- job_shop_lib/{job_shop_instance.py → _job_shop_instance.py} +155 -81
- job_shop_lib/_operation.py +118 -0
- job_shop_lib/{schedule.py → _schedule.py} +102 -84
- job_shop_lib/{scheduled_operation.py → _scheduled_operation.py} +25 -49
- 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} +77 -22
- job_shop_lib/dispatching/__init__.py +51 -42
- job_shop_lib/dispatching/{dispatcher.py → _dispatcher.py} +223 -130
- job_shop_lib/dispatching/_dispatcher_observer_config.py +67 -0
- job_shop_lib/dispatching/_factories.py +135 -0
- job_shop_lib/dispatching/{history_tracker.py → _history_observer.py} +6 -7
- job_shop_lib/dispatching/_optimal_operations_observer.py +113 -0
- job_shop_lib/dispatching/_ready_operation_filters.py +168 -0
- job_shop_lib/dispatching/_unscheduled_operations_observer.py +70 -0
- job_shop_lib/dispatching/feature_observers/__init__.py +51 -13
- job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py +212 -0
- job_shop_lib/dispatching/feature_observers/{duration_observer.py → _duration_observer.py} +20 -18
- job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py +289 -0
- job_shop_lib/dispatching/feature_observers/_factory.py +95 -0
- job_shop_lib/dispatching/feature_observers/_feature_observer.py +228 -0
- job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +97 -0
- job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +35 -0
- job_shop_lib/dispatching/feature_observers/{is_scheduled_observer.py → _is_scheduled_observer.py} +9 -5
- job_shop_lib/dispatching/feature_observers/{position_in_job_observer.py → _position_in_job_observer.py} +8 -10
- job_shop_lib/dispatching/feature_observers/{remaining_operations_observer.py → _remaining_operations_observer.py} +8 -26
- job_shop_lib/dispatching/rules/__init__.py +87 -0
- job_shop_lib/dispatching/rules/_dispatching_rule_factory.py +84 -0
- job_shop_lib/dispatching/rules/_dispatching_rule_solver.py +201 -0
- job_shop_lib/dispatching/{dispatching_rules.py → rules/_dispatching_rules_functions.py} +70 -16
- job_shop_lib/dispatching/rules/_machine_chooser_factory.py +71 -0
- job_shop_lib/dispatching/rules/_utils.py +128 -0
- job_shop_lib/exceptions.py +18 -0
- job_shop_lib/generation/__init__.py +10 -2
- job_shop_lib/generation/_general_instance_generator.py +165 -0
- job_shop_lib/generation/{instance_generator.py → _instance_generator.py} +37 -26
- job_shop_lib/{generators/transformations.py → generation/_transformations.py} +16 -12
- job_shop_lib/generation/_utils.py +124 -0
- job_shop_lib/graphs/__init__.py +30 -12
- job_shop_lib/graphs/{build_disjunctive_graph.py → _build_disjunctive_graph.py} +41 -3
- job_shop_lib/graphs/{build_agent_task_graph.py → _build_resource_task_graphs.py} +28 -26
- job_shop_lib/graphs/_constants.py +38 -0
- job_shop_lib/graphs/_job_shop_graph.py +320 -0
- job_shop_lib/graphs/_node.py +182 -0
- job_shop_lib/graphs/graph_updaters/__init__.py +26 -0
- job_shop_lib/graphs/graph_updaters/_disjunctive_graph_updater.py +108 -0
- job_shop_lib/graphs/graph_updaters/_graph_updater.py +57 -0
- job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +155 -0
- job_shop_lib/graphs/graph_updaters/_utils.py +25 -0
- job_shop_lib/py.typed +0 -0
- job_shop_lib/reinforcement_learning/__init__.py +68 -0
- job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +398 -0
- job_shop_lib/reinforcement_learning/_resource_task_graph_observation.py +329 -0
- job_shop_lib/reinforcement_learning/_reward_observers.py +87 -0
- job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +443 -0
- job_shop_lib/reinforcement_learning/_types_and_constants.py +62 -0
- job_shop_lib/reinforcement_learning/_utils.py +199 -0
- job_shop_lib/visualization/__init__.py +0 -25
- job_shop_lib/visualization/gantt/__init__.py +48 -0
- job_shop_lib/visualization/gantt/_gantt_chart_creator.py +257 -0
- job_shop_lib/visualization/gantt/_gantt_chart_video_and_gif_creation.py +422 -0
- job_shop_lib/visualization/{gantt_chart.py → gantt/_plot_gantt_chart.py} +84 -21
- job_shop_lib/visualization/graphs/__init__.py +29 -0
- job_shop_lib/visualization/graphs/_plot_disjunctive_graph.py +418 -0
- job_shop_lib/visualization/graphs/_plot_resource_task_graph.py +389 -0
- {job_shop_lib-0.5.1.dist-info → job_shop_lib-1.0.0.dist-info}/METADATA +87 -55
- job_shop_lib-1.0.0.dist-info/RECORD +73 -0
- {job_shop_lib-0.5.1.dist-info → job_shop_lib-1.0.0.dist-info}/WHEEL +1 -1
- job_shop_lib/benchmarking/load_benchmark.py +0 -142
- job_shop_lib/cp_sat/__init__.py +0 -5
- job_shop_lib/dispatching/dispatching_rule_solver.py +0 -119
- job_shop_lib/dispatching/factories.py +0 -206
- job_shop_lib/dispatching/feature_observers/composite_feature_observer.py +0 -87
- job_shop_lib/dispatching/feature_observers/earliest_start_time_observer.py +0 -156
- job_shop_lib/dispatching/feature_observers/factory.py +0 -58
- job_shop_lib/dispatching/feature_observers/feature_observer.py +0 -113
- 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/dispatching/pruning_functions.py +0 -116
- job_shop_lib/generation/general_instance_generator.py +0 -169
- job_shop_lib/generation/transformations.py +0 -164
- job_shop_lib/generators/__init__.py +0 -8
- job_shop_lib/generators/basic_generator.py +0 -200
- job_shop_lib/graphs/constants.py +0 -21
- job_shop_lib/graphs/job_shop_graph.py +0 -202
- job_shop_lib/graphs/node.py +0 -166
- job_shop_lib/operation.py +0 -122
- job_shop_lib/visualization/agent_task_graph.py +0 -257
- job_shop_lib/visualization/create_gif.py +0 -209
- job_shop_lib/visualization/disjunctive_graph.py +0 -210
- job_shop_lib-0.5.1.dist-info/RECORD +0 -52
- {job_shop_lib-0.5.1.dist-info → job_shop_lib-1.0.0.dist-info}/LICENSE +0 -0
@@ -0,0 +1,398 @@
|
|
1
|
+
"""Home of the `GraphEnvironment` class."""
|
2
|
+
|
3
|
+
from collections import defaultdict
|
4
|
+
from collections.abc import Callable, Sequence
|
5
|
+
from typing import Any, Tuple, Dict, List, Optional, Type
|
6
|
+
from copy import deepcopy
|
7
|
+
|
8
|
+
import gymnasium as gym
|
9
|
+
import numpy as np
|
10
|
+
|
11
|
+
from job_shop_lib import JobShopInstance, Operation
|
12
|
+
from job_shop_lib.dispatching import (
|
13
|
+
Dispatcher,
|
14
|
+
filter_dominated_operations,
|
15
|
+
DispatcherObserverConfig,
|
16
|
+
)
|
17
|
+
from job_shop_lib.dispatching.feature_observers import FeatureObserverConfig
|
18
|
+
from job_shop_lib.generation import InstanceGenerator
|
19
|
+
from job_shop_lib.graphs import JobShopGraph, build_resource_task_graph
|
20
|
+
from job_shop_lib.graphs.graph_updaters import (
|
21
|
+
GraphUpdater,
|
22
|
+
ResidualGraphUpdater,
|
23
|
+
)
|
24
|
+
from job_shop_lib.reinforcement_learning import (
|
25
|
+
SingleJobShopGraphEnv,
|
26
|
+
RewardObserver,
|
27
|
+
RenderConfig,
|
28
|
+
MakespanReward,
|
29
|
+
ObservationDict,
|
30
|
+
ObservationSpaceKey,
|
31
|
+
add_padding,
|
32
|
+
)
|
33
|
+
|
34
|
+
|
35
|
+
class MultiJobShopGraphEnv(gym.Env):
|
36
|
+
"""Gymnasium environment for solving multiple Job Shop Scheduling Problems
|
37
|
+
using reinforcement learning and Graph Neural Networks.
|
38
|
+
|
39
|
+
This environment generates a new Job Shop Scheduling Problem instance
|
40
|
+
for each reset, creates a graph representation, and manages the scheduling
|
41
|
+
process using a :class:`~job_shop_lib.dispatching.Dispatcher`.
|
42
|
+
|
43
|
+
The observation space includes:
|
44
|
+
|
45
|
+
- removed_nodes: Binary vector indicating removed nodes.
|
46
|
+
- edge_index: Edge list in COO format.
|
47
|
+
- operations: Matrix of operation features.
|
48
|
+
- jobs: Matrix of job features (if applicable).
|
49
|
+
- machines: Matrix of machine features (if applicable).
|
50
|
+
|
51
|
+
Internally, the class creates a
|
52
|
+
:class:`~job_shop_lib.reinforcement_learning.SingleJobShopGraphEnv`
|
53
|
+
environment to manage the scheduling process for each
|
54
|
+
:class:`~job_shop_lib.JobShopInstance`.
|
55
|
+
|
56
|
+
Attributes:
|
57
|
+
instance_generator:
|
58
|
+
A :class:`~job_shop_lib.generation.InstanceGenerator` that
|
59
|
+
generates a new problem instance on each reset.
|
60
|
+
|
61
|
+
action_space:
|
62
|
+
:class:`gymnasium.spaces.Discrete`) action space with size equal to
|
63
|
+
the maximum number of jobs.
|
64
|
+
|
65
|
+
observation_space:
|
66
|
+
Dictionary of observation spaces. Keys are defined in
|
67
|
+
:class:`~job_shop_lib.reinforcement_learning.ObservationSpaceKey`.
|
68
|
+
|
69
|
+
single_job_shop_graph_env:
|
70
|
+
Environment for a specific Job Shop Scheduling Problem instance.
|
71
|
+
See :class:`SingleJobShopGraphEnv`.
|
72
|
+
|
73
|
+
graph_initializer:
|
74
|
+
Function to create the initial graph representation. It should
|
75
|
+
take a :class:`~job_shop_lib.JobShopInstance` as input and return
|
76
|
+
a :class:`~job_shop_lib.graphs.JobShopGraph`.
|
77
|
+
|
78
|
+
render_mode:
|
79
|
+
Rendering mode for visualization. Supported modes are:
|
80
|
+
|
81
|
+
- human: Renders the current Gannt chart.
|
82
|
+
- save_video: Saves a video of the Gantt chart. Used only if the
|
83
|
+
schedule is completed.
|
84
|
+
- save_gif: Saves a GIF of the Gantt chart. Used only if the
|
85
|
+
schedule is completed.
|
86
|
+
|
87
|
+
render_config:
|
88
|
+
Configuration for rendering. See
|
89
|
+
:class:`~job_shop_lib.RenderConfig`.
|
90
|
+
|
91
|
+
feature_observer_configs:
|
92
|
+
List of :class:`~job_shop_lib.dispatching.DispatcherObserverConfig`
|
93
|
+
for feature observers.
|
94
|
+
reward_function_config:
|
95
|
+
Configuration for the reward function. See
|
96
|
+
:class:`~job_shop_lib.dispatching.DispatcherObserverConfig` and
|
97
|
+
:class:`~job_shop_lib.dispatching.RewardObserver`.
|
98
|
+
|
99
|
+
graph_updater_config:
|
100
|
+
Configuration for the graph updater. The graph updater is used to
|
101
|
+
update the graph representation after each action. See
|
102
|
+
:class:`~job_shop_lib.dispatching.DispatcherObserverConfig` and
|
103
|
+
:class:`~job_shop_lib.graphs.GraphUpdater`.
|
104
|
+
Args:
|
105
|
+
instance_generator:
|
106
|
+
A :class:`~job_shop_lib.generation.InstanceGenerator` that
|
107
|
+
generates a new problem instance on each reset.
|
108
|
+
|
109
|
+
feature_observer_configs:
|
110
|
+
Configurations for feature observers. Each configuration
|
111
|
+
should be a
|
112
|
+
:class:`~job_shop_lib.dispatching.DispatcherObserverConfig`
|
113
|
+
with a class type that inherits from
|
114
|
+
:class:`~job_shop_lib.dispatching.FeatureObserver` or a string
|
115
|
+
or enum that represents a built-in feature observer.
|
116
|
+
|
117
|
+
graph_initializer:
|
118
|
+
Function to create the initial graph representation.
|
119
|
+
If ``None``, the default graph initializer is used:
|
120
|
+
:func:`~job_shop_lib.graphs.build_resource_task_graph`.
|
121
|
+
graph_updater_config:
|
122
|
+
Configuration for the graph updater. The graph updater is used
|
123
|
+
to update the graph representation after each action. If
|
124
|
+
``None``, the default graph updater is used:
|
125
|
+
:class:`~job_shop_lib.graphs.ResidualGraphUpdater`.
|
126
|
+
|
127
|
+
ready_operations_filter:
|
128
|
+
Function to filter ready operations. If ``None``, the default
|
129
|
+
filter is used:
|
130
|
+
:func:`~job_shop_lib.dispatching.filter_dominated_operations`.
|
131
|
+
|
132
|
+
reward_function_config:
|
133
|
+
Configuration for the reward function. If ``None``, the default
|
134
|
+
reward function is used:
|
135
|
+
:class:`~job_shop_lib.dispatching.MakespanReward`.
|
136
|
+
|
137
|
+
render_mode:
|
138
|
+
Rendering mode for visualization. Supported modes are:
|
139
|
+
|
140
|
+
- human: Renders the current Gannt chart.
|
141
|
+
- save_video: Saves a video of the Gantt chart. Used only if
|
142
|
+
the schedule is completed.
|
143
|
+
- save_gif: Saves a GIF of the Gantt chart. Used only if the
|
144
|
+
schedule is completed.
|
145
|
+
render_config:
|
146
|
+
Configuration for rendering. See
|
147
|
+
:class:`~job_shop_lib.RenderConfig`.
|
148
|
+
|
149
|
+
use_padding:
|
150
|
+
Whether to use padding in observations. If True, all matrices
|
151
|
+
are padded to fixed sizes based on the maximum instance size.
|
152
|
+
Values are padded with -1, except for the "removed_nodes" key,
|
153
|
+
which is padded with ``True``, indicating that the node is
|
154
|
+
removed.
|
155
|
+
"""
|
156
|
+
|
157
|
+
def __init__(
|
158
|
+
self,
|
159
|
+
instance_generator: InstanceGenerator,
|
160
|
+
feature_observer_configs: Sequence[FeatureObserverConfig],
|
161
|
+
graph_initializer: Callable[
|
162
|
+
[JobShopInstance], JobShopGraph
|
163
|
+
] = build_resource_task_graph,
|
164
|
+
graph_updater_config: DispatcherObserverConfig[
|
165
|
+
Type[GraphUpdater]
|
166
|
+
] = DispatcherObserverConfig(class_type=ResidualGraphUpdater),
|
167
|
+
ready_operations_filter: Callable[
|
168
|
+
[Dispatcher, List[Operation]], List[Operation]
|
169
|
+
] = filter_dominated_operations,
|
170
|
+
reward_function_config: DispatcherObserverConfig[
|
171
|
+
Type[RewardObserver]
|
172
|
+
] = DispatcherObserverConfig(class_type=MakespanReward),
|
173
|
+
render_mode: Optional[str] = None,
|
174
|
+
render_config: Optional[RenderConfig] = None,
|
175
|
+
use_padding: bool = True,
|
176
|
+
) -> None:
|
177
|
+
super().__init__()
|
178
|
+
|
179
|
+
# Create an instance with the maximum size
|
180
|
+
instance_with_max_size = instance_generator.generate(
|
181
|
+
num_jobs=instance_generator.max_num_jobs,
|
182
|
+
num_machines=instance_generator.max_num_machines,
|
183
|
+
)
|
184
|
+
graph = graph_initializer(instance_with_max_size)
|
185
|
+
|
186
|
+
self.single_job_shop_graph_env = SingleJobShopGraphEnv(
|
187
|
+
job_shop_graph=graph,
|
188
|
+
feature_observer_configs=feature_observer_configs,
|
189
|
+
reward_function_config=reward_function_config,
|
190
|
+
graph_updater_config=graph_updater_config,
|
191
|
+
ready_operations_filter=ready_operations_filter,
|
192
|
+
render_mode=render_mode,
|
193
|
+
render_config=render_config,
|
194
|
+
use_padding=use_padding,
|
195
|
+
)
|
196
|
+
self.instance_generator = instance_generator
|
197
|
+
self.graph_initializer = graph_initializer
|
198
|
+
self.render_mode = render_mode
|
199
|
+
self.render_config = render_config
|
200
|
+
self.feature_observer_configs = feature_observer_configs
|
201
|
+
self.reward_function_config = reward_function_config
|
202
|
+
self.graph_updater_config = graph_updater_config
|
203
|
+
|
204
|
+
self.action_space = deepcopy(
|
205
|
+
self.single_job_shop_graph_env.action_space
|
206
|
+
)
|
207
|
+
self.observation_space: gym.spaces.Dict = deepcopy(
|
208
|
+
self.single_job_shop_graph_env.observation_space
|
209
|
+
)
|
210
|
+
|
211
|
+
@property
|
212
|
+
def dispatcher(self) -> Dispatcher:
|
213
|
+
"""Returns the current dispatcher instance."""
|
214
|
+
return self.single_job_shop_graph_env.dispatcher
|
215
|
+
|
216
|
+
@property
|
217
|
+
def reward_function(self) -> RewardObserver:
|
218
|
+
"""Returns the current reward function instance."""
|
219
|
+
return self.single_job_shop_graph_env.reward_function
|
220
|
+
|
221
|
+
@reward_function.setter
|
222
|
+
def reward_function(self, reward_function: RewardObserver) -> None:
|
223
|
+
"""Sets the reward function instance."""
|
224
|
+
self.single_job_shop_graph_env.reward_function = reward_function
|
225
|
+
|
226
|
+
@property
|
227
|
+
def ready_operations_filter(
|
228
|
+
self,
|
229
|
+
) -> Optional[Callable[[Dispatcher, List[Operation]], List[Operation]]]:
|
230
|
+
"""Returns the current ready operations filter."""
|
231
|
+
return (
|
232
|
+
self.single_job_shop_graph_env.dispatcher.ready_operations_filter
|
233
|
+
)
|
234
|
+
|
235
|
+
@ready_operations_filter.setter
|
236
|
+
def ready_operations_filter(
|
237
|
+
self,
|
238
|
+
ready_operations_filter: Callable[
|
239
|
+
[Dispatcher, List[Operation]], List[Operation]
|
240
|
+
],
|
241
|
+
) -> None:
|
242
|
+
"""Sets the ready operations filter."""
|
243
|
+
self.single_job_shop_graph_env.dispatcher.ready_operations_filter = (
|
244
|
+
ready_operations_filter
|
245
|
+
)
|
246
|
+
|
247
|
+
@property
|
248
|
+
def use_padding(self) -> bool:
|
249
|
+
"""Returns whether the padding is used."""
|
250
|
+
return self.single_job_shop_graph_env.use_padding
|
251
|
+
|
252
|
+
@use_padding.setter
|
253
|
+
def use_padding(self, use_padding: bool) -> None:
|
254
|
+
"""Sets whether the padding is used."""
|
255
|
+
self.single_job_shop_graph_env.use_padding = use_padding
|
256
|
+
|
257
|
+
@property
|
258
|
+
def job_shop_graph(self) -> JobShopGraph:
|
259
|
+
"""Returns the current job shop graph."""
|
260
|
+
return self.single_job_shop_graph_env.job_shop_graph
|
261
|
+
|
262
|
+
@property
|
263
|
+
def instance(self) -> JobShopInstance:
|
264
|
+
"""Returns the current job shop instance."""
|
265
|
+
return self.single_job_shop_graph_env.instance
|
266
|
+
|
267
|
+
def reset(
|
268
|
+
self,
|
269
|
+
*,
|
270
|
+
seed: Optional[int] = None,
|
271
|
+
options: Dict[str, Any] | None = None,
|
272
|
+
) -> Tuple[ObservationDict, Dict[str, Any]]:
|
273
|
+
"""Resets the environment and returns the initial observation.
|
274
|
+
|
275
|
+
Args:
|
276
|
+
seed: Random seed for reproducibility.
|
277
|
+
options: Additional options for reset (currently unused).
|
278
|
+
|
279
|
+
Returns:
|
280
|
+
A tuple containing:
|
281
|
+
- ObservationDict: The initial observation of the environment.
|
282
|
+
- dict: An info dictionary containing additional information about
|
283
|
+
the reset state. This may include details about the generated
|
284
|
+
instance or initial graph structure.
|
285
|
+
"""
|
286
|
+
instance = self.instance_generator.generate()
|
287
|
+
graph = self.graph_initializer(instance)
|
288
|
+
self.single_job_shop_graph_env = SingleJobShopGraphEnv(
|
289
|
+
job_shop_graph=graph,
|
290
|
+
feature_observer_configs=self.feature_observer_configs,
|
291
|
+
reward_function_config=self.reward_function_config,
|
292
|
+
ready_operations_filter=self.ready_operations_filter,
|
293
|
+
render_mode=self.render_mode,
|
294
|
+
render_config=self.render_config,
|
295
|
+
use_padding=self.single_job_shop_graph_env.use_padding,
|
296
|
+
)
|
297
|
+
obs, info = self.single_job_shop_graph_env.reset(
|
298
|
+
seed=seed, options=options
|
299
|
+
)
|
300
|
+
if self.use_padding:
|
301
|
+
obs = self._add_padding_to_observation(obs)
|
302
|
+
|
303
|
+
return obs, info
|
304
|
+
|
305
|
+
def step(
|
306
|
+
self, action: Tuple[int, int]
|
307
|
+
) -> Tuple[ObservationDict, float, bool, bool, Dict[str, Any]]:
|
308
|
+
"""Takes a step in the environment.
|
309
|
+
|
310
|
+
Args:
|
311
|
+
action:
|
312
|
+
The action to take. The action is a tuple of two integers
|
313
|
+
(job_id, machine_id):
|
314
|
+
the job ID and the machine ID in which to schedule the
|
315
|
+
operation.
|
316
|
+
|
317
|
+
Returns:
|
318
|
+
A tuple containing the following elements:
|
319
|
+
|
320
|
+
- The observation of the environment.
|
321
|
+
- The reward obtained.
|
322
|
+
- Whether the environment is done.
|
323
|
+
- Whether the episode was truncated (always False).
|
324
|
+
- A dictionary with additional information. The dictionary
|
325
|
+
contains the following keys: "feature_names", the names of the
|
326
|
+
features in the observation; and "available_operations_with_ids",
|
327
|
+
a list of available actions in the form of (operation_id,
|
328
|
+
machine_id, job_id).
|
329
|
+
"""
|
330
|
+
obs, reward, done, truncated, info = (
|
331
|
+
self.single_job_shop_graph_env.step(action)
|
332
|
+
)
|
333
|
+
if self.use_padding:
|
334
|
+
obs = self._add_padding_to_observation(obs)
|
335
|
+
|
336
|
+
return obs, reward, done, truncated, info
|
337
|
+
|
338
|
+
def _add_padding_to_observation(
|
339
|
+
self, observation: ObservationDict
|
340
|
+
) -> ObservationDict:
|
341
|
+
"""Adds padding to the observation.
|
342
|
+
|
343
|
+
"removed_nodes":
|
344
|
+
input_shape: (num_nodes,)
|
345
|
+
output_shape: (max_num_nodes,) (padded with True)
|
346
|
+
"edge_index":
|
347
|
+
input_shape: (2, num_edges)
|
348
|
+
output_shape: (2, max_num_edges) (padded with -1)
|
349
|
+
"operations":
|
350
|
+
input_shape: (num_operations, num_features)
|
351
|
+
output_shape: (max_num_operations, num_features) (padded with -1)
|
352
|
+
"jobs":
|
353
|
+
input_shape: (num_jobs, num_features)
|
354
|
+
output_shape: (max_num_jobs, num_features) (padded with -1)
|
355
|
+
"machines":
|
356
|
+
input_shape: (num_machines, num_features)
|
357
|
+
output_shape: (max_num_machines, num_features) (padded with -1)
|
358
|
+
"""
|
359
|
+
padding_value: Dict[str, float | bool] = defaultdict(lambda: -1)
|
360
|
+
padding_value[ObservationSpaceKey.REMOVED_NODES.value] = True
|
361
|
+
for key, value in observation.items():
|
362
|
+
if not isinstance(value, np.ndarray): # Make mypy happy
|
363
|
+
continue
|
364
|
+
expected_shape = self._get_output_shape(key)
|
365
|
+
observation[key] = add_padding( # type: ignore[literal-required]
|
366
|
+
value,
|
367
|
+
expected_shape,
|
368
|
+
padding_value=padding_value[key],
|
369
|
+
)
|
370
|
+
return observation
|
371
|
+
|
372
|
+
def _get_output_shape(self, key: str) -> Tuple[int, ...]:
|
373
|
+
"""Returns the output shape of the observation space key."""
|
374
|
+
output_shape = self.observation_space[key].shape
|
375
|
+
assert output_shape is not None # Make mypy happy
|
376
|
+
return output_shape
|
377
|
+
|
378
|
+
def render(self) -> None:
|
379
|
+
self.single_job_shop_graph_env.render()
|
380
|
+
|
381
|
+
def get_available_actions_with_ids(self) -> List[Tuple[int, int, int]]:
|
382
|
+
"""Returns a list of available actions in the form of
|
383
|
+
(operation_id, machine_id, job_id)."""
|
384
|
+
return self.single_job_shop_graph_env.get_available_actions_with_ids()
|
385
|
+
|
386
|
+
def validate_action(self, action: Tuple[int, int]) -> None:
|
387
|
+
"""Validates the action.
|
388
|
+
|
389
|
+
Args:
|
390
|
+
action:
|
391
|
+
The action to validate. The action is a tuple of two integers
|
392
|
+
(job_id, machine_id): the job ID and the machine ID in which
|
393
|
+
to schedule the operation.
|
394
|
+
|
395
|
+
Raises:
|
396
|
+
ValidationError: If the action is invalid.
|
397
|
+
"""
|
398
|
+
self.single_job_shop_graph_env.validate_action(action)
|