/**@file
 * Bandwidth and profile reduction.
 */

#if HAVE_CONFIG_H
# include "config.h"
#endif

#include <alberta.h>
#include <crs_matrix.h>
#include <sys/time.h>

#include "optimize_profile.h"

/**@addtogroup CRS_MATRIX
 * @{
 */

#ifndef IF_INFO
# define IF_INFO(info, level) if (msg_info && info > level)
#endif
#ifndef INFO_VERBOSE
# define INFO_VERBOSE 5
#endif
#ifndef INFO_DEBUG
# define INFO_DEBUG 10
#endif

extern void F77_FUNC(gpskca,GPSKCA)(int *n, int *degree, int *rstart,
				    int *connec, /* int *connsz, */
				    int *optpro, int *wrklen,
				    int *permut, int *work,
				    int *bandwd, int64_t *profil,
				    int *error, int *space);

static int check_symmetry(const CRS_MATRIX_INFO *info)
{
  int dof, dof2, col, col2;
  int start, stop, start2, stop2;

  start = 0;
  for (dof = 0; dof < info->dim; dof++) {
    stop = info->row[dof+1] - 1;
    for (col = start; col < stop; col++) {
      dof2 = info->col[col]-1;
      start2 = info->row[dof2]-1;
      stop2 = info->row[dof2 +1]-1;
      for (col2 = start2; col2 < stop2; col2++) {
	if (info->col[col2]-1 == dof) {
	  break;
	}
      }
      if (col2 == stop2) {
	ERROR_EXIT("Matrix structure not symmetric");
      }
    }
    start = stop;
  }
  return 0;
}

/* Prepare the input for GPSKCA, CRS-matrix version */
static CRS_MATRIX_INFO *
generate_profile(const CRS_MATRIX_INFO *info, int * degree, int *P)
{
  CRS_MATRIX_INFO *pinfo = (CRS_MATRIX_INFO *)
    crs_matrix_info_alloc(info->dim, info->n_entries);
  const S_CHAR *bound = info->bound ? info->bound->vec : NULL;
  int start, stop, col, pcol, dof;

  pinfo->row[0] = 1; /* fortran indices */
  start = 1;         /* off diagonal entries */
  pcol  = 0;
  for (dof = 0; dof < info->dim; dof++) {
    stop = info->row[dof+1];                            /* only off-diagonal */
    if (!bound) {      
      for (col = start; col < stop; col++, pcol++) {
	pinfo->col[pcol] = info->col[col] + 1;
      }
    } else {
      for (col = start; col < stop; col++) {
	if (bound[info->col[col]] < DIRICHLET) {
	  pinfo->col[pcol++] = info->col[col] + 1;
	}
      }
    }
    pinfo->row[dof+1] = pcol+1;
    degree[dof] = pinfo->row[dof+1] - pinfo->row[dof]; /* off-diagonal only */
    start  = stop+1;
    P[dof] = dof + 1; /* initialize permutation, fortran indices */
  }
  check_symmetry(pinfo);
  pinfo->n_entries = pinfo->row[pinfo->dim];
  return pinfo;
}

/* Prepare the input for GPSKCA (DOF-matrix version) */
static CRS_MATRIX_INFO *
generate_profile_dm(const DOF_MATRIX *matrix, const DOF_SCHAR_VEC *mask,
		    int *degree, int *P, int info_level)
{
  int row_dim = matrix->row_fe_space->admin->size_used;
  CRS_MATRIX_INFO *pinfo = (CRS_MATRIX_INFO *)
    crs_matrix_info_alloc(row_dim, matrix->n_entries);
  const S_CHAR *bound = mask != NULL ? mask->vec : NULL;
  int pcol, dof;

  pinfo->row[0] = 1; /* fortran indices */
  pcol  = 0;
  for (dof = 0; dof < row_dim; dof++) {
    if (bound == NULL) {
      FOR_ALL_MAT_COLS(REAL, matrix->matrix_row[dof], {
	  if (col_dof == dof) {
	    continue; /* off-diagonal entries */
	  }
	  pinfo->col[pcol++] = col_dof + 1;
	});
    } else {
      FOR_ALL_MAT_COLS(REAL, matrix->matrix_row[dof], {
	  if (col_dof == dof) {
	    continue; /* off-diagonal entries */
	  }
	  if (bound[col_dof] < DIRICHLET) {
	    pinfo->col[pcol++] = col_dof + 1;
	  }
	});
    }
    pinfo->row[dof+1] = pcol+1;
    degree[dof] = pinfo->row[dof+1] - pinfo->row[dof]; /* off-diagonal only */
    P[dof] = dof + 1; /* initialize permutation, fortran indices */
  }
  check_symmetry(pinfo);
  pinfo->n_entries = pinfo->row[pinfo->dim];
  IF_INFO(info_level, INFO_VERBOSE) {
    MSG("n_entries: %d\n", pinfo->n_entries);
  }

  return pinfo;
}

static void optimize_profile(CRS_MATRIX_INFO *pinfo,
			     int *degree,
			     int *P, int *PI,
			     int *band_width,
			     int64_t *profile,
			     int optpro,
			     int info_level)
{
  FUNCNAME("optimize_profile");
  int dof;
  int wrklen = 6 * pinfo->dim + 3;
  int *work;
  int space;
  int error = 0;
  int row_dim = pinfo->dim;
  
#if 1 /* as an undocumented feature gpskca needs initialized
       * workspace, crazy fortran programmers (no blame on the
       * language, this time)
       */
  work  = MEM_ALLOC(wrklen+3*pinfo->dim, int);

#if 0
  memset(work, 0xff, sizeof(int)*wrklen);
#else
  memset(work, 0x00, sizeof(int)*wrklen);
#endif
#endif

#if 1
  F77_FUNC(gpskca,GPSKCA)(&pinfo->dim,
			  degree, (int *)pinfo->row, pinfo->col,
			  /* &pinfo->n_entries, */
			  &optpro, &wrklen,
			  P, work, band_width, profile, &error, &space);
#endif

  MEM_FREE(work, wrklen, int);

  /* initialize as identity in case of an error */
  if (error != 0) {
    WARNING("gpskca failed.\n");
    for (dof = 0; dof < row_dim; dof++) {
      P[dof] = dof+1;
    }
    *profile    = 5*pinfo->n_entries;
    *band_width = pinfo->dim;
  }

  for (dof = 0; dof < row_dim; dof++) {
    P[dof] --;
    if ((unsigned int)P[dof] >= (unsigned int)row_dim) {
      ERROR_EXIT("Permutation garbled at DOF %d, P[%d] = %d, dim = %d\n",
		 dof, dof, P[dof], row_dim);
    }
    PI[P[dof]] = dof;
  }

  /* error checking */
  for (dof = 0; dof < row_dim; dof++) {
    if (P[PI[dof]] != dof) {
      ERROR_EXIT("Permutation garbled at DOF %d, P[PI[%d]] = %d, PI[%d] = %d\n",
		 dof, dof, P[PI[dof]], dof, PI[dof]);
    }
  }

  IF_INFO(info_level, INFO_VERBOSE) {
    MSG("Dim: %d, New Bandwidth: %d, new profile: %Ld, error: %d\n",
	row_dim, *band_width, *profile, error);
  }
}

/** Bandwidth or profile reduction with GPSKCA algorithm (netlib).
 *
 * @param[in] A The matrix.
 * @param[out] P Permutation.
 * @param[out] PI Inverse of P.
 * @param[out] band_width The new band-width.
 * @param[out] profile The new profile (is this the number of non-zero entries? FIXME)
 * @param[in] optpro If != 0 then optimize for profile reduction, if
 *                   == 0 reduce the band-width.
 */
void crs_matrix_optimize_profile(const CRS_MATRIX *A,
				 int *P, int *PI,
				 int *band_width,
				 int64_t *profile,
				 int optpro,
				 int info_level)
{
  FUNCNAME("crs_matrix_optimize_profile");
  CRS_MATRIX_INFO *info = (CRS_MATRIX_INFO *)A->info;
  CRS_MATRIX_INFO *pinfo;
  int dof;
  int *degree;
  struct timeval real_tv;
  REAL real_t;

  gettimeofday(&real_tv, NULL);
  real_t = -(REAL)real_tv.tv_sec - (REAL)real_tv.tv_usec*1e-6;

#if 0
  memset(P, 0xff, sizeof(int)*info->dim);
  memset(PI, 0xff, sizeof(int)*info->dim);
#endif

  degree = MEM_ALLOC(info->dim, int);

  pinfo = generate_profile(info, degree, P);

  for (dof = 0; dof < A->info->dim; dof++) {
    TEST_EXIT(degree[dof] != -1 && P[dof] != -1,
	      "Foobar at dof %d!\n", dof);
    TEST_EXIT(info->row[dof] < info->row[dof+1],
	      "Really empty matrix row at dof %d\n",
	      dof);
  }
  
  optimize_profile(pinfo,
		   degree, P, PI, band_width, profile, optpro, info_level);

  crs_matrix_info_free(pinfo);
  MEM_FREE(degree, info->dim, int);

  gettimeofday(&real_tv, NULL);
  real_t += (REAL)real_tv.tv_sec + (REAL)real_tv.tv_usec*1e-6;

  IF_INFO(info_level, INFO_VERBOSE) {
    MSG("Real time elapsed: %e\n", real_t);
  }
}

/** Bandwidth or profile reduction with GPSKCA algorithm (netlib).
 *
 * @param[in] A The matrix.
 * @param[out] P Permutation.
 * @param[out] PI Inverse of P.
 * @param[out] band_width The new band-width.
 * @param[out] profile The new profile (is this the number of non-zero entries? FIXME)
 * @param[in] optpro If != 0 then optimize for profile reduction, if
 *                   == 0 reduce the band-width.
 */
void dof_matrix_optimize_profile(const DOF_MATRIX *matrix,
				 const DOF_SCHAR_VEC *mask,
				 int *P, int *PI,
				 int *band_width,
				 int64_t *profile,
				 int optpro,
				 int info_level)
{
  FUNCNAME("crs_matrix_optimize_profile");
  CRS_MATRIX_INFO *pinfo;
  int *degree;
  int row_dim = matrix->row_fe_space->admin->size_used;
  struct timeval real_tv;
  REAL real_t;

  gettimeofday(&real_tv, NULL);
  real_t = -(REAL)real_tv.tv_sec - (REAL)real_tv.tv_usec*1e-6;

#if 0
  memset(P, 0xff, sizeof(int)*info->dim);
  memset(PI, 0xff, sizeof(int)*info->dim);
#endif

  degree = MEM_ALLOC(row_dim, int);

  pinfo = generate_profile_dm(matrix, mask, degree, P, info_level);

  optimize_profile(pinfo, degree, P, PI, band_width, profile, optpro,
		   info_level);

  crs_matrix_info_free(pinfo);
  MEM_FREE(degree, row_dim, int);

  IF_INFO(info_level, INFO_VERBOSE) {
    gettimeofday(&real_tv, NULL);
    real_t += (REAL)real_tv.tv_sec + (REAL)real_tv.tv_usec*1e-6;
    MSG("Real time elapsed: %e\n", real_t);
  }
}

/**@} CRS_MATRIX */

/*
 * Local variables:
 *  version-control: t
 *  kept-new-versions: 5
 *  c-basic-offset: 2
 * End:
 */

