job-shop-lib 1.0.0a5__py3-none-any.whl → 1.0.0b2__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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