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.
- job_shop_lib/__init__.py +1 -1
- job_shop_lib/_job_shop_instance.py +34 -29
- job_shop_lib/_operation.py +4 -2
- job_shop_lib/_schedule.py +11 -11
- job_shop_lib/benchmarking/_load_benchmark.py +3 -3
- job_shop_lib/constraint_programming/_ortools_solver.py +6 -6
- job_shop_lib/dispatching/__init__.py +4 -3
- job_shop_lib/dispatching/_dispatcher.py +19 -19
- job_shop_lib/dispatching/_dispatcher_observer_config.py +4 -4
- job_shop_lib/dispatching/_factories.py +4 -2
- job_shop_lib/dispatching/_history_observer.py +2 -1
- job_shop_lib/dispatching/_optimal_operations_observer.py +115 -0
- job_shop_lib/dispatching/_ready_operation_filters.py +19 -18
- job_shop_lib/dispatching/_unscheduled_operations_observer.py +4 -3
- job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py +7 -8
- job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py +3 -1
- job_shop_lib/dispatching/feature_observers/_factory.py +13 -14
- job_shop_lib/dispatching/feature_observers/_feature_observer.py +9 -8
- job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +2 -1
- job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +4 -2
- job_shop_lib/dispatching/rules/__init__.py +37 -1
- job_shop_lib/dispatching/rules/_dispatching_rule_factory.py +4 -2
- job_shop_lib/dispatching/rules/_dispatching_rule_solver.py +50 -20
- job_shop_lib/dispatching/rules/_dispatching_rules_functions.py +9 -8
- job_shop_lib/dispatching/rules/_machine_chooser_factory.py +4 -3
- job_shop_lib/dispatching/rules/_utils.py +9 -8
- job_shop_lib/generation/__init__.py +8 -0
- job_shop_lib/generation/_general_instance_generator.py +42 -64
- job_shop_lib/generation/_instance_generator.py +11 -7
- job_shop_lib/generation/_transformations.py +5 -4
- job_shop_lib/generation/_utils.py +124 -0
- job_shop_lib/graphs/__init__.py +7 -7
- job_shop_lib/graphs/{_build_agent_task_graph.py → _build_resource_task_graphs.py} +26 -24
- job_shop_lib/graphs/_job_shop_graph.py +17 -13
- job_shop_lib/graphs/_node.py +6 -4
- job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +4 -2
- job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +40 -20
- job_shop_lib/reinforcement_learning/_reward_observers.py +3 -1
- job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +89 -22
- job_shop_lib/reinforcement_learning/_types_and_constants.py +1 -1
- job_shop_lib/reinforcement_learning/_utils.py +3 -3
- job_shop_lib/visualization/__init__.py +0 -60
- job_shop_lib/visualization/gantt/__init__.py +48 -0
- job_shop_lib/visualization/{_gantt_chart_creator.py → gantt/_gantt_chart_creator.py} +12 -12
- job_shop_lib/visualization/{_gantt_chart_video_and_gif_creation.py → gantt/_gantt_chart_video_and_gif_creation.py} +22 -22
- job_shop_lib/visualization/{_plot_gantt_chart.py → gantt/_plot_gantt_chart.py} +12 -13
- job_shop_lib/visualization/graphs/__init__.py +29 -0
- job_shop_lib/visualization/{_plot_disjunctive_graph.py → graphs/_plot_disjunctive_graph.py} +18 -16
- job_shop_lib/visualization/graphs/_plot_resource_task_graph.py +389 -0
- {job_shop_lib-1.0.0a5.dist-info → job_shop_lib-1.0.0b2.dist-info}/METADATA +21 -15
- job_shop_lib-1.0.0b2.dist-info/RECORD +70 -0
- job_shop_lib/visualization/_plot_agent_task_graph.py +0 -276
- job_shop_lib-1.0.0a5.dist-info/RECORD +0 -66
- {job_shop_lib-1.0.0a5.dist-info → job_shop_lib-1.0.0b2.dist-info}/LICENSE +0 -0
- {job_shop_lib-1.0.0a5.dist-info → job_shop_lib-1.0.0b2.dist-info}/WHEEL +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: 1.0.
|
3
|
+
Version: 1.0.0b2
|
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
|
@@ -36,7 +36,7 @@ Description-Content-Type: text/markdown
|
|
36
36
|
|
37
37
|
</div>
|
38
38
|
|
39
|
-
JobShopLib is a Python package for creating, solving, and visualizing
|
39
|
+
JobShopLib is a Python package for creating, solving, and visualizing job shop scheduling problems (JSSP).
|
40
40
|
|
41
41
|
It follows a modular design, allowing users to easily extend the library with new solvers, dispatching rules, visualization functions, etc.
|
42
42
|
|
@@ -57,10 +57,10 @@ pip install job-shop-lib
|
|
57
57
|
See [this](https://colab.research.google.com/drive/1XV_Rvq1F2ns6DFG8uNj66q_rcowwTZ4H?usp=sharing) Google Colab notebook for a quick start guide!
|
58
58
|
|
59
59
|
|
60
|
-
Version 1.0.0 is currently in
|
60
|
+
Version 1.0.0 is currently in beta stage and can be installed with:
|
61
61
|
|
62
62
|
```bash
|
63
|
-
pip install job-shop-lib==1.0.
|
63
|
+
pip install job-shop-lib==1.0.0b2
|
64
64
|
```
|
65
65
|
|
66
66
|
Although this version is not stable and may contain breaking changes in subsequent releases, it is recommended to install it to access the new reinforcement learning environments and familiarize yourself with new changes (see the [latest pull requests](https://github.com/Pabloo22/job_shop_lib/pulls?q=is%3Apr+is%3Aclosed)). There is a [documentation page](https://job-shop-lib.readthedocs.io/en/latest/) for versions 1.0.0a3 and onward.
|
@@ -89,7 +89,11 @@ Although this version is not stable and may contain breaking changes in subseque
|
|
89
89
|
- **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).
|
90
90
|
- Build your own custom graphs with the `JobShopGraph` class.
|
91
91
|
|
92
|
+
<<<<<<< HEAD
|
93
|
+
- **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).
|
94
|
+
=======
|
92
95
|
- **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).
|
96
|
+
>>>>>>> 031bdf3 ([Docs] Update links in README to point to the correct GitHub URLs for tutorials and examples)
|
93
97
|
|
94
98
|
<!-- end key features -->
|
95
99
|
|
@@ -245,7 +249,7 @@ The dashed red line represents the current time step, which is computed as the e
|
|
245
249
|
|
246
250
|
### Representing Instances as Graphs
|
247
251
|
|
248
|
-
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
|
252
|
+
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.
|
249
253
|
|
250
254
|
A graph is represented by the `JobShopGraph` class, which internally stores a `networkx.DiGraph` object.
|
251
255
|
|
@@ -253,7 +257,7 @@ A graph is represented by the `JobShopGraph` class, which internally stores a `n
|
|
253
257
|
|
254
258
|
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.
|
255
259
|
|
256
|
-
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
|
260
|
+
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.
|
257
261
|
|
258
262
|
```python
|
259
263
|
from job_shop_lib.visualization import plot_disjunctive_graph
|
@@ -295,9 +299,9 @@ Other attributes include:
|
|
295
299
|
- `nodes_by_machine`: A nested list mapping each machine to its associated operation nodes, aiding in machine-specific analysis.
|
296
300
|
- `nodes_by_job`: Similar to `nodes_by_machine`, but maps jobs to their operation nodes, useful for job-specific traversal.
|
297
301
|
|
298
|
-
####
|
302
|
+
#### Resource-Task Graph
|
299
303
|
|
300
|
-
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
|
304
|
+
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.
|
301
305
|
|
302
306
|
In contrast to the disjunctive graph, instead of connecting operations
|
303
307
|
that share the same resources directly by disjunctive edges, operation
|
@@ -308,25 +312,27 @@ from the same job are connected by non-directed edges too.
|
|
308
312
|
|
309
313
|
```python
|
310
314
|
from job_shop_lib.graphs import (
|
311
|
-
|
312
|
-
|
313
|
-
|
315
|
+
build_complete_resource_task_graph,
|
316
|
+
build_resource_task_graph_with_jobs,
|
317
|
+
build_resource_task_graph,
|
314
318
|
)
|
315
|
-
from job_shop_lib.visualization import
|
319
|
+
from job_shop_lib.visualization import plot_resource_task_graph
|
316
320
|
|
317
|
-
|
321
|
+
complete_resource_task_graph = build_complete_resource_task_graph(instance)
|
318
322
|
|
319
|
-
fig =
|
323
|
+
fig = plot_resource_task_graph(complete_agent_task_graph)
|
320
324
|
plt.show()
|
321
325
|
```
|
322
326
|
|
323
327
|
<div align="center">
|
324
|
-
<img src="docs/source/
|
328
|
+
<img src="docs/source/examples/output/agent_task_graph.png" width="300">
|
325
329
|
</div>
|
326
330
|
<br>
|
327
331
|
|
328
332
|
----
|
329
333
|
|
334
|
+
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`).
|
335
|
+
|
330
336
|
For more details, check the [examples](examples) folder.
|
331
337
|
|
332
338
|
## Installation for development
|
@@ -0,0 +1,70 @@
|
|
1
|
+
job_shop_lib/__init__.py,sha256=xXYzjYqn-98QKOMQ4_fOBZYQ6Z6GUydDW5BNXiVSRCE,643
|
2
|
+
job_shop_lib/_base_solver.py,sha256=p17XmtufNc9Y481cqZUT45pEkUmmW1HWG53dfhIBJH8,1363
|
3
|
+
job_shop_lib/_job_shop_instance.py,sha256=hNQGSJj0rEQpS-YhzwWmM6QzCWp6r--89jkghSgLvUs,18380
|
4
|
+
job_shop_lib/_operation.py,sha256=hx2atpP8LPj9fvxpZIfhBFr9Uq6JP-MKAX5JzTvFXso,3847
|
5
|
+
job_shop_lib/_schedule.py,sha256=-RdCtTTj-SdNLFucmSVnrCbjZLcBZ4yfhRBdATjAaW8,11292
|
6
|
+
job_shop_lib/_scheduled_operation.py,sha256=krjGn47VwsC7bXUTqlUq8Y-DpiSE9q2z8bqwgJVpAZo,2697
|
7
|
+
job_shop_lib/benchmarking/__init__.py,sha256=BYCrJUNr_uk2c0xIbDt07OnUMhQx8Dudkukx3TFWxgw,3271
|
8
|
+
job_shop_lib/benchmarking/_load_benchmark.py,sha256=Jb6HYGKkub-3uU3l3NreRPE0PU6f0n8G9Mih5vMImOI,2936
|
9
|
+
job_shop_lib/benchmarking/benchmark_instances.json,sha256=F9EvyzFwVxiKAN6rQTsrMhsKstmyUmroyWduM7a00KQ,464841
|
10
|
+
job_shop_lib/constraint_programming/__init__.py,sha256=kKQRUxxS_nVFUdXGnf4bQOD9mqrXxZZWElS753A4YiA,454
|
11
|
+
job_shop_lib/constraint_programming/_ortools_solver.py,sha256=vz_Kg_CmvZ13yGgqi-hZuFkosJR1v449xNaAZV3PhsE,10501
|
12
|
+
job_shop_lib/dispatching/__init__.py,sha256=SXVd0Zh6xTp-lNT7c463pii3l168NCZYf-5uOwBI1Fc,1770
|
13
|
+
job_shop_lib/dispatching/_dispatcher.py,sha256=HW333xJoupufSUCg-pfaR_4KoQHjQMd4SBXJj-vgE7Y,22349
|
14
|
+
job_shop_lib/dispatching/_dispatcher_observer_config.py,sha256=RaUkLxYCHG8Tx2tPgFyOBa8FAcbREZdKuTyLsyaYvhA,2473
|
15
|
+
job_shop_lib/dispatching/_factories.py,sha256=1McCm3iJGdGDZerAe6MTWsthpbomuYb3crTKg_5lfyM,4678
|
16
|
+
job_shop_lib/dispatching/_history_observer.py,sha256=dixJe83quzGNwG0u0k2uES7GsLw0zWCjX0MOUD4VTRU,634
|
17
|
+
job_shop_lib/dispatching/_optimal_operations_observer.py,sha256=HTbmc9EaagM3UA6IIUaj7O0Fq3hCrjONhfonHIZnCE0,4376
|
18
|
+
job_shop_lib/dispatching/_ready_operation_filters.py,sha256=H2-CPL0BKDvpLM2zvDGr9eC849qLgbxsVDHgPAHtNgc,5754
|
19
|
+
job_shop_lib/dispatching/_unscheduled_operations_observer.py,sha256=3E0ePesDdWdNs6520znnOBW3eiegJj5bZg9Tmb0xoSA,2705
|
20
|
+
job_shop_lib/dispatching/feature_observers/__init__.py,sha256=EuJLvSpJpoXUK8A4UuC2k6Mpa293ZR3oCnnvYivIBtU,2240
|
21
|
+
job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py,sha256=5njpWV1Biui931HEuik9JGjsawle8fXaU5-mFYsxhRI,7933
|
22
|
+
job_shop_lib/dispatching/feature_observers/_duration_observer.py,sha256=fbkUIVScF1iNjdVCYr1ImQm53TfahvVnGXhsRAsgdzY,4129
|
23
|
+
job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py,sha256=W5Tr81Kme8N-m85jmX7yVc65_xlwNQBvVjnjlL-aq7w,11493
|
24
|
+
job_shop_lib/dispatching/feature_observers/_factory.py,sha256=JRQLQ4SEXk7_beyzm0VcGQaL1j8NB1v7ZzsEh7NU3IM,3147
|
25
|
+
job_shop_lib/dispatching/feature_observers/_feature_observer.py,sha256=inFim_vKRk9_MTHURkr2ZLP1aJ1AHd3OQXqYg7VvCAo,8722
|
26
|
+
job_shop_lib/dispatching/feature_observers/_is_completed_observer.py,sha256=Dm67d90C-a-aZXDtRca1VakuYmLFXIl4vD9upr_1q6Q,4642
|
27
|
+
job_shop_lib/dispatching/feature_observers/_is_ready_observer.py,sha256=aP5CpwmCWP4w8J69qAC7QwGRQGMlfNbM31n-BRu92DA,1289
|
28
|
+
job_shop_lib/dispatching/feature_observers/_is_scheduled_observer.py,sha256=OcuMUB9_By6ZMtX-1_3z-xaxGbP85a5Zv0ywAv7XxWQ,1491
|
29
|
+
job_shop_lib/dispatching/feature_observers/_position_in_job_observer.py,sha256=WRknpQBKXs6h6cXLFJW7ZCvjtU8CPL-iXXNPw3g-mLE,1303
|
30
|
+
job_shop_lib/dispatching/feature_observers/_remaining_operations_observer.py,sha256=5V87lCrJUabEe8AkTGXPu5yS8OGxeN8L3-xNyHmdmLs,1441
|
31
|
+
job_shop_lib/dispatching/rules/__init__.py,sha256=g3PGvMLMa3WMgNhGSW3S_xkHqoHpW8hr_9JqOfR7Xrk,2140
|
32
|
+
job_shop_lib/dispatching/rules/_dispatching_rule_factory.py,sha256=0Hht2i_aGS5hgwPxh_59-ZtNQxgSOwEFclukXH6Ib9U,2942
|
33
|
+
job_shop_lib/dispatching/rules/_dispatching_rule_solver.py,sha256=9-UE0HiHCeFXFGqB85cSfduLCEm5k5bJkmIujP-_irg,7321
|
34
|
+
job_shop_lib/dispatching/rules/_dispatching_rules_functions.py,sha256=wfBdiKqEQQ8C5Gg_mrWWSuWncPwUkFacjeAQ8D4n9Wc,7648
|
35
|
+
job_shop_lib/dispatching/rules/_machine_chooser_factory.py,sha256=QqYXQr7Cp6WsqhLa1oHvR1vnbG8IFmYvxM44dQeLeSE,2362
|
36
|
+
job_shop_lib/dispatching/rules/_utils.py,sha256=DFDpRoHb56Rtn01vfN69Bq0X3F8P1EtM6trHx9aXg3U,4643
|
37
|
+
job_shop_lib/exceptions.py,sha256=ARzpoZJCvRIvOesCiqqFSRxkv6w9WwEXx0aBP-l2IKA,1597
|
38
|
+
job_shop_lib/generation/__init__.py,sha256=tgMVhnh62lkwGKywvingFD9SLhc-vERKiWsS-41qQKA,605
|
39
|
+
job_shop_lib/generation/_general_instance_generator.py,sha256=e-NDkH-NoCwa14oADj6n_I7BX5xWWVVzRLvb4rpJ92w,6374
|
40
|
+
job_shop_lib/generation/_instance_generator.py,sha256=VV0OKX4JgFq3I1EY6s3LrOdPjM3v4lH6S1hkUebTkFQ,4615
|
41
|
+
job_shop_lib/generation/_transformations.py,sha256=X-hTAJVIHZ3bmF1rqS0zCit8r5SGpHpV8Fcl92fejow,5336
|
42
|
+
job_shop_lib/generation/_utils.py,sha256=cBhGILE0FE3TqvWoHqpaFEffO8D2fb869pF-BdMlYsg,3617
|
43
|
+
job_shop_lib/graphs/__init__.py,sha256=zw4aOE-7QF8Lt8316rCUwOEAGqznjiijumTlGqBmfuw,1840
|
44
|
+
job_shop_lib/graphs/_build_disjunctive_graph.py,sha256=UbUYdeQaaeEqLchcKJGHEFGl4wElfGLb1o_R-u8wqnA,5120
|
45
|
+
job_shop_lib/graphs/_build_resource_task_graphs.py,sha256=GHUHkUNPxVf1miScgPPMe2YqlXFEMxIy5cDhNw7OZ1E,6954
|
46
|
+
job_shop_lib/graphs/_constants.py,sha256=K-GeVvh_DTWpo1KOX1clmxWS_pkUJbq19yOBmrCVIxI,1086
|
47
|
+
job_shop_lib/graphs/_job_shop_graph.py,sha256=mgawDY-PlEkGeTxoF0HaZCs0K514YH6zKFB0SssbJak,10761
|
48
|
+
job_shop_lib/graphs/_node.py,sha256=9TFH8C1D44W1IvOIG8MucLNQyLzasyBXVkMZTJU4rso,6075
|
49
|
+
job_shop_lib/graphs/graph_updaters/__init__.py,sha256=UhnZL55e3cAv7hVetB6bRmIOn8BDhG2bsbrdRoHtxLY,516
|
50
|
+
job_shop_lib/graphs/graph_updaters/_graph_updater.py,sha256=j1f7iWsa62GVszK2BPaMxnKBCEGWa9owm8g4VWUje8w,1967
|
51
|
+
job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py,sha256=SfgmDyMwfW56OBjJPaU76c42IsX5qx9j-eMtrv0DjKk,6047
|
52
|
+
job_shop_lib/graphs/graph_updaters/_utils.py,sha256=X5YfwJA1CCgpm1r9C036Gal2CkDh2SSak7wl7TbdjHw,704
|
53
|
+
job_shop_lib/reinforcement_learning/__init__.py,sha256=gOY-C6BMeFr3084MKMMbW0CoK7gMsaOYNsgnYuepswQ,1033
|
54
|
+
job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py,sha256=ib1Y6cItVvId4PfesiQ0XKbh9y6h8LVhD0gYDO4wSlk,15732
|
55
|
+
job_shop_lib/reinforcement_learning/_reward_observers.py,sha256=iWHccnujeAKyTQn2ilQ4BhcEccoSTyJqQ5yOiP5GG_Y,2984
|
56
|
+
job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py,sha256=DZnXeXmzMGKq-vwFhxukmSDN1UyrkUfbnjpjFtC9_Bs,15845
|
57
|
+
job_shop_lib/reinforcement_learning/_types_and_constants.py,sha256=xozdM_Wabdbe9e1a769p5980OSNBwQqc9yyaSGW2ODQ,1743
|
58
|
+
job_shop_lib/reinforcement_learning/_utils.py,sha256=ksg2ghSncxd0K3AR5hGS5PQejjd-Hgx6LGtXX_oatOc,2523
|
59
|
+
job_shop_lib/visualization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
60
|
+
job_shop_lib/visualization/gantt/__init__.py,sha256=HGXwRgDuMAldqU0JBdiZCd5e79XBz1r96qHeDVlzE54,1145
|
61
|
+
job_shop_lib/visualization/gantt/_gantt_chart_creator.py,sha256=LTsVhpB1Fb_2o08HRZPPXSekwzR7fyTSC6h549XMqhU,8638
|
62
|
+
job_shop_lib/visualization/gantt/_gantt_chart_video_and_gif_creation.py,sha256=CcHcvafYrTy7UaScM_wp9QlLOgKiTIKV7tFkttMgoLU,14474
|
63
|
+
job_shop_lib/visualization/gantt/_plot_gantt_chart.py,sha256=9-NSSNsVcW8gYLZtAuFeYURqi8cHNkVYufosKtbKFOI,6881
|
64
|
+
job_shop_lib/visualization/graphs/__init__.py,sha256=282hZFg07EyQu4HVt4GzFfYnY6ZF376IMjnWZ5eg0ZQ,611
|
65
|
+
job_shop_lib/visualization/graphs/_plot_disjunctive_graph.py,sha256=4VBMYiFXXkCGSnGYN9iqNtWrbLJQxAMHojPHhAbdA0s,14387
|
66
|
+
job_shop_lib/visualization/graphs/_plot_resource_task_graph.py,sha256=RgJqHS5hJh3KkyaLbtpG_bER981BFRwGpflz7I7gS64,13271
|
67
|
+
job_shop_lib-1.0.0b2.dist-info/LICENSE,sha256=9mggivMGd5taAu3xbmBway-VQZMBzurBGHofFopvUsQ,1069
|
68
|
+
job_shop_lib-1.0.0b2.dist-info/METADATA,sha256=_6waZfwgtsPOyvZ5rw18aulHrqEOJji5jcde9NrQBpg,16978
|
69
|
+
job_shop_lib-1.0.0b2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
70
|
+
job_shop_lib-1.0.0b2.dist-info/RECORD,,
|