wk_LinProg/LinProg_Scripts/LinProg_lib.py

557 lines
No EOL
21 KiB
Python

import dash
import dash_cytoscape as cyto
from dash import html
import networkx as nx
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import matplotlib.colors as mcolors
from matplotlib import colormaps
import pickle
def draw_networkx_graph(G):
"""
Draws the given NetworkX graph using Matplotlib, ensuring a left-to-right layout.
Parameters:
G (networkx.Graph): The graph to be drawn.
"""
pos = nx.nx_agraph.graphviz_layout(G, prog='dot') # Use Graphviz for left-to-right layout
plt.figure(figsize=(36, 24))
# Draw nodes
nx.draw_networkx_nodes(G, pos, node_size=500, node_color='skyblue')
# Draw edges
nx.draw_networkx_edges(G, pos, arrowstyle='->', arrowsize=20, edge_color='gray')
# Draw labels
nx.draw_networkx_labels(G, pos, font_size=10, font_color='black')
# Draw edge labels (e.g., flits)
edge_labels = nx.get_edge_attributes(G, 'flits')
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_size=8)
plt.title("Graph Visualization")
plt.axis('off') # Turn off the axis
plt.show()
def draw_networkx_graph_color(G, node_groups, mapping):
"""
Draws the given NetworkX graph using Matplotlib, ensuring a left-to-right layout.
Also draws a 4x4 mesh NoC and colors its nodes based on the provided mapping.
Parameters:
G (networkx.Graph): The graph to be drawn.
node_groups (dict): A dictionary where keys are group identifiers and values are lists of nodes in the group.
mapping (dict): A dictionary mapping graph nodes to NoC nodes (0 to 15).
"""
import matplotlib.pyplot as plt
import networkx as nx
import matplotlib.colors as mcolors
import matplotlib.cm as cm
import pickle
# Generate a color map for the groups
unique_colors = list(mcolors.TABLEAU_COLORS.values()) # Use Tableau colors for distinct groups
color_map = {}
for idx, (group, nodes) in enumerate(node_groups.items()):
color = unique_colors[idx % len(unique_colors)] # Cycle through colors if there are more groups than colors
for node in nodes:
color_map[node] = color
# Assign colors to nodes in the first graph
graph_node_colors = [color_map.get(node, 'gray') for node in G.nodes]
# Draw the first graph
pos = nx.nx_agraph.graphviz_layout(G, prog='dot') # Use Graphviz for left-to-right layout
plt.figure(figsize=(24, 16))
nx.draw_networkx_nodes(G, pos, node_size=500, node_color=graph_node_colors)
nx.draw_networkx_edges(G, pos, arrowstyle='->', arrowsize=20, edge_color='gray')
nx.draw_networkx_labels(G, pos, font_size=10, font_color='black')
edge_labels = nx.get_edge_attributes(G, 'flits')
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_size=8)
plt.title("Graph Visualization with Node Groups")
plt.axis('off')
plt.show()
# Map colors to the second graph's nodes based on the mapping
second_graph_color_map = {}
for node, noc_node in mapping.items():
node = int(node) # Ensure the node is an integer
second_graph_color_map[noc_node] = color_map.get(node, 'gray')
# Load the plot from the pickle file
with open("/home/sfischer/Documents/projects/wk_LinProg/LinProg_Scripts/graph_plot.pkl", "rb") as f:
combined_graph, combined_pos, highlighted_edges_per_layer, num_layers = pickle.load(f)
# Assign colors to nodes in the second graph
combined_graph_node_colors = [second_graph_color_map.get(node, 'gray') for node in combined_graph.nodes]
# Generate a color map for the layers
colors = cm.get_cmap("tab10", num_layers) # Use a colormap with `num_layers` distinct colors
# Plot the combined graph
plt.figure(figsize=(8, 8))
nx.draw(
combined_graph,
pos=combined_pos,
with_labels=True,
node_color=combined_graph_node_colors,
edge_color="gray",
node_size=500,
arrows=False,
)
# Draw highlighted edges for each layer with a different color
for z, edges in enumerate(highlighted_edges_per_layer):
nx.draw_networkx_edges(
combined_graph,
pos=combined_pos,
edgelist=edges,
edge_color=[colors(z)],
width=2,
label=f"Path {z}",
)
# Add a legend for the layers
handles = [plt.Line2D([0], [0], color=colors(z), lw=2, label=f"Path {z}") for z in range(num_layers)]
plt.legend(handles=handles, loc="upper left", title="Paths")
plt.title("Full Mapping on NoC")
plt.show()
def CreateNetworkXGraphManuelParallel():
G = nx.DiGraph()
num_parallel = 3 # number of nodes in parallel paths
num_paths = 4
front = 2
back = 2
flit_delay = 10
max_task_num = front + back + num_parallel * num_paths
delay_mem=10
delay_comp=100
for i in range(max_task_num):
G.add_node(i)
G.nodes[i]['delay_comp'] = delay_comp
G.nodes[i]['delay_mem'] = delay_mem
G.nodes[i]['delay_send'] = flit_delay
if i==0:
G.nodes[i]['src'] = True
else :
G.nodes[i]['src'] = False
if i==max_task_num-1:
G.nodes[i]['dst'] = True
else :
G.nodes[i]['dst'] = False
node_counter = front
for i in range(num_paths):
for m in range(num_parallel):
if m == 0:
G.add_edge(front - 1, node_counter, flits=flit_delay)
elif m == num_parallel - 1:
G.add_edge(node_counter - 1, node_counter, flits=flit_delay)
G.add_edge(node_counter, G.number_of_nodes() - back, flits=flit_delay)
else:
G.add_edge(node_counter - 1, node_counter, flits=flit_delay)
node_counter += 1
for i in range(front - 1):
G.add_edge(i, i + 1, flits=flit_delay)
for i in range(back - 1):
G.add_edge(G.number_of_nodes() - i - 2, G.number_of_nodes() - i - 1, flits=flit_delay)
return G
# Convert NetworkX graph to Cytoscape elements with preset positions
def graph_to_cytoscape(G):
pos = nx.spring_layout(G, seed=42) # Generate initial positions
elements = []
for node in G.nodes():
x, y = pos[node][0] * 500, pos[node][1] * 500 # Scale positions for visualization
elements.append({
"data": {"id": str(node), "label": f"Node {node}"},
"position": {"x": x, "y": y} # Assign preset position
})
for edge in G.edges(data=True):
flits = edge[2].get('flits', 0) # Default to 0 if 'flits' is not present
elements.append({
"data": {
"source": str(edge[0]),
"target": str(edge[1]),
"label": f"{flits} flits"
}
})
return elements
def combine_nodes(G, node1, node2, new_node):
"""
Combine two nodes in a NetworkX graph into a single node, merging their attributes and edges.
Parameters:
- G: The NetworkX graph.
- node1: The first node to combine.
- node2: The second node to combine.
- new_node: The name of the new combined node. New node name should be the same as one of the old nodes.
Returns:
- A new graph with the nodes combined.
"""
# Create a new graph to avoid modifying the original
H = nx.relabel_nodes(G, {node1: new_node, node2: new_node}, copy=True)
# Remove all edges where the new node is either the source or destination
edges_to_remove = [(u, v) for u, v in H.edges if u == new_node or v == new_node]
H.remove_edges_from(edges_to_remove)
# ======================================================================
# merge attributes of node1 and node2 to new node
# ======================================================================
new_attributes = {}
new_attributes.update(G.nodes[node1])
new_attributes["delay_comp"] = G.nodes[node1]["delay_comp"] + G.nodes[node2]["delay_comp"]
new_attributes["delay_mem"] = G.nodes[node1]["delay_mem"] + G.nodes[node2]["delay_mem"]
new_attributes["src"] = G.nodes[node1]["src"] or G.nodes[node2]["src"]
new_attributes["dst"] = G.nodes[node1]["dst"] or G.nodes[node2]["dst"]
successor_node = None
if node1 in G.successors(node2):
successor_node = node1
elif node2 in G.successors(node1):
successor_node = node2
if successor_node is None:
new_attributes["delay_send"] = G.nodes[node1]["delay_send"] + G.nodes[node2]["delay_send"]
else:
new_attributes["delay_send"] = G.nodes[successor_node]["delay_send"]
# Assign the merged attributes to the new node
nx.set_node_attributes(H, {new_node: new_attributes})
# ======================================================================
# Select and merge edges to new node
# ======================================================================
edges_to_merge = {
(edge[0], edge[1]): edge[2] for edge in G.edges(data=True)
if node1 in edge[:2] or node2 in edge[:2]
}
# Remove the edges from the original graph
H.remove_edges_from(edges_to_merge.keys())
if (node1, node2) in edges_to_merge:
del edges_to_merge[(node1, node2)]
if (node2, node1) in edges_to_merge:
del edges_to_merge[(node2, node1)]
for edge, attributes in edges_to_merge.items():
if edge[0] == node1 or edge[0] == node2:
if not H.has_edge(new_node, edge[1]):
H.add_edge(new_node, edge[1], **attributes)
if edge[1] == node1 or edge[1] == node2:
if not H.has_edge(edge[0], new_node):
H.add_edge(edge[0], new_node, **attributes)
return H
def convert_edge_list_to_highlight_dict(edge_list):
# Define a list of colors to assign to each connection
colors = ["red", "blue", "green", "orange", "purple", "cyan", "yellow", "pink"]
highlight_edges_with_colors = {}
connection_to_color = {}
# Iterate through the edge list
for connection, edge_start, edge_end in edge_list:
# Assign a color to the connection if not already assigned
if connection not in connection_to_color:
connection_to_color[connection] = colors[len(connection_to_color) % len(colors)]
# Add the edge to the dictionary with the assigned color
highlight_edges_with_colors[(edge_start, edge_end)] = connection_to_color[connection]
return highlight_edges_with_colors
# Draw the graph
from matplotlib.collections import LineCollection
def draw_angled_edges_torus(edge, pos, ax, edge_color="gray", linewidth=1,found_reverse=False):
lines = []
start = pos[edge[0]]
end = pos[edge[1]]
if abs(edge[0] - edge[1])==1:
if edge[0] in [0,4,8,12]:
offset_x = - 1.5
else:
offset_x = 0.5
offset_y = 0
else:
offset_x = 0
if edge[0] in [0,1,2,3]:
offset_y = -1.5
else:
offset_y = 0.5
if start[0] == end[0] or start[1] == end[1]: # Straight horizontal or vertical edge
# Create a single 90-degree turn
mid = (start[0], end[1]) # Midpoint with a 90-degree turn
lines.append([start, mid]) # First segment
lines.append([mid, end]) # Second segment
else:
# Create two 90-degree turns for non-straight edges
if abs(start[0] - end[0]) > abs(start[1] - end[1]): # Horizontal first
mid1 = (end[0]+offset_x, start[1]+offset_y) # Go past the node horizontally
mid2 = (end[0]+offset_x, end[1]+offset_y) # Come back vertically
else: # Vertical first
mid1 = (start[0]+offset_x, end[1]+offset_y) # Go past the node vertically
mid2 = (end[0]+offset_x, end[1]+offset_y) # Come back horizontally
lines.append([start, mid1]) # First segment
lines.append([mid1, mid2]) # Second segment
lines.append([mid2, end]) # Final segment
# Create a LineCollection for the edges
lc = LineCollection(lines, colors=edge_color, linewidths=linewidth)
ax.add_collection(lc)
if edge_color != "gray":
print(lines)
for line in lines:
(x0, y0), (x1, y1) = line
print(f"Edge: {edge}, Start: ({x0}, {y0}), End: ({x1}, {y1})")
if not found_reverse:
ax.annotate("",
xy=(x1, y1), xytext=(x0, y0),
arrowprops=dict(
arrowstyle="->",
color=edge_color,
lw=linewidth,
shrinkA=0, shrinkB=0 # optional: prevent shortening
))
else:
ax.annotate("",
xy=(x0, y0), xytext=(x1, y1),
arrowprops=dict(
arrowstyle="->",
color=edge_color,
lw=linewidth,
shrinkA=0, shrinkB=0
))
ax.autoscale()
ax.set_aspect('equal')
def draw_folded_torus_noc(mesh_size, G_NoC, highlight_edges_with_colors, title):
"""
Draws a folded torus NoC graph with angled edges and highlights specific edges with specified colors.
Parameters:
mesh_size (int): The size of the mesh.
G_NoC (networkx.DiGraph): The graph to draw.
highlight_edges_with_colors (dict): A dictionary where keys are edges (tuples) and values are colors (strings).
title (str): The title of the plot.
"""
pos = {}
for y in range(mesh_size):
for x in range(mesh_size):
if x % 2 == 0:
y_pos = y
else:
y_pos = y + 0.2
if y % 2 == 0:
x_pos = x
else:
x_pos = x + 0.2
node_number = coord_to_number(y, x, mesh_size)
pos[node_number] = (x_pos, y_pos)
G_NoC_no_duplicates = G_NoC.copy()
# Remove duplicate edges (edges going in both directions between the same pair of nodes)
edges_to_remove = []
for u, v in G_NoC_no_duplicates.edges():
if (G_NoC_no_duplicates.has_edge(v, u) and G_NoC_no_duplicates.has_edge(u, v) and ((u, v) not in edges_to_remove)):
edges_to_remove.append((v, u))
# Remove the identified edges from the new graph
G_NoC_no_duplicates.remove_edges_from(edges_to_remove)
plt.figure(figsize=(8, 8))
ax = plt.gca()
for edge in G_NoC_no_duplicates.edges():
# Check if the edge is in the highlight dictionary and get its color
found_reverse = False # Initialize a flag to track if the reverse edge was found
color = highlight_edges_with_colors.get(edge)
if color is None: # If the edge is not found
color = highlight_edges_with_colors.get(tuple(reversed(edge)), "gray")
if color != "gray": # If the reverse edge was found
found_reverse = True
draw_angled_edges_torus(edge, pos, ax, edge_color=color, linewidth=1,found_reverse=found_reverse)
nx.draw_networkx_nodes(G_NoC_no_duplicates, pos, node_color="lightblue", node_size=500)
nx.draw_networkx_labels(G_NoC_no_duplicates, pos, labels={node: node for node in G_NoC_no_duplicates.nodes()}, font_size=12, font_color="black")
plt.title(title, fontsize=16)
plt.axis('off')
plt.show()
# ======================================================================
# Create images for the NoC with the chosen paths highlighted
# ======================================================================
def plot_nx4x4_grid_with_highlighted_edges(highlighted_edges):
# Determine the number of layers based on the input
num_layers = max(edge[0] for edge in highlighted_edges) + 1
# Create a 3D grid graph
G = nx.DiGraph()
for z in range(num_layers): # Layers
for y in range(4): # Rows
for x in range(4): # Columns
node = z * 16 + y * 4 + x
if x < 3: # Add edge to the right
G.add_edge(node, node + 1)
if y < 3: # Add edge below
G.add_edge(node, node + 4)
if z < num_layers - 1: # Add edge to the next layer
G.add_edge(node, node + 16)
# Define 3D positions for the nodes
pos = {i: (i % 4, (i // 4) % 4, i // 16) for i in G.nodes}
# Convert input edges to graph edges
highlighted_edges_indices = []
highlighted_edges_indices_standard = []
for edge in highlighted_edges:
layer, src_node, dest_node = edge
start = layer * 16 + src_node # Convert layer and src_node to node index
end = layer * 16 + dest_node # Convert layer and dest_node to node index
start_temp=min(start,end)
end_temp=max(start,end)
highlighted_edges_indices_standard.append((start, end))
highlighted_edges_indices.append((start_temp, end_temp))
# Plot the 3D grid
fig = plt.figure(figsize=(10, 10))
ax = fig.add_subplot(111, projection='3d')
# Draw nodes
for node, (x, y, z) in pos.items():
ax.scatter(x, y, z, color="lightblue", s=100, zorder=1)
layer = z # Extract the layer from the z-coordinate
node_in_layer = node % 16 # Node number within the layer
# Offset the text slightly to make it visible and set a higher zorder
ax.text(x + 0.1, y + 0.1, z + 0.1, f"{layer},{node_in_layer}", color="black", fontsize=8, zorder=2)
# Draw edges
for edge in G.edges:
x_coords = [pos[edge[0]][0], pos[edge[1]][0]]
y_coords = [pos[edge[0]][1], pos[edge[1]][1]]
z_coords = [pos[edge[0]][2], pos[edge[1]][2]]
ax.plot(x_coords, y_coords, z_coords, color="gray", zorder=0)
# Highlight the specified edges
for edge in highlighted_edges_indices:
if edge in G.edges:
x_coords = [pos[edge[0]][0], pos[edge[1]][0]]
y_coords = [pos[edge[0]][1], pos[edge[1]][1]]
z_coords = [pos[edge[0]][2], pos[edge[1]][2]]
ax.plot(x_coords, y_coords, z_coords, color="red", linewidth=2, zorder=1)
# Set labels and show the plot
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.show()
# Plot each layer as a 2D graph
for z in range(num_layers):
# Define nodes and edges for the current layer
layer_nodes = list(range(16)) # Always include nodes 0 to 15
layer_edges = [(i % 16, j % 16) for i, j in G.edges if pos[i][2] == z and pos[j][2] == z]
# Create a subgraph for the layer
layer_graph = nx.DiGraph()
layer_graph.add_nodes_from(layer_nodes)
layer_graph.add_edges_from(layer_edges)
# Define 2D positions for the layer
layer_pos = {node: (node % 4, node // 4) for node in layer_nodes}
# Plot the layer
plt.figure(figsize=(6, 6))
nx.draw(layer_graph, pos=layer_pos, with_labels=True, node_color="lightblue", edge_color="gray", node_size=500,arrows=False)
nx.draw_networkx_edges(layer_graph, pos=layer_pos, edgelist=[(i % 16, j % 16) for i, j in highlighted_edges_indices_standard if pos[i][2] == z and pos[j][2] == z], edge_color="red", width=2)
plt.title(f"Layer {z}")
plt.show()
# Create a combined graph
combined_graph = nx.DiGraph()
# Add all nodes and edges from all layers
for z in range(num_layers):
layer_nodes = list(range(16)) # Nodes 0 to 15 for each layer
layer_edges = [(i % 16, j % 16) for i, j in G.edges if pos[i][2] == z and pos[j][2] == z]
combined_graph.add_nodes_from(layer_nodes)
combined_graph.add_edges_from(layer_edges)
# Define 2D positions for the combined graph
combined_pos = {node: (node % 4, node // 4) for node in range(16)}
# Highlighted edges for each layer
highlighted_edges_per_layer = [
[(i % 16, j % 16) for i, j in highlighted_edges_indices_standard if pos[i][2] == z and pos[j][2] == z]
for z in range(num_layers)
]
# Generate a color map for the layers
colors = cm.get_cmap("tab10", num_layers) # Use a colormap with `num_layers` distinct colors
# Plot the combined graph
plt.figure(figsize=(8, 8))
nx.draw(combined_graph, pos=combined_pos, with_labels=True, node_color="lightblue", edge_color="gray", node_size=500, arrows=False)
# Draw highlighted edges for each layer with a different color
for z, edges in enumerate(highlighted_edges_per_layer):
nx.draw_networkx_edges(
combined_graph,
pos=combined_pos,
edgelist=edges,
edge_color=[colors(z)],
width=2,
label=f"Path {z}"
)
# Add a legend for the layers
handles = [plt.Line2D([0], [0], color=colors(z), lw=2, label=f"Path {z}") for z in range(num_layers)]
plt.legend(handles=handles, loc="upper left", title="Paths")
plt.title("Combined Plot of Highlighted Paths Across All Layers")
fig = plt.gcf() # Get the current figure
with open("/home/sfischer/Documents/projects/wk_LinProg/LinProg_Scripts/graph_plot.pkl", "wb") as f:
pickle.dump((combined_graph, combined_pos,highlighted_edges_per_layer,num_layers), f)
print("Plot saved to graph_plot.pkl")
plt.show()
plt.close(fig)
def coord_to_number(i, j, mesh_size):
return i * mesh_size + j