job-shop-lib 1.0.0a5__py3-none-any.whl → 1.0.0b2__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 (55) 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/__init__.py +4 -3
  8. job_shop_lib/dispatching/_dispatcher.py +19 -19
  9. job_shop_lib/dispatching/_dispatcher_observer_config.py +4 -4
  10. job_shop_lib/dispatching/_factories.py +4 -2
  11. job_shop_lib/dispatching/_history_observer.py +2 -1
  12. job_shop_lib/dispatching/_optimal_operations_observer.py +115 -0
  13. job_shop_lib/dispatching/_ready_operation_filters.py +19 -18
  14. job_shop_lib/dispatching/_unscheduled_operations_observer.py +4 -3
  15. job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py +7 -8
  16. job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py +3 -1
  17. job_shop_lib/dispatching/feature_observers/_factory.py +13 -14
  18. job_shop_lib/dispatching/feature_observers/_feature_observer.py +9 -8
  19. job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +2 -1
  20. job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +4 -2
  21. job_shop_lib/dispatching/rules/__init__.py +37 -1
  22. job_shop_lib/dispatching/rules/_dispatching_rule_factory.py +4 -2
  23. job_shop_lib/dispatching/rules/_dispatching_rule_solver.py +50 -20
  24. job_shop_lib/dispatching/rules/_dispatching_rules_functions.py +9 -8
  25. job_shop_lib/dispatching/rules/_machine_chooser_factory.py +4 -3
  26. job_shop_lib/dispatching/rules/_utils.py +9 -8
  27. job_shop_lib/generation/__init__.py +8 -0
  28. job_shop_lib/generation/_general_instance_generator.py +42 -64
  29. job_shop_lib/generation/_instance_generator.py +11 -7
  30. job_shop_lib/generation/_transformations.py +5 -4
  31. job_shop_lib/generation/_utils.py +124 -0
  32. job_shop_lib/graphs/__init__.py +7 -7
  33. job_shop_lib/graphs/{_build_agent_task_graph.py → _build_resource_task_graphs.py} +26 -24
  34. job_shop_lib/graphs/_job_shop_graph.py +17 -13
  35. job_shop_lib/graphs/_node.py +6 -4
  36. job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +4 -2
  37. job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +40 -20
  38. job_shop_lib/reinforcement_learning/_reward_observers.py +3 -1
  39. job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +89 -22
  40. job_shop_lib/reinforcement_learning/_types_and_constants.py +1 -1
  41. job_shop_lib/reinforcement_learning/_utils.py +3 -3
  42. job_shop_lib/visualization/__init__.py +0 -60
  43. job_shop_lib/visualization/gantt/__init__.py +48 -0
  44. job_shop_lib/visualization/{_gantt_chart_creator.py → gantt/_gantt_chart_creator.py} +12 -12
  45. job_shop_lib/visualization/{_gantt_chart_video_and_gif_creation.py → gantt/_gantt_chart_video_and_gif_creation.py} +22 -22
  46. job_shop_lib/visualization/{_plot_gantt_chart.py → gantt/_plot_gantt_chart.py} +12 -13
  47. job_shop_lib/visualization/graphs/__init__.py +29 -0
  48. job_shop_lib/visualization/{_plot_disjunctive_graph.py → graphs/_plot_disjunctive_graph.py} +18 -16
  49. job_shop_lib/visualization/graphs/_plot_resource_task_graph.py +389 -0
  50. {job_shop_lib-1.0.0a5.dist-info → job_shop_lib-1.0.0b2.dist-info}/METADATA +21 -15
  51. job_shop_lib-1.0.0b2.dist-info/RECORD +70 -0
  52. job_shop_lib/visualization/_plot_agent_task_graph.py +0 -276
  53. job_shop_lib-1.0.0a5.dist-info/RECORD +0 -66
  54. {job_shop_lib-1.0.0a5.dist-info → job_shop_lib-1.0.0b2.dist-info}/LICENSE +0 -0
  55. {job_shop_lib-1.0.0a5.dist-info → job_shop_lib-1.0.0b2.dist-info}/WHEEL +0 -0
@@ -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))
@@ -1,6 +1,6 @@
1
1
  """Module for plotting static Gantt charts for job shop schedules."""
2
2
 
3
- from typing import Optional
3
+ from typing import Optional, List, Tuple, Dict
4
4
 
5
5
  from matplotlib.figure import Figure
6
6
  import matplotlib.pyplot as plt
@@ -9,23 +9,22 @@ from matplotlib.patches import Patch
9
9
 
10
10
  from job_shop_lib import Schedule, ScheduledOperation
11
11
 
12
-
13
12
  _BASE_Y_POSITION = 1
14
13
  _Y_POSITION_INCREMENT = 10
15
14
 
16
15
 
17
16
  def plot_gantt_chart(
18
17
  schedule: Schedule,
19
- title: str | None = None,
18
+ title: Optional[str] = None,
20
19
  cmap_name: str = "viridis",
21
- xlim: int | None = None,
20
+ xlim: Optional[int] = None,
22
21
  number_of_x_ticks: int = 15,
23
- job_labels: None | list[str] = None,
24
- machine_labels: None | list[str] = None,
22
+ job_labels: Optional[List[str]] = None,
23
+ machine_labels: Optional[List[str]] = None,
25
24
  legend_title: str = "",
26
25
  x_label: str = "Time units",
27
26
  y_label: str = "Machines",
28
- ) -> tuple[Figure, plt.Axes]:
27
+ ) -> Tuple[Figure, plt.Axes]:
29
28
  """Plots a Gantt chart for the schedule.
30
29
 
31
30
  This function generates a Gantt chart that visualizes the schedule of jobs
@@ -91,10 +90,10 @@ def plot_gantt_chart(
91
90
 
92
91
  def _initialize_plot(
93
92
  schedule: Schedule,
94
- title: str | None,
93
+ title: Optional[str],
95
94
  x_label: str = "Time units",
96
95
  y_label: str = "Machines",
97
- ) -> tuple[Figure, plt.Axes]:
96
+ ) -> Tuple[Figure, plt.Axes]:
98
97
  """Initializes the plot."""
99
98
  fig, ax = plt.subplots()
100
99
  ax.set_xlabel(x_label)
@@ -111,8 +110,8 @@ def _plot_machine_schedules(
111
110
  schedule: Schedule,
112
111
  ax: plt.Axes,
113
112
  cmap_name: str,
114
- job_labels: list[str] | None,
115
- ) -> dict[int, Patch]:
113
+ job_labels: Optional[List[str]],
114
+ ) -> Dict[int, Patch]:
116
115
  """Plots the schedules for each machine."""
117
116
  max_job_id = schedule.instance.num_jobs - 1
118
117
  cmap = plt.get_cmap(cmap_name, max_job_id + 1)
@@ -138,7 +137,7 @@ def _plot_machine_schedules(
138
137
  return legend_handles
139
138
 
140
139
 
141
- def _get_job_label(job_labels: list[str] | None, job_id: int) -> str:
140
+ def _get_job_label(job_labels: Optional[List[str]], job_id: int) -> str:
142
141
  """Returns the label for the job."""
143
142
  if job_labels is None:
144
143
  return f"Job {job_id}"
@@ -162,7 +161,7 @@ def _plot_scheduled_operation(
162
161
 
163
162
 
164
163
  def _configure_legend(
165
- ax: plt.Axes, legend_handles: dict[int, Patch], legend_title: str
164
+ ax: plt.Axes, legend_handles: Dict[int, Patch], legend_title: str
166
165
  ):
167
166
  """Configures the legend for the plot."""
168
167
  sorted_legend_handles = [
@@ -0,0 +1,29 @@
1
+ """Contains functions and classes for visualizing job shop scheduling problems.
2
+
3
+ .. autosummary::
4
+
5
+ plot_disjunctive_graph
6
+ plot_resource_task_graph
7
+ three_columns_layout
8
+ duration_labeler
9
+ color_nodes_by_machine
10
+
11
+ """
12
+
13
+ from ._plot_disjunctive_graph import (
14
+ plot_disjunctive_graph,
15
+ duration_labeler,
16
+ )
17
+ from ._plot_resource_task_graph import (
18
+ plot_resource_task_graph,
19
+ three_columns_layout,
20
+ color_nodes_by_machine,
21
+ )
22
+
23
+ __all__ = [
24
+ "plot_disjunctive_graph",
25
+ "plot_resource_task_graph",
26
+ "three_columns_layout",
27
+ "duration_labeler",
28
+ "color_nodes_by_machine",
29
+ ]
@@ -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
4
+ from typing import Any, Optional, Tuple, Dict, Union
5
5
  from collections.abc import Callable, Sequence, Iterable
6
6
  import warnings
7
7
  import copy
@@ -22,7 +22,7 @@ from job_shop_lib.graphs import (
22
22
  from job_shop_lib.exceptions import ValidationError
23
23
 
24
24
 
25
- Layout = Callable[[nx.Graph], dict[str, tuple[float, float]]]
25
+ Layout = Callable[[nx.Graph], Dict[str, Tuple[float, float]]]
26
26
 
27
27
 
28
28
  def duration_labeler(node: Node) -> str:
@@ -50,15 +50,15 @@ def duration_labeler(node: Node) -> str:
50
50
  # For the "too many arguments" warning no satisfactory solution was
51
51
  # found. I believe is still better than using `**kwargs` and losing the
52
52
  # function signature or adding a dataclass for configuration (it would add
53
- # unnecessary complexity). A TypedDict could be used too, but the default
53
+ # more complexity). A TypedDict could be used too, but the default
54
54
  # values would not be explicit.
55
55
  # pylint: disable=too-many-arguments, too-many-locals, too-many-statements
56
56
  # pylint: disable=too-many-branches, line-too-long
57
57
  def plot_disjunctive_graph(
58
- job_shop: JobShopGraph | JobShopInstance,
58
+ job_shop: Union[JobShopGraph, JobShopInstance],
59
59
  *,
60
- title: str | None = None,
61
- figsize: tuple[float, float] = (6, 4),
60
+ title: Optional[str] = None,
61
+ figsize: Tuple[float, float] = (6, 4),
62
62
  node_size: int = 1600,
63
63
  edge_width: int = 2,
64
64
  font_size: int = 10,
@@ -69,21 +69,21 @@ def plot_disjunctive_graph(
69
69
  color_map: str = "Dark2_r",
70
70
  disjunctive_edge_color: str = "red",
71
71
  conjunctive_edge_color: str = "black",
72
- layout: Layout | None = None,
73
- draw_disjunctive_edges: bool | str = True,
74
- conjunctive_edges_additional_params: dict[str, Any] | None = None,
75
- disjunctive_edges_additional_params: dict[str, Any] | None = None,
72
+ layout: Optional[Layout] = None,
73
+ draw_disjunctive_edges: Union[bool, str] = True,
74
+ conjunctive_edges_additional_params: Optional[Dict[str, Any]] = None,
75
+ disjunctive_edges_additional_params: Optional[Dict[str, Any]] = None,
76
76
  conjunctive_patch_label: str = "Conjunctive edges",
77
77
  disjunctive_patch_label: str = "Disjunctive edges",
78
78
  legend_text: str = "$p_{ij}=$duration of $O_{ij}$",
79
79
  show_machine_colors_in_legend: bool = True,
80
- machine_labels: Sequence[str] | None = None,
80
+ machine_labels: Optional[Sequence[str]] = None,
81
81
  legend_location: str = "upper left",
82
- legend_bbox_to_anchor: tuple[float, float] = (1.01, 1),
82
+ legend_bbox_to_anchor: Tuple[float, float] = (1.01, 1),
83
83
  start_node_label: str = "$S$",
84
84
  end_node_label: str = "$T$",
85
85
  font_family: str = "sans-serif",
86
- ) -> tuple[plt.Figure, plt.Axes]:
86
+ ) -> Tuple[plt.Figure, plt.Axes]:
87
87
  r"""Plots the disjunctive graph of the given job shop instance or graph.
88
88
 
89
89
  Args:
@@ -251,7 +251,7 @@ def plot_disjunctive_graph(
251
251
  for u, v, d in job_shop_graph.graph.edges(data=True)
252
252
  if d["type"] == EdgeType.CONJUNCTIVE
253
253
  ]
254
- disjunctive_edges: Iterable[tuple[int, int]] = [
254
+ disjunctive_edges: Iterable[Tuple[int, int]] = [
255
255
  (u, v)
256
256
  for u, v, d in job_shop_graph.graph.edges(data=True)
257
257
  if d["type"] == EdgeType.DISJUNCTIVE
@@ -299,7 +299,7 @@ def plot_disjunctive_graph(
299
299
 
300
300
  sink_node = job_shop_graph.nodes_by_type[NodeType.SINK][0]
301
301
  labels[sink_node] = end_node_label
302
- machine_colors: dict[int, tuple[float, float, float, float]] = {}
302
+ machine_colors: dict[int, Tuple[float, float, float, float]] = {}
303
303
  for operation_node in operation_nodes:
304
304
  if job_shop_graph.is_removed(operation_node.node_id):
305
305
  continue
@@ -343,7 +343,9 @@ def plot_disjunctive_graph(
343
343
  else f"Machine {machine_id}"
344
344
  ),
345
345
  )
346
- for machine_id, color in machine_colors.items()
346
+ for machine_id, color in sorted(
347
+ machine_colors.items(), key=lambda x: x[0]
348
+ )
347
349
  ]
348
350
  handles.extend(machine_patches)
349
351