/*===========================================================================
 
 Copyright (C) 2007-2012 Yves Renard, Julien Pommier.
 
 This file is a part of GETFEM++
 
 Getfem++  is  free software;  you  can  redistribute  it  and/or modify it
 under  the  terms  of the  GNU  Lesser General Public License as published
 by  the  Free Software Foundation;  either version 3 of the License,  or
 (at your option) any later version along with the GCC Runtime Library
 Exception either version 3.1 or (at your option) 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 Lesser General Public
 License and GCC Runtime Library Exception for more details.
 You  should  have received a copy of the GNU Lesser General Public License
 along  with  this program;  if not, write to the Free Software Foundation,
 Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA.
 
===========================================================================*/

#include "crack_exact_solution.h"

using std::endl; using std::cout; using std::cerr;
using std::ends; using std::cin;

/* returns sin(theta/2) where theta is the angle
   of 0-(x,y) with the axis Ox */
static scalar_type sint2(scalar_type x, scalar_type y) {
  scalar_type r = sqrt(x*x+y*y);
  if (r == 0) return 0;
  else return (y<0 ? -1:1) * sqrt(gmm::abs(r-x)/(2*r));
  // sometimes (gcc3.3.2 -O3), r-x < 0 ....
}
static scalar_type cost2(scalar_type x, scalar_type y) {
  scalar_type r = sqrt(x*x+y*y);
  if (r == 0) return 0;
  else return sqrt(gmm::abs(r+x)/(2*r));
}
/* analytical solution for a semi-infinite crack [-inf,a] in an
   infinite plane submitted to +sigma above the crack
   and -sigma under the crack. (The crack is directed along the x axis).
   
   nu and E are the poisson ratio and young modulus
   
   solution taken from "an extended finite elt method with high order
   elts for curved cracks", Stazi, Budyn,Chessa, Belytschko
*/

/*static void elasticite2lame(const scalar_type young_modulus,
			    const scalar_type poisson_ratio, 
			    scalar_type& lambda, scalar_type& mu) {
  mu = young_modulus/(2*(1+poisson_ratio));
  lambda = 2*mu*poisson_ratio/(1-poisson_ratio);
}
*/

/* solution reguliere pour une fracture en y=0 */
static base_small_vector solution_P2(double lambda, double mu, const base_node& pos, base_matrix* pgrad = 0) {
  double x=pos[0], y = pos[1];
  base_small_vector v(2);
  double c[6] = {0, 0, 0., 0., 0., 0.01};
  double d[6] = {0, .2, .1, 0.05, -.03, 0.06};
  v[0] = c[0]-1/lambda*(lambda+2.0*mu)*d[2]*x-d[1]*y-1/lambda*(lambda+2.0*mu)*d[4]*x*x/2.0-2.0*(lambda+2.0*mu)*d[5]/lambda*x*y+(3.0*lambda+4.0*mu)*d[4]/lambda*y*y/2.0;
  v[1]= d[0]+d[1]*x+d[2]*y+1/lambda*(lambda+2.0*mu)*d[5]*x*x+d[4]*x*y+d[5]*y*y;
  if (pgrad) {
    (*pgrad).resize(2,2);
    (*pgrad)(0,0) = -1/lambda*(lambda+2.0*mu)*d[2]-1/lambda*(lambda+2.0*mu)*d[4]*x-2.0*(lambda+2.0*mu)*d[5]/lambda*y;
    (*pgrad)(1,0) = -d[1]-2.0*(lambda+2.0*mu)*d[5]/lambda*x+(3.0*lambda+4.0*mu)*d[4]/lambda*y;
    (*pgrad)(0,1) = d[1]+d[4]*y+2/lambda*(lambda+2.0*mu)*d[5]*x;
    (*pgrad)(1,1) = d[2]+d[4]*x+2*d[5]*y;
  }
  return v;
}

static void sol_ref_infinite_plane(scalar_type lambda, scalar_type mu, 
				   scalar_type x, scalar_type y,
				   base_small_vector& U, int mode,
				   base_matrix *pgrad) {
  //scalar_type KI = sigma*sqrt(M_PI*a);
  scalar_type r = std::max(sqrt(x*x+y*y),1e-16);
  scalar_type sqrtr = sqrt(r), sqrtr3 = sqrtr*sqrtr*sqrtr;
  scalar_type cost = x/r, sint = y/r;
  scalar_type theta = atan2(y,x);
  scalar_type s2 = sin(theta/2); //sint2(x,y);
  scalar_type c2 = cos(theta/2); //cost2(x,y);
  // scalar_type c3 = cos(3*theta/2); //4*c2*c2*c2-3*c2; /* cos(3*theta/2) */
  // scalar_type s3 = sin(3*theta/2); //4*s2*c2*c2-s2;  /* sin(3*theta/2) */

  U.resize(2);
  if (pgrad) (*pgrad).resize(2,2);
  scalar_type C=1; //1./E * (mode == 1 ? 1. : (1+nu));
  if (mode == 1) {
    scalar_type A=2+2*mu/(lambda+2*mu);
    scalar_type B=-2*(lambda+mu)/(lambda+2*mu);
    U[0] = sqrtr/sqrt(2*M_PI) * C * c2 * (A + B*cost);
    U[1] = sqrtr/sqrt(2*M_PI) * C * s2 * (A + B*cost);
    if (pgrad) {
      (*pgrad)(0,0) = C/(2.*sqrt(2*M_PI)*sqrtr)
	* (cost*c2*A-cost*cost*c2*B+sint*s2*A+sint*s2*B*cost+2*c2*B);
      (*pgrad)(1,0) = -C/(2*sqrt(2*M_PI)*sqrtr)
	* (-sint*c2*A+sint*c2*B*cost+cost*s2*A+cost*cost*s2*B);
      (*pgrad)(0,1) = C/(2.*sqrt(2*M_PI)*sqrtr)
	* (cost*s2*A-cost*cost*s2*B-sint*c2*A-sint*c2*B*cost+2*s2*B);
      (*pgrad)(1,1) = C/(2.*sqrt(2*M_PI)*sqrtr)
	* (sint*s2*A-sint*s2*B*cost+cost*c2*A+cost*cost*c2*B);
    }
  } else if (mode == 2) {
    scalar_type C1 = (lambda+3*mu)/(lambda+mu);
    U[0] = sqrtr/sqrt(2*M_PI) * C * s2 * (C1 + 2 + cost);
    U[1] = sqrtr/sqrt(2*M_PI) * C * c2 * (C1 - 2 + cost) * (-1.);
    if (pgrad) {
      (*pgrad)(0,0) = C/(2.*sqrt(2*M_PI)*sqrtr)
	* (cost*s2*C1+2*cost*s2-cost*cost*s2-sint*c2*C1
	   -2*sint*c2-sint*cost*c2+2*s2);
      (*pgrad)(1,0) = C/(2.*sqrt(2*M_PI)*sqrtr)
	* (sint*s2*C1+2*sint*s2-sint*s2*cost+cost*c2*C1
	   +2*cost*c2+cost*cost*c2);
      (*pgrad)(0,1) = -C/(2.*sqrt(2*M_PI)*sqrtr)
	* (cost*c2*C1-2*cost*c2-cost*cost*c2+sint*s2*C1
	   -2*sint*s2+sint*s2*cost+2*c2);
      (*pgrad)(1,1) =  C/(2.*sqrt(2*M_PI)*sqrtr)
	* (-sint*c2*C1+2*sint*c2+sint*cost*c2+cost*s2*C1
	   -2*cost*s2+cost*cost*s2);
    }
  } else if (mode == 100) {
    U[0] = - sqrtr3 * (c2 + 4./3 *(7*mu+3*lambda)/(lambda+mu)*c2*s2*s2
		       -1./3*(7*mu+3*lambda)/(lambda+mu)*c2);
    U[1] = - sqrtr3 * (s2+4./3*(lambda+5*mu)/(lambda+mu)*s2*s2*s2
		       -(lambda+5*mu)/(lambda+mu)*s2);
    if (pgrad) {
      (*pgrad)(0,0) = 2*sqrtr*(-6*cost*c2*mu+7*cost*c2*c2*c2*mu
			       -3*cost*c2*lambda+3*cost*c2*c2*c2*lambda
			       -2*sint*s2*mu
			       +7*sint*s2*c2*c2*mu-sint*s2*lambda
			       +3*sint*s2*c2*c2*lambda)/(lambda+mu);
      (*pgrad)(1,0) = -2*sqrtr*(6*sint*c2*mu-7*sint*c2*c2*c2*mu
				+3*sint*c2*lambda-3*sint*c2*c2*c2*lambda
				-2*cost*s2*mu
				+7*cost*s2*c2*c2*mu-cost*s2*lambda
				+3*cost*s2*c2*c2*lambda)/(lambda+mu);
      (*pgrad)(0,1) = 2*sqrtr*(-2*cost*s2*mu-cost*s2*lambda
			       +cost*s2*c2*c2*lambda+5*cost*s2*c2*c2*mu
			       +4*sint*c2*mu
			       +sint*c2*lambda-sint*c2*c2*c2*lambda
			       -5*sint*c2*c2*c2*mu)/(lambda+mu);
      (*pgrad)(1,1) = 2*sqrtr*(-2*sint*s2*mu-sint*s2*lambda
			       +sint*s2*c2*c2*lambda+5*sint*s2*c2*c2*mu
			       -4*cost*c2*mu
			       -cost*c2*lambda+cost*c2*c2*c2*lambda
			       +5*cost*c2*c2*c2*mu)/(lambda+mu);
    }
  } else if (mode == 101) {
    U[0] = -4*sqrtr3*s2*(-lambda-2*mu+7*lambda*c2*c2
			 +11*mu*c2*c2)/(3*lambda-mu);
    U[1] = -4*sqrtr3*c2*(-3*lambda+3*lambda*c2*c2-mu*c2*c2)/(3*lambda-mu);
    if (pgrad) {
      (*pgrad)(0,0) = -6*sqrtr*(-cost*s2*lambda-2*cost*s2*mu
				+7*cost*s2*lambda*c2*c2
				+11*cost*s2*mu*c2*c2+5*sint*c2*lambda
				+8*sint*c2*mu-7*sint*c2*c2*c2*lambda
				-11*sint*c2*c2*c2*mu)/(3*lambda-mu);
      (*pgrad)(1,0) = -6*sqrtr*(-sint*s2*lambda-2*sint*s2*mu
				+7*sint*s2*lambda*c2*c2
				+11*sint*s2*mu*c2*c2-5*cost*c2*lambda
				-8*cost*c2*mu+7*cost*c2*c2*c2*lambda
				+11*cost*c2*c2*c2*mu)/(3*lambda-mu);
      (*pgrad)(0,1) = -6*sqrtr*(-3*cost*c2*lambda+3*cost*c2*c2*c2*lambda
				-cost*c2*c2*c2*mu-sint*s2*lambda
				+3*sint*s2*lambda*c2*c2
				-sint*s2*mu*c2*c2)/(3*lambda-mu);
      (*pgrad)(1,1) = 6*sqrtr*(3*sint*c2*lambda
			       -3*sint*c2*c2*c2*lambda+sint*c2*c2*c2*mu
			       -cost*s2*lambda+3*cost*s2*lambda*c2*c2
			       -cost*s2*mu*c2*c2)/(3*lambda-mu);
    }

  } else if (mode == 10166666) {

    U[0] = 4*sqrtr3*s2*(-lambda+lambda*c2*c2-3*mu*c2*c2)/(lambda-3*mu);
    U[1] = 4*sqrtr3*c2*(-3*lambda-6*mu+5*lambda*c2*c2+9*mu*c2*c2)/(lambda-3*mu);
    if (pgrad) {
      (*pgrad)(0,0) = 6*sqrtr*(-cost*s2*lambda+cost*s2*lambda*c2*c2-
			       3*cost*s2*mu*c2*c2-2*sint*c2*mu+sint*c2*lambda-
			       sint*c2*c2*c2*lambda
			       +3*sint*c2*c2*c2*mu)/(lambda-3*mu);
      (*pgrad)(1,0) = 6*sqrtr*(-sint*s2*lambda+sint*s2*lambda*c2*c2-
			       3*sint*s2*mu*c2*c2+2*cost*c2*mu-cost*c2*lambda+
			       cost*c2*c2*c2*lambda
			       -3*cost*c2*c2*c2*mu)/(lambda-3*mu);
      (*pgrad)(0,1) = 6*sqrtr*(-3*cost*c2*lambda-6*cost*c2*mu
			       +5*cost*c2*c2*c2*lambda+
			       9*cost*c2*c2*c2*mu-sint*s2*lambda-2*sint*s2*mu+
			       5*sint*s2*lambda*c2*c2
			       +9*sint*s2*mu*c2*c2)/(lambda-3*mu);
      (*pgrad)(1,1) = -6*sqrtr*(3*sint*c2*lambda+6*sint*c2*mu
				-5*sint*c2*c2*c2*lambda-
				9*sint*c2*c2*c2*mu-cost*s2*lambda-2*cost*s2*mu+
				5*cost*s2*lambda*c2*c2
				+9*cost*s2*mu*c2*c2)/(lambda-3*mu);
    }
  } else assert(0);
  if (std::isnan(U[0]))
    cerr << "raaah not a number ...\n";
  assert(!std::isnan(U[0]));
  assert(!std::isnan(U[1]));
}


base_small_vector crack_exact_solution_function::eval(const base_node &x, base_matrix *pgrad) const {
  int N = x.size(); base_small_vector res(N); res.fill(0.);
  gmm::clear(res); if (pgrad) gmm::clear(*pgrad);
  if (function_num == 1) {
    sol_ref_infinite_plane(lambda, mu, x[0], x[1], res, 1, pgrad);
  } else if (function_num == 2) {
    sol_ref_infinite_plane(lambda, mu, x[0], x[1], res, 2, pgrad);
  } else if (function_num == 3) {
    //res[0] = (x[1]+2.5)/10;
    res[1] = x[1]/4;
  } else if (function_num == 4) {
    base_small_vector modeI(2), modeII(2);
    base_matrix gradI, gradII;
    sol_ref_infinite_plane(lambda, mu, x[0], x[1], modeI, 1, (pgrad) ? &gradI : 0);
    sol_ref_infinite_plane(lambda, mu, x[0], x[1], modeII, 2, (pgrad) ? &gradII : 0);
    res = solution_P2(lambda, mu, x, pgrad);
    res += modeI*4. - modeII*3.;
    if (pgrad) { 
      gmm::add(gmm::scaled(gradI,4.),*pgrad);
      gmm::add(gmm::scaled(gradII,-3.),*pgrad); 
    }
  } else if (function_num == 5) {
    base_small_vector modeI(2), modeII(2), modeZ(2);
    base_matrix gradI, gradII, gradZ;
    sol_ref_infinite_plane(lambda, mu, x[0], x[1], modeI, 1, (pgrad) ? &gradI : 0);
    sol_ref_infinite_plane(lambda, mu, x[0], x[1], modeII, 2, (pgrad) ? &gradII : 0);
    sol_ref_infinite_plane(lambda, mu, x[0], x[1], modeZ, 100, (pgrad) ? &gradZ : 0);
    res = solution_P2(lambda, mu, x, pgrad); 
    res = -1. * res; if (pgrad) { for (size_type k=0; k < (*pgrad).size(); ++k) (*pgrad)[k] = -(*pgrad)[k]; }
    res += modeI*(1.) - modeII*3. - modeZ*.5;
    if (pgrad) { 
      gmm::add(gmm::scaled(gradI,(1.)),*pgrad);
      gmm::add(gmm::scaled(gradII,-3.),*pgrad); 
      gmm::add(gmm::scaled(gradZ,-.5),*pgrad);
    }
  } else if (function_num == 6) {
    base_small_vector modeI(2), modeII(2), modeZI(2), modeZII(2);
    base_matrix gradI, gradII, gradZI, gradZII;
    scalar_type coef[5] = {0.5,1,1.5,1,0};
    res = solution_P2(lambda, mu, x, pgrad); 
    sol_ref_infinite_plane(lambda, mu, x[0], x[1], modeI, 1, (pgrad) ? &gradI : 0);
    sol_ref_infinite_plane(lambda, mu, x[0], x[1], modeII, 2, (pgrad) ? &gradII : 0);
    sol_ref_infinite_plane(lambda, mu, x[0], x[1], modeZI, 100, (pgrad) ? &gradZI : 0);
    sol_ref_infinite_plane(lambda, mu, x[0], x[1], modeZII, 101, (pgrad) ? &gradZII : 0);
    //res = -1. * res; if (pgrad) { for (size_type k=0; k < (*pgrad).size(); ++k) (*pgrad)[k] = -(*pgrad)[k]; }
    res = res*coef[0] + modeI*coef[1] + modeII*coef[2] + modeZI*coef[3] + modeZII*coef[4];
    
    if (pgrad) { 
      if (pgrad) { for (size_type k=0; k < (*pgrad).size(); ++k) (*pgrad)[k] *= coef[0]; }
      gmm::add(gmm::scaled(gradI,coef[1]),*pgrad);
      gmm::add(gmm::scaled(gradII,coef[2]),*pgrad);
      gmm::add(gmm::scaled(gradZI,coef[3]),*pgrad);
      gmm::add(gmm::scaled(gradZII,coef[4]),*pgrad);
    }
  } else if (function_num == 7) {
    sol_ref_infinite_plane(lambda, mu, x[0], x[1], res, 100, pgrad);
    res /= -40; if (pgrad) gmm::scale(*pgrad, -1./40);
  } else if (function_num == 8) {
    sol_ref_infinite_plane(lambda, mu, x[0], x[1], res, 101, pgrad);
    res /= 200; if (pgrad) gmm::scale(*pgrad, 1./200);
  }
  return res;
}

scalar_type crack_exact_solution_function::val(scalar_type x, scalar_type y) const {
  return eval(base_node(x,y), 0)[component_num];
}

base_small_vector crack_exact_solution_function::grad(scalar_type x, scalar_type y) const {
  base_matrix gr(2,2);
  eval(base_node(x,y), &gr);
  base_small_vector v(2);
  for (unsigned i=0; i < 2; ++i) v[i] = gr(i,component_num);
  return v;
}



void crack_exact_solution::init(int function_num, scalar_type lambda, scalar_type mu,
				getfem::level_set &ls) {
  std::vector<getfem::pglobal_function> cfun(2);
  for (size_type i = 0; i < 2; ++i) {
    /* use the singularity */
    getfem::abstract_xy_function *s = 
      new crack_exact_solution_function(function_num, 
					i, lambda, mu);
    cfun[i] = getfem::global_function_on_level_set(ls, *s);
  }
  
  mf.set_functions(cfun);
  
  mf.set_qdim(1);
  
  U.resize(4); assert(mf.nb_dof() == 2);
  U[0] = 1; U[1] = 0;
  U[2] = 0; U[3] = 1;
}
