job-shop-lib 0.5.0__py3-none-any.whl → 1.0.0__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 (93) hide show
  1. job_shop_lib/__init__.py +19 -8
  2. job_shop_lib/{base_solver.py → _base_solver.py} +1 -1
  3. job_shop_lib/{job_shop_instance.py → _job_shop_instance.py} +155 -81
  4. job_shop_lib/_operation.py +118 -0
  5. job_shop_lib/{schedule.py → _schedule.py} +102 -84
  6. job_shop_lib/{scheduled_operation.py → _scheduled_operation.py} +25 -49
  7. job_shop_lib/benchmarking/__init__.py +66 -43
  8. job_shop_lib/benchmarking/_load_benchmark.py +88 -0
  9. job_shop_lib/constraint_programming/__init__.py +13 -0
  10. job_shop_lib/{cp_sat/ortools_solver.py → constraint_programming/_ortools_solver.py} +77 -22
  11. job_shop_lib/dispatching/__init__.py +51 -42
  12. job_shop_lib/dispatching/{dispatcher.py → _dispatcher.py} +223 -130
  13. job_shop_lib/dispatching/_dispatcher_observer_config.py +67 -0
  14. job_shop_lib/dispatching/_factories.py +135 -0
  15. job_shop_lib/dispatching/{history_tracker.py → _history_observer.py} +6 -7
  16. job_shop_lib/dispatching/_optimal_operations_observer.py +113 -0
  17. job_shop_lib/dispatching/_ready_operation_filters.py +168 -0
  18. job_shop_lib/dispatching/_unscheduled_operations_observer.py +70 -0
  19. job_shop_lib/dispatching/feature_observers/__init__.py +51 -13
  20. job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py +212 -0
  21. job_shop_lib/dispatching/feature_observers/{duration_observer.py → _duration_observer.py} +20 -18
  22. job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py +289 -0
  23. job_shop_lib/dispatching/feature_observers/_factory.py +95 -0
  24. job_shop_lib/dispatching/feature_observers/_feature_observer.py +228 -0
  25. job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +97 -0
  26. job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +35 -0
  27. job_shop_lib/dispatching/feature_observers/{is_scheduled_observer.py → _is_scheduled_observer.py} +9 -5
  28. job_shop_lib/dispatching/feature_observers/{position_in_job_observer.py → _position_in_job_observer.py} +8 -10
  29. job_shop_lib/dispatching/feature_observers/{remaining_operations_observer.py → _remaining_operations_observer.py} +8 -26
  30. job_shop_lib/dispatching/rules/__init__.py +87 -0
  31. job_shop_lib/dispatching/rules/_dispatching_rule_factory.py +84 -0
  32. job_shop_lib/dispatching/rules/_dispatching_rule_solver.py +201 -0
  33. job_shop_lib/dispatching/{dispatching_rules.py → rules/_dispatching_rules_functions.py} +70 -16
  34. job_shop_lib/dispatching/rules/_machine_chooser_factory.py +71 -0
  35. job_shop_lib/dispatching/rules/_utils.py +128 -0
  36. job_shop_lib/exceptions.py +18 -0
  37. job_shop_lib/generation/__init__.py +19 -0
  38. job_shop_lib/generation/_general_instance_generator.py +165 -0
  39. job_shop_lib/generation/_instance_generator.py +133 -0
  40. job_shop_lib/{generators/transformations.py → generation/_transformations.py} +16 -12
  41. job_shop_lib/generation/_utils.py +124 -0
  42. job_shop_lib/graphs/__init__.py +30 -12
  43. job_shop_lib/graphs/{build_disjunctive_graph.py → _build_disjunctive_graph.py} +41 -3
  44. job_shop_lib/graphs/{build_agent_task_graph.py → _build_resource_task_graphs.py} +28 -26
  45. job_shop_lib/graphs/_constants.py +38 -0
  46. job_shop_lib/graphs/_job_shop_graph.py +320 -0
  47. job_shop_lib/graphs/_node.py +182 -0
  48. job_shop_lib/graphs/graph_updaters/__init__.py +26 -0
  49. job_shop_lib/graphs/graph_updaters/_disjunctive_graph_updater.py +108 -0
  50. job_shop_lib/graphs/graph_updaters/_graph_updater.py +57 -0
  51. job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +155 -0
  52. job_shop_lib/graphs/graph_updaters/_utils.py +25 -0
  53. job_shop_lib/py.typed +0 -0
  54. job_shop_lib/reinforcement_learning/__init__.py +68 -0
  55. job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +398 -0
  56. job_shop_lib/reinforcement_learning/_resource_task_graph_observation.py +329 -0
  57. job_shop_lib/reinforcement_learning/_reward_observers.py +87 -0
  58. job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +443 -0
  59. job_shop_lib/reinforcement_learning/_types_and_constants.py +62 -0
  60. job_shop_lib/reinforcement_learning/_utils.py +199 -0
  61. job_shop_lib/visualization/__init__.py +0 -25
  62. job_shop_lib/visualization/gantt/__init__.py +48 -0
  63. job_shop_lib/visualization/gantt/_gantt_chart_creator.py +257 -0
  64. job_shop_lib/visualization/gantt/_gantt_chart_video_and_gif_creation.py +422 -0
  65. job_shop_lib/visualization/{gantt_chart.py → gantt/_plot_gantt_chart.py} +84 -21
  66. job_shop_lib/visualization/graphs/__init__.py +29 -0
  67. job_shop_lib/visualization/graphs/_plot_disjunctive_graph.py +418 -0
  68. job_shop_lib/visualization/graphs/_plot_resource_task_graph.py +389 -0
  69. {job_shop_lib-0.5.0.dist-info → job_shop_lib-1.0.0.dist-info}/METADATA +87 -55
  70. job_shop_lib-1.0.0.dist-info/RECORD +73 -0
  71. {job_shop_lib-0.5.0.dist-info → job_shop_lib-1.0.0.dist-info}/WHEEL +1 -1
  72. job_shop_lib/benchmarking/load_benchmark.py +0 -142
  73. job_shop_lib/cp_sat/__init__.py +0 -5
  74. job_shop_lib/dispatching/dispatching_rule_solver.py +0 -119
  75. job_shop_lib/dispatching/factories.py +0 -206
  76. job_shop_lib/dispatching/feature_observers/composite_feature_observer.py +0 -87
  77. job_shop_lib/dispatching/feature_observers/earliest_start_time_observer.py +0 -156
  78. job_shop_lib/dispatching/feature_observers/factory.py +0 -58
  79. job_shop_lib/dispatching/feature_observers/feature_observer.py +0 -113
  80. job_shop_lib/dispatching/feature_observers/is_completed_observer.py +0 -98
  81. job_shop_lib/dispatching/feature_observers/is_ready_observer.py +0 -40
  82. job_shop_lib/dispatching/pruning_functions.py +0 -116
  83. job_shop_lib/generators/__init__.py +0 -7
  84. job_shop_lib/generators/basic_generator.py +0 -197
  85. job_shop_lib/graphs/constants.py +0 -21
  86. job_shop_lib/graphs/job_shop_graph.py +0 -202
  87. job_shop_lib/graphs/node.py +0 -166
  88. job_shop_lib/operation.py +0 -122
  89. job_shop_lib/visualization/agent_task_graph.py +0 -257
  90. job_shop_lib/visualization/create_gif.py +0 -209
  91. job_shop_lib/visualization/disjunctive_graph.py +0 -210
  92. job_shop_lib-0.5.0.dist-info/RECORD +0 -48
  93. {job_shop_lib-0.5.0.dist-info → job_shop_lib-1.0.0.dist-info}/LICENSE +0 -0
@@ -0,0 +1,389 @@
1
+ """Contains functions to plot a resource-task graph representation of a job
2
+ shop instance.
3
+
4
+ It was introduced by Junyoung Park et al. (2021).
5
+ In contrast to the disjunctive graph, instead of connecting operations that
6
+ share the same resources directly by disjunctive edges, operation nodes are
7
+ connected with machine ones. All machine nodes are connected between them, and
8
+ all operation nodes from the same job are connected by non-directed edges too.
9
+ """
10
+
11
+ from collections.abc import Callable
12
+ from copy import deepcopy
13
+ from typing import Optional, Any, Tuple, Dict, Union, List
14
+
15
+ import matplotlib.pyplot as plt
16
+ import matplotlib.colors as mcolors
17
+ import networkx as nx
18
+
19
+ from job_shop_lib.graphs import NodeType, JobShopGraph, Node
20
+
21
+
22
+ def plot_resource_task_graph(
23
+ job_shop_graph: JobShopGraph,
24
+ *,
25
+ title: Optional[str] = None,
26
+ figsize: Tuple[int, int] = (10, 10),
27
+ layout: Optional[Dict[Node, Tuple[float, float]]] = None,
28
+ node_size: int = 1200,
29
+ node_font_color: str = "k",
30
+ font_size: int = 10,
31
+ alpha: float = 0.95,
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",
40
+ machine_color_map_name: str = "tab10",
41
+ legend_text: str = "$p_{ij}$ = duration of $O_{ij}$",
42
+ edge_additional_params: Optional[Dict[str, Any]] = None,
43
+ draw_only_one_edge: bool = False,
44
+ ) -> plt.Figure:
45
+ """Returns a plot of the hetereogeneous graph of the instance.
46
+
47
+ Machine and job nodes are represented by squares, and the operation nodes
48
+ are represented by circles.
49
+
50
+ Args:
51
+ job_shop_graph:
52
+ The job shop graph instance.
53
+ title:
54
+ The title of the plot. If ``None``, the title "Heterogeneous Graph
55
+ Visualization: {instance_name}" is used. The default is ``None``.
56
+ figsize:
57
+ The size of the figure. It should be a tuple with the width and
58
+ height in inches. The default is ``(10, 10)``.
59
+ layout:
60
+ A dictionary with the position of each node in the graph. The keys
61
+ are the node ids, and the values are tuples with the x and y
62
+ coordinates. If ``None``, the :func:`three_columns_layout` function
63
+ is used. The default is ``None``.
64
+ node_size:
65
+ The size of the nodes. The default is 1000.
66
+ alpha:
67
+ The transparency of the nodes. It should be a float between 0 and
68
+ 1. The default is 0.95.
69
+ add_legend:
70
+ Whether to add a legend with the meaning of the colors and shapes.
71
+ The default is ``False``.
72
+ node_shapes:
73
+ A dictionary with the shapes of the nodes. The keys are the node
74
+ types, and the values are the shapes. The default is
75
+ ``{"machine": "s", "job": "d", "operation": "o", "global": "o"}``.
76
+ node_color_map:
77
+ A function that receives a node and returns a tuple with the RGBA
78
+ values of the color to use in the plot. If ``None``,
79
+ :func:`color_nodes_by_machine` is used.
80
+ machine_color_map_name:
81
+ The name of the colormap to use for the machines. This argument is
82
+ only used if ``node_color_map`` is ``None``. The default is
83
+ ``"tab10"``.
84
+
85
+ Returns:
86
+ The figure of the plot. This figure can be used to save the plot to a
87
+ file or to show it in a Jupyter notebook.
88
+ """
89
+ if title is None:
90
+ title = (
91
+ "Heterogeneous Graph Visualization:"
92
+ f"{job_shop_graph.instance.name}"
93
+ )
94
+ # Create a new figure and axis
95
+ fig, ax = plt.subplots(figsize=figsize)
96
+ fig.suptitle(title)
97
+
98
+ # Create the networkx graph
99
+ graph = job_shop_graph.graph
100
+ nodes = job_shop_graph.non_removed_nodes()
101
+
102
+ # Create the layout if it was not provided
103
+ if layout is None:
104
+ layout = three_columns_layout(job_shop_graph)
105
+
106
+ # Define colors and shapes
107
+ color_map = plt.get_cmap(machine_color_map_name)
108
+ machine_colors = {
109
+ machine.machine_id: color_map(i)
110
+ for i, machine in enumerate(
111
+ job_shop_graph.nodes_by_type[NodeType.MACHINE]
112
+ )
113
+ }
114
+ node_color_map = (
115
+ color_nodes_by_machine(machine_colors, default_node_color)
116
+ if node_color_map is None
117
+ else node_color_map
118
+ )
119
+ node_colors = [
120
+ node_color_map(node) for node in job_shop_graph.nodes
121
+ ] # We need to get the color of all nodes to avoid an index error
122
+ if node_shapes is None:
123
+ node_shapes = {
124
+ "machine": "s",
125
+ "job": "d",
126
+ "operation": "o",
127
+ "global": "o",
128
+ }
129
+
130
+ # Draw nodes with different shapes based on their type
131
+ for node_type, shape in node_shapes.items():
132
+ current_nodes = [
133
+ node.node_id
134
+ for node in nodes
135
+ if node.node_type.name.lower() == node_type
136
+ ]
137
+ nx.draw_networkx_nodes(
138
+ graph,
139
+ layout,
140
+ nodelist=current_nodes,
141
+ node_color=[node_colors[i] for i in current_nodes],
142
+ node_shape=shape,
143
+ ax=ax,
144
+ node_size=node_size,
145
+ alpha=alpha,
146
+ )
147
+
148
+ # Draw edges
149
+ if edge_additional_params is None:
150
+ edge_additional_params = {}
151
+ if draw_only_one_edge:
152
+ graph = deepcopy(graph)
153
+ edges = list(graph.edges)
154
+ present_edges = set()
155
+ for edge in edges:
156
+ unorder_edge = frozenset(edge)
157
+ if unorder_edge in present_edges:
158
+ graph.remove_edge(*edge)
159
+ else:
160
+ present_edges.add(unorder_edge)
161
+
162
+ nx.draw_networkx_edges(graph, layout, ax=ax, **edge_additional_params)
163
+
164
+ node_color_map = (
165
+ color_nodes_by_machine(machine_colors, "lightblue")
166
+ if node_color_map is None
167
+ else node_color_map
168
+ )
169
+ node_labels = {node.node_id: _get_node_label(node) for node in nodes}
170
+ nx.draw_networkx_labels(
171
+ graph,
172
+ layout,
173
+ node_labels,
174
+ ax=ax,
175
+ font_size=font_size,
176
+ font_color=node_font_color,
177
+ )
178
+
179
+ ax.set_axis_off()
180
+
181
+ plt.tight_layout()
182
+
183
+ # Add to the legend the meaning of m and d
184
+ if add_legend:
185
+ plt.figtext(0, 0.95, legend_text, wrap=True, fontsize=12)
186
+ return fig
187
+
188
+
189
+ def _get_node_label(node: Node) -> str:
190
+ if node.node_type == NodeType.OPERATION:
191
+ i = node.operation.job_id
192
+ j = node.operation.position_in_job
193
+ ij = str(i) + str(j)
194
+ return f"$p_{{{ij}}}={node.operation.duration}$"
195
+ if node.node_type == NodeType.MACHINE:
196
+ return f"$M_{node.machine_id}$"
197
+ if node.node_type == NodeType.JOB:
198
+ return f"$J_{node.job_id}$"
199
+ if node.node_type == NodeType.GLOBAL:
200
+ return "$G$"
201
+
202
+ raise ValueError(f"Invalid node type: {node.node_type}")
203
+
204
+
205
+ def _color_to_rgba(
206
+ color: Union[str, Tuple[float, float, float, float]]
207
+ ) -> Tuple[float, float, float, float]:
208
+ if isinstance(color, str):
209
+ return mcolors.to_rgba(color)
210
+ return color
211
+
212
+
213
+ 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]]:
217
+ """Returns a function that assigns a color to a node based on its type.
218
+
219
+ The function returns a color based on the node type. If the node is an
220
+ operation, the color is based on the machine it is assigned to. If the node
221
+ is a machine, the color is based on the machine id. If the node is a job or
222
+ global node, the color is the default color.
223
+
224
+ Args:
225
+ machine_colors:
226
+ A dictionary with the colors of each machine. The keys are the
227
+ machine ids, and the values are tuples with the RGBA values.
228
+ default_color:
229
+ The default color to use for job and global nodes. It can be a
230
+ string with a color name or a tuple with the RGBA values.
231
+
232
+ Returns:
233
+ A function that receives a node and returns a tuple with the RGBA
234
+ values of the color to use in the plot.
235
+ """
236
+
237
+ def _get_node_color(node: Node) -> Tuple[float, float, float, float]:
238
+ if node.node_type == NodeType.OPERATION:
239
+ return machine_colors[node.operation.machine_id]
240
+ if node.node_type == NodeType.MACHINE:
241
+ return machine_colors[node.machine_id]
242
+
243
+ return _color_to_rgba(default_color)
244
+
245
+ return _get_node_color
246
+
247
+
248
+ def three_columns_layout(
249
+ job_shop_graph: JobShopGraph,
250
+ *,
251
+ leftmost_position: float = 0.1,
252
+ rightmost_position: float = 0.9,
253
+ topmost_position: float = 1.0,
254
+ bottommost_position: float = 0.0,
255
+ ) -> Dict[Node, Tuple[float, float]]:
256
+ """Generates coordinates for a three-column grid layout.
257
+
258
+ 1. Left column: Machine nodes (M1, M2, etc.)
259
+ 2. Middle column: Operation nodes (O_ij where i=job, j=operation)
260
+ 3. Right column: Job nodes (J1, J2, etc.)
261
+
262
+ The operations are arranged vertically in groups by job, with a global
263
+ node (G) at the bottom.
264
+
265
+ For example, in a 2-machine, 3-job problem:
266
+
267
+ - Machine nodes (M1, M2) appear in the left column where needed
268
+ - Operation nodes (O_11 through O_33) form the central column
269
+ - Job nodes (J1, J2, J3) appear in the right column at the middle of their
270
+ respective operations
271
+ - The global node (G) appears at the bottom of the middle column
272
+
273
+ Args:
274
+ job_shop_graph:
275
+ The job shop graph instance. It should be already initialized with
276
+ the instance with a valid agent-task graph representation.
277
+ leftmost_position:
278
+ The center position of the leftmost column of the layout. It should
279
+ be a float between 0 and 1. The default is 0.1.
280
+ rightmost_position:
281
+ The center position of the rightmost column of the layout. It
282
+ should be a float between 0 and 1. The default is 0.9.
283
+ topmost_position:
284
+ The center position of the topmost node of the layout. It should be
285
+ a float between 0 and 1. The default is 0.9.
286
+ bottommost_position:
287
+ The center position of the bottommost node of the layout. It should
288
+ be a float between 0 and 1. The default is 0.1.
289
+
290
+ Returns:
291
+ A dictionary with the position of each node in the graph. The keys are
292
+ the node ids, and the values are tuples with the x and y coordinates.
293
+ """
294
+
295
+ x_positions = _get_x_positions(leftmost_position, rightmost_position)
296
+
297
+ operation_nodes = [
298
+ node
299
+ for node in job_shop_graph.nodes_by_type[NodeType.OPERATION]
300
+ if not job_shop_graph.is_removed(node)
301
+ ]
302
+ machine_nodes = [
303
+ node
304
+ for node in job_shop_graph.nodes_by_type[NodeType.MACHINE]
305
+ if not job_shop_graph.is_removed(node)
306
+ ]
307
+ job_nodes = [
308
+ node
309
+ for node in job_shop_graph.nodes_by_type[NodeType.JOB]
310
+ if not job_shop_graph.is_removed(node)
311
+ ]
312
+ global_nodes = [
313
+ node
314
+ for node in job_shop_graph.nodes_by_type[NodeType.GLOBAL]
315
+ if not job_shop_graph.is_removed(node)
316
+ ]
317
+
318
+ # job_nodes = job_shop_graph.nodes_by_type[NodeType.JOB]
319
+ # global_nodes = job_shop_graph.nodes_by_type[NodeType.GLOBAL]
320
+
321
+ total_positions = len(operation_nodes) + len(global_nodes) * 2
322
+ y_spacing = (topmost_position - bottommost_position) / total_positions
323
+
324
+ layout: Dict[Node, Tuple[float, float]] = {}
325
+
326
+ machines_spacing_multiplier = len(operation_nodes) // len(machine_nodes)
327
+ layout.update(
328
+ _assign_positions_from_top(
329
+ machine_nodes,
330
+ x_positions["machine"],
331
+ topmost_position,
332
+ y_spacing * machines_spacing_multiplier,
333
+ )
334
+ )
335
+ layout.update(
336
+ (
337
+ _assign_positions_from_top(
338
+ operation_nodes,
339
+ x_positions["operation"],
340
+ topmost_position,
341
+ y_spacing,
342
+ )
343
+ )
344
+ )
345
+
346
+ if global_nodes:
347
+ layout[global_nodes[0]] = (
348
+ x_positions["operation"],
349
+ bottommost_position,
350
+ )
351
+
352
+ if job_nodes:
353
+ job_multiplier = len(operation_nodes) // len(job_nodes)
354
+ layout.update(
355
+ _assign_positions_from_top(
356
+ job_nodes,
357
+ x_positions["job"],
358
+ topmost_position,
359
+ y_spacing * job_multiplier,
360
+ )
361
+ )
362
+ return layout
363
+
364
+
365
+ def _get_x_positions(
366
+ leftmost_position: float, rightmost_position: float
367
+ ) -> Dict[str, float]:
368
+ center_position = (
369
+ leftmost_position + (rightmost_position - leftmost_position) / 2
370
+ )
371
+ return {
372
+ "machine": leftmost_position,
373
+ "operation": center_position,
374
+ "job": rightmost_position,
375
+ }
376
+
377
+
378
+ def _assign_positions_from_top(
379
+ nodes: List[Node],
380
+ x: float,
381
+ top: float,
382
+ y_spacing: float,
383
+ ) -> Dict[Node, Tuple[float, float]]:
384
+ layout: Dict[Node, Tuple[float, float]] = {}
385
+ for i, node in enumerate(nodes):
386
+ y = top - (i + 1) * y_spacing
387
+ layout[node] = (x, y)
388
+
389
+ return layout
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: job-shop-lib
3
- Version: 0.5.0
3
+ Version: 1.0.0
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
@@ -12,47 +12,86 @@ Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
14
  Provides-Extra: pygraphviz
15
- Requires-Dist: imageio (>=2,<3)
15
+ Requires-Dist: gymnasium (>=0.29.1,<0.30.0)
16
+ Requires-Dist: imageio[ffmpeg] (>=2.34.1,<3.0.0)
16
17
  Requires-Dist: matplotlib (>=3,<4)
17
18
  Requires-Dist: networkx (>=3,<4)
18
19
  Requires-Dist: numpy (>=1.26.4,<2.0.0)
19
- Requires-Dist: ortools (>=9.9,<9.10)
20
+ Requires-Dist: ortools (>=9.9,<10.0)
20
21
  Requires-Dist: pyarrow (>=15.0.0,<16.0.0)
21
22
  Requires-Dist: pygraphviz (>=1.12,<2.0) ; extra == "pygraphviz"
22
23
  Description-Content-Type: text/markdown
23
24
 
24
25
  <div align="center">
25
26
 
26
- <img src="images/logo_with_transparent_background.png" height="150px">
27
+ <img src="docs/source/images/jslib_minimalist_logo_no_background_fixed.png" height="150px">
27
28
 
28
- <h1>Job Shop Library</h1>
29
+ <h1>JobShopLib</h1>
29
30
 
30
31
  [![Tests](https://github.com/Pabloo22/job_shop_lib/actions/workflows/tests.yaml/badge.svg)](https://github.com/Pabloo22/job_shop_lib/actions/workflows/tests.yaml)
32
+ [![Documentation Status](https://readthedocs.org/projects/job-shop-lib/badge/?version=latest)](https://job-shop-lib.readthedocs.io/en/latest/?badge=latest)
31
33
  ![Python versions](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12-blue)
32
34
  [![Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
33
35
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
34
36
 
35
37
  </div>
36
38
 
37
- An easy-to-use and modular Python library for the Job Shop Scheduling Problem (JSSP) with a special focus on graph representations.
39
+ JobShopLib is a Python package for creating, solving, and visualizing job shop scheduling problems (JSSP).
38
40
 
39
- It provides intuitive data structures to represent instances and solutions, as well as solvers and visualization tools.
41
+ It follows a modular design, allowing users to easily extend the library with new solvers, dispatching rules, visualization functions, etc.
40
42
 
41
- See the [this](https://colab.research.google.com/drive/1XV_Rvq1F2ns6DFG8uNj66q_rcowwTZ4H?usp=sharing) Google Colab notebook for a quick start guide!
43
+ See the [documentation](https://job-shop-lib.readthedocs.io/en/latest/) for more details about the latest version.
42
44
 
43
- ## Installation
45
+ ## Installation :package:
44
46
 
45
- You can install the library from PyPI:
47
+ <!-- start installation -->
48
+
49
+ JobShopLib is distributed on [PyPI](https://pypi.org/project/job-shop-lib/) and it supports Python 3.10+.
50
+
51
+ You can install the latest stable version (version 0.5.1) using `pip`:
46
52
 
47
53
  ```bash
48
54
  pip install job-shop-lib
49
55
  ```
50
56
 
51
- ## Quick Start :rocket:
57
+ See [this](https://colab.research.google.com/drive/1XV_Rvq1F2ns6DFG8uNj66q_rcowwTZ4H?usp=sharing) Google Colab notebook for a quick start guide!
58
+
59
+
60
+ There is a [documentation page](https://job-shop-lib.readthedocs.io/en/latest/) for versions 1.0.0a3 and onward. See see the [latest pull requests](https://github.com/Pabloo22/job_shop_lib/pulls?q=is%3Apr+is%3Aclosed) for the latest changes.
61
+
62
+ <!-- end installation -->
63
+
64
+ <!-- key features -->
65
+
66
+ ## Key Features :star:
67
+
68
+ - **Data Structures**: Easily create, manage, and manipulate job shop instances and solutions with user-friendly data structures. See [Getting Started](https://github.com/Pabloo22/job_shop_lib/blob/main/docs/source/tutorial/00-Getting-Started.ipynb) and [How Solutions are Represented](https://github.com/Pabloo22/job_shop_lib/blob/main/docs/source/tutorial/01-How-Solutions-are-Represented.ipynb).
69
+
70
+ - **Benchmark Instances**: Load well-known benchmark instances directly from the library without manual downloading. See [Load Benchmark Instances](https://github.com/Pabloo22/job_shop_lib/blob/main/docs/source/examples/05-Load-Benchmark-Instances.ipynb).
71
+
72
+ - **Random Instance Generation**: Create random instances with customizable sizes and properties or augment existing ones. See [`generation`](job_shop_lib/generation) package.
73
+
74
+ - **Multiple Solvers**:
75
+ - **Constraint Programming Solver**: OR-Tools' CP-SAT solver. See [Solving the Problem](https://github.com/Pabloo22/job_shop_lib/blob/main/docs/source/tutorial/02-Solving-the-Problem.ipynb).
76
+
77
+ - **Dispatching Rule Solvers**: Use any of the available dispatching rules or create custom ones. See [Dispatching Rules](https://github.com/Pabloo22/job_shop_lib/blob/main/docs/source/examples/03-Dispatching-Rules.ipynb).
78
+
79
+ - **Gantt Charts**: Visualize final schedules and how are they created iteratively by dispatching rule solvers or sequences of scheduling decisions with GIFs or videos. See [Save Gif](https://github.com/Pabloo22/job_shop_lib/blob/main/docs/source/examples/06-Save-Gif.ipynb).
80
+
81
+ - **Graph Representations**:
82
+ - **Disjunctive Graphs**: Represent and visualize instances as disjunctive graphs. See [Disjunctive Graph](https://github.com/Pabloo22/job_shop_lib/blob/main/docs/source/examples/04-Disjunctive-Graph.ipynb).
83
+ - **Agent-Task Graphs**: Encode instances as agent-task graphs (introduced in [ScheduleNet paper](https://arxiv.org/abs/2106.03051)). See [Agent-Task Graph](https://github.com/Pabloo22/job_shop_lib/blob/main/docs/source/examples/07-Agent-Task-Graph.ipynb).
84
+ - Build your own custom graphs with the `JobShopGraph` class.
85
+
86
+ - **Gymnasium Environments**: Two environments for solving the problem with graph neural networks (GNNs) or any other method, and reinforcement learning (RL). See [SingleJobShopGraphEnv](https://github.com/Pabloo22/job_shop_lib/blob/main/docs/source/examples/09-SingleJobShopGraphEnv.ipynb) and [MultiJobShopGraphEnv](https://github.com/Pabloo22/job_shop_lib/blob/main/docs/source/examples/10-MultiJobShopGraphEnv.ipynb).
87
+
88
+ <!-- end key features -->
89
+
90
+ ## Some Examples :rocket:
52
91
 
53
92
  ### Create a Job Shop Instance
54
93
 
55
- You can create a Job Shop Instance by defining the jobs and operations. An operation is defined by the machine(s) it is processed on and the duration (processing time).
94
+ You can create a `JobShopInstance` by defining the jobs and operations. An operation is defined by the machine(s) it is processed on and the duration (processing time).
56
95
 
57
96
  ```python
58
97
  from job_shop_lib import JobShopInstance, Operation
@@ -83,7 +122,7 @@ from job_shop_lib.benchmarking import load_benchmark_instance
83
122
  ft06 = load_benchmark_instance("ft06")
84
123
  ```
85
124
 
86
- The module `benchmarks` contains functions to load the instances from the file and return them as `JobShopInstance` objects without having to download them
125
+ The module `benchmarking` contains functions to load the instances from the file and return them as `JobShopInstance` objects without having to download them
87
126
  manually.
88
127
 
89
128
  The contributions to this benchmark dataset are as follows:
@@ -115,12 +154,12 @@ https://github.com/thomasWeise/jsspInstancesAndResults
115
154
 
116
155
  ### Generate a Random Instance
117
156
 
118
- You can also generate a random instance with the `BasicGenerator` class.
157
+ You can also generate a random instance with the `GeneralInstanceGenerator` class.
119
158
 
120
159
  ```python
121
- from job_shop_lib.generators import BasicGenerator
160
+ from job_shop_lib.generation import GeneralInstanceGenerator
122
161
 
123
- generator = BasicGenerator(
162
+ generator = GeneralInstanceGenerator(
124
163
  duration_range=(5, 10), seed=42, num_jobs=5, num_machines=5
125
164
  )
126
165
  random_instance = generator.generate()
@@ -129,7 +168,7 @@ random_instance = generator.generate()
129
168
  This class can also work as an iterator to generate multiple instances:
130
169
 
131
170
  ```python
132
- generator = BasicGenerator(iteration_limit=100, seed=42)
171
+ generator = GeneralInstanceGenerator(iteration_limit=100, seed=42)
133
172
  instances = []
134
173
  for instance in generator:
135
174
  instances.append(instance)
@@ -145,7 +184,7 @@ Every solver is a `Callable` that receives a `JobShopInstance` and returns a `Sc
145
184
  ```python
146
185
  import matplotlib.pyplot as plt
147
186
 
148
- from job_shop_lib.cp_sat import ORToolsSolver
187
+ from job_shop_lib.constraint_programming import ORToolsSolver
149
188
  from job_shop_lib.visualization import plot_gantt_chart
150
189
 
151
190
  solver = ORToolsSolver(max_time_in_seconds=10)
@@ -154,7 +193,7 @@ ft06_schedule = solver(ft06)
154
193
  fig, ax = plot_gantt_chart(ft06_schedule)
155
194
  plt.show()
156
195
  ```
157
- ![Example Gannt Chart](images/ft06_solution.png)
196
+ ![Example Gannt Chart](docs/source/images/ft06_solution.png)
158
197
 
159
198
  ### Solve an Instance with a Dispatching Rule Solver
160
199
 
@@ -190,7 +229,7 @@ create_gif(
190
229
  )
191
230
  ```
192
231
 
193
- ![Example Gif](examples/ft06_optimized.gif)
232
+ ![Example Gif](docs/source/examples/output/ft06_optimized.gif)
194
233
 
195
234
  The dashed red line represents the current time step, which is computed as the earliest time when the next operation can start.
196
235
 
@@ -200,7 +239,7 @@ The dashed red line represents the current time step, which is computed as the e
200
239
 
201
240
  ### Representing Instances as Graphs
202
241
 
203
- One of the main purposes of this library is to provide an easy way to encode instances as graphs. This can be very useful, not only for visualization purposes but also for developing Graph Neural Network-based algorithms.
242
+ One of the main purposes of this library is to provide an easy way to encode instances as graphs. This can be very useful, not only for visualization purposes but also for developing graph neural network-based algorithms.
204
243
 
205
244
  A graph is represented by the `JobShopGraph` class, which internally stores a `networkx.DiGraph` object.
206
245
 
@@ -208,7 +247,7 @@ A graph is represented by the `JobShopGraph` class, which internally stores a `n
208
247
 
209
248
  The disjunctive graph is created by first adding nodes representing each operation in the jobs, along with two special nodes: a source $S$ and a sink $T$. Each operation node is linked to the next operation in its job sequence by **conjunctive edges**, forming a path from the source to the sink. These edges represent the order in which operations of a single job must be performed.
210
249
 
211
- Additionally, the graph includes **disjunctive edges** between operations that use the same machine but belong to different jobs. These edges are bidirectional, indicating that either of the connected operations can be performed first. The disjunctive edges thus represent the scheduling choices available: the order in which operations sharing a machine can be processed. Solving the Job Shop Scheduling problem involves choosing a direction for each disjunctive edge such that the overall processing time is minimized.
250
+ Additionally, the graph includes **disjunctive edges** between operations that use the same machine but belong to different jobs. These edges are bidirectional, indicating that either of the connected operations can be performed first. The disjunctive edges thus represent the scheduling choices available: the order in which operations sharing a machine can be processed. Solving the job shop scheduling problem involves choosing a direction for each disjunctive edge such that the overall processing time is minimized.
212
251
 
213
252
  ```python
214
253
  from job_shop_lib.visualization import plot_disjunctive_graph
@@ -217,11 +256,11 @@ fig = plot_disjunctive_graph(instance)
217
256
  plt.show()
218
257
  ```
219
258
 
220
- ![Example Disjunctive Graph](images/example_disjunctive_graph.png)
259
+ ![Example Disjunctive Graph](docs/source/images/example_disjunctive_graph.png)
221
260
 
222
261
 
223
- > [!WARNING]
224
- > This plot function requires having the optional dependency [PyGraphViz](https://pygraphviz.github.io/) installed.
262
+ > [!TIP]
263
+ > Installing the optional dependency [PyGraphViz](https://pygraphviz.github.io/) is recommended.
225
264
 
226
265
  The `JobShopGraph` class provides easy access to the nodes, for example, to get all the nodes of a specific type:
227
266
 
@@ -250,9 +289,9 @@ Other attributes include:
250
289
  - `nodes_by_machine`: A nested list mapping each machine to its associated operation nodes, aiding in machine-specific analysis.
251
290
  - `nodes_by_job`: Similar to `nodes_by_machine`, but maps jobs to their operation nodes, useful for job-specific traversal.
252
291
 
253
- #### Agent-Task Graph
292
+ #### Resource-Task Graph
254
293
 
255
- Introduced in the paper "ScheduleNet: Learn to solve multi-agent scheduling problems with reinforcement learning" by [Park et al. (2021)](https://arxiv.org/abs/2106.03051), the Agent-Task Graph is a graph that represents the scheduling problem as a multi-agent reinforcement learning problem.
294
+ Introduced in the paper "ScheduleNet: Learn to solve multi-agent scheduling problems with reinforcement learning" by [Park et al. (2021)](https://arxiv.org/abs/2106.03051), the resource-task graph (orginally named "agent-task graph") is a graph that represents the scheduling problem as a multi-agent reinforcement learning problem.
256
295
 
257
296
  In contrast to the disjunctive graph, instead of connecting operations
258
297
  that share the same resources directly by disjunctive edges, operation
@@ -263,66 +302,59 @@ from the same job are connected by non-directed edges too.
263
302
 
264
303
  ```python
265
304
  from job_shop_lib.graphs import (
266
- build_complete_agent_task_graph,
267
- build_agent_task_graph_with_jobs,
268
- build_agent_task_graph,
305
+ build_complete_resource_task_graph,
306
+ build_resource_task_graph_with_jobs,
307
+ build_resource_task_graph,
269
308
  )
270
- from job_shop_lib.visualization import plot_agent_task_graph
309
+ from job_shop_lib.visualization import plot_resource_task_graph
271
310
 
272
- complete_agent_task_graph = build_complete_agent_task_graph(instance)
311
+ complete_resource_task_graph = build_complete_resource_task_graph(instance)
273
312
 
274
- fig = plot_agent_task_graph(complete_agent_task_graph)
313
+ fig = plot_resource_task_graph(complete_agent_task_graph)
275
314
  plt.show()
276
315
  ```
277
316
 
278
317
  <div align="center">
279
- <img src="examples/agent_task_graph.png" width="300">
318
+ <img src="docs/source/examples/output/agent_task_graph.png" width="300">
280
319
  </div>
281
320
  <br>
282
321
 
283
322
  ----
284
323
 
324
+ The library generalizes this graph by allowing the addition of job nodes and a global one (see `build_resource_task_graph_with_jobs` and `build_resource_task_graph`).
325
+
285
326
  For more details, check the [examples](examples) folder.
286
327
 
287
328
  ## Installation for development
288
329
 
289
- ### With Poetry
330
+ <!-- start installation development -->
290
331
 
291
332
  1. Clone the repository.
292
333
 
293
- 2. Install [poetry](https://python-poetry.org/docs/) if you don't have it already:
294
- ```bash
295
- pip install poetry==1.7
296
- ```
297
- 3. Create the virtual environment:
298
334
  ```bash
299
- poetry shell
335
+ git clone https://github.com/Pabloo22/job_shop_lib.git
336
+ cd job_shop_lib
300
337
  ```
301
- 4. Install dependencies:
338
+
339
+ 2. Install [poetry](https://python-poetry.org/docs/) if you don't have it already:
340
+
302
341
  ```bash
303
- poetry install --with notebooks --with test --with lint --all-extras
342
+ pip install poetry
304
343
  ```
305
- or equivalently:
344
+
345
+ 3. Install dependencies:
306
346
  ```bash
307
347
  make poetry_install_all
308
348
  ```
309
349
 
310
- ### With PyPI
311
-
312
- If you don't want to use Poetry, you can install the library directly from the source code:
313
-
314
- ```bash
315
- git clone https://github.com/Pabloo22/job_shop_lib.git
316
- cd job_shop_lib
317
- pip install -e .
318
- ```
350
+ <!-- end installation development -->
319
351
 
320
- ## License
352
+ ## License :scroll:
321
353
 
322
354
  This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
323
355
 
324
356
 
325
- ## References
357
+ ## References :books:
326
358
 
327
359
  - J. Adams, E. Balas, and D. Zawack, "The shifting bottleneck procedure
328
360
  for job shop scheduling," Management Science, vol. 34, no. 3,