From be75689e462e78705da032a5f501b2960e6c4314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Del=20Pino?= <stephane.delpino44@gmail.com> Date: Wed, 1 Dec 2021 18:58:36 +0100 Subject: [PATCH] Add CellIntegrator This allows to compute numerical quadrature of C++ functions on all the mesh cells or on a given set of cells. --- src/scheme/CellIntegrator.hpp | 554 +++++++++++++++++++++++++ tests/CMakeLists.txt | 1 + tests/test_CellIntegrator.cpp | 751 ++++++++++++++++++++++++++++++++++ 3 files changed, 1306 insertions(+) create mode 100644 src/scheme/CellIntegrator.hpp create mode 100644 tests/test_CellIntegrator.cpp diff --git a/src/scheme/CellIntegrator.hpp b/src/scheme/CellIntegrator.hpp new file mode 100644 index 000000000..51758560a --- /dev/null +++ b/src/scheme/CellIntegrator.hpp @@ -0,0 +1,554 @@ +#ifndef CELL_INTEGRATOR_HPP +#define CELL_INTEGRATOR_HPP + +#include <analysis/IQuadratureDescriptor.hpp> +#include <analysis/QuadratureManager.hpp> +#include <geometry/CubeTransformation.hpp> +#include <geometry/LineTransformation.hpp> +#include <geometry/PrismTransformation.hpp> +#include <geometry/PyramidTransformation.hpp> +#include <geometry/SquareTransformation.hpp> +#include <geometry/TetrahedronTransformation.hpp> +#include <geometry/TriangleTransformation.hpp> +#include <mesh/CellType.hpp> +#include <mesh/Connectivity.hpp> +#include <mesh/ItemId.hpp> +#include <utils/Array.hpp> + +#include <functional> + +class CellIntegrator +{ + private: + template <size_t Dimension> + class AllCells + { + private: + const Connectivity<Dimension>& m_connectivity; + + public: + using index_type = CellId; + + PUGS_INLINE + CellId + cellId(const CellId cell_id) const + { + return cell_id; + } + + PUGS_INLINE + size_t + size() const + { + return m_connectivity.numberOfCells(); + } + + PUGS_INLINE + AllCells(const Connectivity<Dimension>& connectivity) : m_connectivity{connectivity} {} + }; + + template <template <typename T> typename ArrayT> + class CellList + { + private: + const ArrayT<CellId>& m_cell_list; + + public: + using index_type = typename ArrayT<CellId>::index_type; + + PUGS_INLINE + CellId + cellId(const index_type index) const + { + return m_cell_list[index]; + } + + PUGS_INLINE + size_t + size() const + { + return m_cell_list.size(); + } + + PUGS_INLINE + CellList(const ArrayT<CellId>& cell_list) : m_cell_list{cell_list} {} + }; + + template <typename MeshType, typename OutputArrayT, typename ListTypeT, typename OutputType, typename InputType> + static PUGS_INLINE void + _tensorialIntegrateTo(std::function<OutputType(InputType)> function, + const IQuadratureDescriptor& quadrature_descriptor, + const MeshType& mesh, + const ListTypeT& cell_list, + OutputArrayT& value) + { + constexpr size_t Dimension = MeshType::Dimension; + + static_assert(std::is_same_v<TinyVector<Dimension>, std::decay_t<InputType>>, "invalid input data type"); + static_assert(std::is_same_v<std::remove_const_t<typename OutputArrayT::data_type>, OutputType>, + "invalid output data type"); + + using execution_space = typename Kokkos::DefaultExecutionSpace::execution_space; + Kokkos::Experimental::UniqueToken<execution_space, Kokkos::Experimental::UniqueTokenScope::Global> tokens; + + if constexpr (std::is_arithmetic_v<OutputType>) { + value.fill(0); + } else if constexpr (is_tiny_vector_v<OutputType> or is_tiny_matrix_v<OutputType>) { + value.fill(zero); + } else { + static_assert(std::is_same_v<OutputType, double>, "unexpected output type"); + } + + const auto& connectivity = mesh.connectivity(); + + const auto cell_to_node_matrix = connectivity.cellToNodeMatrix(); + const auto cell_type = connectivity.cellType(); + const auto xr = mesh.xr(); + + auto invalid_cell = std::make_pair(false, CellId{}); + + const auto qf = [&] { + if constexpr (Dimension == 1) { + return QuadratureManager::instance().getLineFormula(quadrature_descriptor); + } else if constexpr (Dimension == 2) { + return QuadratureManager::instance().getSquareFormula(quadrature_descriptor); + } else { + static_assert(Dimension == 3); + return QuadratureManager::instance().getCubeFormula(quadrature_descriptor); + } + }(); + + parallel_for(cell_list.size(), [=, &invalid_cell, &qf, &cell_list](typename ListTypeT::index_type index) { + const CellId cell_id = cell_list.cellId(index); + + const auto cell_to_node = cell_to_node_matrix[cell_id]; + + if constexpr (Dimension == 1) { + switch (cell_type[cell_id]) { + case CellType::Line: { + const LineTransformation<1> T(xr[cell_to_node[0]], xr[cell_to_node[1]]); + + for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) { + const auto xi = qf.point(i_point); + value[index] += qf.weight(i_point) * T.jacobianDeterminant() * function(T(xi)); + } + break; + } + default: { + invalid_cell = std::make_pair(true, cell_id); + break; + } + } + } else if constexpr (Dimension == 2) { + switch (cell_type[cell_id]) { + case CellType::Triangle: { + const SquareTransformation<2> T(xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]], + xr[cell_to_node[2]]); + + for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) { + const auto xi = qf.point(i_point); + value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * function(T(xi)); + } + break; + } + case CellType::Quadrangle: { + const SquareTransformation<2> T(xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]], + xr[cell_to_node[3]]); + + for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) { + const auto xi = qf.point(i_point); + value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * function(T(xi)); + } + break; + } + default: { + invalid_cell = std::make_pair(true, cell_id); + break; + } + } + } else { + static_assert(Dimension == 3); + + switch (cell_type[cell_id]) { + case CellType::Tetrahedron: { + const CubeTransformation T(xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]], + xr[cell_to_node[2]], // + xr[cell_to_node[3]], xr[cell_to_node[3]], xr[cell_to_node[3]], + xr[cell_to_node[3]]); + + for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) { + const auto xi = qf.point(i_point); + value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * function(T(xi)); + } + break; + } + case CellType::Pyramid: { + if (cell_to_node.size() == 5) { + const CubeTransformation T(xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]], // + xr[cell_to_node[3]], // + xr[cell_to_node[4]], xr[cell_to_node[4]], xr[cell_to_node[4]], + xr[cell_to_node[4]]); + + for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) { + const auto xi = qf.point(i_point); + value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * function(T(xi)); + } + } else { + throw NotImplementedError("integration on pyramid with non-quadrangular base (number of nodes " + + std::to_string(cell_to_node.size()) + ")"); + } + break; + } + case CellType::Diamond: { + if (cell_to_node.size() == 5) { + { // top tetrahedron + const CubeTransformation T0(xr[cell_to_node[1]], xr[cell_to_node[2]], xr[cell_to_node[3]], + xr[cell_to_node[3]], // + xr[cell_to_node[4]], xr[cell_to_node[4]], xr[cell_to_node[4]], + xr[cell_to_node[4]]); + + for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) { + const auto xi = qf.point(i_point); + value[index] += qf.weight(i_point) * T0.jacobianDeterminant(xi) * function(T0(xi)); + } + } + { // bottom tetrahedron + const CubeTransformation T1(xr[cell_to_node[3]], xr[cell_to_node[3]], xr[cell_to_node[2]], + xr[cell_to_node[1]], // + xr[cell_to_node[0]], xr[cell_to_node[0]], xr[cell_to_node[0]], + xr[cell_to_node[0]]); + + for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) { + const auto xi = qf.point(i_point); + value[index] += qf.weight(i_point) * T1.jacobianDeterminant(xi) * function(T1(xi)); + } + } + } else if (cell_to_node.size() == 6) { + { // top pyramid + const CubeTransformation T0(xr[cell_to_node[1]], xr[cell_to_node[2]], xr[cell_to_node[3]], + xr[cell_to_node[4]], // + xr[cell_to_node[5]], xr[cell_to_node[5]], xr[cell_to_node[5]], + xr[cell_to_node[5]]); + + for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) { + const auto xi = qf.point(i_point); + value[index] += qf.weight(i_point) * T0.jacobianDeterminant(xi) * function(T0(xi)); + } + } + { // bottom pyramid + const CubeTransformation T1(xr[cell_to_node[4]], xr[cell_to_node[3]], xr[cell_to_node[2]], + xr[cell_to_node[1]], // + xr[cell_to_node[0]], xr[cell_to_node[0]], xr[cell_to_node[0]], + xr[cell_to_node[0]]); + + for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) { + const auto xi = qf.point(i_point); + value[index] += qf.weight(i_point) * T1.jacobianDeterminant(xi) * function(T1(xi)); + } + } + } else { + throw NotImplementedError("integration on diamond with non-quadrangular base (" + + std::to_string(cell_to_node.size()) + ")"); + } + break; + } + case CellType::Prism: { + const CubeTransformation T(xr[cell_to_node[0]], xr[cell_to_node[1]], // + xr[cell_to_node[2]], xr[cell_to_node[2]], // + xr[cell_to_node[3]], xr[cell_to_node[4]], // + xr[cell_to_node[5]], xr[cell_to_node[5]]); + + for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) { + const auto xi = qf.point(i_point); + value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * function(T(xi)); + } + break; + } + case CellType::Hexahedron: { + const CubeTransformation T(xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]], xr[cell_to_node[3]], + xr[cell_to_node[4]], xr[cell_to_node[5]], xr[cell_to_node[6]], + xr[cell_to_node[7]]); + + for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) { + const auto xi = qf.point(i_point); + value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * function(T(xi)); + } + break; + } + default: { + invalid_cell = std::make_pair(true, cell_id); + break; + } + } + } + }); + + if (invalid_cell.first) { + std::ostringstream os; + os << "invalid cell type for integration: " << name(cell_type[invalid_cell.second]); + throw UnexpectedError(os.str()); + } + } + + template <typename MeshType, typename OutputArrayT, typename ListTypeT, typename OutputType, typename InputType> + static PUGS_INLINE void + _directIntegrateTo(std::function<OutputType(InputType)> function, + const IQuadratureDescriptor& quadrature_descriptor, + const MeshType& mesh, + const ListTypeT& cell_list, + OutputArrayT& value) + { + Assert(not quadrature_descriptor.isTensorial()); + + constexpr size_t Dimension = MeshType::Dimension; + + static_assert(std::is_same_v<TinyVector<Dimension>, std::decay_t<InputType>>, "invalid input data type"); + static_assert(std::is_same_v<std::remove_const_t<typename OutputArrayT::data_type>, OutputType>, + "invalid output data type"); + + if constexpr (std::is_arithmetic_v<OutputType>) { + value.fill(0); + } else if constexpr (is_tiny_vector_v<OutputType> or is_tiny_matrix_v<OutputType>) { + value.fill(zero); + } else { + static_assert(std::is_same_v<OutputType, double>, "unexpected output type"); + } + + const auto& connectivity = mesh.connectivity(); + + const auto cell_to_node_matrix = connectivity.cellToNodeMatrix(); + const auto cell_type = connectivity.cellType(); + const auto xr = mesh.xr(); + + auto invalid_cell = std::make_pair(false, CellId{}); + + parallel_for(cell_list.size(), [=, &invalid_cell, &quadrature_descriptor, + &cell_list](const typename ListTypeT::index_type& index) { + const CellId cell_id = cell_list.cellId(index); + + const auto cell_to_node = cell_to_node_matrix[cell_id]; + + if constexpr (Dimension == 1) { + switch (cell_type[cell_id]) { + case CellType::Line: { + const LineTransformation<1> T(xr[cell_to_node[0]], xr[cell_to_node[1]]); + const auto qf = QuadratureManager::instance().getLineFormula(quadrature_descriptor); + + for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) { + const auto xi = qf.point(i_point); + value[index] += qf.weight(i_point) * T.jacobianDeterminant() * function(T(xi)); + } + break; + } + default: { + invalid_cell = std::make_pair(true, cell_id); + break; + } + } + } else if constexpr (Dimension == 2) { + switch (cell_type[cell_id]) { + case CellType::Triangle: { + const TriangleTransformation<2> T(xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]]); + const auto qf = QuadratureManager::instance().getTriangleFormula(quadrature_descriptor); + + for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) { + const auto xi = qf.point(i_point); + value[index] += qf.weight(i_point) * T.jacobianDeterminant() * function(T(xi)); + } + break; + } + case CellType::Quadrangle: { + const SquareTransformation<2> T(xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]], + xr[cell_to_node[3]]); + const auto qf = QuadratureManager::instance().getSquareFormula(quadrature_descriptor); + + for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) { + const auto xi = qf.point(i_point); + value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * function(T(xi)); + } + break; + } + default: { + invalid_cell = std::make_pair(true, cell_id); + break; + } + } + } else { + static_assert(Dimension == 3); + + switch (cell_type[cell_id]) { + case CellType::Tetrahedron: { + const TetrahedronTransformation T(xr[cell_to_node[0]], xr[cell_to_node[1]], // + xr[cell_to_node[2]], xr[cell_to_node[3]]); + const auto qf = QuadratureManager::instance().getTetrahedronFormula(quadrature_descriptor); + + for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) { + const auto xi = qf.point(i_point); + value[index] += qf.weight(i_point) * T.jacobianDeterminant() * function(T(xi)); + } + break; + } + case CellType::Pyramid: { + if (cell_to_node.size() == 5) { + const PyramidTransformation T(xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]], // + xr[cell_to_node[3]], xr[cell_to_node[4]]); + const auto qf = QuadratureManager::instance().getPyramidFormula(quadrature_descriptor); + + for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) { + const auto xi = qf.point(i_point); + value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * function(T(xi)); + } + } else { + throw NotImplementedError("integration on pyramid with non-quadrangular base"); + } + break; + } + case CellType::Diamond: { + if (cell_to_node.size() == 5) { + const auto qf = QuadratureManager::instance().getTetrahedronFormula(quadrature_descriptor); + { // top tetrahedron + const TetrahedronTransformation T0(xr[cell_to_node[1]], xr[cell_to_node[2]], xr[cell_to_node[3]], // + xr[cell_to_node[4]]); + + for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) { + const auto xi = qf.point(i_point); + value[index] += qf.weight(i_point) * T0.jacobianDeterminant() * function(T0(xi)); + } + } + { // bottom tetrahedron + const TetrahedronTransformation T1(xr[cell_to_node[3]], xr[cell_to_node[2]], xr[cell_to_node[1]], // + xr[cell_to_node[0]]); + + for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) { + const auto xi = qf.point(i_point); + value[index] += qf.weight(i_point) * T1.jacobianDeterminant() * function(T1(xi)); + } + } + } else if (cell_to_node.size() == 6) { + const auto qf = QuadratureManager::instance().getCubeFormula(quadrature_descriptor); + { // top pyramid + const CubeTransformation T0(xr[cell_to_node[1]], xr[cell_to_node[2]], xr[cell_to_node[3]], + xr[cell_to_node[4]], // + xr[cell_to_node[5]], xr[cell_to_node[5]], xr[cell_to_node[5]], + xr[cell_to_node[5]]); + + for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) { + const auto xi = qf.point(i_point); + value[index] += qf.weight(i_point) * T0.jacobianDeterminant(xi) * function(T0(xi)); + } + } + { // bottom pyramid + const CubeTransformation T1(xr[cell_to_node[4]], xr[cell_to_node[3]], xr[cell_to_node[2]], + xr[cell_to_node[1]], // + xr[cell_to_node[0]], xr[cell_to_node[0]], xr[cell_to_node[0]], + xr[cell_to_node[0]]); + + for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) { + const auto xi = qf.point(i_point); + value[index] += qf.weight(i_point) * T1.jacobianDeterminant(xi) * function(T1(xi)); + } + } + } else { + throw NotImplementedError("integration on diamond with non-quadrangular base (" + + std::to_string(cell_to_node.size()) + ")"); + } + break; + } + case CellType::Prism: { + const PrismTransformation T(xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]], + xr[cell_to_node[3]], xr[cell_to_node[4]], xr[cell_to_node[5]]); + const auto qf = QuadratureManager::instance().getPrismFormula(quadrature_descriptor); + + for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) { + const auto xi = qf.point(i_point); + value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * function(T(xi)); + } + break; + } + case CellType::Hexahedron: { + const CubeTransformation T(xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]], xr[cell_to_node[3]], + xr[cell_to_node[4]], xr[cell_to_node[5]], xr[cell_to_node[6]], + xr[cell_to_node[7]]); + const auto qf = QuadratureManager::instance().getCubeFormula(quadrature_descriptor); + + for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) { + const auto xi = qf.point(i_point); + value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * function(T(xi)); + } + break; + } + default: { + invalid_cell = std::make_pair(true, cell_id); + break; + } + } + } + }); + + if (invalid_cell.first) { + std::ostringstream os; + os << "invalid cell type for integration: " << name(cell_type[invalid_cell.second]); + throw UnexpectedError(os.str()); + } + } + + public: + template <typename MeshType, typename OutputArrayT, typename OutputType, typename InputType> + static PUGS_INLINE void + integrateTo(const std::function<OutputType(InputType)>& function, + const IQuadratureDescriptor& quadrature_descriptor, + const MeshType& mesh, + OutputArrayT& value) + { + constexpr size_t Dimension = MeshType::Dimension; + + if (quadrature_descriptor.isTensorial()) { + _tensorialIntegrateTo<MeshType, OutputArrayT>(function, quadrature_descriptor, mesh, + AllCells<Dimension>{mesh.connectivity()}, value); + } else { + _directIntegrateTo<MeshType, OutputArrayT>(function, quadrature_descriptor, mesh, + AllCells<Dimension>{mesh.connectivity()}, value); + } + } + + template <typename MeshType, typename OutputArrayT, typename FunctionType> + static PUGS_INLINE void + integrateTo(const FunctionType& function, + const IQuadratureDescriptor& quadrature_descriptor, + const MeshType& mesh, + OutputArrayT& value) + { + integrateTo(std::function(function), quadrature_descriptor, mesh, value); + } + + template <typename MeshType, typename OutputType, typename InputType, template <typename DataType> typename ArrayT> + static PUGS_INLINE ArrayT<OutputType> + integrate(const std::function<OutputType(InputType)>& function, + const IQuadratureDescriptor& quadrature_descriptor, + const MeshType& mesh, + const ArrayT<CellId>& cell_list) + { + ArrayT<OutputType> value(size(cell_list)); + if (quadrature_descriptor.isTensorial()) { + _tensorialIntegrateTo<MeshType, ArrayT<OutputType>>(function, quadrature_descriptor, mesh, CellList{cell_list}, + value); + } else { + _directIntegrateTo<MeshType, ArrayT<OutputType>>(function, quadrature_descriptor, mesh, CellList{cell_list}, + value); + } + + return value; + } + + template <typename MeshType, typename FunctionType, template <typename DataType> typename ArrayT> + static PUGS_INLINE auto + integrate(const FunctionType& function, + const IQuadratureDescriptor& quadrature_descriptor, + const MeshType& mesh, + const ArrayT<CellId>& cell_list) + { + return integrate(std::function(function), quadrature_descriptor, mesh, cell_list); + } +}; + +#endif // CELL_INTEGRATOR_HPP diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0994985c3..7f4c6a323 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -127,6 +127,7 @@ add_executable (unit_tests add_executable (mpi_unit_tests mpi_test_main.cpp + test_CellIntegrator.cpp test_DiscreteFunctionInterpoler.cpp test_DiscreteFunctionP0.cpp test_DiscreteFunctionP0Vector.cpp diff --git a/tests/test_CellIntegrator.cpp b/tests/test_CellIntegrator.cpp new file mode 100644 index 000000000..ef0fca0b9 --- /dev/null +++ b/tests/test_CellIntegrator.cpp @@ -0,0 +1,751 @@ +#include <catch2/catch_approx.hpp> +#include <catch2/catch_test_macros.hpp> + +#include <analysis/GaussLobattoQuadratureDescriptor.hpp> +#include <analysis/GaussQuadratureDescriptor.hpp> +#include <mesh/ItemValue.hpp> +#include <mesh/Mesh.hpp> +#include <scheme/CellIntegrator.hpp> + +#include <MeshDataBaseForTests.hpp> + +// clazy:excludeall=non-pod-global-static + +TEST_CASE("CellIntegrator", "[scheme]") +{ + SECTION("1D") + { + using R1 = TinyVector<1>; + + const auto mesh = MeshDataBaseForTests::get().unordered1DMesh(); + auto f = [](const R1& x) -> double { return x[0] * x[0] + 1; }; + + Array<const double> int_f_per_cell = [=] { + Array<double> int_f(mesh->numberOfCells()); + auto cell_to_node_matrix = mesh->connectivity().cellToNodeMatrix(); + parallel_for( + mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { + auto cell_node_list = cell_to_node_matrix[cell_id]; + auto xr = mesh->xr(); + const double x_left = xr[cell_node_list[0]][0]; + const double x_right = xr[cell_node_list[1]][0]; + int_f[cell_id] = 1. / 3 * (x_right * x_right * x_right - x_left * x_left * x_left) + x_right - x_left; + }); + + return int_f; + }(); + + SECTION("direct formula") + { + SECTION("all cells") + { + SECTION("CellValue") + { + CellValue<double> values(mesh->connectivity()); + CellIntegrator::integrateTo([=](const R1 x) { return f(x); }, GaussQuadratureDescriptor(2), *mesh, values); + + double error = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) { + error += std::abs(int_f_per_cell[cell_id] - values[cell_id]); + } + + REQUIRE(error == Catch::Approx(0).margin(1E-10)); + } + + SECTION("Array") + { + Array<double> values(mesh->numberOfCells()); + + CellIntegrator::integrateTo(f, GaussQuadratureDescriptor(2), *mesh, values); + + double error = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) { + error += std::abs(int_f_per_cell[cell_id] - values[cell_id]); + } + + REQUIRE(error == Catch::Approx(0).margin(1E-10)); + } + + SECTION("SmallArray") + { + SmallArray<double> values(mesh->numberOfCells()); + CellIntegrator::integrateTo(f, GaussQuadratureDescriptor(2), *mesh, values); + + double error = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) { + error += std::abs(int_f_per_cell[cell_id] - values[cell_id]); + } + + REQUIRE(error == Catch::Approx(0).margin(1E-10)); + } + } + + SECTION("cell list") + { + SECTION("Array") + { + Array<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 2}; + + { + size_t k = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) { + cell_list[k] = cell_id; + } + + REQUIRE(k == cell_list.size()); + } + + Array<double> values = CellIntegrator::integrate(f, GaussQuadratureDescriptor(2), *mesh, cell_list); + + double error = 0; + for (size_t i = 0; i < cell_list.size(); ++i) { + error += std::abs(int_f_per_cell[cell_list[i]] - values[i]); + } + + REQUIRE(error == Catch::Approx(0).margin(1E-10)); + } + + SECTION("SmallArray") + { + SmallArray<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 2}; + + { + size_t k = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) { + cell_list[k] = cell_id; + } + + REQUIRE(k == cell_list.size()); + } + + SmallArray<double> values = CellIntegrator::integrate(f, GaussQuadratureDescriptor(2), *mesh, cell_list); + + double error = 0; + for (size_t i = 0; i < cell_list.size(); ++i) { + error += std::abs(int_f_per_cell[cell_list[i]] - values[i]); + } + + REQUIRE(error == Catch::Approx(0).margin(1E-10)); + } + } + } + + SECTION("tensorial formula") + { + SECTION("all cells") + { + SECTION("CellValue") + { + CellValue<double> values(mesh->connectivity()); + CellIntegrator::integrateTo([=](const R1 x) { return f(x); }, GaussLobattoQuadratureDescriptor(2), *mesh, + values); + + double error = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) { + error += std::abs(int_f_per_cell[cell_id] - values[cell_id]); + } + + REQUIRE(error == Catch::Approx(0).margin(1E-10)); + } + + SECTION("Array") + { + Array<double> values(mesh->numberOfCells()); + + CellIntegrator::integrateTo(f, GaussLobattoQuadratureDescriptor(2), *mesh, values); + + double error = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) { + error += std::abs(int_f_per_cell[cell_id] - values[cell_id]); + } + + REQUIRE(error == Catch::Approx(0).margin(1E-10)); + } + + SECTION("SmallArray") + { + SmallArray<double> values(mesh->numberOfCells()); + CellIntegrator::integrateTo(f, GaussLobattoQuadratureDescriptor(2), *mesh, values); + + double error = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) { + error += std::abs(int_f_per_cell[cell_id] - values[cell_id]); + } + + REQUIRE(error == Catch::Approx(0).margin(1E-10)); + } + } + + SECTION("cell list") + { + SECTION("Array") + { + Array<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 2}; + + { + size_t k = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) { + cell_list[k] = cell_id; + } + + REQUIRE(k == cell_list.size()); + } + + Array<double> values = CellIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(2), *mesh, cell_list); + + double error = 0; + for (size_t i = 0; i < cell_list.size(); ++i) { + error += std::abs(int_f_per_cell[cell_list[i]] - values[i]); + } + + REQUIRE(error == Catch::Approx(0).margin(1E-10)); + } + + SECTION("SmallArray") + { + SmallArray<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 2}; + + { + size_t k = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) { + cell_list[k] = cell_id; + } + + REQUIRE(k == cell_list.size()); + } + + SmallArray<double> values = + CellIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(2), *mesh, cell_list); + + double error = 0; + for (size_t i = 0; i < cell_list.size(); ++i) { + error += std::abs(int_f_per_cell[cell_list[i]] - values[i]); + } + + REQUIRE(error == Catch::Approx(0).margin(1E-10)); + } + } + } + } + + SECTION("2D") + { + using R2 = TinyVector<2>; + + const auto mesh = MeshDataBaseForTests::get().hybrid2DMesh(); + + auto f = [](const R2& X) -> double { + const double x = X[0]; + const double y = X[1]; + return x * x + 2 * x * y + 3 * y * y + 2; + }; + + Array<const double> int_f_per_cell = [=] { + Array<double> int_f(mesh->numberOfCells()); + auto cell_to_node_matrix = mesh->connectivity().cellToNodeMatrix(); + auto cell_type = mesh->connectivity().cellType(); + + parallel_for( + mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { + auto cell_node_list = cell_to_node_matrix[cell_id]; + auto xr = mesh->xr(); + double integral = 0; + + switch (cell_type[cell_id]) { + case CellType::Triangle: { + TriangleTransformation<2> T(xr[cell_node_list[0]], xr[cell_node_list[1]], xr[cell_node_list[2]]); + auto qf = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(4)); + + for (size_t i = 0; i < qf.numberOfPoints(); ++i) { + const auto& xi = qf.point(i); + integral += qf.weight(i) * T.jacobianDeterminant() * f(T(xi)); + } + break; + } + case CellType::Quadrangle: { + SquareTransformation<2> T(xr[cell_node_list[0]], xr[cell_node_list[1]], xr[cell_node_list[2]], + xr[cell_node_list[3]]); + auto qf = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor(4)); + + for (size_t i = 0; i < qf.numberOfPoints(); ++i) { + const auto& xi = qf.point(i); + integral += qf.weight(i) * T.jacobianDeterminant(xi) * f(T(xi)); + } + break; + } + default: { + throw UnexpectedError("invalid cell type in 2d"); + } + } + int_f[cell_id] = integral; + }); + + return int_f; + }(); + + SECTION("direct formula") + { + SECTION("all cells") + { + SECTION("CellValue") + { + CellValue<double> values(mesh->connectivity()); + CellIntegrator::integrateTo([=](const R2 x) { return f(x); }, GaussQuadratureDescriptor(2), *mesh, values); + + double error = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) { + error += std::abs(int_f_per_cell[cell_id] - values[cell_id]); + } + + REQUIRE(error == Catch::Approx(0).margin(1E-10)); + } + + SECTION("Array") + { + Array<double> values(mesh->numberOfCells()); + + CellIntegrator::integrateTo(f, GaussQuadratureDescriptor(2), *mesh, values); + + double error = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) { + error += std::abs(int_f_per_cell[cell_id] - values[cell_id]); + } + + REQUIRE(error == Catch::Approx(0).margin(1E-10)); + } + + SECTION("SmallArray") + { + SmallArray<double> values(mesh->numberOfCells()); + CellIntegrator::integrateTo(f, GaussQuadratureDescriptor(2), *mesh, values); + + double error = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) { + error += std::abs(int_f_per_cell[cell_id] - values[cell_id]); + } + + REQUIRE(error == Catch::Approx(0).margin(1E-10)); + } + } + + SECTION("cell list") + { + SECTION("Array") + { + Array<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 2}; + + { + size_t k = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) { + cell_list[k] = cell_id; + } + + REQUIRE(k == cell_list.size()); + } + + Array<double> values = CellIntegrator::integrate(f, GaussQuadratureDescriptor(2), *mesh, cell_list); + + double error = 0; + for (size_t i = 0; i < cell_list.size(); ++i) { + error += std::abs(int_f_per_cell[cell_list[i]] - values[i]); + } + + REQUIRE(error == Catch::Approx(0).margin(1E-10)); + } + + SECTION("SmallArray") + { + SmallArray<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 2}; + + { + size_t k = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) { + cell_list[k] = cell_id; + } + + REQUIRE(k == cell_list.size()); + } + + SmallArray<double> values = CellIntegrator::integrate(f, GaussQuadratureDescriptor(2), *mesh, cell_list); + + double error = 0; + for (size_t i = 0; i < cell_list.size(); ++i) { + error += std::abs(int_f_per_cell[cell_list[i]] - values[i]); + } + + REQUIRE(error == Catch::Approx(0).margin(1E-10)); + } + } + } + + SECTION("tensorial formula") + { + SECTION("all cells") + { + SECTION("CellValue") + { + CellValue<double> values(mesh->connectivity()); + CellIntegrator::integrateTo([=](const R2 x) { return f(x); }, GaussLobattoQuadratureDescriptor(2), *mesh, + values); + + double error = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) { + error += std::abs(int_f_per_cell[cell_id] - values[cell_id]); + } + + REQUIRE(error == Catch::Approx(0).margin(1E-10)); + } + + SECTION("Array") + { + Array<double> values(mesh->numberOfCells()); + + CellIntegrator::integrateTo(f, GaussLobattoQuadratureDescriptor(2), *mesh, values); + + double error = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) { + error += std::abs(int_f_per_cell[cell_id] - values[cell_id]); + } + + REQUIRE(error == Catch::Approx(0).margin(1E-10)); + } + + SECTION("SmallArray") + { + SmallArray<double> values(mesh->numberOfCells()); + CellIntegrator::integrateTo(f, GaussLobattoQuadratureDescriptor(2), *mesh, values); + + double error = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) { + error += std::abs(int_f_per_cell[cell_id] - values[cell_id]); + } + + REQUIRE(error == Catch::Approx(0).margin(1E-10)); + } + } + + SECTION("cell list") + { + SECTION("Array") + { + Array<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 2}; + + { + size_t k = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) { + cell_list[k] = cell_id; + } + + REQUIRE(k == cell_list.size()); + } + + Array<double> values = CellIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(2), *mesh, cell_list); + + double error = 0; + for (size_t i = 0; i < cell_list.size(); ++i) { + error += std::abs(int_f_per_cell[cell_list[i]] - values[i]); + } + + REQUIRE(error == Catch::Approx(0).margin(1E-10)); + } + + SECTION("SmallArray") + { + SmallArray<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 2}; + + { + size_t k = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) { + cell_list[k] = cell_id; + } + + REQUIRE(k == cell_list.size()); + } + + SmallArray<double> values = + CellIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(2), *mesh, cell_list); + + double error = 0; + for (size_t i = 0; i < cell_list.size(); ++i) { + error += std::abs(int_f_per_cell[cell_list[i]] - values[i]); + } + + REQUIRE(error == Catch::Approx(0).margin(1E-10)); + } + } + } + } + + SECTION("3D") + { + using R3 = TinyVector<3>; + + const auto mesh = MeshDataBaseForTests::get().hybrid3DMesh(); + + auto f = [](const R3& X) -> double { + const double x = X[0]; + const double y = X[1]; + const double z = X[2]; + return x * x + 2 * x * y + 3 * y * y + 2 * z * z - z + 1; + }; + + Array<const double> int_f_per_cell = [=] { + Array<double> int_f(mesh->numberOfCells()); + auto cell_to_node_matrix = mesh->connectivity().cellToNodeMatrix(); + auto cell_type = mesh->connectivity().cellType(); + + parallel_for( + mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { + auto cell_node_list = cell_to_node_matrix[cell_id]; + auto xr = mesh->xr(); + double integral = 0; + + switch (cell_type[cell_id]) { + case CellType::Tetrahedron: { + TetrahedronTransformation T(xr[cell_node_list[0]], xr[cell_node_list[1]], xr[cell_node_list[2]], + xr[cell_node_list[3]]); + auto qf = QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor(4)); + + for (size_t i = 0; i < qf.numberOfPoints(); ++i) { + const auto& xi = qf.point(i); + integral += qf.weight(i) * T.jacobianDeterminant() * f(T(xi)); + } + break; + } + case CellType::Pyramid: { + PyramidTransformation T(xr[cell_node_list[0]], xr[cell_node_list[1]], xr[cell_node_list[2]], + xr[cell_node_list[3]], xr[cell_node_list[4]]); + auto qf = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor(4)); + + for (size_t i = 0; i < qf.numberOfPoints(); ++i) { + const auto& xi = qf.point(i); + integral += qf.weight(i) * T.jacobianDeterminant(xi) * f(T(xi)); + } + break; + } + case CellType::Prism: { + PrismTransformation T(xr[cell_node_list[0]], xr[cell_node_list[1]], xr[cell_node_list[2]], + xr[cell_node_list[3]], xr[cell_node_list[4]], xr[cell_node_list[5]]); + auto qf = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor(4)); + + for (size_t i = 0; i < qf.numberOfPoints(); ++i) { + const auto& xi = qf.point(i); + integral += qf.weight(i) * T.jacobianDeterminant(xi) * f(T(xi)); + } + break; + } + case CellType::Hexahedron: { + CubeTransformation T(xr[cell_node_list[0]], xr[cell_node_list[1]], xr[cell_node_list[2]], + xr[cell_node_list[3]], xr[cell_node_list[4]], xr[cell_node_list[5]], + xr[cell_node_list[6]], xr[cell_node_list[7]]); + auto qf = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor(4)); + + for (size_t i = 0; i < qf.numberOfPoints(); ++i) { + const auto& xi = qf.point(i); + integral += qf.weight(i) * T.jacobianDeterminant(xi) * f(T(xi)); + } + break; + } + default: { + throw UnexpectedError("invalid cell type in 2d"); + } + } + int_f[cell_id] = integral; + }); + + return int_f; + }(); + + SECTION("direct formula") + { + SECTION("all cells") + { + SECTION("CellValue") + { + CellValue<double> values(mesh->connectivity()); + CellIntegrator::integrateTo([=](const R3 x) { return f(x); }, GaussQuadratureDescriptor(4), *mesh, values); + + double error = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) { + error += std::abs(int_f_per_cell[cell_id] - values[cell_id]); + } + + REQUIRE(error == Catch::Approx(0).margin(1E-10)); + } + + SECTION("Array") + { + Array<double> values(mesh->numberOfCells()); + + CellIntegrator::integrateTo(f, GaussQuadratureDescriptor(4), *mesh, values); + + double error = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) { + error += std::abs(int_f_per_cell[cell_id] - values[cell_id]); + } + + REQUIRE(error == Catch::Approx(0).margin(1E-10)); + } + + SECTION("SmallArray") + { + SmallArray<double> values(mesh->numberOfCells()); + CellIntegrator::integrateTo(f, GaussQuadratureDescriptor(4), *mesh, values); + + double error = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) { + error += std::abs(int_f_per_cell[cell_id] - values[cell_id]); + } + + REQUIRE(error == Catch::Approx(0).margin(1E-10)); + } + } + + SECTION("cell list") + { + SECTION("Array") + { + Array<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 2}; + + { + size_t k = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) { + cell_list[k] = cell_id; + } + + REQUIRE(k == cell_list.size()); + } + + Array<double> values = CellIntegrator::integrate(f, GaussQuadratureDescriptor(4), *mesh, cell_list); + + double error = 0; + for (size_t i = 0; i < cell_list.size(); ++i) { + error += std::abs(int_f_per_cell[cell_list[i]] - values[i]); + } + + REQUIRE(error == Catch::Approx(0).margin(1E-10)); + } + + SECTION("SmallArray") + { + SmallArray<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 2}; + + { + size_t k = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) { + cell_list[k] = cell_id; + } + + REQUIRE(k == cell_list.size()); + } + + SmallArray<double> values = CellIntegrator::integrate(f, GaussQuadratureDescriptor(4), *mesh, cell_list); + + double error = 0; + for (size_t i = 0; i < cell_list.size(); ++i) { + error += std::abs(int_f_per_cell[cell_list[i]] - values[i]); + } + + REQUIRE(error == Catch::Approx(0).margin(1E-10)); + } + } + } + + SECTION("tensorial formula") + { + SECTION("all cells") + { + SECTION("CellValue") + { + CellValue<double> values(mesh->connectivity()); + CellIntegrator::integrateTo([=](const R3 x) { return f(x); }, GaussLobattoQuadratureDescriptor(4), *mesh, + values); + + double error = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) { + error += std::abs(int_f_per_cell[cell_id] - values[cell_id]); + } + + REQUIRE(error == Catch::Approx(0).margin(1E-10)); + } + + SECTION("Array") + { + Array<double> values(mesh->numberOfCells()); + + CellIntegrator::integrateTo(f, GaussLobattoQuadratureDescriptor(4), *mesh, values); + + double error = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) { + error += std::abs(int_f_per_cell[cell_id] - values[cell_id]); + } + + REQUIRE(error == Catch::Approx(0).margin(1E-10)); + } + + SECTION("SmallArray") + { + SmallArray<double> values(mesh->numberOfCells()); + CellIntegrator::integrateTo(f, GaussLobattoQuadratureDescriptor(4), *mesh, values); + + double error = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) { + error += std::abs(int_f_per_cell[cell_id] - values[cell_id]); + } + + REQUIRE(error == Catch::Approx(0).margin(1E-10)); + } + } + + SECTION("cell list") + { + SECTION("Array") + { + Array<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 2}; + + { + size_t k = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) { + cell_list[k] = cell_id; + } + + REQUIRE(k == cell_list.size()); + } + + Array<double> values = CellIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(4), *mesh, cell_list); + + double error = 0; + for (size_t i = 0; i < cell_list.size(); ++i) { + error += std::abs(int_f_per_cell[cell_list[i]] - values[i]); + } + + REQUIRE(error == Catch::Approx(0).margin(1E-10)); + } + + SECTION("SmallArray") + { + SmallArray<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 2}; + + { + size_t k = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) { + cell_list[k] = cell_id; + } + + REQUIRE(k == cell_list.size()); + } + + SmallArray<double> values = + CellIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(4), *mesh, cell_list); + + double error = 0; + for (size_t i = 0; i < cell_list.size(); ++i) { + error += std::abs(int_f_per_cell[cell_list[i]] - values[i]); + } + + REQUIRE(error == Catch::Approx(0).margin(1E-10)); + } + } + } + } +} -- GitLab