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

#include "alberta-demo.h"

static bool do_graphics = true;

/*--------------------------------------------------------------------------*/
/* global variables: finite element space, discrete solution                */
/*                   and information about problem data                     */
/*--------------------------------------------------------------------------*/

static const FE_SPACE  *fe_space;  /* initialized by main()           */
static DOF_REAL_VEC    *u_h;       /* initialized by build()          */
static const PROB_DATA *prob_data; /* initialized by main()           */
static BNDRY_FLAGS dirichlet_mask; /* bit-mask of Dirichlet segments  */

/*--------------------------------------------------------------------------*/
/* rw_el_est(): read and write access to error estimates in LEAF_DATA       */
/* called by ellipt_est() in estimate()                                     */
/*--------------------------------------------------------------------------*/
/* get_el_est(): reading of error estimates from LEAF_DATA                  */
/* called by adapt_method_stat() and graph_el_est()                         */
/*--------------------------------------------------------------------------*/

typedef struct leaf_dat
{
  REAL est;
} LEAF_DAT;

static REAL *rw_el_est(EL *el)
{
  FUNCNAME("rw_el_est");

  if (IS_LEAF_EL(el))
    return(&((LEAF_DAT *)LEAF_DATA(el))->est);
  else
  {
    ERROR("no leaf element\n");
    return(NULL);
  }
}

static REAL get_el_est(EL *el)
{
  FUNCNAME("get_el_est");

  if (IS_LEAF_EL(el))
    return(((LEAF_DAT *)LEAF_DATA(el))->est);
  else
  {
    ERROR("no leaf element\n");
    return(0.0);
  }
}

/*--------------------------------------------------------------------------*/
/* build():  compress the grid, access vector for discrete solution         */
/*           matrix and load vector are handled by nlsolve()                */
/* called by adapt_method_stat()                                            */
/*--------------------------------------------------------------------------*/

static void build(MESH *mesh, U_CHAR flag)
{
  FUNCNAME("build");

  dof_compress(mesh);
  MSG("%d DOFs for %s\n", fe_space->admin->size_used, fe_space->name);

  if (!u_h)                 /*  access and initialize discrete solution    */
  {
    u_h    = get_dof_real_vec("u_h", fe_space);
    u_h->refine_interpol = fe_space->bas_fcts->real_refine_inter;
    u_h->coarse_restrict = fe_space->bas_fcts->real_coarse_inter;
    if (prob_data->u0)
      interpol(prob_data->u0, u_h);
    else
      dof_set(0.0, u_h);
  }
  
  /* set boundary values */
  dirichlet_bound(u_h, NULL, NULL, dirichlet_mask, prob_data->g);

  return;
}

/*--------------------------------------------------------------------------*/
/* solve(): solve the linear system                                         */
/* called by adapt_method_stat()                                            */
/*--------------------------------------------------------------------------*/

static void solve(MESH *mesh)
{
  nlsolve(u_h, prob_data->k, prob_data->sigma, prob_data->f, dirichlet_mask);

  if (do_graphics) {
    graphics(mesh, u_h, NULL, prob_data->u, HUGE_VAL /* time */);
  }

  return;
}

/*--------------------------------------------------------------------------*/
/* estimate(): calculates error estimate via ellipt_est()                   */
/*             calculates exact error also (only for test purpose)          */
/* called by adapt_method_stat()                                            */
/*--------------------------------------------------------------------------*/

static REAL r(const EL_INFO *el_info,
	      const QUAD *quad, int iq,
	      REAL uh_at_qp, const REAL_D grd_uh_at_qp)
{
  REAL_D      x;
  REAL        uhx2 = SQR(uh_at_qp);

  coord_to_world(el_info, quad->lambda[iq], x);

  return prob_data->sigma*uhx2*uhx2 - prob_data->f(x);
}

#define EOC(e,eo) log(eo/MAX(e,1.0e-15))/M_LN2

static REAL estimate(MESH *mesh, ADAPT_STAT *adapt)
{
  FUNCNAME("estimate");
  static int     norm = -1;
  static REAL    C[3] = {1.0, 1.0, 0.0};
  static REAL    est, est_old = -1.0, err = -1.0, err_old = -1.0;
  static FLAGS   r_flag = INIT_UH;
  REAL_DD        A = {{0.0}};
  int            n;

  for (n = 0; n < DIM_OF_WORLD; n++)
    A[n][n] = prob_data->k;  /* set diogonal of A; other elements are zero  */

  if (norm < 0)
  {
    norm = H1_NORM;
    GET_PARAMETER(1, "error norm", "%d", &norm);
    GET_PARAMETER(1, "estimator C0", "%f", C);
    GET_PARAMETER(1, "estimator C1", "%f", C+1);
    GET_PARAMETER(1, "estimator C2", "%f", C+2);
  }
  est = ellipt_est(u_h, adapt, rw_el_est, NULL /* rw_est_c() */,
		   -1 /* quad_degree */, norm, C,
		   (const REAL_D *) A, dirichlet_mask,
		   r, r_flag, NULL /* gn() */, 0 /* gn_flag */);

  MSG("estimate   = %.8le", est);
  if (est_old >= 0)
    print_msg(", EOC: %.2lf\n", EOC(est,est_old));
  else
    print_msg("\n");
  est_old = est;

  if (norm == L2_NORM  &&  prob_data->u)
    err = L2_err(prob_data->u, u_h, NULL, false, false, NULL, NULL);
  else if (norm == H1_NORM  &&  prob_data->grd_u)
    err = H1_err(prob_data->grd_u, u_h, NULL, 0, NULL, NULL);

  if (err >= 0)
  {
    MSG("||u-uh||%s = %.8le", norm == L2_NORM ? "L2" : "H1", err);
    if (err_old >= 0)
      print_msg(", EOC: %.2lf\n", EOC(err,err_old));
    else
      print_msg("\n");
    err_old = err;
    MSG("||u-uh||%s/estimate = %.2lf\n", norm == L2_NORM ? "L2" : "H1",
	err/MAX(est,1.e-15));
  }
  if (do_graphics) {
    graphics(mesh, NULL, get_el_est, NULL, HUGE_VAL /* time */);
  }
  
  return adapt->err_sum;
}

/*--------------------------------------------------------------------------*/
/* main program                                                             */
/*--------------------------------------------------------------------------*/

int main(int argc, char **argv)
{
  FUNCNAME("main");
  MESH           *mesh;
  const BAS_FCTS *lagrange;
  ADAPT_STAT     *adapt;
  int            dim, degree = 1, n_refine;

  /*****************************************************************************
   * first of all, initialize the access to parameters of the init file
   ****************************************************************************/
  parse_parameters(argc, argv, "INIT/nonlin.dat");

  GET_PARAMETER(1, "global refinements", "%d", &n_refine);
  GET_PARAMETER(1, "polynomial degree", "%d", &degree);
  GET_PARAMETER(1, "mesh dimension", "%d", &dim);
  GET_PARAMETER(1, "online graphics", "%B", &do_graphics);

  BNDRY_FLAGS_ALL(dirichlet_mask); /* Only Dirichlet b.c. supported here */

  /*****************************************************************************
   * init problem dependent data and read macro triangulation
   ****************************************************************************/

  prob_data = init_problem();

  /*****************************************************************************
   *  get a mesh with DOFs and leaf data                               
   ****************************************************************************/

  mesh = GET_MESH(dim,"Nonlinear problem mesh", prob_data->data, NULL, NULL);

  free_macro_data(prob_data->data);

  init_leaf_data(mesh, sizeof(LEAF_DAT),
		 NULL /* refine_leaf_data() */,
		 NULL /* coarsen_leaf_data() */);

  lagrange = get_lagrange(mesh->dim, degree);
  TEST_EXIT(lagrange, "no lagrange BAS_FCTS\n");

  fe_space = get_fe_space(mesh, lagrange->name, lagrange, 1, ADM_FLAGS_DFLT);

  global_refine(mesh, n_refine*mesh->dim, FILL_NOTHING);

  /*****************************************************************************
   *  init adapt structure and start adaptive method
   ****************************************************************************/
  adapt = get_adapt_stat(dim, "nonlin", "adapt", 1, NULL);
  adapt->estimate = estimate;
  adapt->get_el_est = get_el_est;
  adapt->build_after_coarsen = build;
  adapt->solve = solve;

  adapt_method_stat(mesh, adapt);

  WAIT_REALLY;

  return 0;
}
