/*--------------------------------------------------------------------------*/
/* ALBERTA:   an Adaptive multi Level finite element toolbox using           */
/*           Bisectioning refinement and Error control by Residual          */
/*           Techniques                                                     */
/*                                                                          */
/* file:     nlsolve.c                                                      */
/*                                                                          */
/* description:  solver for an nonlinear elliptic model problem             */
/*                                                                          */
/*               -k \Delta u + \sigma u^4 = f  in \Omega                    */
/*                                      u = g  on \partial \Omega           */
/*                                                                          */
/*               implementation of the nonlinear solver by a                */
/*               Newton method                                              */
/*                                                                          */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  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 "nonlin.h"

/*--------------------------------------------------------------------------*/
/*  data structure for the assembling of the linearized system              */
/*--------------------------------------------------------------------------*/
struct op_data
{
  REAL_BD Lambda;           /*  the gradient of the barycentric coordinates */
  REAL    det;              /*  |det D F_S|                                 */

  REAL    k, sigma;         /*  diffusion and reaction coefficient          */

  const QUAD_FAST *quad_fast;  /*  quad_fast for the zero order term        */
  const REAL      *v_qp;       /*  v at all quadrature nodes of quad_fast   */
};

/*--------------------------------------------------------------------------*/
/*  data structure for passing information from the newton solver to the    */
/*  the function for assembling, solving and norm calculation               */
/*--------------------------------------------------------------------------*/
typedef struct newton_data NEWTON_DATA;
/* when changing something in this structur,                                */
/* dont forget to change it in the initialisation in function "nlsolve" also*/
struct newton_data
{
  const FE_SPACE   *fe_space;     /* used finite element space              */
  BNDRY_FLAGS      dirichlet_mask;

  REAL       k;                   /* diffusion coefficient                  */
  REAL       sigma;               /* reaction coefficient                   */
  REAL       (*f)(const REAL_D);  /* for evaluation f + sigma u_ext^4       */

  DOF_MATRIX *DF;                 /* pointer to system matrix               */

/*---  parameters for the linear solver  -----------------------------------*/
  OEM_SOLVER solver;              /* used solver: CG (v >= 0) else BiCGStab */
  REAL       tolerance;
  REAL	     ssor_omega;
  int        max_iter;
  int	     ssor_iter;
  int        ilu_k;
  int        restart;
  int        info;
  OEM_PRECON icon;
  const PRECON *precon;
};


static const REAL_B *LALt(const EL_INFO *el_info,
			  const QUAD *quad, 
			  int iq, void *ud)
{
  struct op_data *info = (struct op_data *)ud;
  REAL           fac = info->k*info->det;
  int            i, j, k, dim = el_info->mesh->dim;
  static REAL_BB LALt;

  for (i = 0; i <= dim; i++)
  {
    for (j = i; j <= dim; j++)
    {
      for (LALt[i][j] = k = 0; k < DIM_OF_WORLD; k++)
	LALt[i][j] += info->Lambda[i][k]*info->Lambda[j][k];
      LALt[i][j] *= fac;
      LALt[j][i] = LALt[i][j];
    }
  }
  return (const REAL_B *)LALt;
}

static REAL c(const EL_INFO *el_info, const QUAD *quad, int iq, void *ud)
{
  struct op_data *info = (struct op_data *)ud;
  REAL  v3;

  DEBUG_TEST_EXIT(info->quad_fast->quad == quad, "quads differ\n");

  v3 = info->v_qp[iq]*info->v_qp[iq]*info->v_qp[iq];

  return(info->sigma*info->det*v3);
}

/* The program flow sucks. This just cannot be it. */
static void update(void *ud, int dim, const REAL *v, bool up_DF, REAL *F)
{
  /* Some quantities remembered across calls. Think of this routine
   * like being a "library function" ... The stuff is re-initialized
   * whenever the finite element space changes. We use fe_space->admin
   * to check for changes in the finite element space because
   * DOF_ADMIN's are persisitent within ALBERTA, while fe-space are
   * not.
   */
  static EL_MATRIX_INFO elmi2, elmi0;
  static const DOF_ADMIN *admin = NULL;
  static struct op_data op_data[1]; /* storage for det and Lambda */

  /* Remaining (non-static) variables. */
  const BAS_FCTS  *bas_fcts = NULL;
  int             n_phi;
  int             mesh_dim;
  NEWTON_DATA    *data = (NEWTON_DATA *)ud;
  FLAGS          fill_flag;
  DOF_REAL_VEC   dof_v[1];
  DOF_REAL_VEC   dof_F[1];

/*--------------------------------------------------------------------------*/
/*  init functions for assembling DF(v) and F(v)                            */
/*--------------------------------------------------------------------------*/

  bas_fcts = data->fe_space->bas_fcts;
  n_phi    = bas_fcts->n_bas_fcts;
  mesh_dim = bas_fcts->dim;

  if (admin != data->fe_space->admin) {
    OPERATOR_INFO o_info2 = { NULL, }, o_info0 = { NULL, };
    const QUAD    *quad;

    admin     = data->fe_space->admin;

    quad     = get_quadrature(mesh_dim, 2*bas_fcts->degree-2);
    o_info2.row_fe_space   = data->fe_space;
    o_info2.quad[2]        = quad;
    o_info2.LALt.real      = LALt;
    o_info2.LALt_pw_const  = true;
    o_info2.LALt_symmetric = true;
    o_info2.user_data      = op_data;

    fill_matrix_info(&o_info2, &elmi2);

    o_info0.row_fe_space   = data->fe_space;
    o_info0.quad[0]        = quad;
    o_info0.c.real         = c;
    o_info0.c_pw_const     = false;
    o_info0.user_data      = op_data;

    fill_matrix_info(&o_info0, &elmi0);

    op_data->quad_fast = get_quad_fast(bas_fcts, quad, INIT_PHI);
  }

/*--------------------------------------------------------------------------*/
/*  make a DOF vector from input vector v_vec                               */
/*--------------------------------------------------------------------------*/
  init_dof_real_vec_skel(dof_v, "v", data->fe_space);
  distribute_to_dof_real_vec_skel(dof_v, v);

/*--------------------------------------------------------------------------*/
/*  make a DOF vector from F, if not NULL                                    */
/*--------------------------------------------------------------------------*/
  if (F) {
    init_dof_real_vec_skel(dof_F, "F(v)", data->fe_space);
    distribute_to_dof_real_vec_skel(dof_F, F);
  }

/*--------------------------------------------------------------------------*/
/*  and now assemble DF(v) and/or F(v)                                      */
/*--------------------------------------------------------------------------*/
  op_data->k     = data->k;
  op_data->sigma = data->sigma;

  if (up_DF)
  {
/*--- if v_vec[i] >= 0 for all i => matrix is positive definite (p=1)  ----*/
    data->solver = dof_min(dof_v) >= 0 ? CG : BiCGStab;
    clear_dof_matrix(data->DF);
  }

  if (F)
  {
    dof_set(0.0, dof_F);			//!! Seggi
    L2scp_fct_bas(data->f, op_data->quad_fast->quad, dof_F);
    dof_scal(-1.0, dof_F);
  }

  fill_flag = CALL_LEAF_EL|FILL_COORDS|FILL_BOUND;
  TRAVERSE_FIRST(data->fe_space->mesh, -1, fill_flag) {
    const EL_REAL_VEC  *v_loc;
    const EL_DOF_VEC   *dof;
    const EL_BNDRY_VEC *bndry_bits;
    EL_SCHAR_VEC bound[n_phi];
    const EL_MATRIX *elmat2, *elmat0;

    v_loc      = fill_el_real_vec(NULL, el_info->el, dof_v);
    dof        = get_dof_indices(NULL, data->fe_space, el_info->el);
    bndry_bits = get_bound(NULL, bas_fcts, el_info);

/*--------------------------------------------------------------------------*/
/* initialization of values used by LALt and c                              */
/*--------------------------------------------------------------------------*/

    op_data->det = el_grd_lambda_0cd(el_info, op_data->Lambda);

    op_data->v_qp = uh_at_qp(NULL, op_data->quad_fast, v_loc);

    elmat2 = elmi2.el_matrix_fct(el_info, elmi2.fill_info);

    elmat0 = elmi0.el_matrix_fct(el_info, elmi0.fill_info);

    /* Translate the geometric boundary classification into
     * Dirichlet/Neumann/Interior boundary condition
     * interpretation. Inside the loop over the mesh-elements we need
     * only to care about Dirichlet boundary conditions.
     */
    dirichlet_map(bound, bndry_bits, data->dirichlet_mask);

    if (up_DF)  /*---  add element contribution to matrix DF(v)  ----------*/
    {
/*--------------------------------------------------------------------------*/
/*  add a(phi_i,phi_j) + 4*m(u^3*phi_i,phi_j) to matrix                     */
/*--------------------------------------------------------------------------*/
      add_element_matrix(data->DF, 1.0, elmat2, NoTranspose, dof, dof, bound);
      add_element_matrix(data->DF, 4.0, elmat0, NoTranspose, dof, dof, bound);
    }

    if (F)     /*---  add element contribution to F(v)  --------------------*/
    {
      int i;
/*--------------------------------------------------------------------------*/
/*  F(v) += a(v, phi_i) + m(v^4, phi_i)                                     */
/*--------------------------------------------------------------------------*/
      bi_mat_el_vec(1.0, elmat2, 1.0, elmat0, v_loc, 1.0, dof_F, dof, bound);
      for (i = 0; i < n_phi; i++) {
	if (bound->vec[i] >= DIRICHLET) {
	  F[dof->vec[i]] = 0.0;  /*--- zero Dirichlet boundary conditions! -*/
	}
      }
    }
    
  } TRAVERSE_NEXT();

  /* Record that the boundary conditions are built into the matrix, needed
   * e.g. by the hierarchical preconditioners.
   */
  BNDRY_FLAGS_CPY(data->DF->dirichlet_bndry, data->dirichlet_mask);
}

static int solve(void *ud, int dim, const REAL *F, REAL *d)
{
  NEWTON_DATA   *data = (NEWTON_DATA *)ud;
  int           iter;
  DOF_REAL_VEC  dof_F[1];
  DOF_REAL_VEC  dof_d[1];
  
/*--------------------------------------------------------------------------*/
/*  make DOF vectors from F and d                                           */
/*--------------------------------------------------------------------------*/
  init_dof_real_vec_skel(dof_F, "F", data->fe_space);
  distribute_to_dof_real_vec_skel(dof_F, F);
  
  init_dof_real_vec_skel(dof_d, "d", data->fe_space);
  distribute_to_dof_real_vec_skel(dof_d, d);

  
  if (data->icon == ILUkPrecon)
	  data->precon = init_oem_precon(data->DF, NULL, data->info, ILUkPrecon, data->ilu_k);
  else
	  data->precon = init_oem_precon(data->DF, NULL, data->info, data->icon,
                                 data->ssor_omega, data->ssor_iter);
  iter = oem_solve_s(data->DF, NULL, dof_F, dof_d, data->solver, 
		     data->tolerance, data->precon, data->restart, 
		     data->max_iter, data->info);

  return iter;
}

static REAL norm(void *ud, int dim, const REAL *v)
{
  NEWTON_DATA   *data = (NEWTON_DATA *)ud;
  DOF_REAL_VEC  dof_v[1]; /* = {NULL, NULL, "v"};*/
  
  init_dof_real_vec_skel(dof_v, "v", data->fe_space);
  distribute_to_dof_real_vec_skel(dof_v, v);

  return H1_norm_uh(NULL, dof_v);
}

int nlsolve(DOF_REAL_VEC *u0, REAL k, REAL sigma, REAL (*f)(const REAL_D),
	    const BNDRY_FLAGS dirichlet_mask)
{
  FUNCNAME("nlsolve");
  static NEWTON_DATA  data =
    { NULL, { 0, }, 0, 0, NULL, NULL, CG, 1.e-8, 1.0, 1000, 1, 8, 0, 2, 0, NULL };
  static NLS_DATA     nls_data;
  int                 iter, dim = u0->fe_space->admin->size_used;

  if (!data.fe_space)
  {
/*--------------------------------------------------------------------------*/
/*--  init parameters for newton  ------------------------------------------*/
/*--------------------------------------------------------------------------*/
    nls_data.update      = update; 
    nls_data.update_data = &data;
    nls_data.solve       = solve;
    nls_data.solve_data  = &data;
    nls_data.norm        = norm;
    nls_data.norm_data   = &data;

    nls_data.tolerance = 1.e-4;
    GET_PARAMETER(1, "newton tolerance", "%e", &nls_data.tolerance);
    nls_data.max_iter = 50;
    GET_PARAMETER(1, "newton max. iter", "%d", &nls_data.max_iter);
    nls_data.info = 8;
    GET_PARAMETER(1, "newton info", "%d", &nls_data.info);
    nls_data.restart = 0;
    GET_PARAMETER(1, "newton restart", "%d", &nls_data.restart);
    

/*--------------------------------------------------------------------------*/
/*--  init data for update and solve  --------------------------------------*/
/*--------------------------------------------------------------------------*/

    data.fe_space = u0->fe_space;
    data.DF       = get_dof_matrix("DF(v)", u0->fe_space, NULL);

    data.tolerance = 1.e-2*nls_data.tolerance;
    GET_PARAMETER(1, "linear solver tolerance", "%f", &data.tolerance);
    GET_PARAMETER(1, "linear solver max iteration", "%d", &data.max_iter);
    GET_PARAMETER(1, "linear solver info", "%d", &data.info);
    GET_PARAMETER(1, "linear solver precon", "%d", &data.icon);
    if (data.icon == __SSORPrecon) {
    	GET_PARAMETER(1, "linear precon ssor omega", "%f", &data.ssor_omega);
    	GET_PARAMETER(1, "linear precon ssor iter", "%d", &data.ssor_iter);
    }
    if (data.icon == ILUkPrecon)
        GET_PARAMETER(1, "linear precon ilu(k)", "%d", &data.ilu_k);
    GET_PARAMETER(1, "linear solver restart", "%d", &data.restart);
  }
  TEST_EXIT(data.fe_space == u0->fe_space, "can't change f.e. spaces\n");
  BNDRY_FLAGS_CPY(data.dirichlet_mask, dirichlet_mask);

/*--------------------------------------------------------------------------*/
/*--  init problem dependent parameters  -----------------------------------*/
/*--------------------------------------------------------------------------*/
  data.k     = k;
  data.sigma = sigma;
  data.f     = f;

/*--------------------------------------------------------------------------*/
/*--  enlarge workspace used by newton(_fs), and solve by Newton  ----------*/
/*--------------------------------------------------------------------------*/
  if (nls_data.restart)
  {
    nls_data.ws = REALLOC_WORKSPACE(nls_data.ws, 4*dim*sizeof(REAL));
    iter = nls_newton_fs(&nls_data, dim, u0->vec);
  }
  else
  {
    nls_data.ws = REALLOC_WORKSPACE(nls_data.ws, 2*dim*sizeof(REAL));
    iter = nls_newton(&nls_data, dim, u0->vec);
  }

  return(iter);
}
