// ATC header files
#include "ATC_Error.h"
#include "FE_Mesh.h"
#include "FE_Element.h"
#include "FE_Interpolate.h"
#include "LinearSolver.h"
#include "PolynomialSolver.h"
#include "Utility.h"

// Other headers
#include "math.h"

using ATC_Utility::dbl_geq;
using ATC_Utility::det3;
using std::vector;

namespace ATC {

static const int localCoordinatesMaxIterations_ = 40;
static const double localCoordinatesTolerance = 1.e-09;


  // =============================================================
  //   class FE_Element
  // =============================================================
  FE_Element::FE_Element(const int nSD,
                         int numFaces,
                         int numNodes,
                         int numFaceNodes,
                         int numNodes1d)
    : nSD_(nSD),
      numFaces_(numFaces),
      numNodes_(numNodes),
      numFaceNodes_(numFaceNodes),
      numNodes1d_(numNodes1d),
      tolerance_(localCoordinatesTolerance),
      projectionGuess_(COORDINATE_ALIGNED)
  {
    feInterpolate_ = NULL;
  }

  FE_Element::~FE_Element()
  {
    if (feInterpolate_) delete feInterpolate_;
  }

  int FE_Element::num_ips() const
  { 
    return feInterpolate_->num_ips(); 
  }

  int FE_Element::num_face_ips() const
  { 
    return feInterpolate_->num_face_ips(); 
  }

  void FE_Element::face_coordinates(const DENS_MAT &eltCoords, 
                                    const int faceID,
                                    DENS_MAT & faceCoords) const
  {
    faceCoords.reset(nSD_, numFaceNodes_);

    for (int inode=0; inode < numFaceNodes_; inode++) 
    {
      int id = localFaceConn_(faceID,inode);
      for (int isd=0; isd<nSD_; isd++) {
        faceCoords(isd,inode) = eltCoords(isd,id);
      }
    }
  }

  void FE_Element::mapping(const int inode, vector<int> &mapping) const
  {
    for (int iSD=0; iSD<nSD_; ++iSD) {
      mapping[iSD] = static_cast<int>((localCoords_(iSD,inode)+1)/2*
                                      (numNodes1d_-1));
    }
  }

  DENS_VEC FE_Element::local_coords_1d() const
  {
    DENS_VEC localCoords1d(numNodes1d_);
    for (int inode1d=0; inode1d<numNodes1d_; ++inode1d) {
       localCoords1d(inode1d) = (double(inode1d)/double(numNodes1d_-1))*2 - 1;
    }
    return localCoords1d;
  }

  void FE_Element::centroid(const DENS_MAT &eltCoords,
                                  DENS_VEC &centroid) const
  {
    centroid.reset(nSD_);
    for (int i = 0; i < nSD_; i++) {
      centroid(i) = eltCoords.row_mean(i);
    }
  }
 
  // -------------------------------------------------------------
  //  generic conversion from global to local coordinates using
  //  Newton's method to solve the nonliear equation that arises
  // -------------------------------------------------------------
  bool FE_Element::local_coordinates(const DENS_MAT &eltCoords,
                                     const DENS_VEC &x,
                                     DENS_VEC &xi) const
  {
    // initial guess
    DENS_VEC xiGuess(nSD_);
    this->initial_local_coordinates(eltCoords,x,xiGuess);
    // clip out-of-range guesses
    if (fabs(xiGuess(0)) > 1.0) xiGuess(0) = 0.;
    if (fabs(xiGuess(1)) > 1.0) xiGuess(1) = 0.;
    if (fabs(xiGuess(2)) > 1.0) xiGuess(2) = 0.;
    
    // iteratively solve the equation by calculating the global 
    // position of the guess and bringing the difference between it
    // and the actual global position of x to zero
    //
    // uses Newton's method
    DENS_VEC N(numNodes_);
    DENS_MAT dNdr(nSD_,numNodes_);

    DENS_VEC xGuess(nSD_);
    DENS_VEC xDiff(nSD_);
    DENS_MAT eltCoordsT = transpose(eltCoords);
    int count = 0;
    bool converged = false;
    while (count < localCoordinatesMaxIterations_) {
      feInterpolate_->compute_N_dNdr(xiGuess,N,dNdr);
      xGuess = N*eltCoordsT;
      xDiff  = xGuess-x; 
      // determine if the guess is close enough.
      // if it is, take it and run
      // if not, use Newton's method to update the guess
      if (!dbl_geq(abs(xDiff(0)),tolerance_) && 
          !dbl_geq(abs(xDiff(1)),tolerance_) && 
          !dbl_geq(abs(xDiff(2)),tolerance_)) {
        converged = true;
        xi = xiGuess;
        break;
      } else {
        xiGuess = xiGuess - transpose(inv(dNdr*eltCoordsT))*xDiff;
      }
      ++count;
    }
    return converged;
  }

  // -------------------------------------------------------------
  //  guess for initial local coordinates
  // -------------------------------------------------------------
  void FE_Element::initial_local_coordinates(const DENS_MAT &eltCoords,
                                             const DENS_VEC &x,
                                             DENS_VEC &xi) const
  {
    xi.reset(nSD_);
    if      (projectionGuess_ == COORDINATE_ALIGNED) {
      double min=0; double max=0;
      for (int i=0; i<nSD_; ++i) {
        bounds_in_dim(eltCoords,i,min,max);
        xi(i) = 2.0*(x(i)-min)/(max-min) - 1.0;
      }
    }
    else if (projectionGuess_ == CENTROID_LINEARIZED) { 
      DENS_VEC xi0(nSD_); xi0 = 0;
      DENS_VEC x0(nSD_), dx(nSD_);
      centroid(eltCoords,x0); 
      dx = x - x0; 
      vector<DENS_VEC> ts; ts.reserve(nSD_);
      tangents(eltCoords,xi0,ts);
      DENS_VEC & t1 = ts[0];
      DENS_VEC & t2 = ts[1];
      DENS_VEC & t3 = ts[2];
      double J = det3(t1.ptr(),t2.ptr(),t3.ptr());
      double J1 = det3(dx.ptr(),t2.ptr(),t3.ptr());
      double J2 = det3(t1.ptr(),dx.ptr(),t3.ptr());
      double J3 = det3(t1.ptr(),t2.ptr(),dx.ptr());
      xi(0) = J1/J;
      xi(1) = J2/J;
      xi(2) = J3/J;
    }
    else if (projectionGuess_ == TWOD_ANALYTIC) { 
      // assume x-y planar and HEX8
      double x0 = x(0), y0 = x(1);
      double X[4] = {eltCoords(0,0),eltCoords(0,1),eltCoords(0,2),eltCoords(0,3)};
      double Y[4] = {eltCoords(1,0),eltCoords(1,1),eltCoords(1,2),eltCoords(1,3)};
      double c[3]={0,0,0};
      c[0] = y0*X[0] - y0*X[1] - y0*X[2] + y0*X[3] - x0*Y[0] + (X[1]*Y[0])*0.5 + (X[2]*Y[0])*0.5 + x0*Y[1] - (X[0]*Y[1])*0.5 - (X[3]*Y[1])*0.5 + x0*Y[2] - (X[0]*Y[2])*0.5 - (X[3]*Y[2])*0.5 - x0*Y[3] + (X[1]*Y[3])*0.5 + (X[2]*Y[3])*0.5;
      c[1] = -(y0*X[0]) + y0*X[1] - y0*X[2] + y0*X[3] + x0*Y[0] - X[1]*Y[0] - x0*Y[1] + X[0]*Y[1] + x0*Y[2] - X[3]*Y[2] - x0*Y[3] + X[2]*Y[3];
      c[1] = (X[1]*Y[0])*0.5 - (X[2]*Y[0])*0.5 - (X[0]*Y[1])*0.5 + (X[3]*Y[1])*0.5 + (X[0]*Y[2])*0.5 - (X[3]*Y[2])*0.5 - (X[1]*Y[3])*0.5 + (X[2]*Y[3])*0.5;
      double xi2[2]={0,0};
      int nroots = solve_quadratic(c,xi2);
      if (nroots == 0) throw ATC_Error("no real roots in 2D analytic projection guess");
      double xi1[2]={0,0};
      xi1[0] = (4*x0 - X[0] + xi2[0]*X[0] - X[0] + xi2[0]*X[0] - X[0] - xi2[0]*X[0] - X[0] - xi2[0]*X[0])/(-X[0] + xi2[0]*X[0] + X[0] - xi2[0]*X[0] + X[0] + xi2[0]*X[0] - X[0] - xi2[0]*X[0]);
      xi1[1] = (4*x0 - X[0] + xi2[0]*X[0] - X[0] + xi2[0]*X[0] - X[0] - xi2[0]*X[0] - X[0] - xi2[0]*X[0])/(-X[0] + xi2[0]*X[0] + X[0] - xi2[0]*X[0] + X[0] + xi2[0]*X[0] - X[0] - xi2[0]*X[0]);
      // choose which one gives back x
      xi(0) = xi1[0]; 
      xi(1) = xi2[0]; 
      xi(2) = 0; 
    }
  }

 
  bool FE_Element::range_check(const DENS_MAT &eltCoords,
                               const DENS_VEC &x) const
  {
    double min=0; double max=0;
    for (int i=0; i<nSD_; ++i) {
      bounds_in_dim(eltCoords,i,min,max);
      if (!dbl_geq(x(i),min) || !dbl_geq(max,x(i))) return false;
    }
    return true;
  }


  // -------------------------------------------------------------
  //   Note: Only works for convex elements with planar faces with 
  //   outward normals
  // -------------------------------------------------------------
  bool FE_Element::contains_point(const DENS_MAT &eltCoords,
                                  const DENS_VEC &x) const
  {
    if (! range_check(eltCoords,x) ) return false;
    DENS_MAT faceCoords;
    DENS_VEC normal;
    normal.reset(nSD_);
    DENS_VEC faceToPoint;
    double dot;
    bool inside = true;
    for (int faceID=0; faceID<numFaces_; ++faceID) {
      face_coordinates(eltCoords, faceID, faceCoords);
      feInterpolate_->face_normal(faceCoords, 
                                  0,
                                  normal);
      faceToPoint = x - column(faceCoords, 0);
      dot = normal.dot(faceToPoint);
      if (dbl_geq(dot,0)) {
        inside = false;
        break;
      }
    }
    return inside;
  }

  // -------------------------------------------------------------
  //   returns the minimum and maximum values of an element in the
  //   specified dimension
  // -------------------------------------------------------------
  void FE_Element::bounds_in_dim(const DENS_MAT &eltCoords, const int dim,
                                 double &min, double &max) const
  {
    int it;
    // iterate over all nodes
    min = eltCoords(dim,0);
    it = 1;
    while (it < numNodes_) {
      if (dbl_geq(min,eltCoords(dim,it))) {
        if (dbl_geq(eltCoords(dim,it),min)) {
          ++it;
        } else {
          // set min to this node's coord in the specified dim, if it's 
          // smaller than the value previously stored 
          min = eltCoords(dim,it);
        }
      } else {
        ++it;
      }
    }
    max = eltCoords(dim,0);
    it = 1;
    while (it < numNodes_) {
      if (dbl_geq(max,eltCoords(dim,it))) {
        ++it;
      } else {
        // same, except max/larger
        max = eltCoords(dim,it);
      }
    }
  }
  
  // -------------------------------------------------------------
  //   shape_function calls should stay generic at all costs
  // -------------------------------------------------------------
  void FE_Element::shape_function(const VECTOR &xi,
                                  DENS_VEC &N) const
  {
    feInterpolate_->shape_function(xi, N);
  }

  void FE_Element::shape_function(const DENS_MAT eltCoords,
                                  const VECTOR &x,
                                  DENS_VEC &N)
  {
    DENS_VEC xi;
    local_coordinates(eltCoords, x, xi);

    feInterpolate_->shape_function(xi, N);
  }

  void FE_Element::shape_function(const DENS_MAT eltCoords,
                                  const VECTOR &x,
                                  DENS_VEC &N,
                                  DENS_MAT &dNdx)
  {
    DENS_VEC xi;
    local_coordinates(eltCoords, x, xi);

    feInterpolate_->shape_function(eltCoords, xi, N, dNdx);
  }  

  void FE_Element::shape_function_derivatives(const DENS_MAT eltCoords,
                                  const VECTOR &x,
                                  DENS_MAT &dNdx)
  {
    DENS_VEC xi;
    local_coordinates(eltCoords, x, xi);

    feInterpolate_->shape_function_derivatives(eltCoords, xi, dNdx);
  }  


  void FE_Element::shape_function(const DENS_MAT eltCoords,
                                  DENS_MAT &N,
                                  vector<DENS_MAT> &dN,
                                  DIAG_MAT &weights)
  {
    feInterpolate_->shape_function(eltCoords, N, dN, weights);
  }

  void FE_Element::face_shape_function(const DENS_MAT &eltCoords,
                                       const int faceID,
                                       DENS_MAT &N,
                                       DENS_MAT &n,
                                       DIAG_MAT &weights)
  {
    DENS_MAT faceCoords;
    face_coordinates(eltCoords, faceID, faceCoords); 
    
    feInterpolate_->face_shape_function(eltCoords, faceCoords, faceID,
                                        N, n, weights);
  }

  void FE_Element::face_shape_function(const DENS_MAT &eltCoords,
                                       const int faceID,
                                       DENS_MAT &N,
                                       vector<DENS_MAT> &dN,
                                       vector<DENS_MAT> &Nn,
                                       DIAG_MAT &weights)
  {
    DENS_MAT faceCoords;
    face_coordinates(eltCoords, faceID, faceCoords); 
    
    feInterpolate_->face_shape_function(eltCoords, faceCoords, faceID,
                                        N, dN, Nn, weights);
  }

  double FE_Element::face_normal(const DENS_MAT &eltCoords,
                                 const int faceID,
                                 int ip,
                                 DENS_VEC &normal) 
  {
    DENS_MAT faceCoords;
    face_coordinates(eltCoords, faceID, faceCoords);

    double J = feInterpolate_->face_normal(faceCoords, ip, normal);
    return J;
  }

  void FE_Element::tangents(const DENS_MAT &eltCoords,
                            const DENS_VEC & localCoords,
                            vector<DENS_VEC> &tangents,
                            const bool normalize)  const
  {
    feInterpolate_->tangents(eltCoords,localCoords,tangents,normalize);
  }
  
  
  // =============================================================
  //   class FE_ElementHex
  // =============================================================
  FE_ElementHex::FE_ElementHex(int numNodes, 
                               int numFaceNodes,
                               int numNodes1d)
    : FE_Element(3,                 // number of spatial dimensions
                 6,                 // number of faces
                 numNodes,
                 numFaceNodes,
                 numNodes1d)
  {
    //       3 --- 2
    //      /|    /|
    //     / 0 --/ 1     y
    //    7 --- 6 /      |
    //    |     |/       |
    //    4 --- 5         -----x
    //                  /
    //                 /
    //                z
  
    // Basic properties of element:
    vol_ = 8.0;
    faceArea_ = 4.0;
    
    // Order-specific information:
    if (numNodes != 8  &&
        numNodes != 20 &&
        numNodes != 27) {
      throw ATC_Error("Unrecognized interpolation order specified " 
                      "for element class: \n"                       
                      "  element only knows how to construct lin "  
                        "and quad elments.");
    }

    localCoords_.resize(nSD_,numNodes_);
    localFaceConn_ = Array2D<int>(numFaces_,numFaceNodes_);
    
    // Matrix of local nodal coordinates
    localCoords_(0,0) = -1; localCoords_(0,4) = -1; 
    localCoords_(1,0) = -1; localCoords_(1,4) = -1; 
    localCoords_(2,0) = -1; localCoords_(2,4) =  1;
    // 
    localCoords_(0,1) =  1; localCoords_(0,5) =  1; 
    localCoords_(1,1) = -1; localCoords_(1,5) = -1; 
    localCoords_(2,1) = -1; localCoords_(2,5) =  1;
    //
    localCoords_(0,2) =  1; localCoords_(0,6) =  1; 
    localCoords_(1,2) =  1; localCoords_(1,6) =  1; 
    localCoords_(2,2) = -1; localCoords_(2,6) =  1;
    //
    localCoords_(0,3) = -1; localCoords_(0,7) = -1; 
    localCoords_(1,3) =  1; localCoords_(1,7) =  1; 
    localCoords_(2,3) = -1; localCoords_(2,7) =  1;
    if (numNodes >= 20) {
      // only for quads
      localCoords_(0,8) =  0;  localCoords_(0,14) =  1; 
      localCoords_(1,8) = -1;  localCoords_(1,14) =  1; 
      localCoords_(2,8) = -1;  localCoords_(2,14) =  0;
      // 
      localCoords_(0,9) =  1;  localCoords_(0,15) = -1; 
      localCoords_(1,9) =  0;  localCoords_(1,15) =  1; 
      localCoords_(2,9) = -1;  localCoords_(2,15) =  0;
      //
      localCoords_(0,10) =  0; localCoords_(0,16) =  0; 
      localCoords_(1,10) =  1; localCoords_(1,16) = -1; 
      localCoords_(2,10) = -1; localCoords_(2,16) =  1;
      //
      localCoords_(0,11) = -1; localCoords_(0,17) =  1; 
      localCoords_(1,11) =  0; localCoords_(1,17) =  0; 
      localCoords_(2,11) = -1; localCoords_(2,17) =  1;
      //
      localCoords_(0,12) = -1; localCoords_(0,18) =  0; 
      localCoords_(1,12) = -1; localCoords_(1,18) =  1; 
      localCoords_(2,12) =  0; localCoords_(2,18) =  1;
      //
      localCoords_(0,13) =  1; localCoords_(0,19) = -1; 
      localCoords_(1,13) = -1; localCoords_(1,19) =  0; 
      localCoords_(2,13) =  0; localCoords_(2,19) =  1;
      if (numNodes >= 27) {
        // only for quads
        localCoords_(0,20) =  0; localCoords_(0,24) =  1; 
        localCoords_(1,20) =  0; localCoords_(1,24) =  0; 
        localCoords_(2,20) =  0; localCoords_(2,24) =  0;
        //
        localCoords_(0,21) =  0; localCoords_(0,25) =  0; 
        localCoords_(1,21) =  0; localCoords_(1,25) = -1; 
        localCoords_(2,21) = -1; localCoords_(2,25) =  0;
        //
        localCoords_(0,22) =  0; localCoords_(0,26) =  0; 
        localCoords_(1,22) =  0; localCoords_(1,26) =  1; 
        localCoords_(2,22) =  1; localCoords_(2,26) =  0;
        //
        localCoords_(0,23) = -1; 
        localCoords_(1,23) =  0; 
        localCoords_(2,23) =  0;
      }
    }

    // Matrix of local face connectivity
    // -x                    // +x
    localFaceConn_(0,0) = 0; localFaceConn_(1,0) = 1; 
    localFaceConn_(0,1) = 4; localFaceConn_(1,1) = 2; 
    localFaceConn_(0,2) = 7; localFaceConn_(1,2) = 6; 
    localFaceConn_(0,3) = 3; localFaceConn_(1,3) = 5; 
    if (numNodes >= 20) {
      localFaceConn_(0,4) = 12; localFaceConn_(1,4) = 9; 
      localFaceConn_(0,5) = 19; localFaceConn_(1,5) = 14; 
      localFaceConn_(0,6) = 15; localFaceConn_(1,6) = 17; 
      localFaceConn_(0,7) = 11; localFaceConn_(1,7) = 13; 
      if (numNodes >= 27) {
        localFaceConn_(0,8) = 23; localFaceConn_(1,8) = 24;
      }
    }

    // -y                    // +y
    localFaceConn_(2,0) = 0; localFaceConn_(3,0) = 3; 
    localFaceConn_(2,1) = 1; localFaceConn_(3,1) = 7; 
    localFaceConn_(2,2) = 5; localFaceConn_(3,2) = 6; 
    localFaceConn_(2,3) = 4; localFaceConn_(3,3) = 2; 
    if (numNodes >= 20) {
      localFaceConn_(2,4) = 8;  localFaceConn_(3,4) = 15; 
      localFaceConn_(2,5) = 13; localFaceConn_(3,5) = 18; 
      localFaceConn_(2,6) = 16; localFaceConn_(3,6) = 14; 
      localFaceConn_(2,7) = 12; localFaceConn_(3,7) = 10; 
      if (numNodes >= 27) {
        localFaceConn_(2,8) = 25; localFaceConn_(3,8) = 26;
      }
    }
 
    // -z                    // +z
    localFaceConn_(4,0) = 0; localFaceConn_(5,0) = 4; 
    localFaceConn_(4,1) = 3; localFaceConn_(5,1) = 5; 
    localFaceConn_(4,2) = 2; localFaceConn_(5,2) = 6; 
    localFaceConn_(4,3) = 1; localFaceConn_(5,3) = 7; 
    if (numNodes >= 20) {
      localFaceConn_(4,4) = 8;  localFaceConn_(5,4) = 16; 
      localFaceConn_(4,5) = 11; localFaceConn_(5,5) = 17; 
      localFaceConn_(4,6) = 10; localFaceConn_(5,6) = 18; 
      localFaceConn_(4,7) = 9;  localFaceConn_(5,7) = 19; 
      if (numNodes >= 27) {
        localFaceConn_(4,8) = 21; localFaceConn_(5,8) = 22;
      }
    }

    if (numNodes == 8) {
      feInterpolate_ = new FE_InterpolateCartLin(this);
    } else if (numNodes == 20) {
      feInterpolate_ = new FE_InterpolateCartSerendipity(this);
    } else if (numNodes == 27) {
      feInterpolate_ = new FE_InterpolateCartLagrange(this);
    }

    // determine alignment and skewness to see which guess we should use
    
  }

  FE_ElementHex::~FE_ElementHex()
  {
    // Handled by base class
  }

  void FE_ElementHex::set_quadrature(FeIntQuadrature type) 
  { 
    feInterpolate_->set_quadrature(HEXA,type);
  }
  
  bool FE_ElementHex::contains_point(const DENS_MAT &eltCoords,
                                     const DENS_VEC &x) const
  {
    if (! range_check(eltCoords,x) ) return false;

    DENS_VEC xi;
    bool converged = local_coordinates(eltCoords,x,xi); 
    if (!converged) return false;
    for (int i=0; i<nSD_; ++i) {
      if (!dbl_geq(1.0,abs(xi(i)))) return false;
    }
    return true;
  }

  // =============================================================
  //   class FE_ElementRect
  // =============================================================
  FE_ElementRect::FE_ElementRect()
    : FE_ElementHex(8,4,2)
  {
    // Handled by hex class
  }

  FE_ElementRect::~FE_ElementRect()
  {
    // Handled by base class
  }

  bool FE_ElementRect::contains_point(const DENS_MAT &eltCoords,
                                      const DENS_VEC &x) const
  {
    return range_check(eltCoords,x);
  }

  // much faster than the unstructured method
  bool FE_ElementRect::local_coordinates(const DENS_MAT &eltCoords,
                                         const DENS_VEC &x,
                                         DENS_VEC &xi) const
  {
    xi.reset(nSD_);
    double min = 0.0;
    double max = 0.0;
    for (int iSD=0; iSD<nSD_; ++iSD) {
      min = eltCoords(iSD,0);
      max = eltCoords(iSD,6);
      xi(iSD) = 2.0*(x(iSD)-min)/(max-min) - 1.0;
    }
    return true;
  }
  

  // =============================================================
  //   class FE_ElementTet
  // =============================================================
  FE_ElementTet::FE_ElementTet(int numNodes,
                               int numFaceNodes,
                               int numNodes1d)
    : FE_Element(3,                 // number of spatial dimensions
                 4,                 // number of faces
                 numNodes,
                 numFaceNodes,
                 numNodes1d)
  {
    // t
    // ^
    // |
    // |
    //                          s
    // 3                     .7
    // |\```---           .
    // |  \     ```--2 .
    // |    \       .| 
    // |      \  .   |
    // |      . \    |
    // |   .      \  |
    // |.___________\|  -------> r
    // 0             1  
    //
    // (This is as dictated by the EXODUSII standard.)
    //
    // The face opposite point 1 has r = 0,
    //                         2 has s = 0,
    //                         3 has t = 0,
    //                         0 has u = 0.

    // Basic properties of element:
    vol_ = 1.0/6.0; // local volume
    faceArea_ = 1.0/2.0;
 
    // Order-specific information:
    if (numNodes != 4 && numNodes != 10) {
      throw ATC_Error("Unrecognized interpolation order specified " 
                      "for element class: \n"                       
                      "  element only knows how to construct lin "  
                        "and quad elments.");
    }
    
    localCoords_.resize(nSD_+1, numNodes_);
    localFaceConn_ = Array2D<int>(numFaces_,numFaceNodes_);
    
    // Matrix of local nodal coordinates
    //
    // Remember, there's actually another coordinate too (u), coming
    // out from the third non-normal face. But we can deal with it on
    // the fly; these coordinates are barycentric, such that
    // r + s + t + u = 1 always. As such we only need to deal with r,
    // s, and t and the computations become easy.
    //
    // The first three axes correspond to x, y, and z (essentially),
    // for the canonical element.
    
    // Everyone gets these nodes...
    localCoords_(0,0) = 0; localCoords_(0,2) = 0;
    localCoords_(1,0) = 0; localCoords_(1,2) = 1; 
    localCoords_(2,0) = 0; localCoords_(2,2) = 0;
    localCoords_(3,0) = 1; localCoords_(3,2) = 0;
    //
    localCoords_(0,1) = 1; localCoords_(0,3) = 0; 
    localCoords_(1,1) = 0; localCoords_(1,3) = 0; 
    localCoords_(2,1) = 0; localCoords_(2,3) = 1; 
    localCoords_(3,1) = 0; localCoords_(3,3) = 0;
    if (numNodes >= 10) {
      // ...quads get even more!
      localCoords_(0,4) = 0.5; localCoords_(0,5) = 0.5;
      localCoords_(1,4) = 0.0; localCoords_(1,5) = 0.5; 
      localCoords_(2,4) = 0.0; localCoords_(2,5) = 0.0;
      localCoords_(3,4) = 0.5; localCoords_(3,5) = 0.0;
      //
      localCoords_(0,6) = 0.0; localCoords_(0,7) = 0.0; 
      localCoords_(1,6) = 0.5; localCoords_(1,7) = 0.0; 
      localCoords_(2,6) = 0.0; localCoords_(2,7) = 0.5; 
      localCoords_(3,6) = 0.5; localCoords_(3,7) = 0.5;
      //
      localCoords_(0,8) = 0.5; localCoords_(0,9) = 0.0; 
      localCoords_(1,8) = 0.0; localCoords_(1,9) = 0.5; 
      localCoords_(2,8) = 0.5; localCoords_(2,9) = 0.5; 
      localCoords_(3,8) = 0.0; localCoords_(3,9) = 0.0;
    }
  
    // Matrix of local face connectivity:
    // ...opposite point 0,     ...opposite point 2, 
    localFaceConn_(0,0) = 1; localFaceConn_(2,0) = 0;
    localFaceConn_(0,1) = 2; localFaceConn_(2,1) = 1;
    localFaceConn_(0,2) = 3; localFaceConn_(2,2) = 3;
   
    // ...opposite point 1,     ...opposite point 3.
    localFaceConn_(1,0) = 2; localFaceConn_(3,0) = 0; 
    localFaceConn_(1,1) = 0; localFaceConn_(3,1) = 2; 
    localFaceConn_(1,2) = 3; localFaceConn_(3,2) = 1; 

    feInterpolate_ = new FE_InterpolateSimpLin(this);
  }

  FE_ElementTet::~FE_ElementTet()
  {
    // Handled by base class
  }

  void FE_ElementTet::set_quadrature(FeIntQuadrature type) 
  { 
    feInterpolate_->set_quadrature(TETRA,type);
  }

  bool FE_ElementTet::local_coordinates(const DENS_MAT &eltCoords,
                                        const DENS_VEC &x,
                                        DENS_VEC &xi) const
  {
    DENS_MAT T(nSD_, numNodes_-1);
    DENS_VEC r(nSD_);
    for (int iSD=0; iSD<nSD_; ++iSD) {
      for (int inode=1; inode<numNodes_; ++inode) {
        T(iSD, inode-1) = eltCoords(iSD, inode) -
                          eltCoords(iSD, 0);
      }
      r(iSD) = x(iSD) - eltCoords(iSD, 0);
    }
    MultMv(inv(T), r, xi, false, 1.0, 0.0);
    return true;
  }

  bool FE_ElementTet::contains_point(const DENS_MAT &eltCoords,
                                     const DENS_VEC &x) const
  {
    if (! range_check(eltCoords,x) ) return false;
    DENS_VEC xi(nSD_);
    bool converged = local_coordinates(eltCoords, x, xi);
    if (! converged) return false;
    double sum = 0.0;
    bool inside = true;
    for (int iSD = 0; iSD < nSD_; ++iSD) {
      if (dbl_geq(xi(iSD),1.0) || dbl_geq(0.0,xi(iSD))) {
        inside = false;
        break;
      }
      sum += xi(iSD);
    }
    if (dbl_geq(sum,1.0)) inside = false;
    return inside;
  }


}; // namespace ATC
