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