diff --git a/src/algebra/Givens.hpp b/src/algebra/Givens.hpp new file mode 100644 index 0000000000000000000000000000000000000000..02a71d0aa97d646689d2ef0b6f059131478f38f9 --- /dev/null +++ b/src/algebra/Givens.hpp @@ -0,0 +1,99 @@ +#ifndef GIVENS_HPP +#define GIVENS_HPP +#include <cmath> +#include <iomanip> +#include <iostream> +#include <rang.hpp> +#include <utils/Exceptions.hpp> +#include <utils/PugsAssert.hpp> + +class Givens +{ + private: + static void + _givens(const double a, const double b, double& c, double& s) + { + if (b == 0) { + c = 1; + s = 0; + } else { + if (std::abs(b) > std::abs(a)) { + const double tau = -a / b; + s = 1. / sqrt(1 + tau * tau); + c = s * tau; + } else { + Assert(a != 0); + const double tau = -b / a; + c = 1 / sqrt(1 + tau * tau); + s = c * tau; + } + } + } + + static void + _rotate(const double Ai_1j, const double Aij, const double c, const double s, double& ui_1, double& ui) + { + ui_1 = c * Ai_1j - s * Aij; + ui = s * Ai_1j + c * Aij; + } + + template <typename MatrixType, typename VectorType, typename RHSVectorType> + static void + _lift(const MatrixType& A, const RHSVectorType& b, VectorType& x) + { + x.fill(0); + for (size_t i = x.size(); i >= 1; --i) { + double sum = 0; + for (size_t j = i; j < x.size(); ++j) { + sum += A(i - 1, j) * x[j]; + } + x[i - 1] = (b[i - 1] - sum) / A(i - 1, i - 1); + } + } + + public: + template <typename MatrixType, typename VectorType, typename RHSVectorType> + static void + solveInPlace(MatrixType& A, VectorType& x, RHSVectorType& b) + { + Assert(x.size() == A.numberOfColumns(), "The number of columns of A must be the size of x"); + Assert(b.size() == A.numberOfRows(), "The number of rows of A must be the size of b"); + + for (size_t j = 0; j < A.numberOfColumns(); ++j) { + for (size_t i = A.numberOfRows() - 1; i > j; --i) { + double c(0), s(0); + _givens(A(i - 1, j), A(i, j), c, s); + for (size_t k = j; k < A.numberOfColumns(); ++k) { + double xi_1(0), xi(0); + _rotate(A(i - 1, k), A(i, k), c, s, xi_1, xi); + A(i - 1, k) = xi_1; + A(i, k) = xi; + } + double xi_1(0), xi(0); + _rotate(b[i - 1], b[i], c, s, xi_1, xi); + b[i - 1] = xi_1; + b[i] = xi; + } + } + + _lift(A, b, x); + } + + template <typename MatrixType, typename VectorType, typename RHSVectorType> + static void + solve(const MatrixType& A, VectorType& x, const RHSVectorType& b) + { + Assert(x.size() == A.numberOfColumns(), "The number of columns of A must be the size of x"); + Assert(b.size() == A.numberOfRows(), "The number of rows of A must be the size of b"); + + MatrixType rotateA = copy(A); + RHSVectorType rotateb = copy(b); + + solveInPlace(rotateA, x, rotateb); + } + + Givens() = delete; + ~Givens() = delete; +}; + +#endif // GIVENS_HPP diff --git a/src/scheme/PolynomialReconstruction.hpp b/src/scheme/PolynomialReconstruction.hpp new file mode 100644 index 0000000000000000000000000000000000000000..7010d3d196b012b2f39a434c0c81111844f4b608 --- /dev/null +++ b/src/scheme/PolynomialReconstruction.hpp @@ -0,0 +1,81 @@ +#ifndef POLYNOMIAL_RECONSTRUCTION_HPP +#define POLYNOMIAL_RECONSTRUCTION_HPP + +#include <mesh/MeshTraits.hpp> +#include <mesh/StencilBuilder.hpp> +#include <scheme/DiscreteFunctionDPk.hpp> + +#include <algebra/SmallMatrix.hpp> +#include <algebra/SmallVector.hpp> + +#include <algebra/Givens.hpp> +#include <analysis/GaussLegendreQuadratureDescriptor.hpp> +#include <analysis/QuadratureFormula.hpp> +#include <analysis/QuadratureManager.hpp> +#include <geometry/LineTransformation.hpp> +#include <mesh/MeshData.hpp> +#include <mesh/MeshDataManager.hpp> + +class PolynomialReconstruction +{ + public: + template <MeshConcept MeshType, typename DataType> + PUGS_INLINE DiscreteFunctionDPk<MeshType::Dimension, std::remove_const_t<DataType>> + build(const MeshType& mesh, const DiscreteFunctionP0<DataType> p0_function) + { + const size_t degree = 1; + const Stencil stencil = StencilBuilder{}.build(mesh.connectivity()); + + auto xr = mesh.xr(); + auto xj = MeshDataManager::instance().getMeshData(mesh).xj(); + + auto cell_is_owned = mesh.connectivity().cellIsOwned(); + + DiscreteFunctionDPk<MeshType::Dimension, std::remove_const_t<DataType>> dpk{mesh.meshVariant(), degree}; + + if constexpr ((is_polygonal_mesh_v<MeshType>)and(MeshType::Dimension == 1)) { + auto qf = QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor(1)); + + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_j_id) { + if (cell_is_owned[cell_j_id]) { + auto stencil_cell_list = stencil[cell_j_id]; + SmallVector<double> b(stencil_cell_list.size()); + + const double p_j = p0_function[cell_j_id]; + for (size_t i = 0; i < stencil_cell_list.size(); ++i) { + const CellId cell_i_id = stencil_cell_list[i]; + + b[i] = p0_function[cell_i_id] - p_j; + } + + SmallMatrix<double> A(stencil_cell_list.size(), degree); + + using R1 = TinyVector<1>; + + const R1& Xj = xj[cell_j_id]; + auto X_Xj = [&](const R1& X) { return X - Xj; }; + + for (size_t i = 0; i < stencil_cell_list.size(); ++i) { + const CellId cell_i_id = stencil_cell_list[i]; + + A(i, 0) = X_Xj(xj[cell_i_id])[0]; + } + + SmallVector<double> x(1); + Givens::solveInPlace(A, x, b); + + dpk.coefficients(cell_j_id)[0] = p_j; + dpk.coefficients(cell_j_id)[1] = x[0]; + } + }); + } + + synchronize(dpk.cellArrays()); + return dpk; + } + + PolynomialReconstruction() {} +}; + +#endif // POLYNOMIAL_RECONSTRUCTION_HPP diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 68da0dc88f3ab393e8e7623bc4580fff21161b6e..fccafd015e31ced8c3cf5981b216a4015e15b286 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -96,6 +96,7 @@ add_executable (unit_tests test_GaussLegendreQuadratureDescriptor.cpp test_GaussLobattoQuadratureDescriptor.cpp test_GaussQuadratureDescriptor.cpp + test_Givens.cpp test_IfProcessor.cpp test_IncDecExpressionProcessor.cpp test_IntegrateCellArray.cpp @@ -203,6 +204,7 @@ add_executable (mpi_unit_tests test_OFStream.cpp test_ParallelChecker_read.cpp test_Partitioner.cpp + test_PolynomialReconstruction.cpp test_RandomEngine.cpp test_StencilBuilder.cpp test_SubItemArrayPerItemVariant.cpp diff --git a/tests/test_Givens.cpp b/tests/test_Givens.cpp new file mode 100644 index 0000000000000000000000000000000000000000..818d8222278258fa9323e31925c7d584f471777a --- /dev/null +++ b/tests/test_Givens.cpp @@ -0,0 +1,96 @@ +#include <catch2/catch_test_macros.hpp> +#include <catch2/matchers/catch_matchers_all.hpp> + +#include <algebra/Givens.hpp> +#include <algebra/SmallMatrix.hpp> +#include <algebra/SmallVector.hpp> + +// clazy:excludeall=non-pod-global-static + +TEST_CASE("Givens", "[algebra]") +{ + SECTION("square matrix") + { + SmallMatrix<double> A{5, 5}; + A.fill(0); + A(0, 0) = 2; + A(0, 1) = -1; + + A(1, 0) = -0.2; + A(1, 1) = 2; + A(1, 2) = -1; + + A(2, 1) = -1; + A(2, 2) = 4; + A(2, 3) = -2; + + A(3, 2) = -1; + A(3, 3) = 2; + A(3, 4) = -0.1; + + A(4, 3) = 1; + A(4, 4) = 3; + + // SmallMatrix<double> A{S.getMatrix()}; + + SmallVector<const double> x_exact = [] { + SmallVector<double> y{5}; + y[0] = 1; + y[1] = 3; + y[2] = 2; + y[3] = 4; + y[4] = 5; + return y; + }(); + + SmallVector<double> b = A * x_exact; + + SmallVector<double> x{5}; + x = zero; + + Givens::solve(A, x, b); + SmallVector<double> error = x - x_exact; + + REQUIRE(std::sqrt(dot(error, error)) < 1E-10 * std::sqrt(dot(x, x))); + } + + SECTION("rectangular matrix") + { + SmallMatrix<double> A{5, 3}; + A.fill(0); + A(0, 0) = 2; + A(0, 1) = -1; + + A(1, 0) = -0.2; + A(1, 1) = 2; + A(1, 2) = -1; + + A(2, 1) = -1; + A(2, 2) = 4; + + A(3, 2) = -1; + A(4, 0) = 0.5; + A(4, 1) = 1; + A(4, 2) = 1; + + // SmallMatrix<double> A{S.getMatrix()}; + + SmallVector<const double> x_exact = [] { + SmallVector<double> y{3}; + y[0] = 1; + y[1] = 3; + y[2] = 2; + return y; + }(); + + SmallVector<double> b = A * x_exact; + + SmallVector<double> x{3}; + x = zero; + + Givens::solve(A, x, b); + SmallVector<double> error = x - x_exact; + + REQUIRE(std::sqrt(dot(error, error)) < 1E-10 * std::sqrt(dot(x, x))); + } +} diff --git a/tests/test_PolynomialReconstruction.cpp b/tests/test_PolynomialReconstruction.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9a1df325788e1277b4e0996c0316bb0fe9081ce1 --- /dev/null +++ b/tests/test_PolynomialReconstruction.cpp @@ -0,0 +1,65 @@ +#include <catch2/catch_approx.hpp> +#include <catch2/catch_test_macros.hpp> +#include <catch2/matchers/catch_matchers_all.hpp> + +#include <Kokkos_Core.hpp> + +#include <utils/PugsAssert.hpp> +#include <utils/Types.hpp> + +#include <algebra/SmallMatrix.hpp> +#include <algebra/SmallVector.hpp> +#include <mesh/Mesh.hpp> +#include <mesh/MeshDataManager.hpp> +#include <scheme/DiscreteFunctionP0.hpp> +#include <scheme/PolynomialReconstruction.hpp> + +#include <MeshDataBaseForTests.hpp> + +// clazy:excludeall=non-pod-global-static + +TEST_CASE("PolynomialReconstruction", "[scheme]") +{ + SECTION("1D") + { + using R1 = TinyVector<1>; + + for (auto named_mesh : MeshDataBaseForTests::get().all1DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<1>>(); + auto& mesh = *p_mesh; + + auto affine = [](const R1& x) { return 2.3 + 1.7 * x[0]; }; + auto xj = MeshDataManager::instance().getMeshData(mesh).xj(); + + DiscreteFunctionP0<double> fh{p_mesh}; + + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { fh[cell_id] = affine(xj[cell_id]); }); + + PolynomialReconstruction reconstruction; + auto dpk = reconstruction.build(mesh, fh); + + { + double max_mean_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + max_mean_error = std::max(max_mean_error, std::abs(dpk[cell_id](xj[cell_id]) - affine(xj[cell_id]))); + } + REQUIRE(max_mean_error == Catch::Approx(0).margin(1E-14)); + } + + { + double max_slope_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const double reconstructed_slope = + (dpk[cell_id](R1{0.1} + xj[cell_id]) - dpk[cell_id](xj[cell_id] - R1{0.1})) / 0.2; + + max_slope_error = std::max(max_slope_error, std::abs(reconstructed_slope - 1.7)); + } + REQUIRE(max_slope_error == Catch::Approx(0).margin(1E-14)); + } + } + } + } +}