diff --git a/CMakeLists.txt b/CMakeLists.txt index d3306db95476b2e91773271f548b3937a417b421..bb8832a50565bde6f37ab589737a15e08f116030 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -737,6 +737,7 @@ target_link_libraries( PugsMesh PugsAlgebra PugsScheme + PugsSchemeReconstructionUtils PugsUtils PugsOutput PugsLanguageUtils @@ -774,6 +775,7 @@ target_link_libraries( PugsLanguageModules PugsLanguageUtils PugsScheme + PugsSchemeReconstructionUtils PugsDev PugsAnalysis PugsAlgebra @@ -816,6 +818,7 @@ install(TARGETS PugsMesh PugsOutput PugsScheme + PugsSchemeReconstructionUtils kokkos Catch2 diff --git a/src/algebra/Givens.hpp b/src/algebra/Givens.hpp new file mode 100644 index 0000000000000000000000000000000000000000..dc69a453764659cffa085810846338fc401647a9 --- /dev/null +++ b/src/algebra/Givens.hpp @@ -0,0 +1,161 @@ +#ifndef GIVENS_HPP +#define GIVENS_HPP +#include <utils/Exceptions.hpp> +#include <utils/PugsAssert.hpp> + +#include <cmath> +#include <iomanip> + +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) + { + for (ssize_t i = x.dimension() - 1; i >= 0; --i) { + const double inv_Aii = 1 / A(i, i); + double sum = 0; + for (size_t j = i + 1; j < x.dimension(); ++j) { + sum += A(i, j) * x[j]; + } + x[i] = (b[i] - sum) * inv_Aii; + } + } + + template <typename MatrixType, typename UnknownMatrixType, typename RHSMatrixType> + static void + _liftCollection(const MatrixType& A, const RHSMatrixType& B, UnknownMatrixType& X) + { + for (ssize_t i = X.numberOfRows() - 1; i >= 0; --i) { + const double inv_Aii = 1 / A(i, i); + for (size_t k = 0; k < X.numberOfColumns(); ++k) { + double sum = 0; + for (size_t j = i + 1; j < X.numberOfRows(); ++j) { + sum += A(i, j) * X(j, k); + } + X(i, k) = (B(i, k) - sum) * inv_Aii; + } + } + } + + public: + template <typename MatrixType, typename VectorType, typename RHSVectorType> + static void + solveInPlace(MatrixType& A, VectorType& x, RHSVectorType& b) + { + Assert(x.dimension() == A.numberOfColumns(), "The number of columns of A must be the size of x"); + Assert(b.dimension() == 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; + double s; + _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.dimension() == A.numberOfColumns(), "The number of columns of A must be the size of x"); + Assert(b.dimension() == 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); + } + + template <typename MatrixType, typename UnknownMatrixType, typename RHSMatrixType> + static void + solveCollectionInPlace(MatrixType& A, UnknownMatrixType& X, RHSMatrixType& B) + { + Assert(X.numberOfRows() == A.numberOfColumns(), "The number of columns of A must be the number of rows of X"); + Assert(B.numberOfRows() == A.numberOfRows(), "The number of rows of A must be the rows of B"); + Assert(X.numberOfColumns() == B.numberOfColumns(), "The number of columns of X and B must be the same"); + + for (size_t j = 0; j < A.numberOfColumns(); ++j) { + for (size_t i = A.numberOfRows() - 1; i > j; --i) { + double c; + double s; + _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; + } + + for (size_t l = 0; l < B.numberOfColumns(); ++l) { + double xi_1(0), xi(0); + _rotate(B(i - 1, l), B(i, l), c, s, xi_1, xi); + B(i - 1, l) = xi_1; + B(i, l) = xi; + } + } + } + + _liftCollection(A, B, X); + } + + template <typename MatrixType, typename UnknownMatrixType, typename RHSMatrixType> + static void + solveCollection(const MatrixType& A, UnknownMatrixType& X, const RHSMatrixType& B) + { + Assert(X.numberOfRows() == A.numberOfColumns(), "The number of columns of A must be the number of rows of X"); + Assert(B.numberOfRows() == A.numberOfRows(), "The number of rows of A must be the rows of B"); + Assert(X.numberOfColumns() == B.numberOfColumns(), "The number of columns of X and B must be the same"); + + MatrixType rotateA = copy(A); + RHSMatrixType rotateB = copy(B); + + solveCollectionInPlace(rotateA, X, rotateB); + } + + Givens() = delete; + ~Givens() = delete; +}; + +#endif // GIVENS_HPP diff --git a/src/algebra/ShrinkMatrixView.hpp b/src/algebra/ShrinkMatrixView.hpp new file mode 100644 index 0000000000000000000000000000000000000000..a41089a0af510c088d57a85c3f4606e1709dc2a6 --- /dev/null +++ b/src/algebra/ShrinkMatrixView.hpp @@ -0,0 +1,67 @@ +#ifndef SHRINK_MATRIX_VIEW_HPP +#define SHRINK_MATRIX_VIEW_HPP + +#include <utils/PugsAssert.hpp> +#include <utils/PugsMacros.hpp> + +#include <cstddef> +#include <iostream> +#include <utils/NaNHelper.hpp> + +template <typename MatrixType> +class ShrinkMatrixView +{ + public: + using index_type = typename MatrixType::index_type; + using data_type = typename MatrixType::data_type; + + private: + MatrixType& m_matrix; + const size_t m_nb_rows; + + public: + friend std::ostream& + operator<<(std::ostream& os, const ShrinkMatrixView& A) + { + for (size_t i = 0; i < A.numberOfRows(); ++i) { + os << i << '|'; + for (size_t j = 0; j < A.numberOfColumns(); ++j) { + os << ' ' << j << ':' << NaNHelper(A(i, j)); + } + os << '\n'; + } + return os; + } + + PUGS_INLINE size_t + numberOfRows() const noexcept + { + return m_nb_rows; + } + + PUGS_INLINE size_t + numberOfColumns() const noexcept + { + return m_matrix.numberOfColumns(); + } + + PUGS_INLINE + data_type& + operator()(index_type i, index_type j) const noexcept(NO_ASSERT) + { + Assert(i < m_nb_rows and j < m_matrix.numberOfColumns(), "cannot access element: invalid indices"); + return m_matrix(i, j); + } + + ShrinkMatrixView(MatrixType& matrix, size_t nb_rows) noexcept(NO_ASSERT) : m_matrix{matrix}, m_nb_rows{nb_rows} + { + Assert(m_nb_rows <= matrix.numberOfRows(), "shrink number of rows must be smaller than original matrix's"); + } + + ShrinkMatrixView(const ShrinkMatrixView&) = delete; + ShrinkMatrixView(ShrinkMatrixView&&) = delete; + + ~ShrinkMatrixView() noexcept = default; +}; + +#endif // SHRINK_MATRIX_VIEW_HPP diff --git a/src/algebra/ShrinkVectorView.hpp b/src/algebra/ShrinkVectorView.hpp new file mode 100644 index 0000000000000000000000000000000000000000..83aed7cb2295ee4d20a044f7ba1039f34e7b5ffa --- /dev/null +++ b/src/algebra/ShrinkVectorView.hpp @@ -0,0 +1,60 @@ +#ifndef SHRINK_VECTOR_VIEW_HPP +#define SHRINK_VECTOR_VIEW_HPP + +#include <utils/PugsAssert.hpp> +#include <utils/PugsMacros.hpp> + +#include <cstddef> +#include <iostream> +#include <utils/NaNHelper.hpp> + +template <typename VectorType> +class ShrinkVectorView +{ + public: + using index_type = typename VectorType::index_type; + using data_type = typename VectorType::data_type; + + private: + VectorType& m_vector; + const size_t m_dimension; + + public: + friend std::ostream& + operator<<(std::ostream& os, const ShrinkVectorView& x) + { + if (x.dimension() > 0) { + os << 0 << ':' << NaNHelper(x[0]); + } + for (size_t i = 1; i < x.dimension(); ++i) { + os << ' ' << i << ':' << NaNHelper(x[i]); + } + return os; + } + + PUGS_INLINE size_t + dimension() const noexcept + { + return m_dimension; + } + + PUGS_INLINE + data_type& + operator[](index_type i) const noexcept(NO_ASSERT) + { + Assert(i < m_dimension, "cannot access element: invalid indices"); + return m_vector[i]; + } + + ShrinkVectorView(VectorType& vector, size_t dimension) noexcept(NO_ASSERT) : m_vector{vector}, m_dimension{dimension} + { + Assert(m_dimension <= vector.dimension(), "shrink number of rows must be smaller than original vector's"); + } + + ShrinkVectorView(const ShrinkVectorView&) = delete; + ShrinkVectorView(ShrinkVectorView&&) = delete; + + ~ShrinkVectorView() noexcept = default; +}; + +#endif // SHRINK_VECTOR_VIEW_HPP diff --git a/src/algebra/SmallMatrix.hpp b/src/algebra/SmallMatrix.hpp index 718000c2799067e0084d5bb3460acd688a8642ea..f6bb90e2b9ce0044aa971c991ba3cb740d0761bf 100644 --- a/src/algebra/SmallMatrix.hpp +++ b/src/algebra/SmallMatrix.hpp @@ -28,6 +28,8 @@ class [[nodiscard]] SmallMatrix // LCOV_EXCL_LINE // Allows const version to access our data friend SmallMatrix<std::add_const_t<DataType>>; + // Allows non-const version to access our data + friend SmallMatrix<std::remove_const_t<DataType>>; public: PUGS_INLINE diff --git a/src/algebra/SmallVector.hpp b/src/algebra/SmallVector.hpp index 91093bc67203f29cad05c475e88203b99b743b82..5ffdc106317fb5c2b4300a7a9c606f84d0db4929 100644 --- a/src/algebra/SmallVector.hpp +++ b/src/algebra/SmallVector.hpp @@ -160,6 +160,13 @@ class SmallVector // LCOV_EXCL_LINE return m_values.size(); } + PUGS_INLINE + size_t + dimension() const noexcept + { + return m_values.size(); + } + PUGS_INLINE SmallVector& fill(const DataType& value) noexcept { diff --git a/src/algebra/TinyMatrix.hpp b/src/algebra/TinyMatrix.hpp index 212e5d948315fa07f4c056cbb863d1b575699ac0..fc57d41ae78776aa6c977ae6f0895e1cd9fc0388 100644 --- a/src/algebra/TinyMatrix.hpp +++ b/src/algebra/TinyMatrix.hpp @@ -63,7 +63,7 @@ class [[nodiscard]] TinyMatrix } [[nodiscard]] PUGS_INLINE constexpr friend TinyMatrix<N, M, T> - transpose(const TinyMatrix& A) + transpose(const TinyMatrix& A) noexcept { TinyMatrix<N, M, T> tA; for (size_t i = 0; i < M; ++i) { @@ -75,31 +75,31 @@ class [[nodiscard]] TinyMatrix } [[nodiscard]] PUGS_INLINE constexpr size_t - dimension() const + dimension() const noexcept { return M * N; } [[nodiscard]] PUGS_INLINE constexpr size_t - numberOfValues() const + numberOfValues() const noexcept { return this->dimension(); } [[nodiscard]] PUGS_INLINE constexpr size_t - numberOfRows() const + numberOfRows() const noexcept { return M; } [[nodiscard]] PUGS_INLINE constexpr size_t - numberOfColumns() const + numberOfColumns() const noexcept { return N; } - PUGS_INLINE constexpr TinyMatrix - operator-() const + [[nodiscard]] PUGS_INLINE constexpr TinyMatrix + operator-() const noexcept { TinyMatrix opposite; for (size_t i = 0; i < M * N; ++i) { @@ -108,9 +108,8 @@ class [[nodiscard]] TinyMatrix return opposite; } - PUGS_INLINE - constexpr friend TinyMatrix - operator*(const T& t, const TinyMatrix& A) + [[nodiscard]] PUGS_INLINE constexpr friend TinyMatrix + operator*(const T& t, const TinyMatrix& A) noexcept { TinyMatrix B = A; return B *= t; @@ -118,14 +117,14 @@ class [[nodiscard]] TinyMatrix PUGS_INLINE constexpr friend TinyMatrix - operator*(const T& t, TinyMatrix&& A) + operator*(const T& t, TinyMatrix&& A) noexcept { return std::move(A *= t); } PUGS_INLINE constexpr TinyMatrix& - operator*=(const T& t) + operator*=(const T& t) noexcept { for (size_t i = 0; i < M * N; ++i) { m_values[i] *= t; @@ -144,8 +143,8 @@ class [[nodiscard]] TinyMatrix } template <size_t P> - PUGS_INLINE constexpr TinyMatrix<M, P, T> - operator*(const TinyMatrix<N, P, T>& B) const + [[nodiscard]] PUGS_INLINE constexpr TinyMatrix<M, P, T> + operator*(const TinyMatrix<N, P, T>& B) const noexcept { const TinyMatrix& A = *this; TinyMatrix<M, P, T> AB; @@ -161,9 +160,8 @@ class [[nodiscard]] TinyMatrix return AB; } - PUGS_INLINE - constexpr TinyVector<M, T> - operator*(const TinyVector<N, T>& x) const + [[nodiscard]] PUGS_INLINE constexpr TinyVector<M, T> + operator*(const TinyVector<N, T>& x) const noexcept { const TinyMatrix& A = *this; TinyVector<M, T> Ax; @@ -198,7 +196,7 @@ class [[nodiscard]] TinyMatrix } [[nodiscard]] PUGS_INLINE constexpr bool - operator==(const TinyMatrix& A) const + operator==(const TinyMatrix& A) const noexcept { for (size_t i = 0; i < M * N; ++i) { if (m_values[i] != A.m_values[i]) @@ -208,14 +206,13 @@ class [[nodiscard]] TinyMatrix } [[nodiscard]] PUGS_INLINE constexpr bool - operator!=(const TinyMatrix& A) const + operator!=(const TinyMatrix& A) const noexcept { return not this->operator==(A); } - PUGS_INLINE - constexpr TinyMatrix - operator+(const TinyMatrix& A) const + [[nodiscard]] PUGS_INLINE constexpr TinyMatrix + operator+(const TinyMatrix& A) const noexcept { TinyMatrix sum; for (size_t i = 0; i < M * N; ++i) { @@ -224,17 +221,15 @@ class [[nodiscard]] TinyMatrix return sum; } - PUGS_INLINE - constexpr TinyMatrix - operator+(TinyMatrix&& A) const + [[nodiscard]] PUGS_INLINE constexpr TinyMatrix + operator+(TinyMatrix&& A) const noexcept { A += *this; return std::move(A); } - PUGS_INLINE - constexpr TinyMatrix - operator-(const TinyMatrix& A) const + [[nodiscard]] PUGS_INLINE constexpr TinyMatrix + operator-(const TinyMatrix& A) const noexcept { TinyMatrix difference; for (size_t i = 0; i < M * N; ++i) { @@ -243,9 +238,8 @@ class [[nodiscard]] TinyMatrix return difference; } - PUGS_INLINE - constexpr TinyMatrix - operator-(TinyMatrix&& A) const + [[nodiscard]] PUGS_INLINE constexpr TinyMatrix + operator-(TinyMatrix&& A) const noexcept { for (size_t i = 0; i < M * N; ++i) { A.m_values[i] = m_values[i] - A.m_values[i]; @@ -255,7 +249,7 @@ class [[nodiscard]] TinyMatrix PUGS_INLINE constexpr TinyMatrix& - operator+=(const TinyMatrix& A) + operator+=(const TinyMatrix& A) noexcept { for (size_t i = 0; i < M * N; ++i) { m_values[i] += A.m_values[i]; @@ -265,7 +259,7 @@ class [[nodiscard]] TinyMatrix PUGS_INLINE constexpr TinyMatrix& - operator-=(const TinyMatrix& A) + operator-=(const TinyMatrix& A) noexcept { for (size_t i = 0; i < M * N; ++i) { m_values[i] -= A.m_values[i]; @@ -376,7 +370,7 @@ class [[nodiscard]] TinyMatrix template <size_t M, size_t N, typename T> [[nodiscard]] PUGS_INLINE constexpr TinyMatrix<M, N, T> -tensorProduct(const TinyVector<M, T>& x, const TinyVector<N, T>& y) +tensorProduct(const TinyVector<M, T>& x, const TinyVector<N, T>& y) noexcept { TinyMatrix<M, N, T> A; for (size_t i = 0; i < M; ++i) { @@ -389,7 +383,8 @@ tensorProduct(const TinyVector<M, T>& x, const TinyVector<N, T>& y) template <size_t N, typename T> [[nodiscard]] PUGS_INLINE constexpr T -det(const TinyMatrix<N, N, T>& A) + +det(const TinyMatrix<N, N, T>& A) noexcept { static_assert(std::is_arithmetic<T>::value, "determinant is not defined for non-arithmetic types"); static_assert(std::is_floating_point<T>::value, "determinant for arbitrary dimension N is defined for floating " @@ -440,7 +435,7 @@ det(const TinyMatrix<N, N, T>& A) template <typename T> [[nodiscard]] PUGS_INLINE constexpr T -det(const TinyMatrix<1, 1, T>& A) +det(const TinyMatrix<1, 1, T>& A) noexcept { static_assert(std::is_arithmetic<T>::value, "determinent is not defined for non-arithmetic types"); return A(0, 0); @@ -448,7 +443,7 @@ det(const TinyMatrix<1, 1, T>& A) template <typename T> [[nodiscard]] PUGS_INLINE constexpr T -det(const TinyMatrix<2, 2, T>& A) +det(const TinyMatrix<2, 2, T>& A) noexcept { static_assert(std::is_arithmetic<T>::value, "determinent is not defined for non-arithmetic types"); return A(0, 0) * A(1, 1) - A(1, 0) * A(0, 1); @@ -456,7 +451,7 @@ det(const TinyMatrix<2, 2, T>& A) template <typename T> [[nodiscard]] PUGS_INLINE constexpr T -det(const TinyMatrix<3, 3, T>& A) +det(const TinyMatrix<3, 3, T>& A) noexcept { static_assert(std::is_arithmetic<T>::value, "determinent is not defined for non-arithmetic types"); return A(0, 0) * (A(1, 1) * A(2, 2) - A(2, 1) * A(1, 2)) - A(1, 0) * (A(0, 1) * A(2, 2) - A(2, 1) * A(0, 2)) + @@ -465,7 +460,7 @@ det(const TinyMatrix<3, 3, T>& A) template <size_t M, size_t N, typename T> [[nodiscard]] PUGS_INLINE constexpr TinyMatrix<M - 1, N - 1, T> -getMinor(const TinyMatrix<M, N, T>& A, size_t I, size_t J) +getMinor(const TinyMatrix<M, N, T>& A, size_t I, size_t J) noexcept(NO_ASSERT) { static_assert(M >= 2 and N >= 2, "minor calculation requires at least 2x2 matrices"); Assert((I < M) and (J < N)); @@ -491,7 +486,7 @@ getMinor(const TinyMatrix<M, N, T>& A, size_t I, size_t J) template <size_t N, typename T> [[nodiscard]] PUGS_INLINE T -trace(const TinyMatrix<N, N, T>& A) +trace(const TinyMatrix<N, N, T>& A) noexcept { static_assert(std::is_arithmetic<T>::value, "trace is not defined for non-arithmetic types"); @@ -517,7 +512,7 @@ template <size_t N, typename T> template <typename T> [[nodiscard]] PUGS_INLINE constexpr TinyMatrix<1, 1, T> -inverse(const TinyMatrix<1, 1, T>& A) +inverse(const TinyMatrix<1, 1, T>& A) noexcept { static_assert(std::is_arithmetic<T>::value, "inverse is not defined for non-arithmetic types"); static_assert(std::is_floating_point<T>::value, "inverse is defined for floating point types only"); @@ -528,7 +523,7 @@ inverse(const TinyMatrix<1, 1, T>& A) template <size_t N, typename T> [[nodiscard]] PUGS_INLINE constexpr T -cofactor(const TinyMatrix<N, N, T>& A, size_t i, size_t j) +cofactor(const TinyMatrix<N, N, T>& A, size_t i, size_t j) noexcept(NO_ASSERT) { static_assert(std::is_arithmetic<T>::value, "cofactor is not defined for non-arithmetic types"); const T sign = ((i + j) % 2) ? -1 : 1; @@ -538,7 +533,7 @@ cofactor(const TinyMatrix<N, N, T>& A, size_t i, size_t j) template <typename T> [[nodiscard]] PUGS_INLINE constexpr TinyMatrix<2, 2, T> -inverse(const TinyMatrix<2, 2, T>& A) +inverse(const TinyMatrix<2, 2, T>& A) noexcept { static_assert(std::is_arithmetic<T>::value, "inverse is not defined for non-arithmetic types"); static_assert(std::is_floating_point<T>::value, "inverse is defined for floating point types only"); @@ -552,7 +547,7 @@ inverse(const TinyMatrix<2, 2, T>& A) template <typename T> [[nodiscard]] PUGS_INLINE constexpr TinyMatrix<3, 3, T> -inverse(const TinyMatrix<3, 3, T>& A) +inverse(const TinyMatrix<3, 3, T>& A) noexcept(NO_ASSERT) { static_assert(std::is_arithmetic<T>::value, "inverse is not defined for non-arithmetic types"); static_assert(std::is_floating_point<T>::value, "inverse is defined for floating point types only"); diff --git a/src/algebra/TinyVector.hpp b/src/algebra/TinyVector.hpp index be0efac46ede1211f1519d0e5b9760bcfa20b72d..e6d33fe26c46bdbcef4283dfd318ace9d990c40e 100644 --- a/src/algebra/TinyVector.hpp +++ b/src/algebra/TinyVector.hpp @@ -34,7 +34,7 @@ class [[nodiscard]] TinyVector public: [[nodiscard]] PUGS_INLINE constexpr TinyVector - operator-() const + operator-() const noexcept { TinyVector opposite; for (size_t i = 0; i < N; ++i) { @@ -44,13 +44,13 @@ class [[nodiscard]] TinyVector } [[nodiscard]] PUGS_INLINE constexpr size_t - dimension() const + dimension() const noexcept { return N; } [[nodiscard]] PUGS_INLINE constexpr bool - operator==(const TinyVector& v) const + operator==(const TinyVector& v) const noexcept { for (size_t i = 0; i < N; ++i) { if (m_values[i] != v.m_values[i]) @@ -60,13 +60,13 @@ class [[nodiscard]] TinyVector } [[nodiscard]] PUGS_INLINE constexpr bool - operator!=(const TinyVector& v) const + operator!=(const TinyVector& v) const noexcept { return not this->operator==(v); } [[nodiscard]] PUGS_INLINE constexpr friend T - dot(const TinyVector& u, const TinyVector& v) + dot(const TinyVector& u, const TinyVector& v) noexcept { T t = u.m_values[0] * v.m_values[0]; for (size_t i = 1; i < N; ++i) { @@ -77,7 +77,7 @@ class [[nodiscard]] TinyVector PUGS_INLINE constexpr TinyVector& - operator*=(const T& t) + operator*=(const T& t) noexcept { for (size_t i = 0; i < N; ++i) { m_values[i] *= t; @@ -85,17 +85,15 @@ class [[nodiscard]] TinyVector return *this; } - PUGS_INLINE - constexpr friend TinyVector - operator*(const T& t, const TinyVector& v) + [[nodiscard]] PUGS_INLINE constexpr friend TinyVector + operator*(const T& t, const TinyVector& v) noexcept { TinyVector w = v; return w *= t; } - PUGS_INLINE - constexpr friend TinyVector - operator*(const T& t, TinyVector&& v) + [[nodiscard]] PUGS_INLINE constexpr friend TinyVector + operator*(const T& t, TinyVector&& v) noexcept { v *= t; return std::move(v); @@ -113,9 +111,8 @@ class [[nodiscard]] TinyVector return os; } - PUGS_INLINE - constexpr TinyVector - operator+(const TinyVector& v) const + [[nodiscard]] PUGS_INLINE constexpr TinyVector + operator+(const TinyVector& v) const noexcept { TinyVector sum; for (size_t i = 0; i < N; ++i) { @@ -124,9 +121,8 @@ class [[nodiscard]] TinyVector return sum; } - PUGS_INLINE - constexpr TinyVector - operator+(TinyVector&& v) const + [[nodiscard]] PUGS_INLINE constexpr TinyVector + operator+(TinyVector&& v) const noexcept { for (size_t i = 0; i < N; ++i) { v.m_values[i] += m_values[i]; @@ -134,9 +130,8 @@ class [[nodiscard]] TinyVector return std::move(v); } - PUGS_INLINE - constexpr TinyVector - operator-(const TinyVector& v) const + [[nodiscard]] PUGS_INLINE constexpr TinyVector + operator-(const TinyVector& v) const noexcept { TinyVector difference; for (size_t i = 0; i < N; ++i) { @@ -145,9 +140,8 @@ class [[nodiscard]] TinyVector return difference; } - PUGS_INLINE - constexpr TinyVector - operator-(TinyVector&& v) const + [[nodiscard]] PUGS_INLINE constexpr TinyVector + operator-(TinyVector&& v) const noexcept { for (size_t i = 0; i < N; ++i) { v.m_values[i] = m_values[i] - v.m_values[i]; @@ -155,9 +149,8 @@ class [[nodiscard]] TinyVector return std::move(v); } - PUGS_INLINE - constexpr TinyVector& - operator+=(const TinyVector& v) + PUGS_INLINE constexpr TinyVector& + operator+=(const TinyVector& v) noexcept { for (size_t i = 0; i < N; ++i) { m_values[i] += v.m_values[i]; @@ -167,7 +160,7 @@ class [[nodiscard]] TinyVector PUGS_INLINE constexpr TinyVector& - operator-=(const TinyVector& v) + operator-=(const TinyVector& v) noexcept { for (size_t i = 0; i < N; ++i) { m_values[i] -= v.m_values[i]; @@ -175,16 +168,14 @@ class [[nodiscard]] TinyVector return *this; } - PUGS_INLINE - constexpr T& + [[nodiscard]] PUGS_INLINE constexpr T& operator[](size_t i) noexcept(NO_ASSERT) { Assert(i < N); return m_values[i]; } - PUGS_INLINE - constexpr const T& + [[nodiscard]] PUGS_INLINE constexpr const T& operator[](size_t i) const noexcept(NO_ASSERT) { Assert(i < N); @@ -248,8 +239,8 @@ class [[nodiscard]] TinyVector }; template <size_t N, typename T> -[[nodiscard]] PUGS_INLINE constexpr decltype(std::sqrt(std::declval<T>())) -l2Norm(const TinyVector<N, T>& x) +[[nodiscard]] PUGS_INLINE constexpr auto +l2Norm(const TinyVector<N, T>& x) noexcept { static_assert(std::is_arithmetic<T>(), "Cannot compute L2 norm for non-arithmetic types"); static_assert(std::is_floating_point<T>::value, "L2 norm is defined for floating point types only"); @@ -258,7 +249,7 @@ l2Norm(const TinyVector<N, T>& x) template <size_t N, typename T> [[nodiscard]] PUGS_INLINE constexpr T -min(const TinyVector<N, T>& x) +min(const TinyVector<N, T>& x) noexcept { T m = x[0]; for (size_t i = 1; i < N; ++i) { @@ -271,7 +262,7 @@ min(const TinyVector<N, T>& x) template <size_t N, typename T> [[nodiscard]] PUGS_INLINE constexpr T -max(const TinyVector<N, T>& x) +max(const TinyVector<N, T>& x) noexcept { T m = x[0]; for (size_t i = 1; i < N; ++i) { @@ -285,7 +276,7 @@ max(const TinyVector<N, T>& x) // Cross product is only defined for dimension 3 vectors template <typename T> [[nodiscard]] PUGS_INLINE constexpr TinyVector<3, T> -crossProduct(const TinyVector<3, T>& u, const TinyVector<3, T>& v) +crossProduct(const TinyVector<3, T>& u, const TinyVector<3, T>& v) noexcept { TinyVector<3, T> cross_product(u[1] * v[2] - u[2] * v[1], u[2] * v[0] - u[0] * v[2], u[0] * v[1] - u[1] * v[0]); return cross_product; diff --git a/src/geometry/LineTransformation.hpp b/src/geometry/LineTransformation.hpp index ae0b00a8a1dce7e4b6b6bb13a737ee1114a202f1..4576c0b64904ecb489caeb456b10271be2a076cc 100644 --- a/src/geometry/LineTransformation.hpp +++ b/src/geometry/LineTransformation.hpp @@ -24,12 +24,20 @@ class LineTransformation<1> return m_jacobian * x + m_shift; } + PUGS_INLINE double jacobianDeterminant() const { return m_jacobian; } + PUGS_INLINE + double + jacobianDeterminant(const TinyVector<1>&) const + { + return m_jacobian; + } + PUGS_INLINE LineTransformation(const TinyVector<Dimension>& a, const TinyVector<Dimension>& b) { @@ -59,6 +67,13 @@ class LineTransformation return x[0] * m_velocity + m_shift; } + PUGS_INLINE + const TinyVector<Dimension>& + velocity() const + { + return m_velocity; + } + double velocityNorm() const { diff --git a/src/geometry/SquareTransformation.hpp b/src/geometry/SquareTransformation.hpp index d6474f33e4ecbaa4bcfba525dadc9fd38e95962d..b0e970c6eda673b48c5cf5c0d0a1ad7e73f8ef74 100644 --- a/src/geometry/SquareTransformation.hpp +++ b/src/geometry/SquareTransformation.hpp @@ -84,6 +84,25 @@ class SquareTransformation return m_coefficients * X + m_shift; } + PUGS_INLINE + TinyVector<Dimension> + areaVariation(const TinyVector<2>& X) const + { + const auto& T = m_coefficients; + const double& x = X[0]; + const double& y = X[1]; + + const TinyVector<Dimension> dxT{T(0, 0) + T(0, 2) * y, // + T(1, 0) + T(1, 2) * y, // + T(2, 0) + T(2, 2) * y}; + + const TinyVector<Dimension> dyT{T(0, 1) + T(0, 2) * x, // + T(1, 1) + T(1, 2) * x, // + T(2, 1) + T(2, 2) * x}; + + return crossProduct(dxT, dyT); + } + PUGS_INLINE double areaVariationNorm(const TinyVector<2>& X) const { diff --git a/src/geometry/SymmetryUtils.hpp b/src/geometry/SymmetryUtils.hpp new file mode 100644 index 0000000000000000000000000000000000000000..6524a0791aef79b5440110d7180a472865c12d89 --- /dev/null +++ b/src/geometry/SymmetryUtils.hpp @@ -0,0 +1,32 @@ +#ifndef SYMMETRY_UTILS_HPP +#define SYMMETRY_UTILS_HPP + +#include <algebra/TinyMatrix.hpp> +#include <algebra/TinyVector.hpp> +#include <utils/PugsMacros.hpp> + +template <size_t Dimension> +PUGS_INLINE auto +symmetrize_vector(const TinyVector<Dimension>& normal, const TinyVector<Dimension>& u) +{ + return u - 2 * dot(u, normal) * normal; +} + +template <size_t Dimension> +PUGS_INLINE auto +symmetrize_matrix(const TinyVector<Dimension>& normal, const TinyMatrix<Dimension>& A) +{ + const TinyMatrix S = TinyMatrix<Dimension>{identity} - 2 * tensorProduct(normal, normal); + return S * A * S; +} + +template <size_t Dimension> +PUGS_INLINE auto +symmetrize_coordinates(const TinyVector<Dimension>& origin, + const TinyVector<Dimension>& normal, + const TinyVector<Dimension>& u) +{ + return u - 2 * dot(u - origin, normal) * normal; +} + +#endif // SYMMETRY_UTILS_HPP diff --git a/src/geometry/TetrahedronTransformation.hpp b/src/geometry/TetrahedronTransformation.hpp index 64244266957a350e8be8f78c2eab3d5fead629e1..a81a344bfbabd0999e45b1bcab0b52519f9db4ed 100644 --- a/src/geometry/TetrahedronTransformation.hpp +++ b/src/geometry/TetrahedronTransformation.hpp @@ -24,12 +24,20 @@ class TetrahedronTransformation return m_jacobian * x + m_shift; } + PUGS_INLINE double jacobianDeterminant() const { return m_jacobian_determinant; } + PUGS_INLINE + double + jacobianDeterminant(const TinyVector<3>&) const + { + return m_jacobian_determinant; + } + PUGS_INLINE TetrahedronTransformation(const TinyVector<Dimension>& a, const TinyVector<Dimension>& b, diff --git a/src/geometry/TriangleTransformation.hpp b/src/geometry/TriangleTransformation.hpp index 9210e0bda55bb19dfdfd877e509801fa87f0a4f0..c49db368684d8c96aeb5061041828799e8509fac 100644 --- a/src/geometry/TriangleTransformation.hpp +++ b/src/geometry/TriangleTransformation.hpp @@ -26,12 +26,20 @@ class TriangleTransformation<2> return m_jacobian * x + m_shift; } + PUGS_INLINE double jacobianDeterminant() const { return m_jacobian_determinant; } + PUGS_INLINE + double + jacobianDeterminant(const TinyVector<2>&) const + { + return m_jacobian_determinant; + } + PUGS_INLINE TriangleTransformation(const TinyVector<Dimension>& a, const TinyVector<Dimension>& b, const TinyVector<Dimension>& c) { @@ -58,6 +66,7 @@ class TriangleTransformation private: TinyMatrix<Dimension, 2> m_jacobian; TinyVector<Dimension> m_shift; + TinyVector<Dimension> m_area_variation; double m_area_variation_norm; public: @@ -68,6 +77,21 @@ class TriangleTransformation return m_jacobian * x + m_shift; } + PUGS_INLINE + TinyVector<Dimension> + areaVariation() const + { + return m_area_variation; + } + + PUGS_INLINE + TinyVector<Dimension> + areaVariation(const TinyVector<2>&) const + { + return m_area_variation; + } + + PUGS_INLINE double areaVariationNorm() const { @@ -84,7 +108,8 @@ class TriangleTransformation m_shift = a; - m_area_variation_norm = l2Norm(crossProduct(b - a, c - a)); + m_area_variation = crossProduct(b - a, c - a); + m_area_variation_norm = l2Norm(m_area_variation); } ~TriangleTransformation() = default; diff --git a/src/language/utils/EmbeddedDiscreteFunctionMathFunctions.cpp b/src/language/utils/EmbeddedDiscreteFunctionMathFunctions.cpp index 5d000813aeff1e670a2f547a69827a679cbfa699..892037219c389e6ff7ebd28e8b5afd586845c14f 100644 --- a/src/language/utils/EmbeddedDiscreteFunctionMathFunctions.cpp +++ b/src/language/utils/EmbeddedDiscreteFunctionMathFunctions.cpp @@ -234,10 +234,14 @@ dot(const std::shared_ptr<const DiscreteFunctionVariant>& f_v, throw NormalError(EmbeddedDiscreteFunctionUtils::invalidOperandType(f)); } } else if constexpr (is_discrete_function_P0_vector_v<TypeOfF>) { - if (f.size() == g.size()) { - return std::make_shared<DiscreteFunctionVariant>(dot(f, g)); + if constexpr (std::is_arithmetic_v<DataType>) { + if (f.size() == g.size()) { + return std::make_shared<DiscreteFunctionVariant>(dot(f, g)); + } else { + throw NormalError("operands have different dimension"); + } } else { - throw NormalError("operands have different dimension"); + throw NormalError(EmbeddedDiscreteFunctionUtils::invalidOperandType(f)); } } else { throw NormalError(EmbeddedDiscreteFunctionUtils::invalidOperandType(f)); @@ -691,8 +695,6 @@ sum_of_Vh_components(const std::shared_ptr<const DiscreteFunctionVariant>& f) [&](auto&& discrete_function) -> std::shared_ptr<const DiscreteFunctionVariant> { using DiscreteFunctionT = std::decay_t<decltype(discrete_function)>; if constexpr (is_discrete_function_P0_vector_v<DiscreteFunctionT>) { - using DataType = std::decay_t<typename DiscreteFunctionT::data_type>; - static_assert(std::is_same_v<DataType, double>); return std::make_shared<DiscreteFunctionVariant>(sumOfComponents(discrete_function)); } else { throw NormalError(EmbeddedDiscreteFunctionUtils::invalidOperandType(f)); diff --git a/src/main.cpp b/src/main.cpp index cd576a9f63a8273b366a76a298b508e2fbb1ff54..cbaf6277e5ee0c1c7d689403e8d78cae54c3ed5f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,6 +5,7 @@ #include <mesh/DualConnectivityManager.hpp> #include <mesh/DualMeshManager.hpp> #include <mesh/MeshDataManager.hpp> +#include <mesh/StencilManager.hpp> #include <mesh/SynchronizerManager.hpp> #include <utils/ExecutionStatManager.hpp> #include <utils/GlobalVariableManager.hpp> @@ -21,6 +22,8 @@ main(int argc, char* argv[]) ResumingManager::create(); ParallelChecker::create(); + GlobalVariableManager::create(); + std::string filename = initialize(argc, argv); PluginsLoader plugins_loader; @@ -31,16 +34,14 @@ main(int argc, char* argv[]) MeshDataManager::create(); DualConnectivityManager::create(); DualMeshManager::create(); - - GlobalVariableManager::create(); + StencilManager::create(); parser(filename); ExecutionStatManager::printInfo(); ModuleRepository::destroy(); - GlobalVariableManager::destroy(); - + StencilManager::destroy(); DualMeshManager::destroy(); DualConnectivityManager::destroy(); MeshDataManager::destroy(); @@ -50,6 +51,8 @@ main(int argc, char* argv[]) finalize(); + GlobalVariableManager::destroy(); + ParallelChecker::destroy(); ResumingManager::destroy(); diff --git a/src/mesh/CMakeLists.txt b/src/mesh/CMakeLists.txt index bd45f495eaf08c8b9e000649a4500ddb146aac95..68c6de46daaac99375fcb6b1d3d4e61bffdd83c8 100644 --- a/src/mesh/CMakeLists.txt +++ b/src/mesh/CMakeLists.txt @@ -44,6 +44,8 @@ add_library( MeshTransformer.cpp MeshUtils.cpp MeshVariant.cpp + StencilBuilder.cpp + StencilManager.cpp SynchronizerManager.cpp ) diff --git a/src/mesh/Connectivity.cpp b/src/mesh/Connectivity.cpp index 44c07b62877451940e975fbc929b6da1530a36f4..029b590f3ddb03225a45de45ddc732c6814d3603 100644 --- a/src/mesh/Connectivity.cpp +++ b/src/mesh/Connectivity.cpp @@ -2,6 +2,7 @@ #include <mesh/ConnectivityDescriptor.hpp> #include <mesh/ItemValueUtils.hpp> +#include <mesh/StencilManager.hpp> #include <utils/GlobalVariableManager.hpp> #include <utils/Messenger.hpp> @@ -51,8 +52,7 @@ Connectivity<Dimension>::_buildFrom(const ConnectivityDescriptor& descriptor) { const int rank = parallel::rank(); WeakCellValue<bool> cell_is_owned(*this); - parallel_for( - this->numberOfCells(), PUGS_CLASS_LAMBDA(CellId j) { cell_is_owned[j] = (m_cell_owner[j] == rank); }); + parallel_for(this->numberOfCells(), PUGS_CLASS_LAMBDA(CellId j) { cell_is_owned[j] = (m_cell_owner[j] == rank); }); m_cell_is_owned = cell_is_owned; } @@ -62,8 +62,7 @@ Connectivity<Dimension>::_buildFrom(const ConnectivityDescriptor& descriptor) { const int rank = parallel::rank(); WeakNodeValue<bool> node_is_owned(*this, node_is_owned_array); - parallel_for( - this->numberOfNodes(), PUGS_CLASS_LAMBDA(NodeId r) { node_is_owned[r] = (m_node_owner[r] == rank); }); + parallel_for(this->numberOfNodes(), PUGS_CLASS_LAMBDA(NodeId r) { node_is_owned[r] = (m_node_owner[r] == rank); }); m_node_is_owned = node_is_owned; } @@ -376,6 +375,12 @@ Connectivity<Dimension>::ownCellToCellGraph() const return CRSGraph(entries, neighbors); } +template <size_t Dimension> +Connectivity<Dimension>::~Connectivity() +{ + StencilManager::instance().deleteConnectivity(this->m_id); +} + template std::ostream& Connectivity<1>::_write(std::ostream&) const; template std::ostream& Connectivity<2>::_write(std::ostream&) const; template std::ostream& Connectivity<3>::_write(std::ostream&) const; @@ -396,6 +401,10 @@ template Connectivity<1>::Connectivity(); template Connectivity<2>::Connectivity(); template Connectivity<3>::Connectivity(); +template Connectivity<1>::~Connectivity(); +template Connectivity<2>::~Connectivity(); +template Connectivity<3>::~Connectivity(); + template void Connectivity<1>::_buildFrom(const ConnectivityDescriptor&); template void Connectivity<2>::_buildFrom(const ConnectivityDescriptor&); template void Connectivity<3>::_buildFrom(const ConnectivityDescriptor&); diff --git a/src/mesh/Connectivity.hpp b/src/mesh/Connectivity.hpp index 6d89a820fc25f2ed662f0e4f277a7f44fb136830..d7b8a608464aef2ffe4ab870520488114948d12a 100644 --- a/src/mesh/Connectivity.hpp +++ b/src/mesh/Connectivity.hpp @@ -707,7 +707,7 @@ class Connectivity final : public IConnectivity void _buildFrom(const ConnectivityDescriptor& descriptor); public: - ~Connectivity() = default; + ~Connectivity(); }; template <size_t Dimension> diff --git a/src/mesh/ConnectivityDispatcher.cpp b/src/mesh/ConnectivityDispatcher.cpp index 1270dd78f39183d198e7155198e904799203398a..d87d7f640de7df65882b9880dbe12f2646372096 100644 --- a/src/mesh/ConnectivityDispatcher.cpp +++ b/src/mesh/ConnectivityDispatcher.cpp @@ -2,9 +2,9 @@ #include <mesh/ItemOfItemType.hpp> #include <utils/CRSGraph.hpp> +#include <utils/GlobalVariableManager.hpp> #include <utils/Partitioner.hpp> -#include <iostream> #include <unordered_set> template <size_t Dimension> @@ -86,11 +86,69 @@ ConnectivityDispatcher<Dimension>::_buildItemListToSend() std::vector<std::vector<CellId>> cell_vector_to_send_by_proc(parallel::size()); Array<bool> send_to_rank(parallel::size()); + NodeValue<bool> node_tag{m_connectivity}; + node_tag.fill(false); + std::vector<NodeId> layer_node_id_list; + std::vector<NodeId> tagged_node_id_list; + + const size_t nb_layers = GlobalVariableManager::instance().getNumberOfGhostLayers(); + for (CellId cell_id = 0; cell_id < m_connectivity.numberOfCells(); ++cell_id) { + layer_node_id_list.clear(); send_to_rank.fill(false); const auto& cell_to_node = cell_to_node_matrix[cell_id]; if (cell_is_owned[cell_id]) { + for (size_t i_node = 0; i_node < cell_to_node.size(); ++i_node) { + const NodeId& node_id = cell_to_node[i_node]; + const auto& node_to_cell = node_to_cell_matrix[node_id]; + for (size_t i_node_cell = 0; i_node_cell < node_to_cell.size(); ++i_node_cell) { + const CellId& node_cell_id = node_to_cell[i_node_cell]; + send_to_rank[cell_new_owner[node_cell_id]] = true; + const NodeId cell_node_id = cell_to_node[i_node]; + layer_node_id_list.push_back(cell_node_id); + node_tag[cell_node_id] = true; + } + } + + for (size_t i_layer = 0; i_layer < nb_layers; ++i_layer) { + for (const auto node_id : layer_node_id_list) { + tagged_node_id_list.push_back(node_id); + const auto& node_to_cell = node_to_cell_matrix[node_id]; + for (size_t i_node_cell = 0; i_node_cell < node_to_cell.size(); ++i_node_cell) { + const CellId& node_cell_id = node_to_cell[i_node_cell]; + send_to_rank[cell_new_owner[node_cell_id]] = true; + } + } + + if (i_layer + 1 < nb_layers) { + std::vector<NodeId> old_layer_node_id_list; + std::swap(layer_node_id_list, old_layer_node_id_list); + + for (const auto node_id : old_layer_node_id_list) { + const auto& node_to_cell = node_to_cell_matrix[node_id]; + for (size_t i_node_cell = 0; i_node_cell < node_to_cell.size(); ++i_node_cell) { + const CellId& node_cell_id = node_to_cell[i_node_cell]; + const auto& node_cell_to_node = cell_to_node_matrix[node_cell_id]; + for (size_t i_node = 0; i_node < node_cell_to_node.size(); ++i_node) { + const NodeId cell_node_id = node_cell_to_node[i_node]; + if (not node_tag[cell_node_id]) { + layer_node_id_list.push_back(cell_node_id); + node_tag[cell_node_id] = true; + } + } + } + } + } else { + layer_node_id_list.clear(); + } + } + + for (auto node_id : tagged_node_id_list) { + node_tag[node_id] = false; + } + tagged_node_id_list.clear(); + for (size_t i_node = 0; i_node < cell_to_node.size(); ++i_node) { const NodeId& node_id = cell_to_node[i_node]; const auto& node_to_cell = node_to_cell_matrix[node_id]; diff --git a/src/mesh/ConnectivityMatrix.hpp b/src/mesh/ConnectivityMatrix.hpp index eadc85347cb756932616e655d7b625a2a15505b7..aabd8de949d3e1d2b6c3532876ea3da059d30701 100644 --- a/src/mesh/ConnectivityMatrix.hpp +++ b/src/mesh/ConnectivityMatrix.hpp @@ -65,7 +65,7 @@ class ConnectivityMatrix Assert(m_row_map[0] == 0, "row map should start with 0"); #ifndef NDEBUG for (size_t i = 1; i < m_row_map.size(); ++i) { - Assert(m_row_map[i] > m_row_map[i - 1], "row map values must be strictly increasing"); + Assert(m_row_map[i] >= m_row_map[i - 1], "row map values must be increasing"); } #endif // NDEBUG } diff --git a/src/mesh/ItemToItemMatrix.hpp b/src/mesh/ItemToItemMatrix.hpp index 6cd2d798b6eaa2844d4eae0d33f8405d344593b7..5d9c23d6fee898b87808dcd9361018cf275130ce 100644 --- a/src/mesh/ItemToItemMatrix.hpp +++ b/src/mesh/ItemToItemMatrix.hpp @@ -20,8 +20,8 @@ class ItemToItemMatrix private: using IndexType = typename ConnectivityMatrix::IndexType; - const IndexType* const m_values; const size_t m_size; + const IndexType* const m_values; public: PUGS_INLINE @@ -40,8 +40,9 @@ class ItemToItemMatrix PUGS_INLINE UnsafeSubItemArray(const ConnectivityMatrix& connectivity_matrix, SourceItemId source_item_id) - : m_values{&(connectivity_matrix.values()[connectivity_matrix.rowsMap()[source_item_id]])}, - m_size(connectivity_matrix.rowsMap()[source_item_id + 1] - connectivity_matrix.rowsMap()[source_item_id]) + : m_size(connectivity_matrix.rowsMap()[source_item_id + 1] - connectivity_matrix.rowsMap()[source_item_id]), + m_values{(m_size == 0) ? nullptr + : (&(connectivity_matrix.values()[connectivity_matrix.rowsMap()[source_item_id]]))} {} PUGS_INLINE diff --git a/src/mesh/MeshFlatEdgeBoundary.cpp b/src/mesh/MeshFlatEdgeBoundary.cpp index 7a2d4918d9c822303ba6debee91b5f4d707569c4..9c93f2bc796fd9770440cdbf0c88e435aa55ec07 100644 --- a/src/mesh/MeshFlatEdgeBoundary.cpp +++ b/src/mesh/MeshFlatEdgeBoundary.cpp @@ -11,7 +11,7 @@ getMeshFlatEdgeBoundary(const MeshType& mesh, const IBoundaryDescriptor& boundar MeshEdgeBoundary mesh_edge_boundary = getMeshEdgeBoundary(mesh, boundary_descriptor); MeshFlatNodeBoundary mesh_flat_node_boundary = getMeshFlatNodeBoundary(mesh, boundary_descriptor); - return MeshFlatEdgeBoundary<MeshType>{mesh, mesh_edge_boundary.refEdgeList(), + return MeshFlatEdgeBoundary<MeshType>{mesh, mesh_edge_boundary.refEdgeList(), mesh_flat_node_boundary.origin(), mesh_flat_node_boundary.outgoingNormal()}; } diff --git a/src/mesh/MeshFlatEdgeBoundary.hpp b/src/mesh/MeshFlatEdgeBoundary.hpp index 70b51bc982222b7006b21e3a825cfeeab3fe6c93..ce3d1ded159e5db0d75b7bffbabaecec9b8485c9 100644 --- a/src/mesh/MeshFlatEdgeBoundary.hpp +++ b/src/mesh/MeshFlatEdgeBoundary.hpp @@ -12,9 +12,16 @@ class MeshFlatEdgeBoundary final : public MeshEdgeBoundary // clazy:exclude=co using Rd = TinyVector<MeshType::Dimension, double>; private: + const Rd m_origin; const Rd m_outgoing_normal; public: + const Rd& + origin() const + { + return m_origin; + } + const Rd& outgoingNormal() const { @@ -29,8 +36,11 @@ class MeshFlatEdgeBoundary final : public MeshEdgeBoundary // clazy:exclude=co const IBoundaryDescriptor& boundary_descriptor); private: - MeshFlatEdgeBoundary(const MeshType& mesh, const RefEdgeList& ref_edge_list, const Rd& outgoing_normal) - : MeshEdgeBoundary(mesh, ref_edge_list), m_outgoing_normal(outgoing_normal) + MeshFlatEdgeBoundary(const MeshType& mesh, + const RefEdgeList& ref_edge_list, + const Rd& origin, + const Rd& outgoing_normal) + : MeshEdgeBoundary(mesh, ref_edge_list), m_origin(origin), m_outgoing_normal(outgoing_normal) {} public: diff --git a/src/mesh/MeshFlatFaceBoundary.cpp b/src/mesh/MeshFlatFaceBoundary.cpp index 2ecdf6768eb865413386b10a1a419c6e30799def..46aa7063fae5899c2786e6cece6edfd284ddcb90 100644 --- a/src/mesh/MeshFlatFaceBoundary.cpp +++ b/src/mesh/MeshFlatFaceBoundary.cpp @@ -11,7 +11,7 @@ getMeshFlatFaceBoundary(const MeshType& mesh, const IBoundaryDescriptor& boundar MeshFaceBoundary mesh_face_boundary = getMeshFaceBoundary(mesh, boundary_descriptor); MeshFlatNodeBoundary mesh_flat_node_boundary = getMeshFlatNodeBoundary(mesh, boundary_descriptor); - return MeshFlatFaceBoundary<MeshType>{mesh, mesh_face_boundary.refFaceList(), + return MeshFlatFaceBoundary<MeshType>{mesh, mesh_face_boundary.refFaceList(), mesh_flat_node_boundary.origin(), mesh_flat_node_boundary.outgoingNormal()}; } diff --git a/src/mesh/MeshFlatFaceBoundary.hpp b/src/mesh/MeshFlatFaceBoundary.hpp index fc4d9d0f82b15a0d02dea4efdff3a13a6cceeb7a..2142202682f91059d1a19b80d90f76ef93a354ff 100644 --- a/src/mesh/MeshFlatFaceBoundary.hpp +++ b/src/mesh/MeshFlatFaceBoundary.hpp @@ -12,9 +12,16 @@ class MeshFlatFaceBoundary final : public MeshFaceBoundary // clazy:exclude=co using Rd = TinyVector<MeshType::Dimension, double>; private: + const Rd m_origin; const Rd m_outgoing_normal; public: + const Rd& + origin() const + { + return m_origin; + } + const Rd& outgoingNormal() const { @@ -29,8 +36,11 @@ class MeshFlatFaceBoundary final : public MeshFaceBoundary // clazy:exclude=co const IBoundaryDescriptor& boundary_descriptor); private: - MeshFlatFaceBoundary(const MeshType& mesh, const RefFaceList& ref_face_list, const Rd& outgoing_normal) - : MeshFaceBoundary(mesh, ref_face_list), m_outgoing_normal(outgoing_normal) + MeshFlatFaceBoundary(const MeshType& mesh, + const RefFaceList& ref_face_list, + const Rd& origin, + const Rd& outgoing_normal) + : MeshFaceBoundary(mesh, ref_face_list), m_origin(origin), m_outgoing_normal(outgoing_normal) {} public: diff --git a/src/mesh/MeshFlatNodeBoundary.cpp b/src/mesh/MeshFlatNodeBoundary.cpp index c3ca1dbd4ce980e6290b99af8a7bca878ca4520f..647298e97ef3ea2333dfeac62a7065234c343d1a 100644 --- a/src/mesh/MeshFlatNodeBoundary.cpp +++ b/src/mesh/MeshFlatNodeBoundary.cpp @@ -34,14 +34,13 @@ MeshFlatNodeBoundary<MeshType>::_checkBoundaryIsFlat(const TinyVector<MeshType:: template <> TinyVector<1, double> -MeshFlatNodeBoundary<Mesh<1>>::_getNormal(const Mesh<1>& mesh) +MeshFlatNodeBoundary<Mesh<1>>::_getOrigin(const Mesh<1>& mesh) { - using R = TinyVector<1, double>; + auto node_is_owned = mesh.connectivity().nodeIsOwned(); + auto node_list = m_ref_node_list.list(); const size_t number_of_bc_nodes = [&]() { size_t nb_bc_nodes = 0; - auto node_is_owned = mesh.connectivity().nodeIsOwned(); - auto node_list = m_ref_node_list.list(); for (size_t i_node = 0; i_node < node_list.size(); ++i_node) { nb_bc_nodes += (node_is_owned[node_list[i_node]]); } @@ -55,7 +54,43 @@ MeshFlatNodeBoundary<Mesh<1>>::_getNormal(const Mesh<1>& mesh) throw NormalError(ost.str()); } - return R{1}; + auto xr = mesh.xr(); + + Array<Rd> origin; + for (size_t i_node = 0; i_node < node_list.size(); ++i_node) { + const NodeId node_id = node_list[i_node]; + if (node_is_owned[node_list[i_node]]) { + origin = Array<Rd>(1); + origin[0] = xr[node_id]; + } + } + origin = parallel::allGatherVariable(origin); + Assert(origin.size() == 1); + return origin[0]; +} + +template <> +TinyVector<1, double> +MeshFlatNodeBoundary<Mesh<1>>::_getNormal(const Mesh<1>&) +{ + using R1 = TinyVector<1, double>; + + // The verification of the unicity of the boundary node is performed by _getOrigin() + return R1{1}; +} + +template <> +TinyVector<2, double> +MeshFlatNodeBoundary<Mesh<2>>::_getOrigin(const Mesh<2>& mesh) +{ + using R2 = TinyVector<2, double>; + + std::array<R2, 2> bounds = getBounds(mesh, m_ref_node_list); + + const R2& xmin = bounds[0]; + const R2& xmax = bounds[1]; + + return 0.5 * (xmin + xmax); } template <> @@ -137,6 +172,46 @@ MeshFlatNodeBoundary<Mesh<3>>::_getFarestNode(const Mesh<3>& mesh, const Rd& x0, return farest_x; } +template <> +TinyVector<3, double> +MeshFlatNodeBoundary<Mesh<3>>::_getOrigin(const Mesh<3>& mesh) +{ + using R3 = TinyVector<3, double>; + + std::array<R3, 2> diagonal = [](const std::array<R3, 6>& bounds) { + size_t max_i = 0; + size_t max_j = 0; + double max_length = 0; + + for (size_t i = 0; i < bounds.size(); ++i) { + for (size_t j = i + 1; j < bounds.size(); ++j) { + double length = l2Norm(bounds[i] - bounds[j]); + if (length > max_length) { + max_i = i; + max_j = j; + max_length = length; + } + } + } + + return std::array<R3, 2>{bounds[max_i], bounds[max_j]}; + }(getBounds(mesh, m_ref_node_list)); + + const R3& x0 = diagonal[0]; + const R3& x1 = diagonal[1]; + + if (x0 == x1) { + std::ostringstream ost; + ost << "invalid boundary \"" << rang::fgB::yellow << m_ref_node_list.refId() << rang::style::reset + << "\": unable to compute normal"; + throw NormalError(ost.str()); + } + + const R3 x2 = this->_getFarestNode(mesh, x0, x1); + + return 1. / 3. * (x0 + x1 + x2); +} + template <> TinyVector<3, double> MeshFlatNodeBoundary<Mesh<3>>::_getNormal(const Mesh<3>& mesh) diff --git a/src/mesh/MeshFlatNodeBoundary.hpp b/src/mesh/MeshFlatNodeBoundary.hpp index 66c610ea72a3aed5e6876516890c0d168bc9aac4..2ef2a22da8cd0541c1050aab2f63bebf7c7b8869 100644 --- a/src/mesh/MeshFlatNodeBoundary.hpp +++ b/src/mesh/MeshFlatNodeBoundary.hpp @@ -13,6 +13,7 @@ class [[nodiscard]] MeshFlatNodeBoundary final : public MeshNodeBoundary // cl using Rd = TinyVector<MeshType::Dimension, double>; private: + const Rd m_origin; const Rd m_outgoing_normal; Rd _getFarestNode(const MeshType& mesh, const Rd& x0, const Rd& x1); @@ -24,9 +25,17 @@ class [[nodiscard]] MeshFlatNodeBoundary final : public MeshNodeBoundary // cl const double length, const MeshType& mesh) const; + Rd _getOrigin(const MeshType& mesh); + Rd _getOutgoingNormal(const MeshType& mesh); public: + const Rd& + origin() const + { + return m_origin; + } + const Rd& outgoingNormal() const { @@ -42,11 +51,11 @@ class [[nodiscard]] MeshFlatNodeBoundary final : public MeshNodeBoundary // cl private: MeshFlatNodeBoundary(const MeshType& mesh, const RefFaceList& ref_face_list) - : MeshNodeBoundary(mesh, ref_face_list), m_outgoing_normal(_getOutgoingNormal(mesh)) + : MeshNodeBoundary(mesh, ref_face_list), m_origin{_getOrigin(mesh)}, m_outgoing_normal(_getOutgoingNormal(mesh)) {} MeshFlatNodeBoundary(const MeshType& mesh, const RefNodeList& ref_node_list) - : MeshNodeBoundary(mesh, ref_node_list), m_outgoing_normal(_getOutgoingNormal(mesh)) + : MeshNodeBoundary(mesh, ref_node_list), m_origin{_getOrigin(mesh)}, m_outgoing_normal(_getOutgoingNormal(mesh)) {} public: diff --git a/src/mesh/StencilArray.hpp b/src/mesh/StencilArray.hpp new file mode 100644 index 0000000000000000000000000000000000000000..ffe8638bf0d8fdb472780150603c7fab21ca1eb9 --- /dev/null +++ b/src/mesh/StencilArray.hpp @@ -0,0 +1,108 @@ +#ifndef STENCIL_ARRAY_HPP +#define STENCIL_ARRAY_HPP + +#include <mesh/ConnectivityMatrix.hpp> +#include <mesh/IBoundaryDescriptor.hpp> +#include <mesh/ItemId.hpp> +#include <mesh/ItemToItemMatrix.hpp> + +template <ItemType source_item_type, ItemType target_item_type> +class StencilArray +{ + public: + using ItemToItemMatrixT = ItemToItemMatrix<source_item_type, target_item_type>; + + using SourceItemId = ItemIdT<source_item_type>; + using TargetItemId = ItemIdT<target_item_type>; + + class BoundaryDescriptorStencilArray + { + private: + std::shared_ptr<const IBoundaryDescriptor> m_iboundary_descriptor; + const ConnectivityMatrix m_connectivity_matrix; + const ItemToItemMatrixT m_stencil_array; + + public: + const IBoundaryDescriptor& + boundaryDescriptor() const + { + return *m_iboundary_descriptor; + } + + const auto& + stencilArray() const + { + return m_stencil_array; + } + + BoundaryDescriptorStencilArray(const std::shared_ptr<const IBoundaryDescriptor>& iboundary_descriptor, + const ConnectivityMatrix& connectivity_matrix) + : m_iboundary_descriptor{iboundary_descriptor}, + m_connectivity_matrix{connectivity_matrix}, + m_stencil_array{m_connectivity_matrix} + {} + + BoundaryDescriptorStencilArray(const BoundaryDescriptorStencilArray& bdsa) + : m_iboundary_descriptor{bdsa.m_iboundary_descriptor}, + m_connectivity_matrix{bdsa.m_connectivity_matrix}, + m_stencil_array{m_connectivity_matrix} + {} + + BoundaryDescriptorStencilArray(BoundaryDescriptorStencilArray&& bdsa) + : m_iboundary_descriptor{std::move(bdsa.m_iboundary_descriptor)}, + m_connectivity_matrix{std::move(bdsa.m_connectivity_matrix)}, + m_stencil_array{m_connectivity_matrix} + {} + + ~BoundaryDescriptorStencilArray() = default; + }; + + using BoundaryDescriptorStencilArrayList = std::vector<BoundaryDescriptorStencilArray>; + + private: + const ConnectivityMatrix m_connectivity_matrix; + const ItemToItemMatrixT m_stencil_array; + BoundaryDescriptorStencilArrayList m_symmetry_boundary_stencil_array_list; + + public: + PUGS_INLINE + const auto& + symmetryBoundaryStencilArrayList() const + { + return m_symmetry_boundary_stencil_array_list; + } + + PUGS_INLINE + auto + operator[](SourceItemId source_item_id) const + { + return m_stencil_array[source_item_id]; + } + + StencilArray(const ConnectivityMatrix& connectivity_matrix, + const BoundaryDescriptorStencilArrayList& symmetry_boundary_descriptor_stencil_array_list) + : m_connectivity_matrix{connectivity_matrix}, + m_stencil_array{m_connectivity_matrix}, + m_symmetry_boundary_stencil_array_list{symmetry_boundary_descriptor_stencil_array_list} + {} + + StencilArray(const StencilArray& stencil_array) + : m_connectivity_matrix{stencil_array.m_connectivity_matrix}, + m_stencil_array{m_connectivity_matrix}, + m_symmetry_boundary_stencil_array_list{stencil_array.m_symmetry_boundary_stencil_array_list} + {} + + StencilArray(StencilArray&& stencil_array) + : m_connectivity_matrix{std::move(stencil_array.m_connectivity_matrix)}, + m_stencil_array{m_connectivity_matrix}, + m_symmetry_boundary_stencil_array_list{std::move(stencil_array.m_symmetry_boundary_stencil_array_list)} + {} + + ~StencilArray() = default; +}; + +using CellToCellStencilArray = StencilArray<ItemType::cell, ItemType::cell>; +using CellToFaceStencilArray = StencilArray<ItemType::cell, ItemType::face>; +using NodeToCellStencilArray = StencilArray<ItemType::node, ItemType::cell>; + +#endif // STENCIL_ARRAY_HPP diff --git a/src/mesh/StencilBuilder.cpp b/src/mesh/StencilBuilder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..de7501d44b5b0aef6e50f830460e9be110456331 --- /dev/null +++ b/src/mesh/StencilBuilder.cpp @@ -0,0 +1,895 @@ +#include <mesh/StencilBuilder.hpp> + +#include <mesh/Connectivity.hpp> +#include <mesh/ItemArray.hpp> +#include <utils/GlobalVariableManager.hpp> +#include <utils/Messenger.hpp> + +template <ItemType item_type> +class StencilBuilder::Layer +{ + using ItemId = ItemIdT<item_type>; + ItemValue<const int, item_type> m_number_of; + + std::vector<ItemId> m_item_id_vector; + std::vector<int> m_item_number_vector; + + public: + void + clear() + { + m_item_id_vector.clear(); + m_item_number_vector.clear(); + } + + size_t + size() const + { + Assert(m_item_id_vector.size() == m_item_number_vector.size()); + return m_item_id_vector.size(); + } + + const std::vector<ItemId>& + itemIdList() const + { + return m_item_id_vector; + } + + bool + hasItemNumber(const ItemId item_id) const + { + const int item_number = m_number_of[item_id]; + + ssize_t begin = 0; + ssize_t end = m_item_number_vector.size(); + + while (begin < end) { + const ssize_t mid = (begin + end) / 2; + const auto& mid_number = m_item_number_vector[mid]; + if (mid_number == item_number) { + return true; // We found the value + } else if (mid_number < item_number) { + if (begin == mid) { + break; + } + begin = mid; + } else { + if (end == mid) { + break; + } + end = mid; + } + } + return false; + } + + void + add(const ItemId item_id) + { + const int item_number = m_number_of[item_id]; + + ssize_t begin = 0; + ssize_t end = m_item_number_vector.size(); + + while (begin < end) { + const ssize_t mid = (begin + end) / 2; + const auto& mid_number = m_item_number_vector[mid]; + + if (mid_number == item_number) { + return; // We found the value + } else if (mid_number < item_number) { + if (begin == mid) { + break; + } + begin = mid; + } else { + if (end == mid) { + break; + } + end = mid; + } + } + + m_item_id_vector.push_back(item_id); + m_item_number_vector.push_back(item_number); + + const auto& begin_number = m_item_number_vector[begin]; + + if (begin_number > item_number) { + for (ssize_t i = m_item_number_vector.size() - 2; i >= begin; --i) { + std::swap(m_item_number_vector[i], m_item_number_vector[i + 1]); + std::swap(m_item_id_vector[i], m_item_id_vector[i + 1]); + } + } else if (begin_number < item_number) { + for (ssize_t i = m_item_number_vector.size() - 2; i > begin; --i) { + std::swap(m_item_number_vector[i], m_item_number_vector[i + 1]); + std::swap(m_item_id_vector[i], m_item_id_vector[i + 1]); + } + } + } + + Layer& operator=(const Layer&) = default; + Layer& operator=(Layer&&) = default; + + template <size_t Dimension> + Layer(const Connectivity<Dimension>& connectivity) : m_number_of{connectivity.template number<item_type>()} + {} + + // Layer() = default; + + Layer(const Layer&) = default; + Layer(Layer&&) = default; + + ~Layer() = default; +}; + +template <ItemType connecting_item_type, typename ConnectivityType> +auto +StencilBuilder::_buildSymmetryConnectingItemList(const ConnectivityType& connectivity, + const BoundaryDescriptorList& symmetry_boundary_descriptor_list) const +{ + static_assert(connecting_item_type != ItemType::cell, "cells cannot be used to define symmetry boundaries"); + + using ConnectingItemId = ItemIdT<connecting_item_type>; + ItemArray<bool, connecting_item_type> symmetry_connecting_item_list{connectivity, + symmetry_boundary_descriptor_list.size()}; + symmetry_connecting_item_list.fill(false); + + if constexpr (ItemTypeId<ConnectivityType::Dimension>::itemTId(connecting_item_type) == + ItemTypeId<ConnectivityType::Dimension>::itemTId(ItemType::face)) { + size_t i_symmetry_boundary = 0; + for (auto p_boundary_descriptor : symmetry_boundary_descriptor_list) { + const IBoundaryDescriptor& boundary_descriptor = *p_boundary_descriptor; + + bool found = false; + for (size_t i_ref_connecting_item_list = 0; + i_ref_connecting_item_list < connectivity.template numberOfRefItemList<connecting_item_type>(); + ++i_ref_connecting_item_list) { + const auto& ref_connecting_item_list = + connectivity.template refItemList<connecting_item_type>(i_ref_connecting_item_list); + if (ref_connecting_item_list.refId() == boundary_descriptor) { + found = true; + auto connecting_item_list = ref_connecting_item_list.list(); + for (size_t i_connecting_item = 0; i_connecting_item < connecting_item_list.size(); ++i_connecting_item) { + const ConnectingItemId connecting_item_id = connecting_item_list[i_connecting_item]; + + symmetry_connecting_item_list[connecting_item_id][i_symmetry_boundary] = true; + } + break; + } + } + ++i_symmetry_boundary; + if (not found) { + std::ostringstream error_msg; + error_msg << "cannot find boundary '" << rang::fgB::yellow << boundary_descriptor << rang::fg::reset << '\''; + throw NormalError(error_msg.str()); + } + } + } else { + auto face_to_connecting_item_matrix = + connectivity.template getItemToItemMatrix<ItemType::face, connecting_item_type>(); + size_t i_symmetry_boundary = 0; + for (auto p_boundary_descriptor : symmetry_boundary_descriptor_list) { + const IBoundaryDescriptor& boundary_descriptor = *p_boundary_descriptor; + + bool found = false; + for (size_t i_ref_face_list = 0; i_ref_face_list < connectivity.template numberOfRefItemList<ItemType::face>(); + ++i_ref_face_list) { + const auto& ref_face_list = connectivity.template refItemList<ItemType::face>(i_ref_face_list); + if (ref_face_list.refId() == boundary_descriptor) { + found = true; + for (size_t i_face = 0; i_face < ref_face_list.list().size(); ++i_face) { + const FaceId face_id = ref_face_list.list()[i_face]; + auto connecting_item_list = face_to_connecting_item_matrix[face_id]; + for (size_t i_connecting_item = 0; i_connecting_item < connecting_item_list.size(); ++i_connecting_item) { + const ConnectingItemId connecting_item_id = connecting_item_list[i_connecting_item]; + + symmetry_connecting_item_list[connecting_item_id][i_symmetry_boundary] = true; + } + } + break; + } + } + ++i_symmetry_boundary; + if (not found) { + std::ostringstream error_msg; + error_msg << "cannot find boundary '" << rang::fgB::yellow << boundary_descriptor << rang::fg::reset << '\''; + throw NormalError(error_msg.str()); + } + } + } + + return symmetry_connecting_item_list; +} + +template <ItemType item_type, ItemType connecting_item_type, typename ConnectivityType> +StencilArray<item_type, item_type> +StencilBuilder::_build_for_same_source_and_target(const ConnectivityType& connectivity, + const size_t& number_of_layers, + const BoundaryDescriptorList& symmetry_boundary_descriptor_list) const +{ + if (number_of_layers == 0) { + throw NormalError("number of layers must be greater than 0 to build stencils"); + } + + auto item_to_connecting_item_matrix = connectivity.template getItemToItemMatrix<item_type, connecting_item_type>(); + auto connecting_item_to_item_matrix = connectivity.template getItemToItemMatrix<connecting_item_type, item_type>(); + + auto item_is_owned = connectivity.template isOwned<item_type>(); + auto item_number = connectivity.template number<item_type>(); + auto connecting_item_number = connectivity.template number<connecting_item_type>(); + + using ItemId = ItemIdT<item_type>; + using ConnectingItemId = ItemIdT<connecting_item_type>; + + ItemArray<bool, connecting_item_type> symmetry_item_list = + this->_buildSymmetryConnectingItemList<connecting_item_type>(connectivity, symmetry_boundary_descriptor_list); + + Array<uint32_t> row_map{connectivity.template numberOf<item_type>() + 1}; + row_map[0] = 0; + std::vector<Array<uint32_t>> symmetry_row_map_list(symmetry_boundary_descriptor_list.size()); + for (auto&& symmetry_row_map : symmetry_row_map_list) { + symmetry_row_map = Array<uint32_t>{connectivity.template numberOf<item_type>() + 1}; + symmetry_row_map[0] = 0; + } + + std::vector<ItemId> column_indices_vector; + std::vector<std::vector<uint32_t>> symmetry_column_indices_vector(symmetry_boundary_descriptor_list.size()); + + std::vector<Layer<item_type>> item_layer_list; + std::vector<Layer<connecting_item_type>> connecting_layer_list; + + std::vector<Layer<item_type>> symmetry_item_layer_list; + std::vector<Layer<connecting_item_type>> symmetry_connecting_layer_list; + + for (size_t i = 0; i < number_of_layers; ++i) { + item_layer_list.emplace_back(Layer<item_type>{connectivity}); + connecting_layer_list.emplace_back(Layer<connecting_item_type>{connectivity}); + + if (symmetry_boundary_descriptor_list.size() > 0) { + symmetry_item_layer_list.emplace_back(Layer<item_type>{connectivity}); + symmetry_connecting_layer_list.emplace_back(Layer<connecting_item_type>{connectivity}); + } + } + + for (ItemId item_id = 0; item_id < connectivity.template numberOf<item_type>(); ++item_id) { + if (item_is_owned[item_id]) { + for (auto&& item_layer : item_layer_list) { + item_layer.clear(); + } + for (auto&& connecting_layer : connecting_layer_list) { + connecting_layer.clear(); + } + + // First layer is a special case + { + auto& item_layer = item_layer_list[0]; + auto& connecting_layer = connecting_layer_list[0]; + + for (size_t i_connecting_item = 0; i_connecting_item < item_to_connecting_item_matrix[item_id].size(); + ++i_connecting_item) { + const ConnectingItemId connecting_item_id = item_to_connecting_item_matrix[item_id][i_connecting_item]; + + connecting_layer.add(connecting_item_id); + } + + for (auto connecting_item_id : connecting_layer.itemIdList()) { + for (size_t i_item = 0; i_item < connecting_item_to_item_matrix[connecting_item_id].size(); ++i_item) { + const ItemId layer_item_id = connecting_item_to_item_matrix[connecting_item_id][i_item]; + if (layer_item_id != item_id) { + item_layer.add(layer_item_id); + } + } + } + } + + for (size_t i_layer = 1; i_layer < number_of_layers; ++i_layer) { + auto& connecting_layer = connecting_layer_list[i_layer]; + + auto has_connecting_item = [&i_layer, &connecting_layer_list](ConnectingItemId connecting_item_id) -> bool { + for (ssize_t i = i_layer - 1; i >= 0; --i) { + if (connecting_layer_list[i].hasItemNumber(connecting_item_id)) { + return true; + } + } + + return false; + }; + + for (auto&& previous_layer_item_id_list : item_layer_list[i_layer - 1].itemIdList()) { + const auto item_to_connecting_item_list = item_to_connecting_item_matrix[previous_layer_item_id_list]; + for (size_t i_connecting_item = 0; i_connecting_item < item_to_connecting_item_list.size(); + ++i_connecting_item) { + const ConnectingItemId connecting_item_id = item_to_connecting_item_list[i_connecting_item]; + + if (not has_connecting_item(connecting_item_id)) { + connecting_layer.add(connecting_item_id); + } + } + } + + auto& item_layer = item_layer_list[i_layer]; + + auto has_layer_item = [&i_layer, &item_layer_list](ItemId layer_item_id) -> bool { + for (ssize_t i = i_layer - 1; i >= 0; --i) { + if (item_layer_list[i].hasItemNumber(layer_item_id)) { + return true; + } + } + + return false; + }; + + for (auto connecting_item_id : connecting_layer.itemIdList()) { + const auto& connecting_item_to_item_list = connecting_item_to_item_matrix[connecting_item_id]; + for (size_t i_item = 0; i_item < connecting_item_to_item_list.size(); ++i_item) { + const ItemId layer_item_id = connecting_item_to_item_list[i_item]; + if ((layer_item_id != item_id) and (not has_layer_item(layer_item_id))) { + item_layer.add(layer_item_id); + } + } + } + } + + for (size_t i_symmetry = 0; i_symmetry < symmetry_boundary_descriptor_list.size(); ++i_symmetry) { + for (auto&& symmetry_item_layer : symmetry_item_layer_list) { + symmetry_item_layer.clear(); + } + for (auto&& symmetry_connecting_layer : symmetry_connecting_layer_list) { + symmetry_connecting_layer.clear(); + } + + // First layer is a special case + for (auto&& connecting_item_id : connecting_layer_list[0].itemIdList()) { + if (symmetry_item_list[connecting_item_id][i_symmetry]) { + symmetry_connecting_layer_list[0].add(connecting_item_id); + } + } + + for (auto&& connecting_item_id : symmetry_connecting_layer_list[0].itemIdList()) { + auto item_of_connecting_item_list = connecting_item_to_item_matrix[connecting_item_id]; + for (size_t i_item_of_connecting_item = 0; i_item_of_connecting_item < item_of_connecting_item_list.size(); + ++i_item_of_connecting_item) { + const ItemId item_id_of_connecting_item = item_of_connecting_item_list[i_item_of_connecting_item]; + symmetry_item_layer_list[0].add(item_id_of_connecting_item); + } + } + + for (size_t i_layer = 1; i_layer < number_of_layers; ++i_layer) { + auto has_connecting_item = [&i_layer, + &symmetry_connecting_layer_list](ConnectingItemId connecting_item_id) -> bool { + for (ssize_t i = i_layer - 1; i >= 0; --i) { + if (symmetry_connecting_layer_list[i].hasItemNumber(connecting_item_id)) { + return true; + } + } + + return false; + }; + + for (auto&& symmetry_connecting_item_id : connecting_layer_list[i_layer].itemIdList()) { + if (symmetry_item_list[symmetry_connecting_item_id][i_symmetry]) { + if (not has_connecting_item(symmetry_connecting_item_id)) { + symmetry_connecting_layer_list[i_layer].add(symmetry_connecting_item_id); + } + } + } + + for (auto&& previous_layer_item_id_list : symmetry_item_layer_list[i_layer - 1].itemIdList()) { + const auto item_to_connecting_item_list = item_to_connecting_item_matrix[previous_layer_item_id_list]; + for (size_t i_connecting_item = 0; i_connecting_item < item_to_connecting_item_list.size(); + ++i_connecting_item) { + const ConnectingItemId connecting_item_id = item_to_connecting_item_list[i_connecting_item]; + + if (not has_connecting_item(connecting_item_id)) { + symmetry_connecting_layer_list[i_layer].add(connecting_item_id); + } + } + } + + auto has_symmetry_layer_item = [&i_layer, &symmetry_item_layer_list](ItemId layer_item_id) -> bool { + for (ssize_t i = i_layer - 1; i >= 0; --i) { + if (symmetry_item_layer_list[i].hasItemNumber(layer_item_id)) { + return true; + } + } + + return false; + }; + + for (auto&& connecting_item_id : symmetry_connecting_layer_list[i_layer].itemIdList()) { + auto item_of_connecting_item_list = connecting_item_to_item_matrix[connecting_item_id]; + for (size_t i_item_of_connecting_item = 0; i_item_of_connecting_item < item_of_connecting_item_list.size(); + ++i_item_of_connecting_item) { + const ItemId item_id_of_connecting_item = item_of_connecting_item_list[i_item_of_connecting_item]; + if (not has_symmetry_layer_item(item_id_of_connecting_item)) { + symmetry_item_layer_list[i_layer].add(item_id_of_connecting_item); + } + } + } + } + + for (size_t i_layer = 0; i_layer < number_of_layers; ++i_layer) { + for (auto symmetry_layer_item_id : symmetry_item_layer_list[i_layer].itemIdList()) { + symmetry_column_indices_vector[i_symmetry].push_back(symmetry_layer_item_id); + } + } + + { + size_t stencil_size = 0; + for (size_t i_layer = 0; i_layer < number_of_layers; ++i_layer) { + auto& item_layer = symmetry_item_layer_list[i_layer]; + stencil_size += item_layer.size(); + } + + symmetry_row_map_list[i_symmetry][item_id + 1] = symmetry_row_map_list[i_symmetry][item_id] + stencil_size; + } + } + + { + size_t stencil_size = 0; + for (size_t i_layer = 0; i_layer < number_of_layers; ++i_layer) { + auto& item_layer = item_layer_list[i_layer]; + for (auto stencil_item_id : item_layer.itemIdList()) { + column_indices_vector.push_back(stencil_item_id); + } + stencil_size += item_layer.size(); + } + row_map[item_id + 1] = row_map[item_id] + stencil_size; + } + + } else { + row_map[item_id + 1] = row_map[item_id]; + for (size_t i = 0; i < symmetry_row_map_list.size(); ++i) { + symmetry_row_map_list[i][item_id + 1] = symmetry_row_map_list[i][item_id]; + } + } + } + + Array<uint32_t> column_indices{column_indices_vector.size()}; + parallel_for(column_indices_vector.size(), PUGS_LAMBDA(size_t i) { column_indices[i] = column_indices_vector[i]; }); + + ConnectivityMatrix primal_stencil{row_map, column_indices}; + + typename StencilArray<item_type, item_type>::BoundaryDescriptorStencilArrayList symmetry_boundary_stencil_list; + { + size_t i = 0; + for (auto&& p_boundary_descriptor : symmetry_boundary_descriptor_list) { + symmetry_boundary_stencil_list.emplace_back( + typename StencilArray<item_type, item_type>:: + BoundaryDescriptorStencilArray{p_boundary_descriptor, + ConnectivityMatrix{symmetry_row_map_list[i], + convert_to_array(symmetry_column_indices_vector[i])}}); + ++i; + } + } + + return {{primal_stencil}, {symmetry_boundary_stencil_list}}; +} + +template <ItemType source_item_type, + ItemType connecting_item_type, + ItemType target_item_type, + typename ConnectivityType> +StencilArray<source_item_type, target_item_type> +StencilBuilder::_build_for_different_source_and_target( + const ConnectivityType& connectivity, + const size_t& number_of_layers, + const BoundaryDescriptorList& symmetry_boundary_descriptor_list) const +{ + constexpr size_t Dimension = ConnectivityType::Dimension; + + if (number_of_layers == 0) { + throw NormalError("number of layers must be greater than 0 to build stencils"); + } + + auto connecting_item_to_target_item_matrix = + connectivity.template getItemToItemMatrix<connecting_item_type, target_item_type>(); + auto target_item_to_connecting_item_matrix = + connectivity.template getItemToItemMatrix<target_item_type, connecting_item_type>(); + + auto source_item_to_connecting_item_matrix = + [](const auto& given_connectivity) -> ItemToItemMatrix<source_item_type, connecting_item_type> { + if constexpr (ItemTypeId<Dimension>::itemTId(source_item_type) == + ItemTypeId<Dimension>::itemTId(connecting_item_type)) { + return {ConnectivityMatrix{}}; + } else { + return given_connectivity.template getItemToItemMatrix<source_item_type, connecting_item_type>(); + } + }(connectivity); + + auto source_item_is_owned = connectivity.template isOwned<source_item_type>(); + auto target_item_number = connectivity.template number<target_item_type>(); + auto connecting_item_number = connectivity.template number<connecting_item_type>(); + + using SourceItemId = ItemIdT<source_item_type>; + using TargetItemId = ItemIdT<target_item_type>; + using ConnectingItemId = ItemIdT<connecting_item_type>; + + ItemArray<bool, connecting_item_type> symmetry_item_list = + this->_buildSymmetryConnectingItemList<connecting_item_type>(connectivity, symmetry_boundary_descriptor_list); + + Array<uint32_t> row_map{connectivity.template numberOf<source_item_type>() + 1}; + row_map[0] = 0; + std::vector<Array<uint32_t>> symmetry_row_map_list(symmetry_boundary_descriptor_list.size()); + for (auto&& symmetry_row_map : symmetry_row_map_list) { + symmetry_row_map = Array<uint32_t>{connectivity.template numberOf<source_item_type>() + 1}; + symmetry_row_map[0] = 0; + } + + std::vector<TargetItemId> column_indices_vector; + std::vector<std::vector<uint32_t>> symmetry_column_indices_vector(symmetry_boundary_descriptor_list.size()); + + std::vector<Layer<target_item_type>> target_item_layer_list; + std::vector<Layer<connecting_item_type>> connecting_layer_list; + + std::vector<Layer<target_item_type>> symmetry_target_item_layer_list; + std::vector<Layer<connecting_item_type>> symmetry_connecting_layer_list; + + for (size_t i = 0; i < number_of_layers; ++i) { + target_item_layer_list.emplace_back(Layer<target_item_type>{connectivity}); + connecting_layer_list.emplace_back(Layer<connecting_item_type>{connectivity}); + + if (symmetry_boundary_descriptor_list.size() > 0) { + symmetry_target_item_layer_list.emplace_back(Layer<target_item_type>{connectivity}); + symmetry_connecting_layer_list.emplace_back(Layer<connecting_item_type>{connectivity}); + } + } + + for (SourceItemId source_item_id = 0; source_item_id < connectivity.template numberOf<source_item_type>(); + ++source_item_id) { + if (source_item_is_owned[source_item_id]) { + for (auto&& target_item_layer : target_item_layer_list) { + target_item_layer.clear(); + } + for (auto&& connecting_layer : connecting_layer_list) { + connecting_layer.clear(); + } + + // First layer is a special case + { + auto& target_item_layer = target_item_layer_list[0]; + auto& connecting_layer = connecting_layer_list[0]; + + if constexpr (ItemTypeId<Dimension>::itemTId(source_item_type) == + ItemTypeId<Dimension>::itemTId(connecting_item_type)) { + connecting_layer.add(ConnectingItemId{static_cast<typename ConnectingItemId::base_type>(source_item_id)}); + } else { + for (size_t i_connecting_item = 0; + i_connecting_item < source_item_to_connecting_item_matrix[source_item_id].size(); ++i_connecting_item) { + const ConnectingItemId connecting_item_id = + source_item_to_connecting_item_matrix[source_item_id][i_connecting_item]; + + connecting_layer.add(connecting_item_id); + } + } + + for (auto connecting_item_id : connecting_layer.itemIdList()) { + for (size_t i_item = 0; i_item < connecting_item_to_target_item_matrix[connecting_item_id].size(); ++i_item) { + const TargetItemId layer_item_id = connecting_item_to_target_item_matrix[connecting_item_id][i_item]; + target_item_layer.add(layer_item_id); + } + } + } + + for (size_t i_layer = 1; i_layer < number_of_layers; ++i_layer) { + auto& connecting_layer = connecting_layer_list[i_layer]; + + auto has_connecting_item = [&i_layer, &connecting_layer_list](ConnectingItemId connecting_item_id) -> bool { + for (ssize_t i = i_layer - 1; i >= 0; --i) { + if (connecting_layer_list[i].hasItemNumber(connecting_item_id)) { + return true; + } + } + + return false; + }; + + for (auto&& previous_layer_item_id_list : target_item_layer_list[i_layer - 1].itemIdList()) { + const auto target_item_to_connecting_item_list = + target_item_to_connecting_item_matrix[previous_layer_item_id_list]; + for (size_t i_connecting_item = 0; i_connecting_item < target_item_to_connecting_item_list.size(); + ++i_connecting_item) { + const ConnectingItemId connecting_item_id = target_item_to_connecting_item_list[i_connecting_item]; + + if (not has_connecting_item(connecting_item_id)) { + connecting_layer.add(connecting_item_id); + } + } + } + + auto& target_item_layer = target_item_layer_list[i_layer]; + + auto has_layer_item = [&i_layer, &target_item_layer_list](TargetItemId layer_item_id) -> bool { + for (ssize_t i = i_layer - 1; i >= 0; --i) { + if (target_item_layer_list[i].hasItemNumber(layer_item_id)) { + return true; + } + } + + return false; + }; + + for (auto connecting_item_id : connecting_layer.itemIdList()) { + const auto& connecting_item_to_target_item_list = connecting_item_to_target_item_matrix[connecting_item_id]; + for (size_t i_item = 0; i_item < connecting_item_to_target_item_list.size(); ++i_item) { + const TargetItemId layer_item_id = connecting_item_to_target_item_list[i_item]; + if (not has_layer_item(layer_item_id)) { + target_item_layer.add(layer_item_id); + } + } + } + } + + for (size_t i_symmetry = 0; i_symmetry < symmetry_boundary_descriptor_list.size(); ++i_symmetry) { + for (auto&& symmetry_target_item_layer : symmetry_target_item_layer_list) { + symmetry_target_item_layer.clear(); + } + for (auto&& symmetry_connecting_layer : symmetry_connecting_layer_list) { + symmetry_connecting_layer.clear(); + } + + // First layer is a special case + for (auto&& connecting_item_id : connecting_layer_list[0].itemIdList()) { + if (symmetry_item_list[connecting_item_id][i_symmetry]) { + symmetry_connecting_layer_list[0].add(connecting_item_id); + } + } + + for (auto&& connecting_item_id : symmetry_connecting_layer_list[0].itemIdList()) { + auto item_of_connecting_item_list = connecting_item_to_target_item_matrix[connecting_item_id]; + for (size_t i_item_of_connecting_item = 0; i_item_of_connecting_item < item_of_connecting_item_list.size(); + ++i_item_of_connecting_item) { + const TargetItemId item_id_of_connecting_item = item_of_connecting_item_list[i_item_of_connecting_item]; + symmetry_target_item_layer_list[0].add(item_id_of_connecting_item); + } + } + + for (size_t i_layer = 1; i_layer < number_of_layers; ++i_layer) { + auto has_connecting_item = [&i_layer, + &symmetry_connecting_layer_list](ConnectingItemId connecting_item_id) -> bool { + for (ssize_t i = i_layer - 1; i >= 0; --i) { + if (symmetry_connecting_layer_list[i].hasItemNumber(connecting_item_id)) { + return true; + } + } + + return false; + }; + + for (auto&& symmetry_connecting_item_id : connecting_layer_list[i_layer].itemIdList()) { + if (symmetry_item_list[symmetry_connecting_item_id][i_symmetry]) { + if (not has_connecting_item(symmetry_connecting_item_id)) { + symmetry_connecting_layer_list[i_layer].add(symmetry_connecting_item_id); + } + } + } + + for (auto&& previous_layer_target_item_id_list : symmetry_target_item_layer_list[i_layer - 1].itemIdList()) { + const auto item_to_connecting_item_list = + target_item_to_connecting_item_matrix[previous_layer_target_item_id_list]; + for (size_t i_connecting_item = 0; i_connecting_item < item_to_connecting_item_list.size(); + ++i_connecting_item) { + const ConnectingItemId connecting_item_id = item_to_connecting_item_list[i_connecting_item]; + + if (not has_connecting_item(connecting_item_id)) { + symmetry_connecting_layer_list[i_layer].add(connecting_item_id); + } + } + } + + auto has_symmetry_layer_item = [&i_layer, + &symmetry_target_item_layer_list](TargetItemId layer_target_item_id) -> bool { + for (ssize_t i = i_layer - 1; i >= 0; --i) { + if (symmetry_target_item_layer_list[i].hasItemNumber(layer_target_item_id)) { + return true; + } + } + + return false; + }; + + for (auto&& connecting_item_id : symmetry_connecting_layer_list[i_layer].itemIdList()) { + auto item_of_connecting_item_list = connecting_item_to_target_item_matrix[connecting_item_id]; + for (size_t i_target_item_of_connecting_item = 0; + i_target_item_of_connecting_item < item_of_connecting_item_list.size(); + ++i_target_item_of_connecting_item) { + const TargetItemId target_item_id_of_connecting_item = + item_of_connecting_item_list[i_target_item_of_connecting_item]; + if (not has_symmetry_layer_item(target_item_id_of_connecting_item)) { + symmetry_target_item_layer_list[i_layer].add(target_item_id_of_connecting_item); + } + } + } + } + + for (size_t i_layer = 0; i_layer < number_of_layers; ++i_layer) { + for (auto symmetry_layer_target_item_id : symmetry_target_item_layer_list[i_layer].itemIdList()) { + symmetry_column_indices_vector[i_symmetry].push_back(symmetry_layer_target_item_id); + } + } + + { + size_t stencil_size = 0; + for (size_t i_layer = 0; i_layer < number_of_layers; ++i_layer) { + auto& target_item_layer = symmetry_target_item_layer_list[i_layer]; + stencil_size += target_item_layer.size(); + } + + symmetry_row_map_list[i_symmetry][source_item_id + 1] = + symmetry_row_map_list[i_symmetry][source_item_id] + stencil_size; + } + } + + { + size_t stencil_size = 0; + for (size_t i_layer = 0; i_layer < number_of_layers; ++i_layer) { + auto& item_layer = target_item_layer_list[i_layer]; + for (auto stencil_item_id : item_layer.itemIdList()) { + column_indices_vector.push_back(stencil_item_id); + } + stencil_size += item_layer.size(); + } + row_map[source_item_id + 1] = row_map[source_item_id] + stencil_size; + } + + } else { + row_map[source_item_id + 1] = row_map[source_item_id]; + for (size_t i = 0; i < symmetry_row_map_list.size(); ++i) { + symmetry_row_map_list[i][source_item_id + 1] = symmetry_row_map_list[i][source_item_id]; + } + } + } + + Array<uint32_t> column_indices{column_indices_vector.size()}; + parallel_for(column_indices_vector.size(), PUGS_LAMBDA(size_t i) { column_indices[i] = column_indices_vector[i]; }); + + ConnectivityMatrix primal_stencil{row_map, column_indices}; + + typename StencilArray<source_item_type, target_item_type>::BoundaryDescriptorStencilArrayList + symmetry_boundary_stencil_list; + { + size_t i = 0; + for (auto&& p_boundary_descriptor : symmetry_boundary_descriptor_list) { + symmetry_boundary_stencil_list.emplace_back( + typename StencilArray<source_item_type, target_item_type>:: + BoundaryDescriptorStencilArray{p_boundary_descriptor, + ConnectivityMatrix{symmetry_row_map_list[i], + convert_to_array(symmetry_column_indices_vector[i])}}); + ++i; + } + } + + return {{primal_stencil}, {symmetry_boundary_stencil_list}}; +} + +template <ItemType source_item_type, + ItemType connecting_item_type, + ItemType target_item_type, + typename ConnectivityType> +StencilArray<source_item_type, target_item_type> +StencilBuilder::_build(const ConnectivityType& connectivity, + const size_t& number_of_layers, + const BoundaryDescriptorList& symmetry_boundary_descriptor_list) const +{ + if constexpr (connecting_item_type != target_item_type) { + if constexpr (source_item_type == target_item_type) { + return this + ->_build_for_same_source_and_target<source_item_type, connecting_item_type>(connectivity, number_of_layers, + symmetry_boundary_descriptor_list); + } else { + return this->_build_for_different_source_and_target<source_item_type, connecting_item_type, + target_item_type>(connectivity, number_of_layers, + symmetry_boundary_descriptor_list); + } + } else { + std::ostringstream error_msg; + error_msg << "cannot build stencil of " << rang::fgB::yellow << itemName(target_item_type) << rang::fg::reset + << " using " << rang::fgB::yellow << itemName(connecting_item_type) << rang::fg::reset + << " for connectivity"; + throw UnexpectedError(error_msg.str()); + } +} + +template <ItemType source_item_type, ItemType target_item_type, typename ConnectivityType> +StencilArray<source_item_type, target_item_type> +StencilBuilder::_build(const ConnectivityType& connectivity, + const StencilDescriptor& stencil_descriptor, + const BoundaryDescriptorList& symmetry_boundary_descriptor_list) const +{ + switch (stencil_descriptor.connectionType()) { + case StencilDescriptor::ConnectionType::by_nodes: { + return this->_build<source_item_type, ItemType::node, target_item_type>(connectivity, + stencil_descriptor.numberOfLayers(), + symmetry_boundary_descriptor_list); + } + case StencilDescriptor::ConnectionType::by_edges: { + return this->_build<source_item_type, ItemType::edge, target_item_type>(connectivity, + stencil_descriptor.numberOfLayers(), + symmetry_boundary_descriptor_list); + } + case StencilDescriptor::ConnectionType::by_faces: { + return this->_build<source_item_type, ItemType::face, target_item_type>(connectivity, + stencil_descriptor.numberOfLayers(), + symmetry_boundary_descriptor_list); + } + case StencilDescriptor::ConnectionType::by_cells: { + return this->_build<source_item_type, ItemType::cell, target_item_type>(connectivity, + stencil_descriptor.numberOfLayers(), + symmetry_boundary_descriptor_list); + } + // LCOV_EXCL_START + default: { + throw UnexpectedError("invalid connection type"); + } + // LCOV_EXCL_STOP + } +} + +CellToCellStencilArray +StencilBuilder::buildC2C(const IConnectivity& connectivity, + const StencilDescriptor& stencil_descriptor, + const BoundaryDescriptorList& symmetry_boundary_descriptor_list) const +{ + if ((parallel::size() > 1) and + (stencil_descriptor.numberOfLayers() > GlobalVariableManager::instance().getNumberOfGhostLayers())) { + std::ostringstream error_msg; + error_msg << "Stencil builder requires " << rang::fgB::yellow << stencil_descriptor.numberOfLayers() + << rang::fg::reset << " layers while parallel number of ghost layer is " + << GlobalVariableManager::instance().getNumberOfGhostLayers() << ".\n"; + error_msg << "Increase the number of ghost layers (using the '--number-of-ghost-layers' option)."; + throw NormalError(error_msg.str()); + } + + switch (connectivity.dimension()) { + case 1: { + return this->_build<ItemType::cell, ItemType::cell>(dynamic_cast<const Connectivity<1>&>(connectivity), + stencil_descriptor, symmetry_boundary_descriptor_list); + } + case 2: { + return this->_build<ItemType::cell, ItemType::cell>(dynamic_cast<const Connectivity<2>&>(connectivity), + stencil_descriptor, symmetry_boundary_descriptor_list); + } + case 3: { + return this->_build<ItemType::cell, ItemType::cell>(dynamic_cast<const Connectivity<3>&>(connectivity), + stencil_descriptor, symmetry_boundary_descriptor_list); + } + default: { + throw UnexpectedError("invalid connectivity dimension"); + } + } +} + +NodeToCellStencilArray +StencilBuilder::buildN2C(const IConnectivity& connectivity, + const StencilDescriptor& stencil_descriptor, + const BoundaryDescriptorList& symmetry_boundary_descriptor_list) const +{ + if ((parallel::size() > 1) and + (stencil_descriptor.numberOfLayers() > GlobalVariableManager::instance().getNumberOfGhostLayers())) { + std::ostringstream error_msg; + error_msg << "Stencil builder requires " << rang::fgB::yellow << stencil_descriptor.numberOfLayers() + << rang::fg::reset << " layers while parallel number of ghost layer is " + << GlobalVariableManager::instance().getNumberOfGhostLayers() << ".\n"; + error_msg << "Increase the number of ghost layers (using the '--number-of-ghost-layers' option)."; + throw NormalError(error_msg.str()); + } + + switch (connectivity.dimension()) { + case 1: { + return this->_build<ItemType::node, ItemType::cell>(dynamic_cast<const Connectivity<1>&>(connectivity), + stencil_descriptor, symmetry_boundary_descriptor_list); + } + case 2: { + return this->_build<ItemType::node, ItemType::cell>(dynamic_cast<const Connectivity<2>&>(connectivity), + stencil_descriptor, symmetry_boundary_descriptor_list); + } + case 3: { + return this->_build<ItemType::node, ItemType::cell>(dynamic_cast<const Connectivity<3>&>(connectivity), + stencil_descriptor, symmetry_boundary_descriptor_list); + } + default: { + throw UnexpectedError("invalid connectivity dimension"); + } + } +} diff --git a/src/mesh/StencilBuilder.hpp b/src/mesh/StencilBuilder.hpp new file mode 100644 index 0000000000000000000000000000000000000000..2ed1d3a6c45fdac651ce62ba05690532c506cf26 --- /dev/null +++ b/src/mesh/StencilBuilder.hpp @@ -0,0 +1,93 @@ +#ifndef STENCIL_BUILDER_HPP +#define STENCIL_BUILDER_HPP + +#include <mesh/IBoundaryDescriptor.hpp> +#include <mesh/StencilArray.hpp> +#include <mesh/StencilDescriptor.hpp> + +#include <string> +#include <vector> + +class IConnectivity; + +class StencilBuilder +{ + public: + using BoundaryDescriptorList = std::vector<std::shared_ptr<const IBoundaryDescriptor>>; + + private: + template <ItemType item_type> + class Layer; + + template <ItemType connecting_item, typename ConnectivityType> + auto _buildSymmetryConnectingItemList(const ConnectivityType& connectivity, + const BoundaryDescriptorList& symmetry_boundary_descriptor_list) const; + + template <ItemType item_type, ItemType connecting_item_type, typename ConnectivityType> + StencilArray<item_type, item_type> _build_for_same_source_and_target( + const ConnectivityType& connectivity, + const size_t& number_of_layers, + const BoundaryDescriptorList& symmetry_boundary_descriptor_list) const; + + template <ItemType source_item_type, + ItemType connecting_item_type, + ItemType target_item_type, + typename ConnectivityType> + StencilArray<source_item_type, target_item_type> _build_for_different_source_and_target( + const ConnectivityType& connectivity, + const size_t& number_of_layers, + const BoundaryDescriptorList& symmetry_boundary_descriptor_list) const; + + template <ItemType source_item_type, + ItemType connecting_item_type, + ItemType target_item_type, + typename ConnectivityType> + StencilArray<source_item_type, target_item_type> _build( + const ConnectivityType& connectivity, + const size_t& number_of_layers, + const BoundaryDescriptorList& symmetry_boundary_descriptor_list) const; + + template <ItemType source_item_type, ItemType target_item_type, typename ConnectivityType> + StencilArray<source_item_type, target_item_type> _build( + const ConnectivityType& connectivity, + const StencilDescriptor& stencil_descriptor, + const BoundaryDescriptorList& symmetry_boundary_descriptor_list) const; + + template <typename ConnectivityType> + NodeToCellStencilArray _buildN2C(const ConnectivityType& connectivity, + size_t number_of_layers, + const BoundaryDescriptorList& symmetry_boundary_descriptor_list) const; + + CellToCellStencilArray buildC2C(const IConnectivity& connectivity, + const StencilDescriptor& stencil_descriptor, + const BoundaryDescriptorList& symmetry_boundary_descriptor_list) const; + + NodeToCellStencilArray buildN2C(const IConnectivity& connectivity, + const StencilDescriptor& stencil_descriptor, + const BoundaryDescriptorList& symmetry_boundary_descriptor_list) const; + + friend class StencilManager; + + template <ItemType source_item_type, ItemType target_item_type> + StencilArray<source_item_type, target_item_type> + _build(const IConnectivity& connectivity, + const StencilDescriptor& stencil_descriptor, + const BoundaryDescriptorList& symmetry_boundary_descriptor_list) const + { + if constexpr ((source_item_type == ItemType::cell) and (target_item_type == ItemType::cell)) { + return buildC2C(connectivity, stencil_descriptor, symmetry_boundary_descriptor_list); + } else if constexpr ((source_item_type == ItemType::node) and (target_item_type == ItemType::cell)) { + return buildN2C(connectivity, stencil_descriptor, symmetry_boundary_descriptor_list); + } else { + static_assert(is_false_item_type_v<source_item_type>, "invalid stencil type"); + } + } + + public: + StencilBuilder() = default; + StencilBuilder(const StencilBuilder&) = default; + StencilBuilder(StencilBuilder&&) = default; + ~StencilBuilder() = default; +}; + +#endif // STENCIL_BUILDER_HPP diff --git a/src/mesh/StencilDescriptor.hpp b/src/mesh/StencilDescriptor.hpp new file mode 100644 index 0000000000000000000000000000000000000000..d099f00864ea7f2485664fc4ed6928a69bf95bfc --- /dev/null +++ b/src/mesh/StencilDescriptor.hpp @@ -0,0 +1,62 @@ +#ifndef STENCIL_DESCRIPTOR_HPP +#define STENCIL_DESCRIPTOR_HPP + +#include <utils/PugsMacros.hpp> + +#include <cstddef> + +class StencilDescriptor +{ + public: + enum class ConnectionType : int + { + by_nodes = 1, + by_edges = 2, + by_faces = 3, + by_cells = 4 + }; + + private: + size_t m_number_of_layers; + ConnectionType m_connection_type; + + public: + PUGS_INLINE + const size_t& + numberOfLayers() const + { + return m_number_of_layers; + }; + + PUGS_INLINE + const ConnectionType& + connectionType() const + { + return m_connection_type; + }; + + PUGS_INLINE + friend bool + operator==(const StencilDescriptor& sd0, const StencilDescriptor& sd1) + { + return (sd0.m_number_of_layers == sd1.m_number_of_layers) and (sd0.m_connection_type == sd1.m_connection_type); + } + + PUGS_INLINE + friend bool + operator!=(const StencilDescriptor& sd0, const StencilDescriptor& sd1) + { + return not(sd0 == sd1); + } + + StencilDescriptor(const size_t number_of_layers, const ConnectionType type) + : m_number_of_layers{number_of_layers}, m_connection_type{type} + {} + + StencilDescriptor(const StencilDescriptor&) = default; + StencilDescriptor(StencilDescriptor&&) = default; + + ~StencilDescriptor() = default; +}; + +#endif // STENCIL_DESCRIPTOR_HPP diff --git a/src/mesh/StencilManager.cpp b/src/mesh/StencilManager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..04a8e32a1d50d872d95896e8e7a58e76e32e2189 --- /dev/null +++ b/src/mesh/StencilManager.cpp @@ -0,0 +1,99 @@ +#include <mesh/StencilManager.hpp> + +#include <mesh/StencilBuilder.hpp> +#include <utils/Exceptions.hpp> + +StencilManager* StencilManager::m_instance = nullptr; + +void +StencilManager::create() +{ + Assert(m_instance == nullptr, "StencilManager is already created"); + m_instance = new StencilManager; +} + +void +StencilManager::destroy() +{ + Assert(m_instance != nullptr, "StencilManager was not created"); + + // LCOV_EXCL_START + auto check_still_registered = [](auto& stored_stencil_map) { + if (stored_stencil_map.size() > 0) { + std::stringstream error; + error << ": some connectivities are still registered\n"; + for (const auto& [key, stencil_p] : stored_stencil_map) { + error << " - connectivity of id " << rang::fgB::magenta << key.connectivity_id << rang::style::reset << '\n'; + } + throw UnexpectedError(error.str()); + } + }; + + check_still_registered(m_instance->m_stored_cell_to_cell_stencil_map); + check_still_registered(m_instance->m_stored_node_to_cell_stencil_map); + // LCOV_EXCL_STOP + + delete m_instance; + m_instance = nullptr; +} + +template <ItemType source_item_type, ItemType target_item_type> +const StencilArray<source_item_type, target_item_type>& +StencilManager::_getStencilArray( + const IConnectivity& connectivity, + const StencilDescriptor& stencil_descriptor, + const BoundaryDescriptorList& symmetry_boundary_descriptor_list, + StoredStencilTMap<source_item_type, target_item_type>& stored_source_to_target_stencil_map) +{ + if (not stored_source_to_target_stencil_map.contains( + Key{connectivity.id(), stencil_descriptor, symmetry_boundary_descriptor_list})) { + stored_source_to_target_stencil_map[Key{connectivity.id(), stencil_descriptor, symmetry_boundary_descriptor_list}] = + std::make_shared<StencilArray<source_item_type, target_item_type>>( + StencilBuilder{}.template _build<source_item_type, target_item_type>(connectivity, stencil_descriptor, + symmetry_boundary_descriptor_list)); + } + + return *stored_source_to_target_stencil_map.at( + Key{connectivity.id(), stencil_descriptor, symmetry_boundary_descriptor_list}); +} + +const CellToCellStencilArray& +StencilManager::getCellToCellStencilArray(const IConnectivity& connectivity, + const StencilDescriptor& stencil_descriptor, + const BoundaryDescriptorList& symmetry_boundary_descriptor_list) +{ + return this->_getStencilArray<ItemType::cell, ItemType::cell>(connectivity, stencil_descriptor, + symmetry_boundary_descriptor_list, + m_stored_cell_to_cell_stencil_map); +} + +const NodeToCellStencilArray& +StencilManager::getNodeToCellStencilArray(const IConnectivity& connectivity, + const StencilDescriptor& stencil_descriptor, + const BoundaryDescriptorList& symmetry_boundary_descriptor_list) +{ + return this->_getStencilArray<ItemType::node, ItemType::cell>(connectivity, stencil_descriptor, + symmetry_boundary_descriptor_list, + m_stored_node_to_cell_stencil_map); +} + +void +StencilManager::deleteConnectivity(const size_t connectivity_id) +{ + auto delete_connectivity_stencil = [&connectivity_id](auto& stored_stencil_map) { + bool has_removed = false; + do { + has_removed = false; + for (const auto& [key, p_stencil] : stored_stencil_map) { + if (connectivity_id == key.connectivity_id) { + stored_stencil_map.erase(key); + has_removed = true; + break; + } + } + } while (has_removed); + }; + + delete_connectivity_stencil(m_stored_cell_to_cell_stencil_map); + delete_connectivity_stencil(m_stored_node_to_cell_stencil_map); +} diff --git a/src/mesh/StencilManager.hpp b/src/mesh/StencilManager.hpp new file mode 100644 index 0000000000000000000000000000000000000000..8f07e282848af23bca8604e5bbc68754c63251a7 --- /dev/null +++ b/src/mesh/StencilManager.hpp @@ -0,0 +1,110 @@ +#ifndef STENCIL_MANAGER_HPP +#define STENCIL_MANAGER_HPP + +#include <mesh/IBoundaryDescriptor.hpp> +#include <mesh/IConnectivity.hpp> +#include <mesh/StencilArray.hpp> +#include <mesh/StencilDescriptor.hpp> + +#include <memory> +#include <set> +#include <unordered_map> +#include <vector> + +class StencilManager +{ + public: + using BoundaryDescriptorList = std::vector<std::shared_ptr<const IBoundaryDescriptor>>; + + private: + StencilManager() = default; + ~StencilManager() = default; + + static StencilManager* m_instance; + + struct Key + { + size_t connectivity_id; + StencilDescriptor stencil_descriptor; + BoundaryDescriptorList symmetry_boundary_descriptor_list; + + PUGS_INLINE bool + operator==(const Key& k) const + { + if ((connectivity_id != k.connectivity_id) or (stencil_descriptor != k.stencil_descriptor) or + (symmetry_boundary_descriptor_list.size() != k.symmetry_boundary_descriptor_list.size())) { + return false; + } + + std::set<std::string> boundary_descriptor_set; + for (auto&& p_boundary_descriptor : symmetry_boundary_descriptor_list) { + const std::string name = stringify(*p_boundary_descriptor); + boundary_descriptor_set.insert(name); + } + + std::set<std::string> k_boundary_descriptor_set; + for (auto&& p_boundary_descriptor : k.symmetry_boundary_descriptor_list) { + const std::string name = stringify(*p_boundary_descriptor); + k_boundary_descriptor_set.insert(name); + } + + return boundary_descriptor_set == k_boundary_descriptor_set; + } + }; + + struct HashKey + { + size_t + operator()(const Key& key) const + { + // We do not use the symmetry boundary set by now + return ((std::hash<decltype(Key::connectivity_id)>()(key.connectivity_id)) ^ + (std::hash<std::decay_t<decltype(Key::stencil_descriptor.numberOfLayers())>>()( + key.stencil_descriptor.numberOfLayers()) + << 1)); + } + }; + + template <ItemType source_item_type, ItemType target_item_type> + using StoredStencilTMap = + std::unordered_map<Key, std::shared_ptr<const StencilArray<source_item_type, target_item_type>>, HashKey>; + + StoredStencilTMap<ItemType::cell, ItemType::cell> m_stored_cell_to_cell_stencil_map; + StoredStencilTMap<ItemType::node, ItemType::cell> m_stored_node_to_cell_stencil_map; + + template <ItemType source_item_type, ItemType target_item_type> + const StencilArray<source_item_type, target_item_type>& _getStencilArray( + const IConnectivity& connectivity, + const StencilDescriptor& stencil_descriptor, + const BoundaryDescriptorList& symmetry_boundary_descriptor_list, + StoredStencilTMap<source_item_type, target_item_type>& stored_source_to_target_stencil_map); + + public: + static void create(); + static void destroy(); + + PUGS_INLINE + static StencilManager& + instance() + { + Assert(m_instance != nullptr, "StencilManager was not created!"); + return *m_instance; + } + + void deleteConnectivity(const size_t connectivity_id); + + const CellToCellStencilArray& getCellToCellStencilArray( + const IConnectivity& i_connectivity, + const StencilDescriptor& stencil_descriptor, + const BoundaryDescriptorList& symmetry_boundary_descriptor_list = {}); + + const NodeToCellStencilArray& getNodeToCellStencilArray( + const IConnectivity& i_connectivity, + const StencilDescriptor& stencil_descriptor, + const BoundaryDescriptorList& symmetry_boundary_descriptor_list = {}); + + StencilManager(const StencilManager&) = delete; + StencilManager(StencilManager&&) = delete; +}; + +#endif // STENCIL_MANAGER_HPP diff --git a/src/output/OutputNamedItemValueSet.hpp b/src/output/OutputNamedItemValueSet.hpp index aa2c5e2ed67140d47c7d4ddf1f57caecc2f28360..8c43330420c91417e0c339228b4bfb41f9dc908b 100644 --- a/src/output/OutputNamedItemValueSet.hpp +++ b/src/output/OutputNamedItemValueSet.hpp @@ -38,7 +38,7 @@ class NamedItemData } NamedItemData& operator=(const NamedItemData&) = default; - NamedItemData& operator=(NamedItemData&&) = default; + NamedItemData& operator=(NamedItemData&&) = default; NamedItemData(const std::string& name, const ItemDataT<DataType, item_type, ConnectivityPtr>& item_data) : m_name(name), m_item_data(item_data) @@ -113,24 +113,48 @@ class OutputNamedItemDataSet NodeArray<const long int>, NodeArray<const unsigned long int>, NodeArray<const double>, + NodeArray<const TinyVector<1, double>>, + NodeArray<const TinyVector<2, double>>, + NodeArray<const TinyVector<3, double>>, + NodeArray<const TinyMatrix<1, 1, double>>, + NodeArray<const TinyMatrix<2, 2, double>>, + NodeArray<const TinyMatrix<3, 3, double>>, EdgeArray<const bool>, EdgeArray<const int>, EdgeArray<const long int>, EdgeArray<const unsigned long int>, EdgeArray<const double>, + EdgeArray<const TinyVector<1, double>>, + EdgeArray<const TinyVector<2, double>>, + EdgeArray<const TinyVector<3, double>>, + EdgeArray<const TinyMatrix<1, 1, double>>, + EdgeArray<const TinyMatrix<2, 2, double>>, + EdgeArray<const TinyMatrix<3, 3, double>>, FaceArray<const bool>, FaceArray<const int>, FaceArray<const long int>, FaceArray<const unsigned long int>, FaceArray<const double>, + FaceArray<const TinyVector<1, double>>, + FaceArray<const TinyVector<2, double>>, + FaceArray<const TinyVector<3, double>>, + FaceArray<const TinyMatrix<1, 1, double>>, + FaceArray<const TinyMatrix<2, 2, double>>, + FaceArray<const TinyMatrix<3, 3, double>>, CellArray<const bool>, CellArray<const int>, CellArray<const long int>, CellArray<const unsigned long int>, - CellArray<const double>>; + CellArray<const double>, + CellArray<const TinyVector<1, double>>, + CellArray<const TinyVector<2, double>>, + CellArray<const TinyVector<3, double>>, + CellArray<const TinyMatrix<1, 1, double>>, + CellArray<const TinyMatrix<2, 2, double>>, + CellArray<const TinyMatrix<3, 3, double>>>; private: // We do not use a map, we want variables to be written in the diff --git a/src/output/VTKWriter.cpp b/src/output/VTKWriter.cpp index 03e6337f572e494e94d1a31b48991c9444c881bd..b5771c3abe4b3edb21cb318c5c6dcd04d7b68a79 100644 --- a/src/output/VTKWriter.cpp +++ b/src/output/VTKWriter.cpp @@ -398,10 +398,17 @@ VTKWriter::_write(const MeshType& mesh, << "\">\n"; fout << "<CellData>\n"; for (const auto& [name, item_value_variant] : output_named_item_data_set) { - std::visit([&, var_name = name]( - auto&& - item_value) { return this->_write_cell_data(fout, var_name, item_value, serialize_data_list); }, - item_value_variant); + std::visit( + [&, var_name = name](auto&& item_value) { + using IVType = std::decay_t<decltype(item_value)>; + using DataType = typename IVType::data_type; + if constexpr (is_item_array_v<IVType> and not std::is_arithmetic_v<DataType>) { + throw NotImplementedError("DiscreteFunctionP0Vector of non arithmetic type"); + } else { + return this->_write_cell_data(fout, var_name, item_value, serialize_data_list); + } + }, + item_value_variant); } if (parallel::size() > 1) { CellValue<uint8_t> vtk_ghost_type{mesh.connectivity()}; @@ -415,10 +422,17 @@ VTKWriter::_write(const MeshType& mesh, fout << "</CellData>\n"; fout << "<PointData>\n"; for (const auto& [name, item_value_variant] : output_named_item_data_set) { - std::visit([&, var_name = name]( - auto&& - item_value) { return this->_write_node_data(fout, var_name, item_value, serialize_data_list); }, - item_value_variant); + std::visit( + [&, var_name = name](auto&& item_value) { + using IVType = std::decay_t<decltype(item_value)>; + using DataType = typename IVType::data_type; + if constexpr (is_item_array_v<IVType> and not std::is_arithmetic_v<DataType>) { + throw NotImplementedError("DiscreteFunctionP0Vector of non arithmetic type"); + } else { + return this->_write_node_data(fout, var_name, item_value, serialize_data_list); + } + }, + item_value_variant); } fout << "</PointData>\n"; fout << "<Points>\n"; @@ -646,15 +660,33 @@ VTKWriter::_write(const MeshType& mesh, fout << "<PPointData>\n"; for (const auto& [name, item_value_variant] : output_named_item_data_set) { - std::visit([&, var_name = name](auto&& item_value) { return this->_write_node_pvtu(fout, var_name, item_value); }, - item_value_variant); + std::visit( + [&, var_name = name](auto&& item_value) { + using IVType = std::decay_t<decltype(item_value)>; + using DataType = typename IVType::data_type; + if constexpr (is_item_array_v<IVType> and not std::is_arithmetic_v<DataType>) { + throw NotImplementedError("DiscreteFunctionP0Vector of non arithmetic type"); + } else { + return this->_write_node_pvtu(fout, var_name, item_value); + } + }, + item_value_variant); } fout << "</PPointData>\n"; fout << "<PCellData>\n"; for (const auto& [name, item_value_variant] : output_named_item_data_set) { - std::visit([&, var_name = name](auto&& item_value) { return this->_write_cell_pvtu(fout, var_name, item_value); }, - item_value_variant); + std::visit( + [&, var_name = name](auto&& item_value) { + using IVType = std::decay_t<decltype(item_value)>; + using DataType = typename IVType::data_type; + if constexpr (is_item_array_v<IVType> and not std::is_arithmetic_v<DataType>) { + throw NotImplementedError("DiscreteFunctionP0Vector of non arithmetic type"); + } else { + return this->_write_cell_pvtu(fout, var_name, item_value); + } + }, + item_value_variant); } if (parallel::size() > 1) { fout << "<PDataArray type=\"UInt8\" Name=\"vtkGhostType\" NumberOfComponents=\"1\"/>\n"; diff --git a/src/scheme/AcousticSolver.cpp b/src/scheme/AcousticSolver.cpp index e4db63bd7ec57502c08a1e3f212386ebe8215859..5c8e894c0e2d804813a727f18fd5de44ecdc9cd8 100644 --- a/src/scheme/AcousticSolver.cpp +++ b/src/scheme/AcousticSolver.cpp @@ -17,6 +17,7 @@ #include <scheme/IBoundaryConditionDescriptor.hpp> #include <scheme/IDiscreteFunctionDescriptor.hpp> #include <scheme/SymmetryBoundaryConditionDescriptor.hpp> +#include <utils/GlobalVariableManager.hpp> #include <utils/Socket.hpp> #include <variant> @@ -371,6 +372,10 @@ class AcousticSolverHandler::AcousticSolver final : public AcousticSolverHandler const std::shared_ptr<const DiscreteFunctionVariant>& p_v, const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const { + if ((parallel::size() > 1) and (GlobalVariableManager::instance().getNumberOfGhostLayers() == 0)) { + throw NormalError("Acoustic solver requires 1 layer of ghost cells in parallel"); + } + std::shared_ptr mesh_v = getCommonMesh({rho_v, c_v, u_v, p_v}); if (not mesh_v) { throw NormalError("discrete functions are not defined on the same mesh"); diff --git a/src/scheme/CMakeLists.txt b/src/scheme/CMakeLists.txt index a4642f38ffbc8a07e6e341ada1ce63e54a2137cf..852a6cd751d092a7ab1c59437c1e549ae2f8094f 100644 --- a/src/scheme/CMakeLists.txt +++ b/src/scheme/CMakeLists.txt @@ -1,5 +1,7 @@ # ------------------- Source files -------------------- +add_subdirectory(reconstruction_utils) + add_library( PugsScheme AcousticSolver.cpp @@ -11,9 +13,16 @@ add_library( FluxingAdvectionSolver.cpp HyperelasticSolver.cpp LoadBalancer.cpp + PolynomialReconstruction.cpp ) target_link_libraries( PugsScheme ${HIGHFIVE_TARGET} ) + +# Additional dependencies +add_dependencies( + PugsScheme + PugsSchemeReconstructionUtils +) diff --git a/src/scheme/CellIntegrator.hpp b/src/scheme/CellIntegrator.hpp index 33241f9b8683700d24507000a50ed0f0ae507517..068e9cebe078916c686d9dbe0d7e57874df86e14 100644 --- a/src/scheme/CellIntegrator.hpp +++ b/src/scheme/CellIntegrator.hpp @@ -89,9 +89,6 @@ class CellIntegrator 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>) { diff --git a/src/scheme/DiscreteFunctionDPk.hpp b/src/scheme/DiscreteFunctionDPk.hpp new file mode 100644 index 0000000000000000000000000000000000000000..aacac5380deb5896ea5f502ecac6f7a53dc46a82 --- /dev/null +++ b/src/scheme/DiscreteFunctionDPk.hpp @@ -0,0 +1,148 @@ +#ifndef DISCRETE_FUNCTION_DPK_HPP +#define DISCRETE_FUNCTION_DPK_HPP + +#include <language/utils/ASTNodeDataTypeTraits.hpp> + +#include <mesh/ItemArray.hpp> +#include <mesh/ItemArrayUtils.hpp> +#include <mesh/MeshData.hpp> +#include <mesh/MeshDataManager.hpp> +#include <mesh/MeshVariant.hpp> +#include <scheme/DiscreteFunctionDescriptorP0.hpp> +#include <scheme/PolynomialCenteredCanonicalBasisView.hpp> + +template <size_t Dimension, + typename DataType, + typename BasisView = PolynomialCenteredCanonicalBasisView<Dimension, DataType>> +class DiscreteFunctionDPk +{ + public: + using BasisViewType = BasisView; + using data_type = DataType; + + friend class DiscreteFunctionDPk<Dimension, std::add_const_t<DataType>>; + friend class DiscreteFunctionDPk<Dimension, std::remove_const_t<DataType>>; + + private: + std::shared_ptr<const MeshVariant> m_mesh_v; + size_t m_degree; + CellArray<DataType> m_by_cell_coefficients; + CellValue<const TinyVector<Dimension>> m_xj; + + public: + PUGS_INLINE + ASTNodeDataType + dataType() const + { + return ast_node_data_type_from<std::remove_const_t<DataType>>; + } + + PUGS_INLINE size_t + degree() const + { + return m_degree; + } + + PUGS_INLINE + const CellArray<DataType>& + cellArrays() const + { + return m_by_cell_coefficients; + } + + PUGS_INLINE std::shared_ptr<const MeshVariant> + meshVariant() const + { + return m_mesh_v; + } + + PUGS_FORCEINLINE + operator DiscreteFunctionDPk<Dimension, const DataType>() const + { + return DiscreteFunctionDPk<Dimension, const DataType>(m_mesh_v, m_by_cell_coefficients); + } + + PUGS_INLINE + void + fill(const DataType& data) const noexcept + { + static_assert(not std::is_const_v<DataType>, "cannot modify DiscreteFunctionDPk of const"); + m_by_cell_coefficients.fill(data); + } + + friend PUGS_INLINE DiscreteFunctionDPk<Dimension, std::remove_const_t<DataType>> + copy(const DiscreteFunctionDPk& source) + { + return DiscreteFunctionDPk<Dimension, std::remove_const_t<DataType>>{source.m_mesh_v, copy(source.cellArrays())}; + } + + friend PUGS_INLINE void + copy_to(const DiscreteFunctionDPk<Dimension, DataType>& source, + DiscreteFunctionDPk<Dimension, std::remove_const_t<DataType>>& destination) + { + Assert(source.m_mesh_v->id() == destination.m_mesh_v->id(), "copy_to target must use the same mesh"); + Assert(source.m_degree == destination.m_degree, "copy_to target must have the same degree"); + copy_to(source.m_by_cell_coefficients, destination.m_by_cell_coefficients); + } + + PUGS_INLINE + auto + coefficients(const CellId cell_id) const + { + return m_by_cell_coefficients[cell_id]; + } + + PUGS_FORCEINLINE BasisView + operator[](const CellId cell_id) const noexcept(NO_ASSERT) + { + Assert(m_mesh_v.use_count() > 0, "DiscreteFunctionDPk is not built"); + return BasisView{m_degree, m_by_cell_coefficients[cell_id], m_xj[cell_id]}; + } + + PUGS_INLINE DiscreteFunctionDPk + operator=(const DiscreteFunctionDPk& f) + { + m_mesh_v = f.m_mesh_v; + m_degree = f.m_degree; + m_by_cell_coefficients = f.m_by_cell_coefficients; + m_xj = f.m_xj; + + return *this; + } + + DiscreteFunctionDPk(const std::shared_ptr<const MeshVariant>& mesh_v, size_t degree) + : m_mesh_v{mesh_v}, + m_degree{degree}, + m_by_cell_coefficients{mesh_v->connectivity(), BasisView::dimensionFromDegree(degree)}, + m_xj{MeshDataManager::instance().getMeshData(*m_mesh_v->get<Mesh<Dimension>>()).xj()} + {} + + DiscreteFunctionDPk(const std::shared_ptr<const MeshVariant>& mesh_v, const CellArray<DataType>& cell_array) + : m_mesh_v{mesh_v}, + m_degree{BasisView::degreeFromDimension(cell_array.sizeOfArrays())}, + m_by_cell_coefficients{cell_array}, + m_xj{MeshDataManager::instance().getMeshData(*m_mesh_v->get<Mesh<Dimension>>()).xj()} + { + Assert(mesh_v->connectivity().id() == cell_array.connectivity_ptr()->id(), + "cell_array is built on different connectivity"); + } + + template <MeshConcept MeshType> + DiscreteFunctionDPk(const std::shared_ptr<const MeshType>& p_mesh, size_t degree) + : DiscreteFunctionDPk{p_mesh->meshVariant(), degree} + {} + + template <MeshConcept MeshType> + DiscreteFunctionDPk(const std::shared_ptr<const MeshType>& p_mesh, const CellArray<DataType>& cell_array) + : DiscreteFunctionDPk{p_mesh->meshVariant(), cell_array} + {} + + DiscreteFunctionDPk() noexcept = default; + + DiscreteFunctionDPk(const DiscreteFunctionDPk&) noexcept = default; + DiscreteFunctionDPk(DiscreteFunctionDPk&&) noexcept = default; + + ~DiscreteFunctionDPk() = default; +}; + +#endif // DISCRETE_FUNCTION_DPK_HPP diff --git a/src/scheme/DiscreteFunctionDPkVariant.hpp b/src/scheme/DiscreteFunctionDPkVariant.hpp new file mode 100644 index 0000000000000000000000000000000000000000..2942f141f38452faed9e95a5f95ca675bc7d8c29 --- /dev/null +++ b/src/scheme/DiscreteFunctionDPkVariant.hpp @@ -0,0 +1,134 @@ +#ifndef DISCRETE_FUNCTION_DPK_VARIANT_HPP +#define DISCRETE_FUNCTION_DPK_VARIANT_HPP + +#include <scheme/DiscreteFunctionDPk.hpp> +#include <scheme/DiscreteFunctionDPkVector.hpp> + +#include <memory> +#include <variant> + +class DiscreteFunctionDPkVariant +{ + public: + using Variant = std::variant<DiscreteFunctionDPk<1, const double>, + DiscreteFunctionDPk<1, const TinyVector<1>>, + DiscreteFunctionDPk<1, const TinyVector<2>>, + DiscreteFunctionDPk<1, const TinyVector<3>>, + DiscreteFunctionDPk<1, const TinyMatrix<1>>, + DiscreteFunctionDPk<1, const TinyMatrix<2>>, + DiscreteFunctionDPk<1, const TinyMatrix<3>>, + + DiscreteFunctionDPk<2, const double>, + DiscreteFunctionDPk<2, const TinyVector<1>>, + DiscreteFunctionDPk<2, const TinyVector<2>>, + DiscreteFunctionDPk<2, const TinyVector<3>>, + DiscreteFunctionDPk<2, const TinyMatrix<1>>, + DiscreteFunctionDPk<2, const TinyMatrix<2>>, + DiscreteFunctionDPk<2, const TinyMatrix<3>>, + + DiscreteFunctionDPk<3, const double>, + DiscreteFunctionDPk<3, const TinyVector<1>>, + DiscreteFunctionDPk<3, const TinyVector<2>>, + DiscreteFunctionDPk<3, const TinyVector<3>>, + DiscreteFunctionDPk<3, const TinyMatrix<1>>, + DiscreteFunctionDPk<3, const TinyMatrix<2>>, + DiscreteFunctionDPk<3, const TinyMatrix<3>>, + + DiscreteFunctionDPkVector<1, const double>, + DiscreteFunctionDPkVector<1, const TinyVector<1>>, + DiscreteFunctionDPkVector<1, const TinyVector<2>>, + DiscreteFunctionDPkVector<1, const TinyVector<3>>, + DiscreteFunctionDPkVector<1, const TinyMatrix<1>>, + DiscreteFunctionDPkVector<1, const TinyMatrix<2>>, + DiscreteFunctionDPkVector<1, const TinyMatrix<3>>, + + DiscreteFunctionDPkVector<2, const double>, + DiscreteFunctionDPkVector<2, const TinyVector<1>>, + DiscreteFunctionDPkVector<2, const TinyVector<2>>, + DiscreteFunctionDPkVector<2, const TinyVector<3>>, + DiscreteFunctionDPkVector<2, const TinyMatrix<1>>, + DiscreteFunctionDPkVector<2, const TinyMatrix<2>>, + DiscreteFunctionDPkVector<2, const TinyMatrix<3>>, + + DiscreteFunctionDPkVector<3, const double>, + DiscreteFunctionDPkVector<3, const TinyVector<1>>, + DiscreteFunctionDPkVector<3, const TinyVector<2>>, + DiscreteFunctionDPkVector<3, const TinyVector<3>>, + DiscreteFunctionDPkVector<3, const TinyMatrix<1>>, + DiscreteFunctionDPkVector<3, const TinyMatrix<2>>, + DiscreteFunctionDPkVector<3, const TinyMatrix<3>>>; + + private: + Variant m_discrete_function_dpk; + + public: + PUGS_INLINE + const Variant& + discreteFunctionDPk() const + { + return m_discrete_function_dpk; + } + + template <typename DiscreteFunctionDPkT> + PUGS_INLINE auto + get() const + { + static_assert(is_discrete_function_dpk_v<DiscreteFunctionDPkT>, "invalid template argument"); + using DataType = typename DiscreteFunctionDPkT::data_type; + static_assert(std::is_const_v<DataType>, "data type of extracted discrete function must be const"); + +#ifndef NDEBUG + if (not std::holds_alternative<DiscreteFunctionDPkT>(this->m_discrete_function_dpk)) { + std::ostringstream error_msg; + error_msg << "invalid discrete function type\n"; + error_msg << "- required " << rang::fgB::red << demangle<DiscreteFunctionDPkT>() << rang::fg::reset << '\n'; + error_msg << "- contains " << rang::fgB::yellow + << std::visit([](auto&& f) -> std::string { return demangle<decltype(f)>(); }, + this->m_discrete_function_dpk) + << rang::fg::reset; + throw NormalError(error_msg.str()); + } +#endif // NDEBUG + + return std::get<DiscreteFunctionDPkT>(this->discreteFunctionDPk()); + } + + template <size_t Dimension, typename DataType> + DiscreteFunctionDPkVariant(const DiscreteFunctionDPk<Dimension, DataType>& discrete_function_dpk) + : m_discrete_function_dpk{DiscreteFunctionDPk<Dimension, const DataType>{discrete_function_dpk}} + { + static_assert(std::is_same_v<std::remove_const_t<DataType>, double> or // + std::is_same_v<std::remove_const_t<DataType>, TinyVector<1, double>> or // + std::is_same_v<std::remove_const_t<DataType>, TinyVector<2, double>> or // + std::is_same_v<std::remove_const_t<DataType>, TinyVector<3, double>> or // + std::is_same_v<std::remove_const_t<DataType>, TinyMatrix<1, 1, double>> or // + std::is_same_v<std::remove_const_t<DataType>, TinyMatrix<2, 2, double>> or // + std::is_same_v<std::remove_const_t<DataType>, TinyMatrix<3, 3, double>>, + "DiscreteFunctionDPk with this DataType is not allowed in variant"); + } + + template <size_t Dimension, typename DataType> + DiscreteFunctionDPkVariant(const DiscreteFunctionDPkVector<Dimension, DataType>& discrete_function_dpk) + : m_discrete_function_dpk{DiscreteFunctionDPkVector<Dimension, const DataType>{discrete_function_dpk}} + { + static_assert(std::is_same_v<std::remove_const_t<DataType>, double> or // + std::is_same_v<std::remove_const_t<DataType>, TinyVector<1, double>> or // + std::is_same_v<std::remove_const_t<DataType>, TinyVector<2, double>> or // + std::is_same_v<std::remove_const_t<DataType>, TinyVector<3, double>> or // + std::is_same_v<std::remove_const_t<DataType>, TinyMatrix<1, 1, double>> or // + std::is_same_v<std::remove_const_t<DataType>, TinyMatrix<2, 2, double>> or // + std::is_same_v<std::remove_const_t<DataType>, TinyMatrix<3, 3, double>>, + "DiscreteFunctionDPkVector with this DataType is not allowed in variant"); + } + + DiscreteFunctionDPkVariant& operator=(DiscreteFunctionDPkVariant&&) = default; + DiscreteFunctionDPkVariant& operator=(const DiscreteFunctionDPkVariant&) = default; + + DiscreteFunctionDPkVariant(const DiscreteFunctionDPkVariant&) = default; + DiscreteFunctionDPkVariant(DiscreteFunctionDPkVariant&&) = default; + + DiscreteFunctionDPkVariant() = delete; + ~DiscreteFunctionDPkVariant() = default; +}; + +#endif // DISCRETE_FUNCTION_DPK_VARIANT_HPP diff --git a/src/scheme/DiscreteFunctionDPkVector.hpp b/src/scheme/DiscreteFunctionDPkVector.hpp new file mode 100644 index 0000000000000000000000000000000000000000..e8ac729f4b3e002f8c1c3b2c1cdcbd5f4aef3f77 --- /dev/null +++ b/src/scheme/DiscreteFunctionDPkVector.hpp @@ -0,0 +1,221 @@ +#ifndef DISCRETE_FUNCTION_DPK_VECTOR_HPP +#define DISCRETE_FUNCTION_DPK_VECTOR_HPP + +#include <language/utils/ASTNodeDataTypeTraits.hpp> + +#include <mesh/ItemArray.hpp> +#include <mesh/ItemArrayUtils.hpp> +#include <mesh/MeshData.hpp> +#include <mesh/MeshDataManager.hpp> +#include <mesh/MeshVariant.hpp> +#include <scheme/DiscreteFunctionDescriptorP0.hpp> +#include <scheme/PolynomialCenteredCanonicalBasisView.hpp> + +template <size_t Dimension, + typename DataType, + typename BasisView = PolynomialCenteredCanonicalBasisView<Dimension, DataType>> +class DiscreteFunctionDPkVector +{ + public: + using data_type = DataType; + + friend class DiscreteFunctionDPkVector<Dimension, std::add_const_t<DataType>>; + friend class DiscreteFunctionDPkVector<Dimension, std::remove_const_t<DataType>>; + + class ComponentCoefficientView + { + private: + DataType* const m_data; + const size_t m_size; + + public: + size_t + size() const + { + return m_size; + } + + DataType& + operator[](size_t i) const + { + Assert(i < m_size); + return m_data[i]; + } + + ComponentCoefficientView(DataType* data, size_t size) : m_data{data}, m_size{size} {} + + ComponentCoefficientView(const ComponentCoefficientView&) = delete; + ComponentCoefficientView(ComponentCoefficientView&&) = delete; + + ~ComponentCoefficientView() = default; + }; + + private: + std::shared_ptr<const MeshVariant> m_mesh_v; + size_t m_degree; + size_t m_number_of_components; + size_t m_nb_coefficients_per_component; + CellArray<DataType> m_by_cell_coefficients; + CellValue<const TinyVector<Dimension>> m_xj; + + public: + PUGS_INLINE + ASTNodeDataType + dataType() const + { + return ast_node_data_type_from<std::remove_const_t<DataType>>; + } + + PUGS_INLINE size_t + degree() const + { + return m_degree; + } + + PUGS_INLINE size_t + numberOfComponents() const + { + return m_number_of_components; + } + + PUGS_INLINE + size_t + numberOfCoefficientsPerComponent() const + { + return m_nb_coefficients_per_component; + } + + PUGS_INLINE + const CellArray<DataType>& + cellArrays() const + { + return m_by_cell_coefficients; + } + + PUGS_INLINE std::shared_ptr<const MeshVariant> + meshVariant() const + { + return m_mesh_v; + } + + PUGS_FORCEINLINE + operator DiscreteFunctionDPkVector<Dimension, const DataType>() const + { + return DiscreteFunctionDPkVector<Dimension, const DataType>(m_mesh_v, m_degree, m_number_of_components, + m_by_cell_coefficients); + } + + PUGS_INLINE + void + fill(const DataType& data) const noexcept + { + static_assert(not std::is_const_v<DataType>, "Cannot modify ItemValue of const"); + m_by_cell_coefficients.fill(data); + } + + friend PUGS_INLINE DiscreteFunctionDPkVector<Dimension, std::remove_const_t<DataType>> + copy(const DiscreteFunctionDPkVector& source) + { + return DiscreteFunctionDPkVector<Dimension, std::remove_const_t<DataType>>{source.m_mesh_v, source.m_degree, + source.m_number_of_components, + copy(source.cellArrays())}; + } + + friend PUGS_INLINE void + copy_to(const DiscreteFunctionDPkVector<Dimension, DataType>& source, + DiscreteFunctionDPkVector<Dimension, std::remove_const_t<DataType>>& destination) + { + Assert(source.m_mesh_v->id() == destination.m_mesh_v->id(), "copy_to target must use the same mesh"); + Assert(source.m_degree == destination.m_degree, "copy_to target must have the same degree"); + Assert(source.m_number_of_components == destination.m_number_of_components, + "copy_to target must have the same number of components"); + copy_to(source.m_by_cell_coefficients, destination.m_by_cell_coefficients); + } + + PUGS_INLINE + auto + coefficients(const CellId cell_id) const + { + return m_by_cell_coefficients[cell_id]; + } + + PUGS_INLINE auto + componentCoefficients(const CellId cell_id, size_t i_component) const noexcept(NO_ASSERT) + { + Assert(m_mesh_v.use_count() > 0, "DiscreteFunctionDPkVector is not built"); + Assert(i_component < m_number_of_components, "incorrect component number"); + + return ComponentCoefficientView{&m_by_cell_coefficients[cell_id][i_component * m_nb_coefficients_per_component], + m_nb_coefficients_per_component}; + } + + PUGS_FORCEINLINE BasisView + operator()(const CellId cell_id, size_t i_component) const noexcept(NO_ASSERT) + { + Assert(m_mesh_v.use_count() > 0, "DiscreteFunctionDPkVector is not built"); + Assert(i_component < m_number_of_components, "incorrect component number"); + ComponentCoefficientView + component_coefficient_view{&m_by_cell_coefficients[cell_id][i_component * m_nb_coefficients_per_component], + m_nb_coefficients_per_component}; + return BasisView{m_degree, component_coefficient_view, m_xj[cell_id]}; + } + + PUGS_INLINE DiscreteFunctionDPkVector + operator=(const DiscreteFunctionDPkVector& f) + { + m_mesh_v = f.m_mesh_v; + m_degree = f.m_degree; + m_number_of_components = f.m_number_of_components; + m_nb_coefficients_per_component = f.m_nb_coefficients_per_component; + m_by_cell_coefficients = f.m_by_cell_coefficients; + return *this; + } + + DiscreteFunctionDPkVector(const std::shared_ptr<const MeshVariant>& mesh_v, + size_t degree, + size_t number_of_components) + : m_mesh_v{mesh_v}, + m_degree{degree}, + m_number_of_components{number_of_components}, + m_nb_coefficients_per_component(BasisView::dimensionFromDegree(degree)), + m_by_cell_coefficients{mesh_v->connectivity(), m_number_of_components * BasisView::dimensionFromDegree(degree)}, + m_xj{MeshDataManager::instance().getMeshData(*m_mesh_v->get<Mesh<Dimension>>()).xj()} + {} + + DiscreteFunctionDPkVector(const std::shared_ptr<const MeshVariant>& mesh_v, + size_t degree, + size_t number_of_components, + CellArray<DataType> by_cell_coefficients) + : m_mesh_v{mesh_v}, + m_degree{degree}, + m_number_of_components{number_of_components}, + m_nb_coefficients_per_component(BasisView::dimensionFromDegree(degree)), + m_by_cell_coefficients{by_cell_coefficients}, + m_xj{MeshDataManager::instance().getMeshData(*m_mesh_v->get<Mesh<Dimension>>()).xj()} + { + Assert(mesh_v->connectivity().id() == by_cell_coefficients.connectivity_ptr()->id(), + "cell_array is built on different connectivity"); + } + + template <MeshConcept MeshType> + DiscreteFunctionDPkVector(const std::shared_ptr<const MeshType>& p_mesh, size_t degree, size_t number_of_components) + : DiscreteFunctionDPkVector{p_mesh->meshVariant(), degree, number_of_components} + {} + + template <MeshConcept MeshType> + DiscreteFunctionDPkVector(const std::shared_ptr<const MeshType>& p_mesh, + size_t degree, + size_t number_of_components, + CellArray<DataType> by_cell_coefficients) + : DiscreteFunctionDPkVector{p_mesh->meshVariant(), degree, number_of_components, by_cell_coefficients} + {} + + DiscreteFunctionDPkVector() noexcept = default; + + DiscreteFunctionDPkVector(const DiscreteFunctionDPkVector&) noexcept = default; + DiscreteFunctionDPkVector(DiscreteFunctionDPkVector&&) noexcept = default; + + ~DiscreteFunctionDPkVector() = default; +}; + +#endif // DISCRETE_FUNCTION_DPK_VECTOR_HPP diff --git a/src/scheme/DiscreteFunctionP0Vector.hpp b/src/scheme/DiscreteFunctionP0Vector.hpp index da3904b2ad9853a05a521920c3a3a9434aac14a9..bc08cb2a7f27286b9bafdd8bb15d0c761b892d1b 100644 --- a/src/scheme/DiscreteFunctionP0Vector.hpp +++ b/src/scheme/DiscreteFunctionP0Vector.hpp @@ -19,8 +19,6 @@ class DiscreteFunctionP0Vector friend class DiscreteFunctionP0Vector<std::add_const_t<DataType>>; friend class DiscreteFunctionP0Vector<std::remove_const_t<DataType>>; - static_assert(std::is_arithmetic_v<DataType>, "DiscreteFunctionP0Vector are only defined for arithmetic data type"); - private: std::shared_ptr<const MeshVariant> m_mesh; CellArray<DataType> m_cell_arrays; @@ -188,16 +186,23 @@ class DiscreteFunctionP0Vector return product; } - PUGS_INLINE friend DiscreteFunctionP0<double> + PUGS_INLINE friend DiscreteFunctionP0<std::remove_const_t<DataType>> sumOfComponents(const DiscreteFunctionP0Vector& f) { - DiscreteFunctionP0<double> result{f.m_mesh}; + DiscreteFunctionP0<std::remove_const_t<DataType>> result{f.m_mesh}; parallel_for( f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { const auto& f_cell_id = f[cell_id]; - double sum = 0; + std::remove_const_t<DataType> sum = [] { + if constexpr (std::is_arithmetic_v<DataType>) { + return 0; + } else { + return zero; + } + }(); + for (size_t i = 0; i < f.size(); ++i) { sum += f_cell_id[i]; } @@ -213,6 +218,7 @@ class DiscreteFunctionP0Vector { Assert(f.meshVariant()->id() == g.meshVariant()->id(), "functions are nor defined on the same mesh"); Assert(f.size() == g.size()); + static_assert(std::is_arithmetic_v<std::decay_t<DataType>>); DiscreteFunctionP0<double> result{f.m_mesh}; parallel_for( f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { diff --git a/src/scheme/DiscreteFunctionVariant.hpp b/src/scheme/DiscreteFunctionVariant.hpp index 901b7989084249ca0e437f9eef24e0371dd723a8..616184298a212c10fe4865b451d0a7879a18b2ea 100644 --- a/src/scheme/DiscreteFunctionVariant.hpp +++ b/src/scheme/DiscreteFunctionVariant.hpp @@ -19,7 +19,13 @@ class DiscreteFunctionVariant DiscreteFunctionP0<const TinyMatrix<2>>, DiscreteFunctionP0<const TinyMatrix<3>>, - DiscreteFunctionP0Vector<const double>>; + DiscreteFunctionP0Vector<const double>, + DiscreteFunctionP0Vector<const TinyVector<1>>, + DiscreteFunctionP0Vector<const TinyVector<2>>, + DiscreteFunctionP0Vector<const TinyVector<3>>, + DiscreteFunctionP0Vector<const TinyMatrix<1>>, + DiscreteFunctionP0Vector<const TinyMatrix<2>>, + DiscreteFunctionP0Vector<const TinyMatrix<3>>>; Variant m_discrete_function; @@ -70,7 +76,13 @@ class DiscreteFunctionVariant DiscreteFunctionVariant(const DiscreteFunctionP0Vector<DataType>& discrete_function) : m_discrete_function{DiscreteFunctionP0Vector<const DataType>{discrete_function}} { - static_assert(std::is_same_v<std::remove_const_t<DataType>, double>, + static_assert(std::is_same_v<std::remove_const_t<DataType>, double> or // + std::is_same_v<std::remove_const_t<DataType>, TinyVector<1, double>> or // + std::is_same_v<std::remove_const_t<DataType>, TinyVector<2, double>> or // + std::is_same_v<std::remove_const_t<DataType>, TinyVector<3, double>> or // + std::is_same_v<std::remove_const_t<DataType>, TinyMatrix<1, 1, double>> or // + std::is_same_v<std::remove_const_t<DataType>, TinyMatrix<2, 2, double>> or // + std::is_same_v<std::remove_const_t<DataType>, TinyMatrix<3, 3, double>>, "DiscreteFunctionP0Vector with this DataType is not allowed in variant"); } diff --git a/src/scheme/EdgeIntegrator.hpp b/src/scheme/EdgeIntegrator.hpp index 7557a7ed851fa999e129e99f68bdd7812dbafd3d..d1a8bfec14bd76f3b63e2f25710ae40975b4d07b 100644 --- a/src/scheme/EdgeIntegrator.hpp +++ b/src/scheme/EdgeIntegrator.hpp @@ -82,9 +82,6 @@ class EdgeIntegrator 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>) { diff --git a/src/scheme/FaceIntegrator.hpp b/src/scheme/FaceIntegrator.hpp index 3fc6b25f7b1bf99419e99e22c3873083832b8a7c..4b73a50d73322688ce12d495672775a1bd8ed22d 100644 --- a/src/scheme/FaceIntegrator.hpp +++ b/src/scheme/FaceIntegrator.hpp @@ -84,9 +84,6 @@ class FaceIntegrator 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>) { diff --git a/src/scheme/FluxingAdvectionSolver.cpp b/src/scheme/FluxingAdvectionSolver.cpp index 1dec92a7dcc8e5e0c281d5d0aaa33bce9872b128..6f85610e0f1f87d2bf77ba516ddc9211f0b93fe6 100644 --- a/src/scheme/FluxingAdvectionSolver.cpp +++ b/src/scheme/FluxingAdvectionSolver.cpp @@ -23,6 +23,7 @@ #include <scheme/InflowBoundaryConditionDescriptor.hpp> #include <scheme/OutflowBoundaryConditionDescriptor.hpp> #include <scheme/SymmetryBoundaryConditionDescriptor.hpp> +#include <utils/GlobalVariableManager.hpp> #include <variant> #include <vector> @@ -85,10 +86,15 @@ class FluxingAdvectionSolver m_remapped_list.emplace_back(copy(old_q.cellValues())); } + template <typename DataType> void - _storeValues(const DiscreteFunctionP0Vector<const double>& old_q) + _storeValues(const DiscreteFunctionP0Vector<const DataType>& old_q) { - m_remapped_list.emplace_back(copy(old_q.cellArrays())); + if constexpr (std::is_arithmetic_v<DataType>) { + m_remapped_list.emplace_back(copy(old_q.cellArrays())); + } else { + throw NormalError("remapping DiscreteFunctionP0Vector of non arithmetic data type is not supported"); + } } template <typename DataType> @@ -201,6 +207,10 @@ class FluxingAdvectionSolver throw NormalError("old and new meshes must share the same connectivity"); } + if ((parallel::size() > 1) and (GlobalVariableManager::instance().getNumberOfGhostLayers() == 0)) { + throw NormalError("Fluxing advection requires 1 layer of ghost cells in parallel"); + } + this->_computeGeometricalData(); } @@ -736,8 +746,12 @@ FluxingAdvectionSolver<MeshType>::remap( new_variables.push_back(std::make_shared<DiscreteFunctionVariant>( DiscreteFunctionT(m_new_mesh, std::get<CellValue<DataType>>(m_remapped_list[i])))); } else if constexpr (is_discrete_function_P0_vector_v<DiscreteFunctionT>) { - new_variables.push_back(std::make_shared<DiscreteFunctionVariant>( - DiscreteFunctionT(m_new_mesh, std::get<CellArray<DataType>>(m_remapped_list[i])))); + if constexpr (std::is_arithmetic_v<DataType>) { + new_variables.push_back(std::make_shared<DiscreteFunctionVariant>( + DiscreteFunctionT(m_new_mesh, std::get<CellArray<DataType>>(m_remapped_list[i])))); + } else { + throw NormalError("remapping DiscreteFunctionP0Vector of non arithmetic data type is not supported"); + } } else { throw UnexpectedError("invalid discrete function type"); } diff --git a/src/scheme/IntegrationMethodType.hpp b/src/scheme/IntegrationMethodType.hpp new file mode 100644 index 0000000000000000000000000000000000000000..33264f1dce9953bb9fdfbf471ec8f428ae60a5d4 --- /dev/null +++ b/src/scheme/IntegrationMethodType.hpp @@ -0,0 +1,38 @@ +#ifndef INTEGRATION_METHOD_TYPE_HPP +#define INTEGRATION_METHOD_TYPE_HPP + +#include <utils/PugsMacros.hpp> + +#include <string> + +enum class IntegrationMethodType +{ + boundary, // use divergence theorem to compute polynomial + // integrals + cell_center, // use exact integrals for degree 1 polynomials + // using evaluation at mass center + element // use element based quadrature to compute + // polynomial integrals +}; + +std::string PUGS_INLINE +name(const IntegrationMethodType& method_type) +{ + std::string method_name; + switch (method_type) { + case IntegrationMethodType::boundary: { + method_name = "boundary"; + break; + } + case IntegrationMethodType::cell_center: { + method_name = "cell center"; + break; + } + case IntegrationMethodType::element: { + method_name = "element"; + } + } + return method_name; +} + +#endif // INTEGRATION_METHOD_TYPE_HPP diff --git a/src/scheme/PolynomialCenteredCanonicalBasisView.hpp b/src/scheme/PolynomialCenteredCanonicalBasisView.hpp new file mode 100644 index 0000000000000000000000000000000000000000..2417ec1596e42f572e0e9f624f06f27e89ee4607 --- /dev/null +++ b/src/scheme/PolynomialCenteredCanonicalBasisView.hpp @@ -0,0 +1,205 @@ +#ifndef POLYNOMIAL_CENTERED_CANONICAL_BASIS_VIEW_HPP +#define POLYNOMIAL_CENTERED_CANONICAL_BASIS_VIEW_HPP + +#include <algebra/TinyVector.hpp> +#include <utils/Array.hpp> +#include <utils/Exceptions.hpp> + +template <size_t Dimension, typename DataType> +class PolynomialCenteredCanonicalBasisView +{ + public: + class CoefficientsView + { + private: + DataType* const m_values; + const size_t m_size; + + public: + [[nodiscard]] PUGS_INLINE size_t + size() const + { + return m_size; + } + + [[nodiscard]] PUGS_INLINE DataType& + operator[](size_t i) const + { + Assert(i < m_size, "invalid index"); + return m_values[i]; + } + + CoefficientsView& operator=(const CoefficientsView&) = delete; + CoefficientsView& operator=(CoefficientsView&&) = delete; + + CoefficientsView(DataType* values, size_t size) : m_values{values}, m_size{size} + { + ; + } + + // To try to keep these views close to the initial array one + // forbids copy constructor and take benefits of C++-17 copy + // elisions. + CoefficientsView(const CoefficientsView&) = delete; + + CoefficientsView() = delete; + ~CoefficientsView() = default; + }; + + private: + static_assert((Dimension > 0) and (Dimension <= 3), "invalid dimension"); + + const size_t m_degree; + + // Coefficients are stored in the following order given the shifting + // value is omitted here for simplicity (ie: xj=0). + // + // ---------------------------------------------------- + // For a polynomial of degree n depending on x, one has + // 1, x, x^2, ..., x^n + // + // ---------------------------------------------------- + // For a polynomial of degree n depending on x and y, one has + // 1, x, x^2, ..., x^{n-1}, x^n + // y, x y, x^2 y, ..., x^{n-1} y + // ... + // y^{n-1}, x y^{n-1} + // y^n + // + // ---------------------------------------------------- + // For a polynomial of degree n depending on x, y and z, one has + // 1, x, x^2, ..., x^{n-1}, x^n + // y, x y, x^2 y, ..., x^{n-1} y + // ... + // y^{n-1}, x y^{n-1} + // y^n + // + // z, x z, x^2 z, ..., x^{n-2} z, x^{n-1} z + // y z, x y z, x^2 y z, ..., x^{n-2} y z + // ... + // y^{n-2} z, x y^{n-2} z + // y^{n -1} z + // ... + // ... + // z^{n-2}, x z^{n-2}, x^2 z^{n-2} + // y z^{n-2}, x y z^{n-2} + // y^2 z^{n-2} + // + // z^{n-1}, x z^{n-1} + // y z^{n-1} + // + // z^n + + CoefficientsView m_coefficients; + const TinyVector<Dimension>& m_xj; + + public: + static size_t + dimensionFromDegree(size_t degree) + { + if constexpr (Dimension == 1) { + return degree + 1; + } else if constexpr (Dimension == 2) { + return ((degree + 2) * (degree + 1)) / 2; + } else { // Dimension == 3 + return ((degree + 3) * (degree + 2) * (degree + 1)) / 6; + } + } + + static size_t + degreeFromDimension(size_t polynomial_basis_dimension) + { + Assert(polynomial_basis_dimension > 0); + if constexpr (Dimension == 1) { + return polynomial_basis_dimension - 1; + } else { + // No need fir an explicit formula + // - degree is small and integer + // - do not need the use of sqrt + for (size_t degree = 0; degree < polynomial_basis_dimension; ++degree) { + size_t dimension_from_degree = dimensionFromDegree(degree); + if (dimension_from_degree == polynomial_basis_dimension) { + return degree; + } else if (dimension_from_degree > polynomial_basis_dimension) { + break; + } + } + throw NormalError("incorrect polynomial basis dimension"); + } + } + + DataType + operator()(const TinyVector<Dimension>& x) const + { + if constexpr (Dimension == 1) { + const double x_xj = (x - m_xj)[0]; + + std::remove_const_t<DataType> result = m_coefficients[m_degree]; + for (ssize_t i = m_degree - 1; i >= 0; --i) { + result = x_xj * result + m_coefficients[i]; + } + + return result; + } else if constexpr (Dimension == 2) { + const TinyVector X_Xj = x - m_xj; + const double& x_xj = X_Xj[0]; + const double& y_yj = X_Xj[1]; + + size_t i = m_coefficients.size() - 1; + + std::remove_const_t<DataType> result = m_coefficients[i--]; + for (ssize_t i_y = m_degree - 1; i_y >= 0; --i_y) { + std::remove_const_t<DataType> x_result = m_coefficients[i--]; + for (ssize_t i_x = m_degree - i_y - 1; i_x >= 0; --i_x) { + x_result = x_xj * x_result + m_coefficients[i--]; + } + result = y_yj * result + x_result; + } + + return result; + } else { // Dimension == 3 + const TinyVector X_Xj = x - m_xj; + const double& x_xj = X_Xj[0]; + const double& y_yj = X_Xj[1]; + const double& z_zj = X_Xj[2]; + + size_t i = m_coefficients.size() - 1; + + std::remove_const_t<DataType> result = m_coefficients[i--]; + for (ssize_t i_z = m_degree - 1; i_z >= 0; --i_z) { + std::remove_const_t<DataType> y_result = m_coefficients[i--]; + for (ssize_t i_y = m_degree - i_z - 1; i_y >= 0; --i_y) { + std::remove_const_t<DataType> x_result = m_coefficients[i--]; + for (ssize_t i_x = m_degree - i_z - i_y - 1; i_x >= 0; --i_x) { + x_result = x_xj * x_result + m_coefficients[i--]; + } + y_result = y_yj * y_result + x_result; + } + result = z_zj * result + y_result; + } + + return result; + } + } + + template <typename ArrayType> + PolynomialCenteredCanonicalBasisView(const size_t degree, + ArrayType& coefficient_list, + const TinyVector<Dimension>& xj) + : m_degree{degree}, m_coefficients{&(coefficient_list[0]), coefficient_list.size()}, m_xj{xj} + {} + + template <typename ArrayType> + PolynomialCenteredCanonicalBasisView(const size_t degree, + const ArrayType& coefficient_list, + const TinyVector<Dimension>& xj) + : m_degree{degree}, m_coefficients{&(coefficient_list[0]), coefficient_list.size()}, m_xj{xj} + {} + + PolynomialCenteredCanonicalBasisView(const PolynomialCenteredCanonicalBasisView&) = delete; + PolynomialCenteredCanonicalBasisView(PolynomialCenteredCanonicalBasisView&&) = default; + PolynomialCenteredCanonicalBasisView() = delete; + ~PolynomialCenteredCanonicalBasisView() = default; +}; + +#endif // POLYNOMIAL_CENTERED_CANONICAL_BASIS_VIEW_HPP diff --git a/src/scheme/PolynomialReconstruction.cpp b/src/scheme/PolynomialReconstruction.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f3d3768d9dd4a880386c66c20ca69fd5847be8e2 --- /dev/null +++ b/src/scheme/PolynomialReconstruction.cpp @@ -0,0 +1,852 @@ +#include <scheme/PolynomialReconstruction.hpp> + +#include <algebra/Givens.hpp> +#include <algebra/ShrinkMatrixView.hpp> +#include <algebra/SmallMatrix.hpp> +#include <geometry/SymmetryUtils.hpp> +#include <mesh/MeshData.hpp> +#include <mesh/MeshDataManager.hpp> +#include <mesh/MeshFlatFaceBoundary.hpp> +#include <mesh/NamedBoundaryDescriptor.hpp> +#include <mesh/StencilDescriptor.hpp> +#include <mesh/StencilManager.hpp> +#include <scheme/DiscreteFunctionDPkVariant.hpp> +#include <scheme/DiscreteFunctionUtils.hpp> +#include <scheme/DiscreteFunctionVariant.hpp> +#include <scheme/reconstruction_utils/BoundaryIntegralReconstructionMatrixBuilder.hpp> +#include <scheme/reconstruction_utils/CellCenterReconstructionMatrixBuilder.hpp> +#include <scheme/reconstruction_utils/ElementIntegralReconstructionMatrixBuilder.hpp> +#include <scheme/reconstruction_utils/MutableDiscreteFunctionDPkVariant.hpp> + +template <MeshConcept MeshType> +class PolynomialReconstruction::Internal +{ + private: + using Rd = TinyVector<MeshType::Dimension>; + + friend PolynomialReconstruction; + + template <typename MatrixType> + static void + buildB(const CellId& cell_j_id, + const CellToCellStencilArray& stencil_array, + const std::vector<std::shared_ptr<const DiscreteFunctionVariant>>& discrete_function_variant_list, + SmallArray<const Rd> symmetry_normal_list, + ShrinkMatrixView<MatrixType>& B) + { + auto stencil_cell_list = stencil_array[cell_j_id]; + + size_t column_begin = 0; + for (size_t i_discrete_function_variant = 0; i_discrete_function_variant < discrete_function_variant_list.size(); + ++i_discrete_function_variant) { + const auto& discrete_function_variant = discrete_function_variant_list[i_discrete_function_variant]; + + std::visit( + [&](auto&& discrete_function) { + using DiscreteFunctionT = std::decay_t<decltype(discrete_function)>; + if constexpr (is_discrete_function_P0_v<DiscreteFunctionT>) { + using DataType = std::decay_t<typename DiscreteFunctionT::data_type>; + const DataType& qj = discrete_function[cell_j_id]; + size_t index = 0; + for (size_t i = 0; i < stencil_cell_list.size(); ++i, ++index) { + const CellId cell_i_id = stencil_cell_list[i]; + const DataType& qi_qj = discrete_function[cell_i_id] - qj; + if constexpr (std::is_arithmetic_v<DataType>) { + B(index, column_begin) = qi_qj; + } else if constexpr (is_tiny_vector_v<DataType>) { + for (size_t kB = column_begin, k = 0; k < DataType::Dimension; ++k, ++kB) { + B(index, kB) = qi_qj[k]; + } + } else if constexpr (is_tiny_matrix_v<DataType>) { + for (size_t p = 0; p < DataType::NumberOfRows; ++p) { + const size_t kB = column_begin + p * DataType::NumberOfColumns; + for (size_t q = 0; q < DataType::NumberOfColumns; ++q) { + B(index, kB + q) = qi_qj(p, q); + } + } + } + } + + for (size_t i_symmetry = 0; i_symmetry < stencil_array.symmetryBoundaryStencilArrayList().size(); + ++i_symmetry) { + auto& ghost_stencil = stencil_array.symmetryBoundaryStencilArrayList()[i_symmetry].stencilArray(); + auto ghost_cell_list = ghost_stencil[cell_j_id]; + for (size_t i = 0; i < ghost_cell_list.size(); ++i, ++index) { + const CellId cell_i_id = ghost_cell_list[i]; + if constexpr (std::is_arithmetic_v<DataType>) { + const DataType& qi_qj = discrete_function[cell_i_id] - qj; + B(index, column_begin) = qi_qj; + } else if constexpr (is_tiny_vector_v<DataType>) { + if constexpr (DataType::Dimension == MeshType::Dimension) { + const Rd& normal = symmetry_normal_list[i_symmetry]; + + const DataType& qi = discrete_function[cell_i_id]; + const DataType& qi_qj = symmetrize_vector(normal, qi) - qj; + for (size_t kB = column_begin, k = 0; k < DataType::Dimension; ++k, ++kB) { + B(index, kB) = qi_qj[k]; + } + } else { + // LCOV_EXCL_START + std::stringstream error_msg; + error_msg << "cannot symmetrize vectors of dimension " << DataType::Dimension + << " using a mesh of dimension " << MeshType::Dimension; + throw UnexpectedError(error_msg.str()); + // LCOV_EXCL_STOP + } + } else if constexpr (is_tiny_matrix_v<DataType>) { + if constexpr ((DataType::NumberOfColumns == DataType::NumberOfRows) and + (DataType::NumberOfColumns == MeshType::Dimension)) { + const Rd& normal = symmetry_normal_list[i_symmetry]; + + const DataType& qi = discrete_function[cell_i_id]; + const DataType& qi_qj = symmetrize_matrix(normal, qi) - qj; + for (size_t p = 0; p < DataType::NumberOfRows; ++p) { + for (size_t q = 0; q < DataType::NumberOfColumns; ++q) { + B(index, column_begin + p * DataType::NumberOfColumns + q) = qi_qj(p, q); + } + } + } else { + // LCOV_EXCL_START + std::stringstream error_msg; + error_msg << "cannot symmetrize matrices of dimensions " << DataType::NumberOfRows << 'x' + << DataType::NumberOfColumns << " using a mesh of dimension " << MeshType::Dimension; + throw UnexpectedError(error_msg.str()); + // LCOV_EXCL_STOP + } + } + } + } + + if constexpr (std::is_arithmetic_v<DataType>) { + ++column_begin; + } else if constexpr (is_tiny_vector_v<DataType> or is_tiny_matrix_v<DataType>) { + column_begin += DataType::Dimension; + } + } else if constexpr (is_discrete_function_P0_vector_v<DiscreteFunctionT>) { + using DataType = std::decay_t<typename DiscreteFunctionT::data_type>; + + const auto qj_vector = discrete_function[cell_j_id]; + + if constexpr (std::is_arithmetic_v<DataType>) { + size_t index = 0; + for (size_t i = 0; i < stencil_cell_list.size(); ++i, ++index) { + const CellId cell_i_id = stencil_cell_list[i]; + for (size_t l = 0; l < qj_vector.size(); ++l) { + const DataType& qj = qj_vector[l]; + const DataType& qi_qj = discrete_function[cell_i_id][l] - qj; + B(index, column_begin + l) = qi_qj; + } + } + + for (size_t i_symmetry = 0; i_symmetry < stencil_array.symmetryBoundaryStencilArrayList().size(); + ++i_symmetry) { + auto& ghost_stencil = stencil_array.symmetryBoundaryStencilArrayList()[i_symmetry].stencilArray(); + auto ghost_cell_list = ghost_stencil[cell_j_id]; + for (size_t i = 0; i < ghost_cell_list.size(); ++i, ++index) { + const CellId cell_i_id = ghost_cell_list[i]; + for (size_t l = 0; l < qj_vector.size(); ++l) { + const DataType& qj = qj_vector[l]; + const DataType& qi_qj = discrete_function[cell_i_id][l] - qj; + B(index, column_begin + l) = qi_qj; + } + } + } + } else if constexpr (is_tiny_vector_v<DataType>) { + size_t index = 0; + for (size_t i = 0; i < stencil_cell_list.size(); ++i, ++index) { + const CellId cell_i_id = stencil_cell_list[i]; + for (size_t l = 0; l < qj_vector.size(); ++l) { + const DataType& qj = qj_vector[l]; + const DataType& qi_qj = discrete_function[cell_i_id][l] - qj; + for (size_t kB = column_begin + l * DataType::Dimension, k = 0; k < DataType::Dimension; ++k, ++kB) { + B(index, kB) = qi_qj[k]; + } + } + } + + for (size_t i_symmetry = 0; i_symmetry < stencil_array.symmetryBoundaryStencilArrayList().size(); + ++i_symmetry) { + if constexpr (DataType::Dimension == MeshType::Dimension) { + auto& ghost_stencil = stencil_array.symmetryBoundaryStencilArrayList()[i_symmetry].stencilArray(); + auto ghost_cell_list = ghost_stencil[cell_j_id]; + + const Rd& normal = symmetry_normal_list[i_symmetry]; + + for (size_t i = 0; i < ghost_cell_list.size(); ++i, ++index) { + const CellId cell_i_id = ghost_cell_list[i]; + + for (size_t l = 0; l < qj_vector.size(); ++l) { + const DataType& qj = qj_vector[l]; + const DataType& qi = discrete_function[cell_i_id][l]; + const DataType& qi_qj = symmetrize_vector(normal, qi) - qj; + for (size_t kB = column_begin + l * DataType::Dimension, k = 0; k < DataType::Dimension; + ++k, ++kB) { + B(index, kB) = qi_qj[k]; + } + } + } + } else { + // LCOV_EXCL_START + std::stringstream error_msg; + error_msg << "cannot symmetrize vectors of dimension " << DataType::Dimension + << " using a mesh of dimension " << MeshType::Dimension; + throw UnexpectedError(error_msg.str()); + // LCOV_EXCL_STOP + } + } + } else if constexpr (is_tiny_matrix_v<DataType>) { + size_t index = 0; + for (size_t i = 0; i < stencil_cell_list.size(); ++i, ++index) { + const CellId cell_i_id = stencil_cell_list[i]; + for (size_t l = 0; l < qj_vector.size(); ++l) { + const DataType& qj = qj_vector[l]; + const DataType& qi = discrete_function[cell_i_id][l]; + const DataType& qi_qj = qi - qj; + + for (size_t p = 0; p < DataType::NumberOfRows; ++p) { + const size_t kB = column_begin + l * DataType::Dimension + p * DataType::NumberOfColumns; + for (size_t q = 0; q < DataType::NumberOfColumns; ++q) { + B(index, kB + q) = qi_qj(p, q); + } + } + } + } + + for (size_t i_symmetry = 0; i_symmetry < stencil_array.symmetryBoundaryStencilArrayList().size(); + ++i_symmetry) { + if constexpr ((DataType::NumberOfRows == MeshType::Dimension) and + (DataType::NumberOfColumns == MeshType::Dimension)) { + auto& ghost_stencil = stencil_array.symmetryBoundaryStencilArrayList()[i_symmetry].stencilArray(); + auto ghost_cell_list = ghost_stencil[cell_j_id]; + + const Rd& normal = symmetry_normal_list[i_symmetry]; + + for (size_t i = 0; i < ghost_cell_list.size(); ++i, ++index) { + const CellId cell_i_id = ghost_cell_list[i]; + + for (size_t l = 0; l < qj_vector.size(); ++l) { + const DataType& qj = qj_vector[l]; + const DataType& qi = discrete_function[cell_i_id][l]; + const DataType& qi_qj = symmetrize_matrix(normal, qi) - qj; + + for (size_t p = 0; p < DataType::NumberOfRows; ++p) { + const size_t kB = column_begin + l * DataType::Dimension + p * DataType::NumberOfColumns; + for (size_t q = 0; q < DataType::NumberOfColumns; ++q) { + B(index, kB + q) = qi_qj(p, q); + } + } + } + } + } else { + // LCOV_EXCL_START + std::stringstream error_msg; + error_msg << "cannot symmetrize vectors of dimension " << DataType::Dimension + << " using a mesh of dimension " << MeshType::Dimension; + throw UnexpectedError(error_msg.str()); + // LCOV_EXCL_STOP + } + } + } + + if constexpr (std::is_arithmetic_v<DataType>) { + column_begin += qj_vector.size(); + } else if constexpr (is_tiny_vector_v<DataType> or is_tiny_matrix_v<DataType>) { + column_begin += qj_vector.size() * DataType::Dimension; + } + + } else { + // LCOV_EXCL_START + throw UnexpectedError("invalid discrete function type"); + // LCOV_EXCL_STOP + } + }, + discrete_function_variant->discreteFunction()); + } + } + + template <typename MatrixType> + static void + rowWeighting(const CellId& cell_j_id, + const CellToCellStencilArray& stencil_array, + const CellValue<const Rd>& xj, + const SmallArray<const Rd>& symmetry_origin_list, + const SmallArray<const Rd>& symmetry_normal_list, + ShrinkMatrixView<MatrixType>& A, + ShrinkMatrixView<MatrixType>& B) + { + // Add row weighting (give more importance to cells that are + // closer to j) + auto stencil_cell_list = stencil_array[cell_j_id]; + + const Rd& Xj = xj[cell_j_id]; + + size_t index = 0; + for (size_t i = 0; i < stencil_cell_list.size(); ++i, ++index) { + const CellId cell_i_id = stencil_cell_list[i]; + const double weight = 1. / l2Norm(xj[cell_i_id] - Xj); + for (size_t l = 0; l < A.numberOfColumns(); ++l) { + A(index, l) *= weight; + } + for (size_t l = 0; l < B.numberOfColumns(); ++l) { + B(index, l) *= weight; + } + } + for (size_t i_symmetry = 0; i_symmetry < stencil_array.symmetryBoundaryStencilArrayList().size(); ++i_symmetry) { + auto& ghost_stencil = stencil_array.symmetryBoundaryStencilArrayList()[i_symmetry].stencilArray(); + auto ghost_cell_list = ghost_stencil[cell_j_id]; + + const Rd& origin = symmetry_origin_list[i_symmetry]; + const Rd& normal = symmetry_normal_list[i_symmetry]; + + for (size_t i = 0; i < ghost_cell_list.size(); ++i, ++index) { + const CellId cell_i_id = ghost_cell_list[i]; + const double weight = 1. / l2Norm(symmetrize_coordinates(origin, normal, xj[cell_i_id]) - Xj); + for (size_t l = 0; l < A.numberOfColumns(); ++l) { + A(index, l) *= weight; + } + for (size_t l = 0; l < B.numberOfColumns(); ++l) { + B(index, l) *= weight; + } + } + } + } + + template <typename MatrixType> + static void + solveCollectionInPlaceWithPreconditionner(const ShrinkMatrixView<MatrixType>& A, + const SmallMatrix<double>& X, + const ShrinkMatrixView<MatrixType>& B, + const SmallVector<double>& G) + { + for (size_t l = 0; l < A.numberOfColumns(); ++l) { + double g = 0; + for (size_t i = 0; i < A.numberOfRows(); ++i) { + const double Ail = A(i, l); + + g += Ail * Ail; + } + G[l] = std::sqrt(g); + } + + for (size_t l = 0; l < A.numberOfColumns(); ++l) { + const double Gl = G[l]; + for (size_t i = 0; i < A.numberOfRows(); ++i) { + A(i, l) *= Gl; + } + } + + Givens::solveCollectionInPlace(A, X, B); + + for (size_t l = 0; l < X.numberOfRows(); ++l) { + const double Gl = G[l]; + for (size_t i = 0; i < X.numberOfColumns(); ++i) { + X(l, i) *= Gl; + } + } + } + + template <typename ReconstructionMatrixBuilderType> + static void + populateDiscreteFunctionDPkByCell( + const CellId& cell_j_id, + const size_t& degree, + const SmallMatrix<double>& X, + const ReconstructionMatrixBuilderType& reconstruction_matrix_builder, + const std::vector<std::shared_ptr<const DiscreteFunctionVariant>>& discrete_function_variant_list, + const std::vector<PolynomialReconstruction::MutableDiscreteFunctionDPkVariant>& + mutable_discrete_function_dpk_variant_list) + { + size_t column_begin = 0; + for (size_t i_dpk_variant = 0; i_dpk_variant < mutable_discrete_function_dpk_variant_list.size(); ++i_dpk_variant) { + const auto& dpk_variant = mutable_discrete_function_dpk_variant_list[i_dpk_variant]; + + const auto& discrete_function_variant = discrete_function_variant_list[i_dpk_variant]; + + std::visit( + [&](auto&& dpk_function, auto&& p0_function) { + using DPkFunctionT = std::decay_t<decltype(dpk_function)>; + using P0FunctionT = std::decay_t<decltype(p0_function)>; + using DataType = std::remove_const_t<std::decay_t<typename DPkFunctionT::data_type>>; + using P0DataType = std::remove_const_t<std::decay_t<typename P0FunctionT::data_type>>; + + if constexpr (std::is_same_v<DataType, P0DataType>) { + if constexpr (is_discrete_function_P0_v<P0FunctionT>) { + if constexpr (is_discrete_function_dpk_scalar_v<DPkFunctionT>) { + auto dpk_j = dpk_function.coefficients(cell_j_id); + dpk_j[0] = p0_function[cell_j_id]; + + if constexpr (std::is_arithmetic_v<DataType>) { + if constexpr (ReconstructionMatrixBuilderType::handles_high_degrees) { + if (degree > 1) { + const auto& mean_j_of_ejk = reconstruction_matrix_builder.meanjOfEjk(); + for (size_t i = 0; i < X.numberOfRows(); ++i) { + dpk_j[0] -= X(i, column_begin) * mean_j_of_ejk[i]; + } + } + } + + for (size_t i = 0; i < X.numberOfRows(); ++i) { + auto& dpk_j_ip1 = dpk_j[i + 1]; + dpk_j_ip1 = X(i, column_begin); + } + ++column_begin; + } else if constexpr (is_tiny_vector_v<DataType>) { + if constexpr (ReconstructionMatrixBuilderType::handles_high_degrees) { + if (degree > 1) { + const auto& mean_j_of_ejk = reconstruction_matrix_builder.meanjOfEjk(); + for (size_t i = 0; i < X.numberOfRows(); ++i) { + auto& dpk_j_0 = dpk_j[0]; + for (size_t k = 0; k < DataType::Dimension; ++k) { + dpk_j_0[k] -= X(i, column_begin + k) * mean_j_of_ejk[i]; + } + } + } + } + + for (size_t i = 0; i < X.numberOfRows(); ++i) { + auto& dpk_j_ip1 = dpk_j[i + 1]; + for (size_t k = 0; k < DataType::Dimension; ++k) { + dpk_j_ip1[k] = X(i, column_begin + k); + } + } + column_begin += DataType::Dimension; + } else if constexpr (is_tiny_matrix_v<DataType>) { + if constexpr (ReconstructionMatrixBuilderType::handles_high_degrees) { + if (degree > 1) { + const auto& mean_j_of_ejk = reconstruction_matrix_builder.meanjOfEjk(); + for (size_t i = 0; i < X.numberOfRows(); ++i) { + auto& dpk_j_0 = dpk_j[0]; + for (size_t k = 0; k < DataType::NumberOfRows; ++k) { + for (size_t l = 0; l < DataType::NumberOfColumns; ++l) { + dpk_j_0(k, l) -= X(i, column_begin + k * DataType::NumberOfColumns + l) * mean_j_of_ejk[i]; + } + } + } + } + } + + for (size_t i = 0; i < X.numberOfRows(); ++i) { + auto& dpk_j_ip1 = dpk_j[i + 1]; + for (size_t k = 0; k < DataType::NumberOfRows; ++k) { + for (size_t l = 0; l < DataType::NumberOfColumns; ++l) { + dpk_j_ip1(k, l) = X(i, column_begin + k * DataType::NumberOfColumns + l); + } + } + } + column_begin += DataType::Dimension; + } else { + // LCOV_EXCL_START + throw UnexpectedError("unexpected data type"); + // LCOV_EXCL_STOP + } + } else { + // LCOV_EXCL_START + throw UnexpectedError("unexpected discrete dpk function type"); + // LCOV_EXCL_STOP + } + } else if constexpr (is_discrete_function_P0_vector_v<P0FunctionT>) { + if constexpr (is_discrete_function_dpk_vector_v<DPkFunctionT>) { + auto dpk_j = dpk_function.coefficients(cell_j_id); + auto cell_vector = p0_function[cell_j_id]; + const size_t size = X.numberOfRows() + 1; + + for (size_t l = 0; l < cell_vector.size(); ++l) { + const size_t component_begin = l * size; + dpk_j[component_begin] = cell_vector[l]; + if constexpr (std::is_arithmetic_v<DataType>) { + if constexpr (ReconstructionMatrixBuilderType::handles_high_degrees) { + const auto& mean_j_of_ejk = reconstruction_matrix_builder.meanjOfEjk(); + if (degree > 1) { + for (size_t i = 0; i < X.numberOfRows(); ++i) { + dpk_j[component_begin] -= X(i, column_begin) * mean_j_of_ejk[i]; + } + } + } + + for (size_t i = 0; i < X.numberOfRows(); ++i) { + auto& dpk_j_ip1 = dpk_j[component_begin + i + 1]; + dpk_j_ip1 = X(i, column_begin); + } + ++column_begin; + } else if constexpr (is_tiny_vector_v<DataType>) { + if constexpr (ReconstructionMatrixBuilderType::handles_high_degrees) { + if (degree > 1) { + const auto& mean_j_of_ejk = reconstruction_matrix_builder.meanjOfEjk(); + for (size_t i = 0; i < X.numberOfRows(); ++i) { + auto& dpk_j_0 = dpk_j[component_begin]; + for (size_t k = 0; k < DataType::Dimension; ++k) { + dpk_j_0[k] -= X(i, column_begin + k) * mean_j_of_ejk[i]; + } + } + } + } + + for (size_t i = 0; i < X.numberOfRows(); ++i) { + auto& dpk_j_ip1 = dpk_j[component_begin + i + 1]; + for (size_t k = 0; k < DataType::Dimension; ++k) { + dpk_j_ip1[k] = X(i, column_begin + k); + } + } + column_begin += DataType::Dimension; + } else if constexpr (is_tiny_matrix_v<DataType>) { + if constexpr (ReconstructionMatrixBuilderType::handles_high_degrees) { + if (degree > 1) { + const auto& mean_j_of_ejk = reconstruction_matrix_builder.meanjOfEjk(); + for (size_t i = 0; i < X.numberOfRows(); ++i) { + auto& dpk_j_0 = dpk_j[component_begin]; + for (size_t p = 0; p < DataType::NumberOfRows; ++p) { + for (size_t q = 0; q < DataType::NumberOfColumns; ++q) { + dpk_j_0(p, q) -= + X(i, column_begin + p * DataType::NumberOfColumns + q) * mean_j_of_ejk[i]; + } + } + } + } + } + + for (size_t i = 0; i < X.numberOfRows(); ++i) { + auto& dpk_j_ip1 = dpk_j[component_begin + i + 1]; + for (size_t p = 0; p < DataType::NumberOfRows; ++p) { + for (size_t q = 0; q < DataType::NumberOfColumns; ++q) { + dpk_j_ip1(p, q) = X(i, column_begin + p * DataType::NumberOfColumns + q); + } + } + } + column_begin += DataType::Dimension; + } else { + // LCOV_EXCL_START + throw UnexpectedError("unexpected data type"); + // LCOV_EXCL_STOP + } + } + } else { + // LCOV_EXCL_START + throw UnexpectedError("unexpected discrete dpk function type"); + // LCOV_EXCL_STOP + } + } else { + // LCOV_EXCL_START + throw UnexpectedError("unexpected discrete function type"); + // LCOV_EXCL_STOP + } + } else { + // LCOV_EXCL_START + throw UnexpectedError("incompatible data types"); + // LCOV_EXCL_STOP + } + }, + dpk_variant.mutableDiscreteFunctionDPk(), discrete_function_variant->discreteFunction()); + } + } +}; + +size_t +PolynomialReconstruction::_getNumberOfColumns( + const std::vector<std::shared_ptr<const DiscreteFunctionVariant>>& discrete_function_variant_list) const +{ + size_t number_of_columns = 0; + for (auto discrete_function_variant_p : discrete_function_variant_list) { + number_of_columns += std::visit( + [](auto&& discrete_function) -> size_t { + using DiscreteFunctionT = std::decay_t<decltype(discrete_function)>; + if constexpr (is_discrete_function_P0_v<DiscreteFunctionT>) { + using data_type = std::decay_t<typename DiscreteFunctionT::data_type>; + if constexpr (std::is_arithmetic_v<data_type>) { + return 1; + } else if constexpr (is_tiny_vector_v<data_type> or is_tiny_matrix_v<data_type>) { + return data_type::Dimension; + } else { + // LCOV_EXCL_START + throw UnexpectedError("unexpected data type " + demangle<data_type>()); + // LCOV_EXCL_STOP + } + } else if constexpr (is_discrete_function_P0_vector_v<DiscreteFunctionT>) { + using data_type = std::decay_t<typename DiscreteFunctionT::data_type>; + if constexpr (std::is_arithmetic_v<data_type>) { + return discrete_function.size(); + } else if constexpr (is_tiny_vector_v<data_type> or is_tiny_matrix_v<data_type>) { + return discrete_function.size() * data_type::Dimension; + } else { + // LCOV_EXCL_START + throw UnexpectedError("unexpected data type " + demangle<data_type>()); + // LCOV_EXCL_STOP + } + } else { + // LCOV_EXCL_START + throw UnexpectedError("unexpected discrete function type"); + // LCOV_EXCL_STOP + } + }, + discrete_function_variant_p->discreteFunction()); + } + return number_of_columns; +} + +template <MeshConcept MeshType> +std::vector<PolynomialReconstruction::MutableDiscreteFunctionDPkVariant> +PolynomialReconstruction::_createMutableDiscreteFunctionDPKVariantList( + const std::shared_ptr<const MeshType>& p_mesh, + const std::vector<std::shared_ptr<const DiscreteFunctionVariant>>& discrete_function_variant_list) const +{ + std::vector<MutableDiscreteFunctionDPkVariant> mutable_discrete_function_dpk_variant_list; + for (size_t i_discrete_function_variant = 0; i_discrete_function_variant < discrete_function_variant_list.size(); + ++i_discrete_function_variant) { + auto discrete_function_variant = discrete_function_variant_list[i_discrete_function_variant]; + + std::visit( + [&](auto&& discrete_function) { + using DiscreteFunctionT = std::decay_t<decltype(discrete_function)>; + if constexpr (is_discrete_function_P0_v<DiscreteFunctionT>) { + using DataType = std::remove_const_t<std::decay_t<typename DiscreteFunctionT::data_type>>; + mutable_discrete_function_dpk_variant_list.push_back( + DiscreteFunctionDPk<MeshType::Dimension, DataType>(p_mesh, m_descriptor.degree())); + } else if constexpr (is_discrete_function_P0_vector_v<DiscreteFunctionT>) { + using DataType = std::remove_const_t<std::decay_t<typename DiscreteFunctionT::data_type>>; + mutable_discrete_function_dpk_variant_list.push_back( + DiscreteFunctionDPkVector<MeshType::Dimension, DataType>(p_mesh, m_descriptor.degree(), + discrete_function.size())); + } else { + // LCOV_EXCL_START + throw UnexpectedError("unexpected discrete function type"); + // LCOV_EXCL_STOP + } + }, + discrete_function_variant->discreteFunction()); + } + + return mutable_discrete_function_dpk_variant_list; +} + +template <MeshConcept MeshType> +void +PolynomialReconstruction::_checkDataAndSymmetriesCompatibility( + const std::vector<std::shared_ptr<const DiscreteFunctionVariant>>& discrete_function_variant_list) const +{ + for (auto&& discrete_function_variant : discrete_function_variant_list) { + std::visit( + [&](auto&& discrete_function) { + using DiscreteFunctionT = std::decay_t<decltype(discrete_function)>; + if constexpr (is_discrete_function_P0_v<DiscreteFunctionT> or + is_discrete_function_P0_vector_v<DiscreteFunctionT>) { + using DataType = std::decay_t<typename DiscreteFunctionT::data_type>; + if constexpr (is_tiny_vector_v<DataType>) { + if constexpr (DataType::Dimension != MeshType::Dimension) { + std::stringstream error_msg; + error_msg << "cannot symmetrize vectors of dimension " << DataType::Dimension + << " using a mesh of dimension " << MeshType::Dimension; + throw NormalError(error_msg.str()); + } + } else if constexpr (is_tiny_matrix_v<DataType>) { + if constexpr ((DataType::NumberOfRows != MeshType::Dimension) or + (DataType::NumberOfColumns != MeshType::Dimension)) { + std::stringstream error_msg; + error_msg << "cannot symmetrize matrices of dimensions " << DataType::NumberOfRows << 'x' + << DataType::NumberOfColumns << " using a mesh of dimension " << MeshType::Dimension; + throw NormalError(error_msg.str()); + } + } + } else { + // LCOV_EXCL_START + throw UnexpectedError("invalid discrete function type"); + // LCOV_EXCL_STOP + } + }, + discrete_function_variant->discreteFunction()); + } +} + +template <typename ReconstructionMatrixBuilderType, MeshConcept MeshType> +[[nodiscard]] std::vector<std::shared_ptr<const DiscreteFunctionDPkVariant>> +PolynomialReconstruction::_build( + const std::shared_ptr<const MeshType>& p_mesh, + const std::vector<std::shared_ptr<const DiscreteFunctionVariant>>& discrete_function_variant_list) const +{ + static_assert(std::is_same_v<MeshType, typename ReconstructionMatrixBuilderType::MeshType>); + + const MeshType& mesh = *p_mesh; + + using Rd = TinyVector<MeshType::Dimension>; + + if (m_descriptor.symmetryBoundaryDescriptorList().size() > 0) { + this->_checkDataAndSymmetriesCompatibility<MeshType>(discrete_function_variant_list); + } + + const size_t number_of_columns = this->_getNumberOfColumns(discrete_function_variant_list); + + const size_t basis_dimension = + DiscreteFunctionDPk<MeshType::Dimension, double>::BasisViewType::dimensionFromDegree(m_descriptor.degree()); + + const auto& stencil_array = + StencilManager::instance().getCellToCellStencilArray(mesh.connectivity(), m_descriptor.stencilDescriptor(), + m_descriptor.symmetryBoundaryDescriptorList()); + + auto xr = mesh.xr(); + auto xj = MeshDataManager::instance().getMeshData(mesh).xj(); + auto Vj = MeshDataManager::instance().getMeshData(mesh).Vj(); + + auto cell_is_owned = mesh.connectivity().cellIsOwned(); + auto cell_type = mesh.connectivity().cellType(); + + auto full_stencil_size = [&](const CellId cell_id) { + auto stencil_cell_list = stencil_array[cell_id]; + size_t stencil_size = stencil_cell_list.size(); + for (size_t i = 0; i < m_descriptor.symmetryBoundaryDescriptorList().size(); ++i) { + auto& ghost_stencil = stencil_array.symmetryBoundaryStencilArrayList()[i].stencilArray(); + stencil_size += ghost_stencil[cell_id].size(); + } + + return stencil_size; + }; + + const size_t max_stencil_size = [&]() { + size_t max_size = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const size_t stencil_size = full_stencil_size(cell_id); + if (cell_is_owned[cell_id] and stencil_size > max_size) { + max_size = stencil_size; + } + } + return max_size; + }(); + + SmallArray<const Rd> symmetry_normal_list = [&] { + SmallArray<Rd> normal_list(m_descriptor.symmetryBoundaryDescriptorList().size()); + size_t i_symmetry_boundary = 0; + for (auto p_boundary_descriptor : m_descriptor.symmetryBoundaryDescriptorList()) { + const IBoundaryDescriptor& boundary_descriptor = *p_boundary_descriptor; + + auto symmetry_boundary = getMeshFlatFaceBoundary(mesh, boundary_descriptor); + normal_list[i_symmetry_boundary++] = symmetry_boundary.outgoingNormal(); + } + return normal_list; + }(); + + SmallArray<const Rd> symmetry_origin_list = [&] { + SmallArray<Rd> origin_list(m_descriptor.symmetryBoundaryDescriptorList().size()); + size_t i_symmetry_boundary = 0; + for (auto p_boundary_descriptor : m_descriptor.symmetryBoundaryDescriptorList()) { + const IBoundaryDescriptor& boundary_descriptor = *p_boundary_descriptor; + + auto symmetry_boundary = getMeshFlatFaceBoundary(mesh, boundary_descriptor); + origin_list[i_symmetry_boundary++] = symmetry_boundary.origin(); + } + return origin_list; + }(); + + Kokkos::Experimental::UniqueToken<Kokkos::DefaultExecutionSpace::execution_space, + Kokkos::Experimental::UniqueTokenScope::Global> + tokens; + + auto mutable_discrete_function_dpk_variant_list = + this->_createMutableDiscreteFunctionDPKVariantList(p_mesh, discrete_function_variant_list); + + SmallArray<SmallMatrix<double>> A_pool(Kokkos::DefaultExecutionSpace::concurrency()); + SmallArray<SmallMatrix<double>> B_pool(Kokkos::DefaultExecutionSpace::concurrency()); + SmallArray<SmallVector<double>> G_pool(Kokkos::DefaultExecutionSpace::concurrency()); + SmallArray<SmallMatrix<double>> X_pool(Kokkos::DefaultExecutionSpace::concurrency()); + + for (size_t i = 0; i < A_pool.size(); ++i) { + A_pool[i] = SmallMatrix<double>(max_stencil_size, basis_dimension - 1); + B_pool[i] = SmallMatrix<double>(max_stencil_size, number_of_columns); + G_pool[i] = SmallVector<double>(basis_dimension - 1); + X_pool[i] = SmallMatrix<double>(basis_dimension - 1, number_of_columns); + } + + SmallArray<std::shared_ptr<ReconstructionMatrixBuilderType>> reconstruction_matrix_builder_pool(A_pool.size()); + + for (size_t t = 0; t < reconstruction_matrix_builder_pool.size(); ++t) { + reconstruction_matrix_builder_pool[t] = + std::make_shared<ReconstructionMatrixBuilderType>(*p_mesh, m_descriptor.degree(), symmetry_origin_list, + symmetry_normal_list, stencil_array); + } + + parallel_for( + mesh.numberOfCells(), PUGS_CLASS_LAMBDA(const CellId cell_j_id) { + if (cell_is_owned[cell_j_id]) { + const int32_t t = tokens.acquire(); + + ShrinkMatrixView A(A_pool[t], full_stencil_size(cell_j_id)); + ShrinkMatrixView B(B_pool[t], full_stencil_size(cell_j_id)); + + Internal<MeshType>::buildB(cell_j_id, stencil_array, discrete_function_variant_list, symmetry_normal_list, B); + + ReconstructionMatrixBuilderType& reconstruction_matrix_builder = *reconstruction_matrix_builder_pool[t]; + reconstruction_matrix_builder.build(cell_j_id, A); + + if (m_descriptor.rowWeighting()) { + Internal<MeshType>::rowWeighting(cell_j_id, stencil_array, xj, symmetry_origin_list, symmetry_normal_list, A, + B); + } + + const SmallMatrix<double>& X = X_pool[t]; + + if (m_descriptor.preconditioning()) { + // Add column weighting preconditioning (increase the precision) + SmallVector<double>& G = G_pool[t]; + + Internal<MeshType>::solveCollectionInPlaceWithPreconditionner(A, X, B, G); + } else { + Givens::solveCollectionInPlace(A, X, B); + } + + Internal<MeshType>::template populateDiscreteFunctionDPkByCell(cell_j_id, m_descriptor.degree(), X, + reconstruction_matrix_builder, + discrete_function_variant_list, + mutable_discrete_function_dpk_variant_list); + + tokens.release(t); + } + }); + + std::vector<std::shared_ptr<const DiscreteFunctionDPkVariant>> discrete_function_dpk_variant_list; + + for (auto discrete_function_dpk_variant_p : mutable_discrete_function_dpk_variant_list) { + std::visit( + [&](auto&& mutable_function_dpk) { + synchronize(mutable_function_dpk.cellArrays()); + discrete_function_dpk_variant_list.push_back( + std::make_shared<DiscreteFunctionDPkVariant>(mutable_function_dpk)); + }, + discrete_function_dpk_variant_p.mutableDiscreteFunctionDPk()); + } + + return discrete_function_dpk_variant_list; +} + +template <MeshConcept MeshType> +[[nodiscard]] std::vector<std::shared_ptr<const DiscreteFunctionDPkVariant>> +PolynomialReconstruction::_build( + const std::shared_ptr<const MeshType>& p_mesh, + const std::vector<std::shared_ptr<const DiscreteFunctionVariant>>& discrete_function_variant_list) const +{ + switch (m_descriptor.integrationMethodType()) { + case IntegrationMethodType::cell_center: { + return this->_build<CellCenterReconstructionMatrixBuilder<MeshType>>(p_mesh, discrete_function_variant_list); + } + case IntegrationMethodType::boundary: { + return this->_build<BoundaryIntegralReconstructionMatrixBuilder<MeshType>>(p_mesh, discrete_function_variant_list); + } + case IntegrationMethodType::element: { + return this->_build<ElementIntegralReconstructionMatrixBuilder<MeshType>>(p_mesh, discrete_function_variant_list); + } + // LCOV_EXCL_START + default: { + throw UnexpectedError("invalid reconstruction matrix builder type"); + } + // LCOV_EXCL_STOP + } +} + +std::vector<std::shared_ptr<const DiscreteFunctionDPkVariant>> +PolynomialReconstruction::build( + const std::vector<std::shared_ptr<const DiscreteFunctionVariant>>& discrete_function_variant_list) const +{ + if (not hasSameMesh(discrete_function_variant_list)) { + throw NormalError("cannot reconstruct functions living of different meshes simultaneously"); + } + + auto mesh_v = getCommonMesh(discrete_function_variant_list); + + return std::visit([&](auto&& p_mesh) { return this->_build(p_mesh, discrete_function_variant_list); }, + mesh_v->variant()); +} diff --git a/src/scheme/PolynomialReconstruction.hpp b/src/scheme/PolynomialReconstruction.hpp new file mode 100644 index 0000000000000000000000000000000000000000..4f76844711ada4641b69234c5ac787c472247a37 --- /dev/null +++ b/src/scheme/PolynomialReconstruction.hpp @@ -0,0 +1,84 @@ +#ifndef POLYNOMIAL_RECONSTRUCTION_HPP +#define POLYNOMIAL_RECONSTRUCTION_HPP + +#include <mesh/MeshTraits.hpp> +#include <scheme/PolynomialReconstructionDescriptor.hpp> + +class DiscreteFunctionDPkVariant; +class DiscreteFunctionVariant; + +class PolynomialReconstruction +{ + private: + template <MeshConcept MeshType> + class Internal; + + class MutableDiscreteFunctionDPkVariant; + + template <MeshConcept MeshType> + class CellCenterReconstructionMatrixBuilder; + + template <MeshConcept MeshType> + class ElementIntegralReconstructionMatrixBuilder; + + template <MeshConcept MeshType> + class BoundaryIntegralReconstructionMatrixBuilder; + + const PolynomialReconstructionDescriptor m_descriptor; + + size_t _getNumberOfColumns( + const std::vector<std::shared_ptr<const DiscreteFunctionVariant>>& discrete_function_variant_list) const; + + template <MeshConcept MeshType> + void _checkDataAndSymmetriesCompatibility( + const std::vector<std::shared_ptr<const DiscreteFunctionVariant>>& discrete_function_variant_list) const; + + template <MeshConcept MeshType> + std::vector<MutableDiscreteFunctionDPkVariant> _createMutableDiscreteFunctionDPKVariantList( + const std::shared_ptr<const MeshType>& p_mesh, + const std::vector<std::shared_ptr<const DiscreteFunctionVariant>>& discrete_function_variant_list) const; + + template <typename ReconstructionMatrixBuilderType, MeshConcept MeshType> + [[nodiscard]] std::vector<std::shared_ptr<const DiscreteFunctionDPkVariant>> _build( + const std::shared_ptr<const MeshType>& p_mesh, + const std::vector<std::shared_ptr<const DiscreteFunctionVariant>>& discrete_function_variant_list) const; + + template <MeshConcept MeshType> + [[nodiscard]] std::vector<std::shared_ptr<const DiscreteFunctionDPkVariant>> _build( + const std::shared_ptr<const MeshType>& p_mesh, + const std::vector<std::shared_ptr<const DiscreteFunctionVariant>>& discrete_function_variant_list) const; + + public: + [[nodiscard]] std::vector<std::shared_ptr<const DiscreteFunctionDPkVariant>> build( + const std::vector<std::shared_ptr<const DiscreteFunctionVariant>>& discrete_function_variant_list) const; + + template <typename... DiscreteFunctionT> + [[nodiscard]] std::vector<std::shared_ptr<const DiscreteFunctionDPkVariant>> + build(DiscreteFunctionT... input) const + { + std::vector<std::shared_ptr<const DiscreteFunctionVariant>> variant_vector; + auto convert = [&variant_vector](auto&& df) { + using DF_T = std::decay_t<decltype(df)>; + if constexpr (is_discrete_function_v<DF_T> or std::is_same_v<DiscreteFunctionVariant, DF_T>) { + variant_vector.push_back(std::make_shared<DiscreteFunctionVariant>(df)); + } else if constexpr (is_shared_ptr_v<DF_T>) { + using DF_Value_T = std::decay_t<typename DF_T::element_type>; + if constexpr (is_discrete_function_v<DF_Value_T> or std::is_same_v<DiscreteFunctionVariant, DF_Value_T>) { + variant_vector.push_back(std::make_shared<DiscreteFunctionVariant>(*df)); + } else { + static_assert(is_false_v<DF_T>, "unexpected type"); + } + } else { + static_assert(is_false_v<DF_T>, "unexpected type"); + } + }; + + (convert(std::forward<DiscreteFunctionT>(input)), ...); + return this->build(variant_vector); + } + + PolynomialReconstruction(const PolynomialReconstructionDescriptor& descriptor) : m_descriptor{descriptor} {} + ~PolynomialReconstruction() = default; +}; + +#endif // POLYNOMIAL_RECONSTRUCTION_HPP diff --git a/src/scheme/PolynomialReconstructionDescriptor.hpp b/src/scheme/PolynomialReconstructionDescriptor.hpp new file mode 100644 index 0000000000000000000000000000000000000000..44fdd2082e0efdf550bc1490a6df18d5658ff9bd --- /dev/null +++ b/src/scheme/PolynomialReconstructionDescriptor.hpp @@ -0,0 +1,123 @@ +#ifndef POLYNOMIAL_RECONSTRUCTION_DESCRIPTOR_HPP +#define POLYNOMIAL_RECONSTRUCTION_DESCRIPTOR_HPP + +#include <mesh/IBoundaryDescriptor.hpp> +#include <mesh/StencilDescriptor.hpp> +#include <scheme/IntegrationMethodType.hpp> +#include <utils/PugsMacros.hpp> + +#include <cstddef> +#include <memory> +#include <vector> + +class PolynomialReconstructionDescriptor +{ + public: + using BoundaryDescriptorList = std::vector<std::shared_ptr<const IBoundaryDescriptor>>; + + private: + IntegrationMethodType m_integration_method; + size_t m_degree; + StencilDescriptor m_stencil_descriptor; + + BoundaryDescriptorList m_symmetry_boundary_descriptor_list; + + bool m_preconditioning = true; + bool m_row_weighting = true; + + public: + PUGS_INLINE IntegrationMethodType + integrationMethodType() const + { + return m_integration_method; + } + + PUGS_INLINE + size_t + degree() const + { + return m_degree; + } + + PUGS_INLINE + const StencilDescriptor& + stencilDescriptor() const + { + return m_stencil_descriptor; + } + + PUGS_INLINE + const BoundaryDescriptorList& + symmetryBoundaryDescriptorList() const + { + return m_symmetry_boundary_descriptor_list; + } + + PUGS_INLINE + bool + preconditioning() const + { + return m_preconditioning; + } + + PUGS_INLINE + bool + rowWeighting() const + { + return m_row_weighting; + } + + PUGS_INLINE + void + setPreconditioning(const bool preconditioning) + { + m_preconditioning = preconditioning; + } + + PUGS_INLINE + void + setRowWeighting(const bool row_weighting) + { + m_row_weighting = row_weighting; + } + + PolynomialReconstructionDescriptor(const IntegrationMethodType integration_method, const size_t degree) + : m_integration_method{integration_method}, + m_degree{degree}, + m_stencil_descriptor(degree, StencilDescriptor::ConnectionType::by_nodes) + {} + + PolynomialReconstructionDescriptor(const IntegrationMethodType integration_method, + const size_t degree, + const BoundaryDescriptorList& symmetry_boundary_descriptor_list) + : m_integration_method{integration_method}, + m_degree{degree}, + m_stencil_descriptor(degree, StencilDescriptor::ConnectionType::by_nodes), + m_symmetry_boundary_descriptor_list(symmetry_boundary_descriptor_list) + {} + + PolynomialReconstructionDescriptor(const IntegrationMethodType integration_method, + const size_t degree, + const StencilDescriptor& stencil_descriptor) + : m_integration_method{integration_method}, m_degree{degree}, m_stencil_descriptor{stencil_descriptor} + {} + + PolynomialReconstructionDescriptor(const IntegrationMethodType integration_method, + const size_t degree, + const StencilDescriptor& stencil_descriptor, + const BoundaryDescriptorList& symmetry_boundary_descriptor_list) + : m_integration_method{integration_method}, + m_degree{degree}, + m_stencil_descriptor{stencil_descriptor}, + m_symmetry_boundary_descriptor_list(symmetry_boundary_descriptor_list) + {} + + PolynomialReconstructionDescriptor() = delete; + + PolynomialReconstructionDescriptor(const PolynomialReconstructionDescriptor&) = default; + PolynomialReconstructionDescriptor(PolynomialReconstructionDescriptor&&) = default; + + ~PolynomialReconstructionDescriptor() = default; +}; + +#endif // POLYNOMIAL_RECONSTRUCTION_DESCRIPTOR_HPP diff --git a/src/scheme/reconstruction_utils/BoundaryIntegralReconstructionMatrixBuilder.cpp b/src/scheme/reconstruction_utils/BoundaryIntegralReconstructionMatrixBuilder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..58b8fcd5ea34a64bd8d5808c41ea95079c2fcf84 --- /dev/null +++ b/src/scheme/reconstruction_utils/BoundaryIntegralReconstructionMatrixBuilder.cpp @@ -0,0 +1,553 @@ +#include <scheme/reconstruction_utils/BoundaryIntegralReconstructionMatrixBuilder.hpp> + +#include <analysis/GaussLegendreQuadratureDescriptor.hpp> +#include <analysis/GaussQuadratureDescriptor.hpp> +#include <analysis/QuadratureFormula.hpp> +#include <analysis/QuadratureManager.hpp> +#include <geometry/LineTransformation.hpp> +#include <geometry/SquareTransformation.hpp> +#include <geometry/SymmetryUtils.hpp> +#include <geometry/TriangleTransformation.hpp> +#include <mesh/Mesh.hpp> +#include <mesh/MeshDataManager.hpp> +#include <scheme/DiscreteFunctionDPk.hpp> + +template <MeshConcept MeshTypeT> +class PolynomialReconstruction::BoundaryIntegralReconstructionMatrixBuilder<MeshTypeT>::SpecificDimensionalData +{ + private: + const ItemToItemMatrix<ItemType::cell, ItemType::face> m_cell_to_face_matrix; + const ItemToItemMatrix<ItemType::face, ItemType::node> m_face_to_node_matrix; + const FaceValuePerCell<const bool> m_cell_face_is_reversed; + + public: + PUGS_INLINE const auto& + cellToFaceMatrix() const noexcept + { + return m_cell_to_face_matrix; + } + + PUGS_INLINE + const auto& + faceToNodeMatrix() const noexcept + { + return m_face_to_node_matrix; + } + + PUGS_INLINE + const auto& + cellFaceIsReversed() const noexcept + { + return m_cell_face_is_reversed; + } + + SpecificDimensionalData(const Connectivity<MeshType::Dimension>& connectivity) + : m_cell_to_face_matrix{connectivity.cellToFaceMatrix()}, + m_face_to_node_matrix{connectivity.faceToNodeMatrix()}, + m_cell_face_is_reversed{connectivity.cellFaceIsReversed()} + {} + + ~SpecificDimensionalData() = default; +}; + +template <> +class PolynomialReconstruction::BoundaryIntegralReconstructionMatrixBuilder<Mesh<1>>::SpecificDimensionalData +{ + private: + const ItemToItemMatrix<ItemType::cell, ItemType::node> m_cell_to_node_matrix; + + public: + PUGS_INLINE + const auto& + cellToNodeMatrix() const noexcept + { + return m_cell_to_node_matrix; + } + + SpecificDimensionalData(const Connectivity<1>& connectivity) : m_cell_to_node_matrix{connectivity.cellToNodeMatrix()} + {} + + ~SpecificDimensionalData() = default; +}; + +template <> +template <typename ConformTransformationT> +void +PolynomialReconstruction::BoundaryIntegralReconstructionMatrixBuilder<Mesh<3>>::_computeEjkBoundaryMean( + const QuadratureFormula<MeshType::Dimension - 1>& quadrature, + const ConformTransformationT& T, + const Rd& Xj, + const double inv_Vi, + SmallArray<double>& mean_of_ejk) +{ + for (size_t i_q = 0; i_q < quadrature.numberOfPoints(); ++i_q) { + const double wq = quadrature.weight(i_q); + const TinyVector<2> xi_q = quadrature.point(i_q); + + const double area_variation_e1 = T.areaVariation(xi_q)[0] * inv_Vi; + + const Rd X_Xj = T(xi_q) - Xj; + + const double x_xj = X_Xj[0]; + const double y_yj = X_Xj[1]; + const double z_zj = X_Xj[2]; + + { + size_t k = 0; + m_tmp_array[k++] = x_xj * wq * area_variation_e1; + for (; k <= m_polynomial_degree; ++k) { + m_tmp_array[k] // + = x_xj * m_tmp_array[k - 1] * m_antiderivative_correction_coef[k]; // ((1. * k) / (k + 1)); + } + + for (size_t i_y = 1; i_y <= m_polynomial_degree; ++i_y) { + const size_t begin_i_y_1 = m_yz_row_index[i_y - 1]; + const size_t nb_monoms = m_yz_row_size[i_y]; + for (size_t l = 0; l < nb_monoms; ++l, ++k) { + m_tmp_array[k] = y_yj * m_tmp_array[begin_i_y_1 + l]; + } + } + + for (size_t i_z = 1; i_z <= m_polynomial_degree; ++i_z) { + const size_t nb_y = m_yz_row_size[m_z_triangle_index[i_z]]; + const size_t index_z = m_z_triangle_index[i_z]; + const size_t index_z_1 = m_z_triangle_index[i_z - 1]; + for (size_t i_y = 0; i_y < nb_y; ++i_y) { + const size_t begin_i_yz_1 = m_yz_row_index[index_z_1 + i_y]; + const size_t nb_monoms = m_yz_row_size[index_z + i_y]; + for (size_t l = 0; l < nb_monoms; ++l, ++k) { + m_tmp_array[k] = z_zj * m_tmp_array[begin_i_yz_1 + l]; + } + } + } + } + + for (size_t k = 1; k < m_basis_dimension; ++k) { + mean_of_ejk[k - 1] += m_tmp_array[k]; + } + } +} + +template <> +template <typename ConformTransformationT> +void +PolynomialReconstruction::BoundaryIntegralReconstructionMatrixBuilder<Mesh<2>>::_computeEjkBoundaryMean( + const QuadratureFormula<MeshType::Dimension - 1>& quadrature, + const ConformTransformationT& T, + const Rd& Xj, + const double inv_Vi, + SmallArray<double>& mean_of_ejk) +{ + const double velocity_perp_e1 = T.velocity()[1] * inv_Vi; + + for (size_t i_q = 0; i_q < quadrature.numberOfPoints(); ++i_q) { + const double wq = quadrature.weight(i_q); + const TinyVector<1> xi_q = quadrature.point(i_q); + + const Rd X_Xj = T(xi_q) - Xj; + + const double x_xj = X_Xj[0]; + const double y_yj = X_Xj[1]; + + { + size_t k = 0; + m_tmp_array[k++] = x_xj * wq * velocity_perp_e1; + for (; k <= m_polynomial_degree; ++k) { + m_tmp_array[k] // + = x_xj * m_tmp_array[k - 1] * m_antiderivative_correction_coef[k]; // ((1. * k) / (k + 1)); + } + + for (size_t i_y = 1; i_y <= m_polynomial_degree; ++i_y) { + const size_t begin_i_y_1 = m_y_row_index[i_y - 1]; + for (size_t l = 0; l <= m_polynomial_degree - i_y; ++l) { + m_tmp_array[k++] = y_yj * m_tmp_array[begin_i_y_1 + l]; + } + } + } + + for (size_t k = 1; k < m_basis_dimension; ++k) { + mean_of_ejk[k - 1] += m_tmp_array[k]; + } + } +} + +template <MeshConcept MeshTypeT> +void +PolynomialReconstruction::BoundaryIntegralReconstructionMatrixBuilder<MeshTypeT>::_computeEjkMeanByBoundary( + const Rd& Xj, + const CellId& cell_id, + SmallArray<double>& mean_of_ejk) +{ + const double inv_Vi = 1. / m_Vj[cell_id]; + + const auto& face_is_reversed = m_dimensional_data_ptr->cellFaceIsReversed()[cell_id]; + const auto& cell_face_list = m_dimensional_data_ptr->cellToFaceMatrix()[cell_id]; + const auto& face_to_node_matrix = m_dimensional_data_ptr->faceToNodeMatrix(); + + mean_of_ejk.fill(0); + + if constexpr (MeshType::Dimension == 2) { + const auto& quadrature = + QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor(m_polynomial_degree + 1)); + + for (size_t i_face = 0; i_face < cell_face_list.size(); ++i_face) { + const FaceId face_id = cell_face_list[i_face]; + auto face_node_list = face_to_node_matrix[face_id]; + if (face_is_reversed[i_face]) { + const LineTransformation<2> T{m_xr[face_node_list[1]], m_xr[face_node_list[0]]}; + _computeEjkBoundaryMean(quadrature, T, Xj, inv_Vi, mean_of_ejk); + } else { + const LineTransformation<2> T{m_xr[face_node_list[0]], m_xr[face_node_list[1]]}; + _computeEjkBoundaryMean(quadrature, T, Xj, inv_Vi, mean_of_ejk); + } + } + } else { + static_assert(MeshType::Dimension == 3); + + const auto& triangle_quadrature = + QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(m_polynomial_degree + 1)); + const auto& square_quadrature = + QuadratureManager::instance().getSquareFormula(GaussLegendreQuadratureDescriptor(m_polynomial_degree + 1)); + + for (size_t i_face = 0; i_face < cell_face_list.size(); ++i_face) { + const FaceId face_id = cell_face_list[i_face]; + auto face_node_list = face_to_node_matrix[face_id]; + switch (face_node_list.size()) { + case 3: { + if (face_is_reversed[i_face]) { + const TriangleTransformation<3> T{m_xr[face_node_list[2]], m_xr[face_node_list[1]], m_xr[face_node_list[0]]}; + _computeEjkBoundaryMean(triangle_quadrature, T, Xj, inv_Vi, mean_of_ejk); + } else { + const TriangleTransformation<3> T{m_xr[face_node_list[0]], m_xr[face_node_list[1]], m_xr[face_node_list[2]]}; + _computeEjkBoundaryMean(triangle_quadrature, T, Xj, inv_Vi, mean_of_ejk); + } + break; + } + case 4: { + if (face_is_reversed[i_face]) { + const SquareTransformation<3> T{m_xr[face_node_list[3]], m_xr[face_node_list[2]], m_xr[face_node_list[1]], + m_xr[face_node_list[0]]}; + _computeEjkBoundaryMean(square_quadrature, T, Xj, inv_Vi, mean_of_ejk); + } else { + const SquareTransformation<3> T{m_xr[face_node_list[0]], m_xr[face_node_list[1]], m_xr[face_node_list[2]], + m_xr[face_node_list[3]]}; + _computeEjkBoundaryMean(square_quadrature, T, Xj, inv_Vi, mean_of_ejk); + } + break; + } + default: { + throw NotImplementedError("invalid face type"); + } + } + } + } +} + +template <MeshConcept MeshTypeT> +void +PolynomialReconstruction::BoundaryIntegralReconstructionMatrixBuilder< + MeshTypeT>::_computeEjkMeanByBoundaryInSymmetricCell(const Rd& origin, + const Rd& normal, + const Rd& Xj, + const CellId& cell_id, + SmallArray<double>& mean_of_ejk) +{ + const double inv_Vi = 1. / m_Vj[cell_id]; + + const auto& face_is_reversed = m_dimensional_data_ptr->cellFaceIsReversed()[cell_id]; + const auto& cell_face_list = m_dimensional_data_ptr->cellToFaceMatrix()[cell_id]; + const auto& face_to_node_matrix = m_dimensional_data_ptr->faceToNodeMatrix(); + + mean_of_ejk.fill(0); + if constexpr (MeshType::Dimension == 2) { + const auto& quadrature = + QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor(m_polynomial_degree + 1)); + + for (size_t i_face = 0; i_face < cell_face_list.size(); ++i_face) { + const FaceId face_id = cell_face_list[i_face]; + auto face_node_list = face_to_node_matrix[face_id]; + + const auto x0 = symmetrize_coordinates(origin, normal, m_xr[face_node_list[1]]); + const auto x1 = symmetrize_coordinates(origin, normal, m_xr[face_node_list[0]]); + + if (face_is_reversed[i_face]) { + const LineTransformation<2> T{x1, x0}; + _computeEjkBoundaryMean(quadrature, T, Xj, inv_Vi, mean_of_ejk); + } else { + const LineTransformation<2> T{x0, x1}; + _computeEjkBoundaryMean(quadrature, T, Xj, inv_Vi, mean_of_ejk); + } + } + } else { + static_assert(MeshType::Dimension == 3); + + const auto& triangle_quadrature = + QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(m_polynomial_degree + 1)); + const auto& square_quadrature = + QuadratureManager::instance().getSquareFormula(GaussLegendreQuadratureDescriptor(m_polynomial_degree + 1)); + + for (size_t i_face = 0; i_face < cell_face_list.size(); ++i_face) { + const FaceId face_id = cell_face_list[i_face]; + auto face_node_list = face_to_node_matrix[face_id]; + switch (face_node_list.size()) { + case 3: { + const auto x0 = symmetrize_coordinates(origin, normal, m_xr[face_node_list[2]]); + const auto x1 = symmetrize_coordinates(origin, normal, m_xr[face_node_list[1]]); + const auto x2 = symmetrize_coordinates(origin, normal, m_xr[face_node_list[0]]); + + if (face_is_reversed[i_face]) { + const TriangleTransformation<3> T{x2, x1, x0}; + _computeEjkBoundaryMean(triangle_quadrature, T, Xj, inv_Vi, mean_of_ejk); + } else { + const TriangleTransformation<3> T{x0, x1, x2}; + _computeEjkBoundaryMean(triangle_quadrature, T, Xj, inv_Vi, mean_of_ejk); + } + break; + } + case 4: { + const auto x0 = symmetrize_coordinates(origin, normal, m_xr[face_node_list[3]]); + const auto x1 = symmetrize_coordinates(origin, normal, m_xr[face_node_list[2]]); + const auto x2 = symmetrize_coordinates(origin, normal, m_xr[face_node_list[1]]); + const auto x3 = symmetrize_coordinates(origin, normal, m_xr[face_node_list[0]]); + + if (face_is_reversed[i_face]) { + const SquareTransformation<3> T{x3, x2, x1, x0}; + _computeEjkBoundaryMean(square_quadrature, T, Xj, inv_Vi, mean_of_ejk); + } else { + const SquareTransformation<3> T{x0, x1, x2, x3}; + _computeEjkBoundaryMean(square_quadrature, T, Xj, inv_Vi, mean_of_ejk); + } + break; + } + default: { + throw NotImplementedError("invalid face type"); + } + } + } + } +} + +template <> +void +PolynomialReconstruction::BoundaryIntegralReconstructionMatrixBuilder<Mesh<1>>::_computeEjkMeanByBoundary( + const Rd& Xj, + const CellId& cell_id, + SmallArray<double>& mean_of_ejk) +{ + const double inv_Vi = 1. / m_Vj[cell_id]; + + const auto& cell_node_list = m_dimensional_data_ptr->cellToNodeMatrix()[cell_id]; + + const double xr1_xj = (m_xr[cell_node_list[1]] - Xj)[0]; + + m_tmp_array[0] = xr1_xj * inv_Vi; + for (size_t k = 1; k <= m_polynomial_degree; ++k) { + m_tmp_array[k] // + = xr1_xj * m_tmp_array[k - 1] * m_antiderivative_correction_coef[k]; // ((1. * k) / (k + 1)); + } + + for (size_t k = 1; k < m_basis_dimension; ++k) { + mean_of_ejk[k - 1] = m_tmp_array[k]; + } + + const double xr0_xj = (m_xr[cell_node_list[0]] - Xj)[0]; + + m_tmp_array[0] = xr0_xj * inv_Vi; + for (size_t k = 1; k <= m_polynomial_degree; ++k) { + m_tmp_array[k] // + = xr0_xj * m_tmp_array[k - 1] * m_antiderivative_correction_coef[k]; // ((1. * k) / (k + 1)); + } + + for (size_t k = 1; k < m_basis_dimension; ++k) { + mean_of_ejk[k - 1] -= m_tmp_array[k]; + } +} + +template <> +void +PolynomialReconstruction::BoundaryIntegralReconstructionMatrixBuilder< + Mesh<1>>::_computeEjkMeanByBoundaryInSymmetricCell(const Rd& origin, + const Rd& normal, + const Rd& Xj, + const CellId& cell_id, + SmallArray<double>& mean_of_ejk) +{ + const double inv_Vi = 1. / m_Vj[cell_id]; + + const auto& cell_node_list = m_dimensional_data_ptr->cellToNodeMatrix()[cell_id]; + + const double xr1_xj = (symmetrize_coordinates(origin, normal, m_xr[cell_node_list[0]]) - Xj)[0]; + + m_tmp_array[0] = xr1_xj * inv_Vi; + for (size_t k = 1; k <= m_polynomial_degree; ++k) { + m_tmp_array[k] // + = xr1_xj * m_tmp_array[k - 1] * m_antiderivative_correction_coef[k]; // ((1. * k) / (k + 1)); + } + + for (size_t k = 1; k < m_basis_dimension; ++k) { + mean_of_ejk[k - 1] = m_tmp_array[k]; + } + + const double xr0_xj = (symmetrize_coordinates(origin, normal, m_xr[cell_node_list[1]]) - Xj)[0]; + + m_tmp_array[0] = xr0_xj * inv_Vi; + for (size_t k = 1; k <= m_polynomial_degree; ++k) { + m_tmp_array[k] // + = xr0_xj * m_tmp_array[k - 1] * m_antiderivative_correction_coef[k]; // ((1. * k) / (k + 1)); + } + + for (size_t k = 1; k < m_basis_dimension; ++k) { + mean_of_ejk[k - 1] -= m_tmp_array[k]; + } +} + +template <MeshConcept MeshTypeT> +void +PolynomialReconstruction::BoundaryIntegralReconstructionMatrixBuilder<MeshTypeT>::build( + const CellId cell_j_id, + ShrinkMatrixView<SmallMatrix<double>>& A) +{ + if constexpr (MeshType::Dimension <= 3) { + const auto& stencil_cell_list = m_stencil_array[cell_j_id]; + + const Rd& Xj = m_xj[cell_j_id]; + + _computeEjkMeanByBoundary(Xj, cell_j_id, m_mean_j_of_ejk); + + size_t index = 0; + for (size_t i = 0; i < stencil_cell_list.size(); ++i, ++index) { + const CellId cell_i_id = stencil_cell_list[i]; + + _computeEjkMeanByBoundary(Xj, cell_i_id, m_mean_i_of_ejk); + + for (size_t l = 0; l < m_basis_dimension - 1; ++l) { + A(index, l) = m_mean_i_of_ejk[l] - m_mean_j_of_ejk[l]; + } + } + for (size_t i_symmetry = 0; i_symmetry < m_stencil_array.symmetryBoundaryStencilArrayList().size(); ++i_symmetry) { + auto& ghost_stencil = m_stencil_array.symmetryBoundaryStencilArrayList()[i_symmetry].stencilArray(); + auto ghost_cell_list = ghost_stencil[cell_j_id]; + + const Rd& origin = m_symmetry_origin_list[i_symmetry]; + const Rd& normal = m_symmetry_normal_list[i_symmetry]; + + for (size_t i = 0; i < ghost_cell_list.size(); ++i, ++index) { + const CellId cell_i_id = ghost_cell_list[i]; + + _computeEjkMeanByBoundaryInSymmetricCell(origin, normal, Xj, cell_i_id, m_mean_i_of_ejk); + + for (size_t l = 0; l < m_basis_dimension - 1; ++l) { + A(index, l) = m_mean_i_of_ejk[l] - m_mean_j_of_ejk[l]; + } + } + } + } else { + throw NotImplementedError("invalid mesh dimension"); + } +} + +template <MeshConcept MeshTypeT> +PolynomialReconstruction::BoundaryIntegralReconstructionMatrixBuilder< + MeshTypeT>::BoundaryIntegralReconstructionMatrixBuilder(const MeshType& mesh, + const size_t polynomial_degree, + const SmallArray<const Rd>& symmetry_origin_list, + const SmallArray<const Rd>& symmetry_normal_list, + const CellToCellStencilArray& stencil_array) + : m_mesh{mesh}, + m_basis_dimension{ + DiscreteFunctionDPk<MeshType::Dimension, double>::BasisViewType::dimensionFromDegree(polynomial_degree)}, + m_polynomial_degree{polynomial_degree}, + m_tmp_array{m_basis_dimension}, + m_mean_j_of_ejk{m_basis_dimension - 1}, + m_mean_i_of_ejk{m_basis_dimension - 1}, + + m_dimensional_data_ptr{std::make_shared<SpecificDimensionalData>(mesh.connectivity())}, + + m_stencil_array{stencil_array}, + m_symmetry_origin_list{symmetry_origin_list}, + m_symmetry_normal_list{symmetry_normal_list}, + m_Vj{MeshDataManager::instance().getMeshData(mesh).Vj()}, + m_xj{MeshDataManager::instance().getMeshData(mesh).xj()}, + m_xr{mesh.xr()} +{ + SmallArray<double> antiderivative_correction_coef(m_polynomial_degree + 1); + for (size_t k = 0; k < antiderivative_correction_coef.size(); ++k) { + // The antiderivative of x^k is k/(k+1) times the antiderivative of x^(k-1) + antiderivative_correction_coef[k] = ((1. * k) / (k + 1)); + } + + m_antiderivative_correction_coef = antiderivative_correction_coef; + + if constexpr (MeshType::Dimension == 2) { + SmallArray<size_t> y_row_index(m_polynomial_degree + 1); + + size_t i_y = 0; + + y_row_index[i_y++] = 0; + for (ssize_t n = m_polynomial_degree + 1; n > 1; --n, ++i_y) { + y_row_index[i_y] = y_row_index[i_y - 1] + n; + } + + m_y_row_index = y_row_index; + + } else if constexpr (MeshType::Dimension == 3) { + SmallArray<size_t> yz_row_index((m_polynomial_degree + 2) * (m_polynomial_degree + 1) / 2 + 1); + SmallArray<size_t> z_triangle_index(m_polynomial_degree + 1); + + { + size_t i_z = 0; + size_t i_yz = 0; + + yz_row_index[i_yz++] = 0; + for (ssize_t n = m_polynomial_degree + 1; n >= 1; --n) { + z_triangle_index[i_z++] = i_yz - 1; + for (ssize_t i = n; i >= 1; --i) { + yz_row_index[i_yz] = yz_row_index[i_yz - 1] + i; + ++i_yz; + } + } + } + + SmallArray<size_t> yz_row_size{yz_row_index.size() - 1}; + for (size_t i = 0; i < yz_row_size.size(); ++i) { + yz_row_size[i] = yz_row_index[i + 1] - yz_row_index[i]; + } + + m_yz_row_index = yz_row_index; + m_z_triangle_index = z_triangle_index; + m_yz_row_size = yz_row_size; + } +} + +template void PolynomialReconstruction::BoundaryIntegralReconstructionMatrixBuilder<Mesh<1>>::build( + const CellId, + ShrinkMatrixView<SmallMatrix<double>>&); + +template void PolynomialReconstruction::BoundaryIntegralReconstructionMatrixBuilder<Mesh<2>>::build( + const CellId, + ShrinkMatrixView<SmallMatrix<double>>&); + +template void PolynomialReconstruction::BoundaryIntegralReconstructionMatrixBuilder<Mesh<3>>::build( + const CellId, + ShrinkMatrixView<SmallMatrix<double>>&); + +template PolynomialReconstruction::BoundaryIntegralReconstructionMatrixBuilder< + Mesh<1>>::BoundaryIntegralReconstructionMatrixBuilder(const MeshType&, + const size_t, + const SmallArray<const Rd>&, + const SmallArray<const Rd>&, + const CellToCellStencilArray&); + +template PolynomialReconstruction::BoundaryIntegralReconstructionMatrixBuilder< + Mesh<2>>::BoundaryIntegralReconstructionMatrixBuilder(const MeshType&, + const size_t, + const SmallArray<const Rd>&, + const SmallArray<const Rd>&, + const CellToCellStencilArray&); + +template PolynomialReconstruction::BoundaryIntegralReconstructionMatrixBuilder< + Mesh<3>>::BoundaryIntegralReconstructionMatrixBuilder(const MeshType&, + const size_t, + const SmallArray<const Rd>&, + const SmallArray<const Rd>&, + const CellToCellStencilArray&); diff --git a/src/scheme/reconstruction_utils/BoundaryIntegralReconstructionMatrixBuilder.hpp b/src/scheme/reconstruction_utils/BoundaryIntegralReconstructionMatrixBuilder.hpp new file mode 100644 index 0000000000000000000000000000000000000000..60707189b909e9d5f17ae2852be655cbfb877351 --- /dev/null +++ b/src/scheme/reconstruction_utils/BoundaryIntegralReconstructionMatrixBuilder.hpp @@ -0,0 +1,89 @@ +#ifndef BOUNDARY_INTEGRAL_RECONSTRUCTION_MATRIX_BUILDER_HPP +#define BOUNDARY_INTEGRAL_RECONSTRUCTION_MATRIX_BUILDER_HPP + +#include <algebra/ShrinkMatrixView.hpp> +#include <algebra/SmallMatrix.hpp> +#include <mesh/ItemValue.hpp> +#include <mesh/StencilArray.hpp> +#include <scheme/PolynomialReconstruction.hpp> +#include <utils/SmallArray.hpp> + +template <size_t Dimension> +class QuadratureFormula; + +template <MeshConcept MeshTypeT> +class PolynomialReconstruction::BoundaryIntegralReconstructionMatrixBuilder +{ + public: + using MeshType = MeshTypeT; + + constexpr static bool handles_high_degrees = true; + + private: + using Rd = TinyVector<MeshType::Dimension>; + + const MeshType& m_mesh; + const size_t m_basis_dimension; + const size_t m_polynomial_degree; + + const SmallArray<double> m_tmp_array; + SmallArray<double> m_mean_j_of_ejk; + SmallArray<double> m_mean_i_of_ejk; + + class SpecificDimensionalData; + std::shared_ptr<SpecificDimensionalData> m_dimensional_data_ptr; + + const CellToCellStencilArray& m_stencil_array; + + const SmallArray<const Rd> m_symmetry_origin_list; + const SmallArray<const Rd> m_symmetry_normal_list; + + const CellValue<const double> m_Vj; + const CellValue<const Rd> m_xj; + const NodeValue<const Rd> m_xr; + + // 2D + SmallArray<const size_t> m_y_row_index; + + // 3D + SmallArray<const size_t> m_yz_row_index; + SmallArray<const size_t> m_z_triangle_index; + SmallArray<const size_t> m_yz_row_size; + + SmallArray<const double> m_antiderivative_correction_coef; + + template <typename ConformTransformationT> + void _computeEjkBoundaryMean(const QuadratureFormula<MeshType::Dimension - 1>& quadrature, + const ConformTransformationT& T, + const Rd& Xj, + const double inv_Vi, + SmallArray<double>& mean_of_ejk); + + void _computeEjkMeanByBoundary(const Rd& Xj, const CellId& cell_id, SmallArray<double>& mean_of_ejk); + + void _computeEjkMeanByBoundaryInSymmetricCell(const Rd& origin, + const Rd& normal, + const Rd& Xj, + const CellId& cell_id, + SmallArray<double>& mean_of_ejk); + + public: + PUGS_INLINE + SmallArray<const double> + meanjOfEjk() const + { + return m_mean_j_of_ejk; + } + + void build(const CellId cell_j_id, ShrinkMatrixView<SmallMatrix<double>>& A); + + BoundaryIntegralReconstructionMatrixBuilder(const MeshType& mesh, + const size_t polynomial_degree, + const SmallArray<const Rd>& symmetry_origin_list, + const SmallArray<const Rd>& symmetry_normal_list, + const CellToCellStencilArray& stencil_array); + + ~BoundaryIntegralReconstructionMatrixBuilder() = default; +}; + +#endif // BOUNDARY_INTEGRAL_RECONSTRUCTION_MATRIX_BUILDER_HPP diff --git a/src/scheme/reconstruction_utils/CMakeLists.txt b/src/scheme/reconstruction_utils/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..1d4ebcb48c3d47c9c19421151257deb8a31bf9a9 --- /dev/null +++ b/src/scheme/reconstruction_utils/CMakeLists.txt @@ -0,0 +1,17 @@ +# ------------------- Source files -------------------- + +add_library(PugsSchemeReconstructionUtils + BoundaryIntegralReconstructionMatrixBuilder.cpp + CellCenterReconstructionMatrixBuilder.cpp + ElementIntegralReconstructionMatrixBuilder.cpp +) + +add_dependencies( + PugsUtils + PugsMesh +) + +target_link_libraries( + PugsSchemeReconstructionUtils + ${HIGHFIVE_TARGET} +) diff --git a/src/scheme/reconstruction_utils/CellCenterReconstructionMatrixBuilder.cpp b/src/scheme/reconstruction_utils/CellCenterReconstructionMatrixBuilder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..94d1ef9a79ac1fb8f7e3cc0ece4c8eb21672bef7 --- /dev/null +++ b/src/scheme/reconstruction_utils/CellCenterReconstructionMatrixBuilder.cpp @@ -0,0 +1,92 @@ +#include <scheme/reconstruction_utils/CellCenterReconstructionMatrixBuilder.hpp> + +#include <geometry/SymmetryUtils.hpp> +#include <mesh/Connectivity.hpp> +#include <mesh/Mesh.hpp> +#include <scheme/DiscreteFunctionDPk.hpp> + +template <MeshConcept MeshTypeT> +void +PolynomialReconstruction::CellCenterReconstructionMatrixBuilder<MeshTypeT>::build( + const CellId cell_j_id, + ShrinkMatrixView<SmallMatrix<double>>& A) +{ + const auto& stencil_cell_list = m_stencil_array[cell_j_id]; + + const Rd& Xj = m_xj[cell_j_id]; + size_t index = 0; + for (size_t i = 0; i < stencil_cell_list.size(); ++i, ++index) { + const CellId cell_i_id = stencil_cell_list[i]; + const Rd Xi_Xj = m_xj[cell_i_id] - Xj; + for (size_t l = 0; l < m_basis_dimension - 1; ++l) { + A(index, l) = Xi_Xj[l]; + } + } + for (size_t i_symmetry = 0; i_symmetry < m_stencil_array.symmetryBoundaryStencilArrayList().size(); ++i_symmetry) { + auto& ghost_stencil = m_stencil_array.symmetryBoundaryStencilArrayList()[i_symmetry].stencilArray(); + auto ghost_cell_list = ghost_stencil[cell_j_id]; + + const Rd& origin = m_symmetry_origin_list[i_symmetry]; + const Rd& normal = m_symmetry_normal_list[i_symmetry]; + + for (size_t i = 0; i < ghost_cell_list.size(); ++i, ++index) { + const CellId cell_i_id = ghost_cell_list[i]; + const Rd Xi_Xj = symmetrize_coordinates(origin, normal, m_xj[cell_i_id]) - Xj; + for (size_t l = 0; l < m_basis_dimension - 1; ++l) { + A(index, l) = Xi_Xj[l]; + } + } + } +} + +template <MeshConcept MeshTypeT> +PolynomialReconstruction::CellCenterReconstructionMatrixBuilder<MeshTypeT>::CellCenterReconstructionMatrixBuilder( + const MeshType& mesh, + const size_t polynomial_degree, + const SmallArray<const Rd>& symmetry_origin_list, + const SmallArray<const Rd>& symmetry_normal_list, + const CellToCellStencilArray& stencil_array) + : m_basis_dimension{DiscreteFunctionDPk<MeshType::Dimension, double>::BasisViewType::dimensionFromDegree( + polynomial_degree)}, + m_symmetry_origin_list{symmetry_origin_list}, + m_symmetry_normal_list{symmetry_normal_list}, + m_stencil_array{stencil_array}, + m_xj{MeshDataManager::instance().getMeshData(mesh).xj()} +{ + if (polynomial_degree != 1) { + throw NormalError("cell center based reconstruction is only valid for first order"); + } +} + +template void PolynomialReconstruction::CellCenterReconstructionMatrixBuilder<Mesh<1>>::build( + const CellId, + ShrinkMatrixView<SmallMatrix<double>>&); + +template void PolynomialReconstruction::CellCenterReconstructionMatrixBuilder<Mesh<2>>::build( + const CellId, + ShrinkMatrixView<SmallMatrix<double>>&); + +template void PolynomialReconstruction::CellCenterReconstructionMatrixBuilder<Mesh<3>>::build( + const CellId, + ShrinkMatrixView<SmallMatrix<double>>&); + +template PolynomialReconstruction::CellCenterReconstructionMatrixBuilder< + Mesh<1>>::CellCenterReconstructionMatrixBuilder(const MeshType&, + const size_t, + const SmallArray<const Rd>&, + const SmallArray<const Rd>&, + const CellToCellStencilArray&); + +template PolynomialReconstruction::CellCenterReconstructionMatrixBuilder< + Mesh<2>>::CellCenterReconstructionMatrixBuilder(const MeshType&, + const size_t, + const SmallArray<const Rd>&, + const SmallArray<const Rd>&, + const CellToCellStencilArray&); + +template PolynomialReconstruction::CellCenterReconstructionMatrixBuilder< + Mesh<3>>::CellCenterReconstructionMatrixBuilder(const MeshType&, + const size_t, + const SmallArray<const Rd>&, + const SmallArray<const Rd>&, + const CellToCellStencilArray&); diff --git a/src/scheme/reconstruction_utils/CellCenterReconstructionMatrixBuilder.hpp b/src/scheme/reconstruction_utils/CellCenterReconstructionMatrixBuilder.hpp new file mode 100644 index 0000000000000000000000000000000000000000..a8205a625dc0d62e35a33469fe2864719f8147a6 --- /dev/null +++ b/src/scheme/reconstruction_utils/CellCenterReconstructionMatrixBuilder.hpp @@ -0,0 +1,41 @@ +#ifndef CELL_CENTER_RECONSTRUCTION_MATRIX_BUILDER_HPP +#define CELL_CENTER_RECONSTRUCTION_MATRIX_BUILDER_HPP + +#include <algebra/ShrinkMatrixView.hpp> +#include <algebra/SmallMatrix.hpp> +#include <mesh/ItemValue.hpp> +#include <mesh/StencilArray.hpp> +#include <scheme/PolynomialReconstruction.hpp> +#include <utils/SmallArray.hpp> + +template <MeshConcept MeshTypeT> +class PolynomialReconstruction::CellCenterReconstructionMatrixBuilder +{ + public: + using MeshType = MeshTypeT; + + constexpr static bool handles_high_degrees = false; + + private: + using Rd = TinyVector<MeshType::Dimension>; + + const size_t m_basis_dimension; + const SmallArray<const Rd> m_symmetry_origin_list; + const SmallArray<const Rd> m_symmetry_normal_list; + + const CellToCellStencilArray& m_stencil_array; + const CellValue<const Rd> m_xj; + + public: + void build(const CellId cell_j_id, ShrinkMatrixView<SmallMatrix<double>>& A); + + CellCenterReconstructionMatrixBuilder(const MeshType& mesh, + const size_t polynomial_degree, + const SmallArray<const Rd>& symmetry_origin_list, + const SmallArray<const Rd>& symmetry_normal_list, + const CellToCellStencilArray& stencil_array); + + ~CellCenterReconstructionMatrixBuilder() = default; +}; + +#endif // CELL_CENTER_RECONSTRUCTION_MATRIX_BUILDER_HPP diff --git a/src/scheme/reconstruction_utils/ElementIntegralReconstructionMatrixBuilder.cpp b/src/scheme/reconstruction_utils/ElementIntegralReconstructionMatrixBuilder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..09e410811ed203610926bd605be3618e4219f396 --- /dev/null +++ b/src/scheme/reconstruction_utils/ElementIntegralReconstructionMatrixBuilder.cpp @@ -0,0 +1,501 @@ +#include <scheme/reconstruction_utils/ElementIntegralReconstructionMatrixBuilder.hpp> + +#include <analysis/GaussLegendreQuadratureDescriptor.hpp> +#include <analysis/GaussQuadratureDescriptor.hpp> +#include <analysis/QuadratureFormula.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/SymmetryUtils.hpp> +#include <geometry/TetrahedronTransformation.hpp> +#include <geometry/TriangleTransformation.hpp> +#include <mesh/Connectivity.hpp> +#include <mesh/Mesh.hpp> +#include <mesh/MeshDataManager.hpp> +#include <scheme/DiscreteFunctionDPk.hpp> + +template <MeshConcept MeshTypeT> +template <typename ConformTransformationT> +void +PolynomialReconstruction::ElementIntegralReconstructionMatrixBuilder<MeshTypeT>::_computeEjkMean( + const QuadratureFormula<MeshType::Dimension>& quadrature, + const ConformTransformationT& T, + const Rd& Xj, + const double Vi, + SmallArray<double>& mean_of_ejk) noexcept(NO_ASSERT) +{ + mean_of_ejk.fill(0); + + for (size_t i_q = 0; i_q < quadrature.numberOfPoints(); ++i_q) { + const double wq = quadrature.weight(i_q); + const Rd& xi_q = quadrature.point(i_q); + + const Rd X_Xj = T(xi_q) - Xj; + + if constexpr (MeshType::Dimension == 1) { + const double detT = T.jacobianDeterminant(); + + const double x_xj = X_Xj[0]; + + { + m_wq_detJ_ek[0] = wq * detT; + for (size_t k = 1; k <= m_polynomial_degree; ++k) { + m_wq_detJ_ek[k] = x_xj * m_wq_detJ_ek[k - 1]; + } + } + + } else if constexpr (MeshType::Dimension == 2) { + const double detT = [&] { + if constexpr (std::is_same_v<TriangleTransformation<2>, std::decay_t<decltype(T)>>) { + return T.jacobianDeterminant(); + } else { + return T.jacobianDeterminant(xi_q); + } + }(); + + const double x_xj = X_Xj[0]; + const double y_yj = X_Xj[1]; + + { + size_t k = 0; + m_wq_detJ_ek[k++] = wq * detT; + for (; k <= m_polynomial_degree; ++k) { + m_wq_detJ_ek[k] = x_xj * m_wq_detJ_ek[k - 1]; + } + + for (size_t i_y = 1; i_y <= m_polynomial_degree; ++i_y) { + const size_t begin_i_y_1 = m_y_row_index[i_y - 1]; + for (size_t l = 0; l <= m_polynomial_degree - i_y; ++l, ++k) { + m_wq_detJ_ek[k] = y_yj * m_wq_detJ_ek[begin_i_y_1 + l]; + } + } + } + + } else if constexpr (MeshType::Dimension == 3) { + static_assert(MeshType::Dimension == 3); + + const double detT = T.jacobianDeterminant(xi_q); + + const double x_xj = X_Xj[0]; + const double y_yj = X_Xj[1]; + const double z_zj = X_Xj[2]; + + { + size_t k = 0; + m_wq_detJ_ek[k++] = wq * detT; + for (; k <= m_polynomial_degree; ++k) { + m_wq_detJ_ek[k] = x_xj * m_wq_detJ_ek[k - 1]; + } + + for (size_t i_y = 1; i_y <= m_polynomial_degree; ++i_y) { + const size_t begin_i_y_1 = m_yz_row_index[i_y - 1]; + const size_t nb_monoms = m_yz_row_size[i_y]; + for (size_t l = 0; l < nb_monoms; ++l, ++k) { + m_wq_detJ_ek[k] = y_yj * m_wq_detJ_ek[begin_i_y_1 + l]; + } + } + + for (size_t i_z = 1; i_z <= m_polynomial_degree; ++i_z) { + const size_t nb_y = m_yz_row_size[m_z_triangle_index[i_z]]; + const size_t index_z = m_z_triangle_index[i_z]; + const size_t index_z_1 = m_z_triangle_index[i_z - 1]; + for (size_t i_y = 0; i_y < nb_y; ++i_y) { + const size_t begin_i_yz_1 = m_yz_row_index[index_z_1 + i_y]; + const size_t nb_monoms = m_yz_row_size[index_z + i_y]; + for (size_t l = 0; l < nb_monoms; ++l, ++k) { + m_wq_detJ_ek[k] = z_zj * m_wq_detJ_ek[begin_i_yz_1 + l]; + } + } + } + } + } + + for (size_t k = 1; k < m_basis_dimension; ++k) { + mean_of_ejk[k - 1] += m_wq_detJ_ek[k]; + } + } + + const double inv_Vi = 1. / Vi; + for (size_t k = 0; k < mean_of_ejk.size(); ++k) { + mean_of_ejk[k] *= inv_Vi; + } +} + +template <MeshConcept MeshTypeT> +void +PolynomialReconstruction::ElementIntegralReconstructionMatrixBuilder<MeshTypeT>::_computeEjkMean( + const Rd& Xj, + const CellId& cell_i_id, + SmallArray<double>& mean_of_ejk) +{ + const CellType cell_type = m_cell_type[cell_i_id]; + const auto node_list = m_cell_to_node_matrix[cell_i_id]; + const double Vi = m_Vj[cell_i_id]; + + if constexpr (MeshType::Dimension == 1) { + if (m_cell_type[cell_i_id] == CellType::Line) { + const LineTransformation<1> T{m_xr[node_list[0]], m_xr[node_list[1]]}; + + const auto& quadrature = + QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor{m_polynomial_degree}); + + this->_computeEjkMean(quadrature, T, Xj, Vi, mean_of_ejk); + + } else { + throw NotImplementedError("unexpected cell type: " + std::string{name(cell_type)}); + } + } else if constexpr (MeshType::Dimension == 2) { + switch (cell_type) { + case CellType::Triangle: { + const TriangleTransformation<2> T{m_xr[node_list[0]], m_xr[node_list[1]], m_xr[node_list[2]]}; + const auto& quadrature = + QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor{m_polynomial_degree}); + + this->_computeEjkMean(quadrature, T, Xj, Vi, mean_of_ejk); + break; + } + case CellType::Quadrangle: { + const SquareTransformation<2> T{m_xr[node_list[0]], m_xr[node_list[1]], m_xr[node_list[2]], m_xr[node_list[3]]}; + const auto& quadrature = + QuadratureManager::instance().getSquareFormula(GaussLegendreQuadratureDescriptor{m_polynomial_degree + 1}); + + this->_computeEjkMean(quadrature, T, Xj, Vi, mean_of_ejk); + break; + } + default: { + throw NotImplementedError("unexpected cell type: " + std::string{name(cell_type)}); + } + } + } else { + static_assert(MeshType::Dimension == 3); + + switch (cell_type) { + case CellType::Tetrahedron: { + const TetrahedronTransformation T{m_xr[node_list[0]], m_xr[node_list[1]], m_xr[node_list[2]], m_xr[node_list[3]]}; + + const auto& quadrature = + QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor{m_polynomial_degree}); + + this->_computeEjkMean(quadrature, T, Xj, Vi, mean_of_ejk); + + break; + } + case CellType::Prism: { + const PrismTransformation T{m_xr[node_list[0]], m_xr[node_list[1]], m_xr[node_list[2]], // + m_xr[node_list[3]], m_xr[node_list[4]], m_xr[node_list[5]]}; + + const auto& quadrature = + QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor{m_polynomial_degree + 1}); + + this->_computeEjkMean(quadrature, T, Xj, Vi, mean_of_ejk); + + break; + } + case CellType::Pyramid: { + const PyramidTransformation T{m_xr[node_list[0]], m_xr[node_list[1]], m_xr[node_list[2]], m_xr[node_list[3]], + m_xr[node_list[4]]}; + + const auto& quadrature = + QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor{m_polynomial_degree + 1}); + + this->_computeEjkMean(quadrature, T, Xj, Vi, mean_of_ejk); + break; + } + case CellType::Hexahedron: { + const CubeTransformation T{m_xr[node_list[0]], m_xr[node_list[1]], m_xr[node_list[2]], m_xr[node_list[3]], + m_xr[node_list[4]], m_xr[node_list[5]], m_xr[node_list[6]], m_xr[node_list[7]]}; + + const auto& quadrature = + QuadratureManager::instance().getCubeFormula(GaussLegendreQuadratureDescriptor{m_polynomial_degree + 1}); + + this->_computeEjkMean(quadrature, T, Xj, Vi, mean_of_ejk); + break; + } + default: { + throw NotImplementedError("unexpected cell type: " + std::string{name(cell_type)}); + } + } + } +} + +template <MeshConcept MeshTypeT> +void +PolynomialReconstruction::ElementIntegralReconstructionMatrixBuilder<MeshTypeT>::_computeEjkMeanInSymmetricCell( + const Rd& origin, + const Rd& normal, + const Rd& Xj, + const CellId& cell_i_id, + SmallArray<double>& mean_of_ejk) +{ + if constexpr (MeshType::Dimension == 1) { + auto node_list = m_cell_to_node_matrix[cell_i_id]; + const CellType cell_type = m_cell_type[cell_i_id]; + const double Vi = m_Vj[cell_i_id]; + + if (cell_type == CellType::Line) { + const auto x0 = symmetrize_coordinates(origin, normal, m_xr[node_list[1]]); + const auto x1 = symmetrize_coordinates(origin, normal, m_xr[node_list[0]]); + + const LineTransformation<1> T{x0, x1}; + + const auto& quadrature = + QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor{m_polynomial_degree}); + + this->_computeEjkMean(quadrature, T, Xj, Vi, mean_of_ejk); + + } else { + throw NotImplementedError("unexpected cell type: " + std::string{name(cell_type)}); + } + } else if constexpr (MeshType::Dimension == 2) { + auto node_list = m_cell_to_node_matrix[cell_i_id]; + const CellType cell_type = m_cell_type[cell_i_id]; + const double Vi = m_Vj[cell_i_id]; + + switch (cell_type) { + case CellType::Triangle: { + const auto x0 = symmetrize_coordinates(origin, normal, m_xr[node_list[2]]); + const auto x1 = symmetrize_coordinates(origin, normal, m_xr[node_list[1]]); + const auto x2 = symmetrize_coordinates(origin, normal, m_xr[node_list[0]]); + + const TriangleTransformation<2> T{x0, x1, x2}; + const auto& quadrature = + QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor{m_polynomial_degree}); + + this->_computeEjkMean(quadrature, T, Xj, Vi, mean_of_ejk); + break; + } + case CellType::Quadrangle: { + const auto x0 = symmetrize_coordinates(origin, normal, m_xr[node_list[3]]); + const auto x1 = symmetrize_coordinates(origin, normal, m_xr[node_list[2]]); + const auto x2 = symmetrize_coordinates(origin, normal, m_xr[node_list[1]]); + const auto x3 = symmetrize_coordinates(origin, normal, m_xr[node_list[0]]); + + const SquareTransformation<2> T{x0, x1, x2, x3}; + const auto& quadrature = + QuadratureManager::instance().getSquareFormula(GaussLegendreQuadratureDescriptor{m_polynomial_degree + 1}); + + this->_computeEjkMean(quadrature, T, Xj, Vi, mean_of_ejk); + break; + } + default: { + throw NotImplementedError("unexpected cell type: " + std::string{name(cell_type)}); + } + } + } else { + static_assert(MeshType::Dimension == 3); + auto node_list = m_cell_to_node_matrix[cell_i_id]; + const CellType cell_type = m_cell_type[cell_i_id]; + const double Vi = m_Vj[cell_i_id]; + switch (cell_type) { + case CellType::Tetrahedron: { + const auto x0 = symmetrize_coordinates(origin, normal, m_xr[node_list[1]]); + const auto x1 = symmetrize_coordinates(origin, normal, m_xr[node_list[0]]); + const auto x2 = symmetrize_coordinates(origin, normal, m_xr[node_list[2]]); + const auto x3 = symmetrize_coordinates(origin, normal, m_xr[node_list[3]]); + + const TetrahedronTransformation T{x0, x1, x2, x3}; + + const auto& quadrature = + QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor{m_polynomial_degree}); + + this->_computeEjkMean(quadrature, T, Xj, Vi, mean_of_ejk); + break; + } + case CellType::Prism: { + const auto x0 = symmetrize_coordinates(origin, normal, m_xr[node_list[1]]); + const auto x1 = symmetrize_coordinates(origin, normal, m_xr[node_list[0]]); + const auto x2 = symmetrize_coordinates(origin, normal, m_xr[node_list[2]]); + const auto x3 = symmetrize_coordinates(origin, normal, m_xr[node_list[4]]); + const auto x4 = symmetrize_coordinates(origin, normal, m_xr[node_list[3]]); + const auto x5 = symmetrize_coordinates(origin, normal, m_xr[node_list[5]]); + + const PrismTransformation T{x0, x1, x2, // + x3, x4, x5}; + + const auto& quadrature = + QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor{m_polynomial_degree + 1}); + + this->_computeEjkMean(quadrature, T, Xj, Vi, mean_of_ejk); + break; + } + case CellType::Pyramid: { + const auto x0 = symmetrize_coordinates(origin, normal, m_xr[node_list[3]]); + const auto x1 = symmetrize_coordinates(origin, normal, m_xr[node_list[2]]); + const auto x2 = symmetrize_coordinates(origin, normal, m_xr[node_list[1]]); + const auto x3 = symmetrize_coordinates(origin, normal, m_xr[node_list[0]]); + const auto x4 = symmetrize_coordinates(origin, normal, m_xr[node_list[4]]); + const PyramidTransformation T{x0, x1, x2, x3, x4}; + + const auto& quadrature = + QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor{m_polynomial_degree + 1}); + + this->_computeEjkMean(quadrature, T, Xj, Vi, mean_of_ejk); + break; + } + case CellType::Hexahedron: { + const auto x0 = symmetrize_coordinates(origin, normal, m_xr[node_list[3]]); + const auto x1 = symmetrize_coordinates(origin, normal, m_xr[node_list[2]]); + const auto x2 = symmetrize_coordinates(origin, normal, m_xr[node_list[1]]); + const auto x3 = symmetrize_coordinates(origin, normal, m_xr[node_list[0]]); + const auto x4 = symmetrize_coordinates(origin, normal, m_xr[node_list[7]]); + const auto x5 = symmetrize_coordinates(origin, normal, m_xr[node_list[6]]); + const auto x6 = symmetrize_coordinates(origin, normal, m_xr[node_list[5]]); + const auto x7 = symmetrize_coordinates(origin, normal, m_xr[node_list[4]]); + + const CubeTransformation T{x0, x1, x2, x3, x4, x5, x6, x7}; + + const auto& quadrature = + QuadratureManager::instance().getCubeFormula(GaussLegendreQuadratureDescriptor{m_polynomial_degree + 1}); + + this->_computeEjkMean(quadrature, T, Xj, Vi, mean_of_ejk); + break; + } + default: { + throw NotImplementedError("unexpected cell type: " + std::string{name(cell_type)}); + } + } + } +} + +template <MeshConcept MeshTypeT> +void +PolynomialReconstruction::ElementIntegralReconstructionMatrixBuilder<MeshTypeT>::build( + const CellId cell_j_id, + ShrinkMatrixView<SmallMatrix<double>>& A) +{ + const auto& stencil_cell_list = m_stencil_array[cell_j_id]; + + const Rd& Xj = m_xj[cell_j_id]; + + this->_computeEjkMean(Xj, cell_j_id, m_mean_j_of_ejk); + + size_t index = 0; + for (size_t i = 0; i < stencil_cell_list.size(); ++i, ++index) { + const CellId cell_i_id = stencil_cell_list[i]; + + this->_computeEjkMean(Xj, cell_i_id, m_mean_i_of_ejk); + + for (size_t l = 0; l < m_basis_dimension - 1; ++l) { + A(index, l) = m_mean_i_of_ejk[l] - m_mean_j_of_ejk[l]; + } + } + + for (size_t i_symmetry = 0; i_symmetry < m_stencil_array.symmetryBoundaryStencilArrayList().size(); ++i_symmetry) { + auto& ghost_stencil = m_stencil_array.symmetryBoundaryStencilArrayList()[i_symmetry].stencilArray(); + auto ghost_cell_list = ghost_stencil[cell_j_id]; + + const Rd& origin = m_symmetry_origin_list[i_symmetry]; + const Rd& normal = m_symmetry_normal_list[i_symmetry]; + + for (size_t i = 0; i < ghost_cell_list.size(); ++i, ++index) { + const CellId cell_i_id = ghost_cell_list[i]; + + this->_computeEjkMeanInSymmetricCell(origin, normal, Xj, cell_i_id, m_mean_i_of_ejk); + + for (size_t l = 0; l < m_basis_dimension - 1; ++l) { + A(index, l) = m_mean_i_of_ejk[l] - m_mean_j_of_ejk[l]; + } + } + } +} + +template <MeshConcept MeshTypeT> +PolynomialReconstruction::ElementIntegralReconstructionMatrixBuilder< + MeshTypeT>::ElementIntegralReconstructionMatrixBuilder(const MeshType& mesh, + const size_t polynomial_degree, + const SmallArray<const Rd>& symmetry_origin_list, + const SmallArray<const Rd>& symmetry_normal_list, + const CellToCellStencilArray& stencil_array) + : m_basis_dimension{DiscreteFunctionDPk<MeshType::Dimension, double>::BasisViewType::dimensionFromDegree( + polynomial_degree)}, + m_polynomial_degree{polynomial_degree}, + + m_wq_detJ_ek{m_basis_dimension}, + m_mean_j_of_ejk{m_basis_dimension - 1}, + m_mean_i_of_ejk{m_basis_dimension - 1}, + + m_cell_to_node_matrix{mesh.connectivity().cellToNodeMatrix()}, + m_stencil_array{stencil_array}, + m_symmetry_origin_list{symmetry_origin_list}, + m_symmetry_normal_list{symmetry_normal_list}, + m_cell_type{mesh.connectivity().cellType()}, + m_Vj{MeshDataManager::instance().getMeshData(mesh).Vj()}, + m_xj{MeshDataManager::instance().getMeshData(mesh).xj()}, + m_xr{mesh.xr()} +{ + if constexpr (MeshType::Dimension == 2) { + SmallArray<size_t> y_row_index(m_polynomial_degree + 1); + + size_t i_y = 0; + + y_row_index[i_y++] = 0; + for (ssize_t n = m_polynomial_degree + 1; n > 1; --n, ++i_y) { + y_row_index[i_y] = y_row_index[i_y - 1] + n; + } + + m_y_row_index = y_row_index; + + } else if constexpr (MeshType::Dimension == 3) { + SmallArray<size_t> yz_row_index((m_polynomial_degree + 2) * (m_polynomial_degree + 1) / 2 + 1); + SmallArray<size_t> z_triangle_index(m_polynomial_degree + 1); + + { + size_t i_z = 0; + size_t i_yz = 0; + + yz_row_index[i_yz++] = 0; + for (ssize_t n = m_polynomial_degree + 1; n >= 1; --n) { + z_triangle_index[i_z++] = i_yz - 1; + for (ssize_t i = n; i >= 1; --i) { + yz_row_index[i_yz] = yz_row_index[i_yz - 1] + i; + ++i_yz; + } + } + } + + SmallArray<size_t> yz_row_size{yz_row_index.size() - 1}; + for (size_t i = 0; i < yz_row_size.size(); ++i) { + yz_row_size[i] = yz_row_index[i + 1] - yz_row_index[i]; + } + + m_yz_row_index = yz_row_index; + m_z_triangle_index = z_triangle_index; + m_yz_row_size = yz_row_size; + } +} + +template void PolynomialReconstruction::ElementIntegralReconstructionMatrixBuilder<Mesh<1>>::build( + const CellId, + ShrinkMatrixView<SmallMatrix<double>>&); + +template void PolynomialReconstruction::ElementIntegralReconstructionMatrixBuilder<Mesh<2>>::build( + const CellId, + ShrinkMatrixView<SmallMatrix<double>>&); + +template void PolynomialReconstruction::ElementIntegralReconstructionMatrixBuilder<Mesh<3>>::build( + const CellId, + ShrinkMatrixView<SmallMatrix<double>>&); + +template PolynomialReconstruction::ElementIntegralReconstructionMatrixBuilder< + Mesh<1>>::ElementIntegralReconstructionMatrixBuilder(const MeshType&, + const size_t, + const SmallArray<const Rd>&, + const SmallArray<const Rd>&, + const CellToCellStencilArray&); + +template PolynomialReconstruction::ElementIntegralReconstructionMatrixBuilder< + Mesh<2>>::ElementIntegralReconstructionMatrixBuilder(const MeshType&, + const size_t, + const SmallArray<const Rd>&, + const SmallArray<const Rd>&, + const CellToCellStencilArray&); + +template PolynomialReconstruction::ElementIntegralReconstructionMatrixBuilder< + Mesh<3>>::ElementIntegralReconstructionMatrixBuilder(const MeshType&, + const size_t, + const SmallArray<const Rd>&, + const SmallArray<const Rd>&, + const CellToCellStencilArray&); diff --git a/src/scheme/reconstruction_utils/ElementIntegralReconstructionMatrixBuilder.hpp b/src/scheme/reconstruction_utils/ElementIntegralReconstructionMatrixBuilder.hpp new file mode 100644 index 0000000000000000000000000000000000000000..7a3e8f7fc04d6d4ba21d4a36ed02ba24dd0d67d0 --- /dev/null +++ b/src/scheme/reconstruction_utils/ElementIntegralReconstructionMatrixBuilder.hpp @@ -0,0 +1,88 @@ +#ifndef ELEMENT_INTEGRAL_RECONSTRUCTION_MATRIX_BUILDER_HPP +#define ELEMENT_INTEGRAL_RECONSTRUCTION_MATRIX_BUILDER_HPP + +#include <algebra/ShrinkMatrixView.hpp> +#include <algebra/SmallMatrix.hpp> +#include <mesh/CellType.hpp> +#include <mesh/ItemValue.hpp> +#include <mesh/StencilArray.hpp> +#include <scheme/PolynomialReconstruction.hpp> +#include <utils/SmallArray.hpp> + +template <size_t Dimension> +class QuadratureFormula; + +template <MeshConcept MeshTypeT> +class PolynomialReconstruction::ElementIntegralReconstructionMatrixBuilder +{ + public: + using MeshType = MeshTypeT; + + constexpr static bool handles_high_degrees = true; + + private: + using Rd = TinyVector<MeshType::Dimension>; + + const size_t m_basis_dimension; + const size_t m_polynomial_degree; + + const SmallArray<double> m_wq_detJ_ek; + SmallArray<double> m_mean_j_of_ejk; + SmallArray<double> m_mean_i_of_ejk; + + const ItemToItemMatrix<ItemType::cell, ItemType::node> m_cell_to_node_matrix; + const CellToCellStencilArray& m_stencil_array; + + const SmallArray<const Rd> m_symmetry_origin_list; + const SmallArray<const Rd> m_symmetry_normal_list; + + const CellValue<const CellType> m_cell_type; + const CellValue<const double> m_Vj; + const CellValue<const Rd> m_xj; + const NodeValue<const Rd> m_xr; + + // 2D + SmallArray<const size_t> m_y_row_index; + + // 3D + SmallArray<const size_t> m_yz_row_index; + SmallArray<const size_t> m_z_triangle_index; + SmallArray<const size_t> m_yz_row_size; + + template <typename ConformTransformationT> + void _computeEjkMean(const QuadratureFormula<MeshType::Dimension>& quadrature, + const ConformTransformationT& T, + const Rd& Xj, + const double Vi, + SmallArray<double>& mean_of_ejk) noexcept(NO_ASSERT); + + void _computeEjkMean(const TinyVector<MeshType::Dimension>& Xj, + const CellId& cell_i_id, + SmallArray<double>& mean_of_ejk); + + void _computeEjkMeanInSymmetricCell(const Rd& origin, + const Rd& normal, + const Rd& Xj, + const CellId& cell_i_id, + SmallArray<double>& mean_of_ejk); + + public: + PUGS_INLINE + SmallArray<const double> + meanjOfEjk() const + { + return m_mean_j_of_ejk; + } + + void build(const CellId cell_j_id, ShrinkMatrixView<SmallMatrix<double>>& A); + + ElementIntegralReconstructionMatrixBuilder(const MeshType& mesh, + const size_t polynomial_degree, + const SmallArray<const Rd>& symmetry_origin_list, + const SmallArray<const Rd>& symmetry_normal_list, + const CellToCellStencilArray& stencil_array); + + ~ElementIntegralReconstructionMatrixBuilder() = default; +}; + +#endif // ELEMENT_INTEGRAL_RECONSTRUCTION_MATRIX_BUILDER_HPP diff --git a/src/scheme/reconstruction_utils/MutableDiscreteFunctionDPkVariant.hpp b/src/scheme/reconstruction_utils/MutableDiscreteFunctionDPkVariant.hpp new file mode 100644 index 0000000000000000000000000000000000000000..761f3c1129b00609b1be9a23b5d256db4b48c084 --- /dev/null +++ b/src/scheme/reconstruction_utils/MutableDiscreteFunctionDPkVariant.hpp @@ -0,0 +1,130 @@ +#ifndef MUTABLE_DISCRETE_FUNCTION_D_PK_VARIANT_HPP +#define MUTABLE_DISCRETE_FUNCTION_D_PK_VARIANT_HPP + +#include <scheme/DiscreteFunctionDPk.hpp> +#include <scheme/PolynomialReconstruction.hpp> + +#include <variant> + +class PolynomialReconstruction::MutableDiscreteFunctionDPkVariant +{ + public: + using Variant = std::variant<DiscreteFunctionDPk<1, double>, + DiscreteFunctionDPk<1, TinyVector<1>>, + DiscreteFunctionDPk<1, TinyVector<2>>, + DiscreteFunctionDPk<1, TinyVector<3>>, + DiscreteFunctionDPk<1, TinyMatrix<1>>, + DiscreteFunctionDPk<1, TinyMatrix<2>>, + DiscreteFunctionDPk<1, TinyMatrix<3>>, + + DiscreteFunctionDPk<2, double>, + DiscreteFunctionDPk<2, TinyVector<1>>, + DiscreteFunctionDPk<2, TinyVector<2>>, + DiscreteFunctionDPk<2, TinyVector<3>>, + DiscreteFunctionDPk<2, TinyMatrix<1>>, + DiscreteFunctionDPk<2, TinyMatrix<2>>, + DiscreteFunctionDPk<2, TinyMatrix<3>>, + + DiscreteFunctionDPk<3, double>, + DiscreteFunctionDPk<3, TinyVector<1>>, + DiscreteFunctionDPk<3, TinyVector<2>>, + DiscreteFunctionDPk<3, TinyVector<3>>, + DiscreteFunctionDPk<3, TinyMatrix<1>>, + DiscreteFunctionDPk<3, TinyMatrix<2>>, + DiscreteFunctionDPk<3, TinyMatrix<3>>, + + DiscreteFunctionDPkVector<1, double>, + DiscreteFunctionDPkVector<1, TinyVector<1>>, + DiscreteFunctionDPkVector<1, TinyVector<2>>, + DiscreteFunctionDPkVector<1, TinyVector<3>>, + DiscreteFunctionDPkVector<1, TinyMatrix<1>>, + DiscreteFunctionDPkVector<1, TinyMatrix<2>>, + DiscreteFunctionDPkVector<1, TinyMatrix<3>>, + + DiscreteFunctionDPkVector<2, double>, + DiscreteFunctionDPkVector<2, TinyVector<1>>, + DiscreteFunctionDPkVector<2, TinyVector<2>>, + DiscreteFunctionDPkVector<2, TinyVector<3>>, + DiscreteFunctionDPkVector<2, TinyMatrix<1>>, + DiscreteFunctionDPkVector<2, TinyMatrix<2>>, + DiscreteFunctionDPkVector<2, TinyMatrix<3>>, + + DiscreteFunctionDPkVector<3, double>, + DiscreteFunctionDPkVector<3, TinyVector<1>>, + DiscreteFunctionDPkVector<3, TinyVector<2>>, + DiscreteFunctionDPkVector<3, TinyVector<3>>, + DiscreteFunctionDPkVector<3, TinyMatrix<1>>, + DiscreteFunctionDPkVector<3, TinyMatrix<2>>, + DiscreteFunctionDPkVector<3, TinyMatrix<3>>>; + + private: + Variant m_mutable_discrete_function_dpk; + + public: + PUGS_INLINE + const Variant& + mutableDiscreteFunctionDPk() const + { + return m_mutable_discrete_function_dpk; + } + + template <typename DiscreteFunctionDPkT> + PUGS_INLINE auto&& + get() const + { + static_assert(is_discrete_function_dpk_v<DiscreteFunctionDPkT>, "invalid template argument"); +#ifndef NDEBUG + if (not std::holds_alternative<DiscreteFunctionDPkT>(this->m_mutable_discrete_function_dpk)) { + std::ostringstream error_msg; + error_msg << "invalid discrete function type\n"; + error_msg << "- required " << rang::fgB::red << demangle<DiscreteFunctionDPkT>() << rang::fg::reset << '\n'; + error_msg << "- contains " << rang::fgB::yellow + << std::visit([](auto&& f) -> std::string { return demangle<decltype(f)>(); }, + this->m_mutable_discrete_function_dpk) + << rang::fg::reset; + throw NormalError(error_msg.str()); + } +#endif // NDEBUG + + return std::get<DiscreteFunctionDPkT>(this->mutableDiscreteFunctionDPk()); + } + + template <size_t Dimension, typename DataType> + MutableDiscreteFunctionDPkVariant(const DiscreteFunctionDPk<Dimension, DataType>& discrete_function_dpk) + : m_mutable_discrete_function_dpk{discrete_function_dpk} + { + static_assert(std::is_same_v<DataType, double> or // + std::is_same_v<DataType, TinyVector<1, double>> or // + std::is_same_v<DataType, TinyVector<2, double>> or // + std::is_same_v<DataType, TinyVector<3, double>> or // + std::is_same_v<DataType, TinyMatrix<1, 1, double>> or // + std::is_same_v<DataType, TinyMatrix<2, 2, double>> or // + std::is_same_v<DataType, TinyMatrix<3, 3, double>>, + "DiscreteFunctionDPk with this DataType is not allowed in variant"); + } + + template <size_t Dimension, typename DataType> + MutableDiscreteFunctionDPkVariant(const DiscreteFunctionDPkVector<Dimension, DataType>& discrete_function_dpk) + : m_mutable_discrete_function_dpk{discrete_function_dpk} + { + static_assert(std::is_same_v<DataType, double> or // + std::is_same_v<DataType, TinyVector<1, double>> or // + std::is_same_v<DataType, TinyVector<2, double>> or // + std::is_same_v<DataType, TinyVector<3, double>> or // + std::is_same_v<DataType, TinyMatrix<1, 1, double>> or // + std::is_same_v<DataType, TinyMatrix<2, 2, double>> or // + std::is_same_v<DataType, TinyMatrix<3, 3, double>>, + "DiscreteFunctionDPkVector with this DataType is not allowed in variant"); + } + + MutableDiscreteFunctionDPkVariant& operator=(MutableDiscreteFunctionDPkVariant&&) = default; + MutableDiscreteFunctionDPkVariant& operator=(const MutableDiscreteFunctionDPkVariant&) = default; + + MutableDiscreteFunctionDPkVariant(const MutableDiscreteFunctionDPkVariant&) = default; + MutableDiscreteFunctionDPkVariant(MutableDiscreteFunctionDPkVariant&&) = default; + + MutableDiscreteFunctionDPkVariant() = delete; + ~MutableDiscreteFunctionDPkVariant() = default; +}; + +#endif // MUTABLE_DISCRETE_FUNCTION_D_PK_VARIANT_HPP diff --git a/src/utils/Array.hpp b/src/utils/Array.hpp index a08453ba766450909f3bcdf0f0fd678bdb06456c..94c6bcce4a1ee72869e9b666c45dce5eaf615638 100644 --- a/src/utils/Array.hpp +++ b/src/utils/Array.hpp @@ -63,9 +63,9 @@ class [[nodiscard]] Array UnsafeArrayView& operator=(UnsafeArrayView&&) = delete; UnsafeArrayView(const Array<DataType>& array, index_type begin, index_type size) - : m_values{&array[begin]}, m_size{size} + : m_values{(size == 0) ? nullptr : &array[begin]}, m_size{size} { - Assert((begin < array.size()) and (begin + size <= array.size()), "required view is not contained in the Array"); + Assert((size == 0) or (begin + size <= array.size()), "required view is not contained in the Array"); } // To try to keep these views close to the initial array one diff --git a/src/utils/GlobalVariableManager.hpp b/src/utils/GlobalVariableManager.hpp index 0768a2ab2beb28a9060cadd44ee705f3aba598e2..cdc007677f44bbe64ab7f6a65a9fd5de7d0898e2 100644 --- a/src/utils/GlobalVariableManager.hpp +++ b/src/utils/GlobalVariableManager.hpp @@ -1,15 +1,23 @@ #ifndef GLOBAL_VARIABLE_MANAGER_HPP #define GLOBAL_VARIABLE_MANAGER_HPP +#include <utils/Exceptions.hpp> #include <utils/PugsAssert.hpp> #include <utils/PugsMacros.hpp> +#include <optional> + class GlobalVariableManager { private: + // Give some special access for testing + friend class NbGhostLayersTester; + size_t m_connectivity_id = 0; size_t m_mesh_id = 0; + std::optional<size_t> m_number_of_ghost_layers; + static GlobalVariableManager* m_instance; explicit GlobalVariableManager() = default; @@ -53,6 +61,24 @@ class GlobalVariableManager return m_mesh_id++; } + PUGS_INLINE + void + setNumberOfGhostLayers(const size_t number) + { + if (m_number_of_ghost_layers.has_value()) { + throw UnexpectedError("changing number of ghost layers is forbidden"); + } + m_number_of_ghost_layers = number; + } + + PUGS_INLINE + size_t + getNumberOfGhostLayers() + { + Assert(m_number_of_ghost_layers.has_value()); + return m_number_of_ghost_layers.value(); + } + PUGS_INLINE void setMeshId(size_t mesh_id) diff --git a/src/utils/PugsTraits.hpp b/src/utils/PugsTraits.hpp index 3c7a4e40a217d957dea388223d99574e6b1ecf74..e0008a7a4a0ecf30998e8a739bbf91511d73e3d4 100644 --- a/src/utils/PugsTraits.hpp +++ b/src/utils/PugsTraits.hpp @@ -32,6 +32,12 @@ class DiscreteFunctionP0; template <typename DataType> class DiscreteFunctionP0Vector; +template <size_t Dimension, typename DataType, typename BasisView> +class DiscreteFunctionDPk; + +template <size_t Dimension, typename DataType, typename BasisView> +class DiscreteFunctionDPkVector; + // Traits is_trivially_castable template <typename T> @@ -112,6 +118,12 @@ inline constexpr bool is_tiny_vector_v = false; template <size_t N, typename T> inline constexpr bool is_tiny_vector_v<TinyVector<N, T>> = true; +template <typename T> +inline constexpr bool is_tiny_vector_v<const T> = is_tiny_vector_v<std::remove_cvref_t<T>>; + +template <typename T> +inline constexpr bool is_tiny_vector_v<T&> = is_tiny_vector_v<std::remove_cvref_t<T>>; + // Traits is_tiny_matrix template <typename T> @@ -120,6 +132,12 @@ inline constexpr bool is_tiny_matrix_v = false; template <size_t M, size_t N, typename T> inline constexpr bool is_tiny_matrix_v<TinyMatrix<M, N, T>> = true; +template <typename T> +inline constexpr bool is_tiny_matrix_v<const T> = is_tiny_matrix_v<std::remove_cvref_t<T>>; + +template <typename T> +inline constexpr bool is_tiny_matrix_v<T&> = is_tiny_matrix_v<std::remove_cvref_t<T>>; + // Traits is_small_matrix template <typename T> @@ -128,6 +146,12 @@ inline constexpr bool is_small_matrix_v = false; template <typename DataType> inline constexpr bool is_small_matrix_v<SmallMatrix<DataType>> = true; +template <typename T> +inline constexpr bool is_small_matrix_v<const T> = is_small_matrix_v<std::remove_cvref_t<T>>; + +template <typename T> +inline constexpr bool is_small_matrix_v<T&> = is_small_matrix_v<std::remove_cvref_t<T>>; + // Traits is_crs_matrix template <typename T> @@ -136,6 +160,12 @@ inline constexpr bool is_crs_matrix_v = false; template <typename DataType, typename IndexType> inline constexpr bool is_crs_matrix_v<CRSMatrix<DataType, IndexType>> = true; +template <typename T> +inline constexpr bool is_crs_matrix_v<const T> = is_crs_matrix_v<std::remove_cvref_t<T>>; + +template <typename T> +inline constexpr bool is_crs_matrix_v<T&> = is_crs_matrix_v<std::remove_cvref_t<T>>; + // Trais is ItemValue template <typename T> @@ -173,6 +203,29 @@ constexpr inline bool is_discrete_function_P0_vector_v<DiscreteFunctionP0Vector< template <typename T> constexpr inline bool is_discrete_function_v = is_discrete_function_P0_v<T> or is_discrete_function_P0_vector_v<T>; +// Trais is DiscreteFunctionDPk + +template <typename T> +constexpr inline bool is_discrete_function_dpk_scalar_v = false; + +template <size_t Dimension, typename DataType, typename BasisView> +constexpr inline bool is_discrete_function_dpk_scalar_v<DiscreteFunctionDPk<Dimension, DataType, BasisView>> = true; + +// Trais is DiscreteFunctionDPkVector + +template <typename T> +constexpr inline bool is_discrete_function_dpk_vector_v = false; + +template <size_t Dimension, typename DataType, typename BasisView> +constexpr inline bool is_discrete_function_dpk_vector_v<DiscreteFunctionDPkVector<Dimension, DataType, BasisView>> = + true; + +// Trais is DiscreteFunction + +template <typename T> +constexpr inline bool is_discrete_function_dpk_v = + is_discrete_function_dpk_scalar_v<T> or is_discrete_function_dpk_vector_v<T>; + // helper to check if a type is part of a variant template <typename T, typename V> diff --git a/src/utils/PugsUtils.cpp b/src/utils/PugsUtils.cpp index 356869699a7d76f04648dce9a95d4d091a619df2..5766160347d055a59017cf6ccb9cec43bcecd628 100644 --- a/src/utils/PugsUtils.cpp +++ b/src/utils/PugsUtils.cpp @@ -134,6 +134,10 @@ initialize(int& argc, char* argv[]) bool pause_on_error = false; app.add_flag("-p,--pause-on-error", pause_on_error, "Pause for debugging on unexpected error [default: false]"); + int nb_ghost_layers = 1; + app.add_option("--number-of-ghost-layers", nb_ghost_layers, "Number of ghost layers of cells [default: 1]") + ->check(CLI::Range(0, std::numeric_limits<decltype(nb_ghost_layers)>::max())); + bool reproducible_sums = true; app.add_flag("--reproducible-sums,!--no-reproducible-sums", reproducible_sums, "Special treatment of array sums to ensure reproducibility [default: true]"); @@ -173,6 +177,8 @@ initialize(int& argc, char* argv[]) CommunicatorManager::setSplitColor(mpi_split_color); } + GlobalVariableManager::instance().setNumberOfGhostLayers(nb_ghost_layers); + ResumingManager::getInstance().setIsResuming(is_resuming); if (is_resuming) { ResumingManager::getInstance().setFilename(filename); diff --git a/src/utils/SignalManager.cpp b/src/utils/SignalManager.cpp index 4b2046a98796fc72cef406f77e7498930089f533..5054622c0d9959c089ddfa3f3a323820614c5806 100644 --- a/src/utils/SignalManager.cpp +++ b/src/utils/SignalManager.cpp @@ -52,17 +52,27 @@ SignalManager::signalName(int signal) void SignalManager::pauseForDebug(int signal) { - if (std::string(PUGS_BUILD_TYPE) != "Release") { - if (s_pause_on_error) { - // Each failing process must write - std::cerr.clear(); + if (s_pause_on_error) { + // Each failing process must write + std::cerr.clear(); + if (std::string(PUGS_BUILD_TYPE) != "Release") { + char hostname[HOST_NAME_MAX + 1]; + gethostname(hostname, HOST_NAME_MAX + 1); std::cerr << "\n======================================\n" - << rang::style::reset << rang::fg::reset << rang::style::bold << "to attach gdb to this process run\n" + << rang::style::reset << rang::fg::reset << rang::style::bold << "Process paused on host \"" + << rang::fg::yellow << hostname << rang::fg::reset << "\"\n" + << "to attach gdb to this process run\n" << "\tgdb -pid " << rang::fg::red << getpid() << rang::fg::reset << '\n' << "else press Control-C to exit\n" << rang::style::reset << "======================================\n" << std::flush; pause(); + } else { + std::cerr << '\n' + << rang::style::bold + << "Pausing is useless for Release version.\n" + "To attach debugger use Debug built type." + << rang::style::reset << '\n'; } } std::exit(signal); @@ -73,56 +83,52 @@ SignalManager::handler(int signal) { static std::mutex mutex; - if (mutex.try_lock()) { - std::signal(SIGTERM, SIG_DFL); - std::signal(SIGINT, SIG_DFL); - std::signal(SIGABRT, SIG_DFL); - - // Each failing process must write - std::cerr.clear(); + std::lock_guard<std::mutex> lock(mutex); - std::cerr << BacktraceManager{} << '\n'; + std::signal(SIGINT, SIG_BLOCK); - std::cerr << "\n *** " << rang::style::reset << rang::fg::reset << rang::style::bold << "Signal " << rang::fgB::red - << signalName(signal) << rang::fg::reset << " caught" << rang::style::reset << " ***\n\n"; + // Each failing process must write + std::cerr.clear(); - std::exception_ptr eptr = std::current_exception(); - try { - if (eptr) { - std::rethrow_exception(eptr); - } else { - std::ostringstream error_msg; - error_msg << "received " << signalName(signal); - std::cerr << ASTExecutionStack::getInstance().errorMessageAt(error_msg.str()) << '\n'; - } - } - catch (const IBacktraceError& backtrace_error) { - auto source_location = backtrace_error.sourceLocation(); - std::cerr << rang::fgB::cyan << source_location.file_name() << ':' << source_location.line() << ':' - << source_location.column() << ':' << rang::fg::reset << rang::fgB::yellow - << " threw the following exception" << rang::fg::reset << "\n\n"; - std::cerr << ASTExecutionStack::getInstance().errorMessageAt(backtrace_error.what()) << '\n'; - } - catch (const ParseError& parse_error) { - auto p = parse_error.positions().front(); - std::cerr << rang::style::bold << p.source << ':' << p.line << ':' << p.column << ':' << rang::style::reset - << rang::fgB::red << " error: " << rang::fg::reset << rang::style::bold << parse_error.what() - << rang::style::reset << '\n'; - } - catch (const IExitError& exit_error) { - std::cerr << ASTExecutionStack::getInstance().errorMessageAt(exit_error.what()) << '\n'; - } - catch (const AssertError& assert_error) { - std::cerr << assert_error << '\n'; - } - catch (...) { - std::cerr << "Unknown exception!\n"; - } + std::cerr << BacktraceManager{} << '\n'; - SignalManager::pauseForDebug(signal); + std::cerr << "\n *** " << rang::style::reset << rang::fg::reset << rang::style::bold << "Signal " << rang::fgB::red + << signalName(signal) << rang::fg::reset << " caught" << rang::style::reset << " ***\n\n"; - mutex.unlock(); + std::exception_ptr eptr = std::current_exception(); + try { + if (eptr) { + std::rethrow_exception(eptr); + } else { + std::ostringstream error_msg; + error_msg << "received " << signalName(signal); + std::cerr << ASTExecutionStack::getInstance().errorMessageAt(error_msg.str()) << '\n'; + } + } + catch (const IBacktraceError& backtrace_error) { + auto source_location = backtrace_error.sourceLocation(); + std::cerr << rang::fgB::cyan << source_location.file_name() << ':' << source_location.line() << ':' + << source_location.column() << ':' << rang::fg::reset << rang::fgB::yellow + << " threw the following exception" << rang::fg::reset << "\n\n"; + std::cerr << ASTExecutionStack::getInstance().errorMessageAt(backtrace_error.what()) << '\n'; } + catch (const ParseError& parse_error) { + auto p = parse_error.positions().front(); + std::cerr << rang::style::bold << p.source << ':' << p.line << ':' << p.column << ':' << rang::style::reset + << rang::fgB::red << " error: " << rang::fg::reset << rang::style::bold << parse_error.what() + << rang::style::reset << '\n'; + } + catch (const IExitError& exit_error) { + std::cerr << ASTExecutionStack::getInstance().errorMessageAt(exit_error.what()) << '\n'; + } + catch (const AssertError& assert_error) { + std::cerr << assert_error << '\n'; + } + catch (...) { + std::cerr << "Unknown exception!\n"; + } + + SignalManager::pauseForDebug(signal); } void diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 169d375538eae053ff85a70919e11c24620d8c8a..8c05b9ed0ae319b803f053dda1bda8246c9339be 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -122,6 +122,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 @@ -155,6 +156,7 @@ add_executable (unit_tests test_PugsUtils.cpp test_PyramidGaussQuadrature.cpp test_PyramidTransformation.cpp + test_QuadraticPolynomialReconstruction.cpp test_QuadratureType.cpp test_RefId.cpp test_RefItemList.cpp @@ -229,6 +231,9 @@ endif(PUGS_HAS_HDF5) add_executable (mpi_unit_tests mpi_test_main.cpp test_Connectivity.cpp + test_ConnectivityDispatcher.cpp + test_DiscreteFunctionDPk.cpp + test_DiscreteFunctionDPkVector.cpp test_DiscreteFunctionIntegrator.cpp test_DiscreteFunctionIntegratorByZone.cpp test_DiscreteFunctionInterpoler.cpp @@ -274,7 +279,13 @@ add_executable (mpi_unit_tests test_OFStream.cpp test_ParallelChecker_read.cpp test_Partitioner.cpp + test_PolynomialReconstruction_degree_1.cpp + test_PolynomialReconstruction_degree_2.cpp + test_PolynomialReconstruction_degree_3.cpp + test_PolynomialReconstructionDescriptor.cpp test_RandomEngine.cpp + test_StencilBuilder_cell2cell.cpp + test_StencilBuilder_node2cell.cpp test_SubItemArrayPerItemVariant.cpp test_SubItemValuePerItem.cpp test_SubItemValuePerItemVariant.cpp @@ -306,6 +317,7 @@ target_link_libraries (unit_tests PugsAlgebra PugsAnalysis PugsScheme + PugsSchemeReconstructionUtils PugsOutput PugsUtils PugsCheckpointing @@ -336,6 +348,7 @@ target_link_libraries (mpi_unit_tests PugsUtils PugsLanguageUtils PugsScheme + PugsSchemeReconstructionUtils PugsOutput PugsUtils PugsCheckpointing diff --git a/tests/DiscreteFunctionDPkForTests.hpp b/tests/DiscreteFunctionDPkForTests.hpp new file mode 100644 index 0000000000000000000000000000000000000000..5b51861d811b2d8a35c91375bc9e8dd368e2b281 --- /dev/null +++ b/tests/DiscreteFunctionDPkForTests.hpp @@ -0,0 +1,365 @@ +#ifndef DISCRETE_FUNCTION_DPK_FOR_TESTS_HPP +#define DISCRETE_FUNCTION_DPK_FOR_TESTS_HPP + +#include <analysis/GaussQuadratureDescriptor.hpp> +#include <analysis/QuadratureFormula.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/Mesh.hpp> +#include <mesh/MeshDataManager.hpp> +#include <type_traits> + +namespace test_only +{ + +template <MeshConcept MeshType, typename DataType> +DiscreteFunctionP0<std::remove_const_t<DataType>> +exact_projection(const MeshType& mesh, + size_t degree, + std::function<DataType(const TinyVector<MeshType::Dimension>&)> exact_function) +{ + DiscreteFunctionP0<std::remove_const_t<DataType>> P0_function{mesh.meshVariant()}; + + auto Vj = MeshDataManager::instance().getMeshData(mesh).Vj(); + + auto xr = mesh.xr(); + auto cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix(); + auto cell_type = mesh.connectivity().cellType(); + + auto sum = [&exact_function, &Vj](const CellId cell_id, const auto& T, + const auto& qf) -> std::remove_const_t<DataType> { + std::remove_const_t<DataType> integral = + (qf.weight(0) * T.jacobianDeterminant(qf.point(0))) * exact_function(T(qf.point(0))); + for (size_t i_quadrarture = 1; i_quadrarture < qf.numberOfPoints(); ++i_quadrarture) { + integral += (qf.weight(i_quadrarture) * T.jacobianDeterminant(qf.point(i_quadrarture))) * + exact_function(T(qf.point(i_quadrarture))); + } + return 1. / Vj[cell_id] * integral; + }; + + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + auto cell_nodes = cell_to_node_matrix[cell_id]; + if constexpr (MeshType::Dimension == 1) { + LineTransformation<MeshType::Dimension> T{xr[cell_nodes[0]], xr[cell_nodes[1]]}; + auto qf = QuadratureManager::instance().getLineFormula(GaussQuadratureDescriptor{degree + 1}); + P0_function[cell_id] = sum(cell_id, T, qf); + } else if constexpr (MeshType::Dimension == 2) { + switch (cell_type[cell_id]) { + case CellType::Triangle: { + TriangleTransformation<MeshType::Dimension> T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]]}; + auto qf = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor{degree + 2}); + P0_function[cell_id] = sum(cell_id, T, qf); + break; + } + case CellType::Quadrangle: { + SquareTransformation<MeshType::Dimension> T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]], + xr[cell_nodes[3]]}; + auto qf = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor{degree + 2}); + P0_function[cell_id] = sum(cell_id, T, qf); + break; + } + default: { + throw UnexpectedError("unexpected cell type"); + } + } + } else if constexpr (MeshType::Dimension == 3) { + switch (cell_type[cell_id]) { + case CellType::Tetrahedron: { + TetrahedronTransformation T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]], xr[cell_nodes[3]]}; + auto qf = QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor{degree + 3}); + P0_function[cell_id] = sum(cell_id, T, qf); + break; + } + case CellType::Pyramid: { + PyramidTransformation T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]], xr[cell_nodes[3]], + xr[cell_nodes[4]]}; + auto qf = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor{degree + 3}); + P0_function[cell_id] = sum(cell_id, T, qf); + break; + } + case CellType::Prism: { + PrismTransformation T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]], + xr[cell_nodes[3]], xr[cell_nodes[4]], xr[cell_nodes[5]]}; + auto qf = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor{degree + 3}); + P0_function[cell_id] = sum(cell_id, T, qf); + break; + } + case CellType::Hexahedron: { + CubeTransformation T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]], xr[cell_nodes[3]], + xr[cell_nodes[4]], xr[cell_nodes[5]], xr[cell_nodes[6]], xr[cell_nodes[7]]}; + auto qf = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor{degree + 3}); + P0_function[cell_id] = sum(cell_id, T, qf); + break; + } + default: { + throw UnexpectedError("unexpected cell type"); + } + } + } else { + throw UnexpectedError("invalid mesh dimension"); + } + } + + return P0_function; +} + +template <MeshConcept MeshType, typename DataType, size_t NbComponents> +DiscreteFunctionP0Vector<std::remove_const_t<DataType>> +exact_projection( + const MeshType& mesh, + size_t degree, + const std::array<std::function<DataType(const TinyVector<MeshType::Dimension>&)>, NbComponents>& vector_exact) +{ + DiscreteFunctionP0Vector<std::remove_const_t<DataType>> P0_function_vector{mesh.meshVariant(), vector_exact.size()}; + + for (size_t i_component = 0; i_component < vector_exact.size(); ++i_component) { + auto exact_function = vector_exact[i_component]; + + DiscreteFunctionP0 P0_function = exact_projection(mesh, degree, vector_exact[i_component]); + + parallel_for( + mesh.numberOfCells(), + PUGS_LAMBDA(const CellId cell_id) { P0_function_vector[cell_id][i_component] = P0_function[cell_id]; }); + } + + return P0_function_vector; +} + +template <typename DataType> +PUGS_INLINE double +get_max_error(const DataType& x, const DataType& y) +{ + if constexpr (is_tiny_matrix_v<DataType>) { + return frobeniusNorm(x - y); + } else if constexpr (is_tiny_vector_v<DataType>) { + return l2Norm(x - y); + } else { + static_assert(std::is_arithmetic_v<DataType>, "expecting arithmetic type"); + return std::abs(x - y); + } +} + +template <MeshConcept MeshType, typename DataType> +double +max_reconstruction_error(const MeshType& mesh, + DiscreteFunctionDPk<MeshType::Dimension, const DataType> dpk_f, + std::function<DataType(const TinyVector<MeshType::Dimension>&)> exact) +{ + auto xr = mesh.xr(); + auto cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix(); + auto cell_type = mesh.connectivity().cellType(); + + double max_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + auto cell_nodes = cell_to_node_matrix[cell_id]; + if constexpr (MeshType::Dimension == 1) { + Assert(cell_type[cell_id] == CellType::Line); + LineTransformation<MeshType::Dimension> T{xr[cell_nodes[0]], xr[cell_nodes[1]]}; + auto qf = QuadratureManager::instance().getLineFormula(GaussQuadratureDescriptor{dpk_f.degree() + 1}); + for (size_t i_quadrarture = 0; i_quadrarture < qf.numberOfPoints(); ++i_quadrarture) { + auto x = T(qf.point(i_quadrarture)); + max_error = std::max(max_error, get_max_error(dpk_f[cell_id](x), exact(x))); + } + } else if constexpr (MeshType::Dimension == 2) { + switch (cell_type[cell_id]) { + case CellType::Triangle: { + TriangleTransformation<MeshType::Dimension> T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]]}; + auto qf = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor{dpk_f.degree() + 1}); + for (size_t i_quadrarture = 0; i_quadrarture < qf.numberOfPoints(); ++i_quadrarture) { + auto x = T(qf.point(i_quadrarture)); + max_error = std::max(max_error, get_max_error(dpk_f[cell_id](x), exact(x))); + } + break; + } + case CellType::Quadrangle: { + SquareTransformation<MeshType::Dimension> T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]], + xr[cell_nodes[3]]}; + auto qf = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor{dpk_f.degree() + 1}); + for (size_t i_quadrarture = 0; i_quadrarture < qf.numberOfPoints(); ++i_quadrarture) { + auto x = T(qf.point(i_quadrarture)); + max_error = std::max(max_error, get_max_error(dpk_f[cell_id](x), exact(x))); + } + break; + } + default: { + throw UnexpectedError("unexpected cell type"); + } + } + } else if constexpr (MeshType::Dimension == 3) { + switch (cell_type[cell_id]) { + case CellType::Tetrahedron: { + TetrahedronTransformation T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]], xr[cell_nodes[3]]}; + auto qf = QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor{dpk_f.degree() + 1}); + for (size_t i_quadrarture = 0; i_quadrarture < qf.numberOfPoints(); ++i_quadrarture) { + auto x = T(qf.point(i_quadrarture)); + max_error = std::max(max_error, get_max_error(dpk_f[cell_id](x), exact(x))); + } + break; + } + case CellType::Pyramid: { + PyramidTransformation T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]], xr[cell_nodes[3]], + xr[cell_nodes[4]]}; + auto qf = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor{dpk_f.degree() + 1}); + for (size_t i_quadrarture = 0; i_quadrarture < qf.numberOfPoints(); ++i_quadrarture) { + auto x = T(qf.point(i_quadrarture)); + max_error = std::max(max_error, get_max_error(dpk_f[cell_id](x), exact(x))); + } + break; + } + case CellType::Prism: { + PrismTransformation T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]], + xr[cell_nodes[3]], xr[cell_nodes[4]], xr[cell_nodes[5]]}; + auto qf = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor{dpk_f.degree() + 1}); + for (size_t i_quadrarture = 0; i_quadrarture < qf.numberOfPoints(); ++i_quadrarture) { + auto x = T(qf.point(i_quadrarture)); + max_error = std::max(max_error, get_max_error(dpk_f[cell_id](x), exact(x))); + } + break; + } + case CellType::Hexahedron: { + CubeTransformation T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]], xr[cell_nodes[3]], + xr[cell_nodes[4]], xr[cell_nodes[5]], xr[cell_nodes[6]], xr[cell_nodes[7]]}; + auto qf = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor{dpk_f.degree() + 1}); + for (size_t i_quadrarture = 0; i_quadrarture < qf.numberOfPoints(); ++i_quadrarture) { + auto x = T(qf.point(i_quadrarture)); + max_error = std::max(max_error, get_max_error(dpk_f[cell_id](x), exact(x))); + } + break; + } + default: { + throw UnexpectedError("unexpected cell type"); + } + } + } + } + return max_error; +} + +template <MeshConcept MeshType, typename DataType, size_t NbComponents> +double +max_reconstruction_error( + const MeshType& mesh, + DiscreteFunctionDPkVector<MeshType::Dimension, const DataType> dpk_v, + const std::array<std::function<DataType(const TinyVector<MeshType::Dimension>&)>, NbComponents>& vector_exact) +{ + auto xr = mesh.xr(); + auto cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix(); + double max_error = 0; + auto cell_type = mesh.connectivity().cellType(); + + REQUIRE(NbComponents == dpk_v.numberOfComponents()); + + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + auto cell_nodes = cell_to_node_matrix[cell_id]; + if constexpr (MeshType::Dimension == 1) { + Assert(cell_type[cell_id] == CellType::Line); + LineTransformation<MeshType::Dimension> T{xr[cell_nodes[0]], xr[cell_nodes[1]]}; + auto qf = QuadratureManager::instance().getLineFormula(GaussQuadratureDescriptor{dpk_v.degree() + 1}); + for (size_t i_quadrarture = 0; i_quadrarture < qf.numberOfPoints(); ++i_quadrarture) { + auto x = T(qf.point(i_quadrarture)); + for (size_t i_component = 0; i_component < NbComponents; ++i_component) { + max_error = std::max(max_error, get_max_error(dpk_v(cell_id, i_component)(x), vector_exact[i_component](x))); + } + } + } else if constexpr (MeshType::Dimension == 2) { + switch (cell_type[cell_id]) { + case CellType::Triangle: { + TriangleTransformation<MeshType::Dimension> T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]]}; + auto qf = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor{dpk_v.degree() + 1}); + for (size_t i_quadrarture = 0; i_quadrarture < qf.numberOfPoints(); ++i_quadrarture) { + auto x = T(qf.point(i_quadrarture)); + for (size_t i_component = 0; i_component < NbComponents; ++i_component) { + max_error = + std::max(max_error, get_max_error(dpk_v(cell_id, i_component)(x), vector_exact[i_component](x))); + } + } + break; + } + case CellType::Quadrangle: { + SquareTransformation<MeshType::Dimension> T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]], + xr[cell_nodes[3]]}; + auto qf = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor{dpk_v.degree() + 1}); + for (size_t i_quadrarture = 0; i_quadrarture < qf.numberOfPoints(); ++i_quadrarture) { + auto x = T(qf.point(i_quadrarture)); + for (size_t i_component = 0; i_component < NbComponents; ++i_component) { + max_error = + std::max(max_error, get_max_error(dpk_v(cell_id, i_component)(x), vector_exact[i_component](x))); + } + } + break; + } + default: { + throw UnexpectedError("unexpected cell type"); + } + } + } else if constexpr (MeshType::Dimension == 3) { + switch (cell_type[cell_id]) { + case CellType::Tetrahedron: { + TetrahedronTransformation T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]], xr[cell_nodes[3]]}; + auto qf = QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor{dpk_v.degree() + 1}); + for (size_t i_quadrarture = 0; i_quadrarture < qf.numberOfPoints(); ++i_quadrarture) { + auto x = T(qf.point(i_quadrarture)); + for (size_t i_component = 0; i_component < NbComponents; ++i_component) { + max_error = + std::max(max_error, get_max_error(dpk_v(cell_id, i_component)(x), vector_exact[i_component](x))); + } + } + break; + } + case CellType::Pyramid: { + PyramidTransformation T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]], xr[cell_nodes[3]], + xr[cell_nodes[4]]}; + auto qf = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor{dpk_v.degree() + 1}); + for (size_t i_quadrarture = 0; i_quadrarture < qf.numberOfPoints(); ++i_quadrarture) { + auto x = T(qf.point(i_quadrarture)); + for (size_t i_component = 0; i_component < NbComponents; ++i_component) { + max_error = + std::max(max_error, get_max_error(dpk_v(cell_id, i_component)(x), vector_exact[i_component](x))); + } + } + break; + } + case CellType::Prism: { + PrismTransformation T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]], + xr[cell_nodes[3]], xr[cell_nodes[4]], xr[cell_nodes[5]]}; + auto qf = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor{dpk_v.degree() + 1}); + for (size_t i_quadrarture = 0; i_quadrarture < qf.numberOfPoints(); ++i_quadrarture) { + auto x = T(qf.point(i_quadrarture)); + for (size_t i_component = 0; i_component < NbComponents; ++i_component) { + max_error = + std::max(max_error, get_max_error(dpk_v(cell_id, i_component)(x), vector_exact[i_component](x))); + } + } + break; + } + case CellType::Hexahedron: { + CubeTransformation T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]], xr[cell_nodes[3]], + xr[cell_nodes[4]], xr[cell_nodes[5]], xr[cell_nodes[6]], xr[cell_nodes[7]]}; + auto qf = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor{dpk_v.degree() + 1}); + for (size_t i_quadrarture = 0; i_quadrarture < qf.numberOfPoints(); ++i_quadrarture) { + auto x = T(qf.point(i_quadrarture)); + for (size_t i_component = 0; i_component < NbComponents; ++i_component) { + max_error = + std::max(max_error, get_max_error(dpk_v(cell_id, i_component)(x), vector_exact[i_component](x))); + } + } + break; + } + default: { + throw UnexpectedError("unexpected cell type"); + } + } + } + } + return max_error; +} + +} // namespace test_only + +#endif // DISCRETE_FUNCTION_DPK_FOR_TESTS_HPP diff --git a/tests/MeshDataBaseForTests.cpp b/tests/MeshDataBaseForTests.cpp index 1f2b485edcb7f7b3fadf35d845b59f23c1a03274..1615673d0ba28d7328ab5faef7830a774d5c75f4 100644 --- a/tests/MeshDataBaseForTests.cpp +++ b/tests/MeshDataBaseForTests.cpp @@ -1,11 +1,15 @@ #include <MeshDataBaseForTests.hpp> + #include <mesh/CartesianMeshBuilder.hpp> #include <mesh/Connectivity.hpp> #include <mesh/GmshReader.hpp> #include <mesh/MeshVariant.hpp> +#include <utils/GlobalVariableManager.hpp> #include <utils/Messenger.hpp> #include <utils/PugsAssert.hpp> +#include <NbGhostLayersTester.hpp> + #include <filesystem> #include <fstream> @@ -13,17 +17,22 @@ const MeshDataBaseForTests* MeshDataBaseForTests::m_instance = nullptr; MeshDataBaseForTests::MeshDataBaseForTests() { - m_cartesian_1d_mesh = CartesianMeshBuilder{TinyVector<1>{-1}, TinyVector<1>{3}, TinyVector<1, size_t>{23}}.mesh(); + for (size_t nb_ghost_layers = 1; nb_ghost_layers <= m_max_nb_ghost_layers; ++nb_ghost_layers) { + NbGhostLayersTester t{nb_ghost_layers}; - m_cartesian_2d_mesh = - CartesianMeshBuilder{TinyVector<2>{0, -1}, TinyVector<2>{3, 2}, TinyVector<2, size_t>{6, 7}}.mesh(); + m_cartesian_1d_mesh.push_back( + CartesianMeshBuilder{TinyVector<1>{-1}, TinyVector<1>{3}, TinyVector<1, size_t>{23}}.mesh()); - m_cartesian_3d_mesh = - CartesianMeshBuilder{TinyVector<3>{0, 1, 0}, TinyVector<3>{2, -1, 3}, TinyVector<3, size_t>{6, 7, 4}}.mesh(); + m_cartesian_2d_mesh.push_back( + CartesianMeshBuilder{TinyVector<2>{0, -1}, TinyVector<2>{3, 2}, TinyVector<2, size_t>{6, 7}}.mesh()); - m_unordered_1d_mesh = _buildUnordered1dMesh(); - m_hybrid_2d_mesh = _buildHybrid2dMesh(); - m_hybrid_3d_mesh = _buildHybrid3dMesh(); + m_cartesian_3d_mesh.push_back( + CartesianMeshBuilder{TinyVector<3>{0, 1, 0}, TinyVector<3>{2, -1, 3}, TinyVector<3, size_t>{6, 7, 4}}.mesh()); + + m_unordered_1d_mesh.push_back(_buildUnordered1dMesh()); + m_hybrid_2d_mesh.push_back(_buildHybrid2dMesh()); + m_hybrid_3d_mesh.push_back(_buildHybrid3dMesh()); + } } const MeshDataBaseForTests& @@ -50,37 +59,49 @@ MeshDataBaseForTests::destroy() std::shared_ptr<const MeshVariant> MeshDataBaseForTests::cartesian1DMesh() const { - return m_cartesian_1d_mesh; + const size_t nb_ghost_layers = GlobalVariableManager::instance().getNumberOfGhostLayers(); + Assert((nb_ghost_layers >= 1) and (nb_ghost_layers <= m_max_nb_ghost_layers)); + return m_cartesian_1d_mesh[nb_ghost_layers - 1]; } std::shared_ptr<const MeshVariant> MeshDataBaseForTests::cartesian2DMesh() const { - return m_cartesian_2d_mesh; + const size_t nb_ghost_layers = GlobalVariableManager::instance().getNumberOfGhostLayers(); + Assert((nb_ghost_layers >= 1) and (nb_ghost_layers <= m_max_nb_ghost_layers)); + return m_cartesian_2d_mesh[nb_ghost_layers - 1]; } std::shared_ptr<const MeshVariant> MeshDataBaseForTests::cartesian3DMesh() const { - return m_cartesian_3d_mesh; + const size_t nb_ghost_layers = GlobalVariableManager::instance().getNumberOfGhostLayers(); + Assert((nb_ghost_layers >= 1) and (nb_ghost_layers <= m_max_nb_ghost_layers)); + return m_cartesian_3d_mesh[nb_ghost_layers - 1]; } std::shared_ptr<const MeshVariant> MeshDataBaseForTests::unordered1DMesh() const { - return m_unordered_1d_mesh; + const size_t nb_ghost_layers = GlobalVariableManager::instance().getNumberOfGhostLayers(); + Assert((nb_ghost_layers >= 1) and (nb_ghost_layers <= m_max_nb_ghost_layers)); + return m_unordered_1d_mesh[nb_ghost_layers - 1]; } std::shared_ptr<const MeshVariant> MeshDataBaseForTests::hybrid2DMesh() const { - return m_hybrid_2d_mesh; + const size_t nb_ghost_layers = GlobalVariableManager::instance().getNumberOfGhostLayers(); + Assert((nb_ghost_layers >= 1) and (nb_ghost_layers <= m_max_nb_ghost_layers)); + return m_hybrid_2d_mesh[nb_ghost_layers - 1]; } std::shared_ptr<const MeshVariant> MeshDataBaseForTests::hybrid3DMesh() const { - return m_hybrid_3d_mesh; + const size_t nb_ghost_layers = GlobalVariableManager::instance().getNumberOfGhostLayers(); + Assert((nb_ghost_layers >= 1) and (nb_ghost_layers <= m_max_nb_ghost_layers)); + return m_hybrid_3d_mesh[nb_ghost_layers - 1]; } std::shared_ptr<const MeshVariant> diff --git a/tests/MeshDataBaseForTests.hpp b/tests/MeshDataBaseForTests.hpp index 2b092d544271fbd1b1d0488ed73c9c5a401d548b..f0ab29fa9a927c01f16f94925fba7eb750ffd591 100644 --- a/tests/MeshDataBaseForTests.hpp +++ b/tests/MeshDataBaseForTests.hpp @@ -4,6 +4,7 @@ #include <array> #include <memory> #include <string> +#include <vector> class MeshVariant; @@ -35,15 +36,17 @@ class MeshDataBaseForTests private: explicit MeshDataBaseForTests(); + constexpr static size_t m_max_nb_ghost_layers = 3; + static const MeshDataBaseForTests* m_instance; - std::shared_ptr<const MeshVariant> m_cartesian_1d_mesh; - std::shared_ptr<const MeshVariant> m_cartesian_2d_mesh; - std::shared_ptr<const MeshVariant> m_cartesian_3d_mesh; + std::vector<std::shared_ptr<const MeshVariant>> m_cartesian_1d_mesh; + std::vector<std::shared_ptr<const MeshVariant>> m_cartesian_2d_mesh; + std::vector<std::shared_ptr<const MeshVariant>> m_cartesian_3d_mesh; - std::shared_ptr<const MeshVariant> m_unordered_1d_mesh; - std::shared_ptr<const MeshVariant> m_hybrid_2d_mesh; - std::shared_ptr<const MeshVariant> m_hybrid_3d_mesh; + std::vector<std::shared_ptr<const MeshVariant>> m_unordered_1d_mesh; + std::vector<std::shared_ptr<const MeshVariant>> m_hybrid_2d_mesh; + std::vector<std::shared_ptr<const MeshVariant>> m_hybrid_3d_mesh; std::shared_ptr<const MeshVariant> _buildUnordered1dMesh(); std::shared_ptr<const MeshVariant> _buildHybrid2dMesh(); diff --git a/tests/NbGhostLayersTester.hpp b/tests/NbGhostLayersTester.hpp new file mode 100644 index 0000000000000000000000000000000000000000..0468fe18305367834c9cbd0959ba5b3f443fe870 --- /dev/null +++ b/tests/NbGhostLayersTester.hpp @@ -0,0 +1,27 @@ +#ifndef NB_GHOST_LAYERS_TESTER_HPP +#define NB_GHOST_LAYERS_TESTER_HPP + +#include <cstddef> +#include <utils/GlobalVariableManager.hpp> + +class NbGhostLayersTester +{ + private: + const size_t m_original_number_of_ghost_layers; + + public: + PUGS_INLINE + NbGhostLayersTester(const size_t number_of_ghost_layers) + : m_original_number_of_ghost_layers{GlobalVariableManager::instance().getNumberOfGhostLayers()} + { + GlobalVariableManager::instance().m_number_of_ghost_layers = number_of_ghost_layers; + } + + PUGS_INLINE + ~NbGhostLayersTester() + { + GlobalVariableManager::instance().m_number_of_ghost_layers = m_original_number_of_ghost_layers; + } +}; + +#endif // NB_GHOST_LAYERS_TESTER_HPP diff --git a/tests/mpi_test_main.cpp b/tests/mpi_test_main.cpp index cd74479774dbf255460e5afe1b056c73f3e80821..cabb10386d4e8e99ab0dd9b1946bc7dba818502f 100644 --- a/tests/mpi_test_main.cpp +++ b/tests/mpi_test_main.cpp @@ -7,6 +7,7 @@ #include <mesh/DualConnectivityManager.hpp> #include <mesh/DualMeshManager.hpp> #include <mesh/MeshDataManager.hpp> +#include <mesh/StencilManager.hpp> #include <mesh/SynchronizerManager.hpp> #include <utils/GlobalVariableManager.hpp> #include <utils/Messenger.hpp> @@ -112,7 +113,10 @@ main(int argc, char* argv[]) MeshDataManager::create(); DualConnectivityManager::create(); DualMeshManager::create(); + StencilManager::create(); + GlobalVariableManager::create(); + GlobalVariableManager::instance().setNumberOfGhostLayers(1); MeshDataBaseForTests::create(); @@ -124,6 +128,7 @@ main(int argc, char* argv[]) MeshDataBaseForTests::destroy(); + StencilManager::destroy(); GlobalVariableManager::destroy(); DualMeshManager::destroy(); DualConnectivityManager::destroy(); diff --git a/tests/test_ConnectivityDispatcher.cpp b/tests/test_ConnectivityDispatcher.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7cc747588e9004a410f2a44453374eb81d499b8a --- /dev/null +++ b/tests/test_ConnectivityDispatcher.cpp @@ -0,0 +1,236 @@ +#include <catch2/catch_test_macros.hpp> +#include <catch2/matchers/catch_matchers_all.hpp> + +#include <mesh/CartesianMeshBuilder.hpp> +#include <mesh/Connectivity.hpp> +#include <mesh/ConnectivityDispatcher.hpp> +#include <mesh/GmshReader.hpp> +#include <mesh/Mesh.hpp> +#include <mesh/MeshVariant.hpp> +#include <utils/Messenger.hpp> + +#include <MeshDataBaseForTests.hpp> +#include <NbGhostLayersTester.hpp> + +#include <filesystem> + +// clazy:excludeall=non-pod-global-static + +TEST_CASE("ConnectivityDispatcher", "[mesh]") +{ + auto check_number_of_ghost_layers = [](const auto& connectivity, const size_t number_of_layers) { + // We assume that the specified number of layers can be built + // (there are enough non owned layer of cells in the connectivity) + const auto cell_is_owned = connectivity.cellIsOwned(); + + CellValue<size_t> cell_layer{connectivity}; + cell_layer.fill(number_of_layers + 1); + + NodeValue<size_t> node_layer{connectivity}; + node_layer.fill(number_of_layers + 1); + + auto node_to_cell_matrix = connectivity.nodeToCellMatrix(); + auto cell_to_node_matrix = connectivity.cellToNodeMatrix(); + + parallel_for( + connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { + if (cell_is_owned[cell_id]) { + cell_layer[cell_id] = 0; + } + }); + + for (size_t i_layer = 0; i_layer < number_of_layers; ++i_layer) { + parallel_for( + connectivity.numberOfNodes(), PUGS_LAMBDA(const NodeId node_id) { + auto node_cell_list = node_to_cell_matrix[node_id]; + size_t min_layer = cell_layer[node_cell_list[0]]; + for (size_t i_cell = 1; i_cell < node_cell_list.size(); ++i_cell) { + min_layer = std::min(min_layer, cell_layer[node_cell_list[i_cell]]); + } + if (min_layer < number_of_layers + 1) { + node_layer[node_id] = min_layer; + } + }); + + parallel_for( + connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { + auto cell_node_list = cell_to_node_matrix[cell_id]; + size_t min_layer = node_layer[cell_node_list[0]]; + size_t max_layer = min_layer; + for (size_t i_node = 1; i_node < cell_node_list.size(); ++i_node) { + min_layer = std::min(min_layer, node_layer[cell_node_list[i_node]]); + max_layer = std::max(max_layer, node_layer[cell_node_list[i_node]]); + } + if ((min_layer != max_layer) or + ((min_layer < number_of_layers + 1) and (cell_layer[cell_id] == number_of_layers + 1))) { + cell_layer[cell_id] = min_layer + 1; + } + }); + } + + auto is_boundary_face = connectivity.isBoundaryFace(); + auto face_to_cell_matrix = connectivity.faceToCellMatrix(); + + bool has_required_number_of_ghost_layers = true; + for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) { + auto face_cell_list = face_to_cell_matrix[face_id]; + if ((face_cell_list.size() == 1) and (not is_boundary_face[face_id])) { + const CellId face_cell_id = face_cell_list[0]; + has_required_number_of_ghost_layers &= (cell_layer[face_cell_id] == number_of_layers); + } + } + + REQUIRE(parallel::allReduceAnd(has_required_number_of_ghost_layers)); + bool first_ghost_layer_is_1 = true; + for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) { + auto face_cell_list = face_to_cell_matrix[face_id]; + if (face_cell_list.size() == 2) { + const CellId face_cell0_id = face_cell_list[0]; + const CellId face_cell1_id = face_cell_list[1]; + if (cell_is_owned[face_cell0_id] xor cell_is_owned[face_cell1_id]) { + for (size_t i_cell = 0; i_cell < face_cell_list.size(); ++i_cell) { + const CellId face_cell_id = face_cell_list[i_cell]; + if (not cell_is_owned[face_cell_id]) { + first_ghost_layer_is_1 &= (cell_layer[face_cell_id] == 1); + } + } + } + } + } + + REQUIRE(parallel::allReduceAnd(first_ghost_layer_is_1)); + }; + + SECTION("1 layer meshes") + { + for (auto named_mesh : MeshDataBaseForTests::get().all1DMeshes()) { + SECTION(named_mesh.name()) + { + const std::shared_ptr p_mesh = named_mesh.mesh()->get<const Mesh<1>>(); + check_number_of_ghost_layers(p_mesh->connectivity(), 1); + } + } + for (auto named_mesh : MeshDataBaseForTests::get().all2DMeshes()) { + SECTION(named_mesh.name()) + { + const std::shared_ptr p_mesh = named_mesh.mesh()->get<const Mesh<2>>(); + check_number_of_ghost_layers(p_mesh->connectivity(), 1); + } + } + for (auto named_mesh : MeshDataBaseForTests::get().all3DMeshes()) { + SECTION(named_mesh.name()) + { + const std::shared_ptr p_mesh = named_mesh.mesh()->get<const Mesh<3>>(); + check_number_of_ghost_layers(p_mesh->connectivity(), 1); + } + } + } + + constexpr bool has_partitioner = []() { +#if defined(PUGS_HAS_PARMETIS) || defined(PUGS_HAS_PTSCOTCH) + return true; +#else + return false; +#endif + }(); + + for (size_t nb_ghost_layers = 2; nb_ghost_layers < 5; ++nb_ghost_layers) { + std::stringstream os; + os << nb_ghost_layers << " layer meshes"; + + SECTION(os.str()) + { + REQUIRE(GlobalVariableManager::instance().getNumberOfGhostLayers() == 1); + + NbGhostLayersTester nb_ghost_layers_tester(nb_ghost_layers); + + REQUIRE(GlobalVariableManager::instance().getNumberOfGhostLayers() == nb_ghost_layers); + + SECTION("Cartesian 1D mesh") + { + auto cartesian_1d_mesh = + CartesianMeshBuilder{TinyVector<1>{-1}, TinyVector<1>{3}, TinyVector<1, size_t>{23}}.mesh(); + const std::shared_ptr p_mesh = cartesian_1d_mesh->get<const Mesh<1>>(); + check_number_of_ghost_layers(p_mesh->connectivity(), nb_ghost_layers); + + if (has_partitioner) { + ConnectivityDispatcher cd{p_mesh->connectivity()}; + check_number_of_ghost_layers(*cd.dispatchedConnectivity(), nb_ghost_layers); + } + } + + SECTION("Cartesian 2D mesh") + { + auto cartesian_2d_mesh = + CartesianMeshBuilder{TinyVector<2>{0, -1}, TinyVector<2>{3, 2}, TinyVector<2, size_t>{6, 7}}.mesh(); + const std::shared_ptr p_mesh = cartesian_2d_mesh->get<const Mesh<2>>(); + check_number_of_ghost_layers(p_mesh->connectivity(), nb_ghost_layers); + + if (has_partitioner) { + ConnectivityDispatcher cd{p_mesh->connectivity()}; + check_number_of_ghost_layers(*cd.dispatchedConnectivity(), nb_ghost_layers); + } + } + + SECTION("Cartesian 3D mesh") + { + auto cartesian_3d_mesh = + CartesianMeshBuilder{TinyVector<3>{0, 1, 0}, TinyVector<3>{2, -1, 3}, TinyVector<3, size_t>{6, 7, 4}}.mesh(); + const std::shared_ptr p_mesh = cartesian_3d_mesh->get<const Mesh<3>>(); + check_number_of_ghost_layers(p_mesh->connectivity(), nb_ghost_layers); + + if (has_partitioner) { + ConnectivityDispatcher cd{p_mesh->connectivity()}; + check_number_of_ghost_layers(*cd.dispatchedConnectivity(), nb_ghost_layers); + } + } + + SECTION("unordered 1d mesh") + { + const std::string filename = std::filesystem::path{PUGS_BINARY_DIR}.append("tests").append("unordered-1d.msh"); + + auto mesh_v = GmshReader{filename}.mesh(); + + const std::shared_ptr p_mesh = mesh_v->get<const Mesh<1>>(); + check_number_of_ghost_layers(p_mesh->connectivity(), nb_ghost_layers); + + if (has_partitioner) { + ConnectivityDispatcher cd{p_mesh->connectivity()}; + check_number_of_ghost_layers(*cd.dispatchedConnectivity(), nb_ghost_layers); + } + } + + SECTION("hybrid 2d mesh") + { + const std::string filename = std::filesystem::path{PUGS_BINARY_DIR}.append("tests").append("hybrid-2d.msh"); + + auto mesh_v = GmshReader{filename}.mesh(); + + const std::shared_ptr p_mesh = mesh_v->get<const Mesh<2>>(); + check_number_of_ghost_layers(p_mesh->connectivity(), nb_ghost_layers); + + if (has_partitioner) { + ConnectivityDispatcher cd{p_mesh->connectivity()}; + check_number_of_ghost_layers(*cd.dispatchedConnectivity(), nb_ghost_layers); + } + } + + SECTION("hybrid 3d mesh") + { + const std::string filename = std::filesystem::path{PUGS_BINARY_DIR}.append("tests").append("hybrid-3d.msh"); + + auto mesh_v = GmshReader{filename}.mesh(); + + const std::shared_ptr p_mesh = mesh_v->get<const Mesh<3>>(); + check_number_of_ghost_layers(p_mesh->connectivity(), nb_ghost_layers); + + if (has_partitioner) { + ConnectivityDispatcher cd{p_mesh->connectivity()}; + check_number_of_ghost_layers(*cd.dispatchedConnectivity(), nb_ghost_layers); + } + } + } + + REQUIRE(GlobalVariableManager::instance().getNumberOfGhostLayers() == 1); + } +} diff --git a/tests/test_DiscreteFunctionDPk.cpp b/tests/test_DiscreteFunctionDPk.cpp new file mode 100644 index 0000000000000000000000000000000000000000..60120f894a1c3fb4de851e243f593efc7f41a700 --- /dev/null +++ b/tests/test_DiscreteFunctionDPk.cpp @@ -0,0 +1,670 @@ +#include <catch2/catch_test_macros.hpp> +#include <catch2/matchers/catch_matchers_all.hpp> + +#include <MeshDataBaseForTests.hpp> +#include <mesh/Mesh.hpp> +#include <scheme/DiscreteFunctionDPk.hpp> +#include <scheme/DiscreteFunctionP0.hpp> + +// clazy:excludeall=non-pod-global-static + +TEST_CASE("DiscreteFunctionDPk", "[scheme]") +{ + SECTION("Basic interface") + { + const std::shared_ptr mesh_2d_v = MeshDataBaseForTests::get().cartesian2DMesh(); + const std::shared_ptr mesh_2d = mesh_2d_v->get<Mesh<2>>(); + + const std::shared_ptr mesh_2d_2_v = MeshDataBaseForTests::get().hybrid2DMesh(); + const std::shared_ptr mesh_2d_2 = mesh_2d_2_v->get<Mesh<2>>(); + + DiscreteFunctionDPk<2, const double> R_dkp; + +#ifndef NDEBUG + REQUIRE_THROWS_WITH(R_dkp[CellId(0)], "DiscreteFunctionDPk is not built"); +#endif // NDEBUG + + { + DiscreteFunctionDPk<2, double> tmp_R_dkp(mesh_2d_v, 2); + tmp_R_dkp.fill(2); + R_dkp = tmp_R_dkp; + } + + REQUIRE(R_dkp.degree() == 2); + + REQUIRE(min(R_dkp.cellArrays()) == 2); + REQUIRE(max(R_dkp.cellArrays()) == 2); + + REQUIRE(R_dkp.dataType() == ast_node_data_type_from<double>); + REQUIRE(R_dkp.meshVariant()->id() == mesh_2d->id()); + + DiscreteFunctionDPk<2, double> R_dkp2; + R_dkp2 = copy(R_dkp); + + REQUIRE(R_dkp2.degree() == 2); + + REQUIRE(min(R_dkp2.cellArrays()) == 2); + REQUIRE(max(R_dkp2.cellArrays()) == 2); + + REQUIRE(R_dkp2.dataType() == ast_node_data_type_from<double>); + REQUIRE(R_dkp2.meshVariant()->id() == mesh_2d_v->id()); + + DiscreteFunctionDPk<2, double> R_dkp3(mesh_2d_2_v, 1); + R_dkp3.fill(3); + + REQUIRE(R_dkp3.degree() == 1); + + REQUIRE(min(R_dkp3.cellArrays()) == 3); + REQUIRE(max(R_dkp3.cellArrays()) == 3); + + REQUIRE(R_dkp3.dataType() == ast_node_data_type_from<double>); + REQUIRE(R_dkp3.meshVariant()->id() == mesh_2d_2_v->id()); + + DiscreteFunctionDPk<2, double> R_dkp4(mesh_2d, R_dkp2.cellArrays()); + + REQUIRE(min(R_dkp4.cellArrays()) == 2); + REQUIRE(max(R_dkp4.cellArrays()) == 2); + + R_dkp4.fill(5); + REQUIRE(min(R_dkp4.cellArrays()) == 5); + REQUIRE(max(R_dkp4.cellArrays()) == 5); + REQUIRE(min(R_dkp2.cellArrays()) == 5); + REQUIRE(max(R_dkp2.cellArrays()) == 5); + + copy_to(R_dkp, R_dkp4); + REQUIRE(min(R_dkp4.cellArrays()) == 2); + REQUIRE(max(R_dkp4.cellArrays()) == 2); + REQUIRE(min(R_dkp2.cellArrays()) == 2); + REQUIRE(max(R_dkp2.cellArrays()) == 2); + +#ifndef NDEBUG + REQUIRE_THROWS_WITH(copy_to(R_dkp, R_dkp3), "copy_to target must use the same mesh"); + + DiscreteFunctionDPk<2, double> R_dkp5(mesh_2d, 3); + REQUIRE_THROWS_WITH(copy_to(R_dkp, R_dkp5), "copy_to target must have the same degree"); + + REQUIRE_THROWS_WITH((DiscreteFunctionDPk<2, double>{mesh_2d_2, R_dkp2.cellArrays()}), + "cell_array is built on different connectivity"); +#endif // NDEBUG + } + + SECTION("R data") + { + SECTION("1D") + { + constexpr size_t Dimension = 1; + + using R1 = TinyVector<1>; + + const std::shared_ptr mesh_v = MeshDataBaseForTests::get().cartesian1DMesh(); + const std::shared_ptr mesh = mesh_v->get<Mesh<Dimension>>(); + + auto xj = MeshDataManager::instance().getMeshData(*mesh).xj(); + auto Vj = MeshDataManager::instance().getMeshData(*mesh).Vj(); + + const size_t degree = 4; + + DiscreteFunctionDPk<Dimension, double> pk(mesh_v, degree); + + std::vector<double> a = {1, 1.4, -6.2, 2.7, 3.1}; + + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) { + auto coefficients = pk.coefficients(cell_id); + for (size_t i = 0; i < a.size(); ++i) { + coefficients[i] = a[i]; + } + }; + + DiscreteFunctionP0<double> p_xj(mesh_v); + parallel_for( + mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { p_xj[cell_id] = pk[cell_id](xj[cell_id]); }); + + REQUIRE(max(p_xj) == 1); + REQUIRE(min(p_xj) == 1); + + DiscreteFunctionP0<double> delta(mesh_v); + parallel_for( + mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { + const double x0 = 0.3 * Vj[cell_id]; + delta[cell_id] = pk[cell_id](xj[cell_id] + R1{x0}) // + - (a[0] + x0 * a[1] + x0 * x0 * a[2] + x0 * x0 * x0 * a[3] + x0 * x0 * x0 * x0 * a[4]); + }); + + REQUIRE(max(delta) == Catch::Approx(0.).margin(1E-14)); + REQUIRE(min(delta) == Catch::Approx(0.).margin(1E-14)); + } + + SECTION("2D") + { + constexpr size_t Dimension = 2; + + using R2 = TinyVector<2>; + + const std::shared_ptr mesh_v = MeshDataBaseForTests::get().cartesian2DMesh(); + const std::shared_ptr mesh = mesh_v->get<Mesh<Dimension>>(); + + auto xj = MeshDataManager::instance().getMeshData(*mesh).xj(); + auto Vj = MeshDataManager::instance().getMeshData(*mesh).Vj(); + + const size_t degree = 3; + + DiscreteFunctionDPk<Dimension, double> pk(mesh_v, degree); + + const std::vector<double> a = {1, 1.4, -6.2, 3.5, -2.3, 5.2, 6.1, 2.3, 0.5, -1.3}; + + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) { + auto coefficients = pk.coefficients(cell_id); + for (size_t i = 0; i < coefficients.size(); ++i) { + coefficients[i] = a[i]; + } + }; + + DiscreteFunctionP0<double> p_xj(mesh_v); + parallel_for( + mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { p_xj[cell_id] = pk[cell_id](xj[cell_id]); }); + + REQUIRE(max(p_xj) == 1); + REQUIRE(min(p_xj) == 1); + + DiscreteFunctionP0<double> error(mesh_v); + parallel_for( + mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { + const double x0 = 0.2 * Vj[cell_id]; + const double y0 = 0.3 * Vj[cell_id]; + error[cell_id] // + = std::abs(pk[cell_id](xj[cell_id] + R2{x0, y0}) // + - (a[0] // + + x0 * a[1] // + + x0 * x0 * a[2] // + + x0 * x0 * x0 * a[3] // + + y0 * a[4] // + + y0 * x0 * a[5] // + + y0 * x0 * x0 * a[6] // + + y0 * y0 * a[7] // + + y0 * y0 * x0 * a[8] // + + y0 * y0 * y0 * a[9])); + }); + + REQUIRE(max(error) == Catch::Approx(0.).margin(1E-14)); + } + + SECTION("3D") + { + constexpr size_t Dimension = 3; + + using R3 = TinyVector<3>; + + const std::shared_ptr mesh_v = MeshDataBaseForTests::get().cartesian3DMesh(); + const std::shared_ptr mesh = mesh_v->get<Mesh<Dimension>>(); + + auto xj = MeshDataManager::instance().getMeshData(*mesh).xj(); + auto Vj = MeshDataManager::instance().getMeshData(*mesh).Vj(); + + const size_t degree = 3; + + DiscreteFunctionDPk<Dimension, double> pk(mesh_v, degree); + + const std::vector<double> a = {+1.0, +1.4, -6.2, +3.5, -2.3, +5.2, +6.1, +2.3, +0.5, -1.3, // + +2.8, -8.4, +9.5, +4.0, +4.3, +7.2, -9.1, +6.8, +6.7, +9.2}; + + parallel_for( + mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { + auto coefficients = pk.coefficients(cell_id); + for (size_t i = 0; i < coefficients.size(); ++i) { + coefficients[i] = a[i]; + } + }); + + DiscreteFunctionP0<double> delta_xj(mesh_v); + parallel_for( + mesh->numberOfCells(), + PUGS_LAMBDA(CellId cell_id) { delta_xj[cell_id] = std::abs(pk[cell_id](xj[cell_id]) - a[0]); }); + + REQUIRE(max(delta_xj) == Catch::Approx(0.).margin(1E-14)); + + DiscreteFunctionP0<double> delta(mesh_v); + parallel_for( + mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { + const double x0 = +0.5 * Vj[cell_id]; + const double y0 = +0.3 * Vj[cell_id]; + const double z0 = -0.4 * Vj[cell_id]; + + delta[cell_id] = pk[cell_id](xj[cell_id] + R3{x0, y0, z0}) // + - (a[0] // + + x0 * a[1] // + + x0 * x0 * a[2] // + + x0 * x0 * x0 * a[3] // + + y0 * a[4] // + + y0 * x0 * a[5] // + + y0 * x0 * x0 * a[6] // + + y0 * y0 * a[7] // + + y0 * y0 * x0 * a[8] // + + y0 * y0 * y0 * a[9] // + + z0 * a[10] // + + z0 * x0 * a[11] // + + z0 * x0 * x0 * a[12] // + + z0 * y0 * a[13] // + + z0 * y0 * x0 * a[14] // + + z0 * y0 * y0 * a[15] // + + z0 * z0 * a[16] // + + z0 * z0 * x0 * a[17] // + + z0 * z0 * y0 * a[18] // + + z0 * z0 * z0 * a[19] // + ); + }); + + REQUIRE(max(delta) == Catch::Approx(0.).margin(1E-14)); + REQUIRE(min(delta) == Catch::Approx(0.).margin(1E-14)); + } + } + + SECTION("R^d data") + { + SECTION("1D") + { + constexpr size_t Dimension = 1; + + using R1 = TinyVector<1>; + using R2 = TinyVector<2>; + + const std::shared_ptr mesh_v = MeshDataBaseForTests::get().cartesian1DMesh(); + const std::shared_ptr mesh = mesh_v->get<Mesh<Dimension>>(); + + auto xj = MeshDataManager::instance().getMeshData(*mesh).xj(); + auto Vj = MeshDataManager::instance().getMeshData(*mesh).Vj(); + + const size_t degree = 4; + + DiscreteFunctionDPk<Dimension, R2> pk(mesh_v, degree); + + const std::vector<R2> a = {R2{-1.0, +3.0}, R2{+1.4, +1.9}, R2{-6.2, -1.0}, R2{+2.7, +1.6}, R2{+3.1, -1.3}}; + + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) { + auto coefficients = pk.coefficients(cell_id); + for (size_t i = 0; i < a.size(); ++i) { + coefficients[i] = a[i]; + } + } + + DiscreteFunctionP0<double> delta_xj(mesh_v); + parallel_for( + mesh->numberOfCells(), + PUGS_LAMBDA(CellId cell_id) { delta_xj[cell_id] = l2Norm(pk[cell_id](xj[cell_id]) - a[0]); }); + + REQUIRE(max(delta_xj) == Catch::Approx(0).margin(1E-14)); + + DiscreteFunctionP0<double> delta(mesh_v); + parallel_for( + mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { + const double x0 = 0.3 * Vj[cell_id]; + delta[cell_id] = + l2Norm(pk[cell_id](xj[cell_id] + R1{x0}) // + - (a[0] + x0 * a[1] + x0 * x0 * a[2] + x0 * x0 * x0 * a[3] + x0 * x0 * x0 * x0 * a[4])); + }); + + REQUIRE(max(delta) == Catch::Approx(0.).margin(1E-14)); + } + + SECTION("2D") + { + constexpr size_t Dimension = 2; + + using R2 = TinyVector<2>; + + const std::shared_ptr mesh_v = MeshDataBaseForTests::get().hybrid2DMesh(); + const std::shared_ptr mesh = mesh_v->get<Mesh<Dimension>>(); + + auto xj = MeshDataManager::instance().getMeshData(*mesh).xj(); + auto Vj = MeshDataManager::instance().getMeshData(*mesh).Vj(); + + const size_t degree = 3; + + DiscreteFunctionDPk<Dimension, R2> pk(mesh_v, degree); + + const std::vector<R2> a = {R2{-1.0, +3.0}, R2{+1.4, +1.9}, R2{-6.2, -1.0}, R2{+2.7, +1.6}, R2{+3.1, -1.3}, + R2{+2.5, -4.2}, R2{+2.1, -1.7}, R2{-3.2, +1.0}, R2{-2.3, +1.3}, R2{-2.9, -3.2}}; + + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) { + auto coefficients = pk.coefficients(cell_id); + for (size_t i = 0; i < a.size(); ++i) { + coefficients[i] = a[i]; + } + } + + DiscreteFunctionP0<double> delta_xj(mesh_v); + parallel_for( + mesh->numberOfCells(), + PUGS_LAMBDA(CellId cell_id) { delta_xj[cell_id] = l2Norm(pk[cell_id](xj[cell_id]) - a[0]); }); + + REQUIRE(max(delta_xj) == Catch::Approx(0).margin(1E-14)); + + DiscreteFunctionP0<double> delta(mesh_v); + parallel_for( + mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { + const double x0 = 0.2 * Vj[cell_id]; + const double y0 = 0.3 * Vj[cell_id]; + delta[cell_id] // + = l2Norm(pk[cell_id](xj[cell_id] + R2{x0, y0}) // + - (a[0] // + + x0 * a[1] // + + x0 * x0 * a[2] // + + x0 * x0 * x0 * a[3] // + + y0 * a[4] // + + y0 * x0 * a[5] // + + y0 * x0 * x0 * a[6] // + + y0 * y0 * a[7] // + + y0 * y0 * x0 * a[8] // + + y0 * y0 * y0 * a[9])); + }); + + REQUIRE(max(delta) == Catch::Approx(0.).margin(1E-14)); + } + + SECTION("3D") + { + constexpr size_t Dimension = 3; + + using R2 = TinyVector<2>; + using R3 = TinyVector<3>; + + const std::shared_ptr mesh_v = MeshDataBaseForTests::get().hybrid3DMesh(); + const std::shared_ptr mesh = mesh_v->get<Mesh<Dimension>>(); + + auto xj = MeshDataManager::instance().getMeshData(*mesh).xj(); + auto Vj = MeshDataManager::instance().getMeshData(*mesh).Vj(); + + const size_t degree = 3; + + const std::vector<R2> a = {R2{+9.2, +7.2}, R2{+0.0, -2.7}, R2{+4.8, -5.8}, R2{+6.0, +3.6}, R2{-7.0, -9.7}, + R2{+5.3, -1.2}, R2{-1.2, +3.7}, R2{-0.4, +6.1}, R2{+8.4, +9.5}, R2{-9.7, +3.3}, + R2{+0.5, +4.2}, R2{+3.8, +3.3}, R2{+8.0, -10.0}, R2{-5.1, -4.1}, R2{+2.6, -2.5}, + R2{-3.4, -2.7}, R2{+0.7, +4.9}, R2{+6.0, +6.4}, R2{+3.5, +5.0}, R2{+1.7, +4.8}}; + + DiscreteFunctionDPk<Dimension, R2> pk(mesh_v, degree); + parallel_for( + mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { + auto coefficients = pk.coefficients(cell_id); + for (size_t i = 0; i < coefficients.size(); ++i) { + coefficients[i] = a[i]; + } + }); + + DiscreteFunctionP0<double> delta_xj(mesh_v); + parallel_for( + mesh->numberOfCells(), + PUGS_LAMBDA(CellId cell_id) { delta_xj[cell_id] = l2Norm(pk[cell_id](xj[cell_id]) - a[0]); }); + + REQUIRE(max(delta_xj) == Catch::Approx(0.).margin(1E-14)); + + DiscreteFunctionP0<double> delta(mesh_v); + parallel_for( + mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { + const double x0 = +0.5 * Vj[cell_id]; + const double y0 = +0.3 * Vj[cell_id]; + const double z0 = -0.4 * Vj[cell_id]; + + delta[cell_id] = l2Norm(pk[cell_id](xj[cell_id] + R3{x0, y0, z0}) // + - (a[0] // + + x0 * a[1] // + + x0 * x0 * a[2] // + + x0 * x0 * x0 * a[3] // + + y0 * a[4] // + + y0 * x0 * a[5] // + + y0 * x0 * x0 * a[6] // + + y0 * y0 * a[7] // + + y0 * y0 * x0 * a[8] // + + y0 * y0 * y0 * a[9] // + + z0 * a[10] // + + z0 * x0 * a[11] // + + z0 * x0 * x0 * a[12] // + + z0 * y0 * a[13] // + + z0 * y0 * x0 * a[14] // + + z0 * y0 * y0 * a[15] // + + z0 * z0 * a[16] // + + z0 * z0 * x0 * a[17] // + + z0 * z0 * y0 * a[18] // + + z0 * z0 * z0 * a[19] // + )); + }); + + REQUIRE(max(delta) == Catch::Approx(0.).margin(1E-14)); + } + } + + SECTION("R^d1xd2 data") + { + SECTION("1D") + { + constexpr size_t Dimension = 1; + + using R1 = TinyVector<1>; + using R2x3 = TinyMatrix<2, 3>; + + const std::shared_ptr mesh_v = MeshDataBaseForTests::get().cartesian1DMesh(); + const std::shared_ptr mesh = mesh_v->get<Mesh<Dimension>>(); + + auto xj = MeshDataManager::instance().getMeshData(*mesh).xj(); + auto Vj = MeshDataManager::instance().getMeshData(*mesh).Vj(); + + const size_t degree = 4; + + DiscreteFunctionDPk<Dimension, R2x3> pk(mesh_v, degree); + + std::vector<R2x3> A = {R2x3{+1.0, +2.0, +3.0, +4.0, +5.0, +6.0}, // + R2x3{-1.2, -2.3, +7.2, +8.4, -5.0, +0.7}, // + R2x3{-2.1, -3.3, -2.7, -3.4, -0.5, -2.7}, // + R2x3{+6.2, -2.9, +3.1, -2.6, +1.5, +2.1}, // + R2x3{-2.6, -2.2, +4.2, -1.7, +8.5, -1.4}}; + + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) { + auto coefficients = pk.coefficients(cell_id); + for (size_t i = 0; i < coefficients.size(); ++i) { + coefficients[i] = A[i]; + } + }; + + DiscreteFunctionP0<double> delta_xj(mesh_v); + parallel_for( + mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { + const R2x3 Diff = pk[cell_id](xj[cell_id]) - A[0]; + + delta_xj[cell_id] = l2Norm((transpose(Diff) * Diff) * TinyVector<3>(1, 1, 1)); + }); + + REQUIRE(max(delta_xj) == Catch::Approx(0).margin(1E-14)); + + DiscreteFunctionP0<double> delta(mesh_v); + parallel_for( + mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { + const double x0 = 0.3 * Vj[cell_id]; + const R2x3 Diff = pk[cell_id](xj[cell_id] + R1{x0}) - + (A[0] + x0 * A[1] + x0 * x0 * A[2] + x0 * x0 * x0 * A[3] + x0 * x0 * x0 * x0 * A[4]); + + delta[cell_id] = l2Norm((transpose(Diff) * Diff) * TinyVector<3>(1, 1, 1)); + }); + + REQUIRE(max(delta) == Catch::Approx(0.).margin(1E-14)); + } + + SECTION("2D") + { + constexpr size_t Dimension = 2; + + using R2 = TinyVector<2>; + using R2x3 = TinyMatrix<2, 3>; + + const std::shared_ptr mesh_v = MeshDataBaseForTests::get().cartesian2DMesh(); + const std::shared_ptr mesh = mesh_v->get<Mesh<Dimension>>(); + + auto xj = MeshDataManager::instance().getMeshData(*mesh).xj(); + auto Vj = MeshDataManager::instance().getMeshData(*mesh).Vj(); + + const size_t degree = 3; + + DiscreteFunctionDPk<Dimension, R2x3> pk(mesh_v, degree); + + std::vector<R2x3> A = {R2x3{+1.0, +2.0, +3.0, +4.0, +5.0, +6.0}, // + R2x3{-1.2, -2.3, +7.2, +8.4, -5.0, +0.7}, // + R2x3{-2.1, -3.3, -2.7, -3.4, -0.5, -2.7}, // + R2x3{+6.2, -2.9, +3.1, -2.6, +1.5, +2.1}, // + R2x3{-2.6, -2.2, +4.2, -1.7, +8.5, -1.4}, // + R2x3{+1.7, +3.1, +3.0, +0.4, +3.4, +4.3}, // + R2x3{+2.5, +2.3, +4.7, -8.7, -5.0, +2.4}, // + R2x3{-3.6, -1.3, -1.3, -4.1, -0.5, -6.2}, // + R2x3{+6.4, -2.9, +2.3, -6.1, +1.5, -1.9}, // + R2x3{-7.3, -3.2, +4.1, +2.7, +8.5, -6.9}}; + + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) { + auto coefficients = pk.coefficients(cell_id); + for (size_t i = 0; i < coefficients.size(); ++i) { + coefficients[i] = A[i]; + } + }; + + DiscreteFunctionP0<double> delta_xj(mesh_v); + parallel_for( + mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { + const R2x3 Diff = pk[cell_id](xj[cell_id]) - A[0]; + delta_xj[cell_id] = l2Norm((transpose(Diff) * Diff) * TinyVector<3>(1, 1, 1)); + }); + + REQUIRE(max(delta_xj) == Catch::Approx(0).margin(1E-14)); + + DiscreteFunctionP0<double> delta(mesh_v); + parallel_for( + mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { + const double x0 = 0.2 * Vj[cell_id]; + const double y0 = 0.3 * Vj[cell_id]; + const R2x3 Diff = pk[cell_id](xj[cell_id] + R2{x0, y0}) // + - (A[0] // + + x0 * A[1] // + + x0 * x0 * A[2] // + + x0 * x0 * x0 * A[3] // + + y0 * A[4] // + + y0 * x0 * A[5] // + + y0 * x0 * x0 * A[6] // + + y0 * y0 * A[7] // + + y0 * y0 * x0 * A[8] // + + y0 * y0 * y0 * A[9]); + + delta[cell_id] = l2Norm((transpose(Diff) * Diff) * TinyVector<3>(1, 1, 1)); + }); + + REQUIRE(max(delta) == Catch::Approx(0.).margin(1E-14)); + } + + SECTION("3D") + { + constexpr size_t Dimension = 3; + + using R2x3 = TinyMatrix<2, 3>; + using R3 = TinyVector<3>; + + const std::shared_ptr mesh_v = MeshDataBaseForTests::get().hybrid3DMesh(); + const std::shared_ptr mesh = mesh_v->get<Mesh<Dimension>>(); + + auto xj = MeshDataManager::instance().getMeshData(*mesh).xj(); + auto Vj = MeshDataManager::instance().getMeshData(*mesh).Vj(); + + const size_t degree = 3; + + const std::vector<R2x3> A = {R2x3{-3.1, +0.7, -4.2, -6.6, -2.3, +7.2}, R2x3{-5.1, -4.5, +9.6, +0.8, -3.2, +3.7}, + R2x3{-2.4, +3.8, -2.5, +0.7, -6.5, -7.7}, R2x3{+3.2, +7.6, +7.8, -4.1, +1.6, +9.9}, + R2x3{-0.8, +6.1, -2.4, -1.1, -4.1, +0.6}, R2x3{+6.3, +1.0, +0.0, -9.4, +4.4, +1.2}, + R2x3{-1.1, +9.8, +10., -4.9, -2.4, -4.3}, R2x3{+6.9, +4.2, +8.8, +2.0, +3.4, +6.4}, + R2x3{+6.5, -6.3, +7.1, +8.7, -1.9, -9.7}, R2x3{+3.3, +1.5, -8.2, +8.1, +2.9, +3.3}, + R2x3{+6.9, -9.8, -2.5, -6.8, +0.9, +9.4}, R2x3{+4.3, +3.0, +2.2, -8.8, +3.0, -3.2}, + R2x3{-4.7, +9.0, +2.7, -0.3, -8.1, -8.6}, R2x3{+1.0, +1.7, -3.9, +7.8, -1.2, +5.6}, + R2x3{-2.1, +6.1, -8.7, +4.3, -1.9, -9.3}, R2x3{-0.3, -8.3, -9.7, +9.4, -9.7, +3.8}, + R2x3{+9.2, +7.1, +9.1, -9.1, +6.8, -5.2}, R2x3{-9.1, +4.8, +5.3, +9.4, -1.2, -9.2}, + R2x3{+1.3, -8.7, -1.2, +2.7, -1.8, -1.6}, R2x3{+1.0, -9.7, +1.0, +9.2, -0.1, -4.9}}; + + DiscreteFunctionDPk<Dimension, R2x3> pk(mesh_v, degree); + parallel_for( + mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { + auto coefficients = pk.coefficients(cell_id); + for (size_t i = 0; i < coefficients.size(); ++i) { + coefficients[i] = A[i]; + } + }); + + DiscreteFunctionP0<double> delta_xj(mesh_v); + parallel_for( + mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { + const R2x3 Diff = pk[cell_id](xj[cell_id]) - A[0]; + delta_xj[cell_id] = l2Norm(transpose(Diff) * Diff * TinyVector<3>{1, 1, 1}); + }); + + REQUIRE(max(delta_xj) == Catch::Approx(0.).margin(1E-14)); + + DiscreteFunctionP0<double> delta(mesh_v); + parallel_for( + mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { + const double x0 = +0.5 * Vj[cell_id]; + const double y0 = +0.3 * Vj[cell_id]; + const double z0 = -0.4 * Vj[cell_id]; + + const R2x3 Diff = pk[cell_id](xj[cell_id] + R3{x0, y0, z0}) // + - (A[0] // + + x0 * A[1] // + + x0 * x0 * A[2] // + + x0 * x0 * x0 * A[3] // + + y0 * A[4] // + + y0 * x0 * A[5] // + + y0 * x0 * x0 * A[6] // + + y0 * y0 * A[7] // + + y0 * y0 * x0 * A[8] // + + y0 * y0 * y0 * A[9] // + + z0 * A[10] // + + z0 * x0 * A[11] // + + z0 * x0 * x0 * A[12] // + + z0 * y0 * A[13] // + + z0 * y0 * x0 * A[14] // + + z0 * y0 * y0 * A[15] // + + z0 * z0 * A[16] // + + z0 * z0 * x0 * A[17] // + + z0 * z0 * y0 * A[18] // + + z0 * z0 * z0 * A[19] // + ); + + delta[cell_id] = l2Norm(transpose(Diff) * Diff * TinyVector<3>{1, 1, 1}); + }); + + REQUIRE(max(delta) == Catch::Approx(0.).margin(1E-14)); + } + } + + SECTION("degree and polynomial basis size") + { + SECTION("1D") + { + for (size_t degree = 0; degree < 10; ++degree) { + size_t polynomial_basis_dimension = + PolynomialCenteredCanonicalBasisView<1, double>::dimensionFromDegree(degree); + REQUIRE(polynomial_basis_dimension == degree + 1); + REQUIRE(PolynomialCenteredCanonicalBasisView<1, double>::degreeFromDimension(polynomial_basis_dimension) == + degree); + } + } + + SECTION("2D") + { + for (size_t degree = 0; degree < 10; ++degree) { + size_t polynomial_basis_dimension = + PolynomialCenteredCanonicalBasisView<2, double>::dimensionFromDegree(degree); + REQUIRE(2 * polynomial_basis_dimension == (degree + 1) * (degree + 2)); + REQUIRE(PolynomialCenteredCanonicalBasisView<2, double>::degreeFromDimension(polynomial_basis_dimension) == + degree); + } + + REQUIRE_THROWS_WITH((PolynomialCenteredCanonicalBasisView<2, double>::degreeFromDimension(2)), + "error: incorrect polynomial basis dimension"); + REQUIRE_THROWS_WITH((PolynomialCenteredCanonicalBasisView<2, double>::degreeFromDimension(4)), + "error: incorrect polynomial basis dimension"); + REQUIRE_THROWS_WITH((PolynomialCenteredCanonicalBasisView<2, double>::degreeFromDimension(5)), + "error: incorrect polynomial basis dimension"); + REQUIRE_THROWS_WITH((PolynomialCenteredCanonicalBasisView<2, double>::degreeFromDimension(7)), + "error: incorrect polynomial basis dimension"); + } + } +} diff --git a/tests/test_DiscreteFunctionDPkVector.cpp b/tests/test_DiscreteFunctionDPkVector.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6ea028072b05716d5dec5bfada758cb9539982b2 --- /dev/null +++ b/tests/test_DiscreteFunctionDPkVector.cpp @@ -0,0 +1,291 @@ +#include <catch2/catch_test_macros.hpp> +#include <catch2/matchers/catch_matchers_all.hpp> + +#include <MeshDataBaseForTests.hpp> +#include <mesh/Mesh.hpp> +#include <scheme/DiscreteFunctionDPkVector.hpp> +#include <scheme/DiscreteFunctionP0Vector.hpp> + +// clazy:excludeall=non-pod-global-static + +TEST_CASE("DiscreteFunctionDPkVector", "[scheme]") +{ + SECTION("Basic interface") + { + const std::shared_ptr mesh_2d_v = MeshDataBaseForTests::get().cartesian2DMesh(); + const std::shared_ptr mesh_2d = mesh_2d_v->get<Mesh<2>>(); + + const std::shared_ptr mesh_2d_2_v = MeshDataBaseForTests::get().hybrid2DMesh(); + const std::shared_ptr mesh_2d_2 = mesh_2d_2_v->get<Mesh<2>>(); + + DiscreteFunctionDPkVector<2, const double> R_dkp; + +#ifndef NDEBUG + REQUIRE_THROWS_WITH(R_dkp(CellId(0), 0), "DiscreteFunctionDPkVector is not built"); +#endif // NDEBUG + + { + DiscreteFunctionDPkVector<2, double> tmp0{mesh_2d_v, 2, 3}; + DiscreteFunctionDPkVector<2, double> tmp_R_dkp(std::move(tmp0)); + tmp_R_dkp.fill(2); + R_dkp = tmp_R_dkp; + } + +#ifndef NDEBUG + REQUIRE_THROWS_WITH(R_dkp(CellId(0), 10), "incorrect component number"); +#endif // NDEBUG + + REQUIRE(R_dkp.degree() == 2); + REQUIRE(R_dkp.numberOfComponents() == 3); + REQUIRE(R_dkp.numberOfCoefficientsPerComponent() == + PolynomialCenteredCanonicalBasisView<2, double>::dimensionFromDegree(2)); + + REQUIRE(min(R_dkp.cellArrays()) == 2); + REQUIRE(max(R_dkp.cellArrays()) == 2); + + REQUIRE(R_dkp.dataType() == ast_node_data_type_from<double>); + REQUIRE(R_dkp.meshVariant()->id() == mesh_2d->id()); + + DiscreteFunctionDPkVector<2, double> R_dkp2; + R_dkp2 = copy(R_dkp); + + REQUIRE(R_dkp2.degree() == 2); + REQUIRE(R_dkp2.numberOfComponents() == 3); + REQUIRE(R_dkp2.numberOfCoefficientsPerComponent() == + PolynomialCenteredCanonicalBasisView<2, double>::dimensionFromDegree(2)); + + REQUIRE(min(R_dkp2.cellArrays()) == 2); + REQUIRE(max(R_dkp2.cellArrays()) == 2); + + REQUIRE(R_dkp2.dataType() == ast_node_data_type_from<double>); + REQUIRE(R_dkp2.meshVariant()->id() == mesh_2d_v->id()); + + DiscreteFunctionDPkVector<2, double> R_dkp3(mesh_2d_2_v, 1, 2); + R_dkp3.fill(3); + + REQUIRE(R_dkp3.degree() == 1); + REQUIRE(R_dkp3.numberOfComponents() == 2); + + REQUIRE(min(R_dkp3.cellArrays()) == 3); + REQUIRE(max(R_dkp3.cellArrays()) == 3); + + REQUIRE(R_dkp3.dataType() == ast_node_data_type_from<double>); + REQUIRE(R_dkp3.meshVariant()->id() == mesh_2d_2_v->id()); + + DiscreteFunctionDPkVector<2, double> R_dkp4(mesh_2d_v, R_dkp2.degree(), R_dkp2.numberOfComponents(), + R_dkp2.cellArrays()); + + REQUIRE(min(R_dkp4.cellArrays()) == 2); + REQUIRE(max(R_dkp4.cellArrays()) == 2); + + R_dkp4.fill(5); + REQUIRE(min(R_dkp4.cellArrays()) == 5); + REQUIRE(max(R_dkp4.cellArrays()) == 5); + REQUIRE(min(R_dkp2.cellArrays()) == 5); + REQUIRE(max(R_dkp2.cellArrays()) == 5); + + copy_to(R_dkp, R_dkp4); + REQUIRE(min(R_dkp4.cellArrays()) == 2); + REQUIRE(max(R_dkp4.cellArrays()) == 2); + REQUIRE(min(R_dkp2.cellArrays()) == 2); + REQUIRE(max(R_dkp2.cellArrays()) == 2); + +#ifndef NDEBUG + REQUIRE_THROWS_WITH(copy_to(R_dkp, R_dkp3), "copy_to target must use the same mesh"); + + DiscreteFunctionDPkVector<2, double> R_dkp5(mesh_2d, 3, 3); + REQUIRE_THROWS_WITH(copy_to(R_dkp, R_dkp5), "copy_to target must have the same degree"); + R_dkp5 = DiscreteFunctionDPkVector<2, double>{mesh_2d, 2, 1}; + REQUIRE_THROWS_WITH(copy_to(R_dkp, R_dkp5), "copy_to target must have the same number of components"); + + REQUIRE_THROWS_WITH((DiscreteFunctionDPkVector<2, double>{mesh_2d_2, 2, 3, R_dkp2.cellArrays()}), + "cell_array is built on different connectivity"); +#endif // NDEBUG + } + + SECTION("1D") + { + constexpr size_t Dimension = 1; + + using R1 = TinyVector<1>; + + const std::shared_ptr mesh_v = MeshDataBaseForTests::get().cartesian1DMesh(); + const std::shared_ptr mesh = mesh_v->get<Mesh<Dimension>>(); + + auto xj = MeshDataManager::instance().getMeshData(*mesh).xj(); + auto Vj = MeshDataManager::instance().getMeshData(*mesh).Vj(); + + const size_t degree = 4; + + DiscreteFunctionDPkVector<Dimension, double> pk(mesh_v, degree, 3); + + std::vector<double> a = {+1.0, +1.4, -6.2, +2.7, +3.1, // + +2.0, -2.3, +5.4, -2.7, -1.3, // + -1.2, +2.3, +3.1, +1.6, +2.3}; + + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) { + auto coefficients = pk.coefficients(cell_id); + for (size_t i = 0; i < a.size(); ++i) { + coefficients[i] = a[i]; + } + }; + + const size_t number_of_coefs = pk.numberOfCoefficientsPerComponent(); + + for (size_t i = 0; i < pk.numberOfComponents(); ++i) { + DiscreteFunctionP0<double> p_xj(mesh_v); + parallel_for( + mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { p_xj[cell_id] = pk(cell_id, i)(xj[cell_id]); }); + + REQUIRE(max(p_xj) == a[i * number_of_coefs]); + REQUIRE(min(p_xj) == a[i * number_of_coefs]); + } + + for (size_t i = 0; i < pk.numberOfComponents(); ++i) { + DiscreteFunctionP0<double> delta(mesh_v); + parallel_for( + mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { + const double x0 = 0.3 * Vj[cell_id]; + delta[cell_id] = pk(cell_id, i)(xj[cell_id] + R1{x0}) // + - (a[i * number_of_coefs] + // + x0 * a[i * number_of_coefs + 1] + // + x0 * x0 * a[i * number_of_coefs + 2] + // + x0 * x0 * x0 * a[i * number_of_coefs + 3] + // + x0 * x0 * x0 * x0 * a[i * number_of_coefs + 4]); + }); + + REQUIRE(max(delta) == Catch::Approx(0.).margin(1E-14)); + REQUIRE(min(delta) == Catch::Approx(0.).margin(1E-14)); + } + } + + SECTION("2D") + { + constexpr size_t Dimension = 2; + + using R2 = TinyVector<2>; + + const std::shared_ptr mesh_v = MeshDataBaseForTests::get().cartesian2DMesh(); + const std::shared_ptr mesh = mesh_v->get<Mesh<Dimension>>(); + + auto xj = MeshDataManager::instance().getMeshData(*mesh).xj(); + auto Vj = MeshDataManager::instance().getMeshData(*mesh).Vj(); + + const size_t degree = 3; + + DiscreteFunctionDPkVector<Dimension, double> pk(mesh_v, degree, 2); + + const std::vector<double> a = {+1.0, +1.4, -6.2, +3.5, -2.3, +5.2, +6.1, +2.3, +0.5, -1.3, // + -1.3, +2.3, +2.7, +1.7, +2.1, -2.7, -5.3, +1.2, +1.3, +3.2}; + + for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) { + auto coefficients = pk.coefficients(cell_id); + for (size_t i = 0; i < coefficients.size(); ++i) { + coefficients[i] = a[i]; + } + } + + const size_t number_of_coefs = pk.numberOfCoefficientsPerComponent(); + + for (size_t i = 0; i < pk.numberOfComponents(); ++i) { + DiscreteFunctionP0<double> p_xj(mesh_v); + parallel_for( + mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { p_xj[cell_id] = pk(cell_id, i)(xj[cell_id]); }); + + REQUIRE(max(p_xj) == a[i * number_of_coefs]); + REQUIRE(min(p_xj) == a[i * number_of_coefs]); + } + + for (size_t i = 0; i < pk.numberOfComponents(); ++i) { + DiscreteFunctionP0<double> error(mesh_v); + parallel_for( + mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { + const double x0 = 0.2 * Vj[cell_id]; + const double y0 = 0.3 * Vj[cell_id]; + error[cell_id] // + = std::abs(pk(cell_id, i)(xj[cell_id] + R2{x0, y0}) // + - (a[i * number_of_coefs] // + + x0 * a[i * number_of_coefs + 1] // + + x0 * x0 * a[i * number_of_coefs + 2] // + + x0 * x0 * x0 * a[i * number_of_coefs + 3] // + + y0 * a[i * number_of_coefs + 4] // + + y0 * x0 * a[i * number_of_coefs + 5] // + + y0 * x0 * x0 * a[i * number_of_coefs + 6] // + + y0 * y0 * a[i * number_of_coefs + 7] // + + y0 * y0 * x0 * a[i * number_of_coefs + 8] // + + y0 * y0 * y0 * a[i * number_of_coefs + 9])); + }); + + REQUIRE(max(error) == Catch::Approx(0.).margin(1E-14)); + } + } + + SECTION("3D") + { + constexpr size_t Dimension = 3; + + using R3 = TinyVector<3>; + + const std::shared_ptr mesh_v = MeshDataBaseForTests::get().cartesian3DMesh(); + const std::shared_ptr mesh = mesh_v->get<Mesh<Dimension>>(); + + auto xj = MeshDataManager::instance().getMeshData(*mesh).xj(); + auto Vj = MeshDataManager::instance().getMeshData(*mesh).Vj(); + + const size_t degree = 3; + + DiscreteFunctionDPkVector<Dimension, double> pk(mesh_v, degree, 1); + + const std::vector<double> a = {+1.0, +1.4, -6.2, +3.5, -2.3, +5.2, +6.1, +2.3, +0.5, -1.3, // + +2.8, -8.4, +9.5, +4.0, +4.3, +7.2, -9.1, +6.8, +6.7, +9.2}; + + parallel_for( + mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { + auto coefficients = pk.coefficients(cell_id); + for (size_t i = 0; i < coefficients.size(); ++i) { + coefficients[i] = a[i]; + } + }); + + DiscreteFunctionP0<double> delta_xj(mesh_v); + parallel_for( + mesh->numberOfCells(), + PUGS_LAMBDA(CellId cell_id) { delta_xj[cell_id] = std::abs(pk(cell_id, 0)(xj[cell_id]) - a[0]); }); + + REQUIRE(max(delta_xj) == Catch::Approx(0.).margin(1E-14)); + + DiscreteFunctionP0<double> delta(mesh_v); + parallel_for( + mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { + const double x0 = +0.5 * Vj[cell_id]; + const double y0 = +0.3 * Vj[cell_id]; + const double z0 = -0.4 * Vj[cell_id]; + + delta[cell_id] = pk(cell_id, 0)(xj[cell_id] + R3{x0, y0, z0}) // + - (a[0] // + + x0 * a[1] // + + x0 * x0 * a[2] // + + x0 * x0 * x0 * a[3] // + + y0 * a[4] // + + y0 * x0 * a[5] // + + y0 * x0 * x0 * a[6] // + + y0 * y0 * a[7] // + + y0 * y0 * x0 * a[8] // + + y0 * y0 * y0 * a[9] // + + z0 * a[10] // + + z0 * x0 * a[11] // + + z0 * x0 * x0 * a[12] // + + z0 * y0 * a[13] // + + z0 * y0 * x0 * a[14] // + + z0 * y0 * y0 * a[15] // + + z0 * z0 * a[16] // + + z0 * z0 * x0 * a[17] // + + z0 * z0 * y0 * a[18] // + + z0 * z0 * z0 * a[19] // + ); + }); + + REQUIRE(max(delta) == Catch::Approx(0.).margin(1E-14)); + REQUIRE(min(delta) == Catch::Approx(0.).margin(1E-14)); + } +} diff --git a/tests/test_Givens.cpp b/tests/test_Givens.cpp new file mode 100644 index 0000000000000000000000000000000000000000..aec60a82b5b944706eb5ec1e0ab02dac4d8c407f --- /dev/null +++ b/tests/test_Givens.cpp @@ -0,0 +1,216 @@ +#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("classic (single vector)") + { + 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; + + 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; + + 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))); + } + } + + SECTION("generalized (vector collection)") + { + 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<const double> X_exact = [] { + SmallMatrix<double> Y{5, 3}; + Y(0, 0) = 1; + Y(1, 0) = 3; + Y(2, 0) = 2; + Y(3, 0) = 4; + Y(4, 0) = 5; + + Y(0, 1) = -3; + Y(1, 1) = 6; + Y(2, 1) = 1; + Y(3, 1) = -2; + Y(4, 1) = 4; + + Y(0, 2) = -2; + Y(1, 2) = -4; + Y(2, 2) = 2; + Y(3, 2) = 6; + Y(4, 2) = 7; + + return Y; + }(); + + SmallMatrix<double> b = A * X_exact; + + SmallMatrix<double> X{5, 3}; + + Givens::solveCollection(A, X, b); + SmallMatrix<double> error = X - X_exact; + + double max_error = 0; + for (size_t i = 0; i < error.numberOfRows(); ++i) { + for (size_t j = 0; j < error.numberOfColumns(); ++j) { + max_error = std::max(max_error, std::abs(error(i, j))); + } + } + REQUIRE(max_error < 1E-10); + } + + 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<const double> X_exact = [] { + SmallMatrix<double> Y{3, 4}; + Y(0, 0) = 1; + Y(1, 0) = 3; + Y(2, 0) = 2; + + Y(0, 1) = -1; + Y(1, 1) = 1.5; + Y(2, 1) = 5; + + Y(0, 2) = -3; + Y(1, 2) = 1; + Y(2, 2) = 4; + + Y(0, 3) = 0.7; + Y(1, 3) = -3.2; + Y(2, 3) = 2.5; + + return Y; + }(); + + SmallMatrix<double> B = A * X_exact; + + SmallMatrix<double> X{3, 4}; + X = zero; + + Givens::solveCollection(A, X, B); + SmallMatrix<double> error = X - X_exact; + + double max_error = 0; + for (size_t i = 0; i < error.numberOfRows(); ++i) { + for (size_t j = 0; j < error.numberOfColumns(); ++j) { + max_error = std::max(max_error, std::abs(error(i, j))); + } + } + REQUIRE(max_error < 1E-10); + } + } +} diff --git a/tests/test_PolynomialReconstructionDescriptor.cpp b/tests/test_PolynomialReconstructionDescriptor.cpp new file mode 100644 index 0000000000000000000000000000000000000000..32d3c4af12c48d6c7f69b2b250838bea7e3cf19e --- /dev/null +++ b/tests/test_PolynomialReconstructionDescriptor.cpp @@ -0,0 +1,138 @@ +#include <catch2/catch_approx.hpp> +#include <catch2/catch_test_macros.hpp> +#include <catch2/matchers/catch_matchers_all.hpp> + +#include <mesh/NamedBoundaryDescriptor.hpp> +#include <mesh/NumberedBoundaryDescriptor.hpp> +#include <scheme/PolynomialReconstructionDescriptor.hpp> + +// clazy:excludeall=non-pod-global-static + +TEST_CASE("PolynomialReconstructionDescriptor", "[scheme]") +{ + SECTION("degree 1") + { + PolynomialReconstructionDescriptor descriptor{IntegrationMethodType::cell_center, 1}; + + REQUIRE(descriptor.degree() == 1); + REQUIRE(descriptor.stencilDescriptor().numberOfLayers() == 1); + REQUIRE(descriptor.stencilDescriptor().connectionType() == StencilDescriptor::ConnectionType::by_nodes); + REQUIRE(descriptor.symmetryBoundaryDescriptorList().size() == 0); + + REQUIRE(descriptor.preconditioning() == true); + REQUIRE(descriptor.rowWeighting() == true); + } + + SECTION("degree 4") + { + PolynomialReconstructionDescriptor descriptor{IntegrationMethodType::cell_center, 4}; + + REQUIRE(descriptor.degree() == 4); + REQUIRE(descriptor.stencilDescriptor().numberOfLayers() == 4); + REQUIRE(descriptor.stencilDescriptor().connectionType() == StencilDescriptor::ConnectionType::by_nodes); + REQUIRE(descriptor.symmetryBoundaryDescriptorList().size() == 0); + + REQUIRE(descriptor.preconditioning() == true); + REQUIRE(descriptor.rowWeighting() == true); + } + + SECTION("degree and stencil") + { + StencilDescriptor sd{2, StencilDescriptor::ConnectionType::by_faces}; + + PolynomialReconstructionDescriptor descriptor{IntegrationMethodType::cell_center, 1, sd}; + + REQUIRE(descriptor.degree() == 1); + REQUIRE(descriptor.stencilDescriptor().numberOfLayers() == 2); + REQUIRE(descriptor.stencilDescriptor().connectionType() == StencilDescriptor::ConnectionType::by_faces); + REQUIRE(descriptor.symmetryBoundaryDescriptorList().size() == 0); + + REQUIRE(descriptor.preconditioning() == true); + REQUIRE(descriptor.rowWeighting() == true); + } + + SECTION("degree and symmetries") + { + std::vector<std::shared_ptr<const IBoundaryDescriptor>> bc_list; + bc_list.push_back(std::make_shared<NamedBoundaryDescriptor>("XMIN")); + bc_list.push_back(std::make_shared<NamedBoundaryDescriptor>("YMIN")); + bc_list.push_back(std::make_shared<NumberedBoundaryDescriptor>(2)); + + PolynomialReconstructionDescriptor descriptor{IntegrationMethodType::cell_center, 2, bc_list}; + + REQUIRE(descriptor.degree() == 2); + REQUIRE(descriptor.stencilDescriptor().numberOfLayers() == 2); + REQUIRE(descriptor.stencilDescriptor().connectionType() == StencilDescriptor::ConnectionType::by_nodes); + REQUIRE(descriptor.symmetryBoundaryDescriptorList().size() == 3); + + REQUIRE(descriptor.symmetryBoundaryDescriptorList()[0]->type() == IBoundaryDescriptor::Type::named); + REQUIRE(descriptor.symmetryBoundaryDescriptorList()[1]->type() == IBoundaryDescriptor::Type::named); + REQUIRE(descriptor.symmetryBoundaryDescriptorList()[2]->type() == IBoundaryDescriptor::Type::numbered); + + REQUIRE(descriptor.preconditioning() == true); + REQUIRE(descriptor.rowWeighting() == true); + } + + SECTION("degree, stencil and symmetries") + { + StencilDescriptor sd{3, StencilDescriptor::ConnectionType::by_edges}; + + std::vector<std::shared_ptr<const IBoundaryDescriptor>> bc_list; + bc_list.push_back(std::make_shared<NamedBoundaryDescriptor>("XMIN")); + bc_list.push_back(std::make_shared<NumberedBoundaryDescriptor>(2)); + bc_list.push_back(std::make_shared<NamedBoundaryDescriptor>("YMIN")); + + PolynomialReconstructionDescriptor descriptor{IntegrationMethodType::cell_center, 1, sd, bc_list}; + + REQUIRE(descriptor.degree() == 1); + REQUIRE(descriptor.stencilDescriptor().numberOfLayers() == 3); + REQUIRE(descriptor.stencilDescriptor().connectionType() == StencilDescriptor::ConnectionType::by_edges); + REQUIRE(descriptor.symmetryBoundaryDescriptorList().size() == 3); + + REQUIRE(descriptor.symmetryBoundaryDescriptorList()[0]->type() == IBoundaryDescriptor::Type::named); + REQUIRE(descriptor.symmetryBoundaryDescriptorList()[1]->type() == IBoundaryDescriptor::Type::numbered); + REQUIRE(descriptor.symmetryBoundaryDescriptorList()[2]->type() == IBoundaryDescriptor::Type::named); + + REQUIRE(descriptor.preconditioning() == true); + REQUIRE(descriptor.rowWeighting() == true); + } + + SECTION("utlities") + { + PolynomialReconstructionDescriptor descriptor{IntegrationMethodType::cell_center, 3}; + + REQUIRE(descriptor.degree() == 3); + REQUIRE(descriptor.preconditioning() == true); + REQUIRE(descriptor.rowWeighting() == true); + + SECTION("set preconditioning") + { + descriptor.setPreconditioning(false); + + REQUIRE(descriptor.degree() == 3); + REQUIRE(descriptor.preconditioning() == false); + REQUIRE(descriptor.rowWeighting() == true); + + descriptor.setPreconditioning(true); + + REQUIRE(descriptor.degree() == 3); + REQUIRE(descriptor.preconditioning() == true); + REQUIRE(descriptor.rowWeighting() == true); + } + + SECTION("set row weighting") + { + descriptor.setRowWeighting(false); + + REQUIRE(descriptor.degree() == 3); + REQUIRE(descriptor.preconditioning() == true); + REQUIRE(descriptor.rowWeighting() == false); + + descriptor.setRowWeighting(true); + + REQUIRE(descriptor.degree() == 3); + REQUIRE(descriptor.preconditioning() == true); + REQUIRE(descriptor.rowWeighting() == true); + } + } +} diff --git a/tests/test_PolynomialReconstruction_degree_1.cpp b/tests/test_PolynomialReconstruction_degree_1.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fb2c7b2ec4a56239e37314704b2cc30122182e8c --- /dev/null +++ b/tests/test_PolynomialReconstruction_degree_1.cpp @@ -0,0 +1,800 @@ +#include <catch2/catch_approx.hpp> +#include <catch2/catch_test_macros.hpp> +#include <catch2/matchers/catch_matchers_all.hpp> + +#include <utils/PugsAssert.hpp> + +#include <mesh/Mesh.hpp> +#include <mesh/NamedBoundaryDescriptor.hpp> +#include <scheme/DiscreteFunctionDPkVariant.hpp> +#include <scheme/DiscreteFunctionP0.hpp> +#include <scheme/DiscreteFunctionVariant.hpp> +#include <scheme/PolynomialReconstruction.hpp> + +#include <DiscreteFunctionDPkForTests.hpp> +#include <MeshDataBaseForTests.hpp> + +// clazy:excludeall=non-pod-global-static + +TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]") +{ + constexpr size_t degree = 1; + + SECTION("without symmetries") + { + std::vector<PolynomialReconstructionDescriptor> descriptor_list = + {PolynomialReconstructionDescriptor{IntegrationMethodType::cell_center, degree}, + PolynomialReconstructionDescriptor{IntegrationMethodType::element, degree}, + PolynomialReconstructionDescriptor{IntegrationMethodType::boundary, degree}}; + + for (auto descriptor : descriptor_list) { + SECTION(name(descriptor.integrationMethodType())) + { + SECTION("1D") + { + using R1 = TinyVector<1>; + + SECTION("R data") + { + 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 R_exact = [](const R1& x) { return 2.3 + 1.7 * x[0]; }; + + DiscreteFunctionP0 fh = test_only::exact_projection(mesh, degree, std::function(R_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(fh); + + auto dpk_fh = reconstructions[0]->get<DiscreteFunctionDPk<1, const double>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_fh, std::function(R_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14)); + } + } + } + + SECTION("R^3 data") + { + using R3 = TinyVector<3>; + + 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 R3_exact = [](const R1& x) -> R3 { + return R3{+2.3 + 1.7 * x[0], // + +1.4 - 0.6 * x[0], // + -0.2 + 3.1 * x[0]}; + }; + + DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R3_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(uh); + + auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<1, const R3>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_uh, std::function(R3_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14)); + } + } + } + + SECTION("R^3x3 data") + { + using R3x3 = TinyMatrix<3, 3>; + + 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 R3x3_exact = [](const R1& x) -> R3x3 { + return R3x3{ + +2.3 + 1.7 * x[0], -1.7 + 2.1 * x[0], +1.4 - 0.6 * x[0], // + +2.4 - 2.3 * x[0], -0.2 + 3.1 * x[0], -3.2 - 3.6 * x[0], + -4.1 + 3.1 * x[0], +0.8 + 2.9 * x[0], -1.6 + 2.3 * x[0], + }; + }; + + DiscreteFunctionP0 Ah = test_only::exact_projection(mesh, degree, std::function(R3x3_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Ah); + + auto dpk_Ah = reconstructions[0]->get<DiscreteFunctionDPk<1, const R3x3>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_Ah, std::function(R3x3_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14)); + } + } + } + + SECTION("R vector data") + { + for (auto named_mesh : MeshDataBaseForTests::get().all1DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<1>>(); + auto& mesh = *p_mesh; + std::array<std::function<double(const R1&)>, 3> vector_exact = + {[](const R1& x) -> double { return +2.3 + 1.7 * x[0]; }, + [](const R1& x) -> double { return -1.7 + 2.1 * x[0]; }, + [](const R1& x) -> double { return +1.4 - 0.6 * x[0]; }}; + + DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh); + auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<1, const double>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14)); + } + } + } + + SECTION("R3 vector data") + { + using R3 = TinyVector<3>; + + for (auto named_mesh : MeshDataBaseForTests::get().all1DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<1>>(); + auto& mesh = *p_mesh; + + std::array<std::function<R3(const R1&)>, 2> vector_exact = + {[](const R1& x) -> R3 { + return R3{+2.3 + 1.7 * x[0], -1.7 + 2.1 * x[0], +1.4 - 0.6 * x[0]}; + }, + [](const R1& x) -> R3 { + return R3{+1.6 + 0.7 * x[0], -2.1 + 1.2 * x[0], +1.1 - 0.3 * x[0]}; + }}; + + DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh); + + auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<1, const R3>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14)); + } + } + } + + SECTION("list of various types") + { + using R3x3 = TinyMatrix<3>; + using R3 = TinyVector<3>; + + 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 R_exact = [](const R1& x) { return 2.3 + 1.7 * x[0]; }; + + auto R3_exact = [](const R1& x) -> R3 { + return R3{+2.3 + 1.7 * x[0], // + +1.4 - 0.6 * x[0], // + -0.2 + 3.1 * x[0]}; + }; + + auto R3x3_exact = [](const R1& x) -> R3x3 { + return R3x3{ + +2.3 + 1.7 * x[0], -1.7 + 2.1 * x[0], +1.4 - 0.6 * x[0], // + +2.4 - 2.3 * x[0], -0.2 + 3.1 * x[0], -3.2 - 3.6 * x[0], + -4.1 + 3.1 * x[0], +0.8 + 2.9 * x[0], -1.6 + 2.3 * x[0], + }; + }; + + std::array<std::function<double(const R1&)>, 3> vector_exact = + {[](const R1& x) -> double { return +2.3 + 1.7 * x[0]; }, + [](const R1& x) -> double { return -1.7 + 2.1 * x[0]; }, + [](const R1& x) -> double { return +1.4 - 0.6 * x[0]; }}; + + DiscreteFunctionP0 fh = test_only::exact_projection(mesh, degree, std::function(R_exact)); + DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R3_exact)); + DiscreteFunctionP0 Ah = test_only::exact_projection(mesh, degree, std::function(R3x3_exact)); + DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact); + + auto reconstructions = + PolynomialReconstruction{descriptor}.build(std::make_shared<DiscreteFunctionVariant>(fh), uh, + std::make_shared<DiscreteFunctionP0<R3x3>>(Ah), + DiscreteFunctionVariant(Vh)); + + { + auto dpk_fh = reconstructions[0]->get<DiscreteFunctionDPk<1, const double>>(); + double max_error = test_only::max_reconstruction_error(mesh, dpk_fh, std::function(R_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14)); + } + + { + auto dpk_uh = reconstructions[1]->get<DiscreteFunctionDPk<1, const R3>>(); + double max_error = test_only::max_reconstruction_error(mesh, dpk_uh, std::function(R3_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14)); + } + + { + auto dpk_Ah = reconstructions[2]->get<DiscreteFunctionDPk<1, const R3x3>>(); + double max_error = test_only::max_reconstruction_error(mesh, dpk_Ah, std::function(R3x3_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14)); + } + + { + auto dpk_Vh = reconstructions[3]->get<DiscreteFunctionDPkVector<1, const double>>(); + double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14)); + } + } + } + } + } + + SECTION("2D") + { + using R2 = TinyVector<2>; + + SECTION("R data") + { + for (auto named_mesh : MeshDataBaseForTests::get().all2DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<2>>(); + auto& mesh = *p_mesh; + + auto R_exact = [](const R2& x) { return 2.3 + 1.7 * x[0] - 1.3 * x[1]; }; + + DiscreteFunctionP0 fh = test_only::exact_projection(mesh, degree, std::function(R_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(fh); + + auto dpk_fh = reconstructions[0]->get<DiscreteFunctionDPk<2, const double>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_fh, std::function(R_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14)); + } + } + } + + SECTION("R^3 data") + { + using R3 = TinyVector<3>; + + for (auto named_mesh : MeshDataBaseForTests::get().all2DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<2>>(); + auto& mesh = *p_mesh; + + auto R3_exact = [](const R2& x) -> R3 { + return R3{+2.3 + 1.7 * x[0] - 2.2 * x[1], // + +1.4 - 0.6 * x[0] + 1.3 * x[1], // + -0.2 + 3.1 * x[0] - 1.1 * x[1]}; + }; + + DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R3_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(uh); + + auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<2, const R3>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_uh, std::function(R3_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14)); + } + } + } + + SECTION("R^2x2 data") + { + using R2x2 = TinyMatrix<2, 2>; + + for (auto named_mesh : MeshDataBaseForTests::get().all2DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<2>>(); + auto& mesh = *p_mesh; + + auto R2x2_exact = [](const R2& x) -> R2x2 { + return R2x2{+2.3 + 1.7 * x[0] + 1.2 * x[1], -1.7 + 2.1 * x[0] - 2.2 * x[1], // + +1.4 - 0.6 * x[0] - 2.1 * x[1], +2.4 - 2.3 * x[0] + 1.3 * x[1]}; + }; + + DiscreteFunctionP0 Ah = test_only::exact_projection(mesh, degree, std::function(R2x2_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Ah); + + auto dpk_Ah = reconstructions[0]->get<DiscreteFunctionDPk<2, const R2x2>>(); + double max_error = test_only::max_reconstruction_error(mesh, dpk_Ah, std::function(R2x2_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14)); + } + } + } + + SECTION("vector data") + { + for (auto named_mesh : MeshDataBaseForTests::get().all2DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<2>>(); + auto& mesh = *p_mesh; + + std::array<std::function<double(const R2&)>, 4> vector_exact = + {[](const R2& x) -> double { return +2.3 + 1.7 * x[0] + 1.2 * x[1]; }, + [](const R2& x) -> double { return -1.7 + 2.1 * x[0] - 2.2 * x[1]; }, + [](const R2& x) -> double { return +1.4 - 0.6 * x[0] - 2.1 * x[1]; }, + [](const R2& x) -> double { return +2.4 - 2.3 * x[0] + 1.3 * x[1]; }}; + + DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh); + + auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<2, const double>>(); + double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14)); + } + } + } + } + + SECTION("3D") + { + using R3 = TinyVector<3>; + + SECTION("R data") + { + for (auto named_mesh : MeshDataBaseForTests::get().all3DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<3>>(); + auto& mesh = *p_mesh; + + auto R_exact = [](const R3& x) { return 2.3 + 1.7 * x[0] - 1.3 * x[1] + 2.1 * x[2]; }; + + DiscreteFunctionP0 fh = test_only::exact_projection(mesh, degree, std::function(R_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(fh); + + auto dpk_fh = reconstructions[0]->get<DiscreteFunctionDPk<3, const double>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_fh, std::function(R_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + + SECTION("R^3 data") + { + for (auto named_mesh : MeshDataBaseForTests::get().all3DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<3>>(); + auto& mesh = *p_mesh; + + auto R3_exact = [](const R3& x) -> R3 { + return R3{+2.3 + 1.7 * x[0] - 2.2 * x[1] + 1.8 * x[2], // + +1.4 - 0.6 * x[0] + 1.3 * x[1] - 3.7 * x[2], // + -0.2 + 3.1 * x[0] - 1.1 * x[1] + 1.9 * x[2]}; + }; + + DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R3_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(uh); + + auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<3, const R3>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_uh, std::function(R3_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + + SECTION("R^2x2 data") + { + using R2x2 = TinyMatrix<2, 2>; + + for (auto named_mesh : MeshDataBaseForTests::get().all3DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<3>>(); + auto& mesh = *p_mesh; + + auto R2x2_exact = [](const R3& x) -> R2x2 { + return R2x2{+2.3 + 1.7 * x[0] + 1.2 * x[1] - 1.3 * x[2], -1.7 + 2.1 * x[0] - 2.2 * x[1] - 2.4 * x[2], + // + +2.4 - 2.3 * x[0] + 1.3 * x[1] + 1.4 * x[2], -0.2 + 3.1 * x[0] + 0.8 * x[1] - 1.8 * x[2]}; + }; + + DiscreteFunctionP0 Ah = test_only::exact_projection(mesh, degree, std::function(R2x2_exact)); + + descriptor.setRowWeighting(false); + auto reconstructions = PolynomialReconstruction{descriptor}.build(Ah); + + auto dpk_Ah = reconstructions[0]->get<DiscreteFunctionDPk<3, const R2x2>>(); + double max_error = test_only::max_reconstruction_error(mesh, dpk_Ah, std::function(R2x2_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + + SECTION("vector data") + { + for (auto named_mesh : MeshDataBaseForTests::get().all3DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<3>>(); + auto& mesh = *p_mesh; + + std::array<std::function<double(const R3&)>, 4> vector_exact = + {[](const R3& x) -> double { return +2.3 + 1.7 * x[0] + 1.2 * x[1] - 1.3 * x[2]; }, + [](const R3& x) -> double { return -1.7 + 2.1 * x[0] - 2.2 * x[1] - 2.4 * x[2]; }, + [](const R3& x) -> double { return +2.4 - 2.3 * x[0] + 1.3 * x[1] + 1.4 * x[2]; }, + [](const R3& x) -> double { return -0.2 + 3.1 * x[0] + 0.8 * x[1] - 1.8 * x[2]; }}; + + DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact); + + descriptor.setPreconditioning(false); + auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh); + + auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<3, const double>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + } + + SECTION("errors") + { + auto p_mesh1 = MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>(); + DiscreteFunctionP0<double> f1{p_mesh1}; + + auto p_mesh2 = MeshDataBaseForTests::get().cartesian1DMesh()->get<Mesh<1>>(); + DiscreteFunctionP0<double> f2{p_mesh2}; + + REQUIRE_THROWS_WITH(PolynomialReconstruction{descriptor}.build(f1, f2), + "error: cannot reconstruct functions living of different meshes simultaneously"); + } + } + } + } + + SECTION("with symmetries") + { + SECTION("errors") + { + SECTION("1D") + { + auto p_mesh = MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>(); + + PolynomialReconstructionDescriptor descriptor{IntegrationMethodType::element, degree, + std::vector<std::shared_ptr<const IBoundaryDescriptor>>{ + std::make_shared<NamedBoundaryDescriptor>("XMIN")}}; + + REQUIRE_THROWS_WITH(PolynomialReconstruction{descriptor}.build(DiscreteFunctionP0<TinyVector<2>>{p_mesh}), + "error: cannot symmetrize vectors of dimension 2 using a mesh of dimension 1"); + + REQUIRE_THROWS_WITH(PolynomialReconstruction{descriptor}.build(DiscreteFunctionP0<TinyVector<3>>{p_mesh}), + "error: cannot symmetrize vectors of dimension 3 using a mesh of dimension 1"); + + REQUIRE_THROWS_WITH(PolynomialReconstruction{descriptor}.build( + DiscreteFunctionP0Vector<TinyVector<2>>{p_mesh, 1}), + "error: cannot symmetrize vectors of dimension 2 using a mesh of dimension 1"); + + REQUIRE_THROWS_WITH(PolynomialReconstruction{descriptor}.build( + DiscreteFunctionP0Vector<TinyVector<3>>{p_mesh, 1}), + "error: cannot symmetrize vectors of dimension 3 using a mesh of dimension 1"); + + REQUIRE_THROWS_WITH(PolynomialReconstruction{descriptor}.build(DiscreteFunctionP0<TinyMatrix<2>>{p_mesh}), + "error: cannot symmetrize matrices of dimensions 2x2 using a mesh of dimension 1"); + + REQUIRE_THROWS_WITH(PolynomialReconstruction{descriptor}.build(DiscreteFunctionP0<TinyMatrix<3>>{p_mesh}), + "error: cannot symmetrize matrices of dimensions 3x3 using a mesh of dimension 1"); + + REQUIRE_THROWS_WITH(PolynomialReconstruction{descriptor}.build( + DiscreteFunctionP0Vector<TinyMatrix<2>>{p_mesh, 1}), + "error: cannot symmetrize matrices of dimensions 2x2 using a mesh of dimension 1"); + + REQUIRE_THROWS_WITH(PolynomialReconstruction{descriptor}.build( + DiscreteFunctionP0Vector<TinyMatrix<3>>{p_mesh, 1}), + "error: cannot symmetrize matrices of dimensions 3x3 using a mesh of dimension 1"); + } + + SECTION("2D") + { + auto p_mesh = MeshDataBaseForTests::get().hybrid2DMesh()->get<Mesh<2>>(); + + PolynomialReconstructionDescriptor descriptor{IntegrationMethodType::element, degree, + std::vector<std::shared_ptr<const IBoundaryDescriptor>>{ + std::make_shared<NamedBoundaryDescriptor>("XMIN")}}; + + REQUIRE_THROWS_WITH(PolynomialReconstruction{descriptor}.build(DiscreteFunctionP0<TinyVector<1>>{p_mesh}), + "error: cannot symmetrize vectors of dimension 1 using a mesh of dimension 2"); + + REQUIRE_THROWS_WITH(PolynomialReconstruction{descriptor}.build(DiscreteFunctionP0<TinyVector<3>>{p_mesh}), + "error: cannot symmetrize vectors of dimension 3 using a mesh of dimension 2"); + + REQUIRE_THROWS_WITH(PolynomialReconstruction{descriptor}.build( + DiscreteFunctionP0Vector<TinyVector<1>>{p_mesh, 1}), + "error: cannot symmetrize vectors of dimension 1 using a mesh of dimension 2"); + + REQUIRE_THROWS_WITH(PolynomialReconstruction{descriptor}.build( + DiscreteFunctionP0Vector<TinyVector<3>>{p_mesh, 1}), + "error: cannot symmetrize vectors of dimension 3 using a mesh of dimension 2"); + + REQUIRE_THROWS_WITH(PolynomialReconstruction{descriptor}.build(DiscreteFunctionP0<TinyMatrix<1>>{p_mesh}), + "error: cannot symmetrize matrices of dimensions 1x1 using a mesh of dimension 2"); + + REQUIRE_THROWS_WITH(PolynomialReconstruction{descriptor}.build(DiscreteFunctionP0<TinyMatrix<3>>{p_mesh}), + "error: cannot symmetrize matrices of dimensions 3x3 using a mesh of dimension 2"); + + REQUIRE_THROWS_WITH(PolynomialReconstruction{descriptor}.build( + DiscreteFunctionP0Vector<TinyMatrix<1>>{p_mesh, 1}), + "error: cannot symmetrize matrices of dimensions 1x1 using a mesh of dimension 2"); + + REQUIRE_THROWS_WITH(PolynomialReconstruction{descriptor}.build( + DiscreteFunctionP0Vector<TinyMatrix<3>>{p_mesh, 1}), + "error: cannot symmetrize matrices of dimensions 3x3 using a mesh of dimension 2"); + } + + SECTION("3D") + { + auto p_mesh = MeshDataBaseForTests::get().hybrid3DMesh()->get<Mesh<3>>(); + + PolynomialReconstructionDescriptor descriptor{IntegrationMethodType::element, degree, + std::vector<std::shared_ptr<const IBoundaryDescriptor>>{ + std::make_shared<NamedBoundaryDescriptor>("XMIN")}}; + + REQUIRE_THROWS_WITH(PolynomialReconstruction{descriptor}.build(DiscreteFunctionP0<TinyVector<1>>{p_mesh}), + "error: cannot symmetrize vectors of dimension 1 using a mesh of dimension 3"); + + REQUIRE_THROWS_WITH(PolynomialReconstruction{descriptor}.build(DiscreteFunctionP0<TinyVector<2>>{p_mesh}), + "error: cannot symmetrize vectors of dimension 2 using a mesh of dimension 3"); + + REQUIRE_THROWS_WITH(PolynomialReconstruction{descriptor}.build( + DiscreteFunctionP0Vector<TinyVector<1>>{p_mesh, 1}), + "error: cannot symmetrize vectors of dimension 1 using a mesh of dimension 3"); + + REQUIRE_THROWS_WITH(PolynomialReconstruction{descriptor}.build( + DiscreteFunctionP0Vector<TinyVector<2>>{p_mesh, 1}), + "error: cannot symmetrize vectors of dimension 2 using a mesh of dimension 3"); + + REQUIRE_THROWS_WITH(PolynomialReconstruction{descriptor}.build(DiscreteFunctionP0<TinyMatrix<1>>{p_mesh}), + "error: cannot symmetrize matrices of dimensions 1x1 using a mesh of dimension 3"); + + REQUIRE_THROWS_WITH(PolynomialReconstruction{descriptor}.build(DiscreteFunctionP0<TinyMatrix<2>>{p_mesh}), + "error: cannot symmetrize matrices of dimensions 2x2 using a mesh of dimension 3"); + + REQUIRE_THROWS_WITH(PolynomialReconstruction{descriptor}.build( + DiscreteFunctionP0Vector<TinyMatrix<1>>{p_mesh, 1}), + "error: cannot symmetrize matrices of dimensions 1x1 using a mesh of dimension 3"); + + REQUIRE_THROWS_WITH(PolynomialReconstruction{descriptor}.build( + DiscreteFunctionP0Vector<TinyMatrix<2>>{p_mesh, 1}), + "error: cannot symmetrize matrices of dimensions 2x2 using a mesh of dimension 3"); + } + } + + SECTION("1D") + { + std::vector<PolynomialReconstructionDescriptor> descriptor_list = + {PolynomialReconstructionDescriptor{IntegrationMethodType::cell_center, degree, + std::vector<std::shared_ptr<const IBoundaryDescriptor>>{ + std::make_shared<NamedBoundaryDescriptor>("XMIN")}}, + PolynomialReconstructionDescriptor{IntegrationMethodType::element, degree, + std::vector<std::shared_ptr<const IBoundaryDescriptor>>{ + std::make_shared<NamedBoundaryDescriptor>("XMIN")}}, + PolynomialReconstructionDescriptor{IntegrationMethodType::boundary, degree, + std::vector<std::shared_ptr<const IBoundaryDescriptor>>{ + std::make_shared<NamedBoundaryDescriptor>("XMIN")}}}; + + using R1 = TinyVector<1>; + + for (auto descriptor : descriptor_list) { + SECTION(name(descriptor.integrationMethodType())) + { + SECTION("R^1 data") + { + auto p_mesh = MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>(); + + auto& mesh = *p_mesh; + + auto R1_exact = [](const R1& x) { return R1{1.7 * (x[0] + 1)}; }; + + DiscreteFunctionP0 fh = test_only::exact_projection(mesh, degree, std::function(R1_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(fh); + + auto dpk_fh = reconstructions[0]->get<DiscreteFunctionDPk<1, const R1>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_fh, std::function(R1_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + + SECTION("R1 vector data") + { + for (auto named_mesh : MeshDataBaseForTests::get().all1DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<1>>(); + auto& mesh = *p_mesh; + + std::array<std::function<R1(const R1&)>, 2> vector_exact // + = {[](const R1& x) -> R1 { return R1{+1.7 * (x[0] + 1)}; }, + [](const R1& x) -> R1 { return R1{-0.3 * (x[0] + 1)}; }}; + + DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh); + + auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<1, const R1>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + } + } + } + + SECTION("2D") + { + std::vector<PolynomialReconstructionDescriptor> descriptor_list = + {PolynomialReconstructionDescriptor{IntegrationMethodType::cell_center, degree, + std::vector<std::shared_ptr< + const IBoundaryDescriptor>>{std:: + make_shared<NamedBoundaryDescriptor>( + "XMAX"), + std::make_shared<NamedBoundaryDescriptor>( + "YMAX")}}, + PolynomialReconstructionDescriptor{IntegrationMethodType::element, degree, + std::vector<std::shared_ptr< + const IBoundaryDescriptor>>{std:: + make_shared<NamedBoundaryDescriptor>( + "XMAX"), + std::make_shared<NamedBoundaryDescriptor>( + "YMAX")}}, + PolynomialReconstructionDescriptor{IntegrationMethodType::boundary, degree, + std::vector<std::shared_ptr< + const IBoundaryDescriptor>>{std:: + make_shared<NamedBoundaryDescriptor>( + "XMAX"), + std::make_shared<NamedBoundaryDescriptor>( + "YMAX")}}}; + + using R2 = TinyVector<2>; + + for (auto descriptor : descriptor_list) { + SECTION(name(descriptor.integrationMethodType())) + { + SECTION("R^2 data") + { + auto p_mesh = MeshDataBaseForTests::get().hybrid2DMesh()->get<Mesh<2>>(); + auto& mesh = *p_mesh; + + auto R2_exact = [](const R2& x) -> R2 { return R2{2.3 * (x[0] - 2), -1.3 * (x[1] - 1)}; }; + + DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R2_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(uh); + + auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<2, const R2>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_uh, std::function(R2_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + + SECTION("vector of R2") + { + auto p_mesh = MeshDataBaseForTests::get().hybrid2DMesh()->get<Mesh<2>>(); + auto& mesh = *p_mesh; + + std::array<std::function<R2(const R2&)>, 2> vector_exact // + = {[](const R2& x) -> R2 { + return R2{+1.7 * (x[0] - 2), -0.6 * (x[1] - 1)}; + }, + [](const R2& x) -> R2 { + return R2{-2.3 * (x[0] - 2), +1.1 * (x[1] - 1)}; + }}; + + DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh); + + auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<2, const R2>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + } + + SECTION("3D") + { + std::vector<PolynomialReconstructionDescriptor> descriptor_list = + {PolynomialReconstructionDescriptor{IntegrationMethodType::cell_center, degree, + std::vector<std::shared_ptr< + const IBoundaryDescriptor>>{std:: + make_shared<NamedBoundaryDescriptor>( + "XMAX"), + std::make_shared<NamedBoundaryDescriptor>( + "YMAX"), + std::make_shared<NamedBoundaryDescriptor>( + "ZMAX")}}, + PolynomialReconstructionDescriptor{IntegrationMethodType::element, degree, + std::vector<std::shared_ptr< + const IBoundaryDescriptor>>{std:: + make_shared<NamedBoundaryDescriptor>( + "XMAX"), + std::make_shared<NamedBoundaryDescriptor>( + "YMAX"), + std::make_shared<NamedBoundaryDescriptor>( + "ZMAX")}}, + PolynomialReconstructionDescriptor{IntegrationMethodType::boundary, degree, + std::vector<std::shared_ptr< + const IBoundaryDescriptor>>{std:: + make_shared<NamedBoundaryDescriptor>( + "XMAX"), + std::make_shared<NamedBoundaryDescriptor>( + "YMAX"), + std::make_shared<NamedBoundaryDescriptor>( + "ZMAX")}}}; + + using R3 = TinyVector<3>; + + for (auto descriptor : descriptor_list) { + SECTION(name(descriptor.integrationMethodType())) + { + SECTION("R^3 data") + { + auto p_mesh = MeshDataBaseForTests::get().hybrid3DMesh()->get<Mesh<3>>(); + auto& mesh = *p_mesh; + + auto R3_exact = [](const R3& x) -> R3 { return R3{2.3 * (x[0] - 2), -1.3 * (x[1] - 1), 1.4 * (x[2] - 1)}; }; + + DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R3_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(uh); + + auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<3, const R3>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_uh, std::function(R3_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + + SECTION("vector of R3") + { + auto p_mesh = MeshDataBaseForTests::get().hybrid3DMesh()->get<Mesh<3>>(); + auto& mesh = *p_mesh; + + std::array<std::function<R3(const R3&)>, 2> vector_exact // + = {[](const R3& x) -> R3 { + return R3{+1.7 * (x[0] - 2), -0.6 * (x[1] - 1), +1.2 * (x[2] - 1)}; + }, + [](const R3& x) -> R3 { + return R3{-2.3 * (x[0] - 2), +1.1 * (x[1] - 1), -0.3 * (x[2] - 1)}; + }}; + + DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh); + + auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<3, const R3>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + } + } +} diff --git a/tests/test_PolynomialReconstruction_degree_2.cpp b/tests/test_PolynomialReconstruction_degree_2.cpp new file mode 100644 index 0000000000000000000000000000000000000000..db13612c1d8c5efad94a6f890251e366826d014b --- /dev/null +++ b/tests/test_PolynomialReconstruction_degree_2.cpp @@ -0,0 +1,1043 @@ +#include <catch2/catch_approx.hpp> +#include <catch2/catch_test_macros.hpp> +#include <catch2/matchers/catch_matchers_all.hpp> + +#include <utils/PugsAssert.hpp> + +#include <mesh/Mesh.hpp> +#include <mesh/NamedBoundaryDescriptor.hpp> +#include <scheme/DiscreteFunctionDPkVariant.hpp> +#include <scheme/DiscreteFunctionP0.hpp> +#include <scheme/DiscreteFunctionVariant.hpp> +#include <scheme/PolynomialReconstruction.hpp> + +#include <DiscreteFunctionDPkForTests.hpp> +#include <MeshDataBaseForTests.hpp> + +#include <NbGhostLayersTester.hpp> + +// clazy:excludeall=non-pod-global-static + +TEST_CASE("PolynomialReconstruction_degree_2", "[scheme]") +{ + constexpr size_t degree = 2; + + constexpr size_t nb_ghost_layers = 2; + NbGhostLayersTester t{nb_ghost_layers}; + + SECTION("without symmetries") + { + std::vector<PolynomialReconstructionDescriptor> descriptor_list = + {PolynomialReconstructionDescriptor{IntegrationMethodType::element, degree}, + PolynomialReconstructionDescriptor{IntegrationMethodType::boundary, degree}}; + + for (auto descriptor : descriptor_list) { + SECTION(name(descriptor.integrationMethodType())) + { + SECTION("1D") + { + using R1 = TinyVector<1>; + + auto p0 = [](const R1& x) { return +2.3 + 1.7 * x[0] - 2.3 * x[0] * x[0]; }; + auto p1 = [](const R1& x) { return -1.7 + 2.1 * x[0] + 1.2 * x[0] * x[0]; }; + auto p2 = [](const R1& x) { return +1.4 - 0.6 * x[0] - 2.0 * x[0] * x[0]; }; + auto p3 = [](const R1& x) { return +2.4 - 2.3 * x[0] + 1.1 * x[0] * x[0]; }; + auto p4 = [](const R1& x) { return -0.2 + 3.1 * x[0] - 0.7 * x[0] * x[0]; }; + auto p5 = [](const R1& x) { return -3.2 - 3.6 * x[0] + 0.1 * x[0] * x[0]; }; + auto p6 = [](const R1& x) { return -4.1 + 3.1 * x[0] - 0.2 * x[0] * x[0]; }; + auto p7 = [](const R1& x) { return +0.8 + 2.9 * x[0] + 4.1 * x[0] * x[0]; }; + auto p8 = [](const R1& x) { return -1.6 + 2.3 * x[0] - 1.7 * x[0] * x[0]; }; + auto p9 = [](const R1& x) { return +2.3 + 1.7 * x[0] - 1.4 * x[0] * x[0]; }; + + SECTION("R data") + { + 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 R_exact = p0; + + DiscreteFunctionP0 fh = test_only::exact_projection(mesh, degree, std::function(R_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(fh); + + auto dpk_fh = reconstructions[0]->get<DiscreteFunctionDPk<1, const double>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_fh, std::function(R_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14)); + } + } + } + + SECTION("R^3 data") + { + using R3 = TinyVector<3>; + + 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 R3_exact = [&](const R1& x) -> R3 { return R3{p2(x), p4(x), p1(x)}; }; + + DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R3_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(uh); + + auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<1, const R3>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_uh, std::function(R3_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14)); + } + } + } + + SECTION("R^3x3 data") + { + using R3x3 = TinyMatrix<3, 3>; + + 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 R3x3_exact = [&](const R1& x) -> R3x3 { + return R3x3{p1(x), p2(x), p3(x), // + p4(x), p5(x), p6(x), // + p7(x), p8(x), p9(x)}; + }; + + DiscreteFunctionP0 Ah = test_only::exact_projection(mesh, degree, std::function(R3x3_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Ah); + + auto dpk_Ah = reconstructions[0]->get<DiscreteFunctionDPk<1, const R3x3>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_Ah, std::function(R3x3_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + + SECTION("R vector data") + { + for (auto named_mesh : MeshDataBaseForTests::get().all1DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<1>>(); + auto& mesh = *p_mesh; + + std::array<std::function<double(const R1&)>, 3> vector_exact = {p1, p7, p9}; + + DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh); + auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<1, const double>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + + SECTION("R3 vector data") + { + using R3 = TinyVector<3>; + + for (auto named_mesh : MeshDataBaseForTests::get().all1DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<1>>(); + auto& mesh = *p_mesh; + + std::array<std::function<R3(const R1&)>, 3> vector_exact // + = {[&](const R1& x) -> R3 { + return R3{p1(x), p2(x), p3(x)}; + }, + [&](const R1& x) -> R3 { + return R3{p5(x), p7(x), p0(x)}; + }, + [&](const R1& x) -> R3 { + return R3{p9(x), p8(x), p4(x)}; + }}; + + DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh); + + auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<1, const R3>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + + SECTION("list of various types") + { + using R3x3 = TinyMatrix<3>; + using R3 = TinyVector<3>; + + 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 R_exact = p0; + + auto R3_exact = [&](const R1& x) -> R3 { return R3{p9(x), p4(x), p7(x)}; }; + + auto R3x3_exact = [&](const R1& x) -> R3x3 { + return R3x3{p2(x), p1(x), p0(x), // + p3(x), p2(x), p4(x), // + p6(x), p5(x), p9(x)}; + }; + + std::array<std::function<double(const R1&)>, 3> vector_exact = {p1, p8, p7}; + + DiscreteFunctionP0 fh = test_only::exact_projection(mesh, degree, std::function(R_exact)); + DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R3_exact)); + DiscreteFunctionP0 Ah = test_only::exact_projection(mesh, degree, std::function(R3x3_exact)); + DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact); + + auto reconstructions = + PolynomialReconstruction{descriptor}.build(std::make_shared<DiscreteFunctionVariant>(fh), uh, + std::make_shared<DiscreteFunctionP0<R3x3>>(Ah), + DiscreteFunctionVariant(Vh)); + + { + auto dpk_fh = reconstructions[0]->get<DiscreteFunctionDPk<1, const double>>(); + double max_error = test_only::max_reconstruction_error(mesh, dpk_fh, std::function(R_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14)); + } + + { + auto dpk_uh = reconstructions[1]->get<DiscreteFunctionDPk<1, const R3>>(); + double max_error = test_only::max_reconstruction_error(mesh, dpk_uh, std::function(R3_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + + { + auto dpk_Ah = reconstructions[2]->get<DiscreteFunctionDPk<1, const R3x3>>(); + double max_error = test_only::max_reconstruction_error(mesh, dpk_Ah, std::function(R3x3_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14)); + } + + { + auto dpk_Vh = reconstructions[3]->get<DiscreteFunctionDPkVector<1, const double>>(); + double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + } + } + + SECTION("2D") + { + using R2 = TinyVector<2>; + auto p0 = [](const R2& x) { + return +2.3 + 1.7 * x[0] - 1.3 * x[1] + 1.2 * x[0] * x[0] + 1.3 * x[0] * x[1] - 3.2 * x[1] * x[1]; + }; + + auto p1 = [](const R2& x) { + return +2.3 + 1.7 * x[0] - 2.2 * x[1] - 2.1 * x[0] * x[0] - 2.3 * x[0] * x[1] - 3.2 * x[1] * x[1]; + }; + auto p2 = [](const R2& x) { + return +1.4 - 0.6 * x[0] + 1.3 * x[1] + 2.3 * x[0] * x[0] - 1.3 * x[0] * x[1] + 1.2 * x[1] * x[1]; + }; + auto p3 = [](const R2& x) { + return -0.2 + 3.1 * x[0] - 1.1 * x[1] - 2.1 * x[0] * x[0] + 1.3 * x[0] * x[1] - 1.1 * x[1] * x[1]; + }; + + SECTION("R data") + { + for (auto named_mesh : MeshDataBaseForTests::get().all2DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<2>>(); + auto& mesh = *p_mesh; + + auto R_exact = p0; + + DiscreteFunctionP0 fh = test_only::exact_projection(mesh, degree, std::function(R_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(fh); + + auto dpk_fh = reconstructions[0]->get<DiscreteFunctionDPk<2, const double>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_fh, std::function(R_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + + SECTION("R^3 data") + { + using R3 = TinyVector<3>; + + for (auto named_mesh : MeshDataBaseForTests::get().all2DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<2>>(); + auto& mesh = *p_mesh; + + auto R3_exact = [&](const R2& x) -> R3 { return R3{p1(x), p2(x), p3(x)}; }; + + DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R3_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(uh); + + auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<2, const R3>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_uh, std::function(R3_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + + SECTION("R^2x2 data") + { + using R2x2 = TinyMatrix<2, 2>; + + for (auto named_mesh : MeshDataBaseForTests::get().all2DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<2>>(); + auto& mesh = *p_mesh; + + auto R2x2_exact = [&](const R2& x) -> R2x2 { + return R2x2{p0(x), p1(x), // + p2(x), p3(x)}; + }; + + DiscreteFunctionP0 Ah = test_only::exact_projection(mesh, degree, std::function(R2x2_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Ah); + + auto dpk_Ah = reconstructions[0]->get<DiscreteFunctionDPk<2, const R2x2>>(); + double max_error = test_only::max_reconstruction_error(mesh, dpk_Ah, std::function(R2x2_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + + SECTION("vector data") + { + for (auto named_mesh : MeshDataBaseForTests::get().all2DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<2>>(); + auto& mesh = *p_mesh; + + std::array<std::function<double(const R2&)>, 4> vector_exact = {p0, p1, p2, p3}; + + DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh); + + auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<2, const double>>(); + double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + } + + SECTION("3D") + { + using R3 = TinyVector<3>; + + auto p0 = [](const R3& x) { + return 2.3 + 1.7 * x[0] - 1.3 * x[1] + 2.1 * x[2] // + + 1.7 * x[0] * x[0] + 1.4 * x[1] * x[1] + 1.7 * x[2] * x[2] // + - 2.3 * x[0] * x[1] + 1.6 * x[0] * x[2] - 1.9 * x[1] * x[2]; + }; + + auto p1 = [](const R3& x) { + return +2.3 + 1.7 * x[0] - 2.2 * x[1] + 1.8 * x[2] // + + 1.7 * x[0] * x[0] - 2.4 * x[1] * x[1] - 2.3 * x[2] * x[2] // + - 2.1 * x[0] * x[1] + 2.6 * x[0] * x[2] + 1.6 * x[1] * x[2]; + }; + + auto p2 = [](const R3& x) { + return +1.4 - 0.6 * x[0] + 1.3 * x[1] - 3.7 * x[2] // + + 3.1 * x[0] * x[0] - 1.1 * x[1] * x[1] + 1.7 * x[2] * x[2] // + - 2.3 * x[0] * x[1] - 2.6 * x[0] * x[2] - 1.9 * x[1] * x[2]; + }; + + auto p3 = [](const R3& x) { + return -0.2 + 3.1 * x[0] - 1.1 * x[1] + 1.9 * x[2] // + - 1.5 * x[0] * x[0] + 1.4 * x[1] * x[1] - 1.2 * x[2] * x[2] // + - 1.7 * x[0] * x[1] - 1.3 * x[0] * x[2] + 2.1 * x[1] * x[2]; + }; + + SECTION("R data") + { + for (auto named_mesh : MeshDataBaseForTests::get().all3DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<3>>(); + auto& mesh = *p_mesh; + + auto R_exact = p0; + + DiscreteFunctionP0 fh = test_only::exact_projection(mesh, degree, std::function(R_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(fh); + + auto dpk_fh = reconstructions[0]->get<DiscreteFunctionDPk<3, const double>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_fh, std::function(R_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + + SECTION("R^3 data") + { + for (auto named_mesh : MeshDataBaseForTests::get().all3DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<3>>(); + auto& mesh = *p_mesh; + + auto R3_exact = [&](const R3& x) -> R3 { return R3{p1(x), p2(x), p3(x)}; }; + + DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R3_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(uh); + + auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<3, const R3>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_uh, std::function(R3_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + + SECTION("R^2x2 data") + { + using R2x2 = TinyMatrix<2, 2>; + + for (auto named_mesh : MeshDataBaseForTests::get().all3DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<3>>(); + auto& mesh = *p_mesh; + + auto R2x2_exact = [&](const R3& x) -> R2x2 { + return R2x2{p0(x), p1(x), // + p2(x), p3(x)}; + }; + + DiscreteFunctionP0 Ah = test_only::exact_projection(mesh, degree, std::function(R2x2_exact)); + + descriptor.setRowWeighting(false); + auto reconstructions = PolynomialReconstruction{descriptor}.build(Ah); + + auto dpk_Ah = reconstructions[0]->get<DiscreteFunctionDPk<3, const R2x2>>(); + double max_error = test_only::max_reconstruction_error(mesh, dpk_Ah, std::function(R2x2_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + + SECTION("vector data") + { + for (auto named_mesh : MeshDataBaseForTests::get().all3DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<3>>(); + auto& mesh = *p_mesh; + + std::array<std::function<double(const R3&)>, 4> vector_exact = {p0, p1, p2, p3}; + + DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact); + + descriptor.setPreconditioning(false); + auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh); + + auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<3, const double>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + } + } + } + } + + SECTION("with symmetries") + { + SECTION("1D") + { + std::vector<PolynomialReconstructionDescriptor> descriptor_list = + {PolynomialReconstructionDescriptor{IntegrationMethodType::element, degree, + std::vector<std::shared_ptr<const IBoundaryDescriptor>>{ + std::make_shared<NamedBoundaryDescriptor>("XMIN")}}, + PolynomialReconstructionDescriptor{IntegrationMethodType::boundary, degree, + std::vector<std::shared_ptr<const IBoundaryDescriptor>>{ + std::make_shared<NamedBoundaryDescriptor>("XMIN")}}}; + using R1 = TinyVector<1>; + + auto p0 = [](const R1& x) { return +1.7 * (x[0] + 1) * (x[0] + 1) - 1.1; }; + auto p1 = [](const R1& x) { return -1.2 * (x[0] + 1) * (x[0] + 1) + 1.3; }; + auto p2 = [](const R1& x) { return +1.4 * (x[0] + 1) * (x[0] + 1) - 0.6; }; + + for (auto descriptor : descriptor_list) { + SECTION(name(descriptor.integrationMethodType())) + { + SECTION("R data") + { + auto p_mesh = MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>(); + + auto& mesh = *p_mesh; + + auto R_exact = p0; + + DiscreteFunctionP0 fh = test_only::exact_projection(mesh, degree, std::function(R_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(fh); + + auto dpk_fh = reconstructions[0]->get<DiscreteFunctionDPk<1, const double>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_fh, std::function(R_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + + SECTION("R1x1 data") + { + using R1x1 = TinyMatrix<1>; + + auto p_mesh = MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>(); + + auto& mesh = *p_mesh; + + auto R1x1_exact = [&](const R1& x) { return R1x1{p0(x)}; }; + + DiscreteFunctionP0 fh = test_only::exact_projection(mesh, degree, std::function(R1x1_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(fh); + + auto dpk_fh = reconstructions[0]->get<DiscreteFunctionDPk<1, const R1x1>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_fh, std::function(R1x1_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + + SECTION("R vector data") + { + auto p_mesh = MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>(); + auto& mesh = *p_mesh; + + std::array<std::function<double(const R1&)>, 3> vector_exact // + = {p0, p1, p2}; + + DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh); + + auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<1, const double>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + + SECTION("R1x1 vector data") + { + using R1x1 = TinyMatrix<1>; + + auto p_mesh = MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>(); + auto& mesh = *p_mesh; + + std::array<std::function<R1x1(const R1&)>, 3> vector_exact // + = {[&](const R1& x) { return R1x1{p2(x)}; }, // + [&](const R1& x) { return R1x1{p0(x)}; }, // + [&](const R1& x) { return R1x1{p1(x)}; }}; + + DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh); + + auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<1, const R1x1>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + } + + SECTION("2D") + { + std::vector<PolynomialReconstructionDescriptor> descriptor_list = + {PolynomialReconstructionDescriptor{IntegrationMethodType::element, degree, + std::vector<std::shared_ptr< + const IBoundaryDescriptor>>{std:: + make_shared<NamedBoundaryDescriptor>( + "XMAX"), + std::make_shared<NamedBoundaryDescriptor>( + "YMAX")}}, + PolynomialReconstructionDescriptor{IntegrationMethodType::boundary, degree, + std::vector<std::shared_ptr< + const IBoundaryDescriptor>>{std:: + make_shared<NamedBoundaryDescriptor>( + "XMAX"), + std::make_shared<NamedBoundaryDescriptor>( + "YMAX")}}}; + + using R2 = TinyVector<2>; + + auto p_initial_mesh = MeshDataBaseForTests::get().hybrid2DMesh()->get<Mesh<2>>(); + auto& initial_mesh = *p_initial_mesh; + + constexpr double theta = 1; + TinyMatrix<2> T{std::cos(theta), -std::sin(theta), // + std::sin(theta), std::cos(theta)}; + + auto xr = initial_mesh.xr(); + + NodeValue<R2> new_xr{initial_mesh.connectivity()}; + parallel_for( + initial_mesh.numberOfNodes(), PUGS_LAMBDA(const NodeId node_id) { new_xr[node_id] = T * xr[node_id]; }); + + std::shared_ptr p_mesh = std::make_shared<const Mesh<2>>(initial_mesh.shared_connectivity(), new_xr); + const Mesh<2>& mesh = *p_mesh; + // inverse rotation + TinyMatrix<2> inv_T{std::cos(theta), std::sin(theta), // + -std::sin(theta), std::cos(theta)}; + + auto p0 = [&inv_T](const R2& X) { + const R2 Y = inv_T * X; + const double x = Y[0] - 2; + const double y = Y[1] - 1; + return +1.7 * x * x + 2 * y * y - 1.1; + }; + + auto p1 = [&inv_T](const R2& X) { + const R2 Y = inv_T * X; + const double x = Y[0] - 2; + const double y = Y[1] - 1; + return -1.3 * x * x - 0.2 * y * y + 0.7; + }; + + auto p2 = [&inv_T](const R2& X) { + const R2 Y = inv_T * X; + const double x = Y[0] - 2; + const double y = Y[1] - 1; + return +2.6 * x * x - 1.4 * y * y - 1.9; + }; + + auto p3 = [&inv_T](const R2& X) { + const R2 Y = inv_T * X; + const double x = Y[0] - 2; + const double y = Y[1] - 1; + return -0.6 * x * x + 1.4 * y * y + 2.3; + }; + + auto q0 = [&inv_T](const R2& X) { + const R2 Y = inv_T * X; + const double x = Y[0] - 2; + const double y = Y[1] - 1; + return 3 * x * y; + }; + + auto q1 = [&inv_T](const R2& X) { + const R2 Y = inv_T * X; + const double x = Y[0] - 2; + const double y = Y[1] - 1; + return -1.3 * x * y; + }; + + for (auto descriptor : descriptor_list) { + SECTION(name(descriptor.integrationMethodType())) + { + SECTION("R data") + { + auto R_exact = p0; + + DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(uh); + + auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<2, const double>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_uh, std::function(R_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + + SECTION("R2x2 data") + { + using R2x2 = TinyMatrix<2>; + auto R2x2_exact = [&](const R2& X) -> R2x2 { + return T * TinyMatrix<2>{p0(X), q0(X), q1(X), p1(X)} * inv_T; + }; + + DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R2x2_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(uh); + + auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<2, const R2x2>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_uh, std::function(R2x2_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + + SECTION("vector of R") + { + std::array<std::function<double(const R2&)>, 4> vector_exact // + = {p0, p1, p2, p3}; + + DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh); + + auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<2, const double>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + + SECTION("vector of R2x2") + { + using R2x2 = TinyMatrix<2>; + + std::array<std::function<R2x2(const R2&)>, 2> vector_R2x2_exact // + = {[&](const R2& X) { + return T * R2x2{p0(X), q0(X), q1(X), p1(X)} * inv_T; + }, + [&](const R2& X) { + return T * R2x2{p0(X), q1(X), 0, p1(X)} * inv_T; + }}; + + DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_R2x2_exact); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh); + + auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<2, const R2x2>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_R2x2_exact); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + + SECTION("list") + { + using R2x2 = TinyMatrix<2>; + + auto R_exact = p0; + + DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R_exact)); + + std::array<std::function<double(const R2&)>, 4> vector_exact // + = {p0, p1, p2, p3}; + + DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact); + + std::array<std::function<R2x2(const R2&)>, 2> vector_R2x2_exact // + = {[&](const R2& X) { + return T * R2x2{p0(X), q0(X), q1(X), p1(X)} * inv_T; + }, + [&](const R2& X) { + return T * R2x2{p2(X), q1(X), 0, p3(X)} * inv_T; + }}; + + DiscreteFunctionP0Vector Wh = test_only::exact_projection(mesh, degree, vector_R2x2_exact); + + auto R2x2_exact = [&](const R2& X) -> R2x2 { + return T * TinyMatrix<2>{p0(X), q1(X), q0(X), p3(X)} * inv_T; + }; + + DiscreteFunctionP0 Ah = test_only::exact_projection(mesh, degree, std::function(R2x2_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(uh, Vh, Wh, Ah); + + auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<2, const double>>(); + auto dpk_Vh = reconstructions[1]->get<DiscreteFunctionDPkVector<2, const double>>(); + auto dpk_Wh = reconstructions[2]->get<DiscreteFunctionDPkVector<2, const R2x2>>(); + auto dpk_Ah = reconstructions[3]->get<DiscreteFunctionDPk<2, const R2x2>>(); + + { + double max_error = test_only::max_reconstruction_error(mesh, dpk_uh, std::function(R_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + { + double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + { + double max_error = test_only::max_reconstruction_error(mesh, dpk_Wh, vector_R2x2_exact); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + { + double max_error = test_only::max_reconstruction_error(mesh, dpk_Ah, std::function(R2x2_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + } + } + + SECTION("3D") + { + using R3 = TinyVector<3>; + + auto p_initial_mesh = MeshDataBaseForTests::get().hybrid3DMesh()->get<Mesh<3>>(); + auto& initial_mesh = *p_initial_mesh; + + constexpr double theta = 1; + TinyMatrix<3> T{std::cos(theta), -std::sin(theta), 0, + // + std::sin(theta), std::cos(theta), 0, + // + 0, 0, 1}; + + auto xr = initial_mesh.xr(); + + NodeValue<R3> new_xr{initial_mesh.connectivity()}; + parallel_for( + initial_mesh.numberOfNodes(), PUGS_LAMBDA(const NodeId node_id) { new_xr[node_id] = T * xr[node_id]; }); + + std::shared_ptr p_mesh = std::make_shared<const Mesh<3>>(initial_mesh.shared_connectivity(), new_xr); + const Mesh<3>& mesh = *p_mesh; + // inverse rotation + TinyMatrix<3> inv_T{std::cos(theta), std::sin(theta), 0, + // + -std::sin(theta), std::cos(theta), 0, + // + 0, 0, 1}; + + auto p0 = [&inv_T](const R3& X) { + const R3 Y = inv_T * X; + const double x = Y[0] - 2; + const double y = Y[1] - 1; + const double z = Y[2] - 1; + return +1.7 * x * x + 2 * y * y + 1.3 * z * z - 1.1; + }; + + auto p1 = [&inv_T](const R3& X) { + const R3 Y = inv_T * X; + const double x = Y[0] - 2; + const double y = Y[1] - 1; + const double z = Y[2] - 1; + return +2.1 * x * x - 1.4 * y * y - 3.1 * z * z + 2.2; + }; + + auto p2 = [&inv_T](const R3& X) { + const R3 Y = inv_T * X; + const double x = Y[0] - 2; + const double y = Y[1] - 1; + const double z = Y[2] - 1; + return -1.1 * x * x - 1.2 * y * y + 1.3 * z * z - 1.7; + }; + + auto p3 = [&inv_T](const R3& X) { + const R3 Y = inv_T * X; + const double x = Y[0] - 2; + const double y = Y[1] - 1; + const double z = Y[2] - 1; + return 1.9 * x * x + 2.1 * y * y - 3.1 * z * z + 1.6; + }; + + auto p4 = [&inv_T](const R3& X) { + const R3 Y = inv_T * X; + const double x = Y[0] - 2; + const double y = Y[1] - 1; + const double z = Y[2] - 1; + return -2.4 * x * x + 3.3 * y * y - 1.7 * z * z + 2.1; + }; + + SECTION("3 symmetries") + { + std::vector<PolynomialReconstructionDescriptor> descriptor_list = + {PolynomialReconstructionDescriptor{IntegrationMethodType::element, degree, + std::vector<std::shared_ptr< + const IBoundaryDescriptor>>{std:: + make_shared<NamedBoundaryDescriptor>( + "XMAX"), + std::make_shared<NamedBoundaryDescriptor>( + "YMAX"), + std::make_shared<NamedBoundaryDescriptor>( + "ZMAX")}}, + PolynomialReconstructionDescriptor{IntegrationMethodType::boundary, degree, + std::vector<std::shared_ptr< + const IBoundaryDescriptor>>{std:: + make_shared<NamedBoundaryDescriptor>( + "XMAX"), + std::make_shared<NamedBoundaryDescriptor>( + "YMAX"), + std::make_shared<NamedBoundaryDescriptor>( + "ZMAX")}}}; + + for (auto descriptor : descriptor_list) { + SECTION("R data") + { + auto R_exact = p0; + + DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(uh); + + auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<3, const double>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_uh, std::function(R_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + + SECTION("vector of R") + { + std::array<std::function<double(const R3&)>, 4> vector_exact // + = {p0, p1, p2, p3}; + + DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh); + + auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<3, const double>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + + SECTION("1 symmetry") + { + // Matrix and their transformations are kept simple to + // derive exact solutions + std::vector<PolynomialReconstructionDescriptor> descriptor_list = + {PolynomialReconstructionDescriptor{IntegrationMethodType::element, degree, + std::vector<std::shared_ptr<const IBoundaryDescriptor>>{ + std::make_shared<NamedBoundaryDescriptor>("XMAX")}}, + PolynomialReconstructionDescriptor{IntegrationMethodType::boundary, degree, + std::vector<std::shared_ptr<const IBoundaryDescriptor>>{ + std::make_shared<NamedBoundaryDescriptor>("XMAX")}}}; + for (auto descriptor : descriptor_list) { + SECTION(name(descriptor.integrationMethodType())) + { + SECTION("R3x3 data") + { + // Matrix and their transformations are kept simple to + // derive exact solutions + + using R3x3 = TinyMatrix<3>; + auto R2x2_exact = [&](const R3& X) -> R3x3 { + return T * TinyMatrix<3>{p0(X), 0, 0, // + 0, p1(X), p2(X), // + 0, p3(X), p4(X)} * + inv_T; + }; + + DiscreteFunctionP0 Ah = test_only::exact_projection(mesh, degree, std::function(R2x2_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Ah); + + auto dpk_Ah = reconstructions[0]->get<DiscreteFunctionDPk<3, const R3x3>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_Ah, std::function(R2x2_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + + SECTION("vector of R3x3") + { + using R3x3 = TinyMatrix<3>; + + std::array<std::function<R3x3(const R3&)>, 2> vector_R3x3_exact // + = {[&](const R3& X) { + return T * R3x3{p0(X), 0, 0, // + 0, p1(X), p2(X), // + 0, p3(X), p4(X)} * + inv_T; + }, + [&](const R3& X) { + return T * R3x3{p0(X), 0, 0, // + 0, p2(X), p4(X), // + 0, p3(X), p1(X)} * + inv_T; + }}; + + DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_R3x3_exact); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh); + + auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<3, const R3x3>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_R3x3_exact); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + + SECTION("list") + { + using R3x3 = TinyMatrix<3>; + + auto R_exact = p0; + + DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R_exact)); + + std::array<std::function<double(const R3&)>, 4> vector_exact // + = {p0, p1, p2, p3}; + + DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact); + + std::array<std::function<R3x3(const R3&)>, 2> vector_R3x3_exact // + = {[&](const R3& X) { + return T * R3x3{p1(X), 0, 0, // + 0, p3(X), p0(X), // + 0, p2(X), p4(X)} * + inv_T; + }, + [&](const R3& X) { + return T * R3x3{p2(X), 0, 0, // + 0, p0(X), p1(X), // + 0, p3(X), p4(X)} * + inv_T; + }}; + + DiscreteFunctionP0Vector Wh = test_only::exact_projection(mesh, degree, vector_R3x3_exact); + + auto R3x3_exact = [&](const R3& X) -> R3x3 { + return T * R3x3{p0(X), 0, 0, // + 0, p1(X), p2(X), // + 0, p3(X), p4(X)} * + inv_T; + }; + + DiscreteFunctionP0 Ah = test_only::exact_projection(mesh, degree, std::function(R3x3_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(uh, Vh, Wh, Ah); + + auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<3, const double>>(); + auto dpk_Vh = reconstructions[1]->get<DiscreteFunctionDPkVector<3, const double>>(); + auto dpk_Wh = reconstructions[2]->get<DiscreteFunctionDPkVector<3, const R3x3>>(); + auto dpk_Ah = reconstructions[3]->get<DiscreteFunctionDPk<3, const R3x3>>(); + + { + double max_error = test_only::max_reconstruction_error(mesh, dpk_uh, std::function(R_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + { + double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + { + double max_error = test_only::max_reconstruction_error(mesh, dpk_Wh, vector_R3x3_exact); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + { + double max_error = test_only::max_reconstruction_error(mesh, dpk_Ah, std::function(R3x3_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + } + } + } + } +} diff --git a/tests/test_PolynomialReconstruction_degree_3.cpp b/tests/test_PolynomialReconstruction_degree_3.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e20fa89a3bdcba29f61a771deacba273c536f28b --- /dev/null +++ b/tests/test_PolynomialReconstruction_degree_3.cpp @@ -0,0 +1,766 @@ +#include <catch2/catch_approx.hpp> +#include <catch2/catch_test_macros.hpp> +#include <catch2/matchers/catch_matchers_all.hpp> + +#include <utils/PugsAssert.hpp> + +#include <mesh/Mesh.hpp> +#include <mesh/NamedBoundaryDescriptor.hpp> +#include <scheme/DiscreteFunctionDPkVariant.hpp> +#include <scheme/DiscreteFunctionP0.hpp> +#include <scheme/DiscreteFunctionVariant.hpp> +#include <scheme/PolynomialReconstruction.hpp> + +#include <mesh/CartesianMeshBuilder.hpp> + +#include <DiscreteFunctionDPkForTests.hpp> +#include <MeshDataBaseForTests.hpp> + +#include <NbGhostLayersTester.hpp> + +// clazy:excludeall=non-pod-global-static + +TEST_CASE("PolynomialReconstruction_degree_3", "[scheme]") +{ + constexpr size_t degree = 3; + + constexpr size_t nb_ghost_layers = 3; + NbGhostLayersTester t{nb_ghost_layers}; + + SECTION("without symmetries") + { + std::vector<PolynomialReconstructionDescriptor> descriptor_list = + {PolynomialReconstructionDescriptor{IntegrationMethodType::element, degree}, + PolynomialReconstructionDescriptor{IntegrationMethodType::boundary, degree}}; + + for (auto descriptor : descriptor_list) { + SECTION(name(descriptor.integrationMethodType())) + { + SECTION("1D") + { + using R1 = TinyVector<1>; + + auto p0 = [](const R1& X) { + const double x = X[0]; + return +2.3 + (1.4 + (1.7 - 2.3 * x) * x) * x; + }; + auto p1 = [](const R1& X) { + const double x = X[0]; + return -1.2 - (2.3 - (1.1 + 2.1 * x) * x) * x; + }; + auto p2 = [](const R1& X) { + const double x = X[0]; + return +2.1 + (2.1 + (3.1 - 1.7 * x) * x) * x; + }; + auto p3 = [](const R1& X) { + const double x = X[0]; + return -1.7 + (1.4 + (1.6 - 3.1 * x) * x) * x; + }; + auto p4 = [](const R1& X) { + const double x = X[0]; + return +1.1 - (1.3 - (2.1 + 1.5 * x) * x) * x; + }; + auto p5 = [](const R1& X) { + const double x = X[0]; + return +1.9 - (2.1 + (1.6 - 2.7 * x) * x) * x; + }; + auto p6 = [](const R1& X) { + const double x = X[0]; + return -0.7 + (1.4 + (2.1 + 1.1 * x) * x) * x; + }; + auto p7 = [](const R1& X) { + const double x = X[0]; + return -1.4 - (1.2 + (1.5 - 2.1 * x) * x) * x; + }; + auto p8 = [](const R1& X) { + const double x = X[0]; + return -2.1 + (1.1 - (1.7 + 1.2 * x) * x) * x; + }; + auto p9 = [](const R1& X) { + const double x = X[0]; + return +1.8 - (3.1 + (2.1 - 2.4 * x) * x) * x; + }; + + SECTION("R data") + { + 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 R_exact = p0; + + DiscreteFunctionP0 fh = test_only::exact_projection(mesh, degree, std::function(R_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(fh); + + auto dpk_fh = reconstructions[0]->get<DiscreteFunctionDPk<1, const double>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_fh, std::function(R_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + + SECTION("R^3 data") + { + using R3 = TinyVector<3>; + + 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 R3_exact = [&](const R1& x) -> R3 { return R3{p2(x), p4(x), p1(x)}; }; + + DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R3_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(uh); + + auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<1, const R3>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_uh, std::function(R3_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + + SECTION("R^3x3 data") + { + using R3x3 = TinyMatrix<3, 3>; + + 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 R3x3_exact = [&](const R1& x) -> R3x3 { + return R3x3{p1(x), p2(x), p3(x), // + p4(x), p5(x), p6(x), // + p7(x), p8(x), p9(x)}; + }; + + DiscreteFunctionP0 Ah = test_only::exact_projection(mesh, degree, std::function(R3x3_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Ah); + + auto dpk_Ah = reconstructions[0]->get<DiscreteFunctionDPk<1, const R3x3>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_Ah, std::function(R3x3_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + + SECTION("R vector data") + { + for (auto named_mesh : MeshDataBaseForTests::get().all1DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<1>>(); + auto& mesh = *p_mesh; + + std::array<std::function<double(const R1&)>, 3> vector_exact = {p1, p7, p9}; + + DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh); + auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<1, const double>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + + SECTION("R3 vector data") + { + using R3 = TinyVector<3>; + + for (auto named_mesh : MeshDataBaseForTests::get().all1DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<1>>(); + auto& mesh = *p_mesh; + + std::array<std::function<R3(const R1&)>, 3> vector_exact // + = {[&](const R1& x) -> R3 { return R3{p1(x), p2(x), p3(x)}; }, + [&](const R1& x) -> R3 { return R3{p5(x), p7(x), p0(x)}; }, + [&](const R1& x) -> R3 { return R3{p9(x), p8(x), p4(x)}; }}; + + DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh); + + auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<1, const R3>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + + SECTION("list of various types") + { + using R3x3 = TinyMatrix<3>; + using R3 = TinyVector<3>; + + 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 R_exact = p0; + + auto R3_exact = [&](const R1& x) -> R3 { return R3{p9(x), p4(x), p7(x)}; }; + + auto R3x3_exact = [&](const R1& x) -> R3x3 { + return R3x3{p2(x), p1(x), p0(x), // + p3(x), p2(x), p4(x), // + p6(x), p5(x), p9(x)}; + }; + + std::array<std::function<double(const R1&)>, 3> vector_exact = {p1, p8, p7}; + + DiscreteFunctionP0 fh = test_only::exact_projection(mesh, degree, std::function(R_exact)); + DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R3_exact)); + DiscreteFunctionP0 Ah = test_only::exact_projection(mesh, degree, std::function(R3x3_exact)); + DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact); + + auto reconstructions = + PolynomialReconstruction{descriptor}.build(std::make_shared<DiscreteFunctionVariant>(fh), uh, + std::make_shared<DiscreteFunctionP0<R3x3>>(Ah), + DiscreteFunctionVariant(Vh)); + + { + auto dpk_fh = reconstructions[0]->get<DiscreteFunctionDPk<1, const double>>(); + double max_error = test_only::max_reconstruction_error(mesh, dpk_fh, std::function(R_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + + { + auto dpk_uh = reconstructions[1]->get<DiscreteFunctionDPk<1, const R3>>(); + double max_error = test_only::max_reconstruction_error(mesh, dpk_uh, std::function(R3_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + + { + auto dpk_Ah = reconstructions[2]->get<DiscreteFunctionDPk<1, const R3x3>>(); + double max_error = test_only::max_reconstruction_error(mesh, dpk_Ah, std::function(R3x3_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + + { + auto dpk_Vh = reconstructions[3]->get<DiscreteFunctionDPkVector<1, const double>>(); + double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + } + } + + SECTION("2D") + { + using R2 = TinyVector<2>; + auto p0 = [](const R2& X) { + const double x = X[0]; + const double y = X[1]; + const double x2 = x * x; + const double xy = x * y; + const double y2 = y * y; + const double x3 = x2 * x; + const double x2y = x2 * y; + const double xy2 = x * y2; + const double y3 = y * y2; + + return +2.3 // + + 1.7 * x - 1.3 * y // + + 1.2 * x2 + 1.3 * xy - 3.2 * y2 // + - 1.3 * x3 + 2.1 * x2y - 1.6 * xy2 + 2.1 * y3; + }; + + auto p1 = [](const R2& X) { + const double x = X[0]; + const double y = X[1]; + const double x2 = x * x; + const double xy = x * y; + const double y2 = y * y; + const double x3 = x2 * x; + const double x2y = x2 * y; + const double xy2 = x * y2; + const double y3 = y * y2; + + return +1.4 // + + 1.6 * x - 2.1 * y // + + 1.3 * x2 + 2.6 * xy - 1.4 * y2 // + - 1.2 * x3 - 1.7 * x2y + 2.1 * xy2 - 2.2 * y3; + }; + + auto p2 = [](const R2& X) { + const double x = X[0]; + const double y = X[1]; + const double x2 = x * x; + const double xy = x * y; + const double y2 = y * y; + const double x3 = x2 * x; + const double x2y = x2 * y; + const double xy2 = x * y2; + const double y3 = y * y2; + + return -1.2 // + + 2.3 * x - 1.6 * y // + - 1.2 * x2 + 2.4 * xy - 1.9 * y2 // + + 0.9 * x3 + 1.5 * x2y - 2.3 * xy2 - 1.6 * y3; + }; + + auto p3 = [](const R2& X) { + const double x = X[0]; + const double y = X[1]; + const double x2 = x * x; + const double xy = x * y; + const double y2 = y * y; + const double x3 = x2 * x; + const double x2y = x2 * y; + const double xy2 = x * y2; + const double y3 = y * y2; + + return +2.4 // + + 2.5 * x + 1.4 * y // + - 2.7 * x2 + 1.9 * xy - 2.2 * y2 // + - 1.3 * x3 + 2.3 * x2y - 1.4 * xy2 + 2.2 * y3; + }; + + SECTION("R data") + { + for (auto named_mesh : MeshDataBaseForTests::get().all2DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<2>>(); + auto& mesh = *p_mesh; + + auto R_exact = p0; + + DiscreteFunctionP0 fh = test_only::exact_projection(mesh, degree + 1, std::function(R_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(fh); + + auto dpk_fh = reconstructions[0]->get<DiscreteFunctionDPk<2, const double>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_fh, std::function(R_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + + SECTION("R^3 data") + { + using R3 = TinyVector<3>; + + for (auto named_mesh : MeshDataBaseForTests::get().all2DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<2>>(); + auto& mesh = *p_mesh; + + auto R3_exact = [&](const R2& x) -> R3 { return R3{p1(x), p2(x), p3(x)}; }; + + DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree + 1, std::function(R3_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(uh); + + auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<2, const R3>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_uh, std::function(R3_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + + SECTION("R^2x2 data") + { + using R2x2 = TinyMatrix<2, 2>; + + for (auto named_mesh : MeshDataBaseForTests::get().all2DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<2>>(); + auto& mesh = *p_mesh; + + auto R2x2_exact = [&](const R2& x) -> R2x2 { + return R2x2{p0(x), p1(x), // + p2(x), p3(x)}; + }; + + DiscreteFunctionP0 Ah = test_only::exact_projection(mesh, degree + 1, std::function(R2x2_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Ah); + + auto dpk_Ah = reconstructions[0]->get<DiscreteFunctionDPk<2, const R2x2>>(); + double max_error = test_only::max_reconstruction_error(mesh, dpk_Ah, std::function(R2x2_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + + SECTION("vector data") + { + for (auto named_mesh : MeshDataBaseForTests::get().all2DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<2>>(); + auto& mesh = *p_mesh; + + std::array<std::function<double(const R2&)>, 4> vector_exact = {p0, p1, p2, p3}; + + DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree + 1, vector_exact); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh); + + auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<2, const double>>(); + double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + } + + SECTION("3D") + { + using R3 = TinyVector<3>; + + auto p = [](const R3& X, const std::array<double, 20>& a) -> double { + const double x = X[0]; + const double y = X[1]; + const double z = X[2]; + const double xy = x * y; + const double xz = x * z; + const double yz = y * z; + const double x2 = x * x; + const double y2 = y * y; + const double z2 = z * z; + const double xyz = x * y * z; + const double x2y = x2 * y; + const double x2z = x2 * z; + const double xy2 = x * y2; + const double y2z = y2 * z; + const double xz2 = x * z2; + const double yz2 = y * z2; + const double x3 = x2 * x; + const double y3 = y2 * y; + const double z3 = z2 * z; + + return a[0] + a[1] * x + a[2] * y + a[3] * z // + + a[4] * x2 + a[5] * y2 + a[6] * z2 // + + a[7] * xy + a[8] * xz + a[9] * yz // + + a[10] * x3 + a[11] * y3 + a[12] * z3 // + + a[13] * x2y + a[14] * x2z + a[15] * xy2 // + + a[16] * y2z + a[17] * xz2 + a[18] * yz2 // + + a[19] * xyz // + ; + }; + + constexpr std::array<double, 20> a0 = {+2.3, +1.7, -1.3, +2.1, +1.7, +1.4, +1.7, -2.3, +1.6, -1.9, + +1.2, -2.1, -1.1, -1.7, -1.3, +0.9, -0.7, +1.5, -0.7, +2.8}; + + auto p0 = [&p, &a0](const R3& X) -> double { return p(X, a0); }; + + constexpr std::array<double, 20> a1 = {-1.3, +2.2, +0.1, -2.5, +0.2, -2.3, -1.4, +0.9, +0.2, -0.3, + +2.4, -1.2, +1.7, -2.2, +0.6, +1.9, +1.0, -0.8, +2.4, +2.4}; + + auto p1 = [&p, &a1](const R3& X) -> double { return p(X, a1); }; + + constexpr std::array<double, 20> a2 = {+1.9, -1.2, -0.4, -1.2, -0.8, +1.4, +0.5, -1.6, +1.1, -0.7, + +0.6, +2.3, -1.8, -1.9, -0.3, -2.4, -1.7, +0.2, -2.4, +1.9}; + + auto p2 = [&p, &a2](const R3& X) -> double { return p(X, a2); }; + + constexpr std::array<double, 20> a3 = {+0.8, +0.5, +1.3, -2.3, +0.9, -0.4, -2.0, +1.8, +0.5, +0.7, + +1.0, -0.4, +1.1, +1.8, -0.4, +1.1, -0.0, +1.4, +1.9, -2.2}; + + auto p3 = [&p, &a3](const R3& X) -> double { return p(X, a3); }; + + auto p_mesh = CartesianMeshBuilder{TinyVector<3>{-0.5, -0.5, -0.5}, TinyVector<3>{3.5, 3.5, 3.5}, + TinyVector<3, size_t>{4, 4, 4}} + .mesh() + ->get<Mesh<3>>(); + const auto& mesh = *p_mesh; + + SECTION("R data") + { + auto R_exact = p0; + + DiscreteFunctionP0 fh = test_only::exact_projection(mesh, degree + 3, std::function(R_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(fh); + auto dpk_fh = reconstructions[0]->get<DiscreteFunctionDPk<3, const double>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_fh, std::function(R_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + + SECTION("R^3 data") + { + auto R3_exact = [&](const R3& x) -> R3 { return R3{p1(x), p2(x), p3(x)}; }; + + DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R3_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(uh); + + auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<3, const R3>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_uh, std::function(R3_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + + SECTION("R^2x2 data") + { + using R2x2 = TinyMatrix<2, 2>; + + auto R2x2_exact = [&](const R3& x) -> R2x2 { + return R2x2{p0(x), p1(x), // + p2(x), p3(x)}; + }; + + DiscreteFunctionP0 Ah = test_only::exact_projection(mesh, degree, std::function(R2x2_exact)); + + descriptor.setRowWeighting(false); + auto reconstructions = PolynomialReconstruction{descriptor}.build(Ah); + + auto dpk_Ah = reconstructions[0]->get<DiscreteFunctionDPk<3, const R2x2>>(); + double max_error = test_only::max_reconstruction_error(mesh, dpk_Ah, std::function(R2x2_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + + SECTION("vector data") + { + std::array<std::function<double(const R3&)>, 4> vector_exact = {p0, p1, p2, p3}; + + DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact); + + descriptor.setPreconditioning(false); + auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh); + + auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<3, const double>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + } + } + + SECTION("with symmetries") + { + SECTION("1D") + { + std::vector<PolynomialReconstructionDescriptor> descriptor_list = + {PolynomialReconstructionDescriptor{IntegrationMethodType::element, degree, + std::vector<std::shared_ptr<const IBoundaryDescriptor>>{ + std::make_shared<NamedBoundaryDescriptor>("XMIN")}}, + PolynomialReconstructionDescriptor{IntegrationMethodType::boundary, degree, + std::vector<std::shared_ptr<const IBoundaryDescriptor>>{ + std::make_shared<NamedBoundaryDescriptor>("XMIN")}}}; + using R1 = TinyVector<1>; + + auto p0 = [](const R1& x) -> R1 { return R1{+1.7 * (x[0] + 1) * (x[0] + 1) * (x[0] + 1)}; }; + auto p1 = [](const R1& x) -> R1 { return R1{-1.2 * (x[0] + 1) * (x[0] + 1) * (x[0] + 1)}; }; + auto p2 = [](const R1& x) -> R1 { return R1{+1.4 * (x[0] + 1) * (x[0] + 1) * (x[0] + 1)}; }; + + for (auto descriptor : descriptor_list) { + SECTION(name(descriptor.integrationMethodType())) + { + SECTION("R1 data") + { + auto p_mesh = MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>(); + + auto& mesh = *p_mesh; + + auto R1_exact = p0; + + DiscreteFunctionP0 fh = test_only::exact_projection(mesh, degree, std::function(R1_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(fh); + + auto dpk_fh = reconstructions[0]->get<DiscreteFunctionDPk<1, const R1>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_fh, std::function(R1_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + + SECTION("R1 vector data") + { + auto p_mesh = MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>(); + auto& mesh = *p_mesh; + + std::array<std::function<R1(const R1&)>, 3> vector_exact // + = {p0, p1, p2}; + + DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh); + + auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<1, const R1>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + } + + SECTION("2D") + { + std::vector<PolynomialReconstructionDescriptor> descriptor_list = + {PolynomialReconstructionDescriptor{IntegrationMethodType::element, degree, + std::vector<std::shared_ptr< + const IBoundaryDescriptor>>{std:: + make_shared<NamedBoundaryDescriptor>( + "XMAX"), + std::make_shared<NamedBoundaryDescriptor>( + "YMAX")}}, + PolynomialReconstructionDescriptor{IntegrationMethodType::boundary, degree, + std::vector<std::shared_ptr< + const IBoundaryDescriptor>>{std:: + make_shared<NamedBoundaryDescriptor>( + "XMAX"), + std::make_shared<NamedBoundaryDescriptor>( + "YMAX")}}}; + + using R2 = TinyVector<2>; + + auto p_mesh = CartesianMeshBuilder{TinyVector<2>{0, 0}, TinyVector<2>{2, 1}, TinyVector<2, size_t>{3, 3}} + .mesh() + ->get<Mesh<2>>(); + + auto& mesh = *p_mesh; + + for (auto descriptor : descriptor_list) { + SECTION(name(descriptor.integrationMethodType())) + { + SECTION("R^2 data") + { + auto R2_exact = [](const R2& x) -> R2 { + return R2{+2.3 * (x[0] - 2) * (x[0] - 2) * (x[0] - 2), // + -1.3 * (x[1] - 1) * (x[1] - 1) * (x[1] - 1)}; + }; + + DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R2_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(uh); + + auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<2, const R2>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_uh, std::function(R2_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + + SECTION("vector of R2") + { + std::array<std::function<R2(const R2&)>, 2> vector_exact // + = {[](const R2& x) -> R2 { + return R2{+1.7 * (x[0] - 2) * (x[0] - 2) * (x[0] - 2), // + -0.6 * (x[1] - 1) * (x[1] - 1) * (x[1] - 1)}; + }, + [](const R2& x) -> R2 { + return R2{-2.3 * (x[0] - 2) * (x[0] - 2) * (x[0] - 2), // + +1.1 * (x[1] - 1) * (x[1] - 1) * (x[1] - 1)}; + }}; + + DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh); + + auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<2, const R2>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + } + + SECTION("3D") + { + std::vector<PolynomialReconstructionDescriptor> descriptor_list = + {PolynomialReconstructionDescriptor{IntegrationMethodType::element, degree, + std::vector<std::shared_ptr< + const IBoundaryDescriptor>>{std:: + make_shared<NamedBoundaryDescriptor>( + "XMAX"), + std::make_shared<NamedBoundaryDescriptor>( + "YMAX"), + std::make_shared<NamedBoundaryDescriptor>( + "ZMAX")}}, + PolynomialReconstructionDescriptor{IntegrationMethodType::boundary, degree, + std::vector<std::shared_ptr< + const IBoundaryDescriptor>>{std:: + make_shared<NamedBoundaryDescriptor>( + "XMAX"), + std::make_shared<NamedBoundaryDescriptor>( + "YMAX"), + std::make_shared<NamedBoundaryDescriptor>( + "ZMAX")}}}; + + using R3 = TinyVector<3>; + + auto p_mesh = CartesianMeshBuilder{TinyVector<3>{0, 0, 0}, TinyVector<3>{2, 1, 1}, TinyVector<3, size_t>{3, 3, 3}} + .mesh() + ->get<Mesh<3>>(); + + auto& mesh = *p_mesh; + + for (auto descriptor : descriptor_list) { + SECTION(name(descriptor.integrationMethodType())) + { + SECTION("R^3 data") + { + auto R3_exact = [](const R3& x) -> R3 { + return R3{+2.3 * (x[0] - 2) * (x[0] - 2) * (x[0] - 2), // + -1.3 * (x[1] - 1) * (x[1] - 1) * (x[1] - 1), // + +1.4 * (x[2] - 1) * (x[2] - 1) * (x[2] - 1)}; + }; + + DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R3_exact)); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(uh); + + auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<3, const R3>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_uh, std::function(R3_exact)); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + + SECTION("vector of R3") + { + std::array<std::function<R3(const R3&)>, 2> vector_exact // + = {[](const R3& x) -> R3 { + return R3{+1.7 * (x[0] - 2) * (x[0] - 2) * (x[0] - 2), // + -0.6 * (x[1] - 1) * (x[1] - 1) * (x[1] - 1), // + +1.2 * (x[2] - 1) * (x[2] - 1) * (x[2] - 1)}; + }, + [](const R3& x) -> R3 { + return R3{-2.3 * (x[0] - 2) * (x[0] - 2) * (x[0] - 2), // + +1.1 * (x[1] - 1) * (x[1] - 1) * (x[1] - 1), // + -0.3 * (x[2] - 1) * (x[2] - 1) * (x[2] - 1)}; + }}; + + DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh); + + auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<3, const R3>>(); + + double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact); + REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + } + } +} diff --git a/tests/test_QuadraticPolynomialReconstruction.cpp b/tests/test_QuadraticPolynomialReconstruction.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c9b1e722294627e19ce2d5cadcfbcb067ef7c881 --- /dev/null +++ b/tests/test_QuadraticPolynomialReconstruction.cpp @@ -0,0 +1,1041 @@ +#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 <analysis/GaussLegendreQuadratureDescriptor.hpp> +#include <analysis/GaussQuadratureDescriptor.hpp> +#include <analysis/QuadratureFormula.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/Mesh.hpp> +#include <mesh/MeshDataManager.hpp> +#include <scheme/DiscreteFunctionDPkVariant.hpp> +#include <scheme/DiscreteFunctionP0.hpp> +#include <scheme/DiscreteFunctionVariant.hpp> +#include <scheme/PolynomialReconstruction.hpp> + +#include <MeshDataBaseForTests.hpp> + +// clazy:excludeall=non-pod-global-static + +TEST_CASE("QuadraticPolynomialReconstruction", "[scheme]") +{ + SECTION("degree 2") + { + SECTION("1D") + { + PolynomialReconstructionDescriptor descriptor{IntegrationMethodType::element, 2}; + using R1 = TinyVector<1>; + + QuadratureFormula<1> qf = QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor{2}); + + SECTION("R data") + { + 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 f_exact = [](const R1& x) { return 2.3 + 1.7 * x[0] - 3.2 * x[0] * x[0]; }; + + auto xr = mesh.xr(); + auto Vj = MeshDataManager::instance().getMeshData(mesh).Vj(); + + auto cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix(); + + DiscreteFunctionP0<double> fh{p_mesh}; + + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { + auto cell_node_list = cell_to_node_matrix[cell_id]; + double value = 0; + LineTransformation<1> T{xr[cell_node_list[0]], xr[cell_node_list[1]]}; + + for (size_t i_q = 0; i_q < qf.numberOfPoints(); ++i_q) { + value += qf.weight(i_q) * f_exact(T(qf.point(i_q))) * T.jacobianDeterminant(); + } + + fh[cell_id] = value / Vj[cell_id]; + }); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(fh); + + auto dpk_fh = reconstructions[0]->get<DiscreteFunctionDPk<1, const double>>(); + + { + double L1_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const auto cell_node_list = cell_to_node_matrix[cell_id]; + const auto dpkj = dpk_fh[cell_id]; + + LineTransformation<1> T{xr[cell_node_list[0]], xr[cell_node_list[1]]}; + + double error = 0; + + for (size_t i_q = 0; i_q < qf.numberOfPoints(); ++i_q) { + error += std::abs(qf.weight(i_q) * (f_exact(T(qf.point(i_q))) - dpkj(T(qf.point(i_q)))) * + T.jacobianDeterminant()); + } + + L1_error += error; + } + REQUIRE(parallel::allReduceMax(L1_error) == Catch::Approx(0).margin(1E-13)); + } + } + } + } + + SECTION("R^3 data") + { + using R3 = TinyVector<3>; + + 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 u_exact = [](const R1& X) -> R3 { + const double x = X[0]; + + return R3{2.3 + 1.7 * x - 3.2 * x * x, // + 7 + 5 * x + 3 * x * x, // + -4 + 3.5 * x + 2.7 * x * x}; + }; + + auto xr = mesh.xr(); + auto Vj = MeshDataManager::instance().getMeshData(mesh).Vj(); + + auto cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix(); + + DiscreteFunctionP0<R3> uh{p_mesh}; + + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { + auto cell_node_list = cell_to_node_matrix[cell_id]; + R3 value = zero; + LineTransformation<1> T{xr[cell_node_list[0]], xr[cell_node_list[1]]}; + + for (size_t i_q = 0; i_q < qf.numberOfPoints(); ++i_q) { + value += qf.weight(i_q) * T.jacobianDeterminant() * u_exact(T(qf.point(i_q))); + } + + uh[cell_id] = 1. / Vj[cell_id] * value; + }); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(uh); + + auto dpk_fh = reconstructions[0]->get<DiscreteFunctionDPk<1, const R3>>(); + + { + double L1_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const auto cell_node_list = cell_to_node_matrix[cell_id]; + const auto dpkj = dpk_fh[cell_id]; + + LineTransformation<1> T{xr[cell_node_list[0]], xr[cell_node_list[1]]}; + + double error = 0; + + for (size_t i_q = 0; i_q < qf.numberOfPoints(); ++i_q) { + R3 diff = + qf.weight(i_q) * T.jacobianDeterminant() * (u_exact(T(qf.point(i_q))) - dpkj(T(qf.point(i_q)))); + error += std::abs(diff[0]) + std::abs(diff[1]) + std::abs(diff[2]); + } + + L1_error += error; + } + REQUIRE(parallel::allReduceMax(L1_error) == Catch::Approx(0).margin(1E-13)); + } + } + } + } + + SECTION("R^3x3 data") + { + using R3x3 = TinyMatrix<3, 3>; + + 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 A_exact = [](const R1& X) -> R3x3 { + const double x = X[0]; + const double x2 = x * x; + return R3x3{+2.3 + 1.7 * x + 2.9 * x2, // + -1.7 + 2.1 * x + 1.7 * x2, // + +1.4 - 0.6 * x - 0.5 * x2, // + // + +2.4 - 2.3 * x + 2.3 * x2, // + -0.2 + 3.1 * x - 1.9 * x2, // + -3.2 - 3.6 * x - 0.3 * x2, // + // + -4.1 + 3.1 * x - 1.3 * x2, // + +0.8 + 2.9 * x + 2.1 * x2, // + -1.6 + 2.3 * x + 0.8 * x2}; + }; + + auto xr = mesh.xr(); + auto Vj = MeshDataManager::instance().getMeshData(mesh).Vj(); + + auto cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix(); + DiscreteFunctionP0<R3x3> Ah{p_mesh}; + + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { + auto cell_node_list = cell_to_node_matrix[cell_id]; + R3x3 value = zero; + LineTransformation<1> T{xr[cell_node_list[0]], xr[cell_node_list[1]]}; + + for (size_t i_q = 0; i_q < qf.numberOfPoints(); ++i_q) { + value += qf.weight(i_q) * T.jacobianDeterminant() * A_exact(T(qf.point(i_q))); + } + + Ah[cell_id] = 1. / Vj[cell_id] * value; + }); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Ah); + + auto dpk_Ah = reconstructions[0]->get<DiscreteFunctionDPk<1, const R3x3>>(); + + { + double L1_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const auto cell_node_list = cell_to_node_matrix[cell_id]; + const auto dpkj = dpk_Ah[cell_id]; + + LineTransformation<1> T{xr[cell_node_list[0]], xr[cell_node_list[1]]}; + + double error = 0; + + for (size_t i_q = 0; i_q < qf.numberOfPoints(); ++i_q) { + R3x3 diff = + qf.weight(i_q) * T.jacobianDeterminant() * (A_exact(T(qf.point(i_q))) - dpkj(T(qf.point(i_q)))); + for (size_t i = 0; i < diff.numberOfRows(); ++i) { + for (size_t j = 0; j < diff.numberOfColumns(); ++j) { + error += std::abs(diff(i, j)); + } + } + } + + L1_error += error; + } + REQUIRE(parallel::allReduceMax(L1_error) == Catch::Approx(0).margin(1E-13)); + } + } + } + } + + SECTION("R vector data") + { + using R3 = TinyVector<3>; + + 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 u_exact = [](const R1& X) -> R3 { + const double x = X[0]; + + return R3{2.3 + 1.7 * x - 3.2 * x * x, // + 7 + 5 * x + 3 * x * x, // + -4 + 3.5 * x + 2.7 * x * x}; + }; + + auto xr = mesh.xr(); + auto Vj = MeshDataManager::instance().getMeshData(mesh).Vj(); + + auto cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix(); + + DiscreteFunctionP0Vector<double> uh{p_mesh, 3}; + + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { + auto cell_node_list = cell_to_node_matrix[cell_id]; + R3 value = zero; + LineTransformation<1> T{xr[cell_node_list[0]], xr[cell_node_list[1]]}; + + for (size_t i_q = 0; i_q < qf.numberOfPoints(); ++i_q) { + value += qf.weight(i_q) * T.jacobianDeterminant() * u_exact(T(qf.point(i_q))); + } + + value *= 1. / Vj[cell_id]; + + for (size_t i = 0; i < value.dimension(); ++i) { + uh[cell_id][i] = value[i]; + } + }); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(uh); + + auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPkVector<1, const double>>(); + + { + double L1_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const auto cell_node_list = cell_to_node_matrix[cell_id]; + + LineTransformation<1> T{xr[cell_node_list[0]], xr[cell_node_list[1]]}; + + double error = 0; + + for (size_t i_q = 0; i_q < qf.numberOfPoints(); ++i_q) { + for (size_t l = 0; l < dpk_uh.numberOfComponents(); ++l) { + error += std::abs(qf.weight(i_q) * T.jacobianDeterminant() * + (u_exact(T(qf.point(i_q)))[l] - dpk_uh(cell_id, l)(T(qf.point(i_q))))); + } + } + + L1_error += error; + } + REQUIRE(parallel::allReduceMax(L1_error) == Catch::Approx(0).margin(1E-13)); + } + } + } + } + + SECTION("list of various types") + { + using R3x3 = TinyMatrix<3>; + using R3 = TinyVector<3>; + + 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 f_exact = [](const R1& x) { return 2.3 + 1.7 * x[0] - 3.2 * x[0] * x[0]; }; + + auto u_exact = [](const R1& X) -> R3 { + const double x = X[0]; + + return R3{2.3 + 1.7 * x - 3.2 * x * x, // + 7 + 5 * x + 3 * x * x, // + -4 + 3.5 * x + 2.7 * x * x}; + }; + + auto A_exact = [](const R1& X) -> R3x3 { + const double x = X[0]; + const double x2 = x * x; + return R3x3{+2.3 + 1.7 * x + 2.9 * x2, // + -1.7 + 2.1 * x + 1.7 * x2, // + +1.4 - 0.6 * x - 0.5 * x2, // + // + +2.4 - 2.3 * x + 2.3 * x2, // + -0.2 + 3.1 * x - 1.9 * x2, // + -3.2 - 3.6 * x - 0.3 * x2, // + // + -4.1 + 3.1 * x - 1.3 * x2, // + +0.8 + 2.9 * x + 2.1 * x2, // + -1.6 + 2.3 * x + 0.8 * x2}; + }; + + auto xr = mesh.xr(); + auto Vj = MeshDataManager::instance().getMeshData(mesh).Vj(); + + auto cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix(); + + DiscreteFunctionP0Vector<double> vh{p_mesh, 3}; + + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { + auto cell_node_list = cell_to_node_matrix[cell_id]; + R3 value = zero; + LineTransformation<1> T{xr[cell_node_list[0]], xr[cell_node_list[1]]}; + + for (size_t i_q = 0; i_q < qf.numberOfPoints(); ++i_q) { + value += qf.weight(i_q) * T.jacobianDeterminant() * u_exact(T(qf.point(i_q))); + } + + value *= 1. / Vj[cell_id]; + + for (size_t i = 0; i < value.dimension(); ++i) { + vh[cell_id][i] = value[i]; + } + }); + + DiscreteFunctionP0<double> fh{p_mesh}; + + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { + auto cell_node_list = cell_to_node_matrix[cell_id]; + double value = 0; + LineTransformation<1> T{xr[cell_node_list[0]], xr[cell_node_list[1]]}; + + for (size_t i_q = 0; i_q < qf.numberOfPoints(); ++i_q) { + value += qf.weight(i_q) * f_exact(T(qf.point(i_q))) * T.jacobianDeterminant(); + } + + fh[cell_id] = value / Vj[cell_id]; + }); + + DiscreteFunctionP0<R3x3> Ah{p_mesh}; + + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { + auto cell_node_list = cell_to_node_matrix[cell_id]; + R3x3 value = zero; + LineTransformation<1> T{xr[cell_node_list[0]], xr[cell_node_list[1]]}; + + for (size_t i_q = 0; i_q < qf.numberOfPoints(); ++i_q) { + value += qf.weight(i_q) * T.jacobianDeterminant() * A_exact(T(qf.point(i_q))); + } + + Ah[cell_id] = 1. / Vj[cell_id] * value; + }); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Ah, vh, fh); + + auto dpk_Ah = reconstructions[0]->get<DiscreteFunctionDPk<1, const R3x3>>(); + auto dpk_vh = reconstructions[1]->get<DiscreteFunctionDPkVector<1, const double>>(); + auto dpk_fh = reconstructions[2]->get<DiscreteFunctionDPk<1, const double>>(); + + { + double L1_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const auto cell_node_list = cell_to_node_matrix[cell_id]; + + LineTransformation<1> T{xr[cell_node_list[0]], xr[cell_node_list[1]]}; + + double error = 0; + + for (size_t i_q = 0; i_q < qf.numberOfPoints(); ++i_q) { + for (size_t l = 0; l < dpk_vh.numberOfComponents(); ++l) { + error += std::abs(qf.weight(i_q) * T.jacobianDeterminant() * + (u_exact(T(qf.point(i_q)))[l] - dpk_vh(cell_id, l)(T(qf.point(i_q))))); + } + } + + L1_error += error; + } + REQUIRE(parallel::allReduceMax(L1_error) == Catch::Approx(0).margin(1E-13)); + } + + { + double L1_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const auto cell_node_list = cell_to_node_matrix[cell_id]; + const auto dpkj = dpk_fh[cell_id]; + + LineTransformation<1> T{xr[cell_node_list[0]], xr[cell_node_list[1]]}; + + double error = 0; + + for (size_t i_q = 0; i_q < qf.numberOfPoints(); ++i_q) { + error += std::abs(qf.weight(i_q) * (f_exact(T(qf.point(i_q))) - dpkj(T(qf.point(i_q)))) * + T.jacobianDeterminant()); + } + + L1_error += error; + } + REQUIRE(parallel::allReduceMax(L1_error) == Catch::Approx(0).margin(1E-13)); + } + + { + double L1_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const auto cell_node_list = cell_to_node_matrix[cell_id]; + const auto dpkj = dpk_Ah[cell_id]; + + LineTransformation<1> T{xr[cell_node_list[0]], xr[cell_node_list[1]]}; + + double error = 0; + + for (size_t i_q = 0; i_q < qf.numberOfPoints(); ++i_q) { + R3x3 diff = + qf.weight(i_q) * T.jacobianDeterminant() * (A_exact(T(qf.point(i_q))) - dpkj(T(qf.point(i_q)))); + for (size_t i = 0; i < diff.numberOfRows(); ++i) { + for (size_t j = 0; j < diff.numberOfColumns(); ++j) { + error += std::abs(diff(i, j)); + } + } + } + + L1_error += error; + } + REQUIRE(parallel::allReduceMax(L1_error) == Catch::Approx(0).margin(1E-13)); + } + } + } + } + } + + SECTION("2D") + { + using R2 = TinyVector<2>; + + auto integrate_in_cell = [](const CellType& cell_type, const auto& cell_node_list, const auto& xr, + const auto& exact, auto& value) { + switch (cell_type) { + case CellType::Triangle: { + TriangleTransformation<2> T{xr[cell_node_list[0]], xr[cell_node_list[1]], xr[cell_node_list[2]]}; + QuadratureFormula<2> qf = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor{2}); + for (size_t i_q = 0; i_q < qf.numberOfPoints(); ++i_q) { + value += qf.weight(i_q) * T.jacobianDeterminant() * exact(T(qf.point(i_q))); + } + 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]]}; + QuadratureFormula<2> qf = + QuadratureManager::instance().getSquareFormula(GaussLegendreQuadratureDescriptor{3}); + for (size_t i_q = 0; i_q < qf.numberOfPoints(); ++i_q) { + const R2 x_hat = qf.point(i_q); + const R2 x = T(x_hat); + value += qf.weight(i_q) * T.jacobianDeterminant(x_hat) * exact(x); + } + break; + } + default: { + throw UnexpectedError("invalid cell type"); + } + } + }; + + auto compute_L1_error = [](const auto& cell_type, const auto& mesh, const auto& exact, const auto& dpk) { + auto cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix(); + const auto& xr = mesh.xr(); + + using DataType = typename std::decay_t<decltype(dpk)>::data_type; + + auto sum_abs = [](const DataType& diff) -> double { + if constexpr (std::is_arithmetic_v<DataType>) { + return std::abs(diff); + } else if constexpr (is_tiny_vector_v<DataType>) { + double sum = 0; + for (size_t i = 0; i < diff.dimension(); ++i) { + sum += std::abs(diff[i]); + } + return sum; + } else if constexpr (is_tiny_matrix_v<DataType>) { + double sum = 0; + for (size_t i = 0; i < diff.numberOfRows(); ++i) { + for (size_t j = 0; j < diff.numberOfRows(); ++j) { + sum += std::abs(diff(i, j)); + } + } + return sum; + } else { + throw UnexpectedError("unexpected value type"); + } + }; + + double L1_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const auto cell_node_list = cell_to_node_matrix[cell_id]; + const auto dpkj = dpk[cell_id]; + + double error = 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]]}; + QuadratureFormula<2> qf = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor{2}); + for (size_t i_q = 0; i_q < qf.numberOfPoints(); ++i_q) { + const R2 x = T(qf.point(i_q)); + DataType diff = qf.weight(i_q) * T.jacobianDeterminant() * (exact(x) - dpkj(x)); + error += sum_abs(diff); + } + 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]]}; + QuadratureFormula<2> qf = + QuadratureManager::instance().getSquareFormula(GaussLegendreQuadratureDescriptor{3}); + for (size_t i_q = 0; i_q < qf.numberOfPoints(); ++i_q) { + const R2 x_hat = qf.point(i_q); + const R2 x = T(x_hat); + DataType diff = qf.weight(i_q) * T.jacobianDeterminant(x_hat) * (exact(x) - dpkj(x)); + error += sum_abs(diff); + } + break; + } + default: { + throw UnexpectedError("invalid cell type"); + } + } + + L1_error += error; + } + return L1_error; + }; + + std::vector<PolynomialReconstructionDescriptor> descriptor_list = + {PolynomialReconstructionDescriptor{IntegrationMethodType::element, 2}, + PolynomialReconstructionDescriptor{IntegrationMethodType::boundary, 2}}; + + for (auto descriptor : descriptor_list) { + SECTION(name(descriptor.integrationMethodType())) + { + SECTION("R data") + { + for (auto named_mesh : MeshDataBaseForTests::get().all2DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<2>>(); + auto& mesh = *p_mesh; + + auto f_exact = [](const R2& x) { + return 2.3 + 1.7 * x[0] + 0.2 * x[1] - 3.2 * x[0] * x[0] + 1.3 * x[0] * x[1] - 1.4 * x[1] * x[1]; + }; + + auto xr = mesh.xr(); + auto Vj = MeshDataManager::instance().getMeshData(mesh).Vj(); + + auto cell_type = mesh.connectivity().cellType(); + auto cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix(); + + DiscreteFunctionP0<double> fh{p_mesh}; + + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { + auto cell_node_list = cell_to_node_matrix[cell_id]; + double value = 0; + integrate_in_cell(cell_type[cell_id], cell_node_list, xr, f_exact, value); + fh[cell_id] = value / Vj[cell_id]; + }); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(fh); + + auto dpk_fh = reconstructions[0]->get<DiscreteFunctionDPk<2, const double>>(); + + double L1_error = compute_L1_error(cell_type, mesh, f_exact, dpk_fh); + REQUIRE(parallel::allReduceMax(L1_error) == Catch::Approx(0).margin(1E-13)); + } + } + } + + SECTION("R^3 data") + { + using R3 = TinyVector<3>; + + for (auto named_mesh : MeshDataBaseForTests::get().all2DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<2>>(); + auto& mesh = *p_mesh; + + auto u_exact = [](const R2& X) -> R3 { + const double x = X[0]; + const double y = X[1]; + const double x2 = x * x; + const double xy = x * y; + const double y2 = y * y; + + return R3{+2.3 + 1.7 * x + 1.3 * y - 3.2 * x2 + 1.6 * xy + 0.9 * y2, // + +7.0 + 5.0 * x - 2.4 * y + 3.0 * x2 - 0.8 * xy + 1.1 * y2, // + -4.0 + 3.5 * x - 0.7 * y + 2.7 * x2 - 2.1 * xy - 1.8 * y2}; + }; + + auto xr = mesh.xr(); + auto Vj = MeshDataManager::instance().getMeshData(mesh).Vj(); + + auto cell_type = mesh.connectivity().cellType(); + auto cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix(); + + DiscreteFunctionP0<R3> uh{p_mesh}; + + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { + auto cell_node_list = cell_to_node_matrix[cell_id]; + R3 value = zero; + integrate_in_cell(cell_type[cell_id], cell_node_list, xr, u_exact, value); + uh[cell_id] = 1. / Vj[cell_id] * value; + }); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(uh); + + auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<2, const R3>>(); + + double L1_error = compute_L1_error(cell_type, mesh, u_exact, dpk_uh); + REQUIRE(parallel::allReduceMax(L1_error) == Catch::Approx(0).margin(1E-13)); + } + } + } + + SECTION("R^2x2 data") + { + using R2x2 = TinyMatrix<2, 2>; + + for (auto named_mesh : MeshDataBaseForTests::get().all2DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<2>>(); + auto& mesh = *p_mesh; + + auto A_exact = [](const R2& X) -> R2x2 { + const double x = X[0]; + const double y = X[1]; + const double x2 = x * x; + const double xy = x * y; + const double y2 = y * y; + + return R2x2{+2.3 + 1.7 * x + 1.2 * y + 1.1 * x2 + 0.7 * xy - 0.8 * y2, // + -1.7 + 2.1 * x - 2.2 * y - 1.9 * x2 + 0.2 * xy + 1.2 * y2, // + +1.4 - 0.6 * x - 2.1 * y + 0.3 * x2 - 3.1 * xy + 1.3 * y2, // + +2.4 - 2.3 * x + 1.3 * y - 0.8 * x2 + 1.2 * xy - 2.0 * y2}; + }; + + auto xr = mesh.xr(); + auto Vj = MeshDataManager::instance().getMeshData(mesh).Vj(); + + auto cell_type = mesh.connectivity().cellType(); + auto cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix(); + + DiscreteFunctionP0<R2x2> Ah{p_mesh}; + + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { + auto cell_node_list = cell_to_node_matrix[cell_id]; + R2x2 value = zero; + integrate_in_cell(cell_type[cell_id], cell_node_list, xr, A_exact, value); + Ah[cell_id] = 1. / Vj[cell_id] * value; + }); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Ah); + + auto dpk_Ah = reconstructions[0]->get<DiscreteFunctionDPk<2, const R2x2>>(); + + double L1_error = compute_L1_error(cell_type, mesh, A_exact, dpk_Ah); + REQUIRE(parallel::allReduceMax(L1_error) == Catch::Approx(0).margin(1E-13)); + } + } + } + } + } + } + + SECTION("3D") + { + PolynomialReconstructionDescriptor descriptor{IntegrationMethodType::element, 2}; + + using R3 = TinyVector<3>; + + auto integrate_in_cell = [](const CellType& cell_type, const auto& cell_node_list, const auto& xr, + const auto& exact, auto& value) { + switch (cell_type) { + 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]]}; + QuadratureFormula<3> qf = QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor{2}); + for (size_t i_q = 0; i_q < qf.numberOfPoints(); ++i_q) { + const R3 x_hat = qf.point(i_q); + const R3 x = T(x_hat); + value += qf.weight(i_q) * T.jacobianDeterminant() * exact(x); + } + 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]]}; + QuadratureFormula<3> qf = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor{3}); + for (size_t i_q = 0; i_q < qf.numberOfPoints(); ++i_q) { + const R3 x_hat = qf.point(i_q); + const R3 x = T(x_hat); + value += qf.weight(i_q) * T.jacobianDeterminant(x_hat) * exact(x); + } + 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]]}; + QuadratureFormula<3> qf = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor{3}); + for (size_t i_q = 0; i_q < qf.numberOfPoints(); ++i_q) { + const R3 x_hat = qf.point(i_q); + const R3 x = T(x_hat); + value += qf.weight(i_q) * T.jacobianDeterminant(x_hat) * exact(x); + } + 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]]}; + QuadratureFormula<3> qf = QuadratureManager::instance().getCubeFormula(GaussLegendreQuadratureDescriptor{3}); + for (size_t i_q = 0; i_q < qf.numberOfPoints(); ++i_q) { + const R3 x_hat = qf.point(i_q); + const R3 x = T(x_hat); + value += qf.weight(i_q) * T.jacobianDeterminant(x_hat) * exact(x); + } + break; + } + default: { + throw UnexpectedError("invalid cell type"); + } + } + }; + + auto compute_L1_error = [](const auto& cell_type, const auto& mesh, const auto& exact, const auto& dpk) { + auto cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix(); + const auto& xr = mesh.xr(); + + using DataType = typename std::decay_t<decltype(dpk)>::data_type; + + auto sum_abs = [](const DataType& diff) -> double { + if constexpr (std::is_arithmetic_v<DataType>) { + return std::abs(diff); + } else if constexpr (is_tiny_vector_v<DataType>) { + double sum = 0; + for (size_t i = 0; i < diff.dimension(); ++i) { + sum += std::abs(diff[i]); + } + return sum; + } else if constexpr (is_tiny_matrix_v<DataType>) { + double sum = 0; + for (size_t i = 0; i < diff.numberOfRows(); ++i) { + for (size_t j = 0; j < diff.numberOfRows(); ++j) { + sum += std::abs(diff(i, j)); + } + } + return sum; + } else { + throw UnexpectedError("unexpected value type"); + } + }; + + double L1_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const auto cell_node_list = cell_to_node_matrix[cell_id]; + const auto dpkj = dpk[cell_id]; + + double error = 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]]}; + QuadratureFormula<3> qf = QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor{2}); + for (size_t i_q = 0; i_q < qf.numberOfPoints(); ++i_q) { + const R3 x_hat = qf.point(i_q); + const R3 x = T(x_hat); + DataType diff = qf.weight(i_q) * T.jacobianDeterminant() * (exact(x) - dpkj(x)); + error += sum_abs(diff); + } + 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]]}; + QuadratureFormula<3> qf = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor{3}); + for (size_t i_q = 0; i_q < qf.numberOfPoints(); ++i_q) { + const R3 x_hat = qf.point(i_q); + const R3 x = T(x_hat); + DataType diff = qf.weight(i_q) * T.jacobianDeterminant(x_hat) * (exact(x) - dpkj(x)); + error += sum_abs(diff); + } + 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]]}; + QuadratureFormula<3> qf = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor{3}); + for (size_t i_q = 0; i_q < qf.numberOfPoints(); ++i_q) { + const R3 x_hat = qf.point(i_q); + const R3 x = T(x_hat); + DataType diff = qf.weight(i_q) * T.jacobianDeterminant(x_hat) * (exact(x) - dpkj(x)); + error += sum_abs(diff); + } + 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]]}; + QuadratureFormula<3> qf = + QuadratureManager::instance().getCubeFormula(GaussLegendreQuadratureDescriptor{3}); + for (size_t i_q = 0; i_q < qf.numberOfPoints(); ++i_q) { + const R3 x_hat = qf.point(i_q); + const R3 x = T(x_hat); + DataType diff = qf.weight(i_q) * T.jacobianDeterminant(x_hat) * (exact(x) - dpkj(x)); + error += sum_abs(diff); + } + break; + } + default: { + throw UnexpectedError("invalid cell type"); + } + } + + L1_error += error; + } + return L1_error; + }; + + SECTION("R data") + { + for (auto named_mesh : MeshDataBaseForTests::get().all3DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<3>>(); + auto& mesh = *p_mesh; + auto f_exact = [](const R3& X) { + const double x = X[0]; + const double y = X[1]; + const double z = X[2]; + const double x2 = x * x; + const double xy = x * y; + const double xz = x * z; + const double y2 = y * y; + const double yz = y * z; + const double z2 = z * z; + + return 2.3 + 1.7 * x + 0.2 * y + 1.4 * z - 3.2 * x2 + 1.3 * xy - 1.6 * xz - 1.4 * y2 + 2 * yz - 1.8 * z2; + }; + + auto xr = mesh.xr(); + auto Vj = MeshDataManager::instance().getMeshData(mesh).Vj(); + + auto cell_type = mesh.connectivity().cellType(); + auto cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix(); + + DiscreteFunctionP0<double> fh{p_mesh}; + + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { + auto cell_node_list = cell_to_node_matrix[cell_id]; + double value = 0; + integrate_in_cell(cell_type[cell_id], cell_node_list, xr, f_exact, value); + fh[cell_id] = value / Vj[cell_id]; + }); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(fh); + + auto dpk_fh = reconstructions[0]->get<DiscreteFunctionDPk<3, const double>>(); + + double L1_error = compute_L1_error(cell_type, mesh, f_exact, dpk_fh); + REQUIRE(parallel::allReduceMax(L1_error) == Catch::Approx(0).margin(1E-13)); + } + } + } + + SECTION("R^3 data") + { + for (auto named_mesh : MeshDataBaseForTests::get().all3DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<3>>(); + auto& mesh = *p_mesh; + + auto u_exact = [](const R3& X) -> R3 { + const double x = X[0]; + const double y = X[1]; + const double z = X[2]; + const double x2 = x * x; + const double xy = x * y; + const double xz = x * z; + const double y2 = y * y; + const double yz = y * z; + const double z2 = z * z; + + return R3{+2.3 + 1.7 * x - 2.2 * y + 1.8 * z // + + 0.3 * x2 - 1.3 * xy + 2.7 * xz + 0.7 * y2 + 2.0 * yz - 1.2 * z2, // + +1.4 - 0.6 * x + 1.3 * y - 3.7 * z // + + 3.1 * x2 - 2.4 * xy - 1.5 * xz - 1.9 * y2 + 0.2 * yz + 0.9 * z2, // + -0.2 + 3.1 * x - 1.1 * y + 1.9 * z // + - 1.3 * x2 + 2.8 * xy - 2.1 * xz + 3.2 * y2 - 2.1 * yz + 1.6 * z2}; + }; + + auto xr = mesh.xr(); + auto Vj = MeshDataManager::instance().getMeshData(mesh).Vj(); + + auto cell_type = mesh.connectivity().cellType(); + auto cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix(); + + DiscreteFunctionP0<R3> uh{p_mesh}; + + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { + auto cell_node_list = cell_to_node_matrix[cell_id]; + R3 value = zero; + integrate_in_cell(cell_type[cell_id], cell_node_list, xr, u_exact, value); + uh[cell_id] = 1. / Vj[cell_id] * value; + }); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(uh); + + auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<3, const R3>>(); + double L1_error = compute_L1_error(cell_type, mesh, u_exact, dpk_uh); + REQUIRE(parallel::allReduceMax(L1_error) == Catch::Approx(0).margin(1E-13)); + } + } + } + + SECTION("R^2x2 data") + { + using R2x2 = TinyMatrix<2, 2>; + + for (auto named_mesh : MeshDataBaseForTests::get().all3DMeshes()) { + SECTION(named_mesh.name()) + { + auto p_mesh = named_mesh.mesh()->get<Mesh<3>>(); + auto& mesh = *p_mesh; + + auto A_exact = [](const R3& X) -> R2x2 { + const double x = X[0]; + const double y = X[1]; + const double z = X[2]; + const double x2 = x * x; + const double xy = x * y; + const double xz = x * z; + const double y2 = y * y; + const double yz = y * z; + const double z2 = z * z; + + return R2x2{+2.3 + 1.7 * x - 2.2 * y + 1.8 * z // + + 0.3 * x2 - 1.3 * xy + 2.7 * xz + 0.7 * y2 + 2.0 * yz - 1.2 * z2, // + +1.4 - 0.6 * x + 1.3 * y - 3.7 * z // + + 3.1 * x2 - 2.4 * xy - 1.5 * xz - 1.9 * y2 + 0.2 * yz + 0.9 * z2, // + // + -0.2 + 3.1 * x - 1.1 * y + 1.9 * z // + - 1.3 * x2 + 2.8 * xy - 2.1 * xz + 3.2 * y2 - 2.1 * yz + 1.6 * z2, // + +0.9 - 1.3 * x + 2.1 * y + 3.1 * z // + + 1.1 * x2 + 1.8 * xy + 1.2 * xz - 2.3 * y2 + 3.3 * yz - 1.2 * z2}; + }; + + auto xr = mesh.xr(); + auto Vj = MeshDataManager::instance().getMeshData(mesh).Vj(); + + auto cell_type = mesh.connectivity().cellType(); + auto cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix(); + + DiscreteFunctionP0<R2x2> Ah{p_mesh}; + + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { + auto cell_node_list = cell_to_node_matrix[cell_id]; + R2x2 value = zero; + integrate_in_cell(cell_type[cell_id], cell_node_list, xr, A_exact, value); + Ah[cell_id] = 1. / Vj[cell_id] * value; + }); + + descriptor.setRowWeighting(false); + auto reconstructions = PolynomialReconstruction{descriptor}.build(Ah); + + auto dpk_Ah = reconstructions[0]->get<DiscreteFunctionDPk<3, const R2x2>>(); + } + } + } + } + } +} diff --git a/tests/test_StencilBuilder_cell2cell.cpp b/tests/test_StencilBuilder_cell2cell.cpp new file mode 100644 index 0000000000000000000000000000000000000000..42ceb6a5da583a9869035db09f5239d008c6f780 --- /dev/null +++ b/tests/test_StencilBuilder_cell2cell.cpp @@ -0,0 +1,1081 @@ +#include <catch2/catch_test_macros.hpp> +#include <catch2/matchers/catch_matchers_all.hpp> + +#include <MeshDataBaseForTests.hpp> +#include <mesh/CartesianMeshBuilder.hpp> +#include <mesh/Connectivity.hpp> +#include <mesh/ConnectivityUtils.hpp> +#include <mesh/ItemValue.hpp> +#include <mesh/ItemValueUtils.hpp> +#include <mesh/Mesh.hpp> +#include <mesh/MeshFaceBoundary.hpp> +#include <mesh/MeshVariant.hpp> +#include <mesh/NamedBoundaryDescriptor.hpp> +#include <mesh/StencilManager.hpp> +#include <utils/Messenger.hpp> + +#include <NbGhostLayersTester.hpp> + +// clazy:excludeall=non-pod-global-static + +TEST_CASE("StencilBuilder cell2cell", "[mesh]") +{ + auto is_valid = []<ItemType connecting_item_type>(const auto& connectivity, const auto& stencil_array, + const size_t number_of_layers) { + auto cell_to_connecting_item_matrix = + connectivity.template getItemToItemMatrix<ItemType::cell, connecting_item_type>(); + auto connecting_to_cell_matrix = connectivity.template getItemToItemMatrix<connecting_item_type, ItemType::cell>(); + auto cell_is_owned = connectivity.cellIsOwned(); + auto cell_number = connectivity.cellNumber(); + + using ConnectingItemId = ItemIdT<connecting_item_type>; + + for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) { + if (cell_is_owned[cell_id]) { + std::vector<CellId> expected_stencil; + + std::set<CellId> marked_cell_set; + marked_cell_set.insert(cell_id); + + std::set<ConnectingItemId> marked_connecting_item_set; + std::set<ConnectingItemId> layer_connecting_item_set; + { + auto cell_to_connecting_item_list = cell_to_connecting_item_matrix[cell_id]; + for (size_t i_connecting_item = 0; i_connecting_item < cell_to_connecting_item_list.size(); + ++i_connecting_item) { + const ConnectingItemId connecting_item_id = cell_to_connecting_item_list[i_connecting_item]; + layer_connecting_item_set.insert(connecting_item_id); + marked_connecting_item_set.insert(connecting_item_id); + } + } + + for (size_t i_layer = 0; i_layer < number_of_layers; ++i_layer) { + std::set<CellId, std::function<bool(CellId, CellId)>> cell_set( + [=](CellId cell_0, CellId cell_1) { return cell_number[cell_0] < cell_number[cell_1]; }); + + for (auto&& connecting_item_id : layer_connecting_item_set) { + auto connecting_item_to_cell_list = connecting_to_cell_matrix[connecting_item_id]; + for (size_t i_connecting_item_of_cell = 0; i_connecting_item_of_cell < connecting_item_to_cell_list.size(); + ++i_connecting_item_of_cell) { + const CellId connecting_item_id_of_cell = connecting_item_to_cell_list[i_connecting_item_of_cell]; + if (not marked_cell_set.contains(connecting_item_id_of_cell)) { + cell_set.insert(connecting_item_id_of_cell); + marked_cell_set.insert(connecting_item_id_of_cell); + } + } + } + + layer_connecting_item_set.clear(); + for (auto layer_cell_id : cell_set) { + auto cell_to_connecting_item_list = cell_to_connecting_item_matrix[layer_cell_id]; + for (size_t i_connecting_item = 0; i_connecting_item < cell_to_connecting_item_list.size(); + ++i_connecting_item) { + const ConnectingItemId connecting_item_id = cell_to_connecting_item_list[i_connecting_item]; + if (not marked_connecting_item_set.contains(connecting_item_id)) { + layer_connecting_item_set.insert(connecting_item_id); + marked_connecting_item_set.insert(connecting_item_id); + } + } + } + + for (auto&& set_cell_id : cell_set) { + expected_stencil.push_back(set_cell_id); + } + } + + auto cell_stencil = stencil_array[cell_id]; + + auto i_set_cell = expected_stencil.begin(); + if (cell_stencil.size() != expected_stencil.size()) { + return false; + } + + for (size_t index = 0; index < cell_stencil.size(); ++index, ++i_set_cell) { + if (*i_set_cell != cell_stencil[index]) { + return false; + } + } + } + } + return true; + }; + + SECTION("inner stencil") + { + SECTION("1 layer") + { + SECTION("1D") + { + SECTION("cartesian") + { + const auto& mesh = *MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>(); + + const Connectivity<1>& connectivity = mesh.connectivity(); + + REQUIRE( + is_valid.template + operator()<ItemType::node>(connectivity, + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_nodes}), + 1)); + + REQUIRE( + is_valid.template + operator()<ItemType::edge>(connectivity, + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_edges}), + 1)); + + REQUIRE( + is_valid.template + operator()<ItemType::face>(connectivity, + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_faces}), + 1)); + } + + SECTION("unordered") + { + const auto& mesh = *MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>(); + + const Connectivity<1>& connectivity = mesh.connectivity(); + + REQUIRE( + is_valid.template + operator()<ItemType::node>(connectivity, + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_nodes}), + 1)); + + REQUIRE( + is_valid.template + operator()<ItemType::edge>(connectivity, + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_edges}), + 1)); + + REQUIRE( + is_valid.template + operator()<ItemType::face>(connectivity, + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_faces}), + 1)); + } + } + + SECTION("2D") + { + SECTION("cartesian") + { + const auto& mesh = *MeshDataBaseForTests::get().cartesian2DMesh()->get<Mesh<2>>(); + + const Connectivity<2>& connectivity = mesh.connectivity(); + REQUIRE( + is_valid.template + operator()<ItemType::node>(connectivity, + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_nodes}), + 1)); + + REQUIRE( + is_valid.template + operator()<ItemType::edge>(connectivity, + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_edges}), + 1)); + + REQUIRE( + is_valid.template + operator()<ItemType::face>(connectivity, + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_faces}), + 1)); + } + + SECTION("hybrid") + { + const auto& mesh = *MeshDataBaseForTests::get().hybrid2DMesh()->get<Mesh<2>>(); + + const Connectivity<2>& connectivity = mesh.connectivity(); + REQUIRE( + is_valid.template + operator()<ItemType::node>(connectivity, + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_nodes}), + 1)); + + REQUIRE( + is_valid.template + operator()<ItemType::edge>(connectivity, + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_edges}), + 1)); + + REQUIRE( + is_valid.template + operator()<ItemType::face>(connectivity, + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_faces}), + 1)); + } + } + + SECTION("3D") + { + SECTION("carteian") + { + const auto& mesh = *MeshDataBaseForTests::get().cartesian3DMesh()->get<Mesh<3>>(); + + const Connectivity<3>& connectivity = mesh.connectivity(); + REQUIRE( + is_valid.template + operator()<ItemType::node>(connectivity, + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_nodes}), + 1)); + + REQUIRE( + is_valid.template + operator()<ItemType::edge>(connectivity, + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_edges}), + 1)); + + REQUIRE( + is_valid.template + operator()<ItemType::face>(connectivity, + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_faces}), + 1)); + } + + SECTION("hybrid") + { + const auto& mesh = *MeshDataBaseForTests::get().hybrid3DMesh()->get<Mesh<3>>(); + + const Connectivity<3>& connectivity = mesh.connectivity(); + REQUIRE( + is_valid.template + operator()<ItemType::node>(connectivity, + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_nodes}), + 1)); + + REQUIRE( + is_valid.template + operator()<ItemType::edge>(connectivity, + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_edges}), + 1)); + + REQUIRE( + is_valid.template + operator()<ItemType::face>(connectivity, + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_faces}), + 1)); + } + } + } + + SECTION("2 layers") + { + NbGhostLayersTester nb_ghost_layers_tester(2); + + SECTION("1D") + { + SECTION("cartesian") + { + auto mesh_v = CartesianMeshBuilder(TinyVector<1>{0}, TinyVector<1>{1}, TinyVector<1, uint64_t>(20)).mesh(); + + const auto& mesh = *mesh_v->get<Mesh<1>>(); + + REQUIRE( + is_valid.template + operator()<ItemType::node>(mesh.connectivity(), + StencilManager::instance() + .getCellToCellStencilArray(mesh.connectivity(), + StencilDescriptor{2, StencilDescriptor:: + ConnectionType::by_nodes}), + 2)); + + REQUIRE( + is_valid.template + operator()<ItemType::edge>(mesh.connectivity(), + StencilManager::instance() + .getCellToCellStencilArray(mesh.connectivity(), + StencilDescriptor{2, StencilDescriptor:: + ConnectionType::by_edges}), + 2)); + + REQUIRE( + is_valid.template + operator()<ItemType::face>(mesh.connectivity(), + StencilManager::instance() + .getCellToCellStencilArray(mesh.connectivity(), + StencilDescriptor{2, StencilDescriptor:: + ConnectionType::by_faces}), + 2)); + } + } + + SECTION("2D") + { + SECTION("cartesian") + { + auto mesh_v = + CartesianMeshBuilder(TinyVector<2>{0, 0}, TinyVector<2>{1, 2}, TinyVector<2, uint64_t>(5, 7)).mesh(); + const auto& mesh = *mesh_v->get<Mesh<2>>(); + + REQUIRE( + is_valid.template + operator()<ItemType::node>(mesh.connectivity(), + StencilManager::instance() + .getCellToCellStencilArray(mesh.connectivity(), + StencilDescriptor{2, StencilDescriptor:: + ConnectionType::by_nodes}), + 2)); + + REQUIRE( + is_valid.template + operator()<ItemType::edge>(mesh.connectivity(), + StencilManager::instance() + .getCellToCellStencilArray(mesh.connectivity(), + StencilDescriptor{2, StencilDescriptor:: + ConnectionType::by_edges}), + 2)); + + REQUIRE( + is_valid.template + operator()<ItemType::face>(mesh.connectivity(), + StencilManager::instance() + .getCellToCellStencilArray(mesh.connectivity(), + StencilDescriptor{2, StencilDescriptor:: + ConnectionType::by_faces}), + 2)); + } + } + + SECTION("3D") + { + SECTION("carteian") + { + auto mesh_v = + CartesianMeshBuilder(TinyVector<3>{0, 0, 0}, TinyVector<3>{1, 1, 2}, TinyVector<3, uint64_t>(3, 4, 5)) + .mesh(); + const auto& mesh = *mesh_v->get<Mesh<3>>(); + + REQUIRE( + is_valid.template + operator()<ItemType::node>(mesh.connectivity(), + StencilManager::instance() + .getCellToCellStencilArray(mesh.connectivity(), + StencilDescriptor{2, StencilDescriptor:: + ConnectionType::by_nodes}), + 2)); + + REQUIRE( + is_valid.template + operator()<ItemType::edge>(mesh.connectivity(), + StencilManager::instance() + .getCellToCellStencilArray(mesh.connectivity(), + StencilDescriptor{2, StencilDescriptor:: + ConnectionType::by_edges}), + 2)); + + REQUIRE( + is_valid.template + operator()<ItemType::face>(mesh.connectivity(), + StencilManager::instance() + .getCellToCellStencilArray(mesh.connectivity(), + StencilDescriptor{2, StencilDescriptor:: + ConnectionType::by_faces}), + 2)); + } + } + } + } + + SECTION("Stencil using symmetries") + { + auto check_ghost_cells_have_empty_stencils = [](const auto& stencil_array, const auto& connecticity) { + bool is_empty = true; + + auto cell_is_owned = connecticity.cellIsOwned(); + + for (CellId cell_id = 0; cell_id < connecticity.numberOfCells(); ++cell_id) { + if (not cell_is_owned[cell_id]) { + is_empty &= (stencil_array[cell_id].size() == 0); + for (size_t i_symmetry_stencil = 0; + i_symmetry_stencil < stencil_array.symmetryBoundaryStencilArrayList().size(); ++i_symmetry_stencil) { + is_empty &= + (stencil_array.symmetryBoundaryStencilArrayList()[i_symmetry_stencil].stencilArray()[cell_id].size() == + 0); + } + } + } + + return is_empty; + }; + + auto are_symmetry_stencils_valid = []<ItemType connecting_item_type>(const auto& stencil_array, const auto& mesh, + const size_t number_of_layers) { + bool are_valid_symmetries = true; + + auto connecting_to_cell_matrix = + mesh.connectivity().template getItemToItemMatrix<connecting_item_type, ItemType::cell>(); + auto cell_to_connecting_item_matrix = + mesh.connectivity().template getItemToItemMatrix<ItemType::cell, connecting_item_type>(); + auto cell_is_owned = mesh.connectivity().cellIsOwned(); + auto cell_number = mesh.connectivity().cellNumber(); + + auto connecting_number = mesh.connectivity().template number<connecting_item_type>(); + + using ConnectingItemId = ItemIdT<connecting_item_type>; + using MeshType = std::decay_t<decltype(mesh)>; + + for (size_t i_symmetry_stencil = 0; i_symmetry_stencil < stencil_array.symmetryBoundaryStencilArrayList().size(); + ++i_symmetry_stencil) { + const IBoundaryDescriptor& boundary_descriptor = + stencil_array.symmetryBoundaryStencilArrayList()[i_symmetry_stencil].boundaryDescriptor(); + + auto boundary_stencil = stencil_array.symmetryBoundaryStencilArrayList()[i_symmetry_stencil].stencilArray(); + auto boundary_face_list = getMeshFaceBoundary(mesh, boundary_descriptor); + + std::set<ConnectingItemId> sym_connecting_item_set; + + if constexpr (ItemTypeId<MeshType::Dimension>::itemTId(connecting_item_type) == + ItemTypeId<MeshType::Dimension>::itemTId(ItemType::face)) { + for (size_t i_face = 0; i_face < boundary_face_list.faceList().size(); ++i_face) { + const FaceId face_id = boundary_face_list.faceList()[i_face]; + sym_connecting_item_set.insert(ConnectingItemId{FaceId::base_type{face_id}}); + } + + } else { + auto face_to_connecting_item_matrix = + mesh.connectivity().template getItemToItemMatrix<ItemType::face, connecting_item_type>(); + + for (size_t i_face = 0; i_face < boundary_face_list.faceList().size(); ++i_face) { + const FaceId face_id = boundary_face_list.faceList()[i_face]; + auto connecting_item_list = face_to_connecting_item_matrix[face_id]; + for (size_t i_connecting_item = 0; i_connecting_item < connecting_item_list.size(); ++i_connecting_item) { + sym_connecting_item_set.insert(connecting_item_list[i_connecting_item]); + } + } + } + + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + if (not cell_is_owned[cell_id]) { + are_valid_symmetries &= (boundary_stencil[cell_id].size() == 0); + } else { + std::vector<CellId> expected_stencil; + + std::set<CellId> marked_cell_set; + marked_cell_set.insert(cell_id); + + std::set<ConnectingItemId> marked_connecting_item_set; + std::set<ConnectingItemId> layer_connecting_item_set; + { + auto cell_to_connecting_item_list = cell_to_connecting_item_matrix[cell_id]; + for (size_t i_connecting_item = 0; i_connecting_item < cell_to_connecting_item_list.size(); + ++i_connecting_item) { + const ConnectingItemId connecting_item_id = cell_to_connecting_item_list[i_connecting_item]; + layer_connecting_item_set.insert(connecting_item_id); + marked_connecting_item_set.insert(connecting_item_id); + } + } + + std::set<ConnectingItemId> marked_sym_connecting_item_set; + std::set<ConnectingItemId> layer_sym_connecting_item_set; + + for (auto&& connecting_item_id : marked_connecting_item_set) { + if (sym_connecting_item_set.contains(connecting_item_id)) { + marked_sym_connecting_item_set.insert(connecting_item_id); + layer_sym_connecting_item_set.insert(connecting_item_id); + } + } + + std::set<CellId> marked_sym_cell_set; + + for (size_t i_layer = 0; i_layer < number_of_layers; ++i_layer) { + std::set<CellId, std::function<bool(CellId, CellId)>> cell_set( + [=](CellId cell_0, CellId cell_1) { return cell_number[cell_0] < cell_number[cell_1]; }); + + for (auto&& connecting_item_id : layer_connecting_item_set) { + auto connecting_item_to_cell_list = connecting_to_cell_matrix[connecting_item_id]; + for (size_t i_connecting_item_of_cell = 0; + i_connecting_item_of_cell < connecting_item_to_cell_list.size(); ++i_connecting_item_of_cell) { + const CellId connecting_item_id_of_cell = connecting_item_to_cell_list[i_connecting_item_of_cell]; + if (not marked_cell_set.contains(connecting_item_id_of_cell)) { + cell_set.insert(connecting_item_id_of_cell); + marked_cell_set.insert(connecting_item_id_of_cell); + } + } + } + + std::set<CellId, std::function<bool(CellId, CellId)>> sym_cell_set( + [=](CellId cell_0, CellId cell_1) { return cell_number[cell_0] < cell_number[cell_1]; }); + + for (auto&& connecting_item_id : layer_sym_connecting_item_set) { + auto connecting_item_to_cell_list = connecting_to_cell_matrix[connecting_item_id]; + for (size_t i_connecting_item_of_cell = 0; + i_connecting_item_of_cell < connecting_item_to_cell_list.size(); ++i_connecting_item_of_cell) { + const CellId connecting_item_id_of_cell = connecting_item_to_cell_list[i_connecting_item_of_cell]; + if (not marked_sym_cell_set.contains(connecting_item_id_of_cell)) { + sym_cell_set.insert(connecting_item_id_of_cell); + marked_sym_cell_set.insert(connecting_item_id_of_cell); + } + } + } + + for (auto&& set_sym_cell_id : sym_cell_set) { + expected_stencil.push_back(set_sym_cell_id); + } + + layer_connecting_item_set.clear(); + for (auto&& layer_cell_id : cell_set) { + auto cell_to_connecting_item_list = cell_to_connecting_item_matrix[layer_cell_id]; + for (size_t i_connecting_item = 0; i_connecting_item < cell_to_connecting_item_list.size(); + ++i_connecting_item) { + const ConnectingItemId connecting_item_id = cell_to_connecting_item_list[i_connecting_item]; + if (not marked_connecting_item_set.contains(connecting_item_id)) { + layer_connecting_item_set.insert(connecting_item_id); + marked_connecting_item_set.insert(connecting_item_id); + } + } + } + + layer_sym_connecting_item_set.clear(); + + for (auto&& connecting_item_id : layer_connecting_item_set) { + if (sym_connecting_item_set.contains(connecting_item_id)) { + if (not marked_sym_connecting_item_set.contains(connecting_item_id)) { + marked_sym_connecting_item_set.insert(connecting_item_id); + layer_sym_connecting_item_set.insert(connecting_item_id); + } + } + } + + for (auto layer_sym_cell_id : sym_cell_set) { + auto cell_to_connecting_item_list = cell_to_connecting_item_matrix[layer_sym_cell_id]; + for (size_t i_connecting_item = 0; i_connecting_item < cell_to_connecting_item_list.size(); + ++i_connecting_item) { + const ConnectingItemId connecting_item_id = cell_to_connecting_item_list[i_connecting_item]; + if (not marked_sym_connecting_item_set.contains(connecting_item_id)) { + marked_sym_connecting_item_set.insert(connecting_item_id); + layer_sym_connecting_item_set.insert(connecting_item_id); + } + } + } + } + + auto cell_stencil = boundary_stencil[cell_id]; + + if (cell_stencil.size() != expected_stencil.size()) { + are_valid_symmetries = false; + } + + auto i_set_cell = expected_stencil.begin(); + for (size_t index = 0; index < cell_stencil.size(); ++index, ++i_set_cell) { + if (*i_set_cell != cell_stencil[index]) { + are_valid_symmetries = false; + } + } + } + } + } + + return are_valid_symmetries; + }; + + SECTION("1 layer") + { + SECTION("1D") + { + StencilManager::BoundaryDescriptorList list; + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMIN")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMAX")); + + SECTION("cartesian") + { + const auto& mesh = *MeshDataBaseForTests::get().cartesian1DMesh()->get<Mesh<1>>(); + + const Connectivity<1>& connectivity = mesh.connectivity(); + + { + auto stencil_array = + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2); + REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity)); + REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::node>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 1)); + } + + { + auto stencil_array = + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_edges}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2); + REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity)); + REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::edge>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 1)); + } + + { + auto stencil_array = + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_faces}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2); + REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity)); + REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::face>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 1)); + } + } + + SECTION("unordered") + { + const auto& mesh = *MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>(); + + const Connectivity<1>& connectivity = mesh.connectivity(); + + { + auto stencil_array = + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2); + REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity)); + REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::node>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 1)); + } + + { + auto stencil_array = + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_edges}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2); + REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity)); + REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::edge>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 1)); + } + + { + auto stencil_array = + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_faces}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2); + REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity)); + REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::face>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 1)); + } + } + } + + SECTION("2D") + { + StencilManager::BoundaryDescriptorList list; + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMIN")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMAX")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMIN")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMAX")); + + SECTION("cartesian") + { + const auto& mesh = *MeshDataBaseForTests::get().cartesian2DMesh()->get<Mesh<2>>(); + + const Connectivity<2>& connectivity = mesh.connectivity(); + + { + auto stencil_array = + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4); + REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity)); + REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::node>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 1)); + } + + { + auto stencil_array = + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_edges}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4); + REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity)); + REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::edge>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::edge>(connectivity, stencil_array, 1)); + } + + { + auto stencil_array = + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_faces}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4); + REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity)); + REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::face>(stencil_array, mesh, 1)); + REQUIRE(not(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 1))); + REQUIRE(is_valid.template operator()<ItemType::face>(connectivity, stencil_array, 1)); + } + } + + SECTION("hybrid") + { + const auto& mesh = *MeshDataBaseForTests::get().hybrid2DMesh()->get<Mesh<2>>(); + + const Connectivity<2>& connectivity = mesh.connectivity(); + + { + auto stencil_array = + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4); + REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity)); + REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::node>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 1)); + } + + { + auto stencil_array = + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_edges}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4); + REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity)); + REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::edge>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::edge>(connectivity, stencil_array, 1)); + } + + { + auto stencil_array = + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_faces}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4); + REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity)); + REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::face>(stencil_array, mesh, 1)); + REQUIRE(not(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 1))); + REQUIRE(is_valid.template operator()<ItemType::face>(connectivity, stencil_array, 1)); + } + } + } + + SECTION("3D") + { + StencilManager::BoundaryDescriptorList list; + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMIN")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMAX")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMIN")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMAX")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("ZMIN")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("ZMAX")); + + SECTION("cartesian") + { + const auto& mesh = *MeshDataBaseForTests::get().cartesian3DMesh()->get<Mesh<3>>(); + + const Connectivity<3>& connectivity = mesh.connectivity(); + { + auto stencil_array = + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6); + REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity)); + REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::node>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 1)); + } + + { + auto stencil_array = + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_edges}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6); + REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity)); + REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::edge>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::edge>(connectivity, stencil_array, 1)); + } + + { + auto stencil_array = + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_faces}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6); + REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity)); + REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::face>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::face>(connectivity, stencil_array, 1)); + } + } + + SECTION("hybrid") + { + const auto& mesh = *MeshDataBaseForTests::get().hybrid3DMesh()->get<Mesh<3>>(); + + const Connectivity<3>& connectivity = mesh.connectivity(); + { + auto stencil_array = + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6); + REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity)); + REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::node>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 1)); + } + + { + auto stencil_array = + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_edges}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6); + REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity)); + REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::edge>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::edge>(connectivity, stencil_array, 1)); + } + + { + auto stencil_array = + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_faces}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6); + REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity)); + REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::face>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::face>(connectivity, stencil_array, 1)); + } + } + } + } + + SECTION("2 layers") + { + NbGhostLayersTester nb_ghost_layers_tester(2); + + SECTION("1D") + { + StencilManager::BoundaryDescriptorList list; + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMIN")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMAX")); + + SECTION("cartesian") + { + auto mesh_v = CartesianMeshBuilder(TinyVector<1>{0}, TinyVector<1>{1}, TinyVector<1, uint64_t>(20)).mesh(); + const auto& mesh = *mesh_v->get<Mesh<1>>(); + + const Connectivity<1>& connectivity = mesh.connectivity(); + + { + auto stencil_array = + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{2, StencilDescriptor::ConnectionType::by_nodes}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2); + REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity)); + REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::node>(stencil_array, mesh, 2)); + REQUIRE(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 2)); + } + + { + auto stencil_array = + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{2, StencilDescriptor::ConnectionType::by_edges}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2); + REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity)); + REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::edge>(stencil_array, mesh, 2)); + REQUIRE(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 2)); + } + + { + auto stencil_array = + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{2, StencilDescriptor::ConnectionType::by_faces}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2); + REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity)); + REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::face>(stencil_array, mesh, 2)); + REQUIRE(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 2)); + } + } + } + + SECTION("2D") + { + StencilManager::BoundaryDescriptorList list; + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMIN")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMAX")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMIN")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMAX")); + + SECTION("cartesian") + { + auto mesh_v = + CartesianMeshBuilder(TinyVector<2>{0, 0}, TinyVector<2>{1, 2}, TinyVector<2, uint64_t>(5, 7)).mesh(); + const auto& mesh = *mesh_v->get<Mesh<2>>(); + + const Connectivity<2>& connectivity = mesh.connectivity(); + { + auto stencil_array = + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{2, StencilDescriptor::ConnectionType::by_nodes}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4); + REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity)); + REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::node>(stencil_array, mesh, 2)); + REQUIRE(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 2)); + } + + { + auto stencil_array = + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{2, StencilDescriptor::ConnectionType::by_edges}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4); + REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity)); + REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::edge>(stencil_array, mesh, 2)); + REQUIRE(is_valid.template operator()<ItemType::edge>(connectivity, stencil_array, 2)); + } + + { + auto stencil_array = + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{2, StencilDescriptor::ConnectionType::by_faces}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4); + REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity)); + REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::face>(stencil_array, mesh, 2)); + REQUIRE(not(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 2))); + REQUIRE(is_valid.template operator()<ItemType::face>(connectivity, stencil_array, 2)); + } + } + } + + SECTION("3D") + { + StencilManager::BoundaryDescriptorList list; + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMIN")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMAX")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMIN")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMAX")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("ZMIN")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("ZMAX")); + + SECTION("cartesian") + { + auto mesh_v = + CartesianMeshBuilder(TinyVector<3>{0, 0, 0}, TinyVector<3>{1, 1, 2}, TinyVector<3, uint64_t>(3, 4, 5)) + .mesh(); + const auto& mesh = *mesh_v->get<Mesh<3>>(); + + const Connectivity<3>& connectivity = mesh.connectivity(); + { + auto stencil_array = + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{2, StencilDescriptor::ConnectionType::by_nodes}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6); + REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity)); + REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::node>(stencil_array, mesh, 2)); + REQUIRE(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 2)); + } + + { + auto stencil_array = + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{2, StencilDescriptor::ConnectionType::by_edges}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6); + REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity)); + REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::edge>(stencil_array, mesh, 2)); + REQUIRE(is_valid.template operator()<ItemType::edge>(connectivity, stencil_array, 2)); + } + + { + auto stencil_array = + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{2, StencilDescriptor::ConnectionType::by_faces}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6); + REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity)); + REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::face>(stencil_array, mesh, 2)); + REQUIRE(is_valid.template operator()<ItemType::face>(connectivity, stencil_array, 2)); + } + } + } + } + } +} diff --git a/tests/test_StencilBuilder_node2cell.cpp b/tests/test_StencilBuilder_node2cell.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6d9c429c6d4ba903185c90a1e6c37d63bf9c9feb --- /dev/null +++ b/tests/test_StencilBuilder_node2cell.cpp @@ -0,0 +1,1184 @@ +#include <catch2/catch_test_macros.hpp> +#include <catch2/matchers/catch_matchers_all.hpp> + +#include <MeshDataBaseForTests.hpp> +#include <mesh/CartesianMeshBuilder.hpp> +#include <mesh/Connectivity.hpp> +#include <mesh/ConnectivityUtils.hpp> +#include <mesh/ItemValue.hpp> +#include <mesh/ItemValueUtils.hpp> +#include <mesh/Mesh.hpp> +#include <mesh/MeshFaceBoundary.hpp> +#include <mesh/MeshVariant.hpp> +#include <mesh/NamedBoundaryDescriptor.hpp> +#include <mesh/StencilManager.hpp> +#include <utils/Messenger.hpp> + +#include <NbGhostLayersTester.hpp> + +// clazy:excludeall=non-pod-global-static + +TEST_CASE("StencilBuilder node2cell", "[mesh]") +{ + auto is_valid = []<ItemType source_item_type, ItemType connecting_item_type>(const auto& connectivity, + const auto& stencil_array, + const size_t number_of_layers) { + constexpr size_t Dimension = std::decay_t<decltype(connectivity)>::Dimension; + + auto cell_to_connecting_item_matrix = + connectivity.template getItemToItemMatrix<ItemType::cell, connecting_item_type>(); + + auto source_item_to_connecting_item_matrix = + [](const auto& given_connectivity) -> ItemToItemMatrix<source_item_type, connecting_item_type> { + if constexpr (ItemTypeId<Dimension>::itemTId(source_item_type) == + ItemTypeId<Dimension>::itemTId(connecting_item_type)) { + return ConnectivityMatrix{}; + } else { + return given_connectivity.template getItemToItemMatrix<source_item_type, connecting_item_type>(); + } + }(connectivity); + + auto connecting_to_cell_matrix = connectivity.template getItemToItemMatrix<connecting_item_type, ItemType::cell>(); + auto cell_is_owned = connectivity.cellIsOwned(); + auto cell_number = connectivity.cellNumber(); + auto source_item_is_owned = connectivity.template isOwned<source_item_type>(); + + using SourceItemId = ItemIdT<source_item_type>; + using ConnectingItemId = ItemIdT<connecting_item_type>; + + for (SourceItemId source_item_id = 0; source_item_id < connectivity.template numberOf<source_item_type>(); + ++source_item_id) { + if (source_item_is_owned[source_item_id]) { + std::vector<CellId> expected_stencil; + + std::set<CellId> marked_cell_set; + if constexpr (source_item_type == ItemType::cell) { + marked_cell_set.insert(source_item_id); + } + + std::set<ConnectingItemId> marked_connecting_item_set; + std::set<ConnectingItemId> layer_connecting_item_set; + { + if constexpr (ItemTypeId<Dimension>::itemTId(source_item_type) == + ItemTypeId<Dimension>::itemTId(connecting_item_type)) { + ConnectingItemId connecting_id = + ConnectingItemId{static_cast<typename SourceItemId::base_type>(source_item_id)}; + layer_connecting_item_set.insert(connecting_id); + marked_connecting_item_set.insert(connecting_id); + } else { + auto source_item_to_connecting_item_list = source_item_to_connecting_item_matrix[source_item_id]; + for (size_t i_connecting_item = 0; i_connecting_item < source_item_to_connecting_item_list.size(); + ++i_connecting_item) { + const ConnectingItemId connecting_item_id = source_item_to_connecting_item_list[i_connecting_item]; + layer_connecting_item_set.insert(connecting_item_id); + marked_connecting_item_set.insert(connecting_item_id); + } + } + } + + for (size_t i_layer = 0; i_layer < number_of_layers; ++i_layer) { + std::set<CellId, std::function<bool(CellId, CellId)>> cell_set( + [=](CellId cell_0, CellId cell_1) { return cell_number[cell_0] < cell_number[cell_1]; }); + + for (auto&& connecting_item_id : layer_connecting_item_set) { + auto connecting_item_to_cell_list = connecting_to_cell_matrix[connecting_item_id]; + for (size_t i_connecting_item_of_cell = 0; i_connecting_item_of_cell < connecting_item_to_cell_list.size(); + ++i_connecting_item_of_cell) { + const CellId connecting_item_id_of_cell = connecting_item_to_cell_list[i_connecting_item_of_cell]; + if (not marked_cell_set.contains(connecting_item_id_of_cell)) { + cell_set.insert(connecting_item_id_of_cell); + marked_cell_set.insert(connecting_item_id_of_cell); + } + } + } + + layer_connecting_item_set.clear(); + for (auto layer_cell_id : cell_set) { + auto cell_to_connecting_item_list = cell_to_connecting_item_matrix[layer_cell_id]; + for (size_t i_connecting_item = 0; i_connecting_item < cell_to_connecting_item_list.size(); + ++i_connecting_item) { + const ConnectingItemId connecting_item_id = cell_to_connecting_item_list[i_connecting_item]; + if (not marked_connecting_item_set.contains(connecting_item_id)) { + layer_connecting_item_set.insert(connecting_item_id); + marked_connecting_item_set.insert(connecting_item_id); + } + } + } + + for (auto&& set_cell_id : cell_set) { + expected_stencil.push_back(set_cell_id); + } + } + + auto cell_stencil = stencil_array[source_item_id]; + + auto i_set_cell = expected_stencil.begin(); + if (cell_stencil.size() != expected_stencil.size()) { + return false; + } + + for (size_t index = 0; index < cell_stencil.size(); ++index, ++i_set_cell) { + if (*i_set_cell != cell_stencil[index]) { + return false; + } + } + } + } + return true; + }; + + SECTION("inner stencil") + { + SECTION("1 layer") + { + SECTION("1D") + { + SECTION("cartesian") + { + const auto& mesh = *MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>(); + + const Connectivity<1>& connectivity = mesh.connectivity(); + + REQUIRE( + is_valid.template + operator()<ItemType::node, + ItemType::node>(connectivity, + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_nodes}), + 1)); + + REQUIRE( + is_valid.template + operator()<ItemType::node, + ItemType::edge>(connectivity, + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_edges}), + 1)); + + REQUIRE( + is_valid.template + operator()<ItemType::node, + ItemType::face>(connectivity, + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_faces}), + 1)); + } + + SECTION("unordered") + { + const auto& mesh = *MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>(); + + const Connectivity<1>& connectivity = mesh.connectivity(); + + REQUIRE( + is_valid.template + operator()<ItemType::node, + ItemType::node>(connectivity, + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_nodes}), + 1)); + + REQUIRE( + is_valid.template + operator()<ItemType::node, + ItemType::edge>(connectivity, + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_edges}), + 1)); + + REQUIRE( + is_valid.template + operator()<ItemType::node, + ItemType::face>(connectivity, + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_faces}), + 1)); + } + } + + SECTION("2D") + { + SECTION("cartesian") + { + const auto& mesh = *MeshDataBaseForTests::get().cartesian2DMesh()->get<Mesh<2>>(); + + const Connectivity<2>& connectivity = mesh.connectivity(); + REQUIRE( + is_valid.template + operator()<ItemType::node, + ItemType::node>(connectivity, + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_nodes}), + 1)); + + REQUIRE( + is_valid.template + operator()<ItemType::node, + ItemType::edge>(connectivity, + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_edges}), + 1)); + + REQUIRE( + is_valid.template + operator()<ItemType::node, + ItemType::face>(connectivity, + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_faces}), + 1)); + } + + SECTION("hybrid") + { + const auto& mesh = *MeshDataBaseForTests::get().hybrid2DMesh()->get<Mesh<2>>(); + + const Connectivity<2>& connectivity = mesh.connectivity(); + REQUIRE( + is_valid.template + operator()<ItemType::node, + ItemType::node>(connectivity, + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_nodes}), + 1)); + + REQUIRE( + is_valid.template + operator()<ItemType::node, + ItemType::edge>(connectivity, + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_edges}), + 1)); + + REQUIRE( + is_valid.template + operator()<ItemType::node, + ItemType::face>(connectivity, + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_faces}), + 1)); + } + } + + SECTION("3D") + { + SECTION("carteian") + { + const auto& mesh = *MeshDataBaseForTests::get().cartesian3DMesh()->get<Mesh<3>>(); + + const Connectivity<3>& connectivity = mesh.connectivity(); + REQUIRE( + is_valid.template + operator()<ItemType::node, + ItemType::node>(connectivity, + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_nodes}), + 1)); + + REQUIRE( + is_valid.template + operator()<ItemType::node, + ItemType::edge>(connectivity, + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_edges}), + 1)); + + REQUIRE( + is_valid.template + operator()<ItemType::node, + ItemType::face>(connectivity, + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_faces}), + 1)); + } + + SECTION("hybrid") + { + const auto& mesh = *MeshDataBaseForTests::get().hybrid3DMesh()->get<Mesh<3>>(); + + const Connectivity<3>& connectivity = mesh.connectivity(); + REQUIRE( + is_valid.template + operator()<ItemType::node, + ItemType::node>(connectivity, + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_nodes}), + 1)); + + REQUIRE( + is_valid.template + operator()<ItemType::node, + ItemType::edge>(connectivity, + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_edges}), + 1)); + + REQUIRE( + is_valid.template + operator()<ItemType::node, + ItemType::face>(connectivity, + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor:: + ConnectionType::by_faces}), + 1)); + } + } + } + + SECTION("2 layers") + { + NbGhostLayersTester nb_ghost_layers_tester(2); + + SECTION("1D") + { + SECTION("cartesian") + { + auto mesh_v = CartesianMeshBuilder(TinyVector<1>{0}, TinyVector<1>{1}, TinyVector<1, uint64_t>(20)).mesh(); + + const auto& mesh = *mesh_v->get<Mesh<1>>(); + + REQUIRE( + is_valid.template + operator()<ItemType::node, + ItemType::node>(mesh.connectivity(), + StencilManager::instance() + .getNodeToCellStencilArray(mesh.connectivity(), + StencilDescriptor{2, StencilDescriptor:: + ConnectionType::by_nodes}), + 2)); + + REQUIRE( + is_valid.template + operator()<ItemType::node, + ItemType::edge>(mesh.connectivity(), + StencilManager::instance() + .getNodeToCellStencilArray(mesh.connectivity(), + StencilDescriptor{2, StencilDescriptor:: + ConnectionType::by_edges}), + 2)); + + REQUIRE( + is_valid.template + operator()<ItemType::node, + ItemType::face>(mesh.connectivity(), + StencilManager::instance() + .getNodeToCellStencilArray(mesh.connectivity(), + StencilDescriptor{2, StencilDescriptor:: + ConnectionType::by_faces}), + 2)); + } + } + + SECTION("2D") + { + SECTION("cartesian") + { + auto mesh_v = + CartesianMeshBuilder(TinyVector<2>{0, 0}, TinyVector<2>{1, 2}, TinyVector<2, uint64_t>(5, 7)).mesh(); + const auto& mesh = *mesh_v->get<Mesh<2>>(); + + REQUIRE( + is_valid.template + operator()<ItemType::node, + ItemType::node>(mesh.connectivity(), + StencilManager::instance() + .getNodeToCellStencilArray(mesh.connectivity(), + StencilDescriptor{2, StencilDescriptor:: + ConnectionType::by_nodes}), + 2)); + + REQUIRE( + is_valid.template + operator()<ItemType::node, + ItemType::edge>(mesh.connectivity(), + StencilManager::instance() + .getNodeToCellStencilArray(mesh.connectivity(), + StencilDescriptor{2, StencilDescriptor:: + ConnectionType::by_edges}), + 2)); + + REQUIRE( + is_valid.template + operator()<ItemType::node, + ItemType::face>(mesh.connectivity(), + StencilManager::instance() + .getNodeToCellStencilArray(mesh.connectivity(), + StencilDescriptor{2, StencilDescriptor:: + ConnectionType::by_faces}), + 2)); + } + } + + SECTION("3D") + { + SECTION("carteian") + { + auto mesh_v = + CartesianMeshBuilder(TinyVector<3>{0, 0, 0}, TinyVector<3>{1, 1, 2}, TinyVector<3, uint64_t>(3, 4, 5)) + .mesh(); + const auto& mesh = *mesh_v->get<Mesh<3>>(); + + REQUIRE( + is_valid.template + operator()<ItemType::node, + ItemType::node>(mesh.connectivity(), + StencilManager::instance() + .getNodeToCellStencilArray(mesh.connectivity(), + StencilDescriptor{2, StencilDescriptor:: + ConnectionType::by_nodes}), + 2)); + + REQUIRE( + is_valid.template + operator()<ItemType::node, + ItemType::edge>(mesh.connectivity(), + StencilManager::instance() + .getNodeToCellStencilArray(mesh.connectivity(), + StencilDescriptor{2, StencilDescriptor:: + ConnectionType::by_edges}), + 2)); + + REQUIRE( + is_valid.template + operator()<ItemType::node, + ItemType::face>(mesh.connectivity(), + StencilManager::instance() + .getNodeToCellStencilArray(mesh.connectivity(), + StencilDescriptor{2, StencilDescriptor:: + ConnectionType::by_faces}), + 2)); + } + } + } + } + + SECTION("Stencil using symmetries") + { + auto check_ghost_nodes_have_empty_stencils = [](const auto& stencil_array, const auto& connecticity) { + bool is_empty = true; + + auto node_is_owned = connecticity.nodeIsOwned(); + + for (NodeId node_id = 0; node_id < connecticity.numberOfNodes(); ++node_id) { + if (not node_is_owned[node_id]) { + is_empty &= (stencil_array[node_id].size() == 0); + for (size_t i_symmetry_stencil = 0; + i_symmetry_stencil < stencil_array.symmetryBoundaryStencilArrayList().size(); ++i_symmetry_stencil) { + is_empty &= + (stencil_array.symmetryBoundaryStencilArrayList()[i_symmetry_stencil].stencilArray()[node_id].size() == + 0); + } + } + } + + return is_empty; + }; + + auto are_symmetry_stencils_valid = + []<ItemType source_item_type, ItemType connecting_item_type>(const auto& stencil_array, const auto& mesh, + const size_t number_of_layers) { + bool are_valid_symmetries = true; + + constexpr const size_t Dimension = std::decay_t<decltype(mesh)>::Dimension; + + auto connecting_to_cell_matrix = + mesh.connectivity().template getItemToItemMatrix<connecting_item_type, ItemType::cell>(); + auto cell_to_connecting_item_matrix = + mesh.connectivity().template getItemToItemMatrix<ItemType::cell, connecting_item_type>(); + // auto source_item_is_owned = mesh.connectivity().cellIsOwned(); + auto cell_number = mesh.connectivity().cellNumber(); + auto source_item_is_owned = mesh.connectivity().template isOwned<source_item_type>(); + + auto connecting_number = mesh.connectivity().template number<connecting_item_type>(); + + auto source_item_to_connecting_item_matrix = + [](const auto& given_connectivity) -> ItemToItemMatrix<source_item_type, connecting_item_type> { + if constexpr (ItemTypeId<Dimension>::itemTId(source_item_type) == + ItemTypeId<Dimension>::itemTId(connecting_item_type)) { + return ConnectivityMatrix{}; + } else { + return given_connectivity.template getItemToItemMatrix<source_item_type, connecting_item_type>(); + } + }(mesh.connectivity()); + + using SourceItemId = ItemIdT<source_item_type>; + using ConnectingItemId = ItemIdT<connecting_item_type>; + using MeshType = std::decay_t<decltype(mesh)>; + + for (size_t i_symmetry_stencil = 0; + i_symmetry_stencil < stencil_array.symmetryBoundaryStencilArrayList().size(); ++i_symmetry_stencil) { + const IBoundaryDescriptor& boundary_descriptor = + stencil_array.symmetryBoundaryStencilArrayList()[i_symmetry_stencil].boundaryDescriptor(); + + auto boundary_stencil = stencil_array.symmetryBoundaryStencilArrayList()[i_symmetry_stencil].stencilArray(); + auto boundary_face_list = getMeshFaceBoundary(mesh, boundary_descriptor); + + std::set<ConnectingItemId> sym_connecting_item_set; + + if constexpr (ItemTypeId<MeshType::Dimension>::itemTId(connecting_item_type) == + ItemTypeId<MeshType::Dimension>::itemTId(ItemType::face)) { + for (size_t i_face = 0; i_face < boundary_face_list.faceList().size(); ++i_face) { + const FaceId face_id = boundary_face_list.faceList()[i_face]; + sym_connecting_item_set.insert(ConnectingItemId{FaceId::base_type{face_id}}); + } + + } else { + auto face_to_connecting_item_matrix = + mesh.connectivity().template getItemToItemMatrix<ItemType::face, connecting_item_type>(); + + for (size_t i_face = 0; i_face < boundary_face_list.faceList().size(); ++i_face) { + const FaceId face_id = boundary_face_list.faceList()[i_face]; + auto connecting_item_list = face_to_connecting_item_matrix[face_id]; + for (size_t i_connecting_item = 0; i_connecting_item < connecting_item_list.size(); ++i_connecting_item) { + sym_connecting_item_set.insert(connecting_item_list[i_connecting_item]); + } + } + } + + for (SourceItemId source_item_id = 0; source_item_id < mesh.template numberOf<source_item_type>(); + ++source_item_id) { + if (not source_item_is_owned[source_item_id]) { + are_valid_symmetries &= (boundary_stencil[source_item_id].size() == 0); + } else { + std::vector<CellId> expected_stencil; + + std::set<CellId> marked_cell_set; + if constexpr (source_item_type == ItemType::cell) { + marked_cell_set.insert(source_item_id); + } + + std::set<ConnectingItemId> marked_connecting_item_set; + std::set<ConnectingItemId> layer_connecting_item_set; + if constexpr (ItemTypeId<Dimension>::itemTId(source_item_type) == + ItemTypeId<Dimension>::itemTId(connecting_item_type)) { + const ConnectingItemId connecting_item_id = + static_cast<typename SourceItemId::base_type>(source_item_id); + layer_connecting_item_set.insert(connecting_item_id); + marked_connecting_item_set.insert(connecting_item_id); + } else { + auto source_item_to_connecting_item_list = source_item_to_connecting_item_matrix[source_item_id]; + for (size_t i_connecting_item = 0; i_connecting_item < source_item_to_connecting_item_list.size(); + ++i_connecting_item) { + const ConnectingItemId connecting_item_id = source_item_to_connecting_item_list[i_connecting_item]; + layer_connecting_item_set.insert(connecting_item_id); + marked_connecting_item_set.insert(connecting_item_id); + } + } + + std::set<ConnectingItemId> marked_sym_connecting_item_set; + std::set<ConnectingItemId> layer_sym_connecting_item_set; + + for (auto&& connecting_item_id : marked_connecting_item_set) { + if (sym_connecting_item_set.contains(connecting_item_id)) { + marked_sym_connecting_item_set.insert(connecting_item_id); + layer_sym_connecting_item_set.insert(connecting_item_id); + } + } + + std::set<CellId> marked_sym_cell_set; + + for (size_t i_layer = 0; i_layer < number_of_layers; ++i_layer) { + std::set<CellId, std::function<bool(CellId, CellId)>> cell_set( + [=](CellId cell_0, CellId cell_1) { return cell_number[cell_0] < cell_number[cell_1]; }); + + for (auto&& connecting_item_id : layer_connecting_item_set) { + auto connecting_item_to_cell_list = connecting_to_cell_matrix[connecting_item_id]; + for (size_t i_connecting_item_of_cell = 0; + i_connecting_item_of_cell < connecting_item_to_cell_list.size(); ++i_connecting_item_of_cell) { + const CellId connecting_item_id_of_cell = connecting_item_to_cell_list[i_connecting_item_of_cell]; + if (not marked_cell_set.contains(connecting_item_id_of_cell)) { + cell_set.insert(connecting_item_id_of_cell); + marked_cell_set.insert(connecting_item_id_of_cell); + } + } + } + + std::set<CellId, std::function<bool(CellId, CellId)>> sym_cell_set( + [=](CellId cell_0, CellId cell_1) { return cell_number[cell_0] < cell_number[cell_1]; }); + + for (auto&& connecting_item_id : layer_sym_connecting_item_set) { + auto connecting_item_to_cell_list = connecting_to_cell_matrix[connecting_item_id]; + for (size_t i_connecting_item_of_cell = 0; + i_connecting_item_of_cell < connecting_item_to_cell_list.size(); ++i_connecting_item_of_cell) { + const CellId connecting_item_id_of_cell = connecting_item_to_cell_list[i_connecting_item_of_cell]; + if (not marked_sym_cell_set.contains(connecting_item_id_of_cell)) { + sym_cell_set.insert(connecting_item_id_of_cell); + marked_sym_cell_set.insert(connecting_item_id_of_cell); + } + } + } + + for (auto&& set_sym_cell_id : sym_cell_set) { + expected_stencil.push_back(set_sym_cell_id); + } + + layer_connecting_item_set.clear(); + for (auto&& layer_cell_id : cell_set) { + auto cell_to_connecting_item_list = cell_to_connecting_item_matrix[layer_cell_id]; + for (size_t i_connecting_item = 0; i_connecting_item < cell_to_connecting_item_list.size(); + ++i_connecting_item) { + const ConnectingItemId connecting_item_id = cell_to_connecting_item_list[i_connecting_item]; + if (not marked_connecting_item_set.contains(connecting_item_id)) { + layer_connecting_item_set.insert(connecting_item_id); + marked_connecting_item_set.insert(connecting_item_id); + } + } + } + + layer_sym_connecting_item_set.clear(); + + for (auto&& connecting_item_id : layer_connecting_item_set) { + if (sym_connecting_item_set.contains(connecting_item_id)) { + if (not marked_sym_connecting_item_set.contains(connecting_item_id)) { + marked_sym_connecting_item_set.insert(connecting_item_id); + layer_sym_connecting_item_set.insert(connecting_item_id); + } + } + } + + for (auto layer_sym_cell_id : sym_cell_set) { + auto cell_to_connecting_item_list = cell_to_connecting_item_matrix[layer_sym_cell_id]; + for (size_t i_connecting_item = 0; i_connecting_item < cell_to_connecting_item_list.size(); + ++i_connecting_item) { + const ConnectingItemId connecting_item_id = cell_to_connecting_item_list[i_connecting_item]; + if (not marked_sym_connecting_item_set.contains(connecting_item_id)) { + marked_sym_connecting_item_set.insert(connecting_item_id); + layer_sym_connecting_item_set.insert(connecting_item_id); + } + } + } + } + + auto cell_stencil = boundary_stencil[source_item_id]; + + if (cell_stencil.size() != expected_stencil.size()) { + are_valid_symmetries = false; + } + + auto i_set_cell = expected_stencil.begin(); + for (size_t index = 0; index < cell_stencil.size(); ++index, ++i_set_cell) { + if (*i_set_cell != cell_stencil[index]) { + are_valid_symmetries = false; + } + } + } + } + } + + return are_valid_symmetries; + }; + + SECTION("1 layer") + { + SECTION("1D") + { + StencilManager::BoundaryDescriptorList list; + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMIN")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMAX")); + + SECTION("cartesian") + { + const auto& mesh = *MeshDataBaseForTests::get().cartesian1DMesh()->get<Mesh<1>>(); + + const Connectivity<1>& connectivity = mesh.connectivity(); + + { + auto stencil_array = + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2); + REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity)); + REQUIRE( + are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::node>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 1)); + } + + { + auto stencil_array = + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_edges}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2); + REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity)); + REQUIRE( + are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::edge>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 1)); + } + + { + auto stencil_array = + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_faces}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2); + REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity)); + REQUIRE( + are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::face>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 1)); + } + } + + SECTION("unordered") + { + const auto& mesh = *MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>(); + + const Connectivity<1>& connectivity = mesh.connectivity(); + + { + auto stencil_array = + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2); + REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity)); + REQUIRE( + are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::node>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 1)); + } + + { + auto stencil_array = + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_edges}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2); + REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity)); + REQUIRE( + are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::edge>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 1)); + } + + { + auto stencil_array = + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_faces}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2); + REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity)); + REQUIRE( + are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::face>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 1)); + } + } + } + + SECTION("2D") + { + StencilManager::BoundaryDescriptorList list; + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMIN")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMAX")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMIN")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMAX")); + + SECTION("cartesian") + { + const auto& mesh = *MeshDataBaseForTests::get().cartesian2DMesh()->get<Mesh<2>>(); + + const Connectivity<2>& connectivity = mesh.connectivity(); + + { + auto stencil_array = + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4); + REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity)); + REQUIRE( + are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::node>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 1)); + } + + { + auto stencil_array = + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_edges}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4); + REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity)); + REQUIRE( + are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::edge>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::node, ItemType::edge>(connectivity, stencil_array, 1)); + } + + { + auto stencil_array = + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_faces}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4); + REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity)); + REQUIRE( + are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::face>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::node, ItemType::face>(connectivity, stencil_array, 1)); + } + } + + SECTION("hybrid") + { + const auto& mesh = *MeshDataBaseForTests::get().hybrid2DMesh()->get<Mesh<2>>(); + + const Connectivity<2>& connectivity = mesh.connectivity(); + + { + auto stencil_array = + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4); + REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity)); + REQUIRE( + are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::node>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 1)); + } + + { + auto stencil_array = + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_edges}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4); + REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity)); + REQUIRE( + are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::edge>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::node, ItemType::edge>(connectivity, stencil_array, 1)); + } + + { + auto stencil_array = + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_faces}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4); + REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity)); + REQUIRE( + are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::face>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::node, ItemType::face>(connectivity, stencil_array, 1)); + } + } + } + + SECTION("3D") + { + StencilManager::BoundaryDescriptorList list; + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMIN")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMAX")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMIN")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMAX")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("ZMIN")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("ZMAX")); + + SECTION("cartesian") + { + const auto& mesh = *MeshDataBaseForTests::get().cartesian3DMesh()->get<Mesh<3>>(); + + const Connectivity<3>& connectivity = mesh.connectivity(); + { + auto stencil_array = + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6); + REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity)); + REQUIRE( + are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::node>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 1)); + } + + { + auto stencil_array = + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_edges}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6); + REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity)); + REQUIRE( + are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::edge>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::node, ItemType::edge>(connectivity, stencil_array, 1)); + } + + { + auto stencil_array = + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_faces}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6); + REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity)); + REQUIRE( + are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::face>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::node, ItemType::face>(connectivity, stencil_array, 1)); + } + } + + SECTION("hybrid") + { + const auto& mesh = *MeshDataBaseForTests::get().hybrid3DMesh()->get<Mesh<3>>(); + + const Connectivity<3>& connectivity = mesh.connectivity(); + { + auto stencil_array = + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6); + REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity)); + REQUIRE( + are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::node>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 1)); + } + + { + auto stencil_array = + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_edges}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6); + REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity)); + REQUIRE( + are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::edge>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::node, ItemType::edge>(connectivity, stencil_array, 1)); + } + + { + auto stencil_array = + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_faces}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6); + REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity)); + REQUIRE( + are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::face>(stencil_array, mesh, 1)); + REQUIRE(is_valid.template operator()<ItemType::node, ItemType::face>(connectivity, stencil_array, 1)); + } + } + } + } + + SECTION("2 layers") + { + NbGhostLayersTester nb_ghost_layers_tester(2); + + SECTION("1D") + { + StencilManager::BoundaryDescriptorList list; + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMIN")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMAX")); + + SECTION("cartesian") + { + auto mesh_v = CartesianMeshBuilder(TinyVector<1>{0}, TinyVector<1>{1}, TinyVector<1, uint64_t>(20)).mesh(); + const auto& mesh = *mesh_v->get<Mesh<1>>(); + + const Connectivity<1>& connectivity = mesh.connectivity(); + + { + auto stencil_array = + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{2, StencilDescriptor::ConnectionType::by_nodes}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2); + REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity)); + REQUIRE( + are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::node>(stencil_array, mesh, 2)); + REQUIRE(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 2)); + } + + { + auto stencil_array = + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{2, StencilDescriptor::ConnectionType::by_edges}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2); + REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity)); + REQUIRE( + are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::edge>(stencil_array, mesh, 2)); + REQUIRE(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 2)); + } + + { + auto stencil_array = + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{2, StencilDescriptor::ConnectionType::by_faces}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2); + REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity)); + REQUIRE( + are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::face>(stencil_array, mesh, 2)); + REQUIRE(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 2)); + } + } + } + + SECTION("2D") + { + StencilManager::BoundaryDescriptorList list; + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMIN")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMAX")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMIN")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMAX")); + + SECTION("cartesian") + { + auto mesh_v = + CartesianMeshBuilder(TinyVector<2>{0, 0}, TinyVector<2>{1, 2}, TinyVector<2, uint64_t>(5, 7)).mesh(); + const auto& mesh = *mesh_v->get<Mesh<2>>(); + + const Connectivity<2>& connectivity = mesh.connectivity(); + { + auto stencil_array = + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{2, StencilDescriptor::ConnectionType::by_nodes}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4); + REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity)); + REQUIRE( + are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::node>(stencil_array, mesh, 2)); + REQUIRE(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 2)); + } + + { + auto stencil_array = + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{2, StencilDescriptor::ConnectionType::by_edges}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4); + REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity)); + REQUIRE( + are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::edge>(stencil_array, mesh, 2)); + REQUIRE(is_valid.template operator()<ItemType::node, ItemType::edge>(connectivity, stencil_array, 2)); + } + + { + auto stencil_array = + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{2, StencilDescriptor::ConnectionType::by_faces}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4); + REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity)); + REQUIRE( + are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::face>(stencil_array, mesh, 2)); + REQUIRE(not(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 2))); + REQUIRE(is_valid.template operator()<ItemType::node, ItemType::face>(connectivity, stencil_array, 2)); + } + } + } + + SECTION("3D") + { + StencilManager::BoundaryDescriptorList list; + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMIN")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMAX")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMIN")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMAX")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("ZMIN")); + list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("ZMAX")); + + SECTION("cartesian") + { + auto mesh_v = + CartesianMeshBuilder(TinyVector<3>{0, 0, 0}, TinyVector<3>{1, 1, 2}, TinyVector<3, uint64_t>(3, 4, 5)) + .mesh(); + const auto& mesh = *mesh_v->get<Mesh<3>>(); + + const Connectivity<3>& connectivity = mesh.connectivity(); + { + auto stencil_array = + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{2, StencilDescriptor::ConnectionType::by_nodes}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6); + REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity)); + REQUIRE( + are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::node>(stencil_array, mesh, 2)); + REQUIRE(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 2)); + } + + { + auto stencil_array = + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{2, StencilDescriptor::ConnectionType::by_edges}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6); + REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity)); + REQUIRE( + are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::edge>(stencil_array, mesh, 2)); + REQUIRE(is_valid.template operator()<ItemType::node, ItemType::edge>(connectivity, stencil_array, 2)); + } + + { + auto stencil_array = + StencilManager::instance() + .getNodeToCellStencilArray(connectivity, + StencilDescriptor{2, StencilDescriptor::ConnectionType::by_faces}, list); + + REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6); + REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity)); + REQUIRE( + are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::face>(stencil_array, mesh, 2)); + REQUIRE(is_valid.template operator()<ItemType::node, ItemType::face>(connectivity, stencil_array, 2)); + } + } + } + } + } +} diff --git a/tests/test_TinyMatrix.cpp b/tests/test_TinyMatrix.cpp index 58a96f3acdf690653a4450f6e927febaf027a385..2eaadd46a7cb0780d3d54fe8e7d06fc4e059a9b3 100644 --- a/tests/test_TinyMatrix.cpp +++ b/tests/test_TinyMatrix.cpp @@ -9,6 +9,7 @@ #include <algebra/TinyMatrix.hpp> +#include <cmath> #include <sstream> // Instantiate to ensure full coverage is performed diff --git a/tests/test_main.cpp b/tests/test_main.cpp index c4fca371ef152cba6e319141f3fac6424cb7d4ef..98c01f7879d00fbf33718ab679d05ef3d7c73fde 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -7,6 +7,7 @@ #include <mesh/DualConnectivityManager.hpp> #include <mesh/DualMeshManager.hpp> #include <mesh/MeshDataManager.hpp> +#include <mesh/StencilManager.hpp> #include <mesh/SynchronizerManager.hpp> #include <utils/GlobalVariableManager.hpp> #include <utils/Messenger.hpp> @@ -67,7 +68,10 @@ main(int argc, char* argv[]) MeshDataManager::create(); DualConnectivityManager::create(); DualMeshManager::create(); + StencilManager::create(); + GlobalVariableManager::create(); + GlobalVariableManager::instance().setNumberOfGhostLayers(1); MeshDataBaseForTests::create(); @@ -80,6 +84,7 @@ main(int argc, char* argv[]) MeshDataBaseForTests::destroy(); GlobalVariableManager::destroy(); + StencilManager::destroy(); DualMeshManager::destroy(); DualConnectivityManager::destroy(); MeshDataManager::destroy();