double VRP::RTR_solve(int heuristics, int intensity, int max_stuck, int max_perturbs, double dev, int nlist_size, int perturb_type, int accept_type, bool verbose) { /// /// Uses the given parameters to generate a /// VRP solution via record-to-record travel. /// Assumes that data has already been imported into V and that we have /// some existing solution. /// Returns the objective function value of the best solution found /// // Make sure accept_type is either VRPH_BEST_ACCEPT or VRPH_FIRST_ACCEPT - matters only // for the downhill phase as we use VRPH_LI_ACCEPT in the diversification phase if(accept_type!=VRPH_BEST_ACCEPT && accept_type!=VRPH_FIRST_ACCEPT) report_error("%s: accept_type must be VRPH_BEST_ACCEPT or VRPH_FIRST_ACCEPT\n"); int ctr, n, j, i, R, random, fixed, neighbor_list, objective, tabu; random=fixed=neighbor_list=0; if(heuristics & VRPH_RANDOMIZED) random=VRPH_RANDOMIZED; if(heuristics & VRPH_FIXED_EDGES) fixed=VRPH_FIXED_EDGES; if(heuristics & VRPH_USE_NEIGHBOR_LIST) neighbor_list=VRPH_USE_NEIGHBOR_LIST; objective=VRPH_SAVINGS_ONLY; // default strategy if(heuristics & VRPH_MINIMIZE_NUM_ROUTES) objective=VRPH_MINIMIZE_NUM_ROUTES; if(heuristics & VRPH_TABU) { tabu=VRPH_TABU; // We will use a primitive Tabu Search in the uphill phase // Clear the tabu list this->tabu_list->empty(); } else tabu=0; n=num_nodes; // Define the heuristics we will use OnePointMove OPM; TwoPointMove TPM; TwoOpt TO; OrOpt OR; ThreeOpt ThreeO; CrossExchange CE; ThreePointMove ThreePM; double start_val; int *perm; perm=new int[this->num_nodes]; j=VRPH_ABS(this->next_array[VRPH_DEPOT]); for(i=0;i<this->num_nodes;i++) { perm[i]=j; if(!routed[j]) report_error("%s: Unrouted node in solution!!\n"); j=VRPH_ABS(this->next_array[j]); } if(j!=VRPH_DEPOT) report_error("%s: VRPH_DEPOT is not last node in solution!!\n"); int rules; // Set the neighbor list size used in the improvement search neighbor_list_size=VRPH_MIN(nlist_size, this->num_nodes); // Set the deviation deviation=dev; int num_perturbs=0; record=this->total_route_length; this->best_total_route_length=this->total_route_length; this->export_solution_buff(this->current_sol_buff); this->export_solution_buff(this->best_sol_buff); normalize_route_numbers(); ctr=0; uphill: // Start an uphill phase using the following "rules": double beginning_best=this->best_total_route_length; rules=VRPH_LI_ACCEPT+VRPH_RECORD_TO_RECORD+objective+random+fixed+neighbor_list+tabu; if(verbose) printf("Uphill starting at %5.2f\n",this->total_route_length); for(int k=1;k<intensity;k++) { start_val=total_route_length; if(heuristics & ONE_POINT_MOVE) { if(random) random_permutation(perm, this->num_nodes); for(i=1;i<=n;i++) { #if FIXED_DEBUG if(fixed && !check_fixed_edges("Before 1PM\n")) fprintf(stderr,"Error before OPM search(%d)\n",perm[i-1]); #endif OPM.search(this,perm[i-1],rules); #if FIXED_DEBUG if(fixed && !check_fixed_edges("After 1PM\n")) { fprintf(stderr,"Error after OPM search(%d)\n",perm[i-1]); this->show_route(this->route_num[perm[i-1]]); } #endif } } if(heuristics & TWO_POINT_MOVE) { if(random) random_permutation(perm, this->num_nodes); for(i=1;i<=n;i++) TPM.search(this,perm[i-1],rules + VRPH_INTER_ROUTE_ONLY); //check_fixed_edges("After 2PM\n"); } if(heuristics & THREE_POINT_MOVE) { if(random) random_permutation(perm, this->num_nodes); for(i=1;i<=n;i++) ThreePM.search(this,perm[i-1],rules + VRPH_INTER_ROUTE_ONLY); //check_fixed_edges("After 3PM\n"); } if(heuristics & TWO_OPT) { if(random) random_permutation(perm, this->num_nodes); for(i=1;i<=n;i++) TO.search(this,perm[i-1],rules); //check_fixed_edges("After TO\n"); } if(heuristics & OR_OPT) { if(random) random_permutation(perm, this->num_nodes); for(i=1;i<=n;i++) OR.search(this,perm[i-1],4,rules); for(i=1;i<=n;i++) OR.search(this,perm[i-1],3,rules); for(i=1;i<=n;i++) OR.search(this,perm[i-1],2,rules); //check_fixed_edges("After OR\n"); } if(heuristics & THREE_OPT) { normalize_route_numbers(); R=total_number_of_routes; for(i=1; i<=R; i++) ThreeO.route_search(this,i,rules-neighbor_list); //check_fixed_edges("After 3O\n"); } if(heuristics & CROSS_EXCHANGE) { normalize_route_numbers(); this->find_neighboring_routes(); R=total_number_of_routes; for(i=1; i<=R-1; i++) { for(j=0;j<1;j++) CE.route_search(this,i, route[i].neighboring_routes[j],rules-neighbor_list); } //check_fixed_edges("After CE\n"); } } if(total_route_length<record) record = total_route_length; if(verbose) { printf("Uphill complete\t(%d,%5.2f,%5.2f)\n",count_num_routes(),total_route_length, record); printf("# of recorded routes: %d[%d]\n",total_number_of_routes,count_num_routes()); } if(this->best_total_route_length<beginning_best-VRPH_EPSILON) { if(verbose) printf("New best found in uphill!\n"); // We found a new best solution during the uphill phase that might // now be "forgotten"!! I have seen this happen where it is never recovered // again, so we just import it and start the downhill phase with this solution... //this->import_solution_buff(this->best_sol_buff); } downhill: // Now enter a downhill phase double orig_val=total_route_length; if(verbose) printf("Downhill starting at %f (best=%f)\n",orig_val,this->best_total_route_length); if((heuristics & ONE_POINT_MOVE)|| (heuristics & KITCHEN_SINK) ) { rules=VRPH_DOWNHILL+objective+random+fixed+neighbor_list+accept_type; for(;;) { // One Point Move start_val=total_route_length; if(random) random_permutation(perm, this->num_nodes); for(i=1;i<=n;i++) OPM.search(this,perm[i-1],rules ); if(VRPH_ABS(total_route_length-start_val)<VRPH_EPSILON) break; } } if((heuristics & TWO_POINT_MOVE) || (heuristics & KITCHEN_SINK) ) { rules=VRPH_DOWNHILL+VRPH_INTER_ROUTE_ONLY+objective+random+fixed+neighbor_list+accept_type; for(;;) { // Two Point Move start_val=total_route_length; if(random) random_permutation(perm, this->num_nodes); for(i=1;i<=n;i++) TPM.search(this,perm[i-1],rules); if(VRPH_ABS(total_route_length-start_val)<VRPH_EPSILON) break; } } if((heuristics & TWO_OPT)|| (heuristics & KITCHEN_SINK) ) { // Do inter-route first a la Li rules=VRPH_DOWNHILL+VRPH_INTER_ROUTE_ONLY+objective+random+fixed+neighbor_list+accept_type; for(;;) { start_val=total_route_length; if(random) random_permutation(perm, this->num_nodes); for(i=1;i<=n;i++) TO.search(this,perm[i-1],rules); if(VRPH_ABS(total_route_length-start_val)<VRPH_EPSILON) break; } // Now do both intra and inter rules=VRPH_DOWNHILL+objective+random+fixed+neighbor_list+accept_type; for(;;) { start_val=total_route_length; if(random) random_permutation(perm, this->num_nodes); for(i=1;i<=n;i++) TO.search(this,perm[i-1],rules); if(VRPH_ABS(total_route_length-start_val)<VRPH_EPSILON) break; } } if((heuristics & THREE_POINT_MOVE) || (heuristics & KITCHEN_SINK) ) { rules=VRPH_DOWNHILL+VRPH_INTER_ROUTE_ONLY+objective+random+fixed+accept_type+neighbor_list; for(;;) { // Three Point Move start_val=total_route_length; if(random) random_permutation(perm, this->num_nodes); for(i=1;i<=n;i++) ThreePM.search(this,perm[i-1],rules); if(VRPH_ABS(total_route_length-start_val)<VRPH_EPSILON) break; } } if((heuristics & OR_OPT) || (heuristics & KITCHEN_SINK)) { rules=VRPH_DOWNHILL+ objective +random +fixed + accept_type + neighbor_list; for(;;) { // OrOpt start_val=total_route_length; if(random) random_permutation(perm, this->num_nodes); for(i=1;i<=n;i++) OR.search(this,perm[i-1],4,rules); for(i=1;i<=n;i++) OR.search(this,perm[i-1],3,rules); for(i=1;i<=n;i++) OR.search(this,perm[i-1],2,rules); if(VRPH_ABS(total_route_length-start_val)<VRPH_EPSILON) break; } } if((heuristics & THREE_OPT) || (heuristics & KITCHEN_SINK) ) { normalize_route_numbers(); R= total_number_of_routes; rules=VRPH_DOWNHILL+objective+VRPH_INTRA_ROUTE_ONLY+ random +fixed + accept_type; for(;;) { // 3OPT start_val=total_route_length; for(i=1;i<=R;i++) ThreeO.route_search(this,i,rules); if(VRPH_ABS(total_route_length-start_val)<VRPH_EPSILON) break; } } if( (heuristics & CROSS_EXCHANGE) ) { normalize_route_numbers(); this->find_neighboring_routes(); R=total_number_of_routes; rules=VRPH_DOWNHILL+objective+VRPH_INTRA_ROUTE_ONLY+ random +fixed + accept_type; for(i=1; i<=R-1; i++) { for(j=0;j<=1;j++) CE.route_search(this,i, route[i].neighboring_routes[j], rules); } } // Repeat the downhill phase until we find no more improvements if(total_route_length<orig_val-VRPH_EPSILON) goto downhill; if(verbose) printf("Downhill complete: %5.2f[downhill started at %f] (%5.2f)\n",total_route_length,orig_val, this->best_total_route_length); if(total_route_length < record-VRPH_EPSILON) { // New record - reset ctr ctr=1; record=total_route_length; } else ctr++; if(ctr<max_stuck) goto uphill; if(ctr==max_stuck) { if(num_perturbs<max_perturbs) { if(verbose) printf("perturbing\n"); if(perturb_type==VRPH_LI_PERTURB) perturb(); else osman_perturb(VRPH_MAX(20,num_nodes/10),.5+lcgrand(20)); // Reset record this->record=this->total_route_length; if(tabu) this->tabu_list->empty(); ctr=1; num_perturbs++; goto uphill; } } if(verbose) { if(has_service_times==false) printf("BEST OBJ: %f\n",best_total_route_length); else printf("BEST OBJ: %f\n",best_total_route_length-total_service_time); } delete [] perm; // Import the best solution found this->import_solution_buff(best_sol_buff); if(has_service_times==false) return best_total_route_length; else return best_total_route_length-total_service_time; }
double VRP::SA_solve(int heuristics, double start_temp, double cool_ratio, int iters_per_loop, int num_loops, int nlist_size, bool verbose) { /// /// Uses the given parameters to generate a VRP solution using Simulated Annealing. /// Assumes that data has already been imported into V and that we have /// some existing solution. Returns the total route length of the best solution found. /// this->temperature = start_temp; this->cooling_ratio = cool_ratio; int ctr, n, j, i, R, rules, random, fixed, neighbor_list, objective; if(heuristics & VRPH_RANDOMIZED) random=VRPH_RANDOMIZED; else random=0; if(heuristics & VRPH_FIXED_EDGES) fixed=VRPH_FIXED_EDGES; else fixed=0; if(heuristics & VRPH_USE_NEIGHBOR_LIST) neighbor_list=VRPH_USE_NEIGHBOR_LIST; else neighbor_list=0; objective=VRPH_SAVINGS_ONLY; // default strategy if(heuristics & VRPH_MINIMIZE_NUM_ROUTES) objective=VRPH_MINIMIZE_NUM_ROUTES; n=num_nodes; // The perm[] array will contain all the nodes in the current solution int *perm; perm=new int[this->num_nodes]; j=VRPH_ABS(this->next_array[VRPH_DEPOT]); for(i=0;i<this->num_nodes;i++) { perm[i]=j; if(!routed[j]) report_error("%s: Unrouted node in solution!!\n"); j=VRPH_ABS(this->next_array[j]); } if(j!=VRPH_DEPOT) report_error("%s: VRPH_DEPOT is not last node in solution!!\n"); // Define the heuristics we may use OnePointMove OPM; TwoPointMove TPM; TwoOpt TO; OrOpt OR; ThreeOpt ThreeO; CrossExchange CE; ThreePointMove ThreePM; double start_val; this->export_solution_buff(this->best_sol_buff); // We are assuming we have an existing solution // Set the neighbor list size used in the improvement search this->neighbor_list_size=VRPH_MIN(nlist_size, num_nodes); best_total_route_length=this->total_route_length; normalize_route_numbers(); ctr=0; // The idea is to perform num_loops loops of num_iters iterations each. // For each iteration, run through the given heuristic operation at random // and perform a Simulated Annealing search. rules=VRPH_USE_NEIGHBOR_LIST+VRPH_FIRST_ACCEPT+VRPH_SIMULATED_ANNEALING+VRPH_SAVINGS_ONLY; double worst_obj=0; for(ctr=0;ctr<num_loops;ctr++) { if(verbose) { printf("\nctr=%d of %d, temp=%f, obj=%f (overall best=%f; worst=%f)\n",ctr,num_loops, this->temperature, this->total_route_length,this->best_total_route_length,worst_obj); fflush(stdout); } // Reset worst_obj; worst_obj=0; // Cool it... this->temperature = this->cooling_ratio * this->temperature; for(int k=0; k < iters_per_loop; k++) { start_val=total_route_length; if(heuristics & THREE_OPT) { rules=VRPH_SIMULATED_ANNEALING+VRPH_INTRA_ROUTE_ONLY+random+fixed+objective; normalize_route_numbers(); R=total_number_of_routes; for(i=1; i<=R; i++) { ThreeO.route_search(this,i,rules); if(this->total_route_length > worst_obj) worst_obj=this->total_route_length; } } if(heuristics & ONE_POINT_MOVE) { rules=VRPH_SIMULATED_ANNEALING+neighbor_list+random+fixed+objective; if(random) random_permutation(perm, this->num_nodes); for(i=1;i<=n;i++) { OPM.search(this,perm[i-1],rules); if(this->total_route_length > worst_obj) worst_obj=this->total_route_length; } } if(heuristics & TWO_POINT_MOVE) { rules=VRPH_SIMULATED_ANNEALING+neighbor_list+random+fixed+objective; if(random) random_permutation(perm, this->num_nodes); for(i=1;i<=n;i++) { TPM.search(this,perm[i-1],rules); if(this->total_route_length > worst_obj) worst_obj=this->total_route_length; } } if(heuristics & TWO_OPT) { rules=VRPH_SIMULATED_ANNEALING+neighbor_list+random+fixed+objective; if(random) random_permutation(perm, this->num_nodes); for(i=1;i<=n;i++) { TO.search(this,perm[i-1],rules); if(this->total_route_length > worst_obj) worst_obj=this->total_route_length; } } if(heuristics & THREE_POINT_MOVE) { rules=VRPH_SIMULATED_ANNEALING+VRPH_INTER_ROUTE_ONLY+neighbor_list+random+fixed+objective; if(random) random_permutation(perm, this->num_nodes); for(i=1;i<=n;i++) { ThreePM.search(this,perm[i-1],rules); if(this->total_route_length > worst_obj) worst_obj=this->total_route_length; } } if(heuristics & OR_OPT) { rules=VRPH_SIMULATED_ANNEALING+neighbor_list+random+fixed+objective; if(random) random_permutation(perm, this->num_nodes); for(i=1;i<=n;i++) { OR.search(this,perm[i-1],3,rules); if(this->total_route_length > worst_obj) worst_obj=this->total_route_length; } for(i=1;i<=n;i++) { OR.search(this,perm[i-1],2,rules); if(this->total_route_length > worst_obj) worst_obj=this->total_route_length; } } if(heuristics & CROSS_EXCHANGE) { normalize_route_numbers(); this->find_neighboring_routes(); R=total_number_of_routes; rules=VRPH_SIMULATED_ANNEALING+fixed+objective; if(random) random_permutation(perm, this->num_nodes); for(i=1; i<=R-1; i++) { for(j=0;j<=1;j++) { CE.route_search(this,i, route[i].neighboring_routes[j],rules); if(this->total_route_length > worst_obj) worst_obj=this->total_route_length; } } } } } delete [] perm; // Restore the best sol this->import_solution_buff(this->best_sol_buff); // Now return the obj. function value if(has_service_times==false) return this->best_total_route_length; else return this->best_total_route_length-total_service_time; }
int main(int argc, char **argv) { VRPH_version(); int i, j, k, n, status, num_attempts, *sol_buff, *IP_sol_buff; char in_file[200]; double lambda, best_heur_sol=VRP_INFINITY; bool first_sol=false, bootstrap=false;; VRPSolution *fresh_solution; OsiSolverInterface *si; const double *x; int last_num_cols=0, route_id=0; time_t start, stop; int *orderings[MAX_ROUTES]; for(i=0;i<MAX_ROUTES;i++) orderings[i]=NULL; // Set timing counters to 0 heur_time=mip_time=0; // Check arguments if(argc<5) { fprintf(stderr,"Usage: %s -f input_file -n num_runs [-v,-b,-c max_columns -d cols_to_delete]\n", argv[0]); fprintf(stderr,"\t Will solve the problem num_solutions times and add the routes\n"); fprintf(stderr,"\t to a set partitioning problem.\n"); fprintf(stderr,"\t Other options:\n"); fprintf(stderr,"\t -v runs in verbose mode\n"); fprintf(stderr,"\t -b will use bootstrapping where we send the set partitioning\n" "\t solution back to the metaheuristic solver\n"); fprintf(stderr,"\t -c max_columns will allow this many active columns/variables in the IP.\n"); fprintf(stderr,"\t Default value is max_columns=500\n"); fprintf(stderr,"\t -d num_cols_to_delete will delete this many columns once we have too many\n"); fprintf(stderr,"\t in the IP. Default value is num_cols_to_delete=100\n"); exit(-1); } // Set defaults verbose=false; max_columns=500; num_cols_to_delete=100; // Parse command line for(i=0;i<argc;i++) { if(strcmp(argv[i],"-f")==0) strcpy(in_file,argv[i+1]); if(strcmp(argv[i],"-n")==0) num_attempts=atoi(argv[i+1]); if(strcmp(argv[i],"-v")==0) verbose=true; if(strcmp(argv[i],"-b")==0) bootstrap=true; if(strcmp(argv[i],"-c")==0) max_columns=atoi(argv[i+1]); if(strcmp(argv[i],"-d")==0) num_cols_to_delete=atoi(argv[i+1]); } // This is the # of non-VRPH_DEPOT nodes n=VRPGetDimension(in_file); // This will be used to import/export solutions fresh_solution = new VRPSolution(n); // Create buffers for importing solutions sol_buff= new int[n+2]; IP_sol_buff = new int[n+2]; // Declare an OSI interface si=new OsiGlpkSolverInterface; si->setIntParam(OsiNameDiscipline,2); for(i=0;i<n;i++) { si->addRow(0,NULL,NULL,1,1); } // Declare a VRP of the right size and import the file VRP V(n); ClarkeWright CW(n); VRPRoute route(n); V.read_TSPLIB_file(in_file); // Set up a "route warehouse" to store the routes to be added to the IP V.route_wh=new VRPRouteWarehouse(HASH_TABLE_SIZE); // Set up a minimization problem si->setObjSense(1); // Set to error only output si->setHintParam(OsiDoReducePrint,true, OsiHintDo); // Unfortunately GLPK still prints out something regarding the conflict graph for(i=0;i<num_attempts;i++) { if(i==0 || !bootstrap) { lambda=.5+1.5*lcgrand(0); // Start with a clean VRP object V.reset(); CW.Construct(&V, lambda, false); if(verbose) printf("CW solution %d[%5.3f]: %f\n",i,lambda,V.get_total_route_length()-V.get_total_service_time()); } else // Use the solution from the IP V.import_solution_buff(IP_sol_buff); // Run VRPH's RTR algorithm to improve the solution start=clock(); V.RTR_solve(ONE_POINT_MOVE | TWO_POINT_MOVE | TWO_OPT | VRPH_USE_NEIGHBOR_LIST, 30, 5, 2, .01, 30, VRPH_LI_PERTURB, VRPH_FIRST_ACCEPT,false); stop=clock(); heur_time += (stop-start); if(verbose) printf("RTR Metaheuristic found solution %5.3f\n",V.get_total_route_length()-V.get_total_service_time()); // The RTR algorithm keeps a "warehouse" of the best solutions discovered during // the algorithm's search // Now go through the solutions in the solution warehouse and add the new routes // discovered to the IP for(j=0;j<V.solution_wh->num_sols;j++) { // Import solution j from the warehouse V.import_solution_buff(V.solution_wh->sols[j].sol); if(V.get_total_route_length()-V.get_total_service_time() < best_heur_sol) best_heur_sol = V.get_total_route_length()-V.get_total_service_time() ; // Now add the routes from this solution to the IP for(k=1;k<=V.get_total_number_of_routes();k++) { // Clean up the route by running INTRA_ROUTE optimizations only // using the route_search method of the different local search // heuristics, accepting improving moves only (VRPH_DOWNHILL) OnePointMove OPM; TwoOpt TO; ThreeOpt ThO; while(OPM.route_search(&V,k,k,VRPH_DOWNHILL|VRPH_INTRA_ROUTE_ONLY )){} while(TO.route_search(&V,k,k,VRPH_DOWNHILL|VRPH_INTRA_ROUTE_ONLY )){}; while(ThO.route_search(&V,k,VRPH_DOWNHILL|VRPH_INTRA_ROUTE_ONLY )){}; // Copy route k from the solution to the VRPRoute R V.update_route(k,&route); route.create_name(); // Add it to the "route warehouse" - this uses a hash table to keep track // of duplicate columns status=V.route_wh->add_route(&route); if(status!=DUPLICATE_ROUTE) { // This route is not currently in the WH and so it cannot be in the // set partitioning problem //OSI_add_route(si,&V,&route); OSI_add_route(si,&V,&route,route_id,orderings); route_id++; } } // Set the row RHS's if we need to if(first_sol) { first_sol=false; for(int rownum=0;rownum<n;rownum++) si->setRowBounds(rownum,1,1); // Note that changing this to >= would be a set covering problem // where each customer can be visited by more than one route } } // Now erase all the solutions from the WH V.solution_wh->liquidate(); if(verbose) { printf("Attempt %02d: Solving IP with %d columns\n",i,si->getNumCols()); printf("%d routes in the WH\n",V.route_wh->num_unique_routes); } // Solve the current set partitioning problem using the MIP solver start=clock(); si->branchAndBound(); stop=clock(); mip_time += (stop-start); double opt=si->getObjValue(); x=si->getColSolution(); last_num_cols=si->getNumCols(); if(verbose) printf("Optimal solution (%d columns) is %f\n",last_num_cols,opt); // Now recover the solution from the IP solution OSI_recover_solution(si, orderings, IP_sol_buff); if(verbose) printf("IP solution has obj. function value: %5.2f\n" "Best heuristic obj. function value: %5.2f\n", si->getObjValue(),best_heur_sol); } if(verbose) printf( "\nResults\n" "--------\n" "After %d runs\n" "IP solution has obj. function value: %5.2f\n" "Best heuristic obj. function value: %5.2f\n", num_attempts,si->getObjValue(),best_heur_sol); // print to stderr since GLPK prints "conflict graph" to stdout (a known bug...) // best_heur_sol best_mip_sol heur_time mip_time fprintf(stderr,"%5.3f %5.3f %5.3f %5.3f\n", best_heur_sol, si->getObjValue(), (double)(heur_time)/CLOCKS_PER_SEC, (double)(mip_time)/CLOCKS_PER_SEC); delete V.route_wh; delete fresh_solution; delete [] sol_buff; delete [] IP_sol_buff; delete si; for(i=0;i<MAX_ROUTES;i++) if(orderings[i]) delete [] orderings[i]; return 0; }