/*--------------------------------------------------------------------------*/
/* ALBERTA:  an Adaptive multi Level finite element toolbox using           */
/*           Bisectioning refinement and Error control by Residual          */
/*           Techniques for scientific Applications                         */
/*                                                                          */
/* file:     MG_s.c                                                         */
/*                                                                          */
/* description:  multigrid method for a scalar elliptic equation            */
/*                                                                          */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  authors:   Alfred Schmidt                                               */
/*             Zentrum fuer Technomathematik                                */
/*             Fachbereich 3 Mathematik/Informatik                          */
/*             Universitaet Bremen                                          */
/*             Bibliothekstr. 2                                             */
/*             D-28359 Bremen, Germany                                      */
/*                                                                          */
/*             Kunibert G. Siebert                                          */
/*             Institut fuer Mathematik                                     */
/*             Universitaet Augsburg                                        */
/*             Universitaetsstr. 14                                         */
/*             D-86159 Augsburg, Germany                                    */
/*                                                                          */
/*  http://www.mathematik.uni-freiburg.de/IAM/ALBERTA                       */
/*                                                                          */
/*  (c) by A. Schmidt and K.G. Siebert (1996-2003)                          */
/*                                                                          */
/*--------------------------------------------------------------------------*/

#include "alberta.h"
#include <time.h>
#include <string.h>

/****************************************************************************/

static int init_multi_grid(MULTI_GRID_INFO *mg_info)
{
  FUNCNAME("init_multi_grid");
  MG_S_INFO *mg_s_info;
  int level;

  TEST_EXIT(mg_info && mg_info->data,"no mg_info or mg_s_info\n");
  mg_s_info = (MG_S_INFO *)(mg_info->data);
  level = mg_info->mg_levels-1;
  
  MG_s_dof_copy_to_sparse(mg_s_info, mg_s_info->u, mg_s_info->u_h[level]);
  MG_s_dof_copy_to_sparse(mg_s_info, mg_s_info->f, mg_s_info->f_h[level]);

  return(0);
}

/****************************************************************************/

static void exit_multi_grid(MULTI_GRID_INFO *mg_info)
{
  FUNCNAME("exit_multi_grid");
  MG_S_INFO *mg_s_info;
  int level;

  TEST_EXIT(mg_info && mg_info->data,"no mg_info or mg_s_info\n");
  mg_s_info = (MG_S_INFO *)(mg_info->data);
  level = mg_info->mg_levels-1;
  
  MG_s_dof_copy_from_sparse(mg_s_info, mg_s_info->u_h[level], mg_s_info->u);
 
  return;
}

/****************************************************************************/
/*  MG solver                                                               */
/****************************************************************************/

MG_S_INFO *mg_s_init(DOF_MATRIX *matrix, const DOF_SCHAR_VEC *bound, 
		     int info, char *prefix)
{
  FUNCNAME("mg_s_init");
  char            name[128], *append;
  clock_t         first = 0, second;
  MULTI_GRID_INFO *mg_info;
  MG_S_INFO       *mg_s_info;

  TEST_EXIT(matrix && matrix->row_fe_space, "no matrix or row_fe_space\n");

  mg_s_info = MEM_CALLOC(1, MG_S_INFO);
  mg_info = mg_s_info->mg_info = MEM_CALLOC(1, MULTI_GRID_INFO);

  mg_info->init_multi_grid = init_multi_grid;
  mg_info->pre_smooth = MG_s_smoother;
  mg_info->in_smooth = MG_s_smoother;
  mg_info->post_smooth = MG_s_smoother;
  mg_info->mg_restrict = MG_s_restrict;
  mg_info->mg_prolongate = MG_s_prolongate;
  mg_info->exact_solver = MG_s_exact_solver;
  mg_info->mg_resid = MG_s_resid;
  mg_info->exit_multi_grid = exit_multi_grid;
  mg_info->data = (void *)mg_s_info;

  mg_s_info->mg_info = mg_info;
  mg_s_info->fe_space = matrix->row_fe_space;
  /* Ensure that we have a vertex dof admin (DK).
   *
   * And make sure MG_s uses _this_ vertex admin which has the same
   * flags set as the matrix to support periodic/non-periodic admins.
   */
  mg_s_info->vertex_admin =
    get_vertex_admin(matrix->row_fe_space->mesh,
		     matrix->row_fe_space->admin->flags);

  mg_info->info = info;

  mg_info->cycle           = 1;
  mg_info->n_pre_smooth    = 1;
  mg_info->n_in_smooth     = 1;
  mg_info->n_post_smooth   = 1;
  mg_info->exact_level     = 0; 

  mg_s_info->smoother      = 1;
  mg_s_info->smooth_omega  = 1.0; 
  mg_s_info->exact_solver = 1;
  mg_s_info->exact_omega  = 1.0; 

  if (prefix) {
    strncpy(name, prefix, 100);
    name[100] = 0;
    append = name;
    while(*append) append++;
    strcpy(append, "->tolerance");
    GET_PARAMETER(info, name, "%f", &mg_info->tolerance);
    strcpy(append, "->exact_tolerance");
    GET_PARAMETER(info, name, "%f", &mg_info->exact_tolerance);
    strcpy(append, "->cycle");
    GET_PARAMETER(info, name, "%d", &mg_info->cycle);
    strcpy(append, "->n_pre_smooth");
    GET_PARAMETER(info, name, "%d", &mg_info->n_pre_smooth);
    strcpy(append, "->n_in_smooth");
    GET_PARAMETER(info, name, "%d", &mg_info->n_in_smooth);
    strcpy(append, "->n_post_smooth");
    GET_PARAMETER(info, name, "%d", &mg_info->n_post_smooth);
    strcpy(append, "->exact_level");
    GET_PARAMETER(info, name, "%d", &mg_info->exact_level);
    strcpy(append, "->info");
    GET_PARAMETER(info, name, "%d", &mg_info->info);

    strcpy(append, "->smoother");
    GET_PARAMETER(info, name, "%d", &mg_s_info->smoother);
    strcpy(append, "->smooth_omega");
    GET_PARAMETER(info, name, "%e", &mg_s_info->smooth_omega);
    strcpy(append, "->exact_solver");
    GET_PARAMETER(info, name, "%d", &mg_s_info->exact_solver);
    strcpy(append, "->exact_omega");
    GET_PARAMETER(info, name, "%e", &mg_s_info->exact_omega);
  }

  if (mg_info->info > 2) first = clock();

  MG_s_setup_levels(mg_s_info);
  MG_s_setup_mat_b(mg_s_info, matrix, bound);
  MG_s_reset_mat(mg_s_info);  /* unsort matrix columns */

  if (mg_info->info > 2) {
    second = clock();
    MSG("setup needed %.5lf seconds\n",
	(double)(second-first)/(double)CLOCKS_PER_SEC);
  }
  return(mg_s_info);
}

/****************************************************************************/
int mg_s_solve(MG_S_INFO *mg_s_info,
	       DOF_REAL_VEC *u, const DOF_REAL_VEC *f, REAL tol, int max_iter)
{
  FUNCNAME("mg_s_solve");
  clock_t first = 0, second;
  int     iter;
  MULTI_GRID_INFO *mg_info = NULL;

  TEST_EXIT(mg_s_info && (mg_info = mg_s_info->mg_info),
    "no mg_s_info or mg_info\n");
  TEST_EXIT(mg_s_info->mat,"mg_s_info not initialized?\n");

  mg_info->tolerance = tol;
  mg_info->exact_tolerance = tol*0.01;
  mg_info->max_iter = max_iter;
  mg_s_info->u = u;
  mg_s_info->f = f;

  if (mg_info->info > 2) first = clock();

  MG_s_sort_mat(mg_s_info);  /* sort matrix columns */
  iter = MG(mg_info);
  MG_s_reset_mat(mg_s_info); /* unsort matrix columns */

  
  if (mg_info->info > 2) {
    second = clock();
    MSG("setup+solver needed %.5lf seconds\n",
	(double)(second-first)/(double)CLOCKS_PER_SEC);
  }
  return(iter);
}

/****************************************************************************/
void mg_s_exit(MG_S_INFO *mg_s_info)
{
  FUNCNAME("mg_s_exit");
  MULTI_GRID_INFO *mg_info = NULL;

  TEST_EXIT(mg_s_info && (mg_info = mg_s_info->mg_info),
    "no mg_s_info or mg_info\n");
  TEST_EXIT(mg_s_info->mat,"mg_s_info not initialized?\n");

  MG_s_free_mem(mg_s_info);
  MEM_FREE(mg_info, 1, MULTI_GRID_INFO);
  MEM_FREE(mg_s_info, 1, MG_S_INFO);
  return;
}

/****************************************************************************/
/****************************************************************************/

int mg_s(DOF_MATRIX *matrix, DOF_REAL_VEC *u, const DOF_REAL_VEC *f,
	 const DOF_SCHAR_VEC *bound, 
	 REAL tol, int max_iter, int info, char *prefix)
{
  FUNCNAME("mg_s");
  MG_S_INFO       *mg_s_info;
  clock_t         first = 0, second;
  int             iter;

  if (info >= 2) first = clock();

  mg_s_info = mg_s_init(matrix, bound, info, prefix);

  if (info > 2) {
    second = clock();
    MSG("init needed %.5lf seconds\n",
	(double)(second-first)/(double)CLOCKS_PER_SEC);
  }

  iter = mg_s_solve(mg_s_info, u, f, tol, max_iter);;;

  mg_s_exit(mg_s_info);

  if (info >= 2) {
    second = clock();
    MSG("init+solve needed %.5lf seconds\n",
	(double)(second-first)/(double)CLOCKS_PER_SEC);
  }

  return(iter);
}
