job-shop-lib 1.0.0a3__py3-none-any.whl → 1.0.0a4__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- job_shop_lib/_job_shop_instance.py +104 -38
- job_shop_lib/_operation.py +12 -3
- job_shop_lib/_schedule.py +10 -12
- job_shop_lib/_scheduled_operation.py +15 -16
- job_shop_lib/dispatching/_dispatcher.py +12 -15
- job_shop_lib/dispatching/_dispatcher_observer_config.py +15 -2
- job_shop_lib/dispatching/_factories.py +2 -2
- job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py +0 -1
- job_shop_lib/dispatching/feature_observers/_factory.py +21 -18
- job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +1 -0
- job_shop_lib/dispatching/rules/_dispatching_rule_solver.py +1 -1
- job_shop_lib/generation/_general_instance_generator.py +33 -34
- job_shop_lib/generation/_instance_generator.py +14 -17
- job_shop_lib/generation/_transformations.py +11 -8
- job_shop_lib/graphs/__init__.py +3 -0
- job_shop_lib/graphs/_build_disjunctive_graph.py +41 -3
- job_shop_lib/graphs/graph_updaters/_graph_updater.py +11 -13
- job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +17 -20
- job_shop_lib/reinforcement_learning/__init__.py +16 -7
- job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +69 -57
- job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +42 -31
- job_shop_lib/reinforcement_learning/_types_and_constants.py +2 -2
- job_shop_lib/visualization/__init__.py +24 -5
- job_shop_lib/visualization/_gantt_chart_creator.py +118 -80
- job_shop_lib/visualization/_gantt_chart_video_and_gif_creation.py +15 -11
- job_shop_lib/visualization/_plot_disjunctive_graph.py +382 -0
- {job_shop_lib-1.0.0a3.dist-info → job_shop_lib-1.0.0a4.dist-info}/METADATA +5 -5
- {job_shop_lib-1.0.0a3.dist-info → job_shop_lib-1.0.0a4.dist-info}/RECORD +31 -31
- job_shop_lib/visualization/_disjunctive_graph.py +0 -210
- /job_shop_lib/visualization/{_agent_task_graph.py → _plot_agent_task_graph.py} +0 -0
- {job_shop_lib-1.0.0a3.dist-info → job_shop_lib-1.0.0a4.dist-info}/LICENSE +0 -0
- {job_shop_lib-1.0.0a3.dist-info → job_shop_lib-1.0.0a4.dist-info}/WHEEL +0 -0
@@ -1,210 +0,0 @@
|
|
1
|
-
"""Module for visualizing the disjunctive graph of a job shop instance."""
|
2
|
-
|
3
|
-
import functools
|
4
|
-
from typing import Optional, Callable
|
5
|
-
import warnings
|
6
|
-
import copy
|
7
|
-
|
8
|
-
import matplotlib
|
9
|
-
import matplotlib.pyplot as plt
|
10
|
-
import networkx as nx
|
11
|
-
from networkx.drawing.nx_agraph import graphviz_layout
|
12
|
-
|
13
|
-
from job_shop_lib import JobShopInstance
|
14
|
-
from job_shop_lib.graphs import (
|
15
|
-
JobShopGraph,
|
16
|
-
EdgeType,
|
17
|
-
NodeType,
|
18
|
-
Node,
|
19
|
-
build_disjunctive_graph,
|
20
|
-
)
|
21
|
-
|
22
|
-
|
23
|
-
Layout = Callable[[nx.Graph], dict[str, tuple[float, float]]]
|
24
|
-
|
25
|
-
|
26
|
-
# This function could be improved by a function extraction refactoring
|
27
|
-
# (see `plot_gantt_chart`
|
28
|
-
# function as a reference in how to do it). That would solve the
|
29
|
-
# "too many locals" warning. However, this refactoring is not a priority at
|
30
|
-
# the moment. To compensate, sections are separated by comments.
|
31
|
-
# For the "too many arguments" warning no satisfactory solution was
|
32
|
-
# found. I believe is still better than using `**kwargs` and losing the
|
33
|
-
# function signature or adding a dataclass for configuration (it would add
|
34
|
-
# unnecessary complexity).
|
35
|
-
# pylint: disable=too-many-arguments, too-many-locals
|
36
|
-
def plot_disjunctive_graph(
|
37
|
-
job_shop: JobShopGraph | JobShopInstance,
|
38
|
-
figsize: tuple[float, float] = (6, 4),
|
39
|
-
node_size: int = 1600,
|
40
|
-
title: Optional[str] = None,
|
41
|
-
layout: Optional[Layout] = None,
|
42
|
-
edge_width: int = 2,
|
43
|
-
font_size: int = 10,
|
44
|
-
arrow_size: int = 35,
|
45
|
-
alpha=0.95,
|
46
|
-
node_font_color: str = "white",
|
47
|
-
color_map: str = "Dark2_r",
|
48
|
-
draw_disjunctive_edges: bool = True,
|
49
|
-
) -> plt.Figure:
|
50
|
-
"""Returns a plot of the disjunctive graph of the instance."""
|
51
|
-
|
52
|
-
if isinstance(job_shop, JobShopInstance):
|
53
|
-
job_shop_graph = build_disjunctive_graph(job_shop)
|
54
|
-
else:
|
55
|
-
job_shop_graph = job_shop
|
56
|
-
|
57
|
-
# Set up the plot
|
58
|
-
# ----------------
|
59
|
-
plt.figure(figsize=figsize)
|
60
|
-
if title is None:
|
61
|
-
title = (
|
62
|
-
f"Disjunctive Graph Visualization: {job_shop_graph.instance.name}"
|
63
|
-
)
|
64
|
-
plt.title(title)
|
65
|
-
|
66
|
-
# Set up the layout
|
67
|
-
# -----------------
|
68
|
-
if layout is None:
|
69
|
-
layout = functools.partial(
|
70
|
-
graphviz_layout, prog="dot", args="-Grankdir=LR"
|
71
|
-
)
|
72
|
-
|
73
|
-
temp_graph = copy.deepcopy(job_shop_graph.graph)
|
74
|
-
# Remove disjunctive edges to get a better layout
|
75
|
-
temp_graph.remove_edges_from(
|
76
|
-
[
|
77
|
-
(u, v)
|
78
|
-
for u, v, d in job_shop_graph.graph.edges(data=True)
|
79
|
-
if d["type"] == EdgeType.DISJUNCTIVE
|
80
|
-
]
|
81
|
-
)
|
82
|
-
|
83
|
-
try:
|
84
|
-
pos = layout(temp_graph)
|
85
|
-
except ImportError:
|
86
|
-
warnings.warn(
|
87
|
-
"Default layout requires pygraphviz http://pygraphviz.github.io/. "
|
88
|
-
"Using spring layout instead.",
|
89
|
-
)
|
90
|
-
pos = nx.spring_layout(temp_graph)
|
91
|
-
|
92
|
-
# Draw nodes
|
93
|
-
# ----------
|
94
|
-
node_colors = [
|
95
|
-
_get_node_color(node)
|
96
|
-
for node in job_shop_graph.nodes
|
97
|
-
if not job_shop_graph.is_removed(node.node_id)
|
98
|
-
]
|
99
|
-
|
100
|
-
nx.draw_networkx_nodes(
|
101
|
-
job_shop_graph.graph,
|
102
|
-
pos,
|
103
|
-
node_size=node_size,
|
104
|
-
node_color=node_colors,
|
105
|
-
alpha=alpha,
|
106
|
-
cmap=matplotlib.colormaps.get_cmap(color_map),
|
107
|
-
)
|
108
|
-
|
109
|
-
# Draw edges
|
110
|
-
# ----------
|
111
|
-
conjunctive_edges = [
|
112
|
-
(u, v)
|
113
|
-
for u, v, d in job_shop_graph.graph.edges(data=True)
|
114
|
-
if d["type"] == EdgeType.CONJUNCTIVE
|
115
|
-
]
|
116
|
-
disjunctive_edges = [
|
117
|
-
(u, v)
|
118
|
-
for u, v, d in job_shop_graph.graph.edges(data=True)
|
119
|
-
if d["type"] == EdgeType.DISJUNCTIVE
|
120
|
-
]
|
121
|
-
|
122
|
-
nx.draw_networkx_edges(
|
123
|
-
job_shop_graph.graph,
|
124
|
-
pos,
|
125
|
-
edgelist=conjunctive_edges,
|
126
|
-
width=edge_width,
|
127
|
-
edge_color="black",
|
128
|
-
arrowsize=arrow_size,
|
129
|
-
)
|
130
|
-
|
131
|
-
if draw_disjunctive_edges:
|
132
|
-
nx.draw_networkx_edges(
|
133
|
-
job_shop_graph.graph,
|
134
|
-
pos,
|
135
|
-
edgelist=disjunctive_edges,
|
136
|
-
width=edge_width,
|
137
|
-
edge_color="red",
|
138
|
-
arrowsize=arrow_size,
|
139
|
-
)
|
140
|
-
|
141
|
-
# Draw node labels
|
142
|
-
# ----------------
|
143
|
-
operation_nodes = job_shop_graph.nodes_by_type[NodeType.OPERATION]
|
144
|
-
|
145
|
-
labels = {}
|
146
|
-
source_node = job_shop_graph.nodes_by_type[NodeType.SOURCE][0]
|
147
|
-
labels[source_node] = "S"
|
148
|
-
|
149
|
-
sink_node = job_shop_graph.nodes_by_type[NodeType.SINK][0]
|
150
|
-
labels[sink_node] = "T"
|
151
|
-
for operation_node in operation_nodes:
|
152
|
-
if job_shop_graph.is_removed(operation_node.node_id):
|
153
|
-
continue
|
154
|
-
labels[operation_node] = (
|
155
|
-
f"m={operation_node.operation.machine_id}\n"
|
156
|
-
f"d={operation_node.operation.duration}"
|
157
|
-
)
|
158
|
-
|
159
|
-
nx.draw_networkx_labels(
|
160
|
-
job_shop_graph.graph,
|
161
|
-
pos,
|
162
|
-
labels=labels,
|
163
|
-
font_color=node_font_color,
|
164
|
-
font_size=font_size,
|
165
|
-
font_family="sans-serif",
|
166
|
-
)
|
167
|
-
|
168
|
-
# Final touches
|
169
|
-
# -------------
|
170
|
-
plt.axis("off")
|
171
|
-
plt.tight_layout()
|
172
|
-
# Create a legend to indicate the meaning of the edge colors
|
173
|
-
conjunctive_patch = matplotlib.patches.Patch(
|
174
|
-
color="black", label="conjunctive edges"
|
175
|
-
)
|
176
|
-
disjunctive_patch = matplotlib.patches.Patch(
|
177
|
-
color="red", label="disjunctive edges"
|
178
|
-
)
|
179
|
-
|
180
|
-
# Add to the legend the meaning of m and d
|
181
|
-
text = "m = machine_id\nd = duration"
|
182
|
-
extra = matplotlib.patches.Rectangle(
|
183
|
-
(0, 0),
|
184
|
-
1,
|
185
|
-
1,
|
186
|
-
fc="w",
|
187
|
-
fill=False,
|
188
|
-
edgecolor="none",
|
189
|
-
linewidth=0,
|
190
|
-
label=text,
|
191
|
-
)
|
192
|
-
plt.legend(
|
193
|
-
handles=[conjunctive_patch, disjunctive_patch, extra],
|
194
|
-
loc="upper left",
|
195
|
-
bbox_to_anchor=(1.05, 1),
|
196
|
-
borderaxespad=0.0,
|
197
|
-
)
|
198
|
-
return plt.gcf()
|
199
|
-
|
200
|
-
|
201
|
-
def _get_node_color(node: Node) -> int:
|
202
|
-
"""Returns the color of the node."""
|
203
|
-
if node.node_type == NodeType.SOURCE:
|
204
|
-
return -1
|
205
|
-
if node.node_type == NodeType.SINK:
|
206
|
-
return -1
|
207
|
-
if node.node_type == NodeType.OPERATION:
|
208
|
-
return node.operation.machine_id
|
209
|
-
|
210
|
-
raise ValueError("Invalid node type.")
|
File without changes
|
File without changes
|
File without changes
|