job-shop-lib 1.0.2__py3-none-any.whl → 1.0.3__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 (46) hide show
  1. job_shop_lib/__init__.py +1 -1
  2. job_shop_lib/_job_shop_instance.py +26 -26
  3. job_shop_lib/_operation.py +2 -4
  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 +3 -3
  9. job_shop_lib/dispatching/_factories.py +2 -3
  10. job_shop_lib/dispatching/_history_observer.py +1 -2
  11. job_shop_lib/dispatching/_optimal_operations_observer.py +3 -4
  12. job_shop_lib/dispatching/_ready_operation_filters.py +18 -19
  13. job_shop_lib/dispatching/_unscheduled_operations_observer.py +3 -2
  14. job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py +10 -12
  15. job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py +1 -3
  16. job_shop_lib/dispatching/feature_observers/_factory.py +11 -12
  17. job_shop_lib/dispatching/feature_observers/_feature_observer.py +8 -9
  18. job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +2 -4
  19. job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +2 -4
  20. job_shop_lib/dispatching/rules/_dispatching_rule_factory.py +2 -4
  21. job_shop_lib/dispatching/rules/_dispatching_rule_solver.py +15 -20
  22. job_shop_lib/dispatching/rules/_dispatching_rules_functions.py +9 -10
  23. job_shop_lib/dispatching/rules/_machine_chooser_factory.py +2 -3
  24. job_shop_lib/dispatching/rules/_utils.py +7 -8
  25. job_shop_lib/generation/_general_instance_generator.py +22 -20
  26. job_shop_lib/generation/_instance_generator.py +8 -8
  27. job_shop_lib/generation/_transformations.py +4 -5
  28. job_shop_lib/generation/_utils.py +16 -8
  29. job_shop_lib/graphs/_job_shop_graph.py +13 -14
  30. job_shop_lib/graphs/_node.py +6 -12
  31. job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +2 -4
  32. job_shop_lib/reinforcement_learning/__init__.py +2 -1
  33. job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +17 -17
  34. job_shop_lib/reinforcement_learning/_resource_task_graph_observation.py +1 -1
  35. job_shop_lib/reinforcement_learning/_reward_observers.py +1 -3
  36. job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +22 -24
  37. job_shop_lib/reinforcement_learning/_utils.py +2 -2
  38. job_shop_lib/visualization/gantt/_gantt_chart_creator.py +11 -11
  39. job_shop_lib/visualization/gantt/_gantt_chart_video_and_gif_creation.py +21 -21
  40. job_shop_lib/visualization/gantt/_plot_gantt_chart.py +12 -14
  41. job_shop_lib/visualization/graphs/_plot_disjunctive_graph.py +15 -17
  42. job_shop_lib/visualization/graphs/_plot_resource_task_graph.py +22 -24
  43. {job_shop_lib-1.0.2.dist-info → job_shop_lib-1.0.3.dist-info}/METADATA +1 -1
  44. {job_shop_lib-1.0.2.dist-info → job_shop_lib-1.0.3.dist-info}/RECORD +46 -46
  45. {job_shop_lib-1.0.2.dist-info → job_shop_lib-1.0.3.dist-info}/LICENSE +0 -0
  46. {job_shop_lib-1.0.2.dist-info → job_shop_lib-1.0.3.dist-info}/WHEEL +0 -0
@@ -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, Dict, Tuple, List, Optional, Type, Union
5
+ from typing import Any
6
6
 
7
7
  import matplotlib.pyplot as plt
8
8
  import gymnasium as gym
@@ -140,24 +140,22 @@ class SingleJobShopGraphEnv(gym.Env):
140
140
  self,
141
141
  job_shop_graph: JobShopGraph,
142
142
  feature_observer_configs: Sequence[
143
- Union[
144
- str,
145
- FeatureObserverType,
146
- Type[FeatureObserver],
147
- FeatureObserverConfig,
148
- ],
143
+ str
144
+ | FeatureObserverType
145
+ | type[FeatureObserver]
146
+ | FeatureObserverConfig
149
147
  ],
150
148
  reward_function_config: DispatcherObserverConfig[
151
- Type[RewardObserver]
149
+ type[RewardObserver]
152
150
  ] = DispatcherObserverConfig(class_type=MakespanReward),
153
151
  graph_updater_config: DispatcherObserverConfig[
154
- Type[GraphUpdater]
152
+ type[GraphUpdater]
155
153
  ] = DispatcherObserverConfig(class_type=ResidualGraphUpdater),
156
- ready_operations_filter: Optional[
157
- Callable[[Dispatcher, List[Operation]], List[Operation]]
158
- ] = filter_dominated_operations,
159
- render_mode: Optional[str] = None,
160
- render_config: Optional[RenderConfig] = None,
154
+ ready_operations_filter: (
155
+ Callable[[Dispatcher, list[Operation]], list[Operation]] | None
156
+ ) = filter_dominated_operations,
157
+ render_mode: str | None = None,
158
+ render_config: RenderConfig | None = None,
161
159
  use_padding: bool = True,
162
160
  ) -> None:
163
161
  super().__init__()
@@ -224,7 +222,7 @@ class SingleJobShopGraphEnv(gym.Env):
224
222
  def _get_observation_space(self) -> gym.spaces.Dict:
225
223
  """Returns the observation space dictionary."""
226
224
  num_edges = self.job_shop_graph.num_edges
227
- dict_space: Dict[str, gym.Space] = {
225
+ dict_space: dict[str, gym.Space] = {
228
226
  ObservationSpaceKey.REMOVED_NODES.value: gym.spaces.MultiBinary(
229
227
  len(self.job_shop_graph.nodes)
230
228
  ),
@@ -250,9 +248,9 @@ class SingleJobShopGraphEnv(gym.Env):
250
248
  def reset(
251
249
  self,
252
250
  *,
253
- seed: Optional[int] = None,
254
- options: Optional[Dict[str, Any]] = None,
255
- ) -> Tuple[ObservationDict, dict[str, Any]]:
251
+ seed: int | None = None,
252
+ options: dict[str, Any] | None = None,
253
+ ) -> tuple[ObservationDict, dict[str, Any]]:
256
254
  """Resets the environment.
257
255
 
258
256
  Args:
@@ -284,8 +282,8 @@ class SingleJobShopGraphEnv(gym.Env):
284
282
  }
285
283
 
286
284
  def step(
287
- self, action: Tuple[int, int]
288
- ) -> Tuple[ObservationDict, float, bool, bool, Dict[str, Any]]:
285
+ self, action: tuple[int, int]
286
+ ) -> tuple[ObservationDict, float, bool, bool, dict[str, Any]]:
289
287
  """Takes a step in the environment.
290
288
 
291
289
  Args:
@@ -319,7 +317,7 @@ class SingleJobShopGraphEnv(gym.Env):
319
317
  reward = self.reward_function.last_reward
320
318
  done = self.dispatcher.schedule.is_complete()
321
319
  truncated = False
322
- info: Dict[str, Any] = {
320
+ info: dict[str, Any] = {
323
321
  "feature_names": self.composite_observer.column_names,
324
322
  "available_operations_with_ids": (
325
323
  self.get_available_actions_with_ids()
@@ -374,7 +372,7 @@ class SingleJobShopGraphEnv(gym.Env):
374
372
  elif self.render_mode == "save_gif":
375
373
  self.gantt_chart_creator.create_gif()
376
374
 
377
- def get_available_actions_with_ids(self) -> List[Tuple[int, int, int]]:
375
+ def get_available_actions_with_ids(self) -> list[tuple[int, int, int]]:
378
376
  """Returns a list of available actions in the form of
379
377
  (operation_id, machine_id, job_id)."""
380
378
  available_operations = self.dispatcher.available_operations()
@@ -388,7 +386,7 @@ class SingleJobShopGraphEnv(gym.Env):
388
386
  )
389
387
  return available_operations_with_ids
390
388
 
391
- def validate_action(self, action: Tuple[int, int]) -> None:
389
+ def validate_action(self, action: tuple[int, int]) -> None:
392
390
  """Validates that the action is legal in the current state.
393
391
 
394
392
  Args:
@@ -428,7 +426,7 @@ if __name__ == "__main__":
428
426
 
429
427
  instance = load_benchmark_instance("ft06")
430
428
  job_shop_graph_ = build_disjunctive_graph(instance)
431
- feature_observer_configs_: List[DispatcherObserverConfig] = [
429
+ feature_observer_configs_: list[DispatcherObserverConfig] = [
432
430
  DispatcherObserverConfig(
433
431
  FeatureObserverType.IS_READY,
434
432
  kwargs={"feature_types": [FeatureType.JOBS]},
@@ -1,6 +1,6 @@
1
1
  """Utility functions for reinforcement learning."""
2
2
 
3
- from typing import TypeVar, Any, Type
3
+ from typing import TypeVar, Any
4
4
 
5
5
  import numpy as np
6
6
  from numpy.typing import NDArray
@@ -15,7 +15,7 @@ def add_padding(
15
15
  array: NDArray[Any],
16
16
  output_shape: tuple[int, ...],
17
17
  padding_value: float = -1,
18
- dtype: Type[T] | None = None,
18
+ dtype: type[T] | None = None,
19
19
  ) -> NDArray[T]:
20
20
  """Adds padding to the array.
21
21
 
@@ -1,6 +1,6 @@
1
1
  """Home of the `GanttChartCreator` class and its configuration types."""
2
2
 
3
- from typing import TypedDict, Optional
3
+ from typing import TypedDict
4
4
  import matplotlib.pyplot as plt
5
5
 
6
6
  from job_shop_lib.dispatching import (
@@ -24,7 +24,7 @@ class PartialGanttChartPlotterConfig(TypedDict, total=False):
24
24
  - :func:`get_partial_gantt_chart_plotter`
25
25
  """
26
26
 
27
- title: Optional[str]
27
+ title: str | None
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: Optional[str]
46
+ gif_path: str | None
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: Optional[str]
55
+ frames_dir: str | None
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: Optional[str]
71
+ video_path: str | None
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: Optional[str]
81
+ frames_dir: str | None
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: Optional[
168
- PartialGanttChartPlotterConfig
169
- ] = None,
170
- gif_config: Optional[GifConfig] = None,
171
- video_config: Optional[VideoConfig] = None,
167
+ partial_gantt_chart_plotter_config: (
168
+ PartialGanttChartPlotterConfig | None
169
+ ) = None,
170
+ gif_config: GifConfig | None = None,
171
+ video_config: VideoConfig | None = 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, Optional, List, Any
6
+ from typing import Sequence, Protocol, Any
7
7
 
8
8
  import imageio
9
9
  import matplotlib.pyplot as plt
@@ -43,9 +43,9 @@ class PartialGanttChartPlotter(Protocol):
43
43
  def __call__(
44
44
  self,
45
45
  schedule: Schedule,
46
- makespan: Optional[int] = None,
47
- available_operations: Optional[List[Operation]] = None,
48
- current_time: Optional[int] = None,
46
+ makespan: int | None = None,
47
+ available_operations: list[Operation] | None = None,
48
+ current_time: int | None = 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: Optional[str] = None,
68
+ title: str | None = None,
69
69
  cmap: str = "viridis",
70
70
  show_available_operations: bool = False,
71
71
  **kwargs: Any,
@@ -95,9 +95,9 @@ def get_partial_gantt_chart_plotter(
95
95
 
96
96
  def plot_function(
97
97
  schedule: Schedule,
98
- makespan: Optional[int] = None,
99
- available_operations: Optional[List[Operation]] = None,
100
- current_time: Optional[int] = None,
98
+ makespan: int | None = None,
99
+ available_operations: list[Operation] | None = None,
100
+ current_time: int | None = None,
101
101
  ) -> Figure:
102
102
  fig, ax = plot_gantt_chart(
103
103
  schedule, title=title, cmap_name=cmap, xlim=makespan, **kwargs
@@ -136,14 +136,14 @@ def get_partial_gantt_chart_plotter(
136
136
  # pylint: disable=too-many-arguments
137
137
  def create_gantt_chart_gif(
138
138
  instance: JobShopInstance,
139
- gif_path: Optional[str] = None,
140
- solver: Optional[DispatchingRuleSolver] = None,
141
- plot_function: Optional[PartialGanttChartPlotter] = None,
139
+ gif_path: str | None = None,
140
+ solver: DispatchingRuleSolver | None = None,
141
+ plot_function: PartialGanttChartPlotter | None = None,
142
142
  fps: int = 1,
143
143
  remove_frames: bool = True,
144
- frames_dir: Optional[str] = None,
144
+ frames_dir: str | None = None,
145
145
  plot_current_time: bool = True,
146
- schedule_history: Optional[Sequence[ScheduledOperation]] = None,
146
+ schedule_history: Sequence[ScheduledOperation] | None = None,
147
147
  ) -> None:
148
148
  """Creates a GIF of the schedule being built.
149
149
 
@@ -205,14 +205,14 @@ def create_gantt_chart_gif(
205
205
  # pylint: disable=too-many-arguments
206
206
  def create_gantt_chart_video(
207
207
  instance: JobShopInstance,
208
- video_path: Optional[str] = None,
209
- solver: Optional[DispatchingRuleSolver] = None,
210
- plot_function: Optional[PartialGanttChartPlotter] = None,
208
+ video_path: str | None = None,
209
+ solver: DispatchingRuleSolver | None = None,
210
+ plot_function: PartialGanttChartPlotter | None = None,
211
211
  fps: int = 1,
212
212
  remove_frames: bool = True,
213
- frames_dir: Optional[str] = None,
213
+ frames_dir: str | None = None,
214
214
  plot_current_time: bool = True,
215
- schedule_history: Optional[Sequence[ScheduledOperation]] = None,
215
+ schedule_history: Sequence[ScheduledOperation] | None = None,
216
216
  ) -> None:
217
217
  """Creates a video of the schedule being built.
218
218
 
@@ -271,10 +271,10 @@ def create_gantt_chart_video(
271
271
  def create_gantt_chart_frames(
272
272
  frames_dir: str,
273
273
  instance: JobShopInstance,
274
- solver: Optional[DispatchingRuleSolver],
274
+ solver: DispatchingRuleSolver | None,
275
275
  plot_function: PartialGanttChartPlotter,
276
276
  plot_current_time: bool = True,
277
- schedule_history: Optional[Sequence[ScheduledOperation]] = None,
277
+ schedule_history: Sequence[ScheduledOperation] | None = None,
278
278
  ) -> None:
279
279
  """Creates frames of the Gantt chart for the schedule being built.
280
280
 
@@ -414,7 +414,7 @@ def resize_image_to_macro_block(
414
414
  return image
415
415
 
416
416
 
417
- def _load_images(frames_dir: str) -> List:
417
+ def _load_images(frames_dir: str) -> list:
418
418
  frames = [
419
419
  os.path.join(frames_dir, frame)
420
420
  for frame in sorted(os.listdir(frames_dir))
@@ -1,7 +1,5 @@
1
1
  """Module for plotting static Gantt charts for job shop schedules."""
2
2
 
3
- from typing import Optional, List, Tuple, Dict
4
-
5
3
  from matplotlib.figure import Figure
6
4
  import matplotlib.pyplot as plt
7
5
  from matplotlib.colors import Normalize
@@ -15,16 +13,16 @@ _Y_POSITION_INCREMENT = 10
15
13
 
16
14
  def plot_gantt_chart(
17
15
  schedule: Schedule,
18
- title: Optional[str] = None,
16
+ title: str | None = None,
19
17
  cmap_name: str = "viridis",
20
- xlim: Optional[int] = None,
18
+ xlim: int | None = None,
21
19
  number_of_x_ticks: int = 15,
22
- job_labels: Optional[List[str]] = None,
23
- machine_labels: Optional[List[str]] = None,
20
+ job_labels: list[str] | None = None,
21
+ machine_labels: list[str] | None = None,
24
22
  legend_title: str = "",
25
23
  x_label: str = "Time units",
26
24
  y_label: str = "Machines",
27
- ) -> Tuple[Figure, plt.Axes]:
25
+ ) -> tuple[Figure, plt.Axes]:
28
26
  """Plots a Gantt chart for the schedule.
29
27
 
30
28
  This function generates a Gantt chart that visualizes the schedule of jobs
@@ -90,10 +88,10 @@ def plot_gantt_chart(
90
88
 
91
89
  def _initialize_plot(
92
90
  schedule: Schedule,
93
- title: Optional[str],
91
+ title: str | None,
94
92
  x_label: str = "Time units",
95
93
  y_label: str = "Machines",
96
- ) -> Tuple[Figure, plt.Axes]:
94
+ ) -> tuple[Figure, plt.Axes]:
97
95
  """Initializes the plot."""
98
96
  fig, ax = plt.subplots()
99
97
  ax.set_xlabel(x_label)
@@ -110,8 +108,8 @@ def _plot_machine_schedules(
110
108
  schedule: Schedule,
111
109
  ax: plt.Axes,
112
110
  cmap_name: str,
113
- job_labels: Optional[List[str]],
114
- ) -> Dict[int, Patch]:
111
+ job_labels: list[str] | None,
112
+ ) -> dict[int, Patch]:
115
113
  """Plots the schedules for each machine."""
116
114
  max_job_id = schedule.instance.num_jobs - 1
117
115
  cmap = plt.get_cmap(cmap_name, max_job_id + 1)
@@ -137,7 +135,7 @@ def _plot_machine_schedules(
137
135
  return legend_handles
138
136
 
139
137
 
140
- def _get_job_label(job_labels: Optional[List[str]], job_id: int) -> str:
138
+ def _get_job_label(job_labels: list[str] | None, job_id: int) -> str:
141
139
  """Returns the label for the job."""
142
140
  if job_labels is None:
143
141
  return f"Job {job_id}"
@@ -161,7 +159,7 @@ def _plot_scheduled_operation(
161
159
 
162
160
 
163
161
  def _configure_legend(
164
- ax: plt.Axes, legend_handles: Dict[int, Patch], legend_title: str
162
+ ax: plt.Axes, legend_handles: dict[int, Patch], legend_title: str
165
163
  ):
166
164
  """Configures the legend for the plot."""
167
165
  sorted_legend_handles = [
@@ -178,7 +176,7 @@ def _configure_legend(
178
176
  def _configure_axes(
179
177
  schedule: Schedule,
180
178
  ax: plt.Axes,
181
- xlim: Optional[int],
179
+ xlim: int | None,
182
180
  number_of_x_ticks: int,
183
181
  machine_labels: list[str] | None,
184
182
  ):
@@ -1,7 +1,7 @@
1
1
  """Module for visualizing the disjunctive graph of a job shop instance."""
2
2
 
3
3
  import functools
4
- from typing import Any, Optional, Tuple, Dict, Union
4
+ from typing import Any
5
5
  from collections.abc import Callable, Sequence, Iterable
6
6
  import warnings
7
7
  import copy
@@ -23,7 +23,7 @@ from job_shop_lib.graphs import (
23
23
  from job_shop_lib.exceptions import ValidationError
24
24
 
25
25
 
26
- Layout = Callable[[nx.Graph], Dict[str, Tuple[float, float]]]
26
+ Layout = Callable[[nx.Graph], dict[str, tuple[float, float]]]
27
27
 
28
28
 
29
29
  def duration_labeler(node: Node) -> str:
@@ -56,10 +56,10 @@ def duration_labeler(node: Node) -> str:
56
56
  # pylint: disable=too-many-arguments, too-many-locals, too-many-statements
57
57
  # pylint: disable=too-many-branches, line-too-long
58
58
  def plot_disjunctive_graph(
59
- job_shop: Union[JobShopGraph, JobShopInstance],
59
+ job_shop: JobShopGraph | JobShopInstance,
60
60
  *,
61
- title: Optional[str] = None,
62
- figsize: Tuple[float, float] = (6, 4),
61
+ title: str | None = None,
62
+ figsize: tuple[float, float] = (6, 4),
63
63
  node_size: int = 1600,
64
64
  edge_width: int = 2,
65
65
  font_size: int = 10,
@@ -67,27 +67,25 @@ def plot_disjunctive_graph(
67
67
  alpha: float = 0.95,
68
68
  operation_node_labeler: Callable[[Node], str] = duration_labeler,
69
69
  node_font_color: str = "white",
70
- machine_colors: Optional[
71
- Dict[int, Tuple[float, float, float, float]]
72
- ] = None,
70
+ machine_colors: dict[int, tuple[float, float, float, float]] | None = None,
73
71
  color_map: str = "Dark2_r",
74
72
  disjunctive_edge_color: str = "red",
75
73
  conjunctive_edge_color: str = "black",
76
- layout: Optional[Layout] = None,
77
- draw_disjunctive_edges: Union[bool, str] = True,
78
- conjunctive_edges_additional_params: Optional[Dict[str, Any]] = None,
79
- disjunctive_edges_additional_params: Optional[Dict[str, Any]] = None,
74
+ layout: Layout | None = None,
75
+ draw_disjunctive_edges: bool | str = True,
76
+ conjunctive_edges_additional_params: dict[str, Any] | None = None,
77
+ disjunctive_edges_additional_params: dict[str, Any] | None = None,
80
78
  conjunctive_patch_label: str = "Conjunctive edges",
81
79
  disjunctive_patch_label: str = "Disjunctive edges",
82
80
  legend_text: str = "$p_{ij}=$duration of $O_{ij}$",
83
81
  show_machine_colors_in_legend: bool = True,
84
- machine_labels: Optional[Sequence[str]] = None,
82
+ machine_labels: Sequence[str] | None = None,
85
83
  legend_location: str = "upper left",
86
- legend_bbox_to_anchor: Tuple[float, float] = (1.01, 1),
84
+ legend_bbox_to_anchor: tuple[float, float] = (1.01, 1),
87
85
  start_node_label: str = "$S$",
88
86
  end_node_label: str = "$T$",
89
87
  font_family: str = "sans-serif",
90
- ) -> Tuple[plt.Figure, plt.Axes]:
88
+ ) -> tuple[plt.Figure, plt.Axes]:
91
89
  r"""Plots the disjunctive graph of the given job shop instance or graph.
92
90
 
93
91
  Args:
@@ -240,7 +238,7 @@ def plot_disjunctive_graph(
240
238
  # Draw nodes
241
239
  # ----------
242
240
  operation_nodes = job_shop_graph.nodes_by_type[NodeType.OPERATION]
243
- cmap_func: Optional[matplotlib.colors.Colormap] = None
241
+ cmap_func: matplotlib.colors.Colormap | None = None
244
242
  if machine_colors is None:
245
243
  machine_colors = {}
246
244
  cmap_func = matplotlib.colormaps.get_cmap(color_map)
@@ -289,7 +287,7 @@ def plot_disjunctive_graph(
289
287
  for u, v, d in job_shop_graph.graph.edges(data=True)
290
288
  if d["type"] == EdgeType.CONJUNCTIVE
291
289
  ]
292
- disjunctive_edges: Iterable[Tuple[int, int]] = [
290
+ disjunctive_edges: Iterable[tuple[int, int]] = [
293
291
  (u, v)
294
292
  for u, v, d in job_shop_graph.graph.edges(data=True)
295
293
  if d["type"] == EdgeType.DISJUNCTIVE
@@ -10,7 +10,7 @@ all operation nodes from the same job are connected by non-directed edges too.
10
10
 
11
11
  from collections.abc import Callable
12
12
  from copy import deepcopy
13
- from typing import Optional, Any, Tuple, Dict, Union, List
13
+ from typing import Any
14
14
 
15
15
  import matplotlib.pyplot as plt
16
16
  import matplotlib.colors as mcolors
@@ -22,24 +22,22 @@ from job_shop_lib.graphs import NodeType, JobShopGraph, Node
22
22
  def plot_resource_task_graph(
23
23
  job_shop_graph: JobShopGraph,
24
24
  *,
25
- title: Optional[str] = None,
26
- figsize: Tuple[int, int] = (10, 10),
27
- layout: Optional[Dict[Node, Tuple[float, float]]] = None,
25
+ title: str | None = None,
26
+ figsize: tuple[int, int] = (10, 10),
27
+ layout: dict[Node, tuple[float, float]] | None = None,
28
28
  node_size: int = 1200,
29
29
  node_font_color: str = "k",
30
30
  font_size: int = 10,
31
31
  alpha: float = 0.95,
32
32
  add_legend: bool = False,
33
- node_shapes: Optional[Dict[str, str]] = None,
34
- node_color_map: Optional[
35
- Callable[[Node], Tuple[float, float, float, float]]
36
- ] = None,
37
- default_node_color: Union[
38
- str, Tuple[float, float, float, float]
39
- ] = "lightblue",
33
+ node_shapes: dict[str, str] | None = None,
34
+ node_color_map: (
35
+ Callable[[Node], tuple[float, float, float, float]] | None
36
+ ) = None,
37
+ default_node_color: str | tuple[float, float, float, float] = "lightblue",
40
38
  machine_color_map_name: str = "tab10",
41
39
  legend_text: str = "$p_{ij}$ = duration of $O_{ij}$",
42
- edge_additional_params: Optional[Dict[str, Any]] = None,
40
+ edge_additional_params: dict[str, Any] | None = None,
43
41
  draw_only_one_edge: bool = False,
44
42
  ) -> plt.Figure:
45
43
  """Returns a plot of the hetereogeneous graph of the instance.
@@ -203,17 +201,17 @@ def _get_node_label(node: Node) -> str:
203
201
 
204
202
 
205
203
  def _color_to_rgba(
206
- color: Union[str, Tuple[float, float, float, float]]
207
- ) -> Tuple[float, float, float, float]:
204
+ color: str | tuple[float, float, float, float],
205
+ ) -> tuple[float, float, float, float]:
208
206
  if isinstance(color, str):
209
207
  return mcolors.to_rgba(color)
210
208
  return color
211
209
 
212
210
 
213
211
  def color_nodes_by_machine(
214
- machine_colors: Dict[int, Tuple[float, float, float, float]],
215
- default_color: Union[str, Tuple[float, float, float, float]],
216
- ) -> Callable[[Node], Tuple[float, float, float, float]]:
212
+ machine_colors: dict[int, tuple[float, float, float, float]],
213
+ default_color: str | tuple[float, float, float, float],
214
+ ) -> Callable[[Node], tuple[float, float, float, float]]:
217
215
  """Returns a function that assigns a color to a node based on its type.
218
216
 
219
217
  The function returns a color based on the node type. If the node is an
@@ -234,7 +232,7 @@ def color_nodes_by_machine(
234
232
  values of the color to use in the plot.
235
233
  """
236
234
 
237
- def _get_node_color(node: Node) -> Tuple[float, float, float, float]:
235
+ def _get_node_color(node: Node) -> tuple[float, float, float, float]:
238
236
  if node.node_type == NodeType.OPERATION:
239
237
  return machine_colors[node.operation.machine_id]
240
238
  if node.node_type == NodeType.MACHINE:
@@ -252,7 +250,7 @@ def three_columns_layout(
252
250
  rightmost_position: float = 0.9,
253
251
  topmost_position: float = 1.0,
254
252
  bottommost_position: float = 0.0,
255
- ) -> Dict[Node, Tuple[float, float]]:
253
+ ) -> dict[Node, tuple[float, float]]:
256
254
  """Generates coordinates for a three-column grid layout.
257
255
 
258
256
  1. Left column: Machine nodes (M1, M2, etc.)
@@ -321,7 +319,7 @@ def three_columns_layout(
321
319
  total_positions = len(operation_nodes) + len(global_nodes) * 2
322
320
  y_spacing = (topmost_position - bottommost_position) / total_positions
323
321
 
324
- layout: Dict[Node, Tuple[float, float]] = {}
322
+ layout: dict[Node, tuple[float, float]] = {}
325
323
 
326
324
  machines_spacing_multiplier = len(operation_nodes) // len(machine_nodes)
327
325
  layout.update(
@@ -364,7 +362,7 @@ def three_columns_layout(
364
362
 
365
363
  def _get_x_positions(
366
364
  leftmost_position: float, rightmost_position: float
367
- ) -> Dict[str, float]:
365
+ ) -> dict[str, float]:
368
366
  center_position = (
369
367
  leftmost_position + (rightmost_position - leftmost_position) / 2
370
368
  )
@@ -376,12 +374,12 @@ def _get_x_positions(
376
374
 
377
375
 
378
376
  def _assign_positions_from_top(
379
- nodes: List[Node],
377
+ nodes: list[Node],
380
378
  x: float,
381
379
  top: float,
382
380
  y_spacing: float,
383
- ) -> Dict[Node, Tuple[float, float]]:
384
- layout: Dict[Node, Tuple[float, float]] = {}
381
+ ) -> dict[Node, tuple[float, float]]:
382
+ layout: dict[Node, tuple[float, float]] = {}
385
383
  for i, node in enumerate(nodes):
386
384
  y = top - (i + 1) * y_spacing
387
385
  layout[node] = (x, y)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: job-shop-lib
3
- Version: 1.0.2
3
+ Version: 1.0.3
4
4
  Summary: An easy-to-use and modular Python library for the Job Shop Scheduling Problem (JSSP)
5
5
  License: MIT
6
6
  Author: Pabloo22