0

I am a beginner python user and have been working through a project using different tutorials. One of them is solving the chinese postman problem with matplotlib graphs and the other making a basic GUI with tkinter.

I have tried looking at this question: Tkinter gui graph

but could not really understand what was happening.

Would I have to have a separate .py file and then import my chinese postman problem solution to my other gui related .py file?

I currently have the separate codes for both of them but they are too long to post.

I have used:

class StartPage(tk.Frame):

def __init__(self, parent, controller):
    tk.Frame.__init__(self,parent)
    label = tk.Label(self, text="Start Page", font=LARGE_FONT)
    label.pack(pady=10,padx=10)

    button = ttk.Button(self, text="Visit Page 1",
                       command=lambda: controller.show_frame(PageOne))
    button.pack()

as a code for one of the pages on tkinter


Chinese postman problem code:

df1=pd.read_csv(r"U:\\user\nodes_fixed.csv")
print(df1)

df2=pd.read_csv(r"U:\\user\edge_list_3_fixed.csv")
df2= df2.dropna()
print(df2)

df1.columns

g=nx.Graph()
# Add edges and edge attributes
for i, elrow in df2.iterrows():
# g.add_edge(elrow[0], elrow[1], attr_dict=elrow[2:].to_dict())  # 
deprecated after NX 1.11
g.add_edge(elrow[0], elrow[1], **elrow[2:].to_dict())


# Edge list example
print(elrow[0]) # node1
print(elrow[1]) # node2
print(elrow[2:].to_dict()) # edge attribute dict


# Add node attributes
for i, nlrow in df1.iterrows():
# g.node[nlrow['id']] = nlrow[1:].to_dict() 
nx.set_node_attributes(g, {nlrow['ID']:  nlrow[1:].to_dict()}) 

# Node list example
print(nlrow)

# Preview first 5 edges

list(g.edges(data=True))[0:5] 

# Preview first 10 nodes

list(g.nodes(data=True))[0:10] 

print('# of edges: {}'.format(g.number_of_edges()))
print('# of nodes: {}'.format(g.number_of_nodes()))

# Define node positions data structure (dict) for plotting
for node in g.nodes(data=True):
print(node)
print("")
node_positions = {node[0]: (node[1]['X'], -node[1]['Y']) for node in 
g.nodes(data=True)}

# Preview of node_positions
dict(list(node_positions.items())[0:5])

# Define data structure (list) of edge colors for plotting

# edge_colors = [e[2]['color'] for e in g.edges(data=True)]  
edge_colors = [e[2]['color'] for e in list(g.edges(data=True))]

# Preview first 10
edge_colors[0:10]
plt.figure(figsize=(8, 6))
nx.draw(g, pos=node_positions, edge_color=edge_colors, node_size=10, node_color='black')
plt.title('Graph Representation of repair trail', size=15)
plt.show()
#####
# Calculate list of nodes with odd degree
# nodes_odd_degree = [v for v, d in g.degree_iter() if d % 2 == 1]  # deprecated after NX 1.11
nodes_odd_degree = [v for v, d in g.degree() if d % 2 == 1]

# Preview
nodes_odd_degree[0:5]

# Counts
print('Number of nodes of odd degree: {}'.format(len(nodes_odd_degree)))
print('Number of total nodes: {}'.format(len(g.nodes())))

# Compute all pairs of odd nodes. in a list of tuples
odd_node_pairs = list(itertools.combinations(nodes_odd_degree, 2))

# Preview pairs of odd degree nodes
odd_node_pairs[0:10]

# Counts
print('Number of pairs: {}'.format(len(odd_node_pairs)))

def get_shortest_paths_distances(graph, pairs, edge_weight_name):
    """Compute shortest distance between each pair of nodes in a graph.  Return a dictionary keyed on node pairs (tuples)."""
    distances = {}
    for pair in pairs:
        distances[pair] = nx.dijkstra_path_length(graph, pair[0], pair[1], weight=edge_weight_name)
    return distances

# Compute shortest paths.  Return a dictionary with node pairs keys and a single value equal to shortest path distance.
odd_node_pairs_shortest_paths = get_shortest_paths_distances(g, odd_node_pairs, 'distance')


dict(list(odd_node_pairs_shortest_paths.items())[0:10])

def create_complete_graph(pair_weights, flip_weights=True):
    """
    Create a completely connected graph using a list of vertex pairs and the shortest path distances between them
    Parameters: 
        pair_weights: list[tuple] from the output of get_shortest_paths_distances
        flip_weights: Boolean. Should we negate the edge attribute in pair_weights?
    """
    g = nx.Graph()
    for k, v in pair_weights.items():
        wt_i = - v if flip_weights else v
        # g.add_edge(k[0], k[1], {'distance': v, 'weight': wt_i})  
        g.add_edge(k[0], k[1], **{'distance': v, 'weight': wt_i})  
    return g

# Generate the complete graph
g_odd_complete = create_complete_graph(odd_node_pairs_shortest_paths, flip_weights=True)

# Counts
print('Number of nodes: {}'.format(len(g_odd_complete.nodes())))
print('Number of edges: {}'.format(len(g_odd_complete.edges())))

# Plot the complete graph of odd-degree nodes
plt.figure(figsize=(8, 6))
pos_random = nx.random_layout(g_odd_complete)
nx.draw_networkx_nodes(g_odd_complete, node_positions, node_size=20, node_color="red")
nx.draw_networkx_edges(g_odd_complete, node_positions, alpha=0.1)
plt.axis('off')
plt.title('Complete Graph of Odd-degree Nodes')
plt.show()

# Compute min weight matching.
# Note: max_weight_matching uses the 'weight' attribute by default as the attribute to maximize.
odd_matching_dupes= nx.algorithms.max_weight_matching(g_odd_complete, True)

print('Number of edges in matching: {}'.format(len(odd_matching_dupes)))

# Preview of matching with dupes
odd_matching_dupes

# Convert matching to list of deduped tuples
odd_matching = list(pd.unique([tuple(sorted([k, v])) for k, v in odd_matching_dupes]))

#Counts
print('Number of edges in matching (deduped): {}'.format(len(odd_matching)))

# Preview of deduped matching
odd_matching

plt.figure(figsize=(8, 6))

# Plot the complete graph of odd-degree nodes
nx.draw(g_odd_complete, pos=node_positions, node_size=20, alpha=0.05)



# Create a new graph to overlay on g_odd_complete with just the edges from the min weight matching
g_odd_complete_min_edges = nx.Graph(odd_matching)
nx.draw(g_odd_complete_min_edges, pos=node_positions, node_size=20, edge_color='blue', node_color='red')

plt.title('Min Weight Matching on Complete Graph')
plt.show()

plt.figure(figsize=(8, 6))

# Plot the original trail map graph
nx.draw(g, pos=node_positions, node_size=20, alpha=0.1, node_color='black')

# Plot graph to overlay with just the edges from the min weight matching
nx.draw(g_odd_complete_min_edges, pos=node_positions, node_size=20, alpha=1, node_color='red', edge_color='blue')

plt.title('Min Weight Matching on Orginal Graph')
plt.show()

# Preview of deduped matching
odd_matching

plt.figure(figsize=(8, 6))

# Plot the complete graph of odd-degree nodes
nx.draw(g_odd_complete, pos=node_positions, node_size=20, alpha=0.05)

# Create a new graph to overlay on g_odd_complete with just the edges from the min weight matching
g_odd_complete_min_edges = nx.Graph(odd_matching)
nx.draw(g_odd_complete_min_edges, pos=node_positions, node_size=20, edge_color='blue', node_color='red')

plt.title('Min Weight Matching on Complete Graph')
plt.show()

plt.figure(figsize=(8, 6))

def add_augmenting_path_to_graph(graph, min_weight_pairs):
    """
    Add the min weight matching edges to the original graph
    Parameters:
        graph: NetworkX graph (original graph from trailmap)
        min_weight_pairs: list[tuples] of node pairs from min weight matching
    Returns:
        augmented NetworkX graph
    """

    # We need to make the augmented graph a MultiGraph so we can add parallel edges
    graph_aug=nx.MultiGraph(graph.copy())
    for pair in min_weight_pairs:
        graph_aug.add_edge(pair[0], 
                           pair[1], 
                           **{'Time': nx.dijkstra_path_length(graph, pair[0], pair[1]), 'Trail': 'augmented'}
                           # attr_dict={'distance': nx.dijkstra_path_length(graph, pair[0], pair[1]),
                           #            'trail': 'augmented'}  # deprecated after 1.11
                          )
    return graph_aug

#Create augmented graph: add the min weight matching edges to g
g_aug=add_augmenting_path_to_graph(g, odd_matching)

#Counts
print('Number of edges in original graph: {}'.format(len(g.edges())))
print('Number of edges in augmented graph: {}'.format(len(g_aug.edges())))

# pd.value_counts(g_aug.degree())  # deprecated after NX 1.11
pd.value_counts([e[1] for e in g_aug.degree()])

##Check again 

naive_euler_circuit = list(nx.eulerian_circuit(g_aug, source='rep1'))
print('Length of eulerian circuit: {}'.format(len(naive_euler_circuit)))

naive_euler_circuit[0:10]

def create_eulerian_circuit(graph_augmented, graph_original, starting_node=None):
    """Create the eulerian path using only edges from the original graph."""
    euler_circuit = []
    naive_circuit = list(nx.eulerian_circuit(graph_augmented, source=starting_node))

    for edge in naive_circuit:
        edge_data = graph_augmented.get_edge_data(edge[0], edge[1])    

        if edge_data[0]['Time'] != 'augmented':
            # If `edge` exists in original graph, grab the edge attributes and 
    add to eulerian circuit.
            edge_att = graph_original[edge[0]][edge[1]]
            euler_circuit.append((edge[0], edge[1], edge_att)) 
        else: 
            aug_path = nx.shortest_path(graph_original, edge[0], edge[1], 
    weight='Time')
            aug_path_pairs = list(zip(aug_path[:-1], aug_path[1:]))

            print('Filling in edges for augmented edge: {}'.format(edge))
            print('Augmenting path: {}'.format(' => '.join(aug_path)))
            print('Augmenting path pairs: {}\n'.format(aug_path_pairs))

            # If `edge` does not exist in original graph, find the shortest 
    path between its nodes and 
            #  add the edge attributes for each link in the shortest path.
            for edge_aug in aug_path_pairs:
                edge_aug_att = graph_original[edge_aug[0]][edge_aug[1]]
                euler_circuit.append((edge_aug[0], edge_aug[1], edge_aug_att))

    return euler_circuit

    # Create the Eulerian circuit
    euler_circuit = create_eulerian_circuit(g_aug, g, 'rep1')

    print('Length of Eulerian circuit: {}'.format(len(euler_circuit)))

    # Preview first 20 directions of CPP solution
    for i, edge in enumerate(euler_circuit[0:20]):
    print(i, edge)

    # Computing some stats
    _vcn = pd.value_counts(pd.value_counts([(e[0]) for e in euler_circuit]), 
    sort=False)
    node_visits = pd.DataFrame({'n_visits': _vcn.index, 'n_nodes': 
    _vcn.values})
    _vce = pd.value_counts(pd.value_counts([sorted(e)[0] + sorted(e)[1] for e 
    in nx.MultiDiGraph(euler_circuit).edges()]))
    edge_visits = pd.DataFrame({'n_visits': _vce.index, 'n_edges': 
    _vce.values})

    # Printing stats

    print('Number of edges in circuit: {}'.format(len(euler_circuit)))
    print('Number of edges in original graph: {}'.format(len(g.edges())))
    print('Number of nodes in original graph: {}\n'.format(len(g.nodes())))

    print('Number of edges traversed more than once: 
    {}\n'.format(len(euler_circuit)-len(g.edges())))  

    print('Number of times visiting each node:')
    print(node_visits.to_string(index=False))

    print('\nNumber of times visiting each edge:')
    print(edge_visits.to_string(index=False))

    def create_cpp_edgelist(euler_circuit):
    """
    Create the edgelist without parallel edge for the visualization
    Combine duplicate edges and keep track of their sequence and # of walks
    Parameters:
        euler_circuit: list[tuple] from create_eulerian_circuit
    """
    cpp_edgelist = {}

    for i, e in enumerate(euler_circuit):
        edge = frozenset([e[0], e[1]])

        if edge in cpp_edgelist:
            cpp_edgelist[edge][2]['sequence'] += ', ' + str(i)
            cpp_edgelist[edge][2]['visits'] += 1

        else:
            cpp_edgelist[edge] = e
            cpp_edgelist[edge][2]['sequence'] = str(i)
            cpp_edgelist[edge][2]['visits'] = 1

    return list(cpp_edgelist.values())

    cpp_edgelist = create_cpp_edgelist(euler_circuit)

    print('Number of edges in CPP edge list: {}'.format(len(cpp_edgelist)))

    # Preview CPP plot-friendly edge list
    cpp_edgelist[0:3]

    # Create CPP solution graph
    g_cpp = nx.Graph(cpp_edgelist)

     plt.figure(figsize=(14, 10))

     visit_colors = {1:'lightgray', 2:'blue'}
     edge_colors = [visit_colors[e[2]['visits']] for e in 
     g_cpp.edges(data=True)]
     node_colors = ['red'  if node in nodes_odd_degree else 'lightgray' for 
     node in 
     g_cpp.nodes()]

    nx.draw_networkx(g_cpp, pos=node_positions, node_size=20, 
    node_color=node_colors, edge_color=edge_colors, with_labels=False)
    plt.axis('off')
    plt.show()

    plt.figure(figsize=(14, 10))

    edge_colors = [e[2]['color'] for e in g_cpp.edges(data=True)]
    nx.draw_networkx(g_cpp, pos=node_positions, node_size=10, 
    node_color='black', 
    edge_color=edge_colors, with_labels=False, alpha=0.5)

    bbox = {'ec':[1,1,1,0], 'fc':[1,1,1,0]}  # hack to label edges over line 
    (rather than breaking up line)
    edge_labels = nx.get_edge_attributes(g_cpp, 'sequence')
    nx.draw_networkx_edge_labels(g_cpp, pos=node_positions, 
    edge_labels=edge_labels, bbox=bbox, font_size=6)

    plt.axis('off')
    plt.show()

    visit_colors = {1:'black', 2:'red'}
    edge_cnter = {}
    g_i_edge_colors = []
    for i, e in enumerate(euler_circuit, start=1):

    edge = frozenset([e[0], e[1]])
    if edge in edge_cnter:
        edge_cnter[edge] += 1
    else:
        edge_cnter[edge] = 1

   # Full graph (faded in background) 
    nx.draw_networkx(g_cpp, pos=node_positions, node_size=6, 
    node_color='gray', with_labels=False, alpha=0.07)


    # Edges walked as of iteration i
    euler_circuit_i = copy.deepcopy(euler_circuit[0:i])
    for i in range(len(euler_circuit_i)):
        edge_i = frozenset([euler_circuit_i[i][0], euler_circuit_i[i][1]])
        euler_circuit_i[i][2]['visits_i'] = edge_cnter[edge_i]
    g_i = nx.Graph(euler_circuit_i)
    g_i_edge_colors = [visit_colors[e[2]['visits_i']] for e in 
    g_i.edges(data=True)]
    nx.draw_networkx_nodes(g_i, pos=node_positions, node_size=6, alpha=0.6, 
    node_color='lightgray', with_labels=False, linewidths=0.1)
    nx.draw_networkx_edges(g_i, pos=node_positions, 
    edge_color=g_i_edge_colors, alpha=0.8)

    plt.axis('off')
    plt.savefig('trial.png'.format(i), dpi=120, bbox_inches='tight')
    plt.close()
Kaori21
  • 43
  • 11
  • To answer the part where `you are not sure if you need separate files for your program ` can be answered by saying, its not necessary. If you wish you can have it all in one file – shuberman Aug 13 '19 at 15:08
  • @mishsx Could I also possibly ask as to how I would incorporate the graphs to the tkinter code? I have coded a page but I don't really know how to combine the graph to each of the 'pages'. – Kaori21 Aug 13 '19 at 15:16
  • Lets see your solutions. If you can please post it here. Don't worry about downvotes. I'm sure you wont get down votes on posting information. – shuberman Aug 13 '19 at 15:19
  • @mishsx how much of the solutions would you need to see for the chinese postman problem code? I am a bit unsure which would be relevant to post or not, but I have just put the code I have done for 1 page – Kaori21 Aug 13 '19 at 15:21
  • Basically, is there some issue you are facing ? or your query is more oriented towards code compactness? In latter case you would have to post the entire code to see where we can cut corners. – shuberman Aug 13 '19 at 15:23
  • I am just unsure as to how I would incorporate my graphs/ solution from the other code to the code above, so that it shows on the different pages – Kaori21 Aug 13 '19 at 15:23
  • So if I am comprehending your query properly, you want to include the solution from the answer from a Stack Overflow post to your code? If that is the case and your code is long.Please post the link to your post from this website https://pastebin.com/ and share the URL in your post description. That way you dont have to worry about your post gettting too long – shuberman Aug 13 '19 at 15:26
  • @mishsx my apologies as the firewall at work will not allow me to access this website – Kaori21 Aug 14 '19 at 06:17
  • @mishsx I have added the code here instead. Is there any way I could instead have the plt.show() for the graphs appear in separate pages of the GUI? I have about 8 figures of graphs but want to integrate them into separate pages in the GUI – Kaori21 Aug 14 '19 at 07:49
  • If your query is to have graphs on different pages, this post might be helpful - https://stackoverflow.com/a/14819141/7841468 – shuberman Aug 14 '19 at 09:13
  • @mishsx I am currently trying to add a plot page, but I am getting an error and it is telling me that one of the csv that I have passed on it was not defined, however, when I open my chinese postman solution on it's own, it works fine – Kaori21 Aug 14 '19 at 09:24
  • @mishsx I have made a code on stacking different pages together, it is now integrating the code above into tkinter that I am currently struggling with. My apologies if I am not explaining my question well – Kaori21 Aug 14 '19 at 09:25
  • Please post a new question describing that error and the piece of code that concerns it. Because even after answering you query here would result a downvote to my answer since it is not related to what you originally asked. Hope you understand my concern. – shuberman Aug 14 '19 at 09:26
  • @mishsx yes that would be fine, my apologies as I am new to this website – Kaori21 Aug 14 '19 at 09:28
  • It seems you have trouble understanding what you were having doubts about, why don't you take a while and organize your thoughts and then post a new question containing `1.Your effort` `2. The problem you are facing` `3. What is the expected output you want?` – shuberman Aug 14 '19 at 09:29

0 Answers0