#include "igraph_lsap.h"
#include "igraph_error.h"
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <limits.h> /* INT_MAX */
#include <float.h> /* DBL_MAX */
#include <assert.h>
#include <time.h>
/* constants used for improving readability of code */
#define COVERED 1
#define UNCOVERED 0
#define ASSIGNED 1
#define UNASSIGNED 0
#define TRUE 1
#define FALSE 0
#define MARKED 1
#define UNMARKED 0
#define REDUCE 1
#define NOREDUCE 0
typedef struct {
int n; /* order of problem */
double **C; /* cost matrix */
double **c; /* reduced cost matrix */
int *s; /* assignment */
int *f; /* column i is assigned to f[i] */
int na; /* number of assigned items; */
int runs; /* number of iterations */
double cost; /* minimum cost */
time_t rtime; /* time */
} AP;
/* public interface */
/* constructors and destructor */
AP *ap_create_problem(double *t, int n);
AP *ap_create_problem_from_matrix(double **t, int n);
AP *ap_read_problem(char *file);
void ap_free(AP *p);
int ap_assignment(AP *p, int *res);
int ap_costmatrix(AP *p, double **m);
int ap_datamatrix(AP *p, double **m);
int ap_iterations(AP *p);
int ap_hungarian(AP *p);
double ap_mincost(AP *p);
void ap_print_solution(AP *p);
void ap_show_data(AP *p);
int ap_size(AP *p);
int ap_time(AP *p);
/* error reporting */
void ap_error(char *message);
/* private functions */
void preprocess(AP *p);
void preassign(AP *p);
int cover(AP *p, int *ri, int *ci);
void reduce(AP *p, int *ri, int *ci);
int ap_hungarian(AP *p) {
int n; /* size of problem */
int *ri; /* covered rows */
int *ci; /* covered columns */
time_t start, end; /* timer */
int i, j, ok;
start = time(0);
n = p->n;
p->runs = 0;
/* allocate memory */
p->s = calloc(1 + n, sizeof(int));
p->f = calloc(1 + n, sizeof(int));
ri = calloc(1 + n, sizeof(int));
ci = calloc(1 + n, sizeof(int));
if (ri == NULL || ci == NULL || p->s == NULL || p->f == NULL) {
IGRAPH_ERROR("ap_hungarian: could not allocate memory", IGRAPH_ENOMEM);
}
preprocess(p);
preassign(p);
while (p->na < n) {
if (REDUCE == cover(p, ri, ci)) {
reduce(p, ri, ci);
}
++p->runs;
}
end = time(0);
p->rtime = end - start;
/* check if assignment is a permutation of (1..n) */
for (i = 1; i <= n; i++) {
ok = 0;
for (j = 1; j <= n; j++)
if (p->s[j] == i) {
++ok;
}
if (ok != 1)
IGRAPH_ERROR("ap_hungarian: error in assigment, is not a permutation",
IGRAPH_EINVAL);
}
/* calculate cost of assignment */
p->cost = 0;
for (i = 1; i <= n; i++) {
p->cost += p->C[i][p->s[i]];
}
/* reset result back to base-0 indexing */
for (i = 1; i <= n; i++) {
p->s[i - 1] = p->s[i] - 1;
}
/* free memory */
free(ri);
free(ci);
return 0;
}
/* abbreviated interface */
int ap_assignment(AP *p, int *res) {
int i;
if (p->s == NULL) {
ap_hungarian(p);
}
for (i = 0; i < p->n; i++) {
res[i] = p->s[i];
}
return p->n;
}
/*******************************************************************/
/* constructors */
/* read data from file */
/*******************************************************************/
AP *ap_read_problem(char *file) {
FILE *f;
int i, j, c;
int m, n;
double x;
double **t;
int nrow, ncol;
AP *p;
f = fopen(file, "r");
if (f == NULL) {
return NULL;
}
t = (double **)malloc(sizeof(double*));
m = 0;
n = 0;
nrow = 0;
ncol = 0;
while (EOF != (i = fscanf(f, "%lf", &x))) {
if (i == 1) {
if (n == 0) {
t = (double **) realloc(t, (m + 1) * sizeof(double *));
t[m] = (double *) malloc(sizeof(double));
} else {
t[m] = (double *) realloc(t[m], (n + 1) * sizeof(double));
}
t[m][n++] = x;
ncol = (ncol < n) ? n : ncol;
c = fgetc(f);
if (c == '\n') {
n = 0;
++m;
nrow = (nrow < m) ? m : nrow;
}
}
}
fclose(f);
/* prepare data */
if (nrow != ncol) {
/*
fprintf(stderr,"ap_read_problem: problem not quadratic\nrows =%d, cols = %d\n",nrow,ncol);
*/
igraph_warningf("ap_read_problem: problem not quadratic\nrows = %d, cols = %d\n",
__FILE__, __LINE__, -1, nrow, ncol);
return NULL;
}
p = (AP*) malloc(sizeof(AP));
p->n = ncol;
p->C = (double **) malloc((1 + nrow) * sizeof(double *));
p->c = (double **) malloc((1 + nrow) * sizeof(double *));
if (p->C == NULL || p->c == NULL) {
return NULL;
}
for (i = 1; i <= nrow; i++) {
p->C[i] = (double *) calloc(ncol + 1, sizeof(double));
p->c[i] = (double *) calloc(ncol + 1, sizeof(double));
if (p->C[i] == NULL || p->c[i] == NULL) {
return NULL;
}
}
for (i = 1; i <= nrow; i++)
for ( j = 1; j <= ncol; j++) {
p->C[i][j] = t[i - 1][j - 1];
p->c[i][j] = t[i - 1][j - 1];
}
for (i = 0; i < nrow; i++) {
free(t[i]);
}
free(t);
p->cost = 0;
p->s = NULL;
p->f = NULL;
return p;
}
AP *ap_create_problem_from_matrix(double **t, int n) {
int i, j;
AP *p;
p = (AP*) malloc(sizeof(AP));
if (p == NULL) {
return NULL;
}
p->n = n;
p->C = (double **) malloc((n + 1) * sizeof(double *));
p->c = (double **) malloc((n + 1) * sizeof(double *));
if (p->C == NULL || p->c == NULL) {
return NULL;
}
for (i = 1; i <= n; i++) {
p->C[i] = (double *) calloc(n + 1, sizeof(double));
p->c[i] = (double *) calloc(n + 1, sizeof(double));
if (p->C[i] == NULL || p->c[i] == NULL) {
return NULL;
}
}
for (i = 1; i <= n; i++)
for ( j = 1; j <= n; j++) {
p->C[i][j] = t[i - 1][j - 1];
p->c[i][j] = t[i - 1][j - 1];
}
p->cost = 0;
p->s = NULL;
p->f = NULL;
return p;
}
/* read data from vector */
AP *ap_create_problem(double *t, int n) {
int i, j;
AP *p;
p = (AP*) malloc(sizeof(AP));
if (p == NULL) {
return NULL;
}
p->n = n;
p->C = (double **) malloc((n + 1) * sizeof(double *));
p->c = (double **) malloc((n + 1) * sizeof(double *));
if (p->C == NULL || p->c == NULL) {
return NULL;
}
for (i = 1; i <= n; i++) {
p->C[i] = (double *) calloc(n + 1, sizeof(double));
p->c[i] = (double *) calloc(n + 1, sizeof(double));
if (p->C[i] == NULL || p->c[i] == NULL) {
return NULL;
}
}
for (i = 1; i <= n; i++)
for ( j = 1; j <= n; j++) {
p->C[i][j] = t[n * (j - 1) + i - 1];
p->c[i][j] = t[n * (j - 1) + i - 1];
}
p->cost = 0;
p->s = NULL;
p->f = NULL;
return p;
}
/* destructor */
void ap_free(AP *p) {
int i;
free(p->s);
free(p->f);
for (i = 1; i <= p->n; i++) {
free(p->C[i]);
free(p->c[i]);
}
free(p->C);
free(p->c);
free(p);
}
/* set + get functions */
/*
void ap_show_data(AP *p)
{
int i, j;
for(i = 1; i <= p->n; i++){
for(j = 1; j <= p->n; j++)
printf("%6.2f ", p->c[i][j]);
printf("\n");
}
}
*/
double ap_mincost(AP *p) {
if (p->s == NULL) {
ap_hungarian(p);
}
return p->cost;
}
int ap_size(AP *p) {
return p->n;
}
int ap_time(AP *p) {
return (int) p->rtime;
}
int ap_iterations(AP *p) {
return p->runs;
}
/*
void ap_print_solution(AP *p)
{
int i;
printf("%d itertations, %d secs.\n",p->runs, (int)p->rtime);
printf("Min Cost: %10.4f\n",p->cost);
for(i = 0; i < p->n; i++)
printf("%4d",p->s[i]);
printf("\n");
}
*/
int ap_costmatrix(AP *p, double **m) {
int i, j;
for (i = 0; i < p->n; i++)
for (j = 0; j < p->n; j++) {
m[i][j] = p->C[i + 1][j + 1];
}
return p->n;
}
int ap_datamatrix(AP *p, double **m) {
int i, j;
for (i = 0; i < p->n; i++)
for (j = 0; j < p->n; j++) {
m[i][j] = p->c[i + 1][j + 1];
}
return p->n;
}
/* error reporting */
/*
void ap_error(char *message)
{
fprintf(stderr,"%s\n",message);
exit(1);
}
*/
/*************************************************************/
/* these functions are used internally */
/* by ap_hungarian */
/*************************************************************/
int cover(AP *p, int *ri, int *ci) {
int *mr, i, r;
int n;
n = p->n;
mr = calloc(1 + p->n, sizeof(int));
/* reset cover indices */
for (i = 1; i <= n; i++) {
if (p->s[i] == UNASSIGNED) {
ri[i] = UNCOVERED;
mr[i] = MARKED;
} else {
ri[i] = COVERED;
}
ci[i] = UNCOVERED;
}
while (TRUE) {
/* find marked row */
r = 0;
for (i = 1; i <= n; i++)
if (mr[i] == MARKED) {
r = i;
break;
}
if (r == 0) {
break;
}
for (i = 1; i <= n; i++)
if (p->c[r][i] == 0 && ci[i] == UNCOVERED) {
if (p->f[i]) {
ri[p->f[i]] = UNCOVERED;
mr[p->f[i]] = MARKED;
ci[i] = COVERED;
} else {
if (p->s[r] == UNASSIGNED) {
++p->na;
}
p->f[p->s[r]] = 0;
p->f[i] = r;
p->s[r] = i;
free(mr);
return NOREDUCE;
}
}
mr[r] = UNMARKED;
}
free(mr);
return REDUCE;
}
void reduce(AP *p, int *ri, int *ci) {
int i, j, n;
double min;
n = p->n;
/* find minimum in uncovered c-matrix */
min = DBL_MAX;
for (i = 1; i <= n; i++)
for (j = 1; j <= n; j++)
if (ri[i] == UNCOVERED && ci[j] == UNCOVERED) {
if (p->c[i][j] < min) {
min = p->c[i][j];
}
}
/* subtract min from each uncovered element and add it to each element */
/* which is covered twice */
for (i = 1; i <= n; i++)
for (j = 1; j <= n; j++) {
if (ri[i] == UNCOVERED && ci[j] == UNCOVERED) {
p->c[i][j] -= min;
}
if (ri[i] == COVERED && ci[j] == COVERED) {
p->c[i][j] += min;
}
}
}
void preassign(AP *p) {
int i, j, min, r, c, n, count;
int *ri, *ci, *rz, *cz;
n = p->n;
p->na = 0;
/* row and column markers */
ri = calloc(1 + n, sizeof(int));
ci = calloc(1 + n, sizeof(int));
/* row and column counts of zeroes */
rz = calloc(1 + n, sizeof(int));
cz = calloc(1 + n, sizeof(int));
for (i = 1; i <= n; i++) {
count = 0;
for (j = 1; j <= n; j++)
if (p->c[i][j] == 0) {
++count;
}
rz[i] = count;
}
for (i = 1; i <= n; i++) {
count = 0;
for (j = 1; j <= n; j++)
if (p->c[j][i] == 0) {
++count;
}
cz[i] = count;
}
while (TRUE) {
/* find unassigned row with least number of zeroes > 0 */
min = INT_MAX;
r = 0;
for (i = 1; i <= n; i++)
if (rz[i] > 0 && rz[i] < min && ri[i] == UNASSIGNED) {
min = rz[i];
r = i;
}
/* check if we are done */
if (r == 0) {
break;
}
/* find unassigned column in row r with least number of zeroes */
c = 0;
min = INT_MAX;
for (i = 1; i <= n; i++)
if (p->c[r][i] == 0 && cz[i] < min && ci[i] == UNASSIGNED) {
min = cz[i];
c = i;
}
if (c) {
++p->na;
p->s[r] = c;
p->f[c] = r;
ri[r] = ASSIGNED;
ci[c] = ASSIGNED;
/* adjust zero counts */
cz[c] = 0;
for (i = 1; i <= n; i++)
if (p->c[i][c] == 0) {
--rz[i];
}
}
}
/* free memory */
free(ri);
free(ci);
free(rz);
free(cz);
}
void preprocess(AP *p) {
int i, j, n;
double min;
n = p->n;
/* subtract column minima in each row */
for (i = 1; i <= n; i++) {
min = p->c[i][1];
for (j = 2; j <= n; j++)
if (p->c[i][j] < min) {
min = p->c[i][j];
}
for (j = 1; j <= n; j++) {
p->c[i][j] -= min;
}
}
/* subtract row minima in each column */
for (i = 1; i <= n; i++) {
min = p->c[1][i];
for (j = 2; j <= n; j++)
if (p->c[j][i] < min) {
min = p->c[j][i];
}
for (j = 1; j <= n; j++) {
p->c[j][i] -= min;
}
}
}
int igraph_solve_lsap(igraph_matrix_t *c, igraph_integer_t n,
igraph_vector_int_t *p) {
AP *ap;
IGRAPH_CHECK(igraph_vector_int_resize(p, n));
igraph_vector_int_null(p);
ap = ap_create_problem(&MATRIX(*c, 0, 0), n);
ap_hungarian(ap);
ap_assignment(ap, VECTOR(*p));
ap_free(ap);
return 0;
}