/*--------------------------------------------------------------------------*/
/* ALBERTA:  an Adaptive multi Level finite element toolbox using           */
/*           Bisectioning refinement and Error control by Residual          */
/*           Techniques for scientific Applications                         */
/*                                                                          */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/* file:     nonlin.h                                                       */
/*                                                                          */
/* 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.alberta-fem.de/                                               */
/*                                                                          */
/*--------------------------------------------------------------------------*/
/*  (c) by A. Schmidt and K.G. Siebert (1996-2004)                          */
/*                                                                          */
/*     This program is free software; you can redistribute it and/or modify */
/*     it under the terms of the GNU General Public License as published by */
/*     the Free Software Foundation; either version 2 of the License, or    */
/*     any later version.                                                   */
/*                                                                          */
/*     This program is distributed in the hope that it will be useful,      */
/*     but WITHOUT ANY WARRANTY; without even the implied warranty of       */
/*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        */
/*     GNU General Public License for more details.                         */
/*--------------------------------------------------------------------------*/

#include "nonlin.h"

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

static const FE_SPACE  *fe_space;        /* initialized by init_dof_admin() */
static DOF_REAL_VEC    *u_h = nil;       /* initialized by build()          */
static const PROB_DATA *prob_data = nil; /* initialized by main()           */

/*--------------------------------------------------------------------------*/
/* init DOF_ADMIN: Lagrange elements of order <polynomial degree>           */
/* called by GET_MESH()                                                     */
/*--------------------------------------------------------------------------*/

void init_dof_admin(MESH *mesh)
{
  FUNCNAME("init_dof_admin");
  int             degree;
  const BAS_FCTS  *lagrange;

  TEST_EXIT(mesh)("no MESH\n");

  degree = 1;
  GET_PARAMETER(1, "polynomial degree", "%d", &degree);
  lagrange = get_lagrange(degree);
  TEST_EXIT(lagrange)("no lagrange BAS_FCTS\n");

  fe_space = get_fe_space(mesh, lagrange->name, nil, lagrange);
  return;
}

/*--------------------------------------------------------------------------*/
/*  init_leaf_data(): one REAL on each element for the error estimate       */
/* called by GET_MESH()                                                     */
/*--------------------------------------------------------------------------*/

typedef struct leaf_dat
{
  REAL est;
} LEAF_DAT;

void init_leaf_data(LEAF_DATA_INFO *leaf_data_info)
{
  FUNCNAME("init_leaf_data");
  TEST_EXIT(leaf_data_info)("no LEAF_DATA_INFO\n");

  leaf_data_info->leaf_data_size = sizeof(LEAF_DAT);
  leaf_data_info->coarsen_leaf_data = nil;
  leaf_data_info->refine_leaf_data = nil;
  return;
}

/*--------------------------------------------------------------------------*/
/* 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()                         */
/*--------------------------------------------------------------------------*/

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(nil);
  }
}

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);
  }
  dirichlet_bound(prob_data->g, u_h, nil, nil); /* set boundary values     */

  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);
  graphics(mesh, u_h, nil);
  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_iq, 
              const REAL_D grd_uh_iq)
{
  REAL_D      x;
  REAL        uhx2 = SQR(uh_iq);

  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     degree, 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 REAL    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);
  }
  degree = 2*u_h->fe_space->bas_fcts->degree;
  est = ellipt_est(u_h, adapt, rw_el_est, nil, degree, norm, C, 
		   (const REAL_D *) A, r, r_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, nil, 0, nil, nil);
  else if (norm == H1_NORM  &&  prob_data->grd_u)
    err = H1_err(prob_data->grd_u, u_h, nil, 0, nil, nil);

  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));
  }
  graphics(mesh, nil, get_el_est);
  return(adapt->err_sum);
}

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

int main(int argc, char **argv)
{
  FUNCNAME("main");
  MESH       *mesh;
  ADAPT_STAT *adapt;
  int        k;

/*--------------------------------------------------------------------------*/
/*  first of all, init parameters from the init file and command line       */
/*--------------------------------------------------------------------------*/
  init_parameters(0, "INIT/nonlin.dat");
  for (k = 1; k+1 < argc; k += 2)
    ADD_PARAMETER(0, argv[k], argv[k+1]);

/*--------------------------------------------------------------------------*/
/*  get a mesh with DOFs and leaf data                                      */
/*--------------------------------------------------------------------------*/
  mesh = GET_MESH("Nonlinear problem mesh", init_dof_admin, init_leaf_data);

/*--------------------------------------------------------------------------*/
/*  init problem dependent data and read macro triangulation                */
/*--------------------------------------------------------------------------*/
  prob_data = init_problem(mesh);

/*--------------------------------------------------------------------------*/
/*  init adapt struture and start adaptive method                           */
/*--------------------------------------------------------------------------*/
  adapt = get_adapt_stat("nonlin", "adapt", 1, nil);
  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);
}
