/* main.c */ /* Written by Andrew Makhorin , October 2015. */ /*********************************************************************** * This program is a stand-alone solver intended for solving Symmetric * Traveling Salesman Problem (TSP) with the branch-and-bound method. * * Please note that this program is only an illustrative example. It is * *not* a state-of-the-art code, so only small TSP instances (perhaps, * having up to 150-200 nodes) can be solved with this code. * * To run this program use the following command: * * tspsol tsp-file * * where tsp-file specifies an input text file containing TSP data in * TSPLIB 95 format. * * Detailed description of the input format recognized by this program * is given in the report: Gerhard Reinelt, "TSPLIB 95". This report as * well as TSPLIB, a library of sample TSP instances (and other related * problems), are freely available for research purposes at the webpage * . * * Symmetric Traveling Salesman Problem * ------------------------------------ * Let a complete undirected graph be given: * * K = (V, E), (1) * * where V = {1, ..., n} is a set of nodes, E = V cross V is a set of * edges. Let also each edge e = (i,j) be assigned a positive number * c[i,j], which is the length of e. The Symmetric Traveling Salesman * Problem (TSP) is to find a tour in K of minimal length. * * Integer programming formulation of TSP * -------------------------------------- * For a set of nodes W within V introduce the following notation: * * d(W) = {(i,j):i in W and j not in W or i not in W and j in W}, (2) * * i.e. d(W) is the set of edges which have exactly one endnode in W. * If W = {v}, i.e. W consists of the only node, we write simply d(v). * * The integer programming formulation of TSP is the following: * * minimize sum c[i,j] * x[i,j] (3) * i,j * * subject to sum x[i,j] = 2 for all v in V (4) * (i,j) in d(v) * * sum x[i,j] >= 2 for all W within V, (5) * (i,j) in d(W) W != empty, W != V * * x[i,j] in {0, 1} for all i, j (6) * * The binary variables x[i,j] have conventional meaning: if x[i,j] = 1, * the edge (i,j) is included in the tour, otherwise, if x[i,j] = 0, the * edge is not included in the tour. * * The constraints (4) are called degree constraints. They require that * for each node v in V there must be exactly two edges included in the * tour which are incident to v. * * The constraints (5) are called subtour elimination constraints. They * are used to forbid subtours. Note that the number of the subtour * elimination constraints grows exponentially on the size of the TSP * instance, so these constraints are not included explicitly in the * IP, but generated dynamically during the B&B search. ***********************************************************************/ #include #include #include #include #include #include #include #include "maxflow.h" #include "mincut.h" #include "misc.h" #include "tsplib.h" int n; /* number of nodes in the problem, n >= 2 */ int *c; /* int c[1+n*(n-1)/2]; */ /* upper triangle (without diagonal entries) of the (symmetric) matrix * C = (c[i,j]) in row-wise format, where c[i,j] specifies a length of * edge e = (i,j), 1 <= i < j <= n */ int *tour; /* int tour[1+n]; */ /* solution to TSP, which is a tour specified by the list of node * numbers tour[1] -> ... -> tour[nn] -> tour[1] in the order the nodes * are visited; note that any tour is a permutation of node numbers */ glp_prob *P; /* integer programming problem object */ /*********************************************************************** * loc - determine reduced index of element of symmetric matrix * * Given indices i and j of an element of a symmetric nxn-matrix, * 1 <= i, j <= n, i != j, this routine returns the index of that * element in an array, which is the upper triangle (without diagonal * entries) of the matrix in row-wise format. */ int loc(int i, int j) { xassert(1 <= i && i <= n); xassert(1 <= j && j <= n); xassert(i != j); if (i < j) return ((n - 1) + (n - i + 1)) * (i - 1) / 2 + (j - i); else return loc(j, i); } /*********************************************************************** * read_data - read TSP data * * This routine reads TSP data from a specified text file in TSPLIB 95 * format. */ void read_data(const char *fname) { TSP *tsp; int i, j; tsp = tsp_read_data(fname); if (tsp == NULL) { xprintf("TSP data file processing error\n"); exit(EXIT_FAILURE); } if (tsp->type != TSP_TSP) { xprintf("Invalid TSP data type\n"); exit(EXIT_FAILURE); } n = tsp->dimension; xassert(n >= 2); if (n > 32768) { xprintf("TSP instance too large\n"); exit(EXIT_FAILURE); } c = xalloc(1+loc(n-1, n), sizeof(int)); for (i = 1; i <= n; i++) { for (j = i+1; j <= n; j++) c[loc(i, j)] = tsp_distance(tsp, i, j); } tsp_free_data(tsp); return; } /*********************************************************************** * build_prob - build initial integer programming problem * * This routine builds the initial integer programming problem, which * includes all variables (6), objective (3) and all degree constraints * (4). Subtour elimination constraints (5) are considered "lazy" and * not included in the initial problem. */ void build_prob(void) { int i, j, k, *ind; double *val; char name[50]; /* create problem object */ P = glp_create_prob(); /* add all binary variables (6) */ for (i = 1; i <= n; i++) { for (j = i+1; j <= n; j++) { k = glp_add_cols(P, 1); xassert(k == loc(i,j)); sprintf(name, "x[%d,%d]", i, j); glp_set_col_name(P, k, name); glp_set_col_kind(P, k, GLP_BV); /* set objective coefficient (3) */ glp_set_obj_coef(P, k, c[k]); } } /* add all degree constraints (4) */ ind = xalloc(1+n, sizeof(int)); val = xalloc(1+n, sizeof(double)); for (i = 1; i <= n; i++) { k = glp_add_rows(P, 1); xassert(k == i); sprintf(name, "v[%d]", i); glp_set_row_name(P, i, name); glp_set_row_bnds(P, i, GLP_FX, 2, 2); k = 0; for (j = 1; j <= n; j++) { if (i != j) k++, ind[k] = loc(i,j), val[k] = 1; } xassert(k == n-1); glp_set_mat_row(P, i, n-1, ind, val); } xfree(ind); xfree(val); return; } /*********************************************************************** * build_tour - build tour for corresponding solution to IP * * Given a feasible solution to IP (3)-(6) this routine builds the * corresponding solution to TSP, which is a tour specified by the list * of node numbers tour[1] -> ... -> tour[nn] -> tour[1] in the order * the nodes are to be visited */ void build_tour(void) { int i, j, k, kk, *beg, *end; /* solution to MIP should be feasible */ switch (glp_mip_status(P)) { case GLP_FEAS: case GLP_OPT: break; default: xassert(P != P); } /* build the list of edges included in the tour */ beg = xalloc(1+n, sizeof(int)); end = xalloc(1+n, sizeof(int)); k = 0; for (i = 1; i <= n; i++) { for (j = i+1; j <= n; j++) { double x; x = glp_mip_col_val(P, loc(i,j)); xassert(x == 0 || x == 1); if (x) { k++; xassert(k <= n); beg[k] = i, end[k] = j; } } } xassert(k == n); /* reorder edges in the list as they follow in the tour */ for (k = 1; k <= n; k++) { /* find k-th edge of the tour */ j = (k == 1 ? 1 : end[k-1]); for (kk = k; kk <= n; kk++) { if (beg[kk] == j) break; if (end[kk] == j) { end[kk] = beg[kk], beg[kk] = j; break; } } xassert(kk <= n); /* put the edge to k-th position in the list */ i = beg[k], beg[k] = beg[kk], beg[kk] = i; j = end[k], end[k] = end[kk], end[kk] = j; } /* build the tour starting from node 1 */ xassert(beg[1] == 1); for (k = 1; k <= n; k++) { if (k > 1) xassert(end[k-1] == beg[k]); tour[k] = beg[k]; } xassert(end[n] == 1); xfree(beg); xfree(end); return; } /*********************************************************************** * tour_length - calculate tour length * * This routine calculates the length of the specified tour, which is * the sum of corresponding edge lengths. */ int tour_length(const int tour[/*1+n*/]) { int i, j, sum; sum = 0; for (i = 1; i <= n; i++) { j = (i < n ? i+1 : 1); sum += c[loc(tour[i], tour[j])]; } return sum; } /*********************************************************************** * write_tour - write tour to text file in TSPLIB format * * This routine writes the specified tour to a text file in TSPLIB * format. */ void write_tour(const char *fname, const int tour[/*1+n*/]) { FILE *fp; int i; xprintf("Writing TSP solution to '%s'...\n", fname); fp = fopen(fname, "w"); if (fp == NULL) { xprintf("Unable to create '%s' - %s\n", fname, strerror(errno)); return; } fprintf(fp, "NAME : %s\n", fname); fprintf(fp, "COMMENT : Tour length is %d\n", tour_length(tour)); fprintf(fp, "TYPE : TOUR\n"); fprintf(fp, "DIMENSION : %d\n", n); fprintf(fp, "TOUR_SECTION\n"); for (i = 1; i <= n; i++) fprintf(fp, "%d\n", tour[i]); fprintf(fp, "-1\n"); fprintf(fp, "EOF\n"); fclose(fp); return; } /*********************************************************************** * gen_subt_row - generate violated subtour elimination constraint * * This routine is called from the MIP solver to generate a violated * subtour elimination constraint (5). * * Constraints of this class has the form: * * sum x[i,j] >= 2, i in W, j in V \ W, * * for all W, where W is a proper nonempty subset of V, V is the set of * nodes of the given graph. * * In order to find a violated constraint of this class this routine * finds a min cut in a capacitated network, which has the same sets of * nodes and edges as the original graph, and where capacities of edges * are values of variables x[i,j] in a basic solution to LP relaxation * of the current subproblem. */ void gen_subt(glp_tree *T) { int i, j, ne, nz, *beg, *end, *cap, *cut, *ind; double sum, *val; /* MIP preprocessor should not be used */ xassert(glp_ios_get_prob(T) == P); /* if some variable x[i,j] is zero in basic solution, then the * capacity of corresponding edge in the associated network is * zero, so we may not include such edge in the network */ /* count number of edges having non-zero capacity */ ne = 0; for (i = 1; i <= n; i++) { for (j = i+1; j <= n; j++) { if (glp_get_col_prim(P, loc(i,j)) >= .001) ne++; } } /* build the capacitated network */ beg = xalloc(1+ne, sizeof(int)); end = xalloc(1+ne, sizeof(int)); cap = xalloc(1+ne, sizeof(int)); nz = 0; for (i = 1; i <= n; i++) { for (j = i+1; j <= n; j++) { if (glp_get_col_prim(P, loc(i,j)) >= .001) { nz++; xassert(nz <= ne); beg[nz] = i, end[nz] = j; /* scale all edge capacities to make them integral */ cap[nz] = ceil(1000 * glp_get_col_prim(P, loc(i,j))); } } } xassert(nz == ne); /* find minimal cut in the capacitated network */ cut = xalloc(1+n, sizeof(int)); min_cut(n, ne, beg, end, cap, cut); /* determine the number of non-zero coefficients in the subtour * elimination constraint and calculate its left-hand side which * is the (unscaled) capacity of corresponding min cut */ ne = 0, sum = 0; for (i = 1; i <= n; i++) { for (j = i+1; j <= n; j++) { if (cut[i] && !cut[j] || !cut[i] && cut[j]) { ne++; sum += glp_get_col_prim(P, loc(i,j)); } } } /* if the (unscaled) capacity of min cut is less than 2, the * corresponding subtour elimination constraint is violated */ if (sum <= 1.999) { /* build the list of non-zero coefficients */ ind = xalloc(1+ne, sizeof(int)); val = xalloc(1+ne, sizeof(double)); nz = 0; for (i = 1; i <= n; i++) { for (j = i+1; j <= n; j++) { if (cut[i] && !cut[j] || !cut[i] && cut[j]) { nz++; xassert(nz <= ne); ind[nz] = loc(i,j); val[nz] = 1; } } } xassert(nz == ne); /* add violated tour elimination constraint to the current * subproblem */ i = glp_add_rows(P, 1); glp_set_row_bnds(P, i, GLP_LO, 2, 0); glp_set_mat_row(P, i, nz, ind, val); xfree(ind); xfree(val); } /* free working arrays */ xfree(beg); xfree(end); xfree(cap); xfree(cut); return; } /*********************************************************************** * cb_func - application callback routine * * This routine is called from the MIP solver at various points of * the branch-and-cut algorithm. */ void cb_func(glp_tree *T, void *info) { xassert(info == info); switch (glp_ios_reason(T)) { case GLP_IROWGEN: /* generate one violated subtour elimination constraint */ gen_subt(T); break; } return; } /*********************************************************************** * main - TSP solver main program * * This main program parses command-line arguments, reads specified TSP * instance from a text file, and calls the MIP solver to solve it. */ int main(int argc, char *argv[]) { int j; char *in_file = NULL, *out_file = NULL; time_t start; glp_iocp iocp; /* parse command-line arguments */ # define p(str) (strcmp(argv[j], str) == 0) for (j = 1; j < argc; j++) { if (p("--output") || p("-o")) { j++; if (j == argc || argv[j][0] == '\0' || argv[j][0] == '-') { xprintf("No solution output file specified\n"); exit(EXIT_FAILURE); } if (out_file != NULL) { xprintf("Only one solution output file allowed\n"); exit(EXIT_FAILURE); } out_file = argv[j]; } else if (p("--help") || p("-h")) { xprintf("Usage: %s [options...] tsp-file\n", argv[0]); xprintf("\n"); xprintf("Options:\n"); xprintf(" -o filename, --output filename\n"); xprintf(" write solution to filename\n") ; xprintf(" -h, --help display this help information" " and exit\n"); exit(EXIT_SUCCESS); } else if (argv[j][0] == '-' || (argv[j][0] == '-' && argv[j][1] == '-')) { xprintf("Invalid option '%s'; try %s --help\n", argv[j], argv[0]); exit(EXIT_FAILURE); } else { if (in_file != NULL) { xprintf("Only one input file allowed\n"); exit(EXIT_FAILURE); } in_file = argv[j]; } } if (in_file == NULL) { xprintf("No input file specified; try %s --help\n", argv[0]); exit(EXIT_FAILURE); } # undef p /* display program banner */ xprintf("TSP Solver for GLPK %s\n", glp_version()); /* remove output solution file specified in command-line */ if (out_file != NULL) remove(out_file); /* read TSP instance from input data file */ read_data(in_file); /* build initial IP problem */ start = time(NULL); build_prob(); tour = xalloc(1+n, sizeof(int)); /* solve LP relaxation of initial IP problem */ xprintf("Solving initial LP relaxation...\n"); xassert(glp_simplex(P, NULL) == 0); xassert(glp_get_status(P) == GLP_OPT); /* solve IP problem with "lazy" constraints */ glp_init_iocp(&iocp); iocp.br_tech = GLP_BR_MFV; /* most fractional variable */ iocp.bt_tech = GLP_BT_BLB; /* best local bound */ iocp.sr_heur = GLP_OFF; /* disable simple rounding heuristic */ iocp.gmi_cuts = GLP_ON; /* enable Gomory cuts */ iocp.cb_func = cb_func; glp_intopt(P, &iocp); build_tour(); /* display some statistics */ xprintf("Time used: %.1f secs\n", difftime(time(NULL), start)); { size_t tpeak; glp_mem_usage(NULL, NULL, NULL, &tpeak); xprintf("Memory used: %.1f Mb (%.0f bytes)\n", (double)tpeak / 1048576.0, (double)tpeak); } /* write solution to output file, if required */ if (out_file != NULL) write_tour(out_file, tour); /* deallocate working objects */ xfree(c); xfree(tour); glp_delete_prob(P); /* check that no memory blocks are still allocated */ { int count; size_t total; glp_mem_usage(&count, NULL, &total, NULL); if (count != 0) xerror("Error: %d memory block(s) were lost\n", count); xassert(total == 0); } return 0; } /* eof */