#ifndef DUNE_GFE_FUNCTIONS_LOCALPROJECTEDFEFUNCTION_HH #define DUNE_GFE_FUNCTIONS_LOCALPROJECTEDFEFUNCTION_HH #include <vector> #include <dune/common/fvector.hh> #include <dune/geometry/type.hh> #include <dune/gfe/linearalgebra.hh> #include <dune/gfe/polardecomposition.hh> #include <dune/gfe/spaces/realtuple.hh> #include <dune/gfe/spaces/productmanifold.hh> #include <dune/gfe/spaces/rotation.hh> namespace Dune::GFE { /** \brief Interpolate in an embedding Euclidean space, and project back onto the Riemannian manifold * * \tparam dim Dimension of the reference element * \tparam ctype Type used for coordinates on the reference element * \tparam LocalFiniteElement A Lagrangian finite element whose shape functions define the interpolation weights * \tparam TargetSpace The manifold that the function takes its values in * \tparam conforming Geometrical conformity of the functions (omits projections when set to false) */ template <int dim, class ctype, class LocalFiniteElement, class TS, bool conforming=true> class LocalProjectedFEFunction { public: using TargetSpace=TS; private: typedef typename TargetSpace::ctype RT; typedef typename TargetSpace::EmbeddedTangentVector EmbeddedTangentVector; static const int embeddedDim = EmbeddedTangentVector::dimension; public: /** \brief The type used for derivatives */ typedef Dune::FieldMatrix<RT, embeddedDim, dim> DerivativeType; /** \brief Constructor * \param localFiniteElement A Lagrangian finite element that provides the interpolation points * \param coefficients Values of the function at the Lagrange points */ LocalProjectedFEFunction(const LocalFiniteElement& localFiniteElement, const std::vector<TargetSpace>& coefficients) : localFiniteElement_(localFiniteElement), coefficients_(coefficients) { assert(localFiniteElement_.localBasis().size() == coefficients_.size()); } /** \brief Rebind the FEFunction to another TargetSpace */ template<class U> struct rebind { using other = LocalProjectedFEFunction<dim,ctype,LocalFiniteElement,U,conforming>; }; /** \brief The number of Lagrange points */ unsigned int size() const { return localFiniteElement_.localBasis().size(); } /** \brief The type of the reference element */ Dune::GeometryType type() const { return localFiniteElement_.type(); } /** \brief The scalar finite element used as the interpolation weights * * \note This method was added for InterpolationDerivatives, which needs it * to construct a copy of a LocalGeodesicFEFunction with ADOL-C's adouble * number type. This is not optimal, because the localFiniteElement * really is an implementation detail of LocalGeodesicFEFunction and * should not be needed just to copy an entire object. Other non-Euclidean * interpolation rules may not have such a finite element at all. * Therefore, this method may disappear again eventually. */ const LocalFiniteElement& localFiniteElement() const { return localFiniteElement_; } /** \brief Evaluate the function */ auto evaluate(const Dune::FieldVector<ctype, dim>& local) const; /** \brief Evaluate the derivative of the function */ DerivativeType evaluateDerivative(const Dune::FieldVector<ctype, dim>& local) const; /** \brief Evaluate the derivative of the function, if you happen to know the function value (much faster!) * \param local Local coordinates in the reference element where to evaluate the derivative * \param q Value of the local gfe function at 'local'. If you provide something wrong here the result will be wrong, too! * * \note This method is only usable in the conforming setting, because it requires the caller * to hand over the interpolation value as a TargetSpace object. */ DerivativeType evaluateDerivative(const Dune::FieldVector<ctype, dim>& local, const TargetSpace& q) const; /** \brief Evaluate the value and the derivative of the interpolation function * * \return A std::pair containing the value and the first derivative of the interpolation function. * If the interpolation is conforming then the first member of the pair will be a TargetSpace. * Otherwise it will be a RealTuple. */ auto evaluateValueAndDerivative(const Dune::FieldVector<ctype, dim>& local) const; /** \brief Get the i'th base coefficient. */ const TargetSpace& coefficient(int i) const { return coefficients_[i]; } private: /** \brief The scalar local finite element, which provides the weighting factors * \todo We really only need the local basis */ const LocalFiniteElement& localFiniteElement_; /** \brief The coefficient vector */ std::vector<TargetSpace> coefficients_; }; template <int dim, class ctype, class LocalFiniteElement, class TargetSpace, bool conforming> auto LocalProjectedFEFunction<dim,ctype,LocalFiniteElement,TargetSpace,conforming>:: evaluate(const Dune::FieldVector<ctype, dim>& local) const { // Evaluate the weighting factors---these are the Lagrangian shape function values at 'local' std::vector<Dune::FieldVector<ctype,1> > w; localFiniteElement_.localBasis().evaluateFunction(local,w); typename TargetSpace::CoordinateType c(0); for (size_t i=0; i<coefficients_.size(); i++) c.axpy(w[i][0], coefficients_[i].globalCoordinates()); if constexpr (conforming) return TargetSpace::projectOnto(c); else return (RealTuple<RT, TargetSpace::CoordinateType::dimension>)c; } template <int dim, class ctype, class LocalFiniteElement, class TargetSpace,bool conforming> typename LocalProjectedFEFunction<dim,ctype,LocalFiniteElement,TargetSpace,conforming>::DerivativeType LocalProjectedFEFunction<dim,ctype,LocalFiniteElement,TargetSpace,conforming>:: evaluateDerivative(const Dune::FieldVector<ctype, dim>& local) const { if constexpr(conforming) { // the function value at the point where we are evaluating the derivative TargetSpace q = evaluate(local); // Actually compute the derivative return evaluateDerivative(local, q); } else { std::vector<Dune::FieldMatrix<ctype, 1, dim> > wDer; localFiniteElement_.localBasis().evaluateJacobian(local, wDer); Dune::FieldMatrix<RT, embeddedDim, dim> derivative(0); for (size_t i = 0; i < embeddedDim; i++) for (size_t j = 0; j < dim; j++) for (size_t k = 0; k < coefficients_.size(); k++) derivative[i][j] += wDer[k][0][j] * coefficients_[k].globalCoordinates()[i]; return derivative; } } template <int dim, class ctype, class LocalFiniteElement, class TargetSpace,bool conforming> typename LocalProjectedFEFunction<dim,ctype,LocalFiniteElement,TargetSpace,conforming>::DerivativeType LocalProjectedFEFunction<dim,ctype,LocalFiniteElement,TargetSpace,conforming>:: evaluateDerivative(const Dune::FieldVector<ctype, dim>& local, const TargetSpace& q) const { // Evaluate the weighting factors---these are the Lagrangian shape function values at 'local' std::vector<Dune::FieldVector<ctype,1> > w; localFiniteElement_.localBasis().evaluateFunction(local,w); std::vector<Dune::FieldMatrix<ctype,1,dim> > wDer; localFiniteElement_.localBasis().evaluateJacobian(local,wDer); typename TargetSpace::CoordinateType embeddedInterpolation(0); for (size_t i=0; i<coefficients_.size(); i++) embeddedInterpolation.axpy(w[i][0], coefficients_[i].globalCoordinates()); Dune::FieldMatrix<RT,embeddedDim,dim> derivative(0); for (size_t i=0; i<embeddedDim; i++) for (size_t j=0; j<dim; j++) for (size_t k=0; k<coefficients_.size(); k++) derivative[i][j] += wDer[k][0][j] * coefficients_[k].globalCoordinates()[i]; auto derivativeOfProjection = TargetSpace::derivativeOfProjection(embeddedInterpolation); return derivativeOfProjection*derivative; } template <int dim, class ctype, class LocalFiniteElement, class TargetSpace,bool conforming> auto LocalProjectedFEFunction<dim,ctype,LocalFiniteElement,TargetSpace,conforming>:: evaluateValueAndDerivative(const Dune::FieldVector<ctype, dim>& local) const { // Construct the type of the result -- it depends on whether the interpolation // is conforming or not. using Value = std::conditional_t<conforming,TargetSpace,RealTuple<RT,embeddedDim> >; std::pair<Value,DerivativeType> result; /////////////////////////////////////////////////////////// // Compute the value of the interpolation function /////////////////////////////////////////////////////////// std::vector<Dune::FieldVector<ctype,1> > w; localFiniteElement_.localBasis().evaluateFunction(local,w); typename TargetSpace::CoordinateType embeddedInterpolation(0); for (size_t i=0; i<coefficients_.size(); i++) embeddedInterpolation.axpy(w[i][0], coefficients_[i].globalCoordinates()); if constexpr (conforming) result.first = TargetSpace::projectOnto(embeddedInterpolation); else result.first = (RealTuple<RT, TargetSpace::CoordinateType::dimension>)embeddedInterpolation; /////////////////////////////////////////////////////////// // Compute the derivative of the interpolation function /////////////////////////////////////////////////////////// // Compute the interpolation in the surrounding space std::vector<Dune::FieldMatrix<ctype,1,dim> > wDer; localFiniteElement_.localBasis().evaluateJacobian(local,wDer); result.second = 0.0; for (size_t i=0; i<embeddedDim; i++) for (size_t j=0; j<dim; j++) for (size_t k=0; k<coefficients_.size(); k++) result.second[i][j] += wDer[k][0][j] * coefficients_[k].globalCoordinates()[i]; // The derivative of the projection onto the manifold if constexpr(conforming) result.second = TargetSpace::derivativeOfProjection(embeddedInterpolation) * result.second; return result; } /** \brief Interpolate in an embedding Euclidean space, and project back onto the Riemannian manifold -- specialization for SO(3) * * \tparam dim Dimension of the reference element * \tparam ctype Type used for coordinates on the reference element * \tparam LocalFiniteElement A Lagrangian finite element whose shape functions define the interpolation weights */ template <int dim, class ctype, class LocalFiniteElement, class field_type, bool conforming> class LocalProjectedFEFunction<dim,ctype,LocalFiniteElement,Rotation<field_type,3>,conforming> { public: typedef Rotation<field_type,3> TargetSpace; private: typedef typename TargetSpace::ctype RT; typedef typename TargetSpace::EmbeddedTangentVector EmbeddedTangentVector; static const int embeddedDim = EmbeddedTangentVector::dimension; /** * \param A The argument of the projection * \param polar The image of the projection, i.e., the polar factor of A */ static std::array<std::array<FieldMatrix<field_type,3,3>, 3>, 3> derivativeOfProjection(const FieldMatrix<field_type,3,3>& A, FieldMatrix<field_type,3,3>& polar) { std::array<std::array<FieldMatrix<field_type,3,3>, 3>, 3> result; for (int i=0; i<3; i++) for (int j=0; j<3; j++) for (int k=0; k<3; k++) for (int l=0; l<3; l++) result[i][j][k][l] = (i==k) and (j==l); polar = A; // Use Heron's method const size_t maxIterations = 100; double tol = 0.001; for (size_t iteration=0; iteration<maxIterations; iteration++) { auto oldPolar = polar; auto polarInvert = polar; polarInvert.invert(); for (size_t i=0; i<polar.N(); i++) for (size_t j=0; j<polar.M(); j++) polar[i][j] = 0.5 * (polar[i][j] + polarInvert[j][i]); // Alternative name to align the code better with a description in a math text const auto& dQT = result; // Multiply from the right with Q^{-T} decltype(result) tmp2; for (int i=0; i<3; i++) for (int j=0; j<3; j++) for (int k=0; k<3; k++) for (int l=0; l<3; l++) tmp2[i][j][k][l] = 0.0; for (int i=0; i<3; i++) for (int j=0; j<3; j++) for (int k=0; k<3; k++) for (int l=0; l<3; l++) for (int m=0; m<3; m++) for (int o=0; o<3; o++) tmp2[i][j][k][l] += polarInvert[m][i] * dQT[o][m][k][l] * polarInvert[j][o]; for (int i=0; i<3; i++) for (int j=0; j<3; j++) for (int k=0; k<3; k++) for (int l=0; l<3; l++) result[i][j][k][l] = 0.5 * (result[i][j][k][l] - tmp2[i][j][k][l]); oldPolar -= polar; if (oldPolar.frobenius_norm() < tol) { break; } } return result; } public: /** \brief The type used for derivatives */ typedef Dune::FieldMatrix<RT, embeddedDim, dim> DerivativeType; /** \brief Constructor * \param localFiniteElement A Lagrangian finite element that provides the interpolation points * \param coefficients Values of the function at the Lagrange points */ LocalProjectedFEFunction(const LocalFiniteElement& localFiniteElement, const std::vector<TargetSpace>& coefficients) : localFiniteElement_(localFiniteElement), coefficients_(coefficients) { assert(localFiniteElement_.localBasis().size() == coefficients_.size()); } /** \brief Rebind the FEFunction to another TargetSpace */ template<class U> struct rebind { using other = LocalProjectedFEFunction<dim,ctype,LocalFiniteElement,U>; }; /** \brief The number of Lagrange points */ unsigned int size() const { return localFiniteElement_.size(); } /** \brief The type of the reference element */ Dune::GeometryType type() const { return localFiniteElement_.type(); } /** \brief The scalar finite element used as the interpolation weights * * \note This method was added for InterpolationDerivatives, which needs it * to construct a copy of a LocalGeodesicFEFunction with ADOL-C's adouble * number type. This is not optimal, because the localFiniteElement * really is an implementation detail of LocalGeodesicFEFunction and * should not be needed just to copy an entire object. Other non-Euclidean * interpolation rules may not have such a finite element at all. * Therefore, this method may disappear again eventually. */ const LocalFiniteElement& localFiniteElement() const { return localFiniteElement_; } /** \brief Evaluate the function */ TargetSpace evaluate(const Dune::FieldVector<ctype, dim>& local) const { Rotation<field_type,3> result; // Evaluate the weighting factors---these are the Lagrangian shape function values at 'local' std::vector<Dune::FieldVector<ctype,1> > w; localFiniteElement_.localBasis().evaluateFunction(local,w); // Interpolate in R^{3x3} FieldMatrix<field_type,3,3> interpolatedMatrix(0); for (size_t i=0; i<coefficients_.size(); i++) { FieldMatrix<field_type,3,3> coefficientAsMatrix; coefficients_[i].matrix(coefficientAsMatrix); interpolatedMatrix.axpy(w[i][0], coefficientAsMatrix); } // Project back onto SO(3) result.set(Dune::GFE::PolarDecomposition()(interpolatedMatrix)); return result; } /** \brief Evaluate the derivative of the function */ DerivativeType evaluateDerivative(const Dune::FieldVector<ctype, dim>& local) const { // the function value at the point where we are evaluating the derivative TargetSpace q = evaluate(local); // Actually compute the derivative return evaluateDerivative(local,q); } /** \brief Evaluate the derivative of the function, if you happen to know the function value (much faster!) * \param local Local coordinates in the reference element where to evaluate the derivative * \param q Value of the local function at 'local'. If you provide something wrong here the result will be wrong, too! */ DerivativeType evaluateDerivative(const Dune::FieldVector<ctype, dim>& local, const TargetSpace& q) const { // Evaluate the weighting factors---these are the Lagrangian shape function values at 'local' std::vector<Dune::FieldVector<ctype,1> > w; localFiniteElement_.localBasis().evaluateFunction(local,w); std::vector<Dune::FieldMatrix<ctype,1,dim> > wDer; localFiniteElement_.localBasis().evaluateJacobian(local,wDer); // Compute matrix representations for all coefficients (we only have them in quaternion representation) std::vector<Dune::FieldMatrix<field_type,3,3> > coefficientsAsMatrix(coefficients_.size()); for (size_t i=0; i<coefficients_.size(); i++) coefficients_[i].matrix(coefficientsAsMatrix[i]); // Interpolate in R^{3x3} FieldMatrix<field_type,3,3> interpolatedMatrix(0); for (size_t i=0; i<coefficients_.size(); i++) interpolatedMatrix.axpy(w[i][0], coefficientsAsMatrix[i]); Tensor3<RT,dim,3,3> derivative(0); for (size_t dir=0; dir<dim; dir++) for (size_t i=0; i<3; i++) for (size_t j=0; j<3; j++) for (size_t k=0; k<coefficients_.size(); k++) derivative[dir][i][j] += wDer[k][0][dir] * coefficientsAsMatrix[k][i][j]; FieldMatrix<field_type,3,3> polarFactor; auto derivativeOfProjection = this->derivativeOfProjection(interpolatedMatrix,polarFactor); Tensor3<field_type,dim,3,3> intermediateResult(0); for (size_t dir=0; dir<dim; dir++) for (size_t i=0; i<3; i++) for (size_t j=0; j<3; j++) for (size_t k=0; k<3; k++) for (size_t l=0; l<3; l++) intermediateResult[dir][i][j] += derivativeOfProjection[i][j][k][l]*derivative[dir][k][l]; // One more application of the chain rule: we need to go from orthogonal matrices to quaternions Tensor3<field_type,4,3,3> derivativeOfMatrixToQuaternion = Rotation<field_type,3>::derivativeOfMatrixToQuaternion(polarFactor); DerivativeType result(0); for (size_t dir0=0; dir0<4; dir0++) for (size_t dir1=0; dir1<dim; dir1++) for (size_t i=0; i<3; i++) for (size_t j=0; j<3; j++) result[dir0][dir1] += derivativeOfMatrixToQuaternion[dir0][i][j] * intermediateResult[dir1][i][j]; return result; } /** \brief Evaluate the value and the derivative of the interpolation function * * \return A std::pair containing the value and the first derivative of the interpolation function. * If the interpolation is conforming then the first member of the pair will be a TargetSpace. * Otherwise it will be a RealTuple. */ auto evaluateValueAndDerivative(const Dune::FieldVector<ctype, dim>& local) const { // Construct the type of the result -- it depends on whether the interpolation // is conforming or not. using Value = std::conditional_t<conforming,TargetSpace,RealTuple<RT,embeddedDim> >; std::pair<Value,DerivativeType> result; // Evaluate the weighting factors---these are the Lagrangian shape function values at 'local' std::vector<Dune::FieldVector<ctype,1> > w; localFiniteElement_.localBasis().evaluateFunction(local,w); // Interpolate in R^{3x3} FieldMatrix<field_type,3,3> interpolatedMatrix(0); for (size_t i=0; i<coefficients_.size(); i++) { FieldMatrix<field_type,3,3> coefficientAsMatrix; coefficients_[i].matrix(coefficientAsMatrix); interpolatedMatrix.axpy(w[i][0], coefficientAsMatrix); } // Project back onto SO(3) static_assert(conforming, "Nonconforming interpolation into SO(3) is not implemented!"); result.first.set(Dune::GFE::PolarDecomposition()(interpolatedMatrix)); result.second = evaluateDerivative(local, result.first); return result; } /** \brief Get the i'th base coefficient. */ const TargetSpace& coefficient(int i) const { return coefficients_[i]; } private: /** \brief The scalar local finite element, which provides the weighting factors * \todo We really only need the local basis */ const LocalFiniteElement& localFiniteElement_; /** \brief The coefficient vector */ std::vector<TargetSpace> coefficients_; }; /** \brief Interpolate in an embedding Euclidean space, and project back onto the Riemannian manifold -- specialization for R^3 x SO(3) * * \tparam dim Dimension of the reference element * \tparam ctype Type used for coordinates on the reference element * \tparam LocalFiniteElement A Lagrangian finite element whose shape functions define the interpolation weights */ template <int dim, class ctype, class LocalFiniteElement, class field_type> class LocalProjectedFEFunction<dim,ctype,LocalFiniteElement,ProductManifold<RealTuple<field_type,3>,Rotation<field_type,3> > > { public: using TargetSpace = ProductManifold<RealTuple<field_type,3>,Rotation<field_type,3> >; private: typedef typename TargetSpace::ctype RT; typedef typename TargetSpace::EmbeddedTangentVector EmbeddedTangentVector; static const int embeddedDim = EmbeddedTangentVector::dimension; public: /** \brief The type used for derivatives */ typedef Dune::FieldMatrix<RT, embeddedDim, dim> DerivativeType; /** \brief Constructor * \param localFiniteElement A Lagrangian finite element that provides the interpolation points * \param coefficients Values of the function at the Lagrange points */ LocalProjectedFEFunction(const LocalFiniteElement& localFiniteElement, const std::vector<TargetSpace>& coefficients) : localFiniteElement_(localFiniteElement), coefficients_(coefficients), translationCoefficients_(coefficients.size()) { assert(localFiniteElement.localBasis().size() == coefficients.size()); using namespace Dune::Indices; for (size_t i=0; i<coefficients.size(); i++) translationCoefficients_[i] = coefficients[i][_0].globalCoordinates(); std::vector<Rotation<field_type,3> > orientationCoefficients(coefficients.size()); for (size_t i=0; i<coefficients.size(); i++) orientationCoefficients[i] = coefficients[i][_1]; orientationFunction_ = std::make_unique<LocalProjectedFEFunction<dim,ctype,LocalFiniteElement,Rotation<field_type,3> > > (localFiniteElement,orientationCoefficients); } /** \brief Rebind the FEFunction to another TargetSpace */ template<class U> struct rebind { using other = LocalProjectedFEFunction<dim,ctype,LocalFiniteElement,U>; }; /** \brief The number of Lagrange points */ unsigned int size() const { return localFiniteElement_.size(); } /** \brief The type of the reference element */ Dune::GeometryType type() const { return localFiniteElement_.type(); } /** \brief Evaluate the function */ TargetSpace evaluate(const Dune::FieldVector<ctype, dim>& local) const { using namespace Dune::Indices; TargetSpace result; // Evaluate the weighting factors---these are the Lagrangian shape function values at 'local' std::vector<Dune::FieldVector<ctype,1> > w; localFiniteElement_.localBasis().evaluateFunction(local,w); result[_0] = FieldVector<field_type,3>(0.0); for (size_t i=0; i<w.size(); i++) result[_0].globalCoordinates().axpy(w[i][0], translationCoefficients_[i]); result[_1] = orientationFunction_->evaluate(local); return result; } /** \brief Evaluate the derivative of the function */ DerivativeType evaluateDerivative(const Dune::FieldVector<ctype, dim>& local) const { // the function value at the point where we are evaluating the derivative TargetSpace q = evaluate(local); // Actually compute the derivative return evaluateDerivative(local,q); } /** \brief Evaluate the derivative of the function, if you happen to know the function value (much faster!) * \param local Local coordinates in the reference element where to evaluate the derivative * \param q Value of the local function at 'local'. If you provide something wrong here the result will be wrong, too! */ DerivativeType evaluateDerivative(const Dune::FieldVector<ctype, dim>& local, const TargetSpace& q) const { using namespace Dune::Indices; DerivativeType result(0); // get translation part std::vector<Dune::FieldMatrix<ctype,1,dim> > sfDer(translationCoefficients_.size()); localFiniteElement_.localBasis().evaluateJacobian(local, sfDer); for (size_t i=0; i<translationCoefficients_.size(); i++) for (int j=0; j<3; j++) result[j].axpy(translationCoefficients_[i][j], sfDer[i][0]); // get orientation part Dune::FieldMatrix<field_type,4,dim> qResult = orientationFunction_->evaluateDerivative(local,q[_1]); for (int i=0; i<4; i++) for (int j=0; j<dim; j++) result[3+i][j] = qResult[i][j]; return result; } /** \brief Get the i'th base coefficient. */ const TargetSpace& coefficient(int i) const { return coefficients_[i]; } private: /** \brief The scalar local finite element, which provides the weighting factors * \todo We really only need the local basis */ const LocalFiniteElement& localFiniteElement_; // The coefficients of this interpolation rule std::vector<TargetSpace> coefficients_; // The coefficients again. Yes, we store it twice: To evaluate the interpolation rule efficiently // we need access to the coefficients for the various factor spaces separately. std::vector<Dune::FieldVector<field_type,3> > translationCoefficients_; std::unique_ptr<LocalProjectedFEFunction<dim,ctype,LocalFiniteElement,Rotation<field_type, 3> > > orientationFunction_; }; } // namespace Dune::GFE #endif