diff --git a/dune/gfe/productmanifold.hh b/dune/gfe/productmanifold.hh new file mode 100644 index 0000000000000000000000000000000000000000..48e86df8a9d44ce355f9ab6715a91b9c8b5f8d3f --- /dev/null +++ b/dune/gfe/productmanifold.hh @@ -0,0 +1,503 @@ +#ifndef DUNE_GFE_PRODUCTMANIFOLD_HH +#define DUNE_GFE_PRODUCTMANIFOLD_HH + +#include <iostream> +#include <tuple> + +#include <dune/common/fvector.hh> +#include <dune/common/math.hh> +#include <dune/common/hybridutilities.hh> +#include <dune/common/tuplevector.hh> +#include <dune/common/power.hh> + +#include <dune/gfe/linearalgebra.hh> + +namespace Dune::GFE +{ + namespace Impl + { + template<typename T, typename ... Ts> + constexpr auto sumDim() + { + return (T::dim + ... + sumDim<Ts>()); + } + + template<typename T, typename ... Ts> + constexpr auto sumEmbeddedDim() + { + return (T::embeddedDim + ... + sumEmbeddedDim<Ts>()); + } + + template<typename T, typename ... Ts> + constexpr auto variadicConvexityRadiusMin() + { + if constexpr (sizeof...(Ts)!=0) + return std::min(T::convexityRadius , variadicConvexityRadiusMin<Ts ...>()); + else + return T::convexityRadius; + } + + template <typename TS, typename ... TargetSpaces> + class ProductManifold; + + template<class U,typename Tfirst,typename ... TargetSpaces2> + struct rebindHelper + { + typedef ProductManifold<typename Tfirst::template rebind<U>::other ,typename rebindHelper<U,TargetSpaces2...>::other> other; + }; + + template<class U,typename Tlast> + struct rebindHelper<U,Tlast> + { + typedef typename Tlast::template rebind<U>::other other; + }; + } + + /** \brief A Product manifold */ + template <typename TS, typename ... TargetSpaces> + class ProductManifold + { + public: + template<std::size_t I> + using IC = Dune::index_constant<I>; + /** \brief The type used for coordinates. */ + using ctype = typename std::common_type<typename TS::ctype, typename TargetSpaces::ctype...>::type ; + using field_type = typename std::common_type<typename TS::ctype, typename TargetSpaces::ctype...>::type ; + + /** \brief Number of factors */ + static constexpr int numTS = 1 + sizeof...(TargetSpaces); + + /** \brief Dimension of manifold */ + static constexpr int dim = TS::dim + Impl::sumDim<TargetSpaces ...>(); + + /** \brief Dimension of the embedding space */ + static constexpr int embeddedDim = TS::embeddedDim + Impl::sumEmbeddedDim<TargetSpaces ...>(); + + /** \brief Type of a tangent of the ProductManifold with inner dimensions*/ + typedef Dune::FieldVector<field_type, dim> TangentVector; + + /** \brief Type of a tangent of the ProductManifold represented in the embedding space*/ + typedef Dune::FieldVector<field_type, embeddedDim> EmbeddedTangentVector; + + /** \brief The global convexity radius of the Product space */ + static constexpr double convexityRadius = Impl::variadicConvexityRadiusMin<TS,TargetSpaces ...>(); + + /** \brief The type used for global coordinates */ + typedef Dune::FieldVector<field_type ,embeddedDim> CoordinateType; + + /** \brief Default constructor */ + ProductManifold() = default; + + /** \brief Constructor from another ProductManifold */ + ProductManifold(const ProductManifold<TS,TargetSpaces ...>& productManifold) + : data_(productManifold.data_) + {} + + /** \brief Constructor from a coordinates vector of the embedding space */ + explicit ProductManifold(const CoordinateType& globalCoordinates) + { + DUNE_ASSERT_BOUNDS(globalCoordinates.size()== sumEmbeddedDim) + auto constructorFunctor =[] (auto& argsTuple, std::array<std::size_t,2>& posHelper, const auto& manifoldInt) + { + auto& res = std::get<0>(argsTuple)[manifoldInt]; + const auto& globalCoords =std::get<1>(argsTuple); + using Manifold = std::remove_const_t<typename std::remove_reference_t<decltype(res)> >; + res = Manifold(Dune::GFE::segmentAt<Manifold::embeddedDim>(globalCoords,posHelper[0])); + posHelper[0] += Manifold::embeddedDim; + }; + foreachManifold(constructorFunctor,*this,globalCoordinates); + } + + /** \brief Assignment from a coordinates vector of the embedding space */ + ProductManifold<TS,TargetSpaces ...>& operator=(const CoordinateType& globalCoordinates) + { + ProductManifold<TS,TargetSpaces ...> res(globalCoordinates); + data_ = res.data_; + return *this; + } + + /** \brief Assignment from ProductManifold with different type -- used for automatic differentiation with ADOL-C */ + template <typename TS2, typename ... TargetSpaces2> + ProductManifold<TS,TargetSpaces ...>& operator <<=(const ProductManifold<TS2,TargetSpaces2 ...>& other) + { + forEach(integralRange(IC<numTS>()), [&](auto&& i) { + (*this)[i] <<= other[i]; + }); + return *this; + } + + /** \brief Rebind the ProductManifold to another coordinate type using an embedded tangent vector*/ + template<class U,typename ... TargetSpaces2> + struct rebind + { + typedef typename Impl::rebindHelper<U,TS,TargetSpaces...>::other other; + }; + + /** \brief The exponential map from a given point. */ + static ProductManifold<TS,TargetSpaces ...> exp(const ProductManifold<TS,TargetSpaces ...>& p, const EmbeddedTangentVector& v) + { + ProductManifold<TS,TargetSpaces ...> res; + auto expFunctor =[] (auto& argsTuple, std::array<std::size_t,2>& posHelper, const auto& manifoldInt) + { + auto& res = std::get<0>(argsTuple)[manifoldInt]; + const auto& p = std::get<1>(argsTuple)[manifoldInt]; + const auto& tang = std::get<2>(argsTuple); + using Manifold = std::remove_const_t<typename std::remove_reference_t<decltype(res)> >; + auto currentEmbeddedTangentVector = Dune::GFE::segmentAt<Manifold::embeddedDim>(tang,posHelper[0]); + res = Manifold::exp(p,currentEmbeddedTangentVector); + posHelper[0] += Manifold::embeddedDim; + }; + foreachManifold(expFunctor,res,p,v); + return res; + } + + /** \brief The exponential map from a given point using an intrinsic tangent vector*/ + static ProductManifold<TS,TargetSpaces ...> exp(const ProductManifold<TS,TargetSpaces ...>& p, const TangentVector& v) + { + auto basis = p.orthonormalFrame(); + EmbeddedTangentVector embeddedTangent; + basis.mtv(v, embeddedTangent); + return exp(p,embeddedTangent); + } + + /** \brief Compute difference vector from a to b on the tangent space of a */ + static TangentVector log(const ProductManifold<TS,TargetSpaces...>& a, const ProductManifold<TS,TargetSpaces...>& b) + { + TangentVector diff; + auto logFunctor =[] (auto& argsTuple, std::array<std::size_t,2>& posHelper, const auto& manifoldInt) + { + auto& res = std::get<0>(argsTuple); + const auto& a = std::get<1>(argsTuple)[manifoldInt]; + const auto& b = std::get<2>(argsTuple)[manifoldInt]; + using Manifold = std::remove_const_t<typename std::remove_reference_t<decltype(a)> >; + const auto diffLoc = Manifold::log(a,b); + std::copy(diffLoc.begin(),diffLoc.end(),res.begin()+posHelper[0]); + posHelper[0] += Manifold::dim; + }; + foreachManifold(logFunctor,diff,a, b); + return diff; + } + + /** \brief Compute geodesic distance from a to b */ + static field_type distance(const ProductManifold<TS,TargetSpaces...>& a, const ProductManifold<TS,TargetSpaces...>& b) + { + field_type dist=0.0; + auto distanceFunctor =[] (auto& argsTuple, std::array<std::size_t,2>& posHelper, const auto& manifoldInt) + { + auto& res = std::get<0>(argsTuple); + const auto& a = std::get<1>(argsTuple)[manifoldInt]; + const auto& b = std::get<2>(argsTuple)[manifoldInt]; + using Manifold = std::remove_const_t<typename std::remove_reference_t<decltype(a)> >; + res += Dune::Power<2>().eval(Manifold::distance(a,b)); + }; + foreachManifold(distanceFunctor,dist,a, b); + return sqrt(dist); + } + + /** \brief Compute the gradient of the squared distance function keeping the first argument fixed */ + static EmbeddedTangentVector derivativeOfDistanceSquaredWRTSecondArgument(const ProductManifold<TS,TargetSpaces...>& a, + const ProductManifold<TS,TargetSpaces...>& b) + { + EmbeddedTangentVector derivative; + derivative= 0.0; + + auto derivOfDistSqdWRTSecArgFunctor = [] (auto& argsTuple, std::array<std::size_t,2>& posHelper, const auto& manifoldInt) + { + auto& derivative = std::get<0>(argsTuple); + const auto& a = std::get<1>(argsTuple)[manifoldInt]; + const auto& b = std::get<2>(argsTuple)[manifoldInt]; + using Manifold = std::remove_const_t<typename std::remove_reference_t<decltype(a)> >; + const auto diffLoc = Manifold::derivativeOfDistanceSquaredWRTSecondArgument(a,b); + std::copy(diffLoc.begin(),diffLoc.end(),derivative.begin()+posHelper[0]); + posHelper[0] += Manifold::embeddedDim; + }; + foreachManifold(derivOfDistSqdWRTSecArgFunctor, derivative,a, b); + return derivative; + } + + /** \brief Compute the Hessian of the squared distance function keeping the first argument fixed */ + static auto secondDerivativeOfDistanceSquaredWRTSecondArgument(const ProductManifold<TS,TargetSpaces...>& a, + const ProductManifold<TS, TargetSpaces...>& b) + { + Dune::SymmetricMatrix<field_type,embeddedDim> result; + auto secDerivOfDistSqWRTSecArgFunctor = [] (auto& argsTuple, std::array<std::size_t,2>& posHelper, const auto& manifoldInt) + { + auto& deriv = std::get<0>(argsTuple); + const auto& a = std::get<1>(argsTuple)[manifoldInt]; + const auto& b = std::get<2>(argsTuple)[manifoldInt]; + using Manifold = std::remove_const_t<typename std::remove_reference_t<decltype(a)> >; + const auto diffLoc = Manifold::secondDerivativeOfDistanceSquaredWRTSecondArgument(a,b); + for(size_t i=posHelper[0]; i<posHelper[0]+Manifold::embeddedDim; ++i ) + for(size_t j=posHelper[0]; j<=i; ++j ) + deriv(i,j) = diffLoc(i - posHelper[0], j - posHelper[0]); + posHelper[0] += Manifold::embeddedDim; + }; + foreachManifold(secDerivOfDistSqWRTSecArgFunctor,result,a, b); + return result; + } + + /** \brief Compute the mixed second derivate \partial d^2 / \partial da db */ + static Dune::FieldMatrix<field_type ,embeddedDim,embeddedDim> + secondDerivativeOfDistanceSquaredWRTFirstAndSecondArgument(const ProductManifold<TS,TargetSpaces ...>& a, + const ProductManifold<TS, TargetSpaces ...>& b) + { + Dune::FieldMatrix<field_type,embeddedDim,embeddedDim> result(0); + auto secDerivOfDistSqWRTFirstAndSecArgFunctor = [] (auto& argsTuple, std::array<std::size_t,2>& posHelper, const auto& manifoldInt) + { + auto& deriv = std::get<0>(argsTuple); + const auto& a = std::get<1>(argsTuple)[manifoldInt]; + const auto& b = std::get<2>(argsTuple)[manifoldInt]; + using Manifold = std::remove_const_t<typename std::remove_reference_t<decltype(a)> >; + const auto diffLoc = Manifold::secondDerivativeOfDistanceSquaredWRTFirstAndSecondArgument(a,b); + for(size_t i=posHelper[0]; i<posHelper[0]+Manifold::embeddedDim; ++i ) + for(size_t j=posHelper[0]; j<posHelper[0]+Manifold::embeddedDim; ++j ) + deriv[i][j] = diffLoc[i - posHelper[0]][ j - posHelper[0]]; + posHelper[0] += Manifold::embeddedDim; + }; + foreachManifold(secDerivOfDistSqWRTFirstAndSecArgFunctor,result,a, b); + return result; + } + + /** \brief Compute the third derivative \partial d^3 / \partial dq^3 */ + static Tensor3<field_type ,embeddedDim,embeddedDim,embeddedDim> + thirdDerivativeOfDistanceSquaredWRTSecondArgument(const ProductManifold<TS,TargetSpaces ...>& a, + const ProductManifold<TS, TargetSpaces ...>& b) + { + Tensor3<field_type,embeddedDim,embeddedDim,embeddedDim> result(0); + auto thirdDerivOfDistSqWRTSecArgFunctor =[](auto& argsTuple, std::array<std::size_t,2>& posHelper, const auto& manifoldInt) + { + auto& deriv = std::get<0>(argsTuple); + const auto& a = std::get<1>(argsTuple)[manifoldInt]; + const auto& b = std::get<2>(argsTuple)[manifoldInt]; + using Manifold = std::remove_const_t<typename std::remove_reference_t<decltype(a)>>; + const auto diffLoc = Manifold::thirdDerivativeOfDistanceSquaredWRTSecondArgument(a,b); + for(size_t i=posHelper[0]; i<posHelper[0]+Manifold::embeddedDim; ++i ) + for(size_t j=posHelper[0]; j<posHelper[0]+Manifold::embeddedDim; ++j ) + for(size_t k=posHelper[0]; k<posHelper[0]+Manifold::embeddedDim; ++k ) + deriv[i][j][k] = diffLoc[i - posHelper[0]][ j - posHelper[0]][k - posHelper[0]]; + posHelper[0] += Manifold::embeddedDim; + }; + foreachManifold(thirdDerivOfDistSqWRTSecArgFunctor,result,a, b); + return result; + } + + /** \brief Compute the mixed third derivative \partial d^3 / \partial da db^2 */ + static Tensor3<field_type ,embeddedDim,embeddedDim,embeddedDim> + thirdDerivativeOfDistanceSquaredWRTFirst1AndSecond2Argument(const ProductManifold<TS,TargetSpaces ...>& a, + const ProductManifold<TS, TargetSpaces ...>& b) + { + Tensor3<field_type,embeddedDim,embeddedDim,embeddedDim> result(0); + auto thirdDerivOfDistSqWRT1And2ArgFunctor =[] (auto& argsTuple, std::array<std::size_t,2>& posHelper, const auto& manifoldInt) + { + auto& deriv = std::get<0>(argsTuple); + const auto& a = std::get<1>(argsTuple)[manifoldInt]; + const auto& b = std::get<2>(argsTuple)[manifoldInt]; + using Manifold = std::remove_const_t<typename std::remove_reference_t<decltype(a)>>; + const auto diffLoc = Manifold::thirdDerivativeOfDistanceSquaredWRTFirst1AndSecond2Argument(a,b); + for(size_t i=posHelper[0]; i<posHelper[0]+Manifold::embeddedDim; ++i ) + for(size_t j=posHelper[0]; j<posHelper[0]+Manifold::embeddedDim; ++j ) + for(size_t k=posHelper[0]; k<posHelper[0]+Manifold::embeddedDim; ++k ) + deriv[i][j][k] = diffLoc[i - posHelper[0]][ j - posHelper[0]][k - posHelper[0]]; + posHelper[0] += Manifold::embeddedDim; + }; + foreachManifold(thirdDerivOfDistSqWRT1And2ArgFunctor,result,a, b); + return result; + } + + /** \brief Project tangent vector of R^n onto the tangent space */ + EmbeddedTangentVector projectOntoTangentSpace(const EmbeddedTangentVector& v) const + { + EmbeddedTangentVector result {v}; + auto projectOntoTangentSpaceFunctor =[] (auto& argsTuple, std::array<std::size_t,2>& posHelper, const auto& manifoldInt) + { + auto& v = std::get<0>(argsTuple); + const auto& a = std::get<1>(argsTuple)[manifoldInt]; + using Manifold = std::remove_const_t<typename std::remove_reference_t<decltype(a)> >; + const auto vLoc = a.projectOntoTangentSpace(Dune::GFE::segmentAt<Manifold::embeddedDim>(v,posHelper[0])); + std::copy(vLoc.begin(),vLoc.end(),v.begin()+posHelper[0]); + posHelper[0] += Manifold::embeddedDim; + }; + foreachManifold(projectOntoTangentSpaceFunctor,result,*this); + return result; + } + + /** \brief Project tangent vector of R^n onto the normal space space */ + EmbeddedTangentVector projectOntoNormalSpace(const EmbeddedTangentVector& v) const + { + EmbeddedTangentVector result {v}; + auto projectOntoNormalSpaceFunctor =[] (auto& argsTuple, std::array<std::size_t,2>& posHelper, const auto& manifoldInt) + { + auto& v = std::get<0>(argsTuple); + const auto& a = std::get<1>(argsTuple)[manifoldInt]; + using Manifold = std::remove_const_t<typename std::remove_reference_t<decltype(a)> >; + const auto vLoc = a.projectOntoNormalSpace(Dune::GFE::segmentAt<Manifold::embeddedDim>(v,posHelper[0])); + std::copy(vLoc.begin(),vLoc.end(),v.begin()+posHelper[0]); + posHelper[0] += Manifold::embeddedDim; + }; + foreachManifold(projectOntoNormalSpaceFunctor,result,*this); + return result; + } + + /** \brief Project tangent vector of R^n onto the normal space space */ + static ProductManifold<TS,TargetSpaces ...> projectOnto(const CoordinateType& v) + { + ProductManifold<TS,TargetSpaces ...> result {v}; + auto projectOntoFunctor =[] (auto& argsTuple, std::array<std::size_t,2>& posHelper, const auto& manifoldInt) + { + auto& res = std::get<0>(argsTuple)[manifoldInt]; + auto& v = std::get<1>(argsTuple); + using Manifold = std::remove_const_t<typename std::remove_reference_t<decltype(res)> >; + res = Manifold::projectOnto(Dune::GFE::segmentAt<Manifold::embeddedDim>(v,posHelper[0])); + posHelper[0] += Manifold::embeddedDim; + }; + foreachManifold(projectOntoFunctor,result, v); + return result; + } + + /** \brief Project tangent vector of R^n onto the normal space space */ + static auto derivativeOfProjection(const CoordinateType& v) + { + Dune::FieldMatrix<typename CoordinateType::value_type, embeddedDim, embeddedDim> result; + auto derivativeOfProjectionFunctor =[] (auto& argsTuple, std::array<std::size_t,2>& posHelper, const auto& manifoldInt) + { + auto& res = std::get<0>(argsTuple); + const auto& v = std::get<1>(argsTuple); + const Dune::TupleVector<TS,TargetSpaces...> ManifoldTuple; + using Manifold = std::remove_const_t<typename std::remove_reference_t<decltype(ManifoldTuple[manifoldInt])> >; + const auto vLoc = Manifold::derivativeOfProjection(Dune::GFE::segmentAt<Manifold::embeddedDim>(v,posHelper[0])); + for(size_t k=posHelper[0]; k<posHelper[0]+Manifold::embeddedDim; ++k ) + for(size_t j=posHelper[0]; j<posHelper[0]+Manifold::embeddedDim; ++j ) + res[k][j] = vLoc[k - posHelper[0]][j-posHelper[0]]; + posHelper[0] += Manifold::embeddedDim; + }; + foreachManifold(derivativeOfProjectionFunctor,result, v); + return result; + } + + /** \brief The Weingarten map */ + EmbeddedTangentVector weingarten(const EmbeddedTangentVector& z, const EmbeddedTangentVector& v) const + { + EmbeddedTangentVector result; + auto weingartenFunctor =[] (auto& argsTuple, std::array<std::size_t,2>& posHelper, const auto& manifoldInt) + { + auto& res = std::get<0>(argsTuple); + const auto& a = std::get<1>(argsTuple)[manifoldInt]; + const auto& z = std::get<2>(argsTuple); + const auto& v = std::get<3>(argsTuple); + using Manifold = std::remove_const_t<typename std::remove_reference_t<decltype(a)> >; + const auto resLoc = a.weingarten(Dune::GFE::segmentAt<Manifold::embeddedDim>(z,posHelper[0]), + Dune::GFE::segmentAt<Manifold::embeddedDim>(v,posHelper[0])); + std::copy(resLoc.begin(),resLoc.end(),res.begin()+posHelper[0]); + posHelper[0] +=Manifold::embeddedDim; + }; + foreachManifold(weingartenFunctor,result, *this,z, v); + return result; + } + + /** \brief Compute an orthonormal basis of the tangent space of the ProductManifold + This basis may not be globally continuous. */ + Dune::FieldMatrix<field_type ,dim,embeddedDim> orthonormalFrame() const + { + Dune::FieldMatrix<field_type,dim,embeddedDim> result(0); + auto orthonormalFrameFunctor =[] (auto& argsTuple, std::array<std::size_t,2>& posHelper, const auto& manifoldInt) + { + auto& res = std::get<0>(argsTuple); + const auto& a = std::get<1>(argsTuple)[manifoldInt]; + using Manifold = std::remove_const_t<typename std::remove_reference_t<decltype(a)> >; + const auto resLoc = a.orthonormalFrame(); + for(size_t i=posHelper[0]; i<posHelper[0]+Manifold::dim; ++i ) + for(size_t j=posHelper[1]; j<posHelper[1]+Manifold::embeddedDim; ++j ) + res[i][j] = resLoc[i - posHelper[0]][j-posHelper[1]]; + posHelper[0] += Manifold::dim; + posHelper[1] += Manifold::embeddedDim; + }; + foreachManifold(orthonormalFrameFunctor,result,*this); + return result; + } + + /** \brief The global coordinates */ + CoordinateType globalCoordinates() const + { + CoordinateType returnValue; + auto globalCoordinatesFunctor =[] (auto& argsTuple, std::array<std::size_t,2>& posHelper, const auto& i) + { + auto& res = std::get<0>(argsTuple); + const auto& p = std::get<1>(argsTuple)[i]; + const auto vLoc = p.globalCoordinates(); + using Manifold = std::remove_const_t<typename std::remove_reference_t<decltype(p)> >; + std::copy(vLoc.begin(),vLoc.end(),res.begin()+posHelper[0]); + posHelper[0] += Manifold::embeddedDim; + }; + foreachManifold(globalCoordinatesFunctor,returnValue,*this); + return returnValue; + } + + /** \brief Const access to the tuple elements */ + template<std::size_t i> + constexpr decltype(auto) operator[] (const Dune::index_constant<i>&) const + { + return std::get<i>(data_); + } + + /** \brief Non-const access to the tuple elements */ + template<std::size_t i> + decltype(auto) operator[] (const Dune::index_constant<i>&) + { + return std::get<i>(data_); + } + + /** \brief Number of elements of the tuple */ + static constexpr std::size_t size() + { + return numTS; + } + + template<class TS2, class... TargetSpaces2> + friend std::ostream& operator<<(std::ostream& s, const ProductManifold<TS2, TargetSpaces2 ...>& c); + + private: + /** + * \brief Range based for loop over all Manifolds in ProductManifold + * + * \tparam FunctorType the functor which should be applied for all manifolds + * \tparam Args... The arguments which are passed to the functor + * + * The functor has to extract the current manifold from its arguments. + * Therefore, the current index i is passed to the functor. + * + * Furthermore, the functor gets two integers to deal with FieldVectors + * or FieldMatrices which span the whole (embedding) coordinate range of the ProductManifold. + * + * Example: + * If a argument of the functor is + * ProductManifold<Realtuple<double,3>,UnitVector<double,3>>::EmbeddedTangentVector, + * the embedded coordinate vector has 6 entries. The functor needs to know where + * to insert the next subvector of the submanifold. Therefore, posHelper[0] should be 0 at the first + * iteration and 3 at the second one. Then first indices [0..2] stores the result of + * Realtuple<double,3> and [3..5] stores + * the result of UnitVector<double,3>. + * See e.g. the globalCoordinates() function + */ + template<typename FunctorType, typename ... Args> + static void foreachManifold( FunctorType&& functor,Args&&... args) + { + auto argsTuple = std::forward_as_tuple(args ...); + std::array<std::size_t,2> posHelper({0,0}); + Dune::Hybrid::forEach(Dune::Hybrid::integralRange(IC<numTS>()),[&](auto&& i) { + functor(argsTuple,posHelper,i); + }); + } + + std::tuple<TS,TargetSpaces ...> data_; +}; + + template< typename TS,typename ... TargetSpaces> + std::ostream& operator<<(std::ostream& s, const ProductManifold<TS, TargetSpaces ...>& c) + { + Dune::Hybrid::forEach(Dune::Hybrid::integralRange(Dune::index_constant<ProductManifold<TS, TargetSpaces ...>::numTS>()), [&](auto&& i) { + s<<Dune::className<decltype(c[i])>()<<" "<< c[i]<<"\n"; + }); + return s; + } +} +#endif