job-shop-lib 1.0.0a5__py3-none-any.whl → 1.0.0b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- job_shop_lib/__init__.py +1 -1
- job_shop_lib/_job_shop_instance.py +34 -29
- job_shop_lib/_operation.py +4 -2
- job_shop_lib/_schedule.py +11 -11
- job_shop_lib/benchmarking/_load_benchmark.py +3 -3
- job_shop_lib/constraint_programming/_ortools_solver.py +6 -6
- job_shop_lib/dispatching/_dispatcher.py +19 -19
- job_shop_lib/dispatching/_dispatcher_observer_config.py +4 -4
- job_shop_lib/dispatching/_factories.py +4 -2
- job_shop_lib/dispatching/_history_observer.py +2 -1
- job_shop_lib/dispatching/_ready_operation_filters.py +19 -18
- job_shop_lib/dispatching/_unscheduled_operations_observer.py +4 -3
- job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py +7 -8
- job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py +3 -1
- job_shop_lib/dispatching/feature_observers/_factory.py +13 -14
- job_shop_lib/dispatching/feature_observers/_feature_observer.py +9 -8
- job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +2 -1
- job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +4 -2
- job_shop_lib/dispatching/rules/_dispatching_rule_factory.py +4 -2
- job_shop_lib/dispatching/rules/_dispatching_rule_solver.py +23 -15
- job_shop_lib/dispatching/rules/_dispatching_rules_functions.py +9 -8
- job_shop_lib/dispatching/rules/_machine_chooser_factory.py +4 -3
- job_shop_lib/dispatching/rules/_utils.py +9 -8
- job_shop_lib/generation/__init__.py +8 -0
- job_shop_lib/generation/_general_instance_generator.py +42 -64
- job_shop_lib/generation/_instance_generator.py +11 -7
- job_shop_lib/generation/_transformations.py +5 -4
- job_shop_lib/generation/_utils.py +124 -0
- job_shop_lib/graphs/__init__.py +7 -7
- job_shop_lib/graphs/{_build_agent_task_graph.py → _build_resource_task_graphs.py} +26 -24
- job_shop_lib/graphs/_job_shop_graph.py +17 -13
- job_shop_lib/graphs/_node.py +6 -4
- job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +4 -2
- job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +40 -20
- job_shop_lib/reinforcement_learning/_reward_observers.py +3 -1
- job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +89 -22
- job_shop_lib/reinforcement_learning/_types_and_constants.py +1 -1
- job_shop_lib/reinforcement_learning/_utils.py +3 -3
- job_shop_lib/visualization/__init__.py +0 -60
- job_shop_lib/visualization/gantt/__init__.py +48 -0
- job_shop_lib/visualization/{_gantt_chart_creator.py → gantt/_gantt_chart_creator.py} +12 -12
- job_shop_lib/visualization/{_gantt_chart_video_and_gif_creation.py → gantt/_gantt_chart_video_and_gif_creation.py} +22 -22
- job_shop_lib/visualization/{_plot_gantt_chart.py → gantt/_plot_gantt_chart.py} +12 -13
- job_shop_lib/visualization/graphs/__init__.py +29 -0
- job_shop_lib/visualization/{_plot_disjunctive_graph.py → graphs/_plot_disjunctive_graph.py} +18 -16
- job_shop_lib/visualization/graphs/_plot_resource_task_graph.py +389 -0
- {job_shop_lib-1.0.0a5.dist-info → job_shop_lib-1.0.0b1.dist-info}/METADATA +17 -15
- job_shop_lib-1.0.0b1.dist-info/RECORD +69 -0
- job_shop_lib/visualization/_plot_agent_task_graph.py +0 -276
- job_shop_lib-1.0.0a5.dist-info/RECORD +0 -66
- {job_shop_lib-1.0.0a5.dist-info → job_shop_lib-1.0.0b1.dist-info}/LICENSE +0 -0
- {job_shop_lib-1.0.0a5.dist-info → job_shop_lib-1.0.0b1.dist-info}/WHEEL +0 -0
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
from collections import defaultdict
|
4
4
|
from collections.abc import Callable, Sequence
|
5
|
-
from typing import Any
|
5
|
+
from typing import Any, Tuple, Dict, List, Optional, Type
|
6
6
|
from copy import deepcopy
|
7
7
|
|
8
8
|
import gymnasium as gym
|
@@ -16,7 +16,7 @@ from job_shop_lib.dispatching import (
|
|
16
16
|
)
|
17
17
|
from job_shop_lib.dispatching.feature_observers import FeatureObserverConfig
|
18
18
|
from job_shop_lib.generation import InstanceGenerator
|
19
|
-
from job_shop_lib.graphs import JobShopGraph,
|
19
|
+
from job_shop_lib.graphs import JobShopGraph, build_resource_task_graph
|
20
20
|
from job_shop_lib.graphs.graph_updaters import (
|
21
21
|
GraphUpdater,
|
22
22
|
ResidualGraphUpdater,
|
@@ -160,18 +160,18 @@ class MultiJobShopGraphEnv(gym.Env):
|
|
160
160
|
feature_observer_configs: Sequence[FeatureObserverConfig],
|
161
161
|
graph_initializer: Callable[
|
162
162
|
[JobShopInstance], JobShopGraph
|
163
|
-
] =
|
163
|
+
] = build_resource_task_graph,
|
164
164
|
graph_updater_config: DispatcherObserverConfig[
|
165
|
-
|
165
|
+
Type[GraphUpdater]
|
166
166
|
] = DispatcherObserverConfig(class_type=ResidualGraphUpdater),
|
167
167
|
ready_operations_filter: Callable[
|
168
|
-
[Dispatcher,
|
168
|
+
[Dispatcher, List[Operation]], List[Operation]
|
169
169
|
] = filter_dominated_operations,
|
170
170
|
reward_function_config: DispatcherObserverConfig[
|
171
|
-
|
171
|
+
Type[RewardObserver]
|
172
172
|
] = DispatcherObserverConfig(class_type=MakespanReward),
|
173
|
-
render_mode: str
|
174
|
-
render_config: RenderConfig
|
173
|
+
render_mode: Optional[str] = None,
|
174
|
+
render_config: Optional[RenderConfig] = None,
|
175
175
|
use_padding: bool = True,
|
176
176
|
) -> None:
|
177
177
|
super().__init__()
|
@@ -226,7 +226,7 @@ class MultiJobShopGraphEnv(gym.Env):
|
|
226
226
|
@property
|
227
227
|
def ready_operations_filter(
|
228
228
|
self,
|
229
|
-
) -> Callable[[Dispatcher,
|
229
|
+
) -> Optional[Callable[[Dispatcher, List[Operation]], List[Operation]]]:
|
230
230
|
"""Returns the current ready operations filter."""
|
231
231
|
return (
|
232
232
|
self.single_job_shop_graph_env.dispatcher.ready_operations_filter
|
@@ -236,7 +236,7 @@ class MultiJobShopGraphEnv(gym.Env):
|
|
236
236
|
def ready_operations_filter(
|
237
237
|
self,
|
238
238
|
pruning_function: Callable[
|
239
|
-
[Dispatcher,
|
239
|
+
[Dispatcher, List[Operation]], List[Operation]
|
240
240
|
],
|
241
241
|
) -> None:
|
242
242
|
"""Sets the ready operations filter."""
|
@@ -267,9 +267,9 @@ class MultiJobShopGraphEnv(gym.Env):
|
|
267
267
|
def reset(
|
268
268
|
self,
|
269
269
|
*,
|
270
|
-
seed: int
|
271
|
-
options:
|
272
|
-
) ->
|
270
|
+
seed: Optional[int] = None,
|
271
|
+
options: Dict[str, Any] | None = None,
|
272
|
+
) -> Tuple[ObservationDict, Dict[str, Any]]:
|
273
273
|
"""Resets the environment and returns the initial observation.
|
274
274
|
|
275
275
|
Args:
|
@@ -303,8 +303,8 @@ class MultiJobShopGraphEnv(gym.Env):
|
|
303
303
|
return obs, info
|
304
304
|
|
305
305
|
def step(
|
306
|
-
self, action:
|
307
|
-
) ->
|
306
|
+
self, action: Tuple[int, int]
|
307
|
+
) -> Tuple[ObservationDict, float, bool, bool, Dict[str, Any]]:
|
308
308
|
"""Takes a step in the environment.
|
309
309
|
|
310
310
|
Args:
|
@@ -322,9 +322,10 @@ class MultiJobShopGraphEnv(gym.Env):
|
|
322
322
|
- Whether the environment is done.
|
323
323
|
- Whether the episode was truncated (always False).
|
324
324
|
- A dictionary with additional information. The dictionary
|
325
|
-
contains the following keys:
|
326
|
-
|
327
|
-
|
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).
|
328
329
|
"""
|
329
330
|
obs, reward, done, truncated, info = (
|
330
331
|
self.single_job_shop_graph_env.step(action)
|
@@ -355,7 +356,7 @@ class MultiJobShopGraphEnv(gym.Env):
|
|
355
356
|
input_shape: (num_machines, num_features)
|
356
357
|
output_shape: (max_num_machines, num_features) (padded with -1)
|
357
358
|
"""
|
358
|
-
padding_value:
|
359
|
+
padding_value: Dict[str, float | bool] = defaultdict(lambda: -1)
|
359
360
|
padding_value[ObservationSpaceKey.REMOVED_NODES.value] = True
|
360
361
|
for key, value in observation.items():
|
361
362
|
if not isinstance(value, np.ndarray): # Make mypy happy
|
@@ -368,7 +369,7 @@ class MultiJobShopGraphEnv(gym.Env):
|
|
368
369
|
)
|
369
370
|
return observation
|
370
371
|
|
371
|
-
def _get_output_shape(self, key: str) ->
|
372
|
+
def _get_output_shape(self, key: str) -> Tuple[int, ...]:
|
372
373
|
"""Returns the output shape of the observation space key."""
|
373
374
|
output_shape = self.observation_space[key].shape
|
374
375
|
assert output_shape is not None # Make mypy happy
|
@@ -376,3 +377,22 @@ class MultiJobShopGraphEnv(gym.Env):
|
|
376
377
|
|
377
378
|
def render(self) -> None:
|
378
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)
|
@@ -1,6 +1,8 @@
|
|
1
1
|
"""Rewards functions are defined as `DispatcherObervers` and are used to
|
2
2
|
calculate the reward for a given state."""
|
3
3
|
|
4
|
+
from typing import List
|
5
|
+
|
4
6
|
from job_shop_lib.dispatching import DispatcherObserver, Dispatcher
|
5
7
|
from job_shop_lib import ScheduledOperation
|
6
8
|
|
@@ -18,7 +20,7 @@ class RewardObserver(DispatcherObserver):
|
|
18
20
|
self, dispatcher: Dispatcher, *, subscribe: bool = True
|
19
21
|
) -> None:
|
20
22
|
super().__init__(dispatcher, subscribe=subscribe)
|
21
|
-
self.rewards:
|
23
|
+
self.rewards: List[float] = []
|
22
24
|
|
23
25
|
@property
|
24
26
|
def last_reward(self) -> float:
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
from copy import deepcopy
|
4
4
|
from collections.abc import Callable, Sequence
|
5
|
-
from typing import Any
|
5
|
+
from typing import Any, Dict, Tuple, List, Optional, Type
|
6
6
|
|
7
7
|
import matplotlib.pyplot as plt
|
8
8
|
import gymnasium as gym
|
@@ -15,6 +15,7 @@ from job_shop_lib.graphs.graph_updaters import (
|
|
15
15
|
GraphUpdater,
|
16
16
|
ResidualGraphUpdater,
|
17
17
|
)
|
18
|
+
from job_shop_lib.exceptions import ValidationError
|
18
19
|
from job_shop_lib.dispatching import (
|
19
20
|
Dispatcher,
|
20
21
|
filter_dominated_operations,
|
@@ -24,7 +25,7 @@ from job_shop_lib.dispatching.feature_observers import (
|
|
24
25
|
FeatureObserverConfig,
|
25
26
|
CompositeFeatureObserver,
|
26
27
|
)
|
27
|
-
from job_shop_lib.visualization import GanttChartCreator
|
28
|
+
from job_shop_lib.visualization.gantt import GanttChartCreator
|
28
29
|
from job_shop_lib.reinforcement_learning import (
|
29
30
|
RewardObserver,
|
30
31
|
MakespanReward,
|
@@ -138,16 +139,16 @@ class SingleJobShopGraphEnv(gym.Env):
|
|
138
139
|
job_shop_graph: JobShopGraph,
|
139
140
|
feature_observer_configs: Sequence[FeatureObserverConfig],
|
140
141
|
reward_function_config: DispatcherObserverConfig[
|
141
|
-
|
142
|
+
Type[RewardObserver]
|
142
143
|
] = DispatcherObserverConfig(class_type=MakespanReward),
|
143
144
|
graph_updater_config: DispatcherObserverConfig[
|
144
|
-
|
145
|
+
Type[GraphUpdater]
|
145
146
|
] = DispatcherObserverConfig(class_type=ResidualGraphUpdater),
|
146
|
-
ready_operations_filter:
|
147
|
-
Callable[[Dispatcher,
|
148
|
-
|
149
|
-
render_mode: str
|
150
|
-
render_config: RenderConfig
|
147
|
+
ready_operations_filter: Optional[
|
148
|
+
Callable[[Dispatcher, List[Operation]], List[Operation]]
|
149
|
+
] = filter_dominated_operations,
|
150
|
+
render_mode: Optional[str] = None,
|
151
|
+
render_config: Optional[RenderConfig] = None,
|
151
152
|
use_padding: bool = True,
|
152
153
|
) -> None:
|
153
154
|
super().__init__()
|
@@ -195,10 +196,26 @@ class SingleJobShopGraphEnv(gym.Env):
|
|
195
196
|
"""Returns the job shop graph."""
|
196
197
|
return self.graph_updater.job_shop_graph
|
197
198
|
|
199
|
+
def current_makespan(self) -> int:
|
200
|
+
"""Returns current makespan of partial schedule."""
|
201
|
+
return self.dispatcher.schedule.makespan()
|
202
|
+
|
203
|
+
def machine_utilization(self) -> NDArray[np.float32]:
|
204
|
+
"""Returns utilization percentage for each machine."""
|
205
|
+
total_time = max(1, self.current_makespan()) # Avoid division by zero
|
206
|
+
machine_busy_time = np.zeros(self.instance.num_machines)
|
207
|
+
|
208
|
+
for m_id, m_schedule in enumerate(self.dispatcher.schedule.schedule):
|
209
|
+
machine_busy_time[m_id] = sum(
|
210
|
+
op.operation.duration for op in m_schedule
|
211
|
+
)
|
212
|
+
|
213
|
+
return machine_busy_time / total_time
|
214
|
+
|
198
215
|
def _get_observation_space(self) -> gym.spaces.Dict:
|
199
216
|
"""Returns the observation space dictionary."""
|
200
217
|
num_edges = self.job_shop_graph.num_edges
|
201
|
-
dict_space:
|
218
|
+
dict_space: Dict[str, gym.Space] = {
|
202
219
|
ObservationSpaceKey.REMOVED_NODES.value: gym.spaces.MultiBinary(
|
203
220
|
len(self.job_shop_graph.nodes)
|
204
221
|
),
|
@@ -224,18 +241,23 @@ class SingleJobShopGraphEnv(gym.Env):
|
|
224
241
|
def reset(
|
225
242
|
self,
|
226
243
|
*,
|
227
|
-
seed: int
|
228
|
-
options:
|
229
|
-
) ->
|
244
|
+
seed: Optional[int] = None,
|
245
|
+
options: Optional[Dict[str, Any]] = None,
|
246
|
+
) -> Tuple[ObservationDict, dict]:
|
230
247
|
"""Resets the environment."""
|
231
248
|
super().reset(seed=seed, options=options)
|
232
249
|
self.dispatcher.reset()
|
233
250
|
obs = self.get_observation()
|
234
|
-
return obs, {
|
251
|
+
return obs, {
|
252
|
+
"feature_names": self.composite_observer.column_names,
|
253
|
+
"available_operations_with_ids": (
|
254
|
+
self.get_available_actions_with_ids()
|
255
|
+
),
|
256
|
+
}
|
235
257
|
|
236
258
|
def step(
|
237
|
-
self, action:
|
238
|
-
) ->
|
259
|
+
self, action: Tuple[int, int]
|
260
|
+
) -> Tuple[ObservationDict, float, bool, bool, Dict[str, Any]]:
|
239
261
|
"""Takes a step in the environment.
|
240
262
|
|
241
263
|
Args:
|
@@ -254,9 +276,9 @@ class SingleJobShopGraphEnv(gym.Env):
|
|
254
276
|
- Whether the episode was truncated (always False).
|
255
277
|
- A dictionary with additional information. The dictionary
|
256
278
|
contains the following keys: "feature_names", the names of the
|
257
|
-
features in the observation; "
|
258
|
-
|
259
|
-
|
279
|
+
features in the observation; and "available_operations_with_ids",
|
280
|
+
a list of available actions in the form of (operation_id,
|
281
|
+
machine_id, job_id).
|
260
282
|
"""
|
261
283
|
job_id, machine_id = action
|
262
284
|
operation = self.dispatcher.next_operation(job_id)
|
@@ -269,9 +291,11 @@ class SingleJobShopGraphEnv(gym.Env):
|
|
269
291
|
reward = self.reward_function.last_reward
|
270
292
|
done = self.dispatcher.schedule.is_complete()
|
271
293
|
truncated = False
|
272
|
-
info:
|
294
|
+
info: Dict[str, Any] = {
|
273
295
|
"feature_names": self.composite_observer.column_names,
|
274
|
-
"
|
296
|
+
"available_operations_with_ids": (
|
297
|
+
self.get_available_actions_with_ids()
|
298
|
+
),
|
275
299
|
}
|
276
300
|
return obs, reward, done, truncated, info
|
277
301
|
|
@@ -322,6 +346,49 @@ class SingleJobShopGraphEnv(gym.Env):
|
|
322
346
|
elif self.render_mode == "save_gif":
|
323
347
|
self.gantt_chart_creator.create_gif()
|
324
348
|
|
349
|
+
def get_available_actions_with_ids(self) -> List[Tuple[int, int, int]]:
|
350
|
+
"""Returns a list of available actions in the form of
|
351
|
+
(operation_id, machine_id, job_id)."""
|
352
|
+
available_operations = self.dispatcher.available_operations()
|
353
|
+
available_operations_with_ids = []
|
354
|
+
for operation in available_operations:
|
355
|
+
job_id = operation.job_id
|
356
|
+
operation_id = operation.operation_id
|
357
|
+
for machine_id in operation.machines:
|
358
|
+
available_operations_with_ids.append(
|
359
|
+
(operation_id, machine_id, job_id)
|
360
|
+
)
|
361
|
+
return available_operations_with_ids
|
362
|
+
|
363
|
+
def validate_action(self, action: Tuple[int, int]) -> None:
|
364
|
+
"""Validates that the action is legal in the current state.
|
365
|
+
|
366
|
+
Args:
|
367
|
+
action:
|
368
|
+
The action to validate. The action is a tuple of two integers
|
369
|
+
(job_id, machine_id).
|
370
|
+
|
371
|
+
Raises:
|
372
|
+
ValidationError: If the action is invalid.
|
373
|
+
"""
|
374
|
+
job_id, machine_id = action
|
375
|
+
if not 0 <= job_id < self.instance.num_jobs:
|
376
|
+
raise ValidationError(f"Invalid job_id {job_id}")
|
377
|
+
|
378
|
+
if not -1 <= machine_id < self.instance.num_machines:
|
379
|
+
raise ValidationError(f"Invalid machine_id {machine_id}")
|
380
|
+
|
381
|
+
# Check if job has operations left
|
382
|
+
job = self.instance.jobs[job_id]
|
383
|
+
if self.dispatcher.job_next_operation_index[job_id] >= len(job):
|
384
|
+
raise ValidationError(f"Job {job_id} has no operations left")
|
385
|
+
|
386
|
+
next_operation = self.dispatcher.next_operation(job_id)
|
387
|
+
if machine_id == -1 and len(next_operation.machines) > 1:
|
388
|
+
raise ValidationError(
|
389
|
+
f"Operation {next_operation} requires a machine_id"
|
390
|
+
)
|
391
|
+
|
325
392
|
|
326
393
|
if __name__ == "__main__":
|
327
394
|
from job_shop_lib.dispatching.feature_observers import (
|
@@ -333,7 +400,7 @@ if __name__ == "__main__":
|
|
333
400
|
|
334
401
|
instance = load_benchmark_instance("ft06")
|
335
402
|
job_shop_graph_ = build_disjunctive_graph(instance)
|
336
|
-
feature_observer_configs_ = [
|
403
|
+
feature_observer_configs_: List[DispatcherObserverConfig] = [
|
337
404
|
DispatcherObserverConfig(
|
338
405
|
FeatureObserverType.IS_READY,
|
339
406
|
kwargs={"feature_types": [FeatureType.JOBS]},
|
@@ -7,7 +7,7 @@ from typing import TypedDict
|
|
7
7
|
import numpy as np
|
8
8
|
|
9
9
|
from job_shop_lib.dispatching.feature_observers import FeatureType
|
10
|
-
from job_shop_lib.visualization import (
|
10
|
+
from job_shop_lib.visualization.gantt import (
|
11
11
|
PartialGanttChartPlotterConfig,
|
12
12
|
GifConfig,
|
13
13
|
VideoConfig,
|
@@ -1,6 +1,6 @@
|
|
1
1
|
"""Utility functions for reinforcement learning."""
|
2
2
|
|
3
|
-
from typing import TypeVar, Any
|
3
|
+
from typing import TypeVar, Any, Tuple, Optional, Type
|
4
4
|
|
5
5
|
import numpy as np
|
6
6
|
from numpy.typing import NDArray
|
@@ -12,9 +12,9 @@ T = TypeVar("T", bound=np.number)
|
|
12
12
|
|
13
13
|
def add_padding(
|
14
14
|
array: NDArray[Any],
|
15
|
-
output_shape:
|
15
|
+
output_shape: Tuple[int, ...],
|
16
16
|
padding_value: float = -1,
|
17
|
-
dtype:
|
17
|
+
dtype: Optional[Type[T]] = None,
|
18
18
|
) -> NDArray[T]:
|
19
19
|
"""Adds padding to the array.
|
20
20
|
|
@@ -1,60 +0,0 @@
|
|
1
|
-
"""Contains functions and classes for visualizing job shop scheduling problems.
|
2
|
-
|
3
|
-
.. autosummary::
|
4
|
-
|
5
|
-
plot_gantt_chart
|
6
|
-
get_partial_gantt_chart_plotter
|
7
|
-
PartialGanttChartPlotter
|
8
|
-
create_gantt_chart_video
|
9
|
-
create_gantt_chart_gif
|
10
|
-
plot_disjunctive_graph
|
11
|
-
plot_agent_task_graph
|
12
|
-
GanttChartCreator
|
13
|
-
GifConfig
|
14
|
-
VideoConfig
|
15
|
-
|
16
|
-
"""
|
17
|
-
|
18
|
-
from job_shop_lib.visualization._plot_gantt_chart import plot_gantt_chart
|
19
|
-
from job_shop_lib.visualization._gantt_chart_video_and_gif_creation import (
|
20
|
-
create_gantt_chart_gif,
|
21
|
-
create_gantt_chart_video,
|
22
|
-
create_gantt_chart_frames,
|
23
|
-
get_partial_gantt_chart_plotter,
|
24
|
-
create_video_from_frames,
|
25
|
-
create_gif_from_frames,
|
26
|
-
PartialGanttChartPlotter,
|
27
|
-
)
|
28
|
-
from job_shop_lib.visualization._plot_disjunctive_graph import (
|
29
|
-
plot_disjunctive_graph,
|
30
|
-
duration_labeler,
|
31
|
-
)
|
32
|
-
from job_shop_lib.visualization._plot_agent_task_graph import (
|
33
|
-
plot_agent_task_graph,
|
34
|
-
three_columns_layout,
|
35
|
-
)
|
36
|
-
from job_shop_lib.visualization._gantt_chart_creator import (
|
37
|
-
GanttChartCreator,
|
38
|
-
PartialGanttChartPlotterConfig,
|
39
|
-
GifConfig,
|
40
|
-
VideoConfig,
|
41
|
-
)
|
42
|
-
|
43
|
-
__all__ = [
|
44
|
-
"plot_gantt_chart",
|
45
|
-
"create_gantt_chart_video",
|
46
|
-
"create_gantt_chart_gif",
|
47
|
-
"create_gantt_chart_frames",
|
48
|
-
"get_partial_gantt_chart_plotter",
|
49
|
-
"create_gif_from_frames",
|
50
|
-
"create_video_from_frames",
|
51
|
-
"plot_disjunctive_graph",
|
52
|
-
"plot_agent_task_graph",
|
53
|
-
"three_columns_layout",
|
54
|
-
"GanttChartCreator",
|
55
|
-
"PartialGanttChartPlotterConfig",
|
56
|
-
"GifConfig",
|
57
|
-
"VideoConfig",
|
58
|
-
"PartialGanttChartPlotter",
|
59
|
-
"duration_labeler",
|
60
|
-
]
|
@@ -0,0 +1,48 @@
|
|
1
|
+
"""Contains functions and classes for visualizing job shop scheduling problems.
|
2
|
+
|
3
|
+
.. autosummary::
|
4
|
+
|
5
|
+
plot_gantt_chart
|
6
|
+
get_partial_gantt_chart_plotter
|
7
|
+
PartialGanttChartPlotter
|
8
|
+
create_gantt_chart_video
|
9
|
+
create_gantt_chart_gif
|
10
|
+
GanttChartCreator
|
11
|
+
GifConfig
|
12
|
+
VideoConfig
|
13
|
+
PartialGanttChartPlotterConfig
|
14
|
+
|
15
|
+
"""
|
16
|
+
|
17
|
+
from ._plot_gantt_chart import plot_gantt_chart
|
18
|
+
from ._gantt_chart_video_and_gif_creation import (
|
19
|
+
create_gantt_chart_gif,
|
20
|
+
create_gantt_chart_video,
|
21
|
+
create_gantt_chart_frames,
|
22
|
+
get_partial_gantt_chart_plotter,
|
23
|
+
create_video_from_frames,
|
24
|
+
create_gif_from_frames,
|
25
|
+
PartialGanttChartPlotter,
|
26
|
+
)
|
27
|
+
|
28
|
+
from ._gantt_chart_creator import (
|
29
|
+
GanttChartCreator,
|
30
|
+
PartialGanttChartPlotterConfig,
|
31
|
+
GifConfig,
|
32
|
+
VideoConfig,
|
33
|
+
)
|
34
|
+
|
35
|
+
__all__ = [
|
36
|
+
"plot_gantt_chart",
|
37
|
+
"create_gantt_chart_video",
|
38
|
+
"create_gantt_chart_gif",
|
39
|
+
"create_gantt_chart_frames",
|
40
|
+
"get_partial_gantt_chart_plotter",
|
41
|
+
"create_gif_from_frames",
|
42
|
+
"create_video_from_frames",
|
43
|
+
"GanttChartCreator",
|
44
|
+
"PartialGanttChartPlotterConfig",
|
45
|
+
"GifConfig",
|
46
|
+
"VideoConfig",
|
47
|
+
"PartialGanttChartPlotter",
|
48
|
+
]
|
@@ -1,13 +1,13 @@
|
|
1
1
|
"""Home of the `GanttChartCreator` class and its configuration types."""
|
2
2
|
|
3
|
-
from typing import TypedDict
|
3
|
+
from typing import TypedDict, Optional
|
4
4
|
import matplotlib.pyplot as plt
|
5
5
|
|
6
6
|
from job_shop_lib.dispatching import (
|
7
7
|
Dispatcher,
|
8
8
|
HistoryObserver,
|
9
9
|
)
|
10
|
-
from job_shop_lib.visualization import (
|
10
|
+
from job_shop_lib.visualization.gantt import (
|
11
11
|
create_gantt_chart_video,
|
12
12
|
get_partial_gantt_chart_plotter,
|
13
13
|
create_gantt_chart_gif,
|
@@ -24,7 +24,7 @@ class PartialGanttChartPlotterConfig(TypedDict, total=False):
|
|
24
24
|
- :func:`get_partial_gantt_chart_plotter`
|
25
25
|
"""
|
26
26
|
|
27
|
-
title: str
|
27
|
+
title: Optional[str]
|
28
28
|
"""The title of the Gantt chart."""
|
29
29
|
|
30
30
|
cmap: str
|
@@ -43,7 +43,7 @@ class GifConfig(TypedDict, total=False):
|
|
43
43
|
:func:`create_gantt_chart_gif`
|
44
44
|
"""
|
45
45
|
|
46
|
-
gif_path: str
|
46
|
+
gif_path: Optional[str]
|
47
47
|
"""The path to save the GIF. It must end with '.gif'."""
|
48
48
|
|
49
49
|
fps: int
|
@@ -52,7 +52,7 @@ class GifConfig(TypedDict, total=False):
|
|
52
52
|
remove_frames: bool
|
53
53
|
"""Whether to remove the frames after creating the GIF."""
|
54
54
|
|
55
|
-
frames_dir: str
|
55
|
+
frames_dir: Optional[str]
|
56
56
|
"""The directory to store the frames."""
|
57
57
|
|
58
58
|
plot_current_time: bool
|
@@ -68,7 +68,7 @@ class VideoConfig(TypedDict, total=False):
|
|
68
68
|
:func:`create_gantt_chart_video`
|
69
69
|
"""
|
70
70
|
|
71
|
-
video_path: str
|
71
|
+
video_path: Optional[str]
|
72
72
|
"""The path to save the video. It must end with a valid video extension
|
73
73
|
(e.g., '.mp4')."""
|
74
74
|
|
@@ -78,7 +78,7 @@ class VideoConfig(TypedDict, total=False):
|
|
78
78
|
remove_frames: bool
|
79
79
|
"""Whether to remove the frames after creating the video."""
|
80
80
|
|
81
|
-
frames_dir: str
|
81
|
+
frames_dir: Optional[str]
|
82
82
|
"""The directory to store the frames."""
|
83
83
|
|
84
84
|
plot_current_time: bool
|
@@ -164,11 +164,11 @@ class GanttChartCreator:
|
|
164
164
|
def __init__(
|
165
165
|
self,
|
166
166
|
dispatcher: Dispatcher,
|
167
|
-
partial_gantt_chart_plotter_config:
|
168
|
-
PartialGanttChartPlotterConfig
|
169
|
-
|
170
|
-
gif_config: GifConfig
|
171
|
-
video_config: VideoConfig
|
167
|
+
partial_gantt_chart_plotter_config: Optional[
|
168
|
+
PartialGanttChartPlotterConfig
|
169
|
+
] = None,
|
170
|
+
gif_config: Optional[GifConfig] = None,
|
171
|
+
video_config: Optional[VideoConfig] = None,
|
172
172
|
):
|
173
173
|
if gif_config is None:
|
174
174
|
gif_config = {}
|
@@ -3,7 +3,7 @@
|
|
3
3
|
import os
|
4
4
|
import pathlib
|
5
5
|
import shutil
|
6
|
-
from typing import Sequence, Protocol
|
6
|
+
from typing import Sequence, Protocol, Optional, List
|
7
7
|
|
8
8
|
import imageio
|
9
9
|
import matplotlib.pyplot as plt
|
@@ -22,7 +22,7 @@ from job_shop_lib.dispatching import (
|
|
22
22
|
HistoryObserver,
|
23
23
|
)
|
24
24
|
from job_shop_lib.dispatching.rules import DispatchingRuleSolver
|
25
|
-
from job_shop_lib.visualization._plot_gantt_chart import plot_gantt_chart
|
25
|
+
from job_shop_lib.visualization.gantt._plot_gantt_chart import plot_gantt_chart
|
26
26
|
|
27
27
|
|
28
28
|
# This class serves as a more meaningful type hint than simply:
|
@@ -43,9 +43,9 @@ class PartialGanttChartPlotter(Protocol):
|
|
43
43
|
def __call__(
|
44
44
|
self,
|
45
45
|
schedule: Schedule,
|
46
|
-
makespan: int
|
47
|
-
available_operations:
|
48
|
-
current_time: int
|
46
|
+
makespan: Optional[int] = None,
|
47
|
+
available_operations: Optional[List[Operation]] = None,
|
48
|
+
current_time: Optional[int] = None,
|
49
49
|
) -> Figure:
|
50
50
|
"""Plots a Gantt chart for an unfinished schedule.
|
51
51
|
|
@@ -65,7 +65,7 @@ class PartialGanttChartPlotter(Protocol):
|
|
65
65
|
|
66
66
|
|
67
67
|
def get_partial_gantt_chart_plotter(
|
68
|
-
title: str
|
68
|
+
title: Optional[str] = None,
|
69
69
|
cmap: str = "viridis",
|
70
70
|
show_available_operations: bool = False,
|
71
71
|
) -> PartialGanttChartPlotter:
|
@@ -92,9 +92,9 @@ def get_partial_gantt_chart_plotter(
|
|
92
92
|
|
93
93
|
def plot_function(
|
94
94
|
schedule: Schedule,
|
95
|
-
makespan: int
|
96
|
-
available_operations:
|
97
|
-
current_time: int
|
95
|
+
makespan: Optional[int] = None,
|
96
|
+
available_operations: Optional[List[Operation]] = None,
|
97
|
+
current_time: Optional[int] = None,
|
98
98
|
) -> Figure:
|
99
99
|
fig, ax = plot_gantt_chart(
|
100
100
|
schedule, title=title, cmap_name=cmap, xlim=makespan
|
@@ -133,14 +133,14 @@ def get_partial_gantt_chart_plotter(
|
|
133
133
|
# pylint: disable=too-many-arguments
|
134
134
|
def create_gantt_chart_gif(
|
135
135
|
instance: JobShopInstance,
|
136
|
-
gif_path: str
|
137
|
-
solver: DispatchingRuleSolver
|
138
|
-
plot_function: PartialGanttChartPlotter
|
136
|
+
gif_path: Optional[str] = None,
|
137
|
+
solver: Optional[DispatchingRuleSolver] = None,
|
138
|
+
plot_function: Optional[PartialGanttChartPlotter] = None,
|
139
139
|
fps: int = 1,
|
140
140
|
remove_frames: bool = True,
|
141
|
-
frames_dir: str
|
141
|
+
frames_dir: Optional[str] = None,
|
142
142
|
plot_current_time: bool = True,
|
143
|
-
schedule_history: Sequence[ScheduledOperation]
|
143
|
+
schedule_history: Optional[Sequence[ScheduledOperation]] = None,
|
144
144
|
) -> None:
|
145
145
|
"""Creates a GIF of the schedule being built.
|
146
146
|
|
@@ -202,14 +202,14 @@ def create_gantt_chart_gif(
|
|
202
202
|
# pylint: disable=too-many-arguments
|
203
203
|
def create_gantt_chart_video(
|
204
204
|
instance: JobShopInstance,
|
205
|
-
video_path: str
|
206
|
-
solver: DispatchingRuleSolver
|
207
|
-
plot_function: PartialGanttChartPlotter
|
205
|
+
video_path: Optional[str] = None,
|
206
|
+
solver: Optional[DispatchingRuleSolver] = None,
|
207
|
+
plot_function: Optional[PartialGanttChartPlotter] = None,
|
208
208
|
fps: int = 1,
|
209
209
|
remove_frames: bool = True,
|
210
|
-
frames_dir: str
|
210
|
+
frames_dir: Optional[str] = None,
|
211
211
|
plot_current_time: bool = True,
|
212
|
-
schedule_history: Sequence[ScheduledOperation]
|
212
|
+
schedule_history: Optional[Sequence[ScheduledOperation]] = None,
|
213
213
|
) -> None:
|
214
214
|
"""Creates a video of the schedule being built.
|
215
215
|
|
@@ -268,10 +268,10 @@ def create_gantt_chart_video(
|
|
268
268
|
def create_gantt_chart_frames(
|
269
269
|
frames_dir: str,
|
270
270
|
instance: JobShopInstance,
|
271
|
-
solver: DispatchingRuleSolver
|
271
|
+
solver: Optional[DispatchingRuleSolver],
|
272
272
|
plot_function: PartialGanttChartPlotter,
|
273
273
|
plot_current_time: bool = True,
|
274
|
-
schedule_history: Sequence[ScheduledOperation]
|
274
|
+
schedule_history: Optional[Sequence[ScheduledOperation]] = None,
|
275
275
|
) -> None:
|
276
276
|
"""Creates frames of the Gantt chart for the schedule being built.
|
277
277
|
|
@@ -411,7 +411,7 @@ def resize_image_to_macro_block(
|
|
411
411
|
return image
|
412
412
|
|
413
413
|
|
414
|
-
def _load_images(frames_dir: str) ->
|
414
|
+
def _load_images(frames_dir: str) -> List:
|
415
415
|
frames = [
|
416
416
|
os.path.join(frames_dir, frame)
|
417
417
|
for frame in sorted(os.listdir(frames_dir))
|