コード例 #1
0
ファイル: cmt_2phase.py プロジェクト: mschmidt87/VeRyPy
def _phase_two(mu_multiplier,route_seeds, D,d,C,L, rr,
               choose_most_associated_route = True,
               repeated_association_with_n_routes=1):
    ## Step 0: reuse seed nodes from phase 1 to act as route seed points
    N = len(D)
    K = len(route_seeds)

    customer_nodes = range(1,N)
    if rr is not None: 
        #->stochastic version, resolve the ties randomly
        shuffle(customer_nodes)
    unrouted_nodes = OrderedSet(customer_nodes)
    unrouted_nodes.difference_update( route_seeds )
    
    # routes are stored in dict with a key of route seed,
    #  the route, demand, cost, and if it is updated are all stored
    routes = [ RouteState( [0, rs],            #initial route
                            d[rs] if C else 0, #initial demand 
                            D[0,rs]+D[rs,0],   #initial cost
                            True) for rs in route_seeds ]
    
    insertion_infeasible = [[False]*N for rs in route_seeds ] 
            
    if __debug__:
        log(DEBUG, "## Parallel route building phase with seeds %s ##" %
                  str(list(route_seeds)))
    
    ## Step 1.1: vectorized calculation of eps
    # TODO: this also calculates eps for depot and seeds. Omitting those would 
    #  be possible and save few CPU cycles, but it would make indexing more
    #  complex and because accuracy>simplicity>speed, it is the way it is.
        
    eps = ( np.tile(D[[0],:],(K, 1)) 
            +mu_multiplier*D[:,route_seeds].transpose()
            -np.tile(D[0,route_seeds],(N,1)).transpose() )
    
    associate_to_nth_best_route = 1
    insertions_made = False
    route_seed_idxs = []
    insertions_made = False
    first_try = True
    
    try:
        while unrouted_nodes:                
            ## Main while loop bookkeeping
            if not route_seed_idxs:
                idxs = range(K)
                if rr is not None: #->stocastic version, construct routes in random order
                    shuffle(idxs)
                route_seed_idxs = deque(idxs)
                
                if not first_try:
                    # The CMT1979 exits when all routes have been tried
                    if repeated_association_with_n_routes is None:
                        break            
                
                    if not insertions_made:
                        associate_to_nth_best_route+=1 # for the next round
                    if associate_to_nth_best_route>\
                       repeated_association_with_n_routes:
                        break
                
                first_try = False
                insertions_made = False
                                
            ## Step 2.1: Choose a (any) route to add customers to.        
        
            # some nodes may have been routed, update these
            eps_unrouted = eps[:,unrouted_nodes]
            
            ## Step 1.2: Associate each node to a route
            # note: the assignments cannot be calculated beforehand, as we do 
            #  not know which customers will be (were?) "left over" in the 
            #  previous route building steps 3. 
            
            if associate_to_nth_best_route==1 or len(route_seed_idxs)==1:
                r_stars_unrouted = np.argmin(eps_unrouted[route_seed_idxs,:], axis=0)    
            else:
                ## note: an extension for the deterministic variant,
                # get smallest AND 2. smallest at the same time using argpartition
                #top = np.argsort(eps, axis=0)[:associate_with_n_routes, :]
                #r_stars = [ top[i,:] for i in range(associate_with_n_routes) ]
                if len(route_seed_idxs)<associate_to_nth_best_route:
                    route_seed_idxs = []
                    continue
                    
                #take_nth = min(len(route_seed_idxs), associate_to_nth_best_route)-1
                take_nth = associate_to_nth_best_route-1
                reorder_rows_per_col_idxs = np.argpartition(eps_unrouted[route_seed_idxs,:],
                                              take_nth, axis=0)
                nth_best, unrouted_node_order = np.where(
                        reorder_rows_per_col_idxs==take_nth)
                r_stars_unrouted = nth_best[ np.argsort(unrouted_node_order) ]
            
            if choose_most_associated_route:
                unique, counts = np.unique(r_stars_unrouted, return_counts=True)
                seed_idx_idx = unique[np.argmax(counts)]
                route_seed_idx = route_seed_idxs[seed_idx_idx]
                route_seed_idxs.remove(route_seed_idx)
                associated_cols = list(np.where(r_stars_unrouted==seed_idx_idx)[0])
            else:
                route_seed_idx = route_seed_idxs.popleft()
                associated_cols = list(np.where(r_stars_unrouted==0)[0])
                
            route,route_demand,route_cost,route_l_updated = routes[route_seed_idx]
                
            ## Step 2.2: Vectorized calculation of sigma score for the customers
            #             associated to the chosen route.
            eps_bar = eps_unrouted[route_seed_idx,associated_cols]
            
            # NOTE: CMT 1979 does not specify what happens if S is empty, we assume
            #  we need (and can) omit the calculation of eps_prime in this case.
            
            brdcast_rs_idxs = [[rsi] for rsi in route_seed_idxs]
            if route_seed_idxs:
                eps_prime = np.min(eps_unrouted[brdcast_rs_idxs, associated_cols],
                                   axis=0)
                sigmas = eps_prime-eps_bar
            else:
                # last route, try to add rest of the nodes
                eps_prime = None
                sigmas = -eps_bar
                
            col_to_node = [unrouted_nodes[c] for c in associated_cols]
            sigma_ls = zip(sigmas.tolist(), col_to_node)
            sigma_ls.sort(reverse=True)
            
            if __debug__:
                log(DEBUG, "Assigning associated nodes %s to a route %s (seed n%d)"%
                          (str(col_to_node), str(route+[0]),route_seeds[route_seed_idx]))
            
            ## Step 3: insert feasible customers from the biggest sigma first
            for sigma, l_star in sigma_ls:
                if __debug__:
                    log(DEBUG-1, "Check feasibility of inserting "+\
                        "n%d with sigma=%.2f"%(l_star,sigma))
                
                if C and route_demand+d[l_star]-C_EPS>C:
                    if __debug__:
                        log(DEBUG-1, "Insertion would break C constraint.")
                    continue
                
                # use cached L feasibility check
                if L and insertion_infeasible[route_seed_idx][l_star]:
                    continue
                
                # Do not run TSP algorithm after every insertion, instead calculate
                #  a simple a upper bound for the route_cost and use that.
                UB_route_cost = (route_cost-
                                   D[route[-1],0]+
                                   D[route[-1],l_star]+D[l_star,0])
                if L and UB_route_cost-S_EPS>L:
                       
                    # check the real TSP cost
                    new_route, new_route_cost = solve_tsp(D, route+[l_star])
    
                    if __debug__:
                        log(DEBUG-1, "Got TSP solution %s (%.2f)" %
                            (str(new_route), new_route_cost, ))
                        
                    if new_route_cost-S_EPS>L:
                        if __debug__:
                            log(DEBUG-1,"DEBUG: Insertion would break L constraint.")
                        insertion_infeasible[route_seed_idx][l_star] = True
                        continue
                    
                    route_cost = new_route_cost
                    route=new_route[:-1]
                    route_l_updated = True
                else:
                    route_l_updated = False
                    route_cost = UB_route_cost
                    route = route+[l_star]
                
                if C: route_demand+=d[l_star]
                unrouted_nodes.remove(l_star)
                insertions_made = True
                
                if __debug__:
                    log(DEBUG, "Inserted n%d to create a route %s."%(l_star, route))
        
        
            # All feasible insertions of the associated customers is done, record
            #  the modified route.
            if insertions_made:
                routes[route_seed_idx] =  RouteState( route,       #updated route
                                                  route_demand,    #updated demand
                                                  route_cost,      #updated cost 
                                                  route_l_updated) #cost state
    except KeyboardInterrupt: #or SIGINT
        rs_sol, _ = _routestates2solution(routes, D)
        interrupted_sol = rs_sol[:-1]+routes2sol([n] for n in unrouted_nodes
                                                if n not in rs_sol)
        raise KeyboardInterrupt(interrupted_sol)
    
    ## Step 4: Redo step 1 or construct the solution and exit 
    if len(unrouted_nodes)>0:
        if __debug__:
            log(DEBUG, "Phase 2 failed to create feasbile solution with %d routes."%K)
            log(DEBUG-1, "Nodes %s remain unrouted."%str(list(unrouted_nodes)))
        return 0, None, None, rr
    else:
        sol, total_cost = _routestates2solution(routes, D)
        if __debug__:
            log(DEBUG, "Phase 2 solution %s (%.2f) complete."%(str(sol),total_cost))        
        return K, sol, total_cost, rr