diff --git a/dune/gfe/nonplanarcosseratshellenergy.hh b/dune/gfe/nonplanarcosseratshellenergy.hh new file mode 100644 index 0000000000000000000000000000000000000000..8ddb04ac58c861d508e3a1cbd49a890595513402 --- /dev/null +++ b/dune/gfe/nonplanarcosseratshellenergy.hh @@ -0,0 +1,492 @@ +#ifndef DUNE_GFE_NONPLANARCOSSERATSHELLENERGY_HH +#define DUNE_GFE_NONPLANARCOSSERATSHELLENERGY_HH + +#include <dune/common/fmatrix.hh> +#include <dune/common/parametertree.hh> +#include <dune/geometry/quadraturerules.hh> + +#include <dune/fufem/functions/virtualgridfunction.hh> +#include <dune/fufem/boundarypatch.hh> + +#include <dune/gfe/localgeodesicfestiffness.hh> +#include <dune/gfe/localgeodesicfefunction.hh> +#include <dune/gfe/rigidbodymotion.hh> +#include <dune/gfe/unitvector.hh> +#include <dune/gfe/tensor3.hh> +#include <dune/gfe/localprojectedfefunction.hh> + + +template<class Basis, int dim, class field_type=double> +class NonplanarCosseratShellEnergy + : public LocalGeodesicFEStiffness<Basis,RigidBodyMotion<field_type,dim> > +{ + // grid types + typedef typename Basis::GridView GridView; + typedef typename Basis::LocalView::Tree::FiniteElement LocalFiniteElement; + typedef typename GridView::ctype DT; + typedef RigidBodyMotion<field_type,dim> TargetSpace; + typedef typename TargetSpace::ctype RT; + typedef typename GridView::template Codim<0>::Entity Entity; + + // some other sizes + enum {gridDim=GridView::dimension}; + enum {dimworld=GridView::dimensionworld}; + + /** \brief Compute the symmetric part of a matrix A, i.e. \f$ \frac 12 (A + A^T) \f$ */ + static Dune::FieldMatrix<field_type,dim,dim> sym(const Dune::FieldMatrix<field_type,dim,dim>& A) + { + Dune::FieldMatrix<field_type,dim,dim> result; + for (int i=0; i<dim; i++) + for (int j=0; j<dim; j++) + result[i][j] = 0.5 * (A[i][j] + A[j][i]); + return result; + } + + /** \brief Compute the antisymmetric part of a matrix A, i.e. \f$ \frac 12 (A - A^T) \f$ */ + static Dune::FieldMatrix<field_type,dim,dim> skew(const Dune::FieldMatrix<field_type,dim,dim>& A) + { + Dune::FieldMatrix<field_type,dim,dim> result; + for (int i=0; i<dim; i++) + for (int j=0; j<dim; j++) + result[i][j] = 0.5 * (A[i][j] - A[j][i]); + return result; + } + + /** \brief Compute the deviator of a matrix A */ + static Dune::FieldMatrix<field_type,dim,dim> dev(const Dune::FieldMatrix<field_type,dim,dim>& A) + { + Dune::FieldMatrix<field_type,dim,dim> result = A; + auto t = trace(A); + for (int i=0; i<dim; i++) + result[i][i] -= t / dim; + return result; + } + + /** \brief Return the trace of a matrix */ + template <class T, int N> + static T trace(const Dune::FieldMatrix<T,N,N>& A) + { + T trace = 0; + for (int i=0; i<N; i++) + trace += A[i][i]; + return trace; + } + + /** \brief Return the square of the trace of a matrix */ + template <int N> + static field_type traceSquared(const Dune::FieldMatrix<field_type,N,N>& A) + { + field_type trace = 0; + for (int i=0; i<N; i++) + trace += A[i][i]; + return trace*trace; + } + + template <class T, int N> + static T frobeniusProduct(const Dune::FieldMatrix<T,N,N>& A, const Dune::FieldMatrix<T,N,N>& B) + { + T result(0.0); + + for (int i=0; i<N; i++) + for (int j=0; j<N; j++) + result += A[i][j] * B[i][j]; + + return result; + } + + template <int N> + static auto dyadicProduct(const Dune::FieldVector<adouble,N>& A, const Dune::FieldVector<adouble,N>& B) + -> Dune::FieldMatrix<adouble,N,N> + { + Dune::FieldMatrix<adouble,N,N> result; + + for (int i=0; i<N; i++) + for (int j=0; j<N; j++) + result[i][j] = A[i]*B[j]; + + return result; + } + + template <int N> + static auto dyadicProduct(const Dune::FieldVector<double,N>& A, const Dune::FieldVector<double,N>& B) + -> Dune::FieldMatrix<double,N,N> + { + Dune::FieldMatrix<double,N,N> result; + + for (int i=0; i<N; i++) + for (int j=0; j<N; j++) + result[i][j] = A[i]*B[j]; + + return result; + } + + template <int N> + static auto dyadicProduct(const Dune::FieldVector<adouble,N>& A, const Dune::FieldVector<double,N>& B) + -> Dune::FieldMatrix<adouble,N,N> + { + Dune::FieldMatrix<adouble,N,N> result; + + for (int i=0; i<N; i++) + for (int j=0; j<N; j++) + result[i][j] = A[i]*B[j]; + + return result; + } + + template <class T, int N> + static Dune::FieldMatrix<T,N,N> transpose(const Dune::FieldMatrix<T,N,N>& A) + { + Dune::FieldMatrix<T,N,N> result; + + for (int i=0; i<N; i++) + for (int j=0; j<N; j++) + result[i][j] = A[j][i]; + + return result; + } + + + /** \brief Compute the derivative of the rotation (with respect to x), but wrt matrix coordinates + \param value Value of the gfe function at a certain point + \param derivative First derivative of the gfe function wrt x at that point, in quaternion coordinates + \param DR First derivative of the gfe function wrt x at that point, in matrix coordinates + */ + static void computeDR(const RigidBodyMotion<field_type,3>& value, + const Dune::FieldMatrix<field_type,7,gridDim>& derivative, + Tensor3<field_type,3,3,gridDim>& DR) + { + // The LocalGFEFunction class gives us the derivatives of the orientation variable, + // but as a map into quaternion space. To obtain matrix coordinates we use the + // chain rule, which means that we have to multiply the given derivative with + // the derivative of the embedding of the unit quaternion into the space of 3x3 matrices. + // This second derivative is almost given by the method getFirstDerivativesOfDirectors. + // However, since the directors of a given unit quaternion are the _columns_ of the + // corresponding orthogonal matrix, we need to invert the i and j indices + // + // So, DR[i][j][k] contains \partial R_ij / \partial k + Tensor3<field_type,3 , 3, 4> dd_dq; + value.q.getFirstDerivativesOfDirectors(dd_dq); + + DR = field_type(0); + for (int i=0; i<3; i++) + for (int j=0; j<3; j++) + for (int k=0; k<gridDim; k++) + for (int l=0; l<4; l++) + DR[i][j][k] += dd_dq[j][i][l] * derivative[l+3][k]; + } + +public: + + /** \brief Constructor with a set of material parameters + * \param parameters The material parameters + */ + NonplanarCosseratShellEnergy(const Dune::ParameterTree& parameters, + const BoundaryPatch<GridView>* neumannBoundary, + const std::shared_ptr<Dune::VirtualFunction<Dune::FieldVector<double,dimworld>, Dune::FieldVector<double,3> > > neumannFunction, + const std::shared_ptr<Dune::VirtualFunction<Dune::FieldVector<double,dimworld>, Dune::FieldVector<double,3> > > volumeLoad) + : neumannBoundary_(neumannBoundary), + neumannFunction_(neumannFunction), + volumeLoad_(volumeLoad) + { + // The shell thickness + thickness_ = parameters.template get<double>("thickness"); + + // Lame constants + mu_ = parameters.template get<double>("mu"); + lambda_ = parameters.template get<double>("lambda"); + + // Cosserat couple modulus + mu_c_ = parameters.template get<double>("mu_c"); + + // Length scale parameter + L_c_ = parameters.template get<double>("L_c"); + + // Curvature parameters + b1_ = parameters.template get<double>("b1"); + b2_ = parameters.template get<double>("b2"); + b3_ = parameters.template get<double>("b3"); + } + + /** \brief Assemble the energy for a single element */ + RT energy (const Entity& e, + const LocalFiniteElement& localFiniteElement, + const std::vector<TargetSpace>& localSolution) const; + + RT W_m(const Dune::FieldMatrix<field_type,3,3>& S) const + { + return W_mixt(S,S); + } + + RT W_mixt(const Dune::FieldMatrix<field_type,3,3>& S, const Dune::FieldMatrix<field_type,3,3>& T) const + { + return mu_ * frobeniusProduct(sym(S), sym(T)) + + mu_c_ * frobeniusProduct(skew(S), skew(T)) + + lambda_ * mu_ / (lambda_ + 2*mu_) * trace(S) * trace(T); + } + + RT W_mp(const Dune::FieldMatrix<field_type,3,3>& S) const + { + return mu_ * sym(S).frobenius_norm2() + mu_c_ * skew(S).frobenius_norm2() + lambda_ * 0.5 * traceSquared(S); + } + + RT W_curv(const Dune::FieldMatrix<field_type,3,3>& S) const + { + return mu_ * L_c_ * L_c_ * (b1_ * dev(sym(S)).frobenius_norm2() + b2_ * skew(S).frobenius_norm2() + b3_ * traceSquared(S)); + } + + /** \brief The shell thickness */ + double thickness_; + + /** \brief Lame constants */ + double mu_, lambda_; + + /** \brief Cosserat couple modulus */ + double mu_c_; + + /** \brief Length scale parameter */ + double L_c_; + + /** \brief Curvature parameters */ + double b1_, b2_, b3_; + + /** \brief The Neumann boundary */ + const BoundaryPatch<GridView>* neumannBoundary_; + + /** \brief The function implementing the Neumann data */ + const std::shared_ptr<Dune::VirtualFunction<Dune::FieldVector<double,dimworld>, Dune::FieldVector<double,3> > > neumannFunction_; + + /** \brief The function implementing a volume load */ + const std::shared_ptr<Dune::VirtualFunction<Dune::FieldVector<double,dimworld>, Dune::FieldVector<double,3> > > volumeLoad_; +}; + +template <class Basis, int dim, class field_type> +typename NonplanarCosseratShellEnergy<Basis,dim,field_type>::RT +NonplanarCosseratShellEnergy<Basis,dim,field_type>:: +energy(const Entity& element, + const typename Basis::LocalView::Tree::FiniteElement& localFiniteElement, + const std::vector<RigidBodyMotion<field_type,dim> >& localSolution) const +{ + assert(element.type() == localFiniteElement.type()); + + // The element geometry + auto geometry = element.geometry(); + + //////////////////////////////////////////////////////////////////////////////////// + // Construct a linear (i.e., non-constant!) normal field on the surface + //////////////////////////////////////////////////////////////////////////////////// + + std::vector<UnitVector<double,3> > cornerNormals(element.subEntities(gridDim)); + for (size_t i=0; i<cornerNormals.size(); i++) + { + // TODO: WARNING WARNING WARNING: Shape is hard-coded here! + // The sphere: + cornerNormals[i] = geometry.corner(i) / geometry.corner(i).two_norm(); + } + + typedef typename Dune::PQkLocalFiniteElementCache<DT, double, gridDim, 1> P1FiniteElementCache; + typedef typename P1FiniteElementCache::FiniteElementType P1LocalFiniteElement; + P1FiniteElementCache p1FiniteElementCache; + const auto& p1LocalFiniteElement = p1FiniteElementCache.get(element.type()); + Dune::GFE::LocalProjectedFEFunction<gridDim, DT, P1LocalFiniteElement, UnitVector<double,3> > unitNormals(p1LocalFiniteElement, cornerNormals); + + //////////////////////////////////////////////////////////////////////////////////// + // Set up the local nonlinear finite element function + //////////////////////////////////////////////////////////////////////////////////// + typedef LocalGeodesicFEFunction<gridDim, DT, LocalFiniteElement, TargetSpace> LocalGFEFunctionType; + LocalGFEFunctionType localGeodesicFEFunction(localFiniteElement,localSolution); + + RT energy = 0; + + auto quadOrder = (element.type().isSimplex()) ? localFiniteElement.localBasis().order() + : localFiniteElement.localBasis().order() * gridDim; + + const auto& quad = Dune::QuadratureRules<DT, gridDim>::rule(element.type(), quadOrder); + + for (size_t pt=0; pt<quad.size(); pt++) + { + // Local position of the quadrature point + const Dune::FieldVector<DT,gridDim>& quadPos = quad[pt].position(); + + const DT integrationElement = geometry.integrationElement(quadPos); + + DT weight = quad[pt].weight() * integrationElement; + + // The value of the local function + RigidBodyMotion<field_type,dim> value = localGeodesicFEFunction.evaluate(quadPos); + + // The derivative of the local function + auto derivative = localGeodesicFEFunction.evaluateDerivative(quadPos,value); + + ////////////////////////////////////////////////////////// + // The rotation and its derivative + // Note: we need it in matrix coordinates + ////////////////////////////////////////////////////////// + + Dune::FieldMatrix<field_type,dim,dim> R; + value.q.matrix(R); + auto RT = transpose(R); + + Tensor3<field_type,3,3,gridDim> DR; + computeDR(value, derivative, DR); + + ////////////////////////////////////////////////////////// + // Fundamental forms and curvature + ////////////////////////////////////////////////////////// + + // First fundamental form + Dune::FieldMatrix<double,3,3> aCovariant; + + aCovariant[0] = geometry.jacobianTransposed(quadPos)[0]; + aCovariant[1] = geometry.jacobianTransposed(quadPos)[1]; + aCovariant[2] = Arithmetic::crossProduct(aCovariant[0], aCovariant[1]); + aCovariant[2] /= aCovariant[2].two_norm(); + + auto aContravariant = aCovariant; + aContravariant.invert(); + + Dune::FieldMatrix<double,3,3> a(0); + for (int alpha=0; alpha<gridDim; alpha++) + a += dyadicProduct(aCovariant[alpha], aContravariant[alpha]); + + auto a00 = aCovariant[0] * aCovariant[0]; + auto a01 = aCovariant[0] * aCovariant[1]; + auto a10 = aCovariant[1] * aCovariant[0]; + auto a11 = aCovariant[1] * aCovariant[1]; + auto aScalar = std::sqrt(a00*a11 - a10*a01); + + // Alternator tensor + Dune::FieldMatrix<int,2,2> eps = {{0,1},{-1,0}}; + Dune::FieldMatrix<double,3,3> c(0); + + for (int alpha=0; alpha<2; alpha++) + for (int beta=0; beta<2; beta++) + c += sqrt(aScalar) * eps[alpha][beta] * dyadicProduct(aContravariant[alpha], aContravariant[beta]); + + // Second fundamental form + // The derivative of the normal field + auto normalDerivative = unitNormals.evaluateDerivative(quadPos); + + Dune::FieldMatrix<double,3,3> b(0); + for (int alpha=0; alpha<gridDim; alpha++) + { + Dune::FieldVector<double,3> vec; + for (int i=0; i<3; i++) + vec[i] = normalDerivative[i][alpha]; + b -= dyadicProduct(vec, aContravariant[alpha]); + } + + // Gauss curvature + auto K = b.determinant(); + + // Mean curvatue + auto H = 0.5 * trace(b); + + ////////////////////////////////////////////////////////// + // Strain tensors + ////////////////////////////////////////////////////////// + + // Elastic shell strain + Dune::FieldMatrix<field_type,3,3> Ee(0); + Dune::FieldMatrix<field_type,3,3> grad_s_m(0); + for (int alpha=0; alpha<gridDim; alpha++) + { + Dune::FieldVector<field_type,3> vec; + for (int i=0; i<3; i++) + vec[i] = derivative[i][alpha]; + grad_s_m += dyadicProduct(vec, aContravariant[alpha]); + } + + Ee = RT * grad_s_m - a; + + // Elastic shell bending-curvature strain + Dune::FieldMatrix<field_type,3,3> Ke(0); + for (int alpha=0; alpha<gridDim; alpha++) + { + Dune::FieldMatrix<field_type,3,3> tmp; + for (int i=0; i<3; i++) + for (int j=0; j<3; j++) + tmp[i][j] = DR[i][j][alpha]; + auto tmp2 = RT * tmp; + Ke += dyadicProduct(SkewMatrix<field_type,3>(tmp2).axial(), aContravariant[alpha]); + } + + ////////////////////////////////////////////////////////// + // Add the local energy density + ////////////////////////////////////////////////////////// + + // Add the membrane energy density + energy += weight * (thickness_ - K*Dune::Power<3>::eval(thickness_) / 12.0) * W_m(Ee) + + weight * (Dune::Power<3>::eval(thickness_) / 12.0 - K * Dune::Power<5>::eval(thickness_) / 80.0)*W_m(Ee*b + c*Ke) + + weight * Dune::Power<3>::eval(thickness_) / 6.0 * W_mixt(Ee, c*Ke*b - 2*H*c*Ke) + + weight * Dune::Power<5>::eval(thickness_) / 80.0 * W_mp( (Ee*b + c*Ke)*b); + + // Add the bending energy density + energy += weight * (thickness_ - K*Dune::Power<3>::eval(thickness_) / 12.0) * W_curv(Ke) + + weight * (Dune::Power<3>::eval(thickness_) / 12.0 - K * Dune::Power<5>::eval(thickness_) / 80.0)*W_curv(Ke*b) + + weight * Dune::Power<5>::eval(thickness_) / 80.0 * W_curv(Ke*b*b); + + /////////////////////////////////////////////////////////// + // Volume load contribution + /////////////////////////////////////////////////////////// + + if (not volumeLoad_) + continue; + + // Value of the volume load density at the current position + Dune::FieldVector<double,3> volumeLoadDensity; + + if (std::dynamic_pointer_cast<const VirtualGridViewFunction<GridView,Dune::FieldVector<double,3> > >(volumeLoad_)) + std::dynamic_pointer_cast<const VirtualGridViewFunction<GridView,Dune::FieldVector<double,3> > >(volumeLoad_)->evaluateLocal(element, quadPos, volumeLoadDensity); + else + volumeLoad_->evaluate(geometry.global(quad[pt].position()), volumeLoadDensity); + + // Only translational dofs are affected by the volume load + for (size_t i=0; i<volumeLoadDensity.size(); i++) + energy -= thickness_ * (volumeLoadDensity[i] * value.r[i]) * quad[pt].weight() * integrationElement; + } + + + ////////////////////////////////////////////////////////////////////////////// + // Assemble boundary contributions + ////////////////////////////////////////////////////////////////////////////// + + if (not neumannFunction_) + return energy; + + for (auto&& it : intersections(neumannBoundary_->gridView(),element) ) + { + if (not neumannBoundary_ or not neumannBoundary_->contains(it)) + continue; + + const auto& quad = Dune::QuadratureRules<DT, gridDim-1>::rule(it.type(), quadOrder); + + for (size_t pt=0; pt<quad.size(); pt++) + { + // Local position of the quadrature point + const Dune::FieldVector<DT,gridDim>& quadPos = it.geometryInInside().global(quad[pt].position()); + + const DT integrationElement = it.geometry().integrationElement(quad[pt].position()); + + // The value of the local function + RigidBodyMotion<field_type,dim> value = localGeodesicFEFunction.evaluate(quadPos); + + // Value of the Neumann data at the current position + Dune::FieldVector<double,3> neumannValue; + + if (std::dynamic_pointer_cast<const VirtualGridViewFunction<GridView,Dune::FieldVector<double,3> > >(neumannFunction_)) + std::dynamic_pointer_cast<const VirtualGridViewFunction<GridView,Dune::FieldVector<double,3> > >(neumannFunction_)->evaluateLocal(element, quadPos, neumannValue); + else + neumannFunction_->evaluate(it.geometry().global(quad[pt].position()), neumannValue); + + // Only translational dofs are affected by the Neumann force + for (size_t i=0; i<neumannValue.size(); i++) + energy -= thickness_ * (neumannValue[i] * value.r[i]) * quad[pt].weight() * integrationElement; + } + + } + + return energy; +} + +#endif //#ifndef DUNE_GFE_NONPLANARCOSSERATSHELLENERGY_HH +