diff --git a/src/amdis/DataTransfer.hpp b/src/amdis/DataTransfer.hpp index 539628cc4de5eb52c0f1ab856148aed969c025b9..3f9f8ca6df4ece3ea9e365157d72169e9da85f27 100644 --- a/src/amdis/DataTransfer.hpp +++ b/src/amdis/DataTransfer.hpp @@ -1,14 +1,8 @@ #pragma once #include <memory> -#include <type_traits> -#include <utility> - -#include <dune/functions/functionspacebases/subspacebasis.hh> #include <amdis/Output.hpp> -#include <amdis/utility/TreeData.hpp> -#include <amdis/utility/Visitor.hpp> namespace AMDiS { @@ -17,17 +11,6 @@ namespace AMDiS INTERPOLATE = 1 } DataTransferOperation; - template <class Node, class RangeType> - class NodeDataTransfer - { - public: - template <class Basis, class Container> - void preAdapt(Basis const& basis, Container const& coeff, bool mightCoarsen) {} - - template <class Basis, class Container> - void postAdapt(Basis const& basis, Container& coeff, bool refined) const {} - }; - template <class Container> class DataTransferInterface @@ -40,7 +23,7 @@ namespace AMDiS virtual void preAdapt(Container const& container, bool mightCoarsen) = 0; /// Interpolate data to new grid after grid adaption - virtual void postAdapt(Container& container, bool refined) const = 0; + virtual void postAdapt(Container& container, bool refined) = 0; }; @@ -51,9 +34,9 @@ namespace AMDiS : public DataTransferInterface<Container> { public: - virtual void preAdapt(Container const& container, bool) override {} + void preAdapt(Container const& container, bool) override {} - virtual void postAdapt(Container& container, bool) const override + void postAdapt(Container& container, bool) override { container.compress(); } @@ -61,45 +44,7 @@ namespace AMDiS template <class Container, class Basis> - class DataTransfer - : public DataTransferInterface<Container> - { - template <class Node> - using NDT = NodeDataTransfer<std::decay_t<Node>, typename Container::value_type>; - - public: - DataTransfer(Basis const& basis) - : basis_(&basis) - {} - - /// Calls \ref NodeDataTransfer::preAdapt() on each basis node - virtual void preAdapt(Container const& container, bool mightCoarsen) override - { - nodeDataTransfer_.init(*basis_); - AMDiS::forEachLeafNode_(basis_->localView().tree(), [&](auto const& node, auto const& treePath) - { - auto subBasis = Dune::Functions::subspaceBasis(*basis_, treePath); - nodeDataTransfer_[node].preAdapt(subBasis, container.vector(), mightCoarsen); - }); - } - - /// Calls \ref NodeDataTransfer::postAdapt() on each basis node after compressing the - /// Container the dimension of the basis - virtual void postAdapt(Container& container, bool refined) const override - { - container.compress(); - AMDiS::forEachLeafNode_(basis_->localView().tree(), [&](auto const& node, auto const& treePath) - { - auto subBasis = Dune::Functions::subspaceBasis(*basis_, treePath); - nodeDataTransfer_[node].postAdapt(subBasis, container.vector(), refined); - }); - } - - private: - Basis const* basis_; - TreeData<Basis, NDT, true> nodeDataTransfer_; - }; - + class DataTransfer; /// Factory to create DataTransfer objects based on the \ref DataTransferOperation template <class Container> @@ -116,7 +61,7 @@ namespace AMDiS case NO_OPERATION: return std::make_unique<NoDataTransfer<Container>>(); case INTERPOLATE: - return std::make_unique<DataTransfer<Container, Basis>>(basis); + return std::make_unique< DataTransfer<Container, Basis> >(basis); default: error_exit("Invalid data transfer\n"); return nullptr; // avoid warnings @@ -125,3 +70,5 @@ namespace AMDiS }; } // end namespace AMDiS + +#include "DataTransfer.inc.hpp" diff --git a/src/amdis/DataTransfer.inc.hpp b/src/amdis/DataTransfer.inc.hpp new file mode 100644 index 0000000000000000000000000000000000000000..9040221aad2fa15da2ee23cf2dcbee74502497e9 --- /dev/null +++ b/src/amdis/DataTransfer.inc.hpp @@ -0,0 +1,470 @@ +#pragma once + +#include <cmath> +#include <functional> +#include <limits> +#include <map> +#include <memory> +#include <numeric> +#include <type_traits> +#include <unordered_map> +#include <utility> +#include <vector> + +#include <dune/common/fvector.hh> +#include <dune/common/hash.hh> + +#include <dune/grid/common/mcmgmapper.hh> +#include <dune/grid/common/rangegenerators.hh> + +#include <dune/functions/common/functionfromcallable.hh> +#include <dune/functions/functionspacebases/lagrangebasis.hh> + +#include <amdis/Output.hpp> +#include <amdis/utility/ConcurrentCache.hpp> +#include <amdis/utility/TreeContainer.hpp> +#include <amdis/utility/Visitor.hpp> + +namespace AMDiS +{ + template <class Node, class Container, class Basis> + class NodeDataTransfer {}; + + + /** Data Transfer implementation for a single grid + * Handles computations related to the geometric information of the grid and passes that to the + * underlying NodeDataTransfer classes + */ + template <class Container, class Basis> + class DataTransfer + : public DataTransferInterface<Container> + { + using LocalView = typename Basis::LocalView; + using Tree = typename LocalView::Tree; + using GridView = typename Basis::GridView; + using Grid = typename GridView::Grid; + using Mapper = Dune::LeafMultipleCodimMultipleGeomTypeMapper<Grid>; + + using IdSet = typename Grid::LocalIdSet; + using IdType = typename IdSet::IdType; + + using Element = typename GridView::template Codim<0>::Entity; + using Geometry = typename Element::Geometry; + using LocalCoordinate = typename Geometry::LocalCoordinate; + using BoolCoordPair = std::pair<bool, LocalCoordinate>; + + // Hash function for cache container + struct Hasher + { + std::size_t operator()(LocalCoordinate const& coord) const + { + std::size_t seed = 0; + for (std::size_t i = 0; i < coord.size(); ++i) + Dune::hash_combine(seed, coord[i]); + return seed; + } + }; + + using CacheImp = std::unordered_map<LocalCoordinate, BoolCoordPair, Hasher>; + using ChildCache = ConcurrentCache<LocalCoordinate, BoolCoordPair, ConsecutivePolicy, CacheImp>; + + constexpr static int d = Geometry::coorddimension; + using ctype = typename Geometry::ctype; + + // Returns the Node's NodeDataTransfer + struct NDT + { + template<class Node> + auto operator()(const Node& node) + { + using NDT = NodeDataTransfer<Node, Container, Basis>; + return NDT{}; + } + }; + + using NodeDataTransferContainer = std::decay_t<decltype( + makeTreeContainer<Tree, NDT>(std::declval<const Tree&>(), NDT()))>; + + // Returns the Node's NodeElementData + struct NodeElementData + { + template<class Node> + auto operator()(const Node& node) + { + using NED = typename NodeDataTransfer<Node, Container, Basis> + ::NodeElementData; + return NED{}; + } + }; + + using ElementData = std::decay_t<decltype( + makeTreeContainer<Tree, NodeElementData>(std::declval<const Tree&>(), NodeElementData()))>; + + public: + // Container with data that persists during grid adaptation + using PersistentContainer = std::map<IdType, ElementData>; + + public: + DataTransfer(Basis const& basis) + : basis_(&basis) + , mapper_(basis.gridView().grid(), Dune::mcmgElementLayout()) + , finished_(mapper_.size(), false) + , nodeDataTransfer_(makeTreeContainer<Tree, NDT>(basis_->localView().tree(), NDT())) + {} + + /** Saves data contained in coeff in the PersistentContainer + * To be called after grid.preAdapt() and before grid.adapt() + */ + void preAdapt(Container const& coeff, bool mightCoarsen) override; + + /** Unpacks data from the PersistentContainer + * To be called after grid.adapt() and before grid.postAdapt() + */ + void postAdapt(Container& coeff, bool refined) override; + + private: + Basis const* basis_; + PersistentContainer persistentContainer_; + Mapper mapper_; + std::vector<bool> finished_; + NodeDataTransferContainer nodeDataTransfer_; + }; + + template <class Container, class Basis> + void DataTransfer<Container, Basis>::preAdapt(Container const& coeff, bool mightCoarsen) + { + auto gv = basis_->gridView(); + auto lv = basis_->localView(); + auto const& idSet = gv.grid().localIdSet(); + + forEachLeafNode_(lv.tree(), [&](auto const& node, auto const& tp) { + nodeDataTransfer_[tp].preAdaptInit(lv, coeff, node); + }); + + // Make persistent DoF container + persistentContainer_.clear(); + for (const auto& e : elements(gv)) + { + auto it = persistentContainer_.emplace(idSet.id(e), + makeTreeContainer<Tree, NodeElementData>(lv.tree(), NodeElementData())); + + lv.bind(e); + auto& treeContainer = it.first->second; + forEachLeafNode_(lv.tree(), [&](auto const& node, auto const& tp) { + nodeDataTransfer_[tp].cacheLocal(treeContainer[tp]); + }); + } + + // Interpolate from possibly vanishing elements + if (mightCoarsen) { + auto maxLevel = gv.grid().maxLevel(); + ctype const checkInsideTolerance = std::sqrt(std::numeric_limits<ctype>::epsilon()); + for (auto const& e : elements(gv)) + { + auto father = e; + while (father.mightVanish() && father.hasFather()) + { + father = father.father(); + auto it = persistentContainer_.emplace(idSet.id(father), + makeTreeContainer<Tree, NodeElementData>(lv.tree(), NodeElementData())); + if (it.second) { + auto& treeContainer = it.first->second; + auto geo = father.geometry(); + bool init = true; // init flag for first call on new father element + bool restrictLocalCompleted = false; + auto hItEnd = father.hend(maxLevel); + for (auto hIt = father.hbegin(maxLevel); hIt != hItEnd; ++hIt) { + if (hIt->isLeaf()) { + auto const& child = *hIt; + auto search = persistentContainer_.find(idSet.id(child)); + assert(search != persistentContainer_.end()); + auto const& childContainer = search->second; + lv.bind(child); + + auto const& childGeo = child.geometry(); + auto const& ref = Dune::referenceElement(childGeo); + auto const& refTypeId = ref.type().id(); + + // Transfers input father-local point x into child-local point y + // Returns false if x is not inside the child + auto xInChild = [&](LocalCoordinate const& x) -> BoolCoordPair { + LocalCoordinate local = childGeo.local(geo.global(x)); + // TODO(FM): Using an implementation detail as workaround for insufficient + // tolerance, see https://gitlab.dune-project.org/core/dune-grid/issues/84 + bool isInside = Dune::Geo::Impl::template checkInside<ctype,d> + (refTypeId, d, local, checkInsideTolerance); + return BoolCoordPair(isInside, std::move(local)); + }; + + // TODO(FM): Disable for single-node basis + ChildCache childCache; + auto xInChildCached = [&](LocalCoordinate const& x) -> BoolCoordPair { + return childCache.get(x, [&](LocalCoordinate const& x) { return xInChild(x); }); + }; + + restrictLocalCompleted = true; + forEachLeafNode_(lv.tree(), [&](auto const& node, auto const& tp) { + restrictLocalCompleted &= + nodeDataTransfer_[tp].restrictLocal(father, treeContainer[tp], xInChildCached, + childContainer[tp], init); + }); + init = false; + } + } + // test if restrictLocal was completed on all nodes + assert(restrictLocalCompleted); + } + } + } + } + } + + template <class Container, class Basis> + void DataTransfer<Container, Basis>::postAdapt(Container& coeff, bool refined) + { + coeff.resize(*basis_); + auto gv = basis_->gridView(); + auto lv = basis_->localView(); + auto const& idSet = gv.grid().localIdSet(); + forEachLeafNode_(lv.tree(), [&](auto const& node, auto const& tp) { + nodeDataTransfer_[tp].postAdaptInit(lv, coeff, node); + }); + + mapper_.update(); + finished_.clear(); + finished_.resize(mapper_.size(), false); + for (const auto& e : elements(gv)) + { + auto index = mapper_.index(e); + if (finished_[index]) + continue; + + auto it = persistentContainer_.find(idSet.id(e)); + + // Data already exists and no interpolation is required + if (it != persistentContainer_.end()) { + lv.bind(e); + auto const& treeContainer = it->second; + forEachLeafNode_(lv.tree(), [&](auto const& node, auto const& tp) { + nodeDataTransfer_[tp].copyLocal(treeContainer[tp]); + }); + finished_[index] = true; + } + // Data needs to be interpolated + else { + auto father = e; + while (father.hasFather() && father.isNew()) + father = father.father(); + + auto maxLevel = gv.grid().maxLevel(); + auto fatherGeo = father.geometry(); + bool init = true; // init flag for first call on new father element + + auto it = persistentContainer_.find(idSet.id(father)); + assert(it != persistentContainer_.end()); + auto const& treeContainer = it->second; + + auto hItEnd = father.hend(maxLevel); + for (auto hIt = father.hbegin(maxLevel); hIt != hItEnd; ++hIt) { + if (hIt->isLeaf()) { + auto const& child = *hIt; + lv.bind(child); + + // coordinate transform from child to father element + auto xInFather = [&fatherGeo, childGeo = child.geometry()] + (LocalCoordinate const& x) -> LocalCoordinate + { + return fatherGeo.local(childGeo.global(x)); + }; + + forEachLeafNode_(lv.tree(), [&](auto const& node, auto const& tp) { + nodeDataTransfer_[tp].prolongLocal(father, treeContainer[tp], xInFather, init); + }); + + finished_[mapper_.index(child)] = true; + init = false; + } + } + } + } + } + + /** Element-local data transfer on a single leaf node of the basis tree + * Handles computations related to the finite element basis node + */ + template <class GV, int k, class TP, class Container, class Basis> + class NodeDataTransfer<Dune::Functions::LagrangeNode<GV,k,TP>, Container, Basis> + { + using T = typename Container::value_type; + using LocalView = typename Basis::LocalView; + using Element = typename GV::template Codim<0>::Entity; + + using Node = typename Dune::Functions::LagrangeNode<GV,k,TP>; + using LocalBasis = typename Node::FiniteElement::Traits::LocalBasisType; + using LBRangeType = typename LocalBasis::Traits::RangeType; + using LocalInterpolation = typename Node::FiniteElement::Traits::LocalBasisType; + using LIDomainType = typename LocalInterpolation::Traits::DomainType; + using LIRangeType = typename LocalInterpolation::Traits::RangeType; + + public: + using NodeElementData = std::vector<T>; + + public: + NodeDataTransfer() = default; + + /// To be called once before cacheLocal/restrictLocal are called within the preAdapt step + void preAdaptInit(LocalView const& lv, Container const& coeff, Node const& node) + { + lv_ = &lv; + node_ = &node; + auto nodeCopy = node; + fatherNode_ = std::make_unique<Node>(std::move(nodeCopy)); + constCoeff_ = &coeff; + } + + /// To be called once before copyLocal/prolongLocal are called within the postAdapt step + void postAdaptInit(LocalView const& lv, Container& coeff, Node const& node) + { + lv_ = &lv; + node_ = &node; + auto nodeCopy = node; + fatherNode_ = std::make_unique<Node>(std::move(nodeCopy)); + coeff_ = &coeff; + } + + /// Cache data on the element bound to node_ + void cacheLocal(NodeElementData& dofs) const + { + auto const& fe = node_->finiteElement(); + auto feSize = fe.size(); + dofs.resize(feSize); + for (std::size_t i = 0; i < feSize; ++i) + dofs[i] = (*constCoeff_)[lv_->index(node_->localIndex(i))]; + } + + /** Evaluate data on the child element bound to node_ and interpolate onto father entity + * using the coordinate transformation trafo from father to child + */ + template <class Trafo> + bool restrictLocal(Element const& father, NodeElementData& fatherDOFs, Trafo const& trafo, + NodeElementData const& childDOFs, bool init); + + /// Copy already existing data to element bound to node_ + void copyLocal(NodeElementData const& dofs) const + { + for (std::size_t i = 0; i < dofs.size(); ++i) + (*coeff_)[lv_->index(node_->localIndex(i))] = dofs[i]; + } + + /** Interpolate data from father onto the child element bound to node_ using the transformation + * trafo from child to father + */ + template <class Trafo> + void prolongLocal(Element const& father, NodeElementData const& fatherDOFs, + Trafo const& trafo, bool init); + + private: + LocalView const* lv_; + Node const* node_; + std::unique_ptr<Node> fatherNode_; + Container const* constCoeff_; + Container* coeff_; + std::vector<bool> finishedDOFs_; + NodeElementData fatherDOFsTemp_; + }; + + template <class GV, int k, class TP, class Container, class Basis> + template <class Trafo> + bool NodeDataTransfer<Dune::Functions::LagrangeNode<GV,k,TP>, Container, Basis>:: + restrictLocal(Element const& father, NodeElementData& fatherDOFs, Trafo const& trafo, + NodeElementData const& childDOFs, bool init) + { + auto& fatherNode = *fatherNode_; + std::size_t currentDOF = 0; + if (init) + { + // TODO(FM): This is UB, replace with FE cache for father + bindTree(fatherNode, father); + } + auto const& childNode = *node_; + auto const& childFE = childNode.finiteElement(); + auto const& fatherFE = fatherNode.finiteElement(); + if (init) + { + finishedDOFs_.assign(fatherFE.size(), false); + fatherDOFsTemp_.assign(fatherFE.size(), 0); + } + + auto evalLeaf = [&](LIDomainType const& x) -> LIRangeType { + if (!finishedDOFs_[currentDOF]) + { + auto const& insideLocal = trafo(x); + bool isInside = insideLocal.first; + if (isInside) + { + auto const& local = insideLocal.second; + thread_local std::vector<LBRangeType> shapeValues; + childFE.localBasis().evaluateFunction(local, shapeValues); + + assert(childDOFs.size() == shapeValues.size()); + + LIRangeType y(0); + for (std::size_t i = 0; i < shapeValues.size(); ++i) + y += shapeValues[i] * childDOFs[i]; + + fatherDOFsTemp_[currentDOF] = y; + finishedDOFs_[currentDOF++] = true; + return y; + } + } + return fatherDOFsTemp_[currentDOF++]; + }; + + using FFC + = Dune::Functions::FunctionFromCallable<LIRangeType(LIDomainType), decltype(evalLeaf)>; + fatherFE.localInterpolation().interpolate(FFC(evalLeaf), fatherDOFs); + + // Return true if all father DOFs have been evaluated + return std::accumulate(finishedDOFs_.begin(), finishedDOFs_.end(), true, + std::logical_and<bool>()); + } + + template <class GV, int k, class TP, class Container, class Basis> + template <class Trafo> + void NodeDataTransfer<Dune::Functions::LagrangeNode<GV,k,TP>, Container, Basis>:: + prolongLocal(Element const& father, NodeElementData const& fatherDOFs, + Trafo const& trafo, bool init) + { + auto& fatherNode = *fatherNode_; + if (init) + { + // TODO(FM): This is UB, replace with FE cache for father + bindTree(fatherNode, father); + } + auto const& childNode = *node_; + + // evaluate father in child coordinate x + auto evalFather = [&](LIDomainType const& x) -> LIRangeType + { + thread_local std::vector<LBRangeType> shapeValues; + fatherNode.finiteElement().localBasis().evaluateFunction(trafo(x), shapeValues); + assert(shapeValues.size() == fatherDOFs.size()); + + LIRangeType y(0); + for (std::size_t i = 0; i < shapeValues.size(); ++i) + y += shapeValues[i] * fatherDOFs[i]; + + return y; + }; + + auto const& childFE = childNode.finiteElement(); + thread_local std::vector<T> childDOFs; + using FFC = Dune::Functions + ::FunctionFromCallable<LIRangeType(LIDomainType), decltype(evalFather)>; + childFE.localInterpolation().interpolate(FFC(evalFather), childDOFs); + + for (std::size_t i = 0; i < childDOFs.size(); ++i) + (*coeff_)[lv_->index(childNode.localIndex(i))] = childDOFs[i]; + } + +} // end namespace AMDiS diff --git a/src/amdis/common/Apply.hpp b/src/amdis/common/Apply.hpp new file mode 100644 index 0000000000000000000000000000000000000000..05bab973c292df65934fb2f3e4237032d93684b8 --- /dev/null +++ b/src/amdis/common/Apply.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include <tuple> +#include <type_traits> +#include <utility> + +#include <amdis/common/Mpl.hpp> + +namespace AMDiS +{ + namespace Tools + { + namespace Impl_ + { + template <class F, class Tuple, std::size_t... I> + constexpr decltype(auto) apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>) + { + return f(std::get<I>(std::forward<Tuple>(t))...); + } + + template <class F, std::size_t I0, std::size_t... I> + constexpr decltype(auto) apply_indices_impl(F&& f, index_t<I0>, std::index_sequence<I...>) + { + return f(index_t<I0+I>{}...); + } + } // namespace Impl_ + + template <class F, class Tuple> + constexpr decltype(auto) apply(F&& f, Tuple&& t) + { + return Impl_::apply_impl( + std::forward<F>(f), std::forward<Tuple>(t), + std::make_index_sequence<std::tuple_size<std::remove_reference_t<Tuple>>::value>{}); + } + + template <class F, class... Args> + constexpr decltype(auto) apply_variadic(F&& f, Args&&... args) + { + return apply(std::forward<F>(f), std::forward_as_tuple(args...)); + } + + template <class F, std::size_t N> + constexpr decltype(auto) apply_indices(F&& f, index_t<N>) + { + return Impl_::apply_indices_impl( + std::forward<F>(f), + index_t<0>{}, + std::make_index_sequence<N>{}); + } + + template <class F, std::size_t I0, std::size_t I1> + constexpr decltype(auto) apply_indices(F&& f, index_t<I0>, index_t<I1>) + { + return Impl_::apply_indices_impl( + std::forward<F>(f), index_t<I0>{}, + std::make_index_sequence<I1-I0>{}); + } + } +} // end namespace AMDiS diff --git a/src/amdis/utility/TreeContainer.hpp b/src/amdis/utility/TreeContainer.hpp new file mode 100644 index 0000000000000000000000000000000000000000..cd013a67158ab5ef5033b0d4921fb5c4898151bf --- /dev/null +++ b/src/amdis/utility/TreeContainer.hpp @@ -0,0 +1,199 @@ +#pragma once + +#include <array> +#include <functional> +#include <type_traits> +#include <utility> + +#include <dune/common/indices.hh> +#include <dune/common/tuplevector.hh> + +#include <dune/typetree/treepath.hh> + +#include <amdis/common/Apply.hpp> + +// NOTE: backport of dune/typetree/treecontainer.hh + +namespace AMDiS +{ + namespace Impl + { + /* + * \brief A factory class creating a hybrid container compatible with a type tree + * + * This class allows to create a nested hybrid container having the same structure + * as a given type tree. Power nodes are represented as std::array's while composite + * nodes are represented as Dune::TupleVector's. The stored values for the leaf nodes + * are creating using a given predicate. Once created, the factory provides an + * operator() creating the container for the tree given as argument. + * + * \tparam LeafToValue Type of a predicate that determines the stored values at the leafs + */ + template<class LeafToValue> + class ContainerFactory + { + public: + + /** + * \brief Create ContainerFactory + * + * The given predicate will be stored by value. + * + * \param A predicate used to generate the stored values for the leaves + */ + ContainerFactory(LeafToValue leafToValue) : + leafToValue_(leafToValue) + {} + + template<class Node, + std::enable_if_t<Node::isLeaf, int> = 0> + auto operator()(const Node& node) + { + return leafToValue_(node); + } + + template<class Node, + std::enable_if_t<Node::isPower, int> = 0> + auto operator()(const Node& node) + { + using TransformedChild = decltype((*this)(node.child(0))); + return std::array<TransformedChild, Node::degree()>(); + } + + template<class Node, + std::enable_if_t<Node::isComposite, int> = 0> + auto operator()(const Node& node) + { + return Tools::apply_indices([&](auto... indices) { return Dune::makeTupleVector((*this)(node.child(indices))...); }, + index_t<Node::degree()>{}); + } + + private: + LeafToValue leafToValue_; + }; + + + /* + * \brief Wrap nested container to provide a VectorBackend + */ + template<class Container> + class TreeContainerVectorBackend + { + template<class C> + static constexpr decltype(auto) accessByTreePath(C&& container, const Dune::TypeTree::HybridTreePath<>& path) + { + return container; + } + + template<class C, class... T> + static constexpr decltype(auto) accessByTreePath(C&& container, const Dune::TypeTree::HybridTreePath<T...>& path) + { + auto head = Dune::TypeTree::treePathEntry(path,Dune::Indices::_0); + auto tailPath = Tools::apply_indices([&](auto... i) + { + using namespace Dune::TypeTree; + return hybridTreePath(treePathEntry(path,index_t<i.value+1>{})...); + }, index_t<sizeof...(T)-1>{}); + return accessByTreePath(container[head], tailPath); + } + + public: + TreeContainerVectorBackend(Container&& container) : + container_(std::move(container)) + {} + + TreeContainerVectorBackend(TreeContainerVectorBackend&& other) : + container_(std::move(other.container_)) + {} + + template<class... T> + decltype(auto) operator[](const Dune::TypeTree::HybridTreePath<T...>& path) const + { + return accessByTreePath(container_, path); + } + + template<class... T> + decltype(auto) operator[](const Dune::TypeTree::HybridTreePath<T...>& path) + { + return accessByTreePath(container_, path); + } + + const Container& data() const + { + return container_; + } + + Container& data() + { + return container_; + } + + private: + Container container_; + }; + + template<class Container> + auto makeTreeContainerVectorBackend(Container&& container) + { + return TreeContainerVectorBackend<std::decay_t<Container>>(std::forward<Container>(container)); + } + + } // end namespace Impl + + /** \addtogroup TypeTree + * \{ + */ + + /** + * \brief Create container havin the same structure as the given tree + * + * This class allows to create a nested hybrid container having the same structure + * as a given type tree. Power nodes are represented as std::array's while composite + * nodes are represented as Dune::TupleVector's. The stored values for the leaf nodes + * are creating using a given predicate. For convenience the created container is + * not returned directly. Instead, the returned object stores the container and + * provides operator[] access using a HybridTreePath. + * + * \param tree The tree which should be mapper to a container + * \param leafToValue A predicate used to generate the stored values for the leaves + * + * \returns A container matching the tree structure + */ + template<class Tree, class LeafToValue> + auto makeTreeContainer(const Tree& tree, LeafToValue&& leafToValue) + { + auto f = std::ref(leafToValue); + auto factory = Impl::ContainerFactory<decltype(f)>(f); + return Impl::makeTreeContainerVectorBackend(factory(tree)); + } + + /** + * \brief Create container havin the same structure as the given tree + * + * This class allows to create a nested hybrid container having the same structure + * as a given type tree. Power nodes are represented as std::array's while composite + * nodes are represented as Dune::TupleVector's. The stored values for the leaf nodes + * are of the given type Value. For convenience the created container is + * not returned directly. Instead, the returned object stores the container and + * provides operator[] access using a HybridTreePath. + * + * \tparam Value Type of the values to be stored for the leafs. Should be default constructible. + * \param leafToValue A predicate used to generate the stored values for the leaves + * + * \returns A container matching the tree structure + */ + template<class Value, class Tree> + auto makeTreeContainer(const Tree& tree) + { + return makeTreeContainer(tree, [](const auto&) {return Value{};}); + } + + /** + * \brief Alias to container type generated by makeTreeContainer for given value and tree type + */ + template<class Value, class Tree> + using TreeContainer = std::decay_t<decltype(makeTreeContainer<Value>(std::declval<const Tree&>()))>; + + //! \} group TypeTree + +} //namespace AMDiS diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f7819bc79529290f40abf4a17a9b798a99d0f011..bb4e86298bb60fb70e9d89fff6c5d804e2706a93 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -8,6 +8,12 @@ dune_add_test(SOURCES ClonablePtrTest.cpp dune_add_test(SOURCES ConceptsTest.cpp LINK_LIBRARIES amdis) +dune_add_test(SOURCES DataTransferTest2d.cpp + LINK_LIBRARIES amdis) + +dune_add_test(SOURCES DataTransferTest3d.cpp + LINK_LIBRARIES amdis) + dune_add_test(SOURCES DOFVectorTest.cpp LINK_LIBRARIES amdis) diff --git a/test/DOFVectorTest.cpp b/test/DOFVectorTest.cpp index 2f883436d854db6ecce3ba64af3706064542b831..61721ef05a6c2e1fd3bf95b396b4c35090a5a5db 100644 --- a/test/DOFVectorTest.cpp +++ b/test/DOFVectorTest.cpp @@ -53,13 +53,13 @@ int main(int argc, char** argv) DOFVector<Basis> vec1(basis); test_dofvector(basis, vec1); - DOFVector<Basis, float> vec2(basis); + DOFVector<Basis, double> vec2(basis); test_dofvector(basis, vec2); auto vec3 = makeDOFVector(basis); test_dofvector(basis, vec3); - auto vec4 = makeDOFVector<float>(basis); + auto vec4 = makeDOFVector<double>(basis); test_dofvector(basis, vec4); #if DUNE_HAVE_CXX_CLASS_TEMPLATE_ARGUMENT_DEDUCTION diff --git a/test/DataTransferTest.hpp b/test/DataTransferTest.hpp new file mode 100644 index 0000000000000000000000000000000000000000..7006d3cd9156d2f1c0c260b955f4c8da79eeacc7 --- /dev/null +++ b/test/DataTransferTest.hpp @@ -0,0 +1,166 @@ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <array> +#include <cmath> +#include <functional> +#include <memory> +#include <vector> + +#include <dune/common/fvector.hh> +#include <dune/common/parallel/mpihelper.hh> + +#ifdef HAVE_DUNE_UGGRID +#include <dune/grid/uggrid.hh> +#else +#include <dune/grid/yaspgrid.hh> +#endif + +#include <amdis/ProblemStat.hpp> +#include <amdis/ProblemStatTraits.hpp> + +#include "Tests.hpp" + +using namespace AMDiS; + +template <class BasisCreator> +auto makeGrid(bool simplex, int globalRefines = 0) +{ + using Grid = typename BasisCreator::GlobalBasis::GridView::Grid; + static constexpr int d = Grid::dimension; // problem dimension + using DomainType = typename Dune::FieldVector<double, d>; + + // constants + DomainType lowerLeft; lowerLeft = 0.0; // lower left grid corner + DomainType upperRight; upperRight = 1.0; // upper right grid corner + std::array<unsigned int, d> s; s.fill(1); // number of elements on each axis + + // make grid + std::unique_ptr<Grid> gridPtr; + if (simplex) + { + gridPtr = std::unique_ptr<Grid>{ + Dune::StructuredGridFactory<Grid>::createSimplexGrid(lowerLeft, upperRight, s)}; + } + else + { + gridPtr = std::unique_ptr<Grid>{ + Dune::StructuredGridFactory<Grid>::createCubeGrid(lowerLeft, upperRight, s)}; + } + gridPtr->globalRefine(globalRefines); + + return gridPtr; +} + +template <class BasisCreator, class Fcts> +auto makeProblem(typename BasisCreator::GlobalBasis::GridView::Grid& grid, Fcts const& funcs) +{ + using Problem = ProblemStat<BasisCreator>; + + // make problem + Problem prob("test", grid); + prob.initialize(INIT_ALL); + + auto& globalBasis = prob.globalBasis(); + auto localView = globalBasis.localView(); + + // interpolate given function to initial grid + int k = 0; + AMDiS::forEachLeafNode_(localView.tree(), [&](auto const& node, auto tp) + { + interpolate(globalBasis, tp, prob.solution(tp).coefficients(), funcs[k]); + k++; + }); + + return prob; +} + +template <class Problem, class Fcts> +double calcError(Problem const& prob, Fcts const& funcs) +{ + auto& globalBasis = prob.globalBasis(); + auto localView = globalBasis.localView(); + auto sol = prob.solution().coefficients(); + std::vector<double> ref; + ref.resize(globalBasis.size()); + double error = 0; + double maxError = 0; + int k = 0; + + // interpolate given function onto reference vector + AMDiS::forEachLeafNode_(localView.tree(), [&](auto const& node, auto tp) + { + interpolate(globalBasis, tp, ref, funcs[k]); + k++; + }); + + // compare the solution with the reference + for (std::size_t i = 0; i < ref.size(); ++i) + { + error = std::abs(ref[i]-sol[i]); + maxError = std::max(maxError, error); + } + return maxError; +} + +template <class BasisCreator> + using Fcts = std::vector<std::function<double( + Dune::FieldVector<double, BasisCreator::GlobalBasis::GridView::Grid::dimension>)> >; + +/// Test data transfer for the case where no grid changes are made +template <class BasisCreator> +bool unchanged_test(Fcts<BasisCreator> const& funcs, bool simplex = true) +{ + using Grid = typename BasisCreator::GlobalBasis::GridView::Grid; + static constexpr int d = Grid::dimension; // problem dimension + + auto gridPtr = makeGrid<BasisCreator>(simplex, (d > 2 ? 3 : 5)); + auto prob = makeProblem<BasisCreator, Fcts<BasisCreator>>(*gridPtr, funcs); + // mark a single element for coarsening -> no adaptation is done + auto e = *gridPtr->leafGridView().template begin<0>(); + gridPtr->mark(-1, e); + AdaptInfo adaptInfo("adapt"); + prob.adaptGrid(adaptInfo); + auto error = calcError(prob, funcs); + + return error < AMDIS_TEST_TOL; +} + +/// Test data transfer for the case of global coarsening +template <class BasisCreator> +bool coarsen_test(Fcts<BasisCreator> const& funcs, bool simplex = true) +{ + using Grid = typename BasisCreator::GlobalBasis::GridView::Grid; + static constexpr int d = Grid::dimension; // problem dimension + + auto gridPtr = makeGrid<BasisCreator>(simplex, (d > 2 ? 2 : 4)); + auto prob = makeProblem<BasisCreator, Fcts<BasisCreator>>(*gridPtr, funcs); + prob.globalCoarsen(1); + auto error = calcError(prob, funcs); + + return error < AMDIS_TEST_TOL; +} + +/// Test data transfer for the case of global refinement +template <class BasisCreator> +bool refine_test(Fcts<BasisCreator> const& funcs, bool simplex = true) +{ + using Grid = typename BasisCreator::GlobalBasis::GridView::Grid; + static constexpr int d = Grid::dimension; // problem dimension + + auto gridPtr = makeGrid<BasisCreator>(simplex, (d > 2 ? 1 : 3)); + auto prob = makeProblem<BasisCreator, Fcts<BasisCreator>>(*gridPtr, funcs); + prob.globalRefine(1); + auto error = calcError(prob, funcs); + + return error < AMDIS_TEST_TOL; +} + +template <class Grid> + using Lagrange3 = LagrangeBasis<typename Grid::LeafGridView, 3>; +template <class Grid> + using TaylorHood = TaylorHoodBasis<typename Grid::LeafGridView>; + + constexpr double pi = 3.141592653589793238463; diff --git a/test/DataTransferTest2d.cpp b/test/DataTransferTest2d.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2f4e92b2f8393fb63a4f46610ff8f68d6fb773e3 --- /dev/null +++ b/test/DataTransferTest2d.cpp @@ -0,0 +1,37 @@ + +#include "DataTransferTest.hpp" + +int main(int argc, char** argv) +{ +#ifdef HAVE_DUNE_UGGRID + using Grid = Dune::UGGrid<2>; +#else + using Grid = Dune::YaspGrid<2>; +#endif + + Dune::MPIHelper::instance(argc, argv); + + using Domain = typename Dune::FieldVector<double, 2>; + + // polynomial of order 1 + auto p1 = [](const Domain& x) -> double { return {0.5 + 0.25*(x[0]+x[1])}; }; + + // polynomial of order 2 + auto p2 = [](const Domain& x) -> double { return {0.5 + 0.25*(2*std::pow(x[0],2) + x[0]*x[1] - std::pow(x[1],2))}; }; + + // polynomial of order 3 + auto p3 = [](const Domain& x) -> double { return {0.5 + 0.25*(2*std::pow(x[0],3) + x[0]*x[1] - std::pow(x[1],3))}; }; + + // analytic function + auto f = [](const Domain& x) -> double { return {0.5 + 0.25*(std::sin(2*pi*x[0]) + 0.25*std::cos(6*pi*x[1]))}; }; + + AMDIS_TEST( (unchanged_test< Lagrange3<Grid> >({f})) ); + AMDIS_TEST( (coarsen_test< Lagrange3<Grid> >({f})) ); + AMDIS_TEST( (refine_test< Lagrange3<Grid> >({p3})) ); + + AMDIS_TEST( (unchanged_test< TaylorHood<Grid> >({f,f,f})) ); + AMDIS_TEST( (coarsen_test< TaylorHood<Grid> >({f,f,f})) ); + AMDIS_TEST( (refine_test< TaylorHood<Grid> >({p2,p2,p1})) ); + + return report_errors(); +} \ No newline at end of file diff --git a/test/DataTransferTest3d.cpp b/test/DataTransferTest3d.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e1b818c7e6d20564419f6a6e81b27c724ee121ba --- /dev/null +++ b/test/DataTransferTest3d.cpp @@ -0,0 +1,37 @@ + +#include "DataTransferTest.hpp" + +int main(int argc, char** argv) +{ +#ifdef HAVE_DUNE_UGGRID + using Grid = Dune::UGGrid<3>; +#else + using Grid = Dune::YaspGrid<3>; +#endif + + Dune::MPIHelper::instance(argc, argv); + + using Domain = typename Dune::FieldVector<double, 3>; + + // polynomial of order 1 + auto p1 = [](const Domain& x) -> double { return {0.5 + 0.25*(x[0]+x[2])}; }; + + // polynomial of order 2 + auto p2 = [](const Domain& x) -> double { return {0.5 + 0.25*(2*std::pow(x[0],2) + x[0]*x[1] - std::pow(x[2],2))}; }; + + // polynomial of order 3 + auto p3 = [](const Domain& x) -> double { return {0.5 + 0.25*(2*std::pow(x[0],3) + x[0]*x[1] - std::pow(x[2],3))}; }; + + // analytic function + auto f = [](const Domain& x) -> double { return {0.5 + 0.25*(std::sin(2*pi*x[0]) + 0.25*std::cos(6*pi*x[2]))}; }; + + AMDIS_TEST( (unchanged_test< Lagrange3<Grid> >({f})) ); + AMDIS_TEST( (coarsen_test< Lagrange3<Grid> >({f})) ); + AMDIS_TEST( (refine_test< Lagrange3<Grid> >({p3})) ); + + AMDIS_TEST( (unchanged_test< TaylorHood<Grid> >({f,f,f,f})) ); + AMDIS_TEST( (coarsen_test< TaylorHood<Grid> >({f,f,f,f})) ); + AMDIS_TEST( (refine_test< TaylorHood<Grid> >({p2,p2,p2,p1})) ); + + return report_errors(); +} \ No newline at end of file diff --git a/test/DiscreteFunctionTest.cpp b/test/DiscreteFunctionTest.cpp index 0862d4ff73649eeea0814577fc15b4d5b35765db..14419ce8a50a1991aaa93cd52f6829a65c16b104 100644 --- a/test/DiscreteFunctionTest.cpp +++ b/test/DiscreteFunctionTest.cpp @@ -97,7 +97,7 @@ int main(int argc, char** argv) dlocalFct.unbind(); } - auto V0 = makeDOFVector<float>(prob.globalBasis()); + auto V0 = makeDOFVector<double>(prob.globalBasis()); auto v0 = makeDOFVectorView(V0); v0 << expr;