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.
Files changed (52) hide show
  1. job_shop_lib/__init__.py +1 -1
  2. job_shop_lib/_job_shop_instance.py +34 -29
  3. job_shop_lib/_operation.py +4 -2
  4. job_shop_lib/_schedule.py +11 -11
  5. job_shop_lib/benchmarking/_load_benchmark.py +3 -3
  6. job_shop_lib/constraint_programming/_ortools_solver.py +6 -6
  7. job_shop_lib/dispatching/_dispatcher.py +19 -19
  8. job_shop_lib/dispatching/_dispatcher_observer_config.py +4 -4
  9. job_shop_lib/dispatching/_factories.py +4 -2
  10. job_shop_lib/dispatching/_history_observer.py +2 -1
  11. job_shop_lib/dispatching/_ready_operation_filters.py +19 -18
  12. job_shop_lib/dispatching/_unscheduled_operations_observer.py +4 -3
  13. job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py +7 -8
  14. job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py +3 -1
  15. job_shop_lib/dispatching/feature_observers/_factory.py +13 -14
  16. job_shop_lib/dispatching/feature_observers/_feature_observer.py +9 -8
  17. job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +2 -1
  18. job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +4 -2
  19. job_shop_lib/dispatching/rules/_dispatching_rule_factory.py +4 -2
  20. job_shop_lib/dispatching/rules/_dispatching_rule_solver.py +23 -15
  21. job_shop_lib/dispatching/rules/_dispatching_rules_functions.py +9 -8
  22. job_shop_lib/dispatching/rules/_machine_chooser_factory.py +4 -3
  23. job_shop_lib/dispatching/rules/_utils.py +9 -8
  24. job_shop_lib/generation/__init__.py +8 -0
  25. job_shop_lib/generation/_general_instance_generator.py +42 -64
  26. job_shop_lib/generation/_instance_generator.py +11 -7
  27. job_shop_lib/generation/_transformations.py +5 -4
  28. job_shop_lib/generation/_utils.py +124 -0
  29. job_shop_lib/graphs/__init__.py +7 -7
  30. job_shop_lib/graphs/{_build_agent_task_graph.py → _build_resource_task_graphs.py} +26 -24
  31. job_shop_lib/graphs/_job_shop_graph.py +17 -13
  32. job_shop_lib/graphs/_node.py +6 -4
  33. job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +4 -2
  34. job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +40 -20
  35. job_shop_lib/reinforcement_learning/_reward_observers.py +3 -1
  36. job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +89 -22
  37. job_shop_lib/reinforcement_learning/_types_and_constants.py +1 -1
  38. job_shop_lib/reinforcement_learning/_utils.py +3 -3
  39. job_shop_lib/visualization/__init__.py +0 -60
  40. job_shop_lib/visualization/gantt/__init__.py +48 -0
  41. job_shop_lib/visualization/{_gantt_chart_creator.py → gantt/_gantt_chart_creator.py} +12 -12
  42. job_shop_lib/visualization/{_gantt_chart_video_and_gif_creation.py → gantt/_gantt_chart_video_and_gif_creation.py} +22 -22
  43. job_shop_lib/visualization/{_plot_gantt_chart.py → gantt/_plot_gantt_chart.py} +12 -13
  44. job_shop_lib/visualization/graphs/__init__.py +29 -0
  45. job_shop_lib/visualization/{_plot_disjunctive_graph.py → graphs/_plot_disjunctive_graph.py} +18 -16
  46. job_shop_lib/visualization/graphs/_plot_resource_task_graph.py +389 -0
  47. {job_shop_lib-1.0.0a5.dist-info → job_shop_lib-1.0.0b1.dist-info}/METADATA +17 -15
  48. job_shop_lib-1.0.0b1.dist-info/RECORD +69 -0
  49. job_shop_lib/visualization/_plot_agent_task_graph.py +0 -276
  50. job_shop_lib-1.0.0a5.dist-info/RECORD +0 -66
  51. {job_shop_lib-1.0.0a5.dist-info → job_shop_lib-1.0.0b1.dist-info}/LICENSE +0 -0
  52. {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, build_agent_task_graph
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
- ] = build_agent_task_graph,
163
+ ] = build_resource_task_graph,
164
164
  graph_updater_config: DispatcherObserverConfig[
165
- type[GraphUpdater]
165
+ Type[GraphUpdater]
166
166
  ] = DispatcherObserverConfig(class_type=ResidualGraphUpdater),
167
167
  ready_operations_filter: Callable[
168
- [Dispatcher, list[Operation]], list[Operation]
168
+ [Dispatcher, List[Operation]], List[Operation]
169
169
  ] = filter_dominated_operations,
170
170
  reward_function_config: DispatcherObserverConfig[
171
- type[RewardObserver]
171
+ Type[RewardObserver]
172
172
  ] = DispatcherObserverConfig(class_type=MakespanReward),
173
- render_mode: str | None = None,
174
- render_config: RenderConfig | None = None,
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, list[Operation]], list[Operation]] | None:
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, list[Operation]], list[Operation]
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 | None = None,
271
- options: dict[str, Any] | None = None,
272
- ) -> tuple[ObservationDict, dict]:
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: tuple[int, int]
307
- ) -> tuple[ObservationDict, float, bool, bool, dict]:
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: ``"feature_names"``, The names of
326
- the features in the observation; ``"available_operations"``, the
327
- operations that are ready to be scheduled.
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: dict[str, float | bool] = defaultdict(lambda: -1)
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) -> tuple[int, ...]:
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: list[float] = []
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
- type[RewardObserver]
142
+ Type[RewardObserver]
142
143
  ] = DispatcherObserverConfig(class_type=MakespanReward),
143
144
  graph_updater_config: DispatcherObserverConfig[
144
- type[GraphUpdater]
145
+ Type[GraphUpdater]
145
146
  ] = DispatcherObserverConfig(class_type=ResidualGraphUpdater),
146
- ready_operations_filter: (
147
- Callable[[Dispatcher, list[Operation]], list[Operation]] | None
148
- ) = filter_dominated_operations,
149
- render_mode: str | None = None,
150
- render_config: RenderConfig | None = None,
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: dict[str, gym.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 | None = None,
228
- options: dict[str, Any] | None = None,
229
- ) -> tuple[ObservationDict, dict]:
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: tuple[int, int]
238
- ) -> tuple[ObservationDict, float, bool, bool, dict[str, Any]]:
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; "available_operations", the
258
- operations that are ready to be scheduled.
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: dict[str, Any] = {
294
+ info: Dict[str, Any] = {
273
295
  "feature_names": self.composite_observer.column_names,
274
- "available_operations": self.dispatcher.available_operations(),
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: tuple[int, ...],
15
+ output_shape: Tuple[int, ...],
16
16
  padding_value: float = -1,
17
- dtype: type[T] | None = None,
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 | None
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 | None
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 | None
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 | None
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 | None
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 | None
169
- ) = None,
170
- gif_config: GifConfig | None = None,
171
- video_config: VideoConfig | None = None,
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 | None = None,
47
- available_operations: list[Operation] | None = None,
48
- current_time: int | None = None,
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 | None = None,
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 | None = None,
96
- available_operations: list | None = None,
97
- current_time: int | None = None,
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 | None = None,
137
- solver: DispatchingRuleSolver | None = None,
138
- plot_function: PartialGanttChartPlotter | None = None,
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 | None = None,
141
+ frames_dir: Optional[str] = None,
142
142
  plot_current_time: bool = True,
143
- schedule_history: Sequence[ScheduledOperation] | None = None,
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 | None = None,
206
- solver: DispatchingRuleSolver | None = None,
207
- plot_function: PartialGanttChartPlotter | None = None,
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 | None = None,
210
+ frames_dir: Optional[str] = None,
211
211
  plot_current_time: bool = True,
212
- schedule_history: Sequence[ScheduledOperation] | None = None,
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 | None,
271
+ solver: Optional[DispatchingRuleSolver],
272
272
  plot_function: PartialGanttChartPlotter,
273
273
  plot_current_time: bool = True,
274
- schedule_history: Sequence[ScheduledOperation] | None = None,
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) -> list:
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))