// ATC header files #include "ATC_Error.h" #include "FE_Element.h" #include "FE_Interpolate.h" #include "FE_Quadrature.h" // Other headers #include using std::map; using std::vector; namespace ATC { FE_Interpolate::FE_Interpolate(FE_Element *feElement) : feElement_(feElement), nSD_(feElement->num_dims()) { // Nothing to do here } FE_Interpolate::~FE_Interpolate() { if (!feQuadList_.empty()) { map::iterator qit; for (qit = feQuadList_.begin(); qit != feQuadList_.end(); ++qit) { delete (qit->second); } } } void FE_Interpolate::set_quadrature(FeEltGeometry geo, FeIntQuadrature quad) { if (feQuadList_.count(quad) == 0) { feQuad_ = new FE_Quadrature(geo,quad); feQuadList_[quad] = feQuad_; } else { feQuad_ = feQuadList_[quad]; } precalculate_shape_functions(); } void FE_Interpolate::precalculate_shape_functions() { int numEltNodes = feElement_->num_elt_nodes(); int numFaces = feElement_->num_faces(); int numFaceNodes = feElement_->num_face_nodes(); int numIPs = feQuad_->numIPs; DENS_MAT &ipCoords = feQuad_->ipCoords; int numFaceIPs = feQuad_->numFaceIPs; vector &ipFaceCoords = feQuad_->ipFaceCoords; DENS_MAT &ipFace2DCoords = feQuad_->ipFace2DCoords; // Compute elemental shape functions at ips N_.reset(numIPs,numEltNodes); dNdr_.assign(numIPs,DENS_MAT(nSD_,numEltNodes)); for (int ip = 0; ip < numIPs; ip++) { CLON_VEC thisIP = column(ipCoords,ip); CLON_VEC thisN = row(N_,ip); DENS_MAT &thisdNdr = dNdr_[ip]; compute_N(thisIP,thisN); compute_N_dNdr(thisIP,thisN,thisdNdr); } // Compute face shape functions at ip's NFace_.assign(numFaces,DENS_MAT(numFaceIPs,numEltNodes)); dNdrFace_.resize(numFaces); for (int f = 0; f < numFaces; f++) { dNdrFace_[f].assign(numIPs,DENS_MAT(nSD_,numEltNodes)); } for (int f = 0; f < numFaces; f++) { for (int ip = 0; ip < numFaceIPs; ip++) { CLON_VEC thisIP = column(ipFaceCoords[f],ip); CLON_VEC thisN = row(NFace_[f],ip); DENS_MAT &thisdNdr = dNdrFace_[f][ip]; compute_N_dNdr(thisIP,thisN,thisdNdr); } } // Compute 2D face shape function derivatives dNdrFace2D_.assign(numFaceIPs,DENS_MAT(nSD_-1,numFaceNodes)); for (int ip = 0; ip < numFaceIPs; ip++) { CLON_VEC thisIP = column(ipFace2DCoords,ip); DENS_MAT &thisdNdr = dNdrFace2D_[ip]; compute_dNdr(thisIP, numFaceNodes,nSD_-1,feElement_->face_area(), thisdNdr); } } //----------------------------------------------------------------- // shape function value at a particular point given local coordinates //----------------------------------------------------------------- void FE_Interpolate::shape_function(const VECTOR &xi, DENS_VEC &N) { int numEltNodes = feElement_->num_elt_nodes(); N.resize(numEltNodes); compute_N(xi,N); } void FE_Interpolate::shape_function(const DENS_MAT &eltCoords, const VECTOR &xi, DENS_VEC &N, DENS_MAT &dNdx) { int numEltNodes = feElement_->num_elt_nodes(); N.resize(numEltNodes); DENS_MAT dNdr(nSD_,numEltNodes,false); compute_N_dNdr(xi,N,dNdr); DENS_MAT eltCoordsT = transpose(eltCoords); DENS_MAT dxdr, drdx; dxdr = dNdr*eltCoordsT; drdx = inv(dxdr); dNdx = drdx*dNdr; } void FE_Interpolate::shape_function_derivatives(const DENS_MAT &eltCoords, const VECTOR &xi, DENS_MAT &dNdx) { int numEltNodes = feElement_->num_elt_nodes(); DENS_MAT dNdr(nSD_,numEltNodes,false); DENS_VEC N(numEltNodes); compute_N_dNdr(xi,N,dNdr); DENS_MAT eltCoordsT = transpose(eltCoords); DENS_MAT dxdr, drdx; dxdr = dNdr*eltCoordsT; // tangents or Jacobian matrix drdx = inv(dxdr); dNdx = drdx*dNdr; // dN/dx = dN/dxi (dx/dxi)^-1 } void FE_Interpolate::tangents(const DENS_MAT &eltCoords, const VECTOR &xi, DENS_MAT &dxdr) const { int numEltNodes = feElement_->num_elt_nodes(); DENS_MAT dNdr(nSD_,numEltNodes,false); DENS_VEC N(numEltNodes); compute_N_dNdr(xi,N,dNdr); //dNdr.print("dNdr"); DENS_MAT eltCoordsT = transpose(eltCoords); //eltCoordsT.print("elt coords"); dxdr = dNdr*eltCoordsT; //dxdr.print("dxdr"); } void FE_Interpolate::tangents(const DENS_MAT &eltCoords, const VECTOR &xi, vector & dxdxi, const bool normalize) const { DENS_MAT dxdr; tangents(eltCoords,xi,dxdr); //dxdr.print("dxdr-post"); dxdxi.resize(nSD_); //for (int i = 0; i < nSD_; ++i) dxdxi[i] = CLON_VEC(dxdr,CLONE_COL,i); for (int i = 0; i < nSD_; ++i) { dxdxi[i].resize(nSD_); for (int j = 0; j < nSD_; ++j) { dxdxi[i](j) = dxdr(i,j); } } //dxdxi[0].print("t1"); //dxdxi[1].print("t2"); //dxdxi[2].print("t3"); if (normalize) { for (int j = 0; j < nSD_; ++j) { double norm = 0; VECTOR & t = dxdxi[j]; for (int i = 0; i < nSD_; ++i) norm += t(i)*t(i); norm = 1./sqrt(norm); for (int i = 0; i < nSD_; ++i) t(i) *= norm; } } } // ------------------------------------------------------------- // shape_function values at nodes // ------------------------------------------------------------- void FE_Interpolate::shape_function(const DENS_MAT &eltCoords, DENS_MAT &N, vector &dN, DIAG_MAT &weights) { int numEltNodes = feElement_->num_elt_nodes(); // Transpose eltCoords DENS_MAT eltCoordsT(transpose(eltCoords)); // Shape functions are simply the canonical element values N = N_; // Set sizes of matrices and vectors if ((int)dN.size() != nSD_) dN.resize(nSD_); for (int isd = 0; isd < nSD_; isd++) dN[isd].resize(feQuad_->numIPs,numEltNodes); weights.resize(feQuad_->numIPs,feQuad_->numIPs); // Create some temporary matrices: // Jacobian matrix: [dx/dr dy/ds dz/dt | dx/ds ... ] DENS_MAT dxdr, drdx, dNdx; // Loop over integration points for (int ip = 0; ip < feQuad_->numIPs; ip++) { // Compute dx/dxi matrix dxdr = dNdr_[ip]*eltCoordsT; drdx = inv(dxdr); // Compute dNdx and fill dN matrix dNdx = drdx * dNdr_[ip]; for (int isd = 0; isd < nSD_; isd++) for (int inode = 0; inode < numEltNodes; inode++) dN[isd](ip,inode) = dNdx(isd,inode); // Compute jacobian determinant of dxdr at this ip double J = dxdr(0,0) * (dxdr(1,1)*dxdr(2,2) - dxdr(2,1)*dxdr(1,2)) - dxdr(0,1) * (dxdr(1,0)*dxdr(2,2) - dxdr(1,2)*dxdr(2,0)) + dxdr(0,2) * (dxdr(1,0)*dxdr(2,1) - dxdr(1,1)*dxdr(2,0)); // Compute ip weight weights(ip,ip) = feQuad_->ipWeights(ip)*J; } } //----------------------------------------------------------------- // shape functions on a given face //----------------------------------------------------------------- void FE_Interpolate::face_shape_function(const DENS_MAT &eltCoords, const DENS_MAT &faceCoords, const int faceID, DENS_MAT &N, DENS_MAT &n, DIAG_MAT &weights) { int numFaceIPs = feQuad_->numFaceIPs; // Transpose eltCoords DENS_MAT eltCoordsT = transpose(eltCoords); // Shape functions are simply the canonical element values N = NFace_[faceID]; // Create some temporary matrices: // Jacobian matrix: [dx/dr dy/ds dz/dt | dx/ds ... ] DENS_MAT dxdr, drdx, dNdx; // Loop over integration points DENS_VEC normal(nSD_); n.resize(nSD_,numFaceIPs); weights.resize(numFaceIPs,numFaceIPs); for (int ip = 0; ip < numFaceIPs; ip++) { // Compute 2d jacobian determinant of dxdr at this ip double J = face_normal(faceCoords,ip,normal); // Copy normal at integration point for (int isd = 0; isd < nSD_; isd++) { n(isd,ip) = normal(isd); } // Compute ip weight weights(ip,ip) = feQuad_->ipFaceWeights(ip)*J; } } void FE_Interpolate::face_shape_function(const DENS_MAT &eltCoords, const DENS_MAT &faceCoords, const int faceID, DENS_MAT &N, vector &dN, vector &Nn, DIAG_MAT &weights) { int numEltNodes = feElement_->num_elt_nodes(); int numFaceIPs = feQuad_->numFaceIPs; // Transpose eltCoords DENS_MAT eltCoordsT = transpose(eltCoords); // Shape functions are simply the canonical element values N = NFace_[faceID]; // Set sizes of matrices and vectors if ((int)dN.size() != nSD_) dN.resize(nSD_); if ((int)Nn.size() != nSD_) Nn.resize(nSD_); for (int isd = 0; isd < nSD_; isd++) { dN[isd].resize(numFaceIPs,numEltNodes); Nn[isd].resize(numFaceIPs,numEltNodes); } weights.resize(numFaceIPs,numFaceIPs); // Create some temporary matrices: // Jacobian matrix: [dx/dr dy/ds dz/dt | dx/ds ... ] DENS_MAT dxdr, drdx, dNdx; DENS_VEC normal(nSD_); // Loop over integration points for (int ip = 0; ip < numFaceIPs; ip++) { // Compute dx/dxi matrix dxdr = dNdrFace_[faceID][ip] * eltCoordsT; drdx = inv(dxdr); // Compute 2d jacobian determinant of dxdr at this ip double J = face_normal(faceCoords,ip,normal); // Compute dNdx and fill dN matrix dNdx = drdx * dNdrFace_[faceID][ip]; for (int isd = 0; isd < nSD_; isd++) { for (int inode = 0; inode < numEltNodes; inode++) { dN[isd](ip,inode) = dNdx(isd,inode); Nn[isd](ip,inode) = N(ip,inode)*normal(isd); } } // Compute ip weight weights(ip,ip) = feQuad_->ipFaceWeights(ip)*J; } } // ------------------------------------------------------------- // face normal // ------------------------------------------------------------- double FE_Interpolate::face_normal(const DENS_MAT &faceCoords, int ip, DENS_VEC &normal) { // Compute dx/dr for deformed element DENS_MAT faceCoordsT = transpose(faceCoords); DENS_MAT dxdr = dNdrFace2D_[ip]*faceCoordsT; // Normal vector from cross product, hardcoded for 3D, sad normal(0) = dxdr(0,1)*dxdr(1,2) - dxdr(0,2)*dxdr(1,1); normal(1) = dxdr(0,2)*dxdr(1,0) - dxdr(0,0)*dxdr(1,2); normal(2) = dxdr(0,0)*dxdr(1,1) - dxdr(0,1)*dxdr(1,0); // Jacobian (3D) double J = sqrt(normal(0)*normal(0) + normal(1)*normal(1) + normal(2)*normal(2)); double invJ = 1.0/J; normal(0) *= invJ; normal(1) *= invJ; normal(2) *= invJ; return J; } int FE_Interpolate::num_ips() const { return feQuad_->numIPs; } int FE_Interpolate::num_face_ips() const { return feQuad_->numFaceIPs; } /********************************************************* * Class FE_InterpolateCartLagrange * * For computing Lagrange shape functions using Cartesian * coordinate systems (all quads/hexes fall under this * category, and any elements derived by degenerating * them). Not to be used for serendipity elements, which * should be implemented for SPEED. * *********************************************************/ FE_InterpolateCartLagrange::FE_InterpolateCartLagrange( FE_Element *feElement) : FE_Interpolate(feElement) { set_quadrature(HEXA,GAUSS2); } FE_InterpolateCartLagrange::~FE_InterpolateCartLagrange() { // Handled by base class } void FE_InterpolateCartLagrange::compute_N(const VECTOR &point, VECTOR &N) { // *** see comments for compute_N_dNdr *** const DENS_VEC &localCoords1d = feElement_->local_coords_1d(); int numEltNodes = feElement_->num_elt_nodes(); int numEltNodes1d = feElement_->num_elt_nodes_1d(); DENS_MAT lagrangeTerms(nSD_,numEltNodes1d); DENS_MAT lagrangeDenom(nSD_,numEltNodes1d); lagrangeTerms = 1.0; lagrangeDenom = 1.0; for (int iSD = 0; iSD < nSD_; ++iSD) { for (int inode = 0; inode < numEltNodes1d; ++inode) { for (int icont = 0; icont < numEltNodes1d; ++icont) { if (inode != icont) { lagrangeDenom(iSD,inode) *= (localCoords1d(inode) - localCoords1d(icont)); lagrangeTerms(iSD,inode) *= (point(iSD)-localCoords1d(icont)); } } } } for (int iSD=0; iSD mapping(nSD_); for (int inode=0; inodemapping(inode,mapping); for (int iSD=0; iSDlocal_coords_1d(); int numEltNodes1d = feElement_->num_elt_nodes_1d(); DENS_MAT lagrangeTerms(nD,numEltNodes1d); DENS_MAT lagrangeDenom(nD,numEltNodes1d); DENS_MAT lagrangeDeriv(nD,numEltNodes1d); lagrangeDenom = 1.0; lagrangeTerms = 1.0; lagrangeDeriv = 0.0; DENS_VEC productRuleVec(numEltNodes1d); productRuleVec = 1.0; for (int iSD = 0; iSD < nD; ++iSD) { for (int inode = 0; inode < numEltNodes1d; ++inode) { for (int icont = 0; icont < numEltNodes1d; ++icont) { if (inode != icont) { lagrangeTerms(iSD,inode) *= (point(iSD)-localCoords1d(icont)); lagrangeDenom(iSD,inode) *= (localCoords1d(inode) - localCoords1d(icont)); for (int dcont=0; dcont mapping(nD); for (int inode=0; inodemapping(inode,mapping); for (int iSD=0; iSDlocal_coords_1d(); int numEltNodes = feElement_->num_elt_nodes(); int numEltNodes1d = feElement_->num_elt_nodes_1d(); // lagrangeTerms stores the numerator for the various Lagrange polynomials // in one dimension, that will be used to produce the three dimensional // shape functions DENS_MAT lagrangeTerms(nSD_,numEltNodes1d); // lagrangeDenom stores the denominator. Stored separately to reduce // redundancy, because it will be used for the shape functions and derivs DENS_MAT lagrangeDenom(nSD_,numEltNodes1d); // lagrangeDeriv stores the numerator for the derivative of the Lagrange // polynomials DENS_MAT lagrangeDeriv(nSD_,numEltNodes1d); // Terms/Denom are products, Deriv will be a sum, so initialize as such: lagrangeTerms = 1.0; lagrangeDenom = 1.0; lagrangeDeriv = 0.0; // the derivative requires use of the product rule; to store the prodcuts // which make up the terms produced by the product rule, we'll use this // vector DENS_VEC productRuleVec(numEltNodes1d); productRuleVec = 1.0; for (int iSD = 0; iSD < nSD_; ++iSD) { for (int inode = 0; inode < numEltNodes1d; ++inode) { for (int icont = 0; icont < numEltNodes1d; ++icont) { if (inode != icont) { // each dimension and each 1d node per dimension has a // contribution from all nodes besides the current node lagrangeTerms(iSD,inode) *= (point(iSD)-localCoords1d(icont)); lagrangeDenom(iSD,inode) *= (localCoords1d(inode) - localCoords1d(icont)); // complciated; each sum produced by the product rule has one // "derivative", and the rest are just identical to the terms // above for (int dcont=0; dcont mapping(nSD_); for (int inode=0; inodemapping(inode,mapping); for (int iSD=0; iSDlocal_coords(); double invVol = 1.0/(feElement_->vol()); int numEltNodes = feElement_->num_elt_nodes(); for (int inode = 0; inode < numEltNodes; ++inode) { N(inode) = invVol; for (int isd = 0; isd < nSD_; ++isd) { N(inode) *= (1.0 + point(isd)*localCoords(isd,inode)); } } } // Sort of a test-ride for a generic version that can be used for // faces too. The only thing that's not "generic" is localCoords, // which very magically works in both cases. void FE_InterpolateCartLin::compute_dNdr(const VECTOR &point, const int numNodes, const int nD, const double vol, DENS_MAT &dNdr) { // *** see comments for compute_N_dNdr *** const DENS_MAT &localCoords = feElement_->local_coords(); double invVol = 1.0/vol; for (int inode = 0; inode < numNodes; ++inode) { for (int idr = 0; idr < nD; ++idr) { dNdr(idr,inode) = invVol; } for (int id = 0; id < nD; ++id) { for (int idr = 0; idr < nD; ++idr) { if (id == idr) dNdr(idr,inode) *= localCoords(id,inode); else dNdr(idr,inode) *= 1.0 + point(id)*localCoords(id,inode); } } } } void FE_InterpolateCartLin::compute_N_dNdr(const VECTOR &point, VECTOR &N, DENS_MAT &dNdr) const { // Required data from element class const DENS_MAT &localCoords = feElement_->local_coords(); double invVol = 1.0/(feElement_->vol()); int numEltNodes = feElement_->num_elt_nodes(); // Fill in for each node for (int inode = 0; inode < numEltNodes; ++inode) { // Intiialize shape function and derivatives N(inode) = invVol; for (int idr = 0; idr < nSD_; ++idr) { dNdr(idr,inode) = invVol; } for (int isd = 0; isd < nSD_; ++isd) { // One term for each dimension N(inode) *= (1.0 + point(isd)*localCoords(isd,inode)); // One term for each dimension, only deriv in deriv's dimension for (int idr = 0; idr < nSD_; ++idr) { if (isd == idr) dNdr(idr,inode) *= localCoords(isd,inode); else dNdr(idr,inode) *= 1.0 + point(isd)*localCoords(isd,inode); } } } } /********************************************************* * Class FE_InterpolateCartSerendipity * * For computing shape functions for quadratic serendipity * elements, implemented for SPEED. * *********************************************************/ FE_InterpolateCartSerendipity::FE_InterpolateCartSerendipity( FE_Element *feElement) : FE_Interpolate(feElement) { set_quadrature(HEXA,GAUSS2); } FE_InterpolateCartSerendipity::~FE_InterpolateCartSerendipity() { // Handled by base class } void FE_InterpolateCartSerendipity::compute_N(const VECTOR &point, VECTOR &N) { // *** see comments for compute_N_dNdr *** const DENS_MAT &localCoords = feElement_->local_coords(); double invVol = 1.0/(feElement_->vol()); int numEltNodes = feElement_->num_elt_nodes(); for (int inode = 0; inode < numEltNodes; ++inode) { N(inode) = invVol; for (int isd = 0; isd < nSD_; ++isd) { if (((inode == 8 || inode == 10 || inode == 16 || inode == 18) && (isd == 0)) || ((inode == 9 || inode == 11 || inode == 17 || inode == 19) && (isd == 1)) || ((inode == 12 || inode == 13 || inode == 14 || inode == 15) && (isd == 2))) { N(inode) *= (1.0 - pow(point(isd),2))*2; } else { N(inode) *= (1.0 + point(isd)*localCoords(isd,inode)); } } if (inode < 8) { N(inode) *= (point(0)*localCoords(0,inode) + point(1)*localCoords(1,inode) + point(2)*localCoords(2,inode) - 2); } } } // Sort of a test-ride for a generic version that can be used for // faces too. The only thing that's not "generic" is localCoords, // which very magically works in both cases. void FE_InterpolateCartSerendipity::compute_dNdr(const VECTOR &point, const int numNodes, const int nD, const double vol, DENS_MAT &dNdr) { // *** see comments for compute_N_dNdr *** const DENS_MAT &localCoords = feElement_->local_coords(); double invVol = 1.0/vol; bool serendipityNode = false; double productRule1 = 0.0; double productRule2 = 0.0; if (nD != 3 && nD != 2) { ATC_Error("Serendipity dNdr calculations are too hard-wired to do " "what you want them to. Only 2D and 3D currently work."); } for (int inode = 0; inode < numNodes; ++inode) { for (int idr = 0; idr < nD; ++idr) { dNdr(idr,inode) = invVol; } for (int id = 0; id < nD; ++id) { for (int idr = 0; idr < nD; ++idr) { // identify nodes/dims differently if 3d or 2d case if (nD == 3) { serendipityNode = (((inode == 8 || inode == 10 || inode == 16 || inode == 18) && (id == 0)) || ((inode == 9 || inode == 11 || inode == 17 || inode == 19) && (id == 1)) || ((inode == 12 || inode == 13 || inode == 14 || inode == 15) && (id == 2))); } else if (nD == 2) { serendipityNode = (((inode == 4 || inode == 6) && (id == 0)) || ((inode == 5 || inode == 7) && (id == 1))); } if (serendipityNode) { if (id == idr) { dNdr(idr,inode) *= point(id)*(-4); } else { dNdr(idr,inode) *= (1.0 - pow(point(id),2))*2; } } else { if (id == idr) { dNdr(idr,inode) *= localCoords(id,inode); } else { dNdr(idr,inode) *= (1.0 + point(id)*localCoords(id,inode)); } } } } for (int idr = 0; idr < nD; ++idr) { if (inode < 8) { // final corner contribution slightly different for 3d and 2d cases if (nD == 3) { productRule2 = (point(0)*localCoords(0,inode) + point(1)*localCoords(1,inode) + point(2)*localCoords(2,inode) - 2); } else if (nD == 2) { productRule2 = (point(0)*localCoords(0,inode) + point(1)*localCoords(1,inode) - 1); } productRule1 = dNdr(idr,inode) * (1 + point(idr)*localCoords(idr,inode)); productRule2 *= dNdr(idr,inode); dNdr(idr,inode) = productRule1 + productRule2; } } } } void FE_InterpolateCartSerendipity::compute_N_dNdr(const VECTOR &point, VECTOR &N, DENS_MAT &dNdr) const { // Required data from element class const DENS_MAT &localCoords = feElement_->local_coords(); double invVol = 1.0/(feElement_->vol()); int numEltNodes = feElement_->num_elt_nodes(); // Will store terms for product rule derivative for dNdr double productRule1; double productRule2; // Fill in for each node for (int inode = 0; inode < numEltNodes; ++inode) { // Initialize shape functions and derivatives N(inode) = invVol; for (int idr = 0; idr < nSD_; ++idr) { dNdr(idr,inode) = invVol; } // Add components from each dimension for (int isd = 0; isd < nSD_; ++isd) { for (int idr = 0; idr < nSD_; ++idr) { // Check to see if the node is NOT a corner node, and if its // "0-coordinate" is in the same dimension as the one we're currently // iterating over. If that's the case, we want to contribute to its // shape functions and derivatives in a modified way: if (((inode == 8 || inode == 10 || inode == 16 || inode == 18) && (isd == 0)) || ((inode == 9 || inode == 11 || inode == 17 || inode == 19) && (isd == 1)) || ((inode == 12 || inode == 13 || inode == 14 || inode == 15) && (isd == 2))) { // If the 1d shape function dimension matches the derivative // dimension... if (isd == idr) { // contribute to N; sloppy, but this is the easiest way to get // N to work right without adding extra, arguably unnecessary // loops, while also computing the shape functions N(inode) *= (1.0 - pow(point(isd),2))*2; // contribute to dNdr with the derivative of this shape function // contribution dNdr(idr,inode) *= point(isd)*(-4); } else { // otherwise, just use the "constant" contribution to the deriv dNdr(idr,inode) *= (1.0 - pow(point(isd),2))*2; } } else { // non-serendipity style contributions if (isd == idr) { N(inode) *= (1.0 + point(isd)*localCoords(isd,inode)); dNdr(idr,inode) *= localCoords(isd,inode); } else { dNdr(idr,inode) *= (1.0 + point(isd)*localCoords(isd,inode)); } } } } // serendipity corner nodes require more extra handling if (inode < 8) { N(inode) *= (point(0)*localCoords(0,inode) + point(1)*localCoords(1,inode) + point(2)*localCoords(2,inode) - 2); } for (int idr = 0; idr < nSD_; ++idr) { if (inode < 8) { productRule1 = dNdr(idr,inode) * (1 + point(idr)*localCoords(idr,inode)); productRule2 = dNdr(idr,inode) * (point(0)*localCoords(0,inode) + point(1)*localCoords(1,inode) + point(2)*localCoords(2,inode) - 2); dNdr(idr,inode) = productRule1 + productRule2; } } } } /********************************************************* * Class FE_InterpolateSimpLin * * For computing linear shape functions of simplices, * which are rather different from those computed * in Cartesian coordinates. * * Note: degenerating quads/hexes can yield simplices * as well, but this class is for computing these * shape fucntions _natively_, in their own * triangular/tetrahedral coordinate systems. * *********************************************************/ FE_InterpolateSimpLin::FE_InterpolateSimpLin( FE_Element *feElement) : FE_Interpolate(feElement) { set_quadrature(TETRA,GAUSS2); } FE_InterpolateSimpLin::~FE_InterpolateSimpLin() { // Handled by base class } void FE_InterpolateSimpLin::compute_N(const VECTOR &point, VECTOR &N) { int numEltNodes = feElement_->num_elt_nodes(); // Fill in for each node for (int inode = 0; inode < numEltNodes; ++inode) { if (inode == 0) { // Fill N...the ips are serving as proxies for "dimensions" // since we're in tetrahedral coordinates, except that // 0th node = 3rd "dimension" (u or O_o) // 1st node = 0th "dimension" (x or r) // 2nd node = 1st "dimension" (y or s) // 3rd node = 3nd "dimension" (z or t) // and remember that u = 1 - r - s - t for tet coords N(inode) = 1; for (int icont = 0; icont < nSD_; ++icont) { N(inode) -= point(icont); } } else { N(inode) = point(inode-1); } } } void FE_InterpolateSimpLin::compute_dNdr(const VECTOR &, const int numNodes, const int nD, const double, DENS_MAT &dNdr) { // Fill in for each node for (int inode = 0; inode < numNodes; ++inode) { // Fill dNdr_; we want 1 if the dimension of derivative // and variable within N correspond. That is, if N == r, // we want the 0th dimension to contain (d/dr)r = 1. Of // course, (d/di)r = 0 forall i != r, so we need that as // well. This is a bit elusively complicated. Also, the 0th // integration point contains the term u = 1 - r - s - t. // (which map to x, y, and z). Therefore, the derivative in // each dimension are -1. // // The idea is similar for 2 dimensions, which this can // handle as well. for (int idr = 0; idr < nD; ++idr) { if (inode == 0) { dNdr(idr,inode) = -1; } else { dNdr(idr,inode) = (inode == (idr + 1)) ? 1 : 0; } } } } void FE_InterpolateSimpLin::compute_N_dNdr(const VECTOR &point, VECTOR &N, DENS_MAT &dNdr) const { int numEltNodes = feElement_->num_elt_nodes(); // Fill in for each node for (int inode = 0; inode < numEltNodes; ++inode) { // Fill N... if (inode == 0) { N(inode) = 1; for (int icont = 0; icont < nSD_; ++icont) { N(inode) -= point(icont); } } else { N(inode) = point(inode-1); } // Fill dNdr... for (int idr = 0; idr < nSD_; ++idr) { if (inode == 0) { dNdr(idr,inode) = -1; } else { dNdr(idr,inode) = (inode == (idr + 1)) ? 1 : 0; } } } } } // namespace ATC