/* Fesslix - Stochastic Analysis
 * Copyright (C) 2010-2016 Wolfgang Betz
 *
 * Fesslix 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 3 of the License, or
 * (at your option) any later version.
 *
 * Fesslix 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with Fesslix.  If not, see <http://www.gnu.org/licenses/>. 
 */

#include "flxfem_element_planestr.h"
#include "flxfem.h"

flxElement_2D_planestr::flxElement_2D_planestr(flxMaterial* material, flxFace& face, const bool planestr, FlxFunDeg* t)
: flxElement_2D_base(material, t, face), degreeEt(t->get_deg() + material->get_deg_E()), planestr(planestr),
bovera(1.0),
Kel(NULL),Kel_t(NULL),rx(NULL),drx(NULL),ry(NULL),dry(NULL),
dNxdx(NULL),dNxdy(NULL),dNydx(NULL),dNydy(NULL),xyequal(false)
{
  DOFvec[0] = face.find_dof(FlxDof::dof_wx,true);
  DOFvec[1] = face.find_dof(FlxDof::dof_wy,true);
  
  // h-FEM requires some initializations
    tensorSpaceX = true;
    tensorSpaceY = true;
    for (tuint i=0;i<4;++i) edgeOrient[i] = true;
    for (tuint i=0;i<4;++i) edgeXorder[i] = 1;
    for (tuint i=0;i<4;++i) edgeYorder[i] = 1;
    bubbleXorderX = 0;
    bubbleXorderY = 0;
    bubbleYorderX = 0;
    bubbleYorderY = 0;
    
  // check if enough Gauss-points are loaded
    // TODO GI->check_GA_polynomialDegree(DOFvec->get_DOFpOrder()*2-2+degreeEA);
}

flxElement_2D_planestr::~flxElement_2D_planestr()
{
  free_mem();
}

void flxElement_2D_planestr::free_mem()
{
  if (Kel) { delete Kel; Kel=NULL; }
  if (Kel_t) { delete Kel_t; Kel_t=NULL; }
  if (rx) { delete rx; rx=NULL; }
  if (drx) { delete drx; drx=NULL; }
  if (ry) { delete ry; ry=NULL; }
  if (dry) { delete dry; dry=NULL; }
  if (dNxdx) { delete dNxdx; dNxdx=NULL; }
  if (dNxdy) { delete dNxdy; dNxdy=NULL; }
  if (!xyequal) {
    if (dNydx) { delete dNydx; dNydx=NULL; }
    if (dNydy) { delete dNydy; dNydy=NULL; }
  }
}

const tdouble flxElement_2D_planestr::get_el_info_help(const FlxElementInfo::elInfo eI) const
{
  return flxElement_2D_base::get_el_info_help(eI);
}

inline void flxElement_2D_planestr::update_GN(const tuint prev_level, tuint& GNx, tuint& GNy) const
{
  if (prev_level>=4 || prev_level==2) {
    GNx=(GNx+1)/2;
    GNy=(GNy+1)/2;
    if (GNx<2) GNx=2;
    if (GNy<2) GNy=2;
  }
}

void flxElement_2D_planestr::assign_u_xy(const tuint i, const tuint j)
{
  // for u_x
    pom_x = bubbleXorderX; pom_y = bubbleXorderY;
    edgeXorder[0] = DOFvec[0]->get_EdgeXPOrderY(j);
      if (pom_x<edgeXorder[0]) pom_x = edgeXorder[0];
    edgeXorder[1] = DOFvec[0]->get_EdgeYPOrderX(i+1);
      if (pom_y<edgeXorder[1]) pom_y = edgeXorder[1];
    edgeXorder[2] = DOFvec[0]->get_EdgeXPOrderY(j+1);
      if (pom_x<edgeXorder[2]) pom_x = edgeXorder[2];
    edgeXorder[3] = DOFvec[0]->get_EdgeYPOrderX(i);
      if (pom_y<edgeXorder[3]) pom_y = edgeXorder[3];
    Nx_N = 4;
    for (tuint k=0;k<4;++k) {
      Nx_N += edgeXorder[k]-1;
    }
    Nx_N += DOFvec[0]->get_NbubblesPsubEl();
  // for u_y
    if (pom_x < bubbleYorderX) pom_x = bubbleYorderX; 
    if (pom_y < bubbleYorderY) pom_y = bubbleYorderY;
    edgeYorder[0] = DOFvec[1]->get_EdgeXPOrderY(j);
      if (pom_x<edgeYorder[0]) pom_x = edgeYorder[0];
    edgeYorder[1] = DOFvec[1]->get_EdgeYPOrderX(i+1);
      if (pom_y<edgeYorder[1]) pom_y = edgeYorder[1];
    edgeYorder[2] = DOFvec[1]->get_EdgeXPOrderY(j+1);
      if (pom_x<edgeYorder[2]) pom_x = edgeYorder[2];
    edgeYorder[3] = DOFvec[1]->get_EdgeYPOrderX(i);
      if (pom_y<edgeYorder[3]) pom_y = edgeYorder[3];
    Ny_N = 4;
    for (tuint k=0;k<4;++k) {
      Ny_N += edgeYorder[k]-1;
    }
    Ny_N += DOFvec[1]->get_NbubblesPsubEl();
}

void flxElement_2D_planestr::assembleStf_help(const tuint level, const_tuintPtr& vpos, tdoublePtr& ccpos, tuint GNx, tuint GNy, const tdouble xm, const tdouble ym, const tdouble dh)
{
  switch (*vpos) {
    case 0:		// parent
    {
      const tdouble dhh = dh/2.0;
      update_GN(level,GNx,GNy); 	// reduce number of Gauss-points
      ++vpos; 
      #if FLX_FC_STABLE
	assembleStf_help(level+1,vpos,ccpos,GNx,GNy,xm-dhh,ym-dhh,dhh);
	assembleStf_help(level+1,vpos,ccpos,GNx,GNy,xm+dhh,ym-dhh,dhh);
	assembleStf_help(level+1,vpos,ccpos,GNx,GNy,xm+dhh,ym+dhh,dhh);
	assembleStf_help(level+1,vpos,ccpos,GNx,GNy,xm-dhh,ym+dhh,dhh);
      #else
	if (*vpos==1) ++vpos; else assembleStf_help(level+1,vpos,ccpos,GNx,GNy,xm-dhh,ym-dhh,dhh);
	if (*vpos==1) ++vpos; else assembleStf_help(level+1,vpos,ccpos,GNx,GNy,xm+dhh,ym-dhh,dhh);
	if (*vpos==1) ++vpos; else assembleStf_help(level+1,vpos,ccpos,GNx,GNy,xm+dhh,ym+dhh,dhh);
	if (*vpos==1) ++vpos; else assembleStf_help(level+1,vpos,ccpos,GNx,GNy,xm-dhh,ym+dhh,dhh);
      #endif
      return;
    }
    #if FLX_FC_STABLE
    #else
    case 1:		// no geometry
	++vpos;
	return;
    #endif
    case 2:		// mixed(2)
    {
      tdouble E,nu,E1,E2,E3,E4;
      tdouble f1,f2;
      tdouble clx = *(ccpos++);
      tdouble cly = *(ccpos++);
      tdouble glx = xm_subel+clx*dxh_subel;
      tdouble gly = ym_subel+cly*dyh_subel;
      hLP_vec(*rx,clx);
      dhLP_vec(*drx,clx);
      hLP_vec(*ry,cly);
      dhLP_vec(*dry,cly);
      dhLP_2D_base();
      face.update_globalConst(glx,gly);
      E = material->get_E(); 
      nu = material->get_nu();
      #if FLX_FC_STABLE
	const tdouble Ap = *ccpos;
	const tdouble fstab = (1.0-Ap)/Ap/FLX_FC_STABLE_DIV;
      #endif
      E *= t->get_fun()->calc() * 4.0 * dh*dh * (*(ccpos++));
      if (planestr) {
	E /= (1.-pow2(nu));
	E1 = E*bovera;
	E2 = E*nu;
	E3 = E/bovera;
	E4 = E*(1-nu)/2.0;
      } else {
	E /= (1+nu)*(1-2*nu);
	E1 = E*(1-nu)*bovera;
	E2 = E*nu;
	E3 = E*(1-nu)/bovera;
	E4 = E*(1-2*nu)/2.0;
      }
      tuint c=0;
      for (tuint k=0;k<Nx_N;++k) {
	f1 = E1*dNxdxp[k];
	f2 = E4/bovera*dNxdyp[k];
	for (tuint l=0;l<=k;++l) {
	  #if FLX_FC_STABLE
	    Kelp[c] += (f1*dNxdxp[l] + f2*dNxdyp[l])*fstab;
	  #endif
	  Kelp[c++] += f1*dNxdxp[l] + f2*dNxdyp[l];
	}
      }
      for (tuint k=0;k<Ny_N;++k) {
	f1 = E2*dNydyp[k];
	f2 = E4*dNydxp[k];
	for (tuint l=0;l<Nx_N;++l) {
	  #if FLX_FC_STABLE
	    Kelp[c] += (f1*dNxdxp[l] + f2*dNxdyp[l])*fstab;
	  #endif
	  Kelp[c++] += f1*dNxdxp[l] + f2*dNxdyp[l];
	}
	f1 = E3*dNydyp[k];
	f2 = E4*bovera*dNydxp[k];
	for (tuint l=0;l<=k;++l) {
	  #if FLX_FC_STABLE
	    Kelp[c] += (f1*dNydyp[l] + f2*dNydxp[l])*fstab;
	  #endif
	  Kelp[c++] += f1*dNydyp[l] + f2*dNydxp[l];
	}
      }
      ++vpos;
      return;
    }
    #if FLX_FC_STABLE
    case 1:
    #endif
    case 3:
    default:		// full(3)
    {  
      // get Gauss points
	const tdouble* GPx = GI->get_GP(GNx);
	const tdouble* GWx = GI->get_GW(GNx);
	const tdouble* GPy = GI->get_GP(GNy);
	const tdouble* GWy = GI->get_GW(GNy);
	tdouble E,nu,E1,E2,E3,E4;
      // integration
	(*Kel_t)=0.0;
	tdouble lx,ly,glx,gly,f1,f2;
	for (tuint i=0;i<GNx;++i) {
	  lx = xm+dh*GPx[i];
	  glx = xm_subel+lx*dxh_subel;
	  hLP_vec(*rx,lx);
	  dhLP_vec(*drx,lx);
	  for (tuint j=0;j<GNy;++j) {
	    ly = ym+dh*GPy[j];
	    gly = ym_subel+ly*dyh_subel;
	    face.update_globalConst(glx,gly);
	    E = material->get_E();
	    nu = material->get_nu();
	    E *= t->get_fun()->calc() * GWx[i]*GWy[j];
	    if (planestr) {
	      E /= (1.-pow2(nu));
	      E1 = E*bovera;
	      E2 = E*nu;
	      E3 = E/bovera;
	      E4 = E*(1-nu)/2.0;
	    } else {
	      E /= (1+nu)*(1-2*nu);
	      E1 = E*(1-nu)*bovera;
	      E2 = E*nu;
	      E3 = E*(1-nu)/bovera;
	      E4 = E*(1-2*nu)/2.0;
	    }
	    hLP_vec(*ry,ly);
	    dhLP_vec(*dry,ly);
	    dhLP_2D_base();
	    tuint c=0;
	    for (tuint k=0;k<Nx_N;++k) {
	      f1 = E1*dNxdxp[k];
	      f2 = E4/bovera*dNxdyp[k];
	      for (tuint l=0;l<=k;++l) {
		Kelp_t[c++] += f1*dNxdxp[l] + f2*dNxdyp[l];
	      }
	    }
	    for (tuint k=0;k<Ny_N;++k) {
	      f1 = E2*dNydyp[k];
	      f2 = E4*dNydxp[k];
	      for (tuint l=0;l<Nx_N;++l) {
		Kelp_t[c++] += f1*dNxdxp[l] + f2*dNxdyp[l];
	      }
	      f1 = E3*dNydyp[k];
	      f2 = E4*bovera*dNydxp[k];
	      for (tuint l=0;l<=k;++l) {
		Kelp_t[c++] += f1*dNydyp[l] + f2*dNydxp[l];
	      }
	    }
	  }
	}
	#if FLX_FC_STABLE
	  if ( *vpos==1 ) {	// geometry at this point
	    (*Kel_t)*=dh*dh/FLX_FC_STABLE_DIV;
	  } else {
	    (*Kel_t)*=dh*dh;
	  }
	#else
	  (*Kel_t)*=dh*dh;
	#endif
	*Kel += *Kel_t;
      }
      ++vpos;
      return;
  }
}

void flxElement_2D_planestr::assembleStf(const tuint meshIntLx, const tuint meshIntLy, tuint** mesh, tdouble** mesh_cc, const tdouble* meshXLx, const tdouble* meshYLy, flxStiffnessMtxBase& stfmtx)
{
  tuint N;	// Nx_N+NyN
  struct h1 {
    tuint ID;
    tuint size;
  };
  h1 Map[18];
	
  bubbleXorderX = DOFvec[0]->get_DOFpOrderLx();
  bubbleXorderY = DOFvec[0]->get_DOFpOrderLy();
  bubbleYorderX = DOFvec[1]->get_DOFpOrderLx();
  bubbleYorderY = DOFvec[1]->get_DOFpOrderLy();
  tensorSpaceX = DOFvec[0]->is_tensorSpace();
  tensorSpaceY = DOFvec[1]->is_tensorSpace();
  
//   check if this is an hFEM-element
    if (check_hFEM(meshIntLx,meshIntLy)) {
      assembleStfHfem(stfmtx);
      return;
    }
  
  for (tuint i=0;i<meshIntLx;++i) {		// in x
    subElX = i;
    for (tuint j=0;j<meshIntLy;++j) {		// in y
      subElY = j;
      // gather data
	bovera = face.get_bovara(i,j);
	// orientation of the edges
	  // edge 1 
	    if (j==0) edgeOrient[0] = face.get_edge_Orient(0);
	    else edgeOrient[0]=true;
	  // edge 2 
	    if (i==meshIntLx-1) edgeOrient[1] = face.get_edge_Orient(1);
	    else edgeOrient[1]=true;
	  // edge 2 
	    if (j==meshIntLy-1) edgeOrient[2] = face.get_edge_Orient(2);
	    else edgeOrient[2]=true;
	  // edge 1 
	    if (i==0) edgeOrient[3] = face.get_edge_Orient(3);
	    else edgeOrient[3]=true;
	assign_u_xy(i,j);
	// check if ansatz-functions of u_x are equal those of u_y
	  if (dNxdx) {
	    if (dNxdx->get_N()!=Nx_N) {
	      delete dNxdx;
	      delete dNxdy;
	      dNxdx = new flxVec(Nx_N);
	      dNxdy = new flxVec(Nx_N);
	      dNxdxp = dNxdx->get_tmp_vptr();
	      dNxdyp = dNxdy->get_tmp_vptr();
	    }
	  } else {
	    dNxdx = new flxVec(Nx_N);
	    dNxdy = new flxVec(Nx_N);
	    dNxdxp = dNxdx->get_tmp_vptr();
	    dNxdyp = dNxdy->get_tmp_vptr();
	  }
	  bool b1 = (tensorSpaceX==tensorSpaceY && bubbleXorderX==bubbleYorderX && bubbleXorderY==bubbleYorderY);
	  for (tuint k=0;k<4 && b1;++k) {
	    if (edgeXorder[k]!=edgeYorder[k]) b1 = false;
	  }
	  if (b1) {
	    if (!xyequal) {
	      delete dNydx;
	      delete dNydy;
	      xyequal = true;
	    }
	    dNydx = dNxdx;
	    dNydy = dNxdy;
	    dNydxp = dNydx->get_tmp_vptr();
	    dNydyp = dNydy->get_tmp_vptr();
	  } else {
	    if (xyequal) {
	      dNydx = NULL;
	      xyequal = false;
	    }
	    if (dNydx) {
	      if (dNydx->get_N()!=Ny_N) {
		delete dNydx;
		delete dNydy;
		dNydx = new flxVec(Ny_N);
		dNydy = new flxVec(Ny_N);
		dNydxp = dNydx->get_tmp_vptr();
		dNydyp = dNydy->get_tmp_vptr();
	      }
	    } else {
	      dNydx = new flxVec(Ny_N);
	      dNydy = new flxVec(Ny_N);
	      dNydxp = dNydx->get_tmp_vptr();
	      dNydyp = dNydy->get_tmp_vptr();
	    }
	  }
	// allocate local stiffness matrix
	  N = Nx_N+Ny_N;
	  if (Kel) {
	    if (Kel->get_N()!=((N*N+N)/2)) {
	      delete Kel; 
	      delete Kel_t;
	      Kel = new flxVec((N*N+N)/2);
	      Kel_t = new flxVec((N*N+N)/2);
	      Kelp = Kel->get_tmp_vptr();
	      Kelp_t = Kel_t->get_tmp_vptr();
	    } else {
	      Kel->set_zero();
	    }
	  } else {
	    Kel = new flxVec((N*N+N)/2);
	    Kel_t = new flxVec((N*N+N)/2);
	    Kelp = Kel->get_tmp_vptr();
	    Kelp_t = Kel_t->get_tmp_vptr();
	  }
	  if (rx) {
	    if (rx->get_N()!=pom_x) {
	      delete rx;
	      delete drx;
	      rx = new flxVec(pom_x+1);
	      drx = new flxVec(pom_x+1);
	      rxp = rx->get_tmp_vptr();
	      drxp = drx->get_tmp_vptr();
	    }
	  } else {
	    rx = new flxVec(pom_x+1);
	    drx = new flxVec(pom_x+1);
	    rxp = rx->get_tmp_vptr();
	    drxp = drx->get_tmp_vptr();
	  }
	  if (ry) {
	    if (ry->get_N()!=pom_y) {
	      delete ry;
	      delete dry;
	      ry = new flxVec(pom_y+1);
	      dry = new flxVec(pom_y+1);
	      ryp = ry->get_tmp_vptr();
	      dryp = dry->get_tmp_vptr();
	    }
	  } else {
	    ry = new flxVec(pom_y+1);
	    dry = new flxVec(pom_y+1);
	    ryp = ry->get_tmp_vptr();
	    dryp = dry->get_tmp_vptr();
	  }
	  
	  
      // integrate
	const tuint GNx = GI->pDegree2GPs(2*pom_x+degreeEt);
	const tuint GNy = GI->pDegree2GPs(2*pom_y+degreeEt);
	dxh_subel = (meshXLx[i+1]-meshXLx[i])/2.0;
	dyh_subel = (meshYLy[j+1]-meshYLy[j])/2.0;
	xm_subel = meshXLx[i]+dxh_subel;
	ym_subel = meshYLy[j]+dyh_subel;
	const_tuintPtr vpos = mesh[i*meshIntLy+j];
	tdoublePtr ccpos = mesh_cc[i*meshIntLy+j];
	assembleStf_help(0,vpos,ccpos,GNx,GNy,0.0,0.0,1.0);
	
      // map to local-face-stf-mtx
	// DOF u_x
	  Map[0].ID = DOFvec[0]->get_localIndex(i,j,0);
	    Map[0].size = 1;
	  Map[1].ID = DOFvec[0]->get_localIndex(i,j,1);
	    Map[1].size = 1;
	  Map[2].ID= DOFvec[0]->get_localIndex(i,j,2);
	    Map[2].size = 1;
	  Map[3].ID = DOFvec[0]->get_localIndex(i,j,3);
	    Map[3].size = 1;
	  Map[4].ID = DOFvec[0]->get_localIndex(i,j,4);
	    Map[4].size = edgeXorder[0]-1;
	  Map[5].ID = DOFvec[0]->get_localIndex(i,j,5);
	    Map[5].size = edgeXorder[1]-1;
	  Map[6].ID = DOFvec[0]->get_localIndex(i,j,6);
	    Map[6].size = edgeXorder[2]-1;
	  Map[7].ID = DOFvec[0]->get_localIndex(i,j,7);
	    Map[7].size = edgeXorder[3]-1;
	  Map[8].ID = DOFvec[0]->get_localIndex(i,j,8);
	    Map[8].size = DOFvec[0]->get_NbubblesPsubEl();
	// DOF u_y
	  Map[9].ID = DOFvec[1]->get_localIndex(i,j,0);
	    Map[9].size = 1;
	  Map[10].ID = DOFvec[1]->get_localIndex(i,j,1);
	    Map[10].size = 1;
	  Map[11].ID= DOFvec[1]->get_localIndex(i,j,2);
	    Map[11].size = 1;
	  Map[12].ID = DOFvec[1]->get_localIndex(i,j,3);
	    Map[12].size = 1;
	  Map[13].ID = DOFvec[1]->get_localIndex(i,j,4);
	    Map[13].size = edgeYorder[0]-1;
	  Map[14].ID = DOFvec[1]->get_localIndex(i,j,5);
	    Map[14].size = edgeYorder[1]-1;
	  Map[15].ID = DOFvec[1]->get_localIndex(i,j,6);
	    Map[15].size = edgeYorder[2]-1;
	  Map[16].ID = DOFvec[1]->get_localIndex(i,j,7);
	    Map[16].size = edgeYorder[3]-1;
	  Map[17].ID = DOFvec[1]->get_localIndex(i,j,8);
	    Map[17].size = DOFvec[1]->get_NbubblesPsubEl();
	  
	tuint c=0, row=0,col;
	for (tuint k=0;k<18;++k) {
	  for (tuint m=0;m<Map[k].size;++m) {
	    col = 0;
	    for (tuint l=0;l<=k && col<=row;++l) {
	      const tuint vlc = (col+Map[l].size-1>row)?row+1-col:Map[l].size;
	      face.update_local_StfMtx(Map[k].ID+m,Map[l].ID,Kelp+c,vlc);
	      c+=vlc;
	      col+=vlc;
	    }
	    ++row;
	  }
	}
    }
  }
  // free memory
    free_mem();
}

void flxElement_2D_planestr::assembleStfHfem(flxStiffnessMtxBase& stfmtx)
{
  xyequal = true;
  dNxdx = new flxVec(4);
  dNxdy = new flxVec(4);
  dNxdxp = dNxdx->get_tmp_vptr();
  dNxdyp = dNxdy->get_tmp_vptr();
  rx = new flxVec(2);
  drx = new flxVec(2);
  rxp = rx->get_tmp_vptr();
  drxp = drx->get_tmp_vptr();
  ry = new flxVec(2);
  dry = new flxVec(2);
  ryp = ry->get_tmp_vptr();
  dryp = dry->get_tmp_vptr();
  // Gauss-Points ...
    const tuint GA = GI->pDegree2GPs(degreeEt+2);		// Gauss-points for full integration
    const tuint GA_SRI = 1;	// Gauss-points for reduced integration
    const tdouble* GP = GI->get_GP(GA);
    const tdouble* GW = GI->get_GW(GA);
    const tdouble* GP_SRI = GI->get_GP(GA_SRI);
    const tdouble* GW_SRI = GI->get_GW(GA_SRI);
  // assemble local stiffness matrix (full integration)
    FlxMtxSym Emtx(2);
    tdouble Ki[36] = {0.0};
    tdouble J[4];
    tdouble dNdx[8];
    for (tuint i=0; i<GA; ++i) {
      const tdouble lx = GP[i];
      hLP_vec(*rx,lx);
      dhLP_vec(*drx,lx);
      for (tuint j=0; j<GA; ++j) {
	const tdouble ly = GP[j];
	face.update_globalConst(lx,ly);
	face.get_J(J,lx,ly);
	const tdouble detJ = J[0]*J[3]-J[1]*J[2];
	{
	  const tdouble t = J[0]/detJ;
	  J[0] = J[3]/detJ;
	  J[1] = -1.0*J[1]/detJ;
	  J[2] = -1.0*J[2]/detJ;
	  J[3] = t;
	}
	const tdouble f = t->get_fun()->calc()*GW[i]*GW[j]*detJ;
	material->get_ElasticityMtxPS_SRI(Emtx,planestr);
	const tdouble E1 = Emtx.operator()(0,0)*f;
	const tdouble E2 = Emtx.operator()(1,0)*f;
	const tdouble E3 = Emtx.operator()(1,1)*f;
	hLP_vec(*ry,ly);
	dhLP_vec(*dry,ly);
	dhLP_2D_base();	// assign dNxdxp & dNxdyp
	for (tuint q=0;q<4;++q) {
	  dNdx[q*2]   = J[0]*dNxdxp[q] + J[1]*dNxdyp[q];
	  dNdx[q*2+1] = J[2]*dNxdxp[q] + J[3]*dNxdyp[q];
	}
	Ki[0]  += E1*dNdx[0]*dNdx[0];
	Ki[1]  += E2*dNdx[1]*dNdx[0];	Ki[2]  += E3*dNdx[1]*dNdx[1];
	Ki[3]  += E1*dNdx[2]*dNdx[0];	Ki[4]  += E2*dNdx[2]*dNdx[1];	Ki[5]  += E1*dNdx[2]*dNdx[2];
	Ki[6]  += E2*dNdx[3]*dNdx[0];	Ki[7]  += E3*dNdx[3]*dNdx[1];	Ki[8]  += E2*dNdx[3]*dNdx[2];	Ki[9]  += E3*dNdx[3]*dNdx[3];	
	Ki[10] += E1*dNdx[4]*dNdx[0];	Ki[11] += E2*dNdx[4]*dNdx[1];	Ki[12] += E1*dNdx[4]*dNdx[2];	Ki[13] += E2*dNdx[4]*dNdx[3];	Ki[14] += E1*dNdx[4]*dNdx[4];	
	Ki[15] += E2*dNdx[5]*dNdx[0];	Ki[16] += E3*dNdx[5]*dNdx[1];	Ki[17] += E2*dNdx[5]*dNdx[2];	Ki[18] += E3*dNdx[5]*dNdx[3];	Ki[19] += E2*dNdx[5]*dNdx[4];	Ki[20] += E3*dNdx[5]*dNdx[5];	
	Ki[21] += E1*dNdx[6]*dNdx[0];	Ki[22] += E2*dNdx[6]*dNdx[1];	Ki[23] += E1*dNdx[6]*dNdx[2];	Ki[24] += E2*dNdx[6]*dNdx[3];	Ki[25] += E1*dNdx[6]*dNdx[4];	Ki[26] += E2*dNdx[6]*dNdx[5];	Ki[27] += E1*dNdx[6]*dNdx[6];
	Ki[28] += E2*dNdx[7]*dNdx[0];	Ki[29] += E3*dNdx[7]*dNdx[1];	Ki[30] += E2*dNdx[7]*dNdx[2];	Ki[31] += E3*dNdx[7]*dNdx[3];	Ki[32] += E2*dNdx[7]*dNdx[4];	Ki[33] += E3*dNdx[7]*dNdx[5];	Ki[34] += E2*dNdx[7]*dNdx[6];	Ki[35] += E3*dNdx[7]*dNdx[7];
      }
    }
  
  // assemble local stiffness matrix (reduced integration)
    for (tuint i=0; i<GA_SRI; ++i) {
      const tdouble lx = GP_SRI[i];
      for (tuint j=0; j<GA_SRI; ++j) {
	const tdouble ly = GP_SRI[j];
	face.update_globalConst(lx,ly);
	face.get_J(J,lx,ly);
	const tdouble detJ = J[0]*J[3]-J[1]*J[2];
	{
	  const tdouble t = J[0]/detJ;
	  J[0] = J[3]/detJ;
	  J[1] = -1.0*J[1]/detJ;
	  J[2] = -1.0*J[2]/detJ;
	  J[3] = t;
	}
	const tdouble f = t->get_fun()->calc()*material->get_ElasticityMtxPS_SRI(planestr)*GW_SRI[i]*GW_SRI[j]*detJ;
	hLP_vec(*rx,lx);
	dhLP_vec(*drx,lx);
	hLP_vec(*ry,ly);
	dhLP_vec(*dry,ly);
	dhLP_2D_base();	// assign dNxdxp & dNxdyp
	for (tuint q=0;q<4;++q) {
	  dNdx[q*2+1] = J[0]*dNxdxp[q] + J[1]*dNxdyp[q];
	  dNdx[q*2]   = J[2]*dNxdxp[q] + J[3]*dNxdyp[q];
	}
	for (tuint k=0,c=0;k<8;++k) {
	  for (tuint l=0;l<=k;++l,++c) {
	    Ki[c] += dNdx[k]*dNdx[l]*f;
	  }
	}
      }
    }
  
  tdouble K[144] = {0.0};
  tuint c = 0;
  for (tuint i=0;i<4;++i) {
    for (tuint k=0;k<2;++k) {
      const tuint row = i*3+k;
      // j<i
	for (tuint j=0;j<i;++j) {
	  for (tuint l=0;l<2;++l) {
	    const tuint col = j*3+l;
	    K[row*12+col] = Ki[c];
	    K[col*12+row] = Ki[c];
	    ++c;
	  }
	}
      // j==i
	if (k==1) {
	  const tuint col = i*3;
	  K[row*12+col] = Ki[c];
	  K[col*12+row] = Ki[c];
	  ++c;
	}
	K[row*12+row] = Ki[c];
	++c;
    }
  }
  
  // transform matrix (1st step)
    tdouble t[3];
    const tdouble* T = face.get_TransMtx();
    for (tuint i=0;i<4;++i) {		// horizontal boxes
      for (tuint k=0;k<2;++k) {		// column in box
	const tuint col = i*3+k;
	for (tuint j=0;j<4;++j) {	// vertical boxes
	  for (tuint l=0;l<3;++l) {	// row in box
	    t[l] = 0.0;
	    for (tuint m=0;m<2;++m) {
	      t[l] += K[(j*3+m)*12+col] * T[m*3+l];
	    }
	  }
	  for (tuint l=0;l<3;++l) {	// row in box
	    K[(j*3+l)*12+col] = t[l];
	  }
	}
      }
    }
  // transform matrix (2nd step)
    for (tuint i=0;i<4;++i) {
      for (tuint k=0;k<3;++k) {
	const tuint row = i*3+k;
	for (tuint j=0;j<=i;++j) {
	  for (tuint l=0;l<3;++l) {
	    t[l] = 0.0;
	    for (tuint m=0;m<2;++m) {
	      t[l] += K[row*12+j*3+m] * T[m*3+l];
	    }
	  }
	  for (tuint l=0;l<3;++l) {
	    K[row*12+j*3+l] = t[l];
	  }
	}
      }
    }
  // assemble global DOFs
    bool reqMap[3] = {false};
    face.get_DOFlist()->requires_nodal_DOF(T,reqMap);
    tnlong mapV[12];
    for (tuint i=0;i<4;++i) {
      for (tuint j=0;j<3;++j) {
	if (reqMap[j]) {
	  mapV[i*3+j] = face.get_vertex(i).find_dof(FlxDof::get_dof(j),false)->get_DOFindex();
	}
      }
    }
    for (tuint i=0;i<12;++i) {
      if (reqMap[i%3]) {
	for (tuint j=0;j<=i;++j) {
	  if (reqMap[j%3]) {
	    stfmtx.Stf_set_value(mapV[i],mapV[j],K[i*12+j]);
	  }
	}
      }
    }
  free_mem();
}

void flxElement_2D_planestr::dhLP_2D_base()
{
  dhLP_2D_base_help(dNxdxp,drxp,ryp,edgeXorder,bubbleXorderX,bubbleXorderY,tensorSpaceX);
  dhLP_2D_base_help(dNxdyp,rxp,dryp,edgeXorder,bubbleXorderX,bubbleXorderY,tensorSpaceX);
  if (xyequal) return;
  dhLP_2D_base_help(dNydxp,drxp,ryp,edgeYorder,bubbleYorderX,bubbleYorderY,tensorSpaceY);
  dhLP_2D_base_help(dNydyp,rxp,dryp,edgeYorder,bubbleYorderX,bubbleYorderY,tensorSpaceY);
}

void flxElement_2D_planestr::dhLP_2D_base_help(tdouble* res, const tdouble* r1, const tdouble* r2, const tuint* edgeorder, const tuint pOrder1, const tuint pOrder2, const bool tensSp)
{
  // nodal DOFs
    res[0] = r1[0]*r2[0];
    res[1] = r1[1]*r2[0];
    res[2] = r1[1]*r2[1];
    res[3] = r1[0]*r2[1];
  // edge DOFs
    tuint c = 4;
    hLP_2D_bemc(&res[c],r1,r2[0],edgeOrient[0],edgeorder[0]); c+=edgeorder[0]-1;
    hLP_2D_bemc(&res[c],r2,r1[1],edgeOrient[1],edgeorder[1]); c+=edgeorder[1]-1;
    hLP_2D_bemc(&res[c],r1,r2[1],edgeOrient[2],edgeorder[2]); c+=edgeorder[2]-1;
    hLP_2D_bemc(&res[c],r2,r1[0],edgeOrient[3],edgeorder[3]); c+=edgeorder[3]-1;
  // bubble-DOFs
    if (tensSp) {
      for (tuint i=2;i<=pOrder1;++i) {
	for (tuint j=2;j<=pOrder2;++j) {
	  res[c++] = r1[i]*r2[j];
	}
      }
    } else {
      const tuint MAX = (pOrder1>=pOrder2)?pOrder1:pOrder2;
      for (tuint i=2;i<=MAX-2 && i<=pOrder1;++i) {
	for (tuint j=2;j<=pOrder2 && i+j<=MAX;++j) {
	  res[c++] = r1[i]*r2[j];
	}
      }
    }
}

void flxElement_2D_planestr::hLP_2D_bemc(tdouble* res, const tdouble* r1p, const tdouble& r2p, const bool orient, const tuint edgeOrder) const
{
  if (orient) {
    for (tuint k=2;k<=edgeOrder;++k) {
      res[k-2] = r1p[k]*r2p;
    }
  } else {
    for (tuint k=2;k<=edgeOrder;k+=2) {
      res[k-2] = r1p[k]*r2p;
    }
    for (tuint k=3;k<=edgeOrder;k+=2) {
      res[k-2] = -1.0*r1p[k]*r2p;
    }
  }
}

const bool flxElement_2D_planestr::check_hFEM(const tuint meshIntLx, const tuint meshIntLy)
{
  if (meshIntLx!=1 || meshIntLy!=1) return false;
  assign_u_xy(0,0);
  if (Nx_N!=4 || Ny_N!=4) return false;
  return true;
}


