diff --git a/src/scheme/CellIntegrator.hpp b/src/scheme/CellIntegrator.hpp index e3a73a89ca5bcc6f5fda9039be3900805ef32843..6960901d335f894e00a86239772f33f3abc50c67 100644 --- a/src/scheme/CellIntegrator.hpp +++ b/src/scheme/CellIntegrator.hpp @@ -134,10 +134,12 @@ class CellIntegrator } break; } + // LCOV_EXCL_START default: { invalid_cell = std::make_pair(true, cell_id); break; } + // LCOV_EXCL_STOP } } else if constexpr (Dimension == 2) { switch (cell_type[cell_id]) { @@ -161,10 +163,12 @@ class CellIntegrator } break; } + // LCOV_EXCL_START default: { invalid_cell = std::make_pair(true, cell_id); break; } + // LCOV_EXCL_STOP } } else { static_assert(Dimension == 3); @@ -194,8 +198,10 @@ class CellIntegrator value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * function(T(xi)); } } else { + // LCOV_EXCL_START throw NotImplementedError("integration on pyramid with non-quadrangular base (number of nodes " + std::to_string(cell_to_node.size()) + ")"); + // LCOV_EXCL_STOP } break; } @@ -247,8 +253,10 @@ class CellIntegrator } } } else { + // LCOV_EXCL_START throw NotImplementedError("integration on diamond with non-quadrangular base (" + std::to_string(cell_to_node.size()) + ")"); + // LCOV_EXCL_STOP } break; } @@ -275,19 +283,23 @@ class CellIntegrator } break; } + // LCOV_EXCL_START default: { invalid_cell = std::make_pair(true, cell_id); break; } + // LCOV_EXCL_STOP } } }); + // LCOV_EXCL_START if (invalid_cell.first) { std::ostringstream os; os << "invalid cell type for integration: " << name(cell_type[invalid_cell.second]); throw UnexpectedError(os.str()); } + // LCOV_EXCL_STOP } template <typename MeshType, typename OutputArrayT, typename ListTypeT, typename OutputType, typename InputType> @@ -340,10 +352,12 @@ class CellIntegrator } break; } + // LCOV_EXCL_START default: { invalid_cell = std::make_pair(true, cell_id); break; } + // LCOV_EXCL_STOP } } else if constexpr (Dimension == 2) { switch (cell_type[cell_id]) { @@ -368,10 +382,12 @@ class CellIntegrator } break; } + // LCOV_EXCL_START default: { invalid_cell = std::make_pair(true, cell_id); break; } + // LCOV_EXCL_STOP } } else { static_assert(Dimension == 3); @@ -399,7 +415,9 @@ class CellIntegrator value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * function(T(xi)); } } else { + // LCOV_EXCL_START throw NotImplementedError("integration on pyramid with non-quadrangular base"); + // LCOV_EXCL_STOP } break; } @@ -445,8 +463,10 @@ class CellIntegrator } } } else { + // LCOV_EXCL_START throw NotImplementedError("integration on diamond with non-quadrangular base (" + std::to_string(cell_to_node.size()) + ")"); + // LCOV_EXCL_STOP } break; } @@ -473,19 +493,23 @@ class CellIntegrator } break; } + // LCOV_EXCL_START default: { invalid_cell = std::make_pair(true, cell_id); break; } + // LCOV_EXCL_STOP } } }); + // LCOV_EXCL_START if (invalid_cell.first) { std::ostringstream os; os << "invalid cell type for integration: " << name(cell_type[invalid_cell.second]); throw UnexpectedError(os.str()); } + // LCOV_EXCL_STOP } public: diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7f4c6a323d865a1cc9b4c2a9c1c997e5672e1a9d..8d7595e7ca2e9809829f9636e6d52de4c343910b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -53,6 +53,7 @@ add_executable (unit_tests test_BuiltinFunctionEmbedderTable.cpp test_BuiltinFunctionProcessor.cpp test_CastArray.cpp + test_CellIntegrator.cpp test_ConsoleManager.cpp test_CG.cpp test_ContinueProcessor.cpp @@ -127,7 +128,6 @@ 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 index ef0fca0b93b62710eed6bcac48a55e889824d075..97bf0b98fa9beaace5bea6bf427b69dbe75548fc 100644 --- a/tests/test_CellIntegrator.cpp +++ b/tests/test_CellIntegrator.cpp @@ -1,8 +1,10 @@ #include <catch2/catch_approx.hpp> #include <catch2/catch_test_macros.hpp> +#include <analysis/GaussLegendreQuadratureDescriptor.hpp> #include <analysis/GaussLobattoQuadratureDescriptor.hpp> #include <analysis/GaussQuadratureDescriptor.hpp> +#include <mesh/DiamondDualMeshManager.hpp> #include <mesh/ItemValue.hpp> #include <mesh/Mesh.hpp> #include <scheme/CellIntegrator.hpp> @@ -480,7 +482,7 @@ TEST_CASE("CellIntegrator", "[scheme]") { using R3 = TinyVector<3>; - const auto mesh = MeshDataBaseForTests::get().hybrid3DMesh(); + auto hybrid_mesh = MeshDataBaseForTests::get().hybrid3DMesh(); auto f = [](const R3& X) -> double { const double x = X[0]; @@ -489,261 +491,442 @@ TEST_CASE("CellIntegrator", "[scheme]") 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; + std::vector<std::pair<std::string, decltype(hybrid_mesh)>> mesh_list; + mesh_list.push_back(std::make_pair("hybrid mesh", hybrid_mesh)); + mesh_list.push_back( + std::make_pair("diamond mesh", DiamondDualMeshManager::instance().getDiamondDualMesh(hybrid_mesh))); - 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 (auto mesh_info : mesh_list) { + auto mesh_name = mesh_info.first; + auto mesh = mesh_info.second; - for (size_t i = 0; i < qf.numberOfPoints(); ++i) { - const auto& xi = qf.point(i); - integral += qf.weight(i) * T.jacobianDeterminant() * f(T(xi)); + SECTION(mesh_name) + { + SECTION("direct formula") + { + 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; + } + case CellType::Diamond: { + if (cell_node_list.size() == 5) { + auto qf = QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor(4)); + { // top tetrahedron + TetrahedronTransformation T0(xr[cell_node_list[1]], xr[cell_node_list[2]], xr[cell_node_list[3]], + xr[cell_node_list[4]]); + + for (size_t i = 0; i < qf.numberOfPoints(); ++i) { + const auto& xi = qf.point(i); + integral += qf.weight(i) * T0.jacobianDeterminant() * f(T0(xi)); + } + } + { // bottom tetrahedron + TetrahedronTransformation T1(xr[cell_node_list[3]], xr[cell_node_list[2]], xr[cell_node_list[1]], + xr[cell_node_list[0]]); + + for (size_t i = 0; i < qf.numberOfPoints(); ++i) { + const auto& xi = qf.point(i); + integral += qf.weight(i) * T1.jacobianDeterminant() * f(T1(xi)); + } + } + } else if (cell_node_list.size() == 6) { + auto qf = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor(4)); + { // top pyramid + PyramidTransformation T0(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]]); + + for (size_t i = 0; i < qf.numberOfPoints(); ++i) { + const auto& xi = qf.point(i); + integral += qf.weight(i) * T0.jacobianDeterminant(xi) * f(T0(xi)); + } + } + { // bottom pyramid + PyramidTransformation T1(xr[cell_node_list[4]], xr[cell_node_list[3]], xr[cell_node_list[2]], + xr[cell_node_list[1]], xr[cell_node_list[0]]); + + for (size_t i = 0; i < qf.numberOfPoints(); ++i) { + const auto& xi = qf.point(i); + integral += qf.weight(i) * T1.jacobianDeterminant(xi) * f(T1(xi)); + } + } + } else { + INFO("Diamond cells with more than 6 vertices are not tested"); + REQUIRE(false); + } + break; + } + default: { + INFO("Diamond cells not tested yet"); + REQUIRE(cell_type[cell_id] != CellType::Diamond); + } + } + int_f[cell_id] = integral; + }); + + return int_f; + }(); + + 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 == 0); } - 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)); + SECTION("Array") + { + Array<double> values(mesh->numberOfCells()); - 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)); + CellIntegrator::integrateTo(f, GaussQuadratureDescriptor(4), *mesh, values); - 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)); + 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 == 0); } - break; - } - default: { - throw UnexpectedError("invalid cell type in 2d"); - } - } - int_f[cell_id] = integral; - }); - return int_f; - }(); + SECTION("SmallArray") + { + SmallArray<double> values(mesh->numberOfCells()); + CellIntegrator::integrateTo(f, GaussQuadratureDescriptor(4), *mesh, values); - 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]); + } - 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 == 0); + } } - REQUIRE(error == Catch::Approx(0).margin(1E-10)); - } + SECTION("cell list") + { + SECTION("Array") + { + Array<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 2}; - SECTION("Array") - { - Array<double> values(mesh->numberOfCells()); + { + size_t k = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) { + cell_list[k] = cell_id; + } - CellIntegrator::integrateTo(f, GaussQuadratureDescriptor(4), *mesh, values); + REQUIRE(k == cell_list.size()); + } - 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]); - } + Array<double> values = CellIntegrator::integrate(f, GaussQuadratureDescriptor(4), *mesh, cell_list); - REQUIRE(error == Catch::Approx(0).margin(1E-10)); - } + 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]); + } - SECTION("SmallArray") - { - SmallArray<double> values(mesh->numberOfCells()); - CellIntegrator::integrateTo(f, GaussQuadratureDescriptor(4), *mesh, values); + REQUIRE(error == 0); + } - 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]); - } + SECTION("SmallArray") + { + SmallArray<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 2}; - REQUIRE(error == Catch::Approx(0).margin(1E-10)); - } - } + { + size_t k = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) { + cell_list[k] = cell_id; + } - SECTION("cell list") - { - SECTION("Array") - { - Array<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 2}; + REQUIRE(k == cell_list.size()); + } - { - size_t k = 0; - for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) { - cell_list[k] = cell_id; - } + SmallArray<double> values = CellIntegrator::integrate(f, GaussQuadratureDescriptor(4), *mesh, cell_list); - 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]); + } - 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 == 0); + } } - - REQUIRE(error == Catch::Approx(0).margin(1E-10)); } - SECTION("SmallArray") + SECTION("tensorial formula") { - SmallArray<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 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(); + + auto qf = QuadratureManager::instance().getCubeFormula(GaussLegendreQuadratureDescriptor(4)); + + 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: { + CubeTransformation T(xr[cell_node_list[0]], xr[cell_node_list[1]], xr[cell_node_list[2]], + xr[cell_node_list[2]], xr[cell_node_list[3]], xr[cell_node_list[3]], + xr[cell_node_list[3]], xr[cell_node_list[3]]); + + 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::Pyramid: { + 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[4]], + xr[cell_node_list[4]], xr[cell_node_list[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: { + CubeTransformation T(xr[cell_node_list[0]], xr[cell_node_list[1]], xr[cell_node_list[2]], + xr[cell_node_list[2]], xr[cell_node_list[3]], xr[cell_node_list[4]], + xr[cell_node_list[5]], xr[cell_node_list[5]]); + 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]]); + + 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::Diamond: { + if (cell_node_list.size() == 5) { + { // top tetrahedron + CubeTransformation T0(xr[cell_node_list[1]], xr[cell_node_list[2]], xr[cell_node_list[3]], + xr[cell_node_list[3]], xr[cell_node_list[4]], xr[cell_node_list[4]], + xr[cell_node_list[4]], xr[cell_node_list[4]]); + + for (size_t i = 0; i < qf.numberOfPoints(); ++i) { + const auto& xi = qf.point(i); + integral += qf.weight(i) * T0.jacobianDeterminant(xi) * f(T0(xi)); + } + } + { // bottom tetrahedron + CubeTransformation T1(xr[cell_node_list[3]], xr[cell_node_list[2]], xr[cell_node_list[1]], + xr[cell_node_list[1]], xr[cell_node_list[0]], xr[cell_node_list[0]], + xr[cell_node_list[0]], xr[cell_node_list[0]]); + + for (size_t i = 0; i < qf.numberOfPoints(); ++i) { + const auto& xi = qf.point(i); + integral += qf.weight(i) * T1.jacobianDeterminant(xi) * f(T1(xi)); + } + } + } else if (cell_node_list.size() == 6) { + { // top pyramid + CubeTransformation T0(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[5]], + xr[cell_node_list[5]], xr[cell_node_list[5]]); + + for (size_t i = 0; i < qf.numberOfPoints(); ++i) { + const auto& xi = qf.point(i); + integral += qf.weight(i) * T0.jacobianDeterminant(xi) * f(T0(xi)); + } + } + { // bottom pyramid + CubeTransformation T1(xr[cell_node_list[4]], xr[cell_node_list[3]], xr[cell_node_list[2]], + xr[cell_node_list[1]], xr[cell_node_list[0]], xr[cell_node_list[0]], + xr[cell_node_list[0]], xr[cell_node_list[0]]); + + for (size_t i = 0; i < qf.numberOfPoints(); ++i) { + const auto& xi = qf.point(i); + integral += qf.weight(i) * T1.jacobianDeterminant(xi) * f(T1(xi)); + } + } + } else { + INFO("Diamond cells with more than 6 vertices are not tested"); + REQUIRE(false); + } + break; + } + default: { + INFO("Diamond cells not tested yet"); + REQUIRE(cell_type[cell_id] != CellType::Diamond); + } + } + int_f[cell_id] = integral; + }); + + return int_f; + }(); + + SECTION("all cells") { - size_t k = 0; - for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) { - cell_list[k] = cell_id; + SECTION("CellValue") + { + CellValue<double> values(mesh->connectivity()); + CellIntegrator::integrateTo([=](const R3 x) { return f(x); }, GaussLegendreQuadratureDescriptor(10), + *mesh, values); + + auto cell_type = mesh->connectivity().cellType(); + 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)); } - REQUIRE(k == cell_list.size()); - } - - SmallArray<double> values = CellIntegrator::integrate(f, GaussQuadratureDescriptor(4), *mesh, cell_list); + SECTION("Array") + { + Array<double> values(mesh->numberOfCells()); - 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]); - } + CellIntegrator::integrateTo(f, GaussLobattoQuadratureDescriptor(4), *mesh, values); - REQUIRE(error == Catch::Approx(0).margin(1E-10)); - } - } - } + 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]); + } - 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)); - } + REQUIRE(error == Catch::Approx(0).margin(1E-10)); + } - SECTION("Array") - { - Array<double> values(mesh->numberOfCells()); + SECTION("SmallArray") + { + SmallArray<double> values(mesh->numberOfCells()); + CellIntegrator::integrateTo(f, GaussLobattoQuadratureDescriptor(4), *mesh, values); - 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]); + } - 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)); + } } - REQUIRE(error == Catch::Approx(0).margin(1E-10)); - } + SECTION("cell list") + { + SECTION("Array") + { + Array<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 2}; - SECTION("SmallArray") - { - SmallArray<double> values(mesh->numberOfCells()); - CellIntegrator::integrateTo(f, GaussLobattoQuadratureDescriptor(4), *mesh, values); + { + size_t k = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) { + cell_list[k] = cell_id; + } - 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(k == cell_list.size()); + } - REQUIRE(error == Catch::Approx(0).margin(1E-10)); - } - } + Array<double> values = + CellIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(4), *mesh, cell_list); - SECTION("cell list") - { - SECTION("Array") - { - Array<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 2}; + 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]); + } - { - size_t k = 0; - for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) { - cell_list[k] = cell_id; + REQUIRE(error == Catch::Approx(0).margin(1E-10)); } - REQUIRE(k == cell_list.size()); - } + SECTION("SmallArray") + { + SmallArray<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 2}; - Array<double> values = CellIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(4), *mesh, cell_list); + { + size_t k = 0; + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) { + cell_list[k] = cell_id; + } - 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(k == cell_list.size()); + } - REQUIRE(error == Catch::Approx(0).margin(1E-10)); - } + SmallArray<double> values = + CellIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(4), *mesh, cell_list); - SECTION("SmallArray") - { - SmallArray<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 2}; + 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]); + } - { - size_t k = 0; - for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) { - cell_list[k] = cell_id; + REQUIRE(error == Catch::Approx(0).margin(1E-10)); } - - 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)); } } } diff --git a/tests/test_CubeTransformation.cpp b/tests/test_CubeTransformation.cpp index 2de108f9c96c5c3659c20b4c3e7d314d5342280c..7d060ccd6faf409fb08813607209da1d9da616b1 100644 --- a/tests/test_CubeTransformation.cpp +++ b/tests/test_CubeTransformation.cpp @@ -2,6 +2,8 @@ #include <catch2/catch_test_macros.hpp> #include <analysis/GaussLegendreQuadratureDescriptor.hpp> +#include <analysis/GaussLobattoQuadratureDescriptor.hpp> +#include <analysis/GaussQuadratureDescriptor.hpp> #include <analysis/QuadratureManager.hpp> #include <geometry/CubeTransformation.hpp> @@ -11,88 +13,88 @@ TEST_CASE("CubeTransformation", "[geometry]") { using R3 = TinyVector<3>; - const R3 a_hat = {-1, -1, -1}; - const R3 b_hat = {+1, -1, -1}; - const R3 c_hat = {+1, +1, -1}; - const R3 d_hat = {-1, +1, -1}; - const R3 e_hat = {-1, -1, +1}; - const R3 f_hat = {+1, -1, +1}; - const R3 g_hat = {+1, +1, +1}; - const R3 h_hat = {-1, +1, +1}; - - const R3 m_hat = zero; + SECTION("invertible transformation") + { + const R3 a_hat = {-1, -1, -1}; + const R3 b_hat = {+1, -1, -1}; + const R3 c_hat = {+1, +1, -1}; + const R3 d_hat = {-1, +1, -1}; + const R3 e_hat = {-1, -1, +1}; + const R3 f_hat = {+1, -1, +1}; + const R3 g_hat = {+1, +1, +1}; + const R3 h_hat = {-1, +1, +1}; - const R3 a = {0, 0, 0}; - const R3 b = {3, 1, 3}; - const R3 c = {2, 5, 2}; - const R3 d = {0, 3, 1}; - const R3 e = {1, 2, 5}; - const R3 f = {3, 1, 7}; - const R3 g = {2, 5, 5}; - const R3 h = {0, 3, 6}; + const R3 m_hat = zero; - const R3 m = 0.125 * (a + b + c + d + e + f + g + h); + const R3 a = {0, 0, 0}; + const R3 b = {3, 1, 3}; + const R3 c = {2, 5, 2}; + const R3 d = {0, 3, 1}; + const R3 e = {1, 2, 5}; + const R3 f = {3, 1, 7}; + const R3 g = {2, 5, 5}; + const R3 h = {0, 3, 6}; - const CubeTransformation t(a, b, c, d, e, f, g, h); + const R3 m = 0.125 * (a + b + c + d + e + f + g + h); - SECTION("values") - { - REQUIRE(l2Norm(t(a_hat) - a) == Catch::Approx(0)); - REQUIRE(l2Norm(t(b_hat) - b) == Catch::Approx(0)); - REQUIRE(l2Norm(t(c_hat) - c) == Catch::Approx(0)); - REQUIRE(l2Norm(t(d_hat) - d) == Catch::Approx(0)); - REQUIRE(l2Norm(t(e_hat) - e) == Catch::Approx(0)); - REQUIRE(l2Norm(t(f_hat) - f) == Catch::Approx(0)); - REQUIRE(l2Norm(t(g_hat) - g) == Catch::Approx(0)); - REQUIRE(l2Norm(t(h_hat) - h) == Catch::Approx(0)); - - REQUIRE(l2Norm(t(m_hat) - m) == Catch::Approx(0)); - } + const CubeTransformation t(a, b, c, d, e, f, g, h); - SECTION("Jacobian determinant") - { - SECTION("at points") + SECTION("values") { - auto detJ = [](const R3& X) { - const double x = X[0]; - const double y = X[1]; - const double z = X[2]; - - return -(3 * x * y * y * z + 9 * y * y * z - 6 * x * x * y * z + 6 * x * y * z - 96 * y * z + 14 * x * x * z - - 49 * x * z + 119 * z - 21 * x * y * y + 21 * y * y + 90 * x * y - 10 * y + 40 * x * x - 109 * x - - 491) / - 128; - }; + REQUIRE(l2Norm(t(a_hat) - a) == Catch::Approx(0)); + REQUIRE(l2Norm(t(b_hat) - b) == Catch::Approx(0)); + REQUIRE(l2Norm(t(c_hat) - c) == Catch::Approx(0)); + REQUIRE(l2Norm(t(d_hat) - d) == Catch::Approx(0)); + REQUIRE(l2Norm(t(e_hat) - e) == Catch::Approx(0)); + REQUIRE(l2Norm(t(f_hat) - f) == Catch::Approx(0)); + REQUIRE(l2Norm(t(g_hat) - g) == Catch::Approx(0)); + REQUIRE(l2Norm(t(h_hat) - h) == Catch::Approx(0)); - REQUIRE(t.jacobianDeterminant(a_hat) == Catch::Approx(detJ(a_hat))); - REQUIRE(t.jacobianDeterminant(b_hat) == Catch::Approx(detJ(b_hat))); - REQUIRE(t.jacobianDeterminant(c_hat) == Catch::Approx(detJ(c_hat))); - REQUIRE(t.jacobianDeterminant(d_hat) == Catch::Approx(detJ(d_hat))); - REQUIRE(t.jacobianDeterminant(e_hat) == Catch::Approx(detJ(e_hat))); - REQUIRE(t.jacobianDeterminant(f_hat) == Catch::Approx(detJ(f_hat))); - REQUIRE(t.jacobianDeterminant(g_hat) == Catch::Approx(detJ(g_hat))); - REQUIRE(t.jacobianDeterminant(h_hat) == Catch::Approx(detJ(h_hat))); - - REQUIRE(t.jacobianDeterminant(m_hat) == Catch::Approx(detJ(m_hat))); + REQUIRE(l2Norm(t(m_hat) - m) == Catch::Approx(0)); } - SECTION("Gauss order 3") + SECTION("Jacobian determinant") { - // The jacobian determinant is of maximal degree 2 in each variable - const QuadratureFormula<3>& gauss = - QuadratureManager::instance().getCubeFormula(GaussLegendreQuadratureDescriptor(2)); + SECTION("at points") + { + auto detJ = [](const R3& X) { + const double x = X[0]; + const double y = X[1]; + const double z = X[2]; - double volume = 0; - for (size_t i = 0; i < gauss.numberOfPoints(); ++i) { - volume += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i)); + return -(3 * x * y * y * z + 9 * y * y * z - 6 * x * x * y * z + 6 * x * y * z - 96 * y * z + 14 * x * x * z - + 49 * x * z + 119 * z - 21 * x * y * y + 21 * y * y + 90 * x * y - 10 * y + 40 * x * x - 109 * x - + 491) / + 128; + }; + + REQUIRE(t.jacobianDeterminant(a_hat) == Catch::Approx(detJ(a_hat))); + REQUIRE(t.jacobianDeterminant(b_hat) == Catch::Approx(detJ(b_hat))); + REQUIRE(t.jacobianDeterminant(c_hat) == Catch::Approx(detJ(c_hat))); + REQUIRE(t.jacobianDeterminant(d_hat) == Catch::Approx(detJ(d_hat))); + REQUIRE(t.jacobianDeterminant(e_hat) == Catch::Approx(detJ(e_hat))); + REQUIRE(t.jacobianDeterminant(f_hat) == Catch::Approx(detJ(f_hat))); + REQUIRE(t.jacobianDeterminant(g_hat) == Catch::Approx(detJ(g_hat))); + REQUIRE(t.jacobianDeterminant(h_hat) == Catch::Approx(detJ(h_hat))); + + REQUIRE(t.jacobianDeterminant(m_hat) == Catch::Approx(detJ(m_hat))); } - // 353. / 12 is actually the volume of the hexahedron - REQUIRE(volume == Catch::Approx(353. / 12)); - } + SECTION("Gauss order 3") + { + // The jacobian determinant is of maximal degree 2 in each variable + const QuadratureFormula<3>& gauss = + QuadratureManager::instance().getCubeFormula(GaussLegendreQuadratureDescriptor(2)); + + double volume = 0; + for (size_t i = 0; i < gauss.numberOfPoints(); ++i) { + volume += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i)); + } + + // 353. / 12 is actually the volume of the hexahedron + REQUIRE(volume == Catch::Approx(353. / 12)); + } - SECTION("Gauss order 4") - { auto p = [](const R3& X) { const double& x = X[0]; const double& y = X[1]; @@ -101,17 +103,196 @@ TEST_CASE("CubeTransformation", "[geometry]") return 2 * x * x + 3 * y * z + z * z + 2 * x - 3 * y + 0.5 * z + 3; }; - // Jacbian determinant is a degree 2 polynomial, so the - // following formula is required to reach exactness + SECTION("Gauss order 6") + { + // Jacbian determinant is a degree 2 polynomial, so the + // following formula is required to reach exactness + const QuadratureFormula<3>& gauss = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor(6)); + + double integral = 0; + for (size_t i = 0; i < gauss.numberOfPoints(); ++i) { + integral += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i)) * p(t(gauss.point(i))); + } + + REQUIRE(integral == Catch::Approx(488879. / 360)); + } + + SECTION("Gauss-Legendre order 4") + { + // Jacbian determinant is a degree 2 polynomial, so the + // following formula is required to reach exactness + const QuadratureFormula<3>& gauss = + QuadratureManager::instance().getCubeFormula(GaussLegendreQuadratureDescriptor(4)); + + double integral = 0; + for (size_t i = 0; i < gauss.numberOfPoints(); ++i) { + integral += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i)) * p(t(gauss.point(i))); + } + + REQUIRE(integral == Catch::Approx(488879. / 360)); + } + + SECTION("Gauss-Lobatto order 4") + { + // Jacbian determinant is a degree 2 polynomial, so the + // following formula is required to reach exactness + const QuadratureFormula<3>& gauss = + QuadratureManager::instance().getCubeFormula(GaussLobattoQuadratureDescriptor(4)); + + double integral = 0; + for (size_t i = 0; i < gauss.numberOfPoints(); ++i) { + integral += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i)) * p(t(gauss.point(i))); + } + + REQUIRE(integral == Catch::Approx(488879. / 360)); + } + } + } + + SECTION("degenerate to tetrahedron") + { + using R3 = TinyVector<3>; + + const R3 a = {1, 2, 1}; + const R3 b = {3, 1, 3}; + const R3 c = {2, 5, 2}; + const R3 d = {2, 3, 4}; + + const CubeTransformation t(a, b, c, c, d, d, d, d); + + auto p = [](const R3& X) { + const double x = X[0]; + const double y = X[1]; + const double z = X[2]; + return 2 * x * x + 3 * x * y + y * y + 3 * y - z * z + 2 * x * z + 2 * z; + }; + + SECTION("Polynomial Gauss integral") + { + QuadratureFormula<3> qf = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor(6)); + + double sum = 0; + for (size_t i = 0; i < qf.numberOfPoints(); ++i) { + const R3 xi = qf.point(i); + sum += qf.weight(i) * t.jacobianDeterminant(xi) * p(t(xi)); + } + + REQUIRE(sum == Catch::Approx(231. / 2)); + } + + SECTION("Polynomial Gauss-Lobatto integral") + { + QuadratureFormula<3> qf = QuadratureManager::instance().getCubeFormula(GaussLobattoQuadratureDescriptor(4)); + + double sum = 0; + for (size_t i = 0; i < qf.numberOfPoints(); ++i) { + const R3 xi = qf.point(i); + sum += qf.weight(i) * t.jacobianDeterminant(xi) * p(t(xi)); + } + + REQUIRE(sum == Catch::Approx(231. / 2)); + } + + SECTION("Polynomial Gauss-Legendre integral") + { + QuadratureFormula<3> qf = QuadratureManager::instance().getCubeFormula(GaussLegendreQuadratureDescriptor(4)); + + double sum = 0; + for (size_t i = 0; i < qf.numberOfPoints(); ++i) { + const R3 xi = qf.point(i); + sum += qf.weight(i) * t.jacobianDeterminant(xi) * p(t(xi)); + } + + REQUIRE(sum == Catch::Approx(231. / 2)); + } + } + + SECTION("degenerate to prism") + { + const R3 a = {1, 2, 0}; + const R3 b = {3, 1, 3}; + const R3 c = {2, 5, 2}; + const R3 d = {0, 3, 1}; + const R3 e = {1, 2, 5}; + const R3 f = {3, 1, 7}; + + const CubeTransformation t(a, b, c, c, d, e, f, f); + + auto p = [](const R3& X) { + const double x = X[0]; + const double y = X[1]; + const double z = X[2]; + + return 3 * x * x + 2 * y * y + 3 * z * z + 4 * x + 3 * y + 2 * z + 1; + }; + + SECTION("Polynomial Gauss integral") + { + // 8 is the minimum quadrature rule to integrate the polynomial + // on the prism due to the jacobian determinant expression + const QuadratureFormula<3>& gauss = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor(8)); + + double integral = 0; + for (size_t i = 0; i < gauss.numberOfPoints(); ++i) { + integral += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i)) * p(t(gauss.point(i))); + } + + REQUIRE(integral == Catch::Approx(30377. / 90)); + } + + SECTION("Polynomial Gauss-Legendre integral") + { const QuadratureFormula<3>& gauss = QuadratureManager::instance().getCubeFormula(GaussLegendreQuadratureDescriptor(4)); double integral = 0; for (size_t i = 0; i < gauss.numberOfPoints(); ++i) { - integral += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i)) * p(gauss.point(i)); + integral += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i)) * p(t(gauss.point(i))); } - REQUIRE(integral == Catch::Approx(8795. / 72)); + REQUIRE(integral == Catch::Approx(30377. / 90)); } + + SECTION("Polynomial Gauss-Lobatto integral") + { + const QuadratureFormula<3>& gauss = + QuadratureManager::instance().getCubeFormula(GaussLobattoQuadratureDescriptor(4)); + + double integral = 0; + for (size_t i = 0; i < gauss.numberOfPoints(); ++i) { + integral += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i)) * p(t(gauss.point(i))); + } + + REQUIRE(integral == Catch::Approx(30377. / 90)); + } + } + + SECTION("degenerate to pyramid") + { + const R3 a = {1, 2, 0}; + const R3 b = {3, 1, 3}; + const R3 c = {2, 5, 2}; + const R3 d = {0, 3, 1}; + const R3 e = {1, 2, 5}; + + const CubeTransformation t(a, b, c, d, e, e, e, e); + + auto p = [](const R3& X) { + const double x = X[0]; + const double y = X[1]; + const double z = X[2]; + + return 3 * x * x + 2 * y * y + 3 * z * z + 4 * x + 3 * y + 2 * z + 1; + }; + + // 4 is the minimum quadrature rule to integrate the polynomial on the pyramid + const QuadratureFormula<3>& gauss = + QuadratureManager::instance().getCubeFormula(GaussLegendreQuadratureDescriptor(20)); + double integral = 0; + for (size_t i = 0; i < gauss.numberOfPoints(); ++i) { + integral += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i)) * p(t(gauss.point(i))); + } + + REQUIRE(integral == Catch::Approx(7238. / 15)); } } diff --git a/tests/test_LineTransformation.cpp b/tests/test_LineTransformation.cpp index 764a842b8fdc0d817955d4d0e4e1c7ef4b5e2e48..908d2d693973d13d48288cda7f7d021ee187c7a7 100644 --- a/tests/test_LineTransformation.cpp +++ b/tests/test_LineTransformation.cpp @@ -23,6 +23,25 @@ TEST_CASE("LineTransformation", "[geometry]") REQUIRE(t(1)[0] == Catch::Approx(2.7)); REQUIRE(t.jacobianDeterminant() == Catch::Approx(1.2)); + + auto p = [](const R1& X) { + const double x = X[0]; + return 2 * x * x + 3 * x + 2; + }; + + QuadratureFormula<1> qf = QuadratureManager::instance().getLineFormula(GaussQuadratureDescriptor(2)); + + double sum = 0; + for (size_t i = 0; i < qf.numberOfPoints(); ++i) { + sum += qf.weight(i) * t.jacobianDeterminant() * p(t(qf.point(i))); + } + + auto P = [](const R1& X) { + const double x = X[0]; + return 2. / 3 * x * x * x + 3. / 2 * x * x + 2 * x; + }; + + REQUIRE(sum == Catch::Approx(P(b) - P(a))); } SECTION("2D") diff --git a/tests/test_PrismTransformation.cpp b/tests/test_PrismTransformation.cpp index e170da3c05cf2643ab22958509b2cf62039f3fa1..f933fe308fbc64915faf04449aeca364f0904053 100644 --- a/tests/test_PrismTransformation.cpp +++ b/tests/test_PrismTransformation.cpp @@ -97,10 +97,10 @@ TEST_CASE("PrismTransformation", "[geometry]") double integral = 0; for (size_t i = 0; i < gauss.numberOfPoints(); ++i) { - integral += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i)) * p(gauss.point(i)); + integral += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i)) * p(t(gauss.point(i))); } - REQUIRE(integral == Catch::Approx(17627. / 720)); + REQUIRE(integral == Catch::Approx(30377. / 90)); } } } diff --git a/tests/test_PyramidTransformation.cpp b/tests/test_PyramidTransformation.cpp index ca51bf080045e0df06d706b60bc5fd1dca497d44..23b7369e625b20307ddab31d222b1b2ae3d607b4 100644 --- a/tests/test_PyramidTransformation.cpp +++ b/tests/test_PyramidTransformation.cpp @@ -1,8 +1,10 @@ #include <catch2/catch_approx.hpp> #include <catch2/catch_test_macros.hpp> +#include <analysis/GaussLegendreQuadratureDescriptor.hpp> #include <analysis/GaussQuadratureDescriptor.hpp> #include <analysis/QuadratureManager.hpp> +#include <geometry/CubeTransformation.hpp> #include <geometry/PyramidTransformation.hpp> // clazy:excludeall=non-pod-global-static @@ -83,14 +85,14 @@ TEST_CASE("PyramidTransformation", "[geometry]") return 3 * x * x + 2 * y * y + 3 * z * z + 4 * x + 3 * y + 2 * z + 1; }; - // 3 is the minimum quadrature rule to integrate the polynomial on the pyramid - const QuadratureFormula<3>& gauss = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor(3)); + // 4 is the minimum quadrature rule to integrate the polynomial on the pyramid + const QuadratureFormula<3>& gauss = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor(4)); double integral = 0; for (size_t i = 0; i < gauss.numberOfPoints(); ++i) { - integral += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i)) * p(gauss.point(i)); + integral += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i)) * p(t(gauss.point(i))); } - REQUIRE(integral == Catch::Approx(1513. / 60)); + REQUIRE(integral == Catch::Approx(213095. / 448)); } } } diff --git a/tests/test_SquareTransformation.cpp b/tests/test_SquareTransformation.cpp index fa12d756f27945d0189f19785c7c5bc1caccb0d5..a1ead02fc75d25f5ff0194b4157e22497d6005dc 100644 --- a/tests/test_SquareTransformation.cpp +++ b/tests/test_SquareTransformation.cpp @@ -2,6 +2,8 @@ #include <catch2/catch_test_macros.hpp> #include <analysis/GaussLegendreQuadratureDescriptor.hpp> +#include <analysis/GaussLobattoQuadratureDescriptor.hpp> +#include <analysis/GaussQuadratureDescriptor.hpp> #include <analysis/QuadratureManager.hpp> #include <geometry/SquareTransformation.hpp> @@ -97,10 +99,69 @@ TEST_CASE("SquareTransformation", "[geometry]") double integral = 0; for (size_t i = 0; i < gauss.numberOfPoints(); ++i) { - integral += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i)) * p(gauss.point(i)); + integral += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i)) * p(t(gauss.point(i))); } - REQUIRE(integral == Catch::Approx(-479. / 2)); + REQUIRE(integral == Catch::Approx(-76277. / 24)); + } + } + } + + SECTION("degenerate 2D ") + { + SECTION("2D") + { + using R2 = TinyVector<2>; + + const R2 a = {1, 2}; + const R2 b = {3, 1}; + const R2 c = {2, 5}; + + const SquareTransformation<2> t(a, b, c, c); + + auto p = [](const R2& X) { + const double x = X[0]; + const double y = X[1]; + return 2 * x * x + 3 * x * y + y * y + 3 * y + 1; + }; + + SECTION("Gauss") + { + QuadratureFormula<2> qf = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor(2)); + + double sum = 0; + for (size_t i = 0; i < qf.numberOfPoints(); ++i) { + R2 xi = qf.point(i); + sum += qf.weight(i) * t.jacobianDeterminant(xi) * p(t(xi)); + } + + REQUIRE(sum == Catch::Approx(3437. / 24)); + } + + SECTION("Gauss Legendre") + { + QuadratureFormula<2> qf = QuadratureManager::instance().getSquareFormula(GaussLegendreQuadratureDescriptor(2)); + + double sum = 0; + for (size_t i = 0; i < qf.numberOfPoints(); ++i) { + R2 xi = qf.point(i); + sum += qf.weight(i) * t.jacobianDeterminant(xi) * p(t(xi)); + } + + REQUIRE(sum == Catch::Approx(3437. / 24)); + } + + SECTION("Gauss Lobatto") + { + QuadratureFormula<2> qf = QuadratureManager::instance().getSquareFormula(GaussLobattoQuadratureDescriptor(2)); + + double sum = 0; + for (size_t i = 0; i < qf.numberOfPoints(); ++i) { + R2 xi = qf.point(i); + sum += qf.weight(i) * t.jacobianDeterminant(xi) * p(t(xi)); + } + + REQUIRE(sum == Catch::Approx(3437. / 24)); } } } diff --git a/tests/test_TetrahedronTransformation.cpp b/tests/test_TetrahedronTransformation.cpp index 1a9d20275fd1df42db4e5ea723b585c2872e69c4..91739b99e7030e1b8490869fb770f1978cce9334 100644 --- a/tests/test_TetrahedronTransformation.cpp +++ b/tests/test_TetrahedronTransformation.cpp @@ -1,6 +1,8 @@ #include <catch2/catch_approx.hpp> #include <catch2/catch_test_macros.hpp> +#include <analysis/GaussQuadratureDescriptor.hpp> +#include <analysis/QuadratureManager.hpp> #include <geometry/TetrahedronTransformation.hpp> // clazy:excludeall=non-pod-global-static @@ -37,4 +39,23 @@ TEST_CASE("TetrahedronTransformation", "[geometry]") REQUIRE(t({0.25, 0.25, 0.25})[2] == Catch::Approx(2.5)); REQUIRE(t.jacobianDeterminant() == Catch::Approx(14)); + + SECTION("Polynomial integral") + { + auto p = [](const R3& X) { + const double x = X[0]; + const double y = X[1]; + const double z = X[2]; + return 2 * x * x + 3 * x * y + y * y + 3 * y - z * z + 2 * x * z + 2 * z; + }; + + QuadratureFormula<3> qf = QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor(2)); + + double sum = 0; + for (size_t i = 0; i < qf.numberOfPoints(); ++i) { + sum += qf.weight(i) * t.jacobianDeterminant() * p(t(qf.point(i))); + } + + REQUIRE(sum == Catch::Approx(231. / 2)); + } } diff --git a/tests/test_TriangleTransformation.cpp b/tests/test_TriangleTransformation.cpp index 99330e41d7376d7a7265ba19798d6a0af82c3faa..e9561e859d727ea6c4019eaa78de125fbab8ba00 100644 --- a/tests/test_TriangleTransformation.cpp +++ b/tests/test_TriangleTransformation.cpp @@ -32,6 +32,24 @@ TEST_CASE("TriangleTransformation", "[geometry]") REQUIRE(t({1. / 3, 1. / 3})[1] == Catch::Approx(8. / 3)); REQUIRE(t.jacobianDeterminant() == Catch::Approx(7)); + + SECTION("Polynomial integral") + { + auto p = [](const R2& X) { + const double x = X[0]; + const double y = X[1]; + return 2 * x * x + 3 * x * y + y * y + 3 * y + 1; + }; + + QuadratureFormula<2> qf = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(2)); + + double sum = 0; + for (size_t i = 0; i < qf.numberOfPoints(); ++i) { + sum += qf.weight(i) * t.jacobianDeterminant() * p(t(qf.point(i))); + } + + REQUIRE(sum == Catch::Approx(3437. / 24)); + } } SECTION("3D")