Skip to content
Snippets Groups Projects
surfacecosseratstressassembler.hh 15.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • #ifndef DUNE_GFE_SURFACECOSSERATSTRESSASSEMBLER_HH
    #define DUNE_GFE_SURFACECOSSERATSTRESSASSEMBLER_HH
    
    #include <dune/fufem/boundarypatch.hh>
    
    #include <dune/gfe/linearalgebra.hh>
    #include <dune/gfe/rotation.hh>
    #include <dune/gfe/localgeodesicfefunction.hh>
    
    #include <dune/matrix-vector/transpose.hh>
    
    namespace Dune::GFE {
      /** \brief An assembler that can calculate the norms of specific stress tensors for each element for an output by film-on-substrate
    
        \tparam BasisOrderD Basis used for the displacement
        \tparam BasisOrderR Basis used for the rotation
        \tparam TargetSpaceD Target space for the Displacement
        \tparam TargetSpaceR Target space for the Rotation
      */
      template <class BasisOrderD, class BasisOrderR, class TargetSpaceD, class TargetSpaceR>
      class SurfaceCosseratStressAssembler
      {
        public:
          const static int dim = TargetSpaceD::dimension;
          using GridView = typename BasisOrderD::GridView;
          using VectorD = std::vector<TargetSpaceD>;
          using VectorR = std::vector<TargetSpaceR>;
    
          BasisOrderD basisOrderD_;
          BasisOrderR basisOrderR_;
    
          SurfaceCosseratStressAssembler(const BasisOrderD basisOrderD,
                                         const BasisOrderR basisOrderR)
          : basisOrderD_(basisOrderD),
            basisOrderR_(basisOrderR)
          {}
    
          /** \brief Calculate the norm of the 1st-Piola-Kirchhoff-Stress-Tensor and the Cauchy-Stress-Tensor for each element
    
              The 1st-Piola-Kirchhoff-Stress-Tensor is the derivative of the energy density with respect to the deformation gradient
              - Calculate the deformation gradient for each element using the basis functions and
                their gradients; then add them up using the localConfiguration
              - Evaluate the deformation gradient at each quadrature point using the respective quadrature rule with the given order
              - Evaluate the density function and tape the evaluation - then use ADOLC to evaluate the derivative (∂/∂F) W(F)
              - The derivative is then a dim x dim matrix
              - Then calculate the final stressTensor of the element by averagin over the quadrature points using the quadrature
                weights and the reference element volume
            \param x Coefficient vector for the displacement
            \param elasticDensity Energy density function
            \param int Order of the quadrature rule
            \param stressSubstrate1stPiolaKirchhoffTensor Vector containing the the 1st-Piola-Kirchhoff-Stress-Tensor for each element
            \param stressSubstrateCauchyTensor Vector containing the Cauchy-Stress-Tensor for each element
         */
          template <class Density>
          void assembleSubstrateStress(
            const VectorD x,
            const Density* elasticDensity,
            const int quadOrder,
            std::vector<FieldMatrix<double,dim,dim>>& stressSubstrate1stPiolaKirchhoffTensor,
            std::vector<FieldMatrix<double,dim,dim>>& stressSubstrateCauchyTensor)
          {
    
            std::cout << "Calculating the Frobenius norm of the 1st-Piola-Kirchhoff-Stress-Tensor ( (∂/∂F) W(F) )" << std::endl
                      << "and the Frobenius norm of the Cauchy-Stress-Tensor (1/det(F) * (∂/∂F) W(F) * F^T) of the substrate..." << std::endl;
    
            auto xFlat = Functions::istlVectorBackend(x);
            static constexpr auto partitionType = Partitions::interiorBorder;
            MultipleCodimMultipleGeomTypeMapper<GridView> elementMapper(basisOrderD_.gridView(),mcmgElementLayout());
            stressSubstrate1stPiolaKirchhoffTensor.resize(elementMapper.size());
            stressSubstrateCauchyTensor.resize(elementMapper.size());
    
            for (const auto& element : elements(basisOrderD_.gridView(), partitionType))
            {
              auto localViewOrderD = basisOrderD_.localView();
              localViewOrderD.bind(element);
              size_t nDofsOrderD = localViewOrderD.tree().size();
    
              // Extract values at this element
              std::vector<double> localConfiguration(nDofsOrderD);
    
              for (size_t i=0; i<nDofsOrderD; i++)
    
                localConfiguration[i] = xFlat[localViewOrderD.index(i)]; //localViewOrderD.index(i) is a multi-index
    
              //Store the reference gradient and the gradients for this element
              const auto& lFEOrderD = localViewOrderD.tree().child(0).finiteElement();
              std::vector<FieldMatrix<double,1,dim> > referenceGradients(lFEOrderD.size());
              std::vector<FieldMatrix<double,1,dim> > gradients(lFEOrderD.size());
    
              auto evaluateAtPoint = [&](FieldVector<double,3> pointGlobal, FieldVector<double,3> pointLocal) -> std::vector<FieldMatrix<double,dim,dim>>{
                std::vector<FieldMatrix<double,dim,dim>> stressTensors(2);
    
                const auto jacobianInverseTransposed = element.geometry().jacobianInverseTransposed(pointLocal);
    
                // Get gradients of shape functions
                lFEOrderD.localBasis().evaluateJacobian(pointLocal, referenceGradients);
    
                // Compute gradients of Base functions
                for (size_t i=0; i<gradients.size(); ++i)
                  gradients[i] = referenceGradients[i] * transpose(jacobianInverseTransposed);
    
                // Deformation gradient in vector form
                size_t nDoubles = dim*dim;
                std::vector<double> deformationGradientFlat(nDoubles);
                for (size_t i=0; i<nDoubles; i++)
                  deformationGradientFlat[i] = 0;
                for (size_t i=0; i<gradients.size(); i++)
                  for (size_t j=0; j<dim; j++)
                    for (size_t k=0; k<dim; k++)
                      deformationGradientFlat[dim*j + k] += localConfiguration[ localViewOrderD.tree().child(j).localIndex(i)]*gradients[i][0][k];
    
                double pureDensity = 0;
    
                trace_on(0);
    
                FieldMatrix<adouble,dim,dim> deformationGradient(0);
                for (size_t j=0; j<dim; j++)
                  for (size_t k=0; k<dim; k++)
                    deformationGradient[j][k] <<= deformationGradientFlat[dim*j + k];
    
                // Tape the actual calculation
                adouble density = 0;
                try {
                  density = (*elasticDensity)(pointGlobal, deformationGradient);
                } catch (Exception &e) {
                  trace_off(0);
                  throw e;
                }
                density >>= pureDensity;
    
                trace_off(0);
    
                // Compute the actual gradient
                std::vector<double> localStressFlat(nDoubles);
                gradient(0,nDoubles,deformationGradientFlat.data(),localStressFlat.data());
    
                FieldMatrix<double,dim,dim> localStress(0);
                for (size_t j=0; j<dim; j++)
                  for (size_t k=0; k<dim; k++)
                    localStress[j][k] = localStressFlat[dim*j + k];
    
                stressTensors[0] = localStress; // 1st-Piola-Kirchhoff-Stress-Tensor
    
                FieldMatrix<double,dim,dim> deformationGradientTransposed(0);
                for (size_t j=0; j<dim; j++)
                  for (size_t k=0; k<dim; k++)
                    deformationGradient[j][k] >>= deformationGradientTransposed[k][j];
    
                localStress /= deformationGradientTransposed.determinant();
                localStress = localStress * deformationGradientTransposed;
                stressTensors[1] = localStress; // Cauchy-Stress-Tensor
    
                return stressTensors;
              };
    
              //Call evaluateAtPoint for all points in the quadrature rule
              const auto& quad = Dune::QuadratureRules<double, dim>::rule(element.type(), quadOrder);
              stressSubstrate1stPiolaKirchhoffTensor[elementMapper.index(element)] = 0;
              stressSubstrateCauchyTensor[elementMapper.index(element)] = 0;
    
              for (size_t pt=0; pt<quad.size(); pt++) {
                auto pointLocal = quad[pt].position();
                auto pointGlobal = element.geometry().global(pointLocal);
                auto stressTensors = evaluateAtPoint(pointGlobal, pointLocal);
                stressSubstrate1stPiolaKirchhoffTensor[elementMapper.index(element)] += stressTensors[0] * quad[pt].weight()/referenceElement(element).volume();
                stressSubstrateCauchyTensor[elementMapper.index(element)] += stressTensors[1] * quad[pt].weight()/referenceElement(element).volume();
              }
            }
          }
          /** \brief  Calculate the norm of the Biot-Type-Stress-Tensor of the shell
    
              The formula for Biot-Type-Stress-Tensor of the Cosserat shell given by (4.11) in 
              Ghiba, Bîrsan, Lewintan, Neff, March 2020: "The isotropic Cosserat shell model including terms up to $O(h^5)$. Part I: Derivation in matrix notation"
            \param rot Coefficient vector for the rotation
            \param x Coefficient vector for the displacement
            \param xInitial Coefficient vector for the stress-free configuration of the shell, used to calculate nablaTheta
    
            \param lameF Function assigning the Lamé parameters to a given point
    
            \param mu_c Cosserat couple modulus
            \param shellBoundary BoundaryPatch containing the elements that actually belong to the shell
            \param order Order of the quadrature rule
            \param stressShellBiotTensor Vector containing the Biot-Stress-Tensor for each element
          */
          void assembleShellStress(
            const VectorR rot,
            const VectorD x,
            const VectorD xInitial,
            const std::function<Dune::FieldVector<double,2>(Dune::FieldVector<double,dim>)> lameF,
            const double mu_c,
            const BoundaryPatch<GridView> shellBoundary,
            const int quadOrder,
            std::vector<FieldMatrix<double,dim,dim>>& stressShellBiotTensor)
          {
            std::cout << "Calculating the Frobenius norm of the Biot-Type-Stress-Tensor of the shell..." << std::endl;
            auto xFlat = Functions::istlVectorBackend(x);
            auto xInitialFlat = Functions::istlVectorBackend(xInitial);
    
            MultipleCodimMultipleGeomTypeMapper<GridView> elementMapper(basisOrderD_.gridView(),mcmgElementLayout());
            stressShellBiotTensor.resize(elementMapper.size());
    
            static constexpr auto partitionType = Partitions::interiorBorder;
            for (const auto& element : elements(basisOrderD_.gridView(), partitionType))
            {
              stressShellBiotTensor[elementMapper.index(element)] = 0;
    
              int intersectionCounter = 0;
              for (auto&& it : intersections(shellBoundary.gridView(), element)) {
                FieldMatrix<double,dim,dim> stressTensorThisIntersection(0);
    
                //Continue if the element does not intersect with the shell boundary
                if (not shellBoundary.contains(it))
                  continue;
    
                //LocalView for the basisOrderD_
                auto localViewOrderD = basisOrderD_.localView();
                localViewOrderD.bind(element);
                size_t nDofsOrderD = localViewOrderD.tree().size();
    
                // Extract local configuration at this element
                std::vector<double> localConfiguration(nDofsOrderD);
                std::vector<double> localConfigurationInitial(nDofsOrderD);
    
                for (size_t i=0; i<nDofsOrderD; i++) {
    
                  localConfiguration[i] = xFlat[localViewOrderD.index(i)];
                  localConfigurationInitial[i] = xInitialFlat[localViewOrderD.index(i)];
                }
    
                const auto& lFEOrderD = localViewOrderD.tree().child(0).finiteElement();
                //Store the reference gradient and the gradients for *this element*
                std::vector<FieldMatrix<double,1,dim> > referenceGradients(lFEOrderD.size());
                std::vector<FieldMatrix<double,1,dim> > gradients(lFEOrderD.size());
    
                //LocalView for the basisOrderR_
                auto localViewOrderR = basisOrderR_.localView();
                localViewOrderR.bind(element);
                const auto& lFEOrderR = localViewOrderR.tree().child(0).finiteElement();
                VectorR localConfigurationRot(lFEOrderR.size());
    
                for (std::size_t i=0; i<localConfigurationRot.size(); i++)
    
                  localConfigurationRot[i] = rot[localViewOrderR.index(i)[0]];//localViewOrderR.index(i) is a multiindex, its first entry is the actual index
                typedef LocalGeodesicFEFunction<dim, double, decltype(lFEOrderR), TargetSpaceR> LocalGFEFunctionType;
                LocalGFEFunctionType localGeodesicFEFunction(lFEOrderR,localConfigurationRot);
                
                auto evaluateAtPoint = [&](FieldVector<double,3> pointGlobal, FieldVector<double,3> pointLocal3d) -> FieldMatrix<double,dim,dim>{
                  Dune::FieldMatrix<double,dim,dim> nablaTheta;
    
                  const auto jacobianInverseTransposed = element.geometry().jacobianInverseTransposed(pointLocal3d);
    
                  // Get gradients of shape functions
                  lFEOrderD.localBasis().evaluateJacobian(pointLocal3d, referenceGradients);
    
                  // Compute gradients of Base functions at this element
                  for (size_t i=0; i<gradients.size(); i++)
                    gradients[i] = referenceGradients[i] * transpose(jacobianInverseTransposed);
    
                  // Deformation gradient - call this U_es_minus_Id already
                  FieldMatrix<double,dim,dim> U_es_minus_Id(0);
                  for (size_t i=0; i<gradients.size(); i++)
                    for (size_t j=0; j<dim; j++){
                      U_es_minus_Id[j].axpy(localConfiguration[ localViewOrderD.tree().child(j).localIndex(i)],gradients[i][0]);
                      nablaTheta[j].axpy(localConfigurationInitial[ localViewOrderD.tree().child(j).localIndex(i)],gradients[i][0]);
                    }
    
                  TargetSpaceR value = localGeodesicFEFunction.evaluate(pointLocal3d);
                  FieldMatrix<double,dim,dim> rotationMatrix(0);
                  FieldMatrix<double,dim,dim> rotationMatrixTransposed(0);
                  value.matrix(rotationMatrix);
    
                  MatrixVector::transpose(rotationMatrix, rotationMatrixTransposed);
    
                  U_es_minus_Id.leftmultiply(rotationMatrixTransposed); // Attention: The rotation here is already is Q_e, we don't have to multiply with Q_0!!!
    
                  nablaTheta.invert();
                  U_es_minus_Id.rightmultiply(nablaTheta);
    
                  for (size_t j = 0; j < dim; j++)
                    U_es_minus_Id[j][j] -= 1.0;
    
                  auto lameConstants = lameF(pointGlobal);
                  double mu = lameConstants[0];
                  double lambda = lameConstants[1];
    
                  FieldMatrix<double,dim,dim> localStressShell(0);
                  localStressShell = 2*mu*GFE::sym(U_es_minus_Id) + 2*mu_c*GFE::skew(U_es_minus_Id);
                  for (size_t j = 0; j < dim; j++)
                    localStressShell[j][j] += lambda*GFE::trace(GFE::sym(U_es_minus_Id));
                  return localStressShell;
                };
    
                //Call evaluateAtPoint for all points in the quadrature rule
                const auto& quad = Dune::QuadratureRules<double, dim-1>::rule(it.type(), quadOrder); //Quad rule on the boundary 
    
                for (size_t pt=0; pt<quad.size(); pt++) {
                  auto pointLocal2d = quad[pt].position();
                  auto pointLocal3d = it.geometryInInside().global(pointLocal2d);
                  auto pointGlobal = it.geometry().global(pointLocal2d);
    
                  auto stressTensor = evaluateAtPoint(pointGlobal, pointLocal3d);
                  stressTensorThisIntersection += stressTensor * quad[pt].weight()/referenceElement(it.inside()).volume();
                }
                stressShellBiotTensor[elementMapper.index(element)] += stressTensorThisIntersection;
                intersectionCounter++;
              }
              if (intersectionCounter >= 1)
                stressShellBiotTensor[elementMapper.index(element)] /= intersectionCounter;
            }
          }
      };
    }