The source code and dockerfile for the GSW2024 AI Lab.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.
 
 
 
 
 
 

535 lines
18 KiB

/* main.c */
/* Written by Andrew Makhorin <mao@gnu.org>, 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
* <http://www.iwr.uni-heidelberg.de/groups/comopt/software/TSPLIB95/>.
*
* 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 <errno.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <glpk.h>
#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 */