0

This is my first question here as I've usually found helpful answers by searching previous posts. I cannot find any that relate to my particular problem this time. I need to complete a branch and bound function in Python (it's for a Coursera course), and I cannot figure out how to keep the algorithm going after it finds the first cycle. I would appreciate any hints or guidance you can give. This is the most frustrated I've been with a programing assignment - and I've been taking Coursera courses for 2 years.

Most of the code has been given to us, and my portion is near the end. The graph is undirected and complete.

`# This function computes a lower bound on the length of Hamiltonian cycles starting with vertices in the list sub_cycle.
def lower_bound(g, sub_cycle):
    # The weight of the current path.
    current_weight = sum([g[sub_cycle[i]][sub_cycle[i + 1]]['weight'] for i in range(len(sub_cycle) - 1)])
    
    # For convenience we create a new graph which only contains vertices not used by g.
    unused = [v for v in g.nodes() if v not in sub_cycle]
    h = g.subgraph(unused)
    

    # Compute the weight of a minimum spanning tree.
    t = list(nx.minimum_spanning_edges(h))
    
    mst_weight = sum([h.get_edge_data(e[0], e[1])['weight'] for e in t])
    
    # If the current sub_cycle is "trivial" (i.e., it contains no vertices or all vertices), then our lower bound is
    # just the sum of the weight of a minimum spanning tree and the current weight.
    if len(sub_cycle) == 0 or len(sub_cycle) == g.number_of_nodes():
        
        return mst_weight + current_weight
    

    # If the current sub_cycle is not trivial, then we can also add the weight of two edges connecting the vertices
    # from sub_cycle and the remaining part of the graph.
    # s is the first vertex of the sub_cycle
    s = sub_cycle[0]
    # t is the last vertex of the sub_cycle
    t = sub_cycle[-1]
    # The minimum weight of an edge connecting a vertex from outside of sub_sycle to s.
    min_to_s_weight = min([g[v][s]['weight'] for v in g.nodes() if v not in sub_cycle])
    # The minimum weight of an edge connecting the vertex t to a vertex from outside of sub_cycle.
    min_from_t_weight = min([g[t][v]['weight'] for v in g.nodes() if v not in sub_cycle])

    # Any cycle which starts with sub_cycle must be of length:
    # the weight of the edges from sub_cycle +
    # the minimum weight of an edge connecting sub_cycle and the remaining vertices +
    # the minimum weight of a spanning tree on the remaining vertices +
    # the minimum weight of an edge connecting the remaining vertices to sub_cycle.
    
    return current_weight + min_from_t_weight + mst_weight + min_to_s_weight


# The branch and bound procedure takes
# 1. a graph g;
# 2. the current sub_cycle, i.e. several first vertices of cycle under consideration.
# Initially sub_cycle is empty;
# 3. currently best solution current_min, so that we don't even consider paths of greater weight.
# Initially the min weight is infinite
def branch_and_bound(g, sub_cycle=None, current_min=float("inf")):
    used_nodes = list()
    # If the current path is empty, then we can safely assume that it starts with the vertex 0.
    if sub_cycle is None:
        sub_cycle = [0]
          
    # If we already have all vertices in the cycle, then we just compute the weight of this cycle and return it.
    if len(sub_cycle) == g.number_of_nodes():
        weight = sum([g[sub_cycle[i]][sub_cycle[i + 1]]['weight'] for i in range(len(sub_cycle) - 1)])
        weight = weight + g[sub_cycle[-1]][sub_cycle[0]]['weight']
        
        return weight

    # Now we look at all nodes which aren't yet used in sub_cycle.
    unused_nodes = list()
    
    for v in g.nodes():
        if v not in sub_cycle:
            unused_nodes.append((g[sub_cycle[-1]][v]['weight'], v))
    
    # We sort them by the distance from the "current node" -- the last node in sub_cycle.
    unused_nodes = sorted(unused_nodes)
   

    for (d, v) in unused_nodes:
        assert v not in sub_cycle
        
        extended_subcycle = list(sub_cycle)
        extended_subcycle.append(v)
                      
        
        # For each unused vertex, we check if there is any chance to find a shorter cycle if we add it now.
        if lower_bound(g, extended_subcycle) < current_min:

            **# THIS IS WHERE WE ARE TO ADD OUR PORTION OF THE CODE. 
            # If there is such a chance, we add the vertex to the current cycle, and proceed  recursively.
            # If we found a short cycle, then we update the current_min value**.
            
            
           if len(extended_subcycle) == g.number_of_nodes():
            
                current_min = sum([g[extended_subcycle[i]][extended_subcycle[i + 1]]['weight'] for i in range(len(extended_subcycle) - 1)])
                current_min = current_min + g[extended_subcycle[-1]][extended_subcycle[0]]['weight']
            
            
            sub_cycle.append(v)
            return branch_and_bound(g, sub_cycle, current_min)

    # The procedure returns the shortest cycle length.
    return current_min`

The optimal cycle should be [0,4,2,1,5,3] with a weight of 695.8588106976. No matter what I try, I keep getting [0,1,2,4,3,5] with a weight of 769.1010673076235.

I believe I'm missing an ELSE: statement; and I've tried a few; but the code was never evaluated. I confirmed this by adding print('else'); but it never printed so I removed the code.

I wish I could enumerate all of the other things I've tried; but I've been revising and re-running this code for two-three days now - so I can't recall specific details.

Here is the input I'm using. They are coordinates that are passed to a function that creates a graph. I've included it below.

coordinates3 = [(199, 59), (152, 117), (68, 87), (281, 161), (11, 53), (254, 227)]

def get_graph(coordinates):
    g = nx.Graph()
    n = len(coordinates)
    for i in range(n):
        for j in range(i + 1):
            g.add_edge(i, j, weight=dist(coordinates[i][0], coordinates[i][1], coordinates[j][0], coordinates[j][1]))
    return g

Edited to add: I'm leaving this question here in case it gets answered and someone else has this same issue. I have unenrolled from the class...my first one due to frustration at an assignment. It's been 5 days, and I've worked on it more than 3 hours each day with no improvement.

0 Answers0