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..cdf786c5a30970b747b0f540642d5637059198b8 --- /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.size() > 0) { + os << 0 << ':' << NaNHelper(x[0]); + } + for (size_t i = 1; i < x.size(); ++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 b8d3b81b5fad0ac65521a94aa4f94183f4cff333..22ccbf8813c1522900f8dfa06c32e808b01ef9e4 100644 --- a/src/algebra/SmallMatrix.hpp +++ b/src/algebra/SmallMatrix.hpp @@ -28,20 +28,25 @@ 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 - bool isSquare() const noexcept + bool + isSquare() const noexcept { return m_nb_rows == m_nb_columns; } - friend PUGS_INLINE SmallMatrix<std::remove_const_t<DataType>> copy(const SmallMatrix& A) noexcept + friend PUGS_INLINE SmallMatrix<std::remove_const_t<DataType>> + copy(const SmallMatrix& A) noexcept { return SmallMatrix<std::remove_const_t<DataType>>{A.m_nb_rows, A.m_nb_columns, copy(A.m_values)}; } - friend PUGS_INLINE SmallMatrix<std::remove_const_t<DataType>> transpose(const SmallMatrix& A) + friend PUGS_INLINE SmallMatrix<std::remove_const_t<DataType>> + transpose(const SmallMatrix& A) { SmallMatrix<std::remove_const_t<DataType>> A_transpose{A.m_nb_columns, A.m_nb_rows}; for (size_t i = 0; i < A.m_nb_rows; ++i) { @@ -52,14 +57,16 @@ class [[nodiscard]] SmallMatrix // LCOV_EXCL_LINE return A_transpose; } - friend PUGS_INLINE SmallMatrix operator*(const DataType& a, const SmallMatrix& A) + friend PUGS_INLINE SmallMatrix + operator*(const DataType& a, const SmallMatrix& A) { SmallMatrix<std::remove_const_t<DataType>> aA = copy(A); return aA *= a; } template <typename DataType2> - PUGS_INLINE SmallVector<std::remove_const_t<DataType>> operator*(const SmallVector<DataType2>& x) const + PUGS_INLINE SmallVector<std::remove_const_t<DataType>> + operator*(const SmallVector<DataType2>& x) const { static_assert(std::is_same_v<std::remove_const_t<DataType>, std::remove_const_t<DataType2>>, "incompatible data types"); @@ -77,7 +84,8 @@ class [[nodiscard]] SmallMatrix // LCOV_EXCL_LINE } template <typename DataType2> - PUGS_INLINE SmallMatrix<std::remove_const_t<DataType>> operator*(const SmallMatrix<DataType2>& B) const + PUGS_INLINE SmallMatrix<std::remove_const_t<DataType>> + operator*(const SmallMatrix<DataType2>& B) const { static_assert(std::is_same_v<std::remove_const_t<DataType>, std::remove_const_t<DataType2>>, "incompatible data types"); @@ -98,14 +106,16 @@ class [[nodiscard]] SmallMatrix // LCOV_EXCL_LINE } template <typename DataType2> - PUGS_INLINE SmallMatrix& operator/=(const DataType2& a) + PUGS_INLINE SmallMatrix& + operator/=(const DataType2& a) { const auto inv_a = 1. / a; return (*this) *= inv_a; } template <typename DataType2> - PUGS_INLINE SmallMatrix& operator*=(const DataType2& a) + PUGS_INLINE SmallMatrix& + operator*=(const DataType2& a) { parallel_for( m_values.size(), PUGS_LAMBDA(index_type i) { m_values[i] *= a; }); @@ -113,7 +123,8 @@ class [[nodiscard]] SmallMatrix // LCOV_EXCL_LINE } template <typename DataType2> - PUGS_INLINE SmallMatrix& operator-=(const SmallMatrix<DataType2>& B) + PUGS_INLINE SmallMatrix& + operator-=(const SmallMatrix<DataType2>& B) { static_assert(std::is_same_v<std::remove_const_t<DataType>, std::remove_const_t<DataType2>>, "incompatible data types"); @@ -126,7 +137,8 @@ class [[nodiscard]] SmallMatrix // LCOV_EXCL_LINE } template <typename DataType2> - PUGS_INLINE SmallMatrix& operator+=(const SmallMatrix<DataType2>& B) + PUGS_INLINE SmallMatrix& + operator+=(const SmallMatrix<DataType2>& B) { static_assert(std::is_same_v<std::remove_const_t<DataType>, std::remove_const_t<DataType2>>, "incompatible data types"); @@ -139,7 +151,8 @@ class [[nodiscard]] SmallMatrix // LCOV_EXCL_LINE } template <typename DataType2> - PUGS_INLINE SmallMatrix<std::remove_const_t<DataType>> operator+(const SmallMatrix<DataType2>& B) const + PUGS_INLINE SmallMatrix<std::remove_const_t<DataType>> + operator+(const SmallMatrix<DataType2>& B) const { static_assert(std::is_same_v<std::remove_const_t<DataType>, std::remove_const_t<DataType2>>, "incompatible data types"); @@ -155,7 +168,8 @@ class [[nodiscard]] SmallMatrix // LCOV_EXCL_LINE } template <typename DataType2> - PUGS_INLINE SmallMatrix<std::remove_const_t<DataType>> operator-(const SmallMatrix<DataType2>& B) const + PUGS_INLINE SmallMatrix<std::remove_const_t<DataType>> + operator-(const SmallMatrix<DataType2>& B) const { static_assert(std::is_same_v<std::remove_const_t<DataType>, std::remove_const_t<DataType2>>, "incompatible data types"); @@ -171,36 +185,42 @@ class [[nodiscard]] SmallMatrix // LCOV_EXCL_LINE } PUGS_INLINE - DataType& operator()(index_type i, index_type j) const noexcept(NO_ASSERT) + DataType& + operator()(index_type i, index_type j) const noexcept(NO_ASSERT) { Assert(i < m_nb_rows and j < m_nb_columns, "cannot access element: invalid indices"); return m_values[i * m_nb_columns + j]; } PUGS_INLINE - size_t numberOfRows() const noexcept + size_t + numberOfRows() const noexcept { return m_nb_rows; } PUGS_INLINE - size_t numberOfColumns() const noexcept + size_t + numberOfColumns() const noexcept { return m_nb_columns; } - PUGS_INLINE void fill(const DataType& value) noexcept + PUGS_INLINE void + fill(const DataType& value) noexcept { m_values.fill(value); } - PUGS_INLINE SmallMatrix& operator=(ZeroType) noexcept + PUGS_INLINE SmallMatrix& + operator=(ZeroType) noexcept { m_values.fill(0); return *this; } - PUGS_INLINE SmallMatrix& operator=(IdentityType) noexcept(NO_ASSERT) + PUGS_INLINE SmallMatrix& + operator=(IdentityType) noexcept(NO_ASSERT) { Assert(m_nb_rows == m_nb_columns, "identity must be a square matrix"); @@ -211,7 +231,8 @@ class [[nodiscard]] SmallMatrix // LCOV_EXCL_LINE } template <typename DataType2> - PUGS_INLINE SmallMatrix& operator=(const SmallMatrix<DataType2>& A) noexcept + PUGS_INLINE SmallMatrix& + operator=(const SmallMatrix<DataType2>& A) noexcept { // ensures that DataType is the same as source DataType2 static_assert(std::is_same<std::remove_const_t<DataType>, std::remove_const_t<DataType2>>(), @@ -232,7 +253,8 @@ class [[nodiscard]] SmallMatrix // LCOV_EXCL_LINE PUGS_INLINE SmallMatrix& operator=(SmallMatrix&&) = default; - friend std::ostream& operator<<(std::ostream& os, const SmallMatrix& A) + friend std::ostream& + operator<<(std::ostream& os, const SmallMatrix& A) { for (size_t i = 0; i < A.numberOfRows(); ++i) { os << i << '|'; @@ -259,7 +281,7 @@ class [[nodiscard]] SmallMatrix // LCOV_EXCL_LINE SmallMatrix(const SmallMatrix&) = default; - SmallMatrix(SmallMatrix &&) = default; + SmallMatrix(SmallMatrix&&) = default; explicit SmallMatrix(size_t nb_rows, size_t nb_columns, const SmallArray<DataType>& values) : m_nb_rows{nb_rows}, m_nb_columns{nb_columns}, m_values{values} diff --git a/src/algebra/SmallVector.hpp b/src/algebra/SmallVector.hpp index d4a0bf84268bdf1d164cf6216153117adafaab56..bc3686c75e227c738129c532f4ce4683b5953e49 100644 --- a/src/algebra/SmallVector.hpp +++ b/src/algebra/SmallVector.hpp @@ -166,6 +166,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 94590ff656784d018e9ca57991674da773d64568..e2a87058c7315737a06b55ef32af967e2adcf9d6 100644 --- a/src/algebra/TinyMatrix.hpp +++ b/src/algebra/TinyMatrix.hpp @@ -56,16 +56,14 @@ class [[nodiscard]] TinyMatrix } public: - PUGS_INLINE - constexpr bool + [[nodiscard]] PUGS_INLINE constexpr bool isSquare() const noexcept { return M == N; } - PUGS_INLINE - constexpr friend TinyMatrix<N, M, T> - transpose(const TinyMatrix& A) + [[nodiscard]] PUGS_INLINE constexpr friend TinyMatrix<N, M, T> + transpose(const TinyMatrix& A) noexcept { TinyMatrix<N, M, T> tA; for (size_t i = 0; i < M; ++i) { @@ -76,37 +74,32 @@ class [[nodiscard]] TinyMatrix return tA; } - PUGS_INLINE - constexpr size_t - dimension() const + [[nodiscard]] PUGS_INLINE constexpr size_t + dimension() const noexcept { return M * N; } - PUGS_INLINE - constexpr size_t - numberOfValues() const + [[nodiscard]] PUGS_INLINE constexpr size_t + numberOfValues() const noexcept { return this->dimension(); } - PUGS_INLINE - constexpr size_t - numberOfRows() const + [[nodiscard]] PUGS_INLINE constexpr size_t + numberOfRows() const noexcept { return M; } - PUGS_INLINE - constexpr size_t - numberOfColumns() const + [[nodiscard]] PUGS_INLINE constexpr size_t + 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) { @@ -115,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; @@ -125,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; @@ -141,8 +133,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; @@ -158,9 +150,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; @@ -194,9 +185,8 @@ class [[nodiscard]] TinyMatrix return os; } - PUGS_INLINE - constexpr bool - operator==(const TinyMatrix& A) const + [[nodiscard]] PUGS_INLINE constexpr bool + operator==(const TinyMatrix& A) const noexcept { for (size_t i = 0; i < M * N; ++i) { if (m_values[i] != A.m_values[i]) @@ -205,16 +195,26 @@ class [[nodiscard]] TinyMatrix return true; } - PUGS_INLINE - constexpr bool - operator!=(const TinyMatrix& A) const + [[nodiscard]] PUGS_INLINE constexpr bool + operator!=(const TinyMatrix& A) const noexcept { return not this->operator==(A); } - PUGS_INLINE - constexpr TinyMatrix - operator+(const TinyMatrix& A) const + [[nodiscard]] PUGS_INLINE constexpr friend T + dot(const TinyMatrix& A, const TinyMatrix& B) noexcept + { + T sum = A.m_values[0] * B.m_values[0]; + + for (size_t i = 1; i < M * N; ++i) { + sum += A.m_values[i] * B.m_values[i]; + } + + return sum; + } + + [[nodiscard]] PUGS_INLINE constexpr TinyMatrix + operator+(const TinyMatrix& A) const noexcept { TinyMatrix sum; for (size_t i = 0; i < M * N; ++i) { @@ -223,17 +223,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) { @@ -242,9 +240,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]; @@ -254,7 +251,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]; @@ -264,7 +261,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]; @@ -377,8 +374,8 @@ class [[nodiscard]] TinyMatrix }; template <size_t M, size_t N, typename T> -PUGS_INLINE constexpr TinyMatrix<M, N, T> -tensorProduct(const TinyVector<M, T>& x, const TinyVector<N, T>& y) +[[nodiscard]] PUGS_INLINE constexpr TinyMatrix<M, N, T> +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,9 +386,17 @@ tensorProduct(const TinyVector<M, T>& x, const TinyVector<N, T>& y) return A; } +template <size_t M, size_t N, typename T> +[[nodiscard]] PUGS_INLINE constexpr auto +frobeniusNorm(const TinyMatrix<M, N, T>& A) noexcept +{ + static_assert(std::is_arithmetic<T>(), "Cannot compute frobeniusNorm value for non-arithmetic types"); + return std::sqrt(dot(A, A)); +} + template <size_t N, typename T> -PUGS_INLINE constexpr T -det(const TinyMatrix<N, N, T>& A) +[[nodiscard]] PUGS_INLINE constexpr T +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 " @@ -441,24 +446,24 @@ det(const TinyMatrix<N, N, T>& A) } template <typename T> -PUGS_INLINE constexpr T -det(const TinyMatrix<1, 1, T>& A) +[[nodiscard]] PUGS_INLINE constexpr T +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); } template <typename T> -PUGS_INLINE constexpr T -det(const TinyMatrix<2, 2, T>& A) +[[nodiscard]] PUGS_INLINE constexpr T +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); } template <typename T> -PUGS_INLINE constexpr T -det(const TinyMatrix<3, 3, T>& A) +[[nodiscard]] PUGS_INLINE constexpr T +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)) + @@ -466,8 +471,8 @@ det(const TinyMatrix<3, 3, T>& A) } template <size_t M, size_t N, typename T> -PUGS_INLINE constexpr TinyMatrix<M - 1, N - 1, T> -getMinor(const TinyMatrix<M, N, T>& A, size_t I, size_t J) +[[nodiscard]] PUGS_INLINE constexpr TinyMatrix<M - 1, N - 1, T> +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)); @@ -492,8 +497,8 @@ getMinor(const TinyMatrix<M, N, T>& A, size_t I, size_t J) } template <size_t N, typename T> -PUGS_INLINE T -trace(const TinyMatrix<N, N, T>& A) +[[nodiscard]] PUGS_INLINE T +trace(const TinyMatrix<N, N, T>& A) noexcept { static_assert(std::is_arithmetic<T>::value, "trace is not defined for non-arithmetic types"); @@ -505,11 +510,11 @@ trace(const TinyMatrix<N, N, T>& A) } template <size_t N, typename T> -PUGS_INLINE constexpr TinyMatrix<N, N, T> inverse(const TinyMatrix<N, N, T>& A); +[[nodiscard]] PUGS_INLINE constexpr TinyMatrix<N, N, T> inverse(const TinyMatrix<N, N, T>& A); template <typename T> -PUGS_INLINE constexpr TinyMatrix<1, 1, T> -inverse(const TinyMatrix<1, 1, T>& A) +[[nodiscard]] PUGS_INLINE constexpr TinyMatrix<1, 1, T> +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"); @@ -519,8 +524,8 @@ inverse(const TinyMatrix<1, 1, T>& A) } template <size_t N, typename T> -PUGS_INLINE constexpr T -cofactor(const TinyMatrix<N, N, T>& A, size_t i, size_t j) +[[nodiscard]] PUGS_INLINE constexpr T +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; @@ -529,8 +534,8 @@ cofactor(const TinyMatrix<N, N, T>& A, size_t i, size_t j) } template <typename T> -PUGS_INLINE constexpr TinyMatrix<2, 2, T> -inverse(const TinyMatrix<2, 2, T>& A) +[[nodiscard]] PUGS_INLINE constexpr TinyMatrix<2, 2, T> +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"); @@ -543,8 +548,8 @@ inverse(const TinyMatrix<2, 2, T>& A) } template <typename T> -PUGS_INLINE constexpr TinyMatrix<3, 3, T> -inverse(const TinyMatrix<3, 3, T>& A) +[[nodiscard]] PUGS_INLINE constexpr TinyMatrix<3, 3, T> +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 2bc9c79c36ebe2caef6cf63ac6dc653d3a8b0bcb..e6d33fe26c46bdbcef4283dfd318ace9d990c40e 100644 --- a/src/algebra/TinyVector.hpp +++ b/src/algebra/TinyVector.hpp @@ -33,9 +33,8 @@ class [[nodiscard]] TinyVector } public: - PUGS_INLINE - constexpr TinyVector - operator-() const + [[nodiscard]] PUGS_INLINE constexpr TinyVector + operator-() const noexcept { TinyVector opposite; for (size_t i = 0; i < N; ++i) { @@ -45,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]) @@ -61,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) { @@ -78,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; @@ -86,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); @@ -114,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) { @@ -125,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]; @@ -135,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) { @@ -146,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]; @@ -156,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]; @@ -168,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]; @@ -176,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); @@ -249,8 +239,8 @@ class [[nodiscard]] TinyVector }; template <size_t N, typename T> -[[nodiscard]] PUGS_INLINE constexpr 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"); @@ -259,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) { @@ -272,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) { @@ -286,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/analysis/PyramidGaussQuadrature.cpp b/src/analysis/PyramidGaussQuadrature.cpp index 679f3ded5b85764bca43ed062f379d8171cd5c65..e85a74ac1af9ec6893d15f5489cd568891e0ca92 100644 --- a/src/analysis/PyramidGaussQuadrature.cpp +++ b/src/analysis/PyramidGaussQuadrature.cpp @@ -24,6 +24,11 @@ PyramidGaussQuadrature::_buildPointAndWeightLists(const size_t degree) const double w = (4. / 3) * unit_weight; + // gcc's bound checking are messed up due to the use of + // std::array and the following dynamic/general switch +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" + switch (id) { case 1: { Assert(value_list.size() == 1); @@ -96,6 +101,8 @@ PyramidGaussQuadrature::_buildPointAndWeightLists(const size_t degree) } // LCOV_EXCL_STOP } + +#pragma GCC diagnostic pop } }; diff --git a/src/geometry/LineTransformation.hpp b/src/geometry/LineTransformation.hpp index ae0b00a8a1dce7e4b6b6bb13a737ee1114a202f1..c9f7def71f9d342cca580debf09aa8e77316c556 100644 --- a/src/geometry/LineTransformation.hpp +++ b/src/geometry/LineTransformation.hpp @@ -59,6 +59,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/main.cpp b/src/main.cpp index 7d0112c80715b3cd0b69ea91b874527134b6a2ed..917addbe58d9fd90d6a5316315dc888f0a6bb97c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,6 +4,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> @@ -16,6 +17,8 @@ main(int argc, char* argv[]) ExecutionStatManager::create(); ParallelChecker::create(); + GlobalVariableManager::create(); + std::string filename = initialize(argc, argv); SynchronizerManager::create(); @@ -24,14 +27,12 @@ main(int argc, char* argv[]) MeshDataManager::create(); DualConnectivityManager::create(); DualMeshManager::create(); - - GlobalVariableManager::create(); + StencilManager::create(); parser(filename); ExecutionStatManager::printInfo(); - GlobalVariableManager::destroy(); - + StencilManager::destroy(); DualMeshManager::destroy(); DualConnectivityManager::destroy(); MeshDataManager::destroy(); @@ -41,6 +42,8 @@ main(int argc, char* argv[]) finalize(); + GlobalVariableManager::destroy(); + ParallelChecker::destroy(); int return_code = ExecutionStatManager::getInstance().exitCode(); diff --git a/src/mesh/CMakeLists.txt b/src/mesh/CMakeLists.txt index 78b72bcafc640ecc888c0997eeb3ff8f3056db58..b4d70359a5a4a7f8a4d2e352ab702ecc26761dbc 100644 --- a/src/mesh/CMakeLists.txt +++ b/src/mesh/CMakeLists.txt @@ -43,6 +43,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 bd1e0d85b6f94d1d2d00326857853f4da75eacfe..b28237ded4485de15e2b8c17233e95906752ab89 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> @@ -310,6 +311,12 @@ Connectivity<Dimension>::_write(std::ostream& os) const return os; } +template <size_t Dim> +Connectivity<Dim>::~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; @@ -330,6 +337,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 24d9ef656f0cdd0746db0ee2f27881ce5244c8b0..c04bdbdf9d215ec9324d5545dcf2a079d4590c7f 100644 --- a/src/mesh/Connectivity.hpp +++ b/src/mesh/Connectivity.hpp @@ -744,7 +744,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 65326b56bd49c1ce155dbf07fe32da8622c2e66c..e9491ad34c928b5c84e2f090057a46cebfaffba8 100644 --- a/src/mesh/ConnectivityDispatcher.cpp +++ b/src/mesh/ConnectivityDispatcher.cpp @@ -2,6 +2,7 @@ #include <mesh/ItemOfItemType.hpp> #include <utils/CRSGraph.hpp> +#include <utils/GlobalVariableManager.hpp> #include <utils/Partitioner.hpp> #include <iostream> @@ -28,17 +29,17 @@ ConnectivityDispatcher<Dimension>::_buildNewOwner() using ItemId = ItemIdT<item_type>; ItemValue<int, item_type> item_new_owner(m_connectivity); parallel_for( - item_new_owner.numberOfItems(), PUGS_LAMBDA(const ItemId& l) { - const auto& item_to_cell = item_to_cell_matrix[l]; - CellId Jmin = item_to_cell[0]; - - for (size_t j = 1; j < item_to_cell.size(); ++j) { - const CellId J = item_to_cell[j]; - if (cell_number[J] < cell_number[Jmin]) { - Jmin = J; + item_new_owner.numberOfItems(), PUGS_LAMBDA(const ItemId& item_id) { + const auto& item_to_cell = item_to_cell_matrix[item_id]; + CellId min_number_cell_id = item_to_cell[0]; + + for (size_t i_cell = 1; i_cell < item_to_cell.size(); ++i_cell) { + const CellId cell_id = item_to_cell[i_cell]; + if (cell_number[cell_id] < cell_number[min_number_cell_id]) { + min_number_cell_id = cell_id; } } - item_new_owner[l] = cell_new_owner[Jmin]; + item_new_owner[item_id] = cell_new_owner[min_number_cell_id]; }); synchronize(item_new_owner); @@ -72,22 +73,65 @@ ConnectivityDispatcher<Dimension>::_buildItemListToSend() std::vector<std::vector<CellId>> cell_vector_to_send_by_proc(parallel::size()); Array<bool> send_to_rank(parallel::size()); - for (CellId j = 0; j < m_connectivity.numberOfCells(); ++j) { + + 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[j]; - - for (size_t R = 0; R < cell_to_node.size(); ++R) { - const NodeId& r = cell_to_node[R]; - const auto& node_to_cell = node_to_cell_matrix[r]; - for (size_t K = 0; K < node_to_cell.size(); ++K) { - const CellId& k = node_to_cell[K]; - send_to_rank[cell_new_owner[k]] = true; + const auto& cell_to_node = cell_to_node_matrix[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]; + layer_node_id_list.push_back(node_id); + node_tag[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 (size_t k = 0; k < send_to_rank.size(); ++k) { - if (send_to_rank[k]) { - cell_vector_to_send_by_proc[k].push_back(j); + for (auto node_id : tagged_node_id_list) { + node_tag[node_id] = false; + } + tagged_node_id_list.clear(); + + for (size_t i_rank = 0; i_rank < send_to_rank.size(); ++i_rank) { + if (send_to_rank[i_rank]) { + cell_vector_to_send_by_proc[i_rank].push_back(cell_id); } } } @@ -110,11 +154,11 @@ ConnectivityDispatcher<Dimension>::_buildItemListToSend() Array<bool> tag(m_connectivity.template numberOf<item_type>()); tag.fill(false); std::vector<ItemId> item_id_vector; - for (size_t j = 0; j < cell_list_to_send_by_proc[i_rank].size(); ++j) { - const CellId& cell_id = cell_list_to_send_by_proc[i_rank][j]; + for (size_t i_cell = 0; i_cell < cell_list_to_send_by_proc[i_rank].size(); ++i_cell) { + const CellId& cell_id = cell_list_to_send_by_proc[i_rank][i_cell]; const auto& cell_sub_item_list = cell_to_sub_item_matrix[cell_id]; - for (size_t r = 0; r < cell_sub_item_list.size(); ++r) { - const ItemId& item_id = cell_sub_item_list[r]; + for (size_t i_item = 0; i_item < cell_sub_item_list.size(); ++i_item) { + const ItemId& item_id = cell_sub_item_list[i_item]; if (not tag[item_id]) { item_id_vector.push_back(item_id); tag[item_id] = true; @@ -155,9 +199,9 @@ ConnectivityDispatcher<Dimension>::_gatherFrom(const ItemValue<DataType, item_ty gathered_array = Array<std::remove_const_t<DataType>>(this->_dispatchedInfo<item_type>().m_number_to_id_map.size()); for (size_t i_rank = 0; i_rank < parallel::size(); ++i_rank) { Assert(recv_id_correspondance_by_proc[i_rank].size() == recv_item_data_by_proc[i_rank].size()); - for (size_t r = 0; r < recv_id_correspondance_by_proc[i_rank].size(); ++r) { - const auto& item_id = recv_id_correspondance_by_proc[i_rank][r]; - gathered_array[item_id] = recv_item_data_by_proc[i_rank][r]; + for (size_t i_item = 0; i_item < recv_id_correspondance_by_proc[i_rank].size(); ++i_item) { + const auto& item_id = recv_id_correspondance_by_proc[i_rank][i_item]; + gathered_array[item_id] = recv_item_data_by_proc[i_rank][i_item]; } } } @@ -180,11 +224,11 @@ ConnectivityDispatcher<Dimension>::_gatherFrom( for (size_t i_rank = 0; i_rank < parallel::size(); ++i_rank) { std::vector<MutableDataType> data_by_item_vector; - for (size_t j = 0; j < item_list_to_send_by_proc[i_rank].size(); ++j) { - const ItemId& item_id = item_list_to_send_by_proc[i_rank][j]; + for (size_t i_item = 0; i_item < item_list_to_send_by_proc[i_rank].size(); ++i_item) { + const ItemId& item_id = item_list_to_send_by_proc[i_rank][i_item]; const auto& item_data = data_to_gather.itemArray(item_id); - for (size_t l = 0; l < item_data.size(); ++l) { - data_by_item_vector.push_back(item_data[l]); + for (size_t i_data = 0; i_data < item_data.size(); ++i_data) { + data_by_item_vector.push_back(item_data[i_data]); } } data_to_send_by_proc[i_rank] = convert_to_array(data_by_item_vector); @@ -211,13 +255,13 @@ ConnectivityDispatcher<Dimension>::_gatherFrom( gathered_array = Array<std::remove_const_t<DataType>>(recv_array_size); { - size_t l = 0; + size_t i_value = 0; for (size_t i_rank = 0; i_rank < parallel::size(); ++i_rank) { - for (size_t j = 0; j < recv_data_to_gather_by_proc[i_rank].size(); ++j) { - gathered_array[l++] = recv_data_to_gather_by_proc[i_rank][j]; + for (size_t i_rank_value = 0; i_rank_value < recv_data_to_gather_by_proc[i_rank].size(); ++i_rank_value) { + gathered_array[i_value++] = recv_data_to_gather_by_proc[i_rank][i_rank_value]; } } - Assert(gathered_array.size() == l); + Assert(gathered_array.size() == i_value); } } @@ -229,8 +273,8 @@ ConnectivityDispatcher<Dimension>::_buildCellNumberIdMap() auto& cell_number_id_map = this->_dispatchedInfo<ItemType::cell>().m_number_to_id_map; for (size_t i_rank = 0; i_rank < parallel::size(); ++i_rank) { CellId cell_id = 0; - for (size_t i = 0; i < recv_cell_number_by_proc[i_rank].size(); ++i) { - const int cell_number = recv_cell_number_by_proc[i_rank][i]; + for (size_t i_rank_cell = 0; i_rank_cell < recv_cell_number_by_proc[i_rank].size(); ++i_rank_cell) { + const int cell_number = recv_cell_number_by_proc[i_rank][i_rank_cell]; auto [iterator, inserted] = cell_number_id_map.insert(std::make_pair(cell_number, cell_id)); if (inserted) ++cell_id; @@ -252,8 +296,9 @@ ConnectivityDispatcher<Dimension>::_buildSubItemNumberToIdMap() auto& sub_item_number_id_map = this->_dispatchedInfo<ItemOfItemT::sub_item_type>().m_number_to_id_map; for (size_t i_rank = 0; i_rank < parallel::size(); ++i_rank) { int sub_item_id = 0; - for (size_t i = 0; i < cell_sub_item_number_to_recv_by_proc[i_rank].size(); ++i) { - int sub_item_number = cell_sub_item_number_to_recv_by_proc[i_rank][i]; + for (size_t i_rank_sub_item = 0; i_rank_sub_item < cell_sub_item_number_to_recv_by_proc[i_rank].size(); + ++i_rank_sub_item) { + int sub_item_number = cell_sub_item_number_to_recv_by_proc[i_rank][i_rank_sub_item]; auto [iterator, inserted] = sub_item_number_id_map.insert(std::make_pair(sub_item_number, sub_item_id)); if (inserted) sub_item_id++; @@ -273,8 +318,9 @@ ConnectivityDispatcher<Dimension>::_buildNumberOfSubItemPerItemToRecvByProc() using ItemId = ItemIdT<SubItemOfItemT::item_type>; parallel_for( - number_of_sub_item_per_item.numberOfItems(), - PUGS_LAMBDA(const ItemId& j) { number_of_sub_item_per_item[j] = item_to_sub_item_matrix[j].size(); }); + number_of_sub_item_per_item.numberOfItems(), PUGS_LAMBDA(const ItemId& item_id) { + number_of_sub_item_per_item[item_id] = item_to_sub_item_matrix[item_id].size(); + }); this->_dispatchedInfo<SubItemOfItemT>().m_number_of_sub_item_per_item_to_recv_by_proc = this->exchange(number_of_sub_item_per_item); @@ -299,11 +345,11 @@ ConnectivityDispatcher<Dimension>::_buildSubItemNumbersToRecvByProc() const auto& item_list_to_send_by_proc = this->_dispatchedInfo<SubItemOfItemT::item_type>().m_list_to_send_by_proc; std::vector<int> sub_item_numbers_by_item_vector; - for (size_t j = 0; j < item_list_to_send_by_proc[i_rank].size(); ++j) { - const ItemId& item_id = item_list_to_send_by_proc[i_rank][j]; + for (size_t i_rank_item = 0; i_rank_item < item_list_to_send_by_proc[i_rank].size(); ++i_rank_item) { + const ItemId& item_id = item_list_to_send_by_proc[i_rank][i_rank_item]; const auto& sub_item_list = item_to_sub_item_matrix[item_id]; - for (size_t r = 0; r < sub_item_list.size(); ++r) { - const SubItemId& sub_item_id = sub_item_list[r]; + for (size_t i_sub_item = 0; i_sub_item < sub_item_list.size(); ++i_sub_item) { + const SubItemId& sub_item_id = sub_item_list[i_sub_item]; sub_item_numbers_by_item_vector.push_back(sub_item_number[sub_item_id]); } } @@ -351,11 +397,14 @@ ConnectivityDispatcher<Dimension>::_buildItemToSubItemDescriptor() std::vector<std::vector<unsigned int>> item_to_subitem_legacy; size_t number_of_node_by_cell = 0; for (size_t i_rank = 0; i_rank < parallel::size(); ++i_rank) { - int l = 0; - for (size_t i = 0; i < item_list_to_recv_size_by_proc[i_rank]; ++i) { + int i_sub_item = 0; + for (size_t i_rank_item = 0; i_rank_item < item_list_to_recv_size_by_proc[i_rank]; ++i_rank_item) { std::vector<unsigned int> sub_item_vector; - for (int k = 0; k < number_of_sub_item_per_item_to_recv_by_proc[i_rank][i]; ++k) { - const auto& searched_sub_item_id = sub_item_number_id_map.find(recv_item_of_item_numbers_by_proc[i_rank][l++]); + for (int i_rank_sub_item_per_item = 0; + i_rank_sub_item_per_item < number_of_sub_item_per_item_to_recv_by_proc[i_rank][i_rank_item]; + ++i_rank_sub_item_per_item) { + const auto& searched_sub_item_id = + sub_item_number_id_map.find(recv_item_of_item_numbers_by_proc[i_rank][i_sub_item++]); Assert(searched_sub_item_id != sub_item_number_id_map.end()); sub_item_vector.push_back(searched_sub_item_id->second); } @@ -371,14 +420,16 @@ ConnectivityDispatcher<Dimension>::_buildItemToSubItemDescriptor() item_to_subitem_list.fill(10000000); item_to_subitem_row_map[0] = 0; - for (size_t i = 0; i < item_to_subitem_legacy.size(); ++i) { - item_to_subitem_row_map[i + 1] = item_to_subitem_row_map[i] + item_to_subitem_legacy[i].size(); + for (size_t i_rank = 0; i_rank < item_to_subitem_legacy.size(); ++i_rank) { + item_to_subitem_row_map[i_rank + 1] = item_to_subitem_row_map[i_rank] + item_to_subitem_legacy[i_rank].size(); } - size_t l = 0; - for (size_t i = 0; i < item_to_subitem_legacy.size(); ++i) { - const auto& subitem_list = item_to_subitem_legacy[i]; - for (size_t j = 0; j < subitem_list.size(); ++j, ++l) { - item_to_subitem_list[l] = subitem_list[j]; + { + size_t i_sub_item = 0; + for (size_t i_rank = 0; i_rank < item_to_subitem_legacy.size(); ++i_rank) { + const auto& subitem_list = item_to_subitem_legacy[i_rank]; + for (size_t i_rank_sub_item = 0; i_rank_sub_item < subitem_list.size(); ++i_rank_sub_item, ++i_sub_item) { + item_to_subitem_list[i_sub_item] = subitem_list[i_rank_sub_item]; + } } } @@ -401,7 +452,8 @@ ConnectivityDispatcher<Dimension>::_buildRecvItemIdCorrespondanceByProc() Array<int> send_item_number(item_list_to_send_by_proc[i_rank].size()); const Array<const ItemId> send_item_id = item_list_to_send_by_proc[i_rank]; parallel_for( - send_item_number.size(), PUGS_LAMBDA(size_t j) { send_item_number[j] = item_number[send_item_id[j]]; }); + send_item_number.size(), + PUGS_LAMBDA(size_t i_item) { send_item_number[i_item] = item_number[send_item_id[i_item]]; }); send_item_number_by_proc[i_rank] = send_item_number; } @@ -415,11 +467,11 @@ ConnectivityDispatcher<Dimension>::_buildRecvItemIdCorrespondanceByProc() const auto& item_number_to_id_map = this->_dispatchedInfo<item_type>().m_number_to_id_map; for (size_t i_rank = 0; i_rank < item_list_to_recv_size_by_proc.size(); ++i_rank) { Array<ItemId> item_id_correspondance(item_list_to_recv_size_by_proc[i_rank]); - for (size_t l = 0; l < item_list_to_recv_size_by_proc[i_rank]; ++l) { - const int& recv_item_number = recv_item_number_by_proc[i_rank][l]; + for (size_t i_rank_item = 0; i_rank_item < item_list_to_recv_size_by_proc[i_rank]; ++i_rank_item) { + const int& recv_item_number = recv_item_number_by_proc[i_rank][i_rank_item]; const auto& searched_item_id = item_number_to_id_map.find(recv_item_number); Assert(searched_item_id != item_number_to_id_map.end()); - item_id_correspondance[l] = searched_item_id->second; + item_id_correspondance[i_rank_item] = searched_item_id->second; } recv_item_id_correspondance_by_proc[i_rank] = item_id_correspondance; } @@ -572,9 +624,9 @@ ConnectivityDispatcher<Dimension>::_buildItemReferenceList() this->_dispatchedInfo<item_type>().m_recv_id_correspondance_by_proc; std::vector<block_type> item_refs(m_new_descriptor.template itemNumberVector<item_type>().size()); for (size_t i_rank = 0; i_rank < parallel::size(); ++i_rank) { - for (size_t r = 0; r < recv_item_refs_by_proc[i_rank].size(); ++r) { - const ItemId& item_id = recv_item_id_correspondance_by_proc[i_rank][r]; - item_refs[item_id] = recv_item_refs_by_proc[i_rank][r]; + for (size_t i_item = 0; i_item < recv_item_refs_by_proc[i_rank].size(); ++i_item) { + const ItemId& item_id = recv_item_id_correspondance_by_proc[i_rank][i_item]; + item_refs[item_id] = recv_item_refs_by_proc[i_rank][i_item]; } } 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..9571c346bae6e4e345a16b09188dce15ab73b456 --- /dev/null +++ b/src/mesh/StencilArray.hpp @@ -0,0 +1,105 @@ +#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>; + + 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[](CellId cell_id) const + { + return m_stencil_array[cell_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..31db762a1002e3c633afeaabd4f06d6a98dff634 --- /dev/null +++ b/src/mesh/StencilBuilder.cpp @@ -0,0 +1,637 @@ +#include <mesh/StencilBuilder.hpp> + +#include <mesh/Connectivity.hpp> +#include <mesh/ItemArray.hpp> +#include <utils/GlobalVariableManager.hpp> +#include <utils/Messenger.hpp> + +#include <set> + +template <ItemType item_type> +class StencilBuilder::Layer +{ + using ItemId = ItemIdT<item_type>; + std::vector<ItemId> m_item_id_vector; + std::vector<int> m_item_number_vector; + + public: + size_t + size() const + { + Assert(m_item_id_vector.size() == m_item_number_vector.size()); + return m_item_id_vector.size(); + } + + bool + hasItemNumber(const int item_number) const + { + 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 - 1; + } else { + if (end == mid) { + break; + } + end = mid + 1; + } + } + return false; + } + + const std::vector<ItemId>& + itemIdList() const + { + return m_item_id_vector; + } + + void + add(const ItemId item_id, const int item_number) + { + 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() = 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 (ConnectivityType::Dimension > 1) { + 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; + } else { + 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()); + } + } + + return symmetry_connecting_item_list; + } +} + +template <typename ConnectivityType> +Array<const uint32_t> +StencilBuilder::_getRowMap(const ConnectivityType& connectivity) const +{ + auto cell_to_node_matrix = connectivity.cellToNodeMatrix(); + auto node_to_cell_matrix = connectivity.nodeToCellMatrix(); + + auto cell_is_owned = connectivity.cellIsOwned(); + + Array<uint32_t> row_map{connectivity.numberOfCells() + 1}; + row_map[0] = 0; + std::vector<CellId> neighbors; + for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) { + neighbors.resize(0); + // The stencil is not built for ghost cells + if (cell_is_owned[cell_id]) { + auto cell_nodes = cell_to_node_matrix[cell_id]; + for (size_t i_node = 0; i_node < cell_nodes.size(); ++i_node) { + const NodeId node_id = cell_nodes[i_node]; + auto node_cells = node_to_cell_matrix[node_id]; + for (size_t i_node_cell = 0; i_node_cell < node_cells.size(); ++i_node_cell) { + const CellId node_cell_id = node_cells[i_node_cell]; + if (node_cell_id != cell_id) { + neighbors.push_back(node_cells[i_node_cell]); + } + } + } + std::sort(neighbors.begin(), neighbors.end()); + neighbors.erase(std::unique(neighbors.begin(), neighbors.end()), neighbors.end()); + } + // The cell itself is not counted + row_map[cell_id + 1] = row_map[cell_id] + neighbors.size(); + } + + return row_map; +} + +template <typename ConnectivityType> +Array<const uint32_t> +StencilBuilder::_getColumnIndices(const ConnectivityType& connectivity, const Array<const uint32_t>& row_map) const +{ + auto cell_number = connectivity.cellNumber(); + + Array<uint32_t> max_index(row_map.size() - 1); + parallel_for( + max_index.size(), PUGS_LAMBDA(size_t i) { max_index[i] = row_map[i]; }); + + auto cell_to_node_matrix = connectivity.cellToNodeMatrix(); + auto node_to_cell_matrix = connectivity.nodeToCellMatrix(); + + auto cell_is_owned = connectivity.cellIsOwned(); + + Array<uint32_t> column_indices(row_map[row_map.size() - 1]); + column_indices.fill(std::numeric_limits<uint32_t>::max()); + + for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) { + // The stencil is not built for ghost cells + if (cell_is_owned[cell_id]) { + auto cell_nodes = cell_to_node_matrix[cell_id]; + for (size_t i_node = 0; i_node < cell_nodes.size(); ++i_node) { + const NodeId node_id = cell_nodes[i_node]; + auto node_cells = node_to_cell_matrix[node_id]; + for (size_t i_node_cell = 0; i_node_cell < node_cells.size(); ++i_node_cell) { + const CellId node_cell_id = node_cells[i_node_cell]; + if (node_cell_id != cell_id) { + bool found = false; + for (size_t i_index = row_map[cell_id]; i_index < max_index[cell_id]; ++i_index) { + if (column_indices[i_index] == node_cell_id) { + found = true; + break; + } + } + if (not found) { + const auto node_cell_number = cell_number[node_cell_id]; + size_t i_index = row_map[cell_id]; + // search for position for index + while ((i_index < max_index[cell_id])) { + if (node_cell_number > cell_number[CellId(column_indices[i_index])]) { + ++i_index; + } else { + break; + } + } + + for (size_t i_destination = max_index[cell_id]; i_destination > i_index; --i_destination) { + const size_t i_source = i_destination - 1; + + column_indices[i_destination] = column_indices[i_source]; + } + ++max_index[cell_id]; + column_indices[i_index] = node_cell_id; + } + } + } + } + } + } + + return column_indices; +} + +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"); + } + if (number_of_layers > 2) { + throw NotImplementedError("number of layers too large"); + } + 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>; + + if (symmetry_boundary_descriptor_list.size() == 0) { + if (number_of_layers == 1) { + Array<uint32_t> row_map{connectivity.template numberOf<item_type>() + 1}; + row_map[0] = 0; + + std::vector<ItemId> column_indices_vector; + + for (ItemId item_id = 0; item_id < connectivity.template numberOf<item_type>(); ++item_id) { + // First layer is a special case + + Layer<item_type> item_layer; + Layer<connecting_item_type> connecting_layer; + + if (item_is_owned[item_id]) { + for (size_t i_connecting_item_1 = 0; i_connecting_item_1 < item_to_connecting_item_matrix[item_id].size(); + ++i_connecting_item_1) { + const ConnectingItemId layer_1_connecting_item_id = + item_to_connecting_item_matrix[item_id][i_connecting_item_1]; + connecting_layer.add(layer_1_connecting_item_id, connecting_item_number[layer_1_connecting_item_id]); + } + + for (auto connecting_item_id : connecting_layer.itemIdList()) { + for (size_t i_item_1 = 0; i_item_1 < connecting_item_to_item_matrix[connecting_item_id].size(); + ++i_item_1) { + const ItemId layer_1_item_id = connecting_item_to_item_matrix[connecting_item_id][i_item_1]; + if (layer_1_item_id != item_id) { + item_layer.add(layer_1_item_id, item_number[layer_1_item_id]); + } + } + } + } + + for (auto layer_item_id : item_layer.itemIdList()) { + column_indices_vector.push_back(layer_item_id); + } + row_map[item_id + 1] = row_map[item_id] + item_layer.itemIdList().size(); + } + + if (row_map[row_map.size() - 1] != column_indices_vector.size()) { + throw UnexpectedError("incorrect stencil size"); + } + Array<uint32_t> column_indices(row_map[row_map.size() - 1]); + column_indices.fill(std::numeric_limits<uint32_t>::max()); + + for (size_t i = 0; i < column_indices.size(); ++i) { + column_indices[i] = column_indices_vector[i]; + } + + return {ConnectivityMatrix{row_map, column_indices}, {}}; + } else { + Array<uint32_t> row_map{connectivity.template numberOf<item_type>() + 1}; + row_map[0] = 0; + + std::vector<ItemId> column_indices_vector; + + for (ItemId item_id = 0; item_id < connectivity.template numberOf<item_type>(); ++item_id) { + if (item_is_owned[item_id]) { + std::set<ItemId, std::function<bool(ItemId, ItemId)>> item_set( + [=](ItemId item_0, ItemId item_1) { return item_number[item_0] < item_number[item_1]; }); + + for (size_t i_connecting_item_1 = 0; i_connecting_item_1 < item_to_connecting_item_matrix[item_id].size(); + ++i_connecting_item_1) { + const ConnectingItemId layer_1_connecting_item_id = + item_to_connecting_item_matrix[item_id][i_connecting_item_1]; + + for (size_t i_item_1 = 0; i_item_1 < connecting_item_to_item_matrix[layer_1_connecting_item_id].size(); + ++i_item_1) { + ItemId item_1_id = connecting_item_to_item_matrix[layer_1_connecting_item_id][i_item_1]; + + for (size_t i_connecting_item_2 = 0; + i_connecting_item_2 < item_to_connecting_item_matrix[item_1_id].size(); ++i_connecting_item_2) { + const ConnectingItemId layer_2_connecting_item_id = + item_to_connecting_item_matrix[item_1_id][i_connecting_item_2]; + + for (size_t i_item_2 = 0; i_item_2 < connecting_item_to_item_matrix[layer_2_connecting_item_id].size(); + ++i_item_2) { + ItemId item_2_id = connecting_item_to_item_matrix[layer_2_connecting_item_id][i_item_2]; + + if (item_2_id != item_id) { + item_set.insert(item_2_id); + } + } + } + } + } + + for (auto stencil_item_id : item_set) { + column_indices_vector.push_back(stencil_item_id); + } + row_map[item_id + 1] = row_map[item_id] + item_set.size(); + } + } + + if (row_map[row_map.size() - 1] != column_indices_vector.size()) { + throw UnexpectedError("incorrect stencil size"); + } + + Array<uint32_t> column_indices(row_map[row_map.size() - 1]); + column_indices.fill(std::numeric_limits<uint32_t>::max()); + + for (size_t i = 0; i < column_indices.size(); ++i) { + column_indices[i] = column_indices_vector[i]; + } + ConnectivityMatrix primal_stencil{row_map, column_indices}; + + return {primal_stencil, {}}; + } + } else { + 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<uint32_t> column_indices_vector; + std::vector<std::vector<uint32_t>> symmetry_column_indices_vector(symmetry_boundary_descriptor_list.size()); + + for (ItemId item_id = 0; item_id < connectivity.template numberOf<item_type>(); ++item_id) { + std::set<ItemId> item_set; + std::vector<std::set<ItemId>> by_boundary_symmetry_item(symmetry_boundary_descriptor_list.size()); + + if (item_is_owned[item_id]) { + auto item_to_connecting_item_list = item_to_connecting_item_matrix[item_id]; + for (size_t i_connecting_item_of_item = 0; i_connecting_item_of_item < item_to_connecting_item_list.size(); + ++i_connecting_item_of_item) { + const ConnectingItemId connecting_item_id_of_item = item_to_connecting_item_list[i_connecting_item_of_item]; + auto connecting_item_to_item_list = connecting_item_to_item_matrix[connecting_item_id_of_item]; + for (size_t i_item_of_connecting_item = 0; i_item_of_connecting_item < connecting_item_to_item_list.size(); + ++i_item_of_connecting_item) { + const ItemId item_id_of_connecting_item = connecting_item_to_item_list[i_item_of_connecting_item]; + if (item_id != item_id_of_connecting_item) { + item_set.insert(item_id_of_connecting_item); + } + } + } + + { + std::vector<ItemId> item_vector; + for (auto&& set_item_id : item_set) { + item_vector.push_back(set_item_id); + } + std::sort(item_vector.begin(), item_vector.end(), + [&item_number](const ItemId& item0_id, const ItemId& item1_id) { + return item_number[item0_id] < item_number[item1_id]; + }); + + for (auto&& vector_item_id : item_vector) { + column_indices_vector.push_back(vector_item_id); + } + } + + for (size_t i = 0; i < symmetry_boundary_descriptor_list.size(); ++i) { + std::set<ItemId> symmetry_item_set; + for (size_t i_connecting_item_of_item = 0; i_connecting_item_of_item < item_to_connecting_item_list.size(); + ++i_connecting_item_of_item) { + const ConnectingItemId connecting_item_id_of_item = item_to_connecting_item_list[i_connecting_item_of_item]; + if (symmetry_item_list[connecting_item_id_of_item][i]) { + auto item_of_connecting_item_list = connecting_item_to_item_matrix[connecting_item_id_of_item]; + 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_set.insert(item_id_of_connecting_item); + } + } + } + by_boundary_symmetry_item[i] = symmetry_item_set; + + std::vector<ItemId> item_vector; + for (auto&& set_item_id : symmetry_item_set) { + item_vector.push_back(set_item_id); + } + std::sort(item_vector.begin(), item_vector.end(), + [&item_number](const ItemId& item0_id, const ItemId& item1_id) { + return item_number[item0_id] < item_number[item1_id]; + }); + + for (auto&& vector_item_id : item_vector) { + symmetry_column_indices_vector[i].push_back(vector_item_id); + } + } + } + row_map[item_id + 1] = row_map[item_id] + item_set.size(); + + 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] + by_boundary_symmetry_item[i].size(); + } + } + ConnectivityMatrix primal_stencil{row_map, convert_to_array(column_indices_vector)}; + + 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 +{ + static_assert(source_item_type != target_item_type); + throw NotImplementedError("different source target"); +} + +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&, const StencilDescriptor&, const BoundaryDescriptorList&) const +{ + throw NotImplementedError("node to cell stencil"); +} diff --git a/src/mesh/StencilBuilder.hpp b/src/mesh/StencilBuilder.hpp new file mode 100644 index 0000000000000000000000000000000000000000..4f7ead00b618db999dd5386258ac058b664c65a7 --- /dev/null +++ b/src/mesh/StencilBuilder.hpp @@ -0,0 +1,99 @@ +#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 <typename ConnectivityType> + Array<const uint32_t> _getRowMap(const ConnectivityType& connectivity) const; + + template <typename ConnectivityType> + Array<const uint32_t> _getColumnIndices(const ConnectivityType& connectivity, + const Array<const uint32_t>& row_map) const; + + 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..842f8858f9d1a571e28d3f0397f70e97682da0af --- /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/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 bf529a9749bcf9907f6899da37e84b5c86d7b244..e26e157ec3c145d4d53397859a4ff6f4de1e0c3f 100644 --- a/src/scheme/CMakeLists.txt +++ b/src/scheme/CMakeLists.txt @@ -3,7 +3,6 @@ add_library( PugsScheme AcousticSolver.cpp - HyperelasticSolver.cpp DiscreteFunctionIntegrator.cpp DiscreteFunctionInterpoler.cpp DiscreteFunctionUtils.cpp @@ -32,6 +31,8 @@ add_library( EulerKineticSolverAcousticLagrangeFV.cpp EulerKineticSolverAcoustic2LagrangeFVOrder2.cpp FluxingAdvectionSolver.cpp + HyperelasticSolver.cpp + PolynomialReconstruction.cpp ) target_link_libraries( 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..0d683c1783c9aad299a81dcc4d7ef3817470d54a --- /dev/null +++ b/src/scheme/DiscreteFunctionDPkVariant.hpp @@ -0,0 +1,108 @@ +#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<2, const double>, + DiscreteFunctionDPkVector<3, const double>>; + + 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>, + "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..2fefc078b192304ab0a80b63782986867e13c3d8 --- /dev/null +++ b/src/scheme/DiscreteFunctionDPkVector.hpp @@ -0,0 +1,211 @@ +#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_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/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 a2ebb2b256a3957169cf52457fd87a3d3236e8f3..d57cb9add43530ff8bbbaee3ff9e4786a61bb57c 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> @@ -201,6 +202,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(); } 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..2af32e157475b0fe99b9f2cc9cc6c445e05681b3 --- /dev/null +++ b/src/scheme/PolynomialReconstruction.cpp @@ -0,0 +1,1348 @@ +#include <scheme/PolynomialReconstruction.hpp> + +#include <algebra/Givens.hpp> +#include <algebra/ShrinkMatrixView.hpp> +#include <algebra/ShrinkVectorView.hpp> +#include <algebra/SmallMatrix.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/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> + +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_coordinates(const TinyVector<Dimension>& origin, + const TinyVector<Dimension>& normal, + const TinyVector<Dimension>& u) +{ + return u - 2 * dot(u - origin, normal) * normal; +} + +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<2, double>, + DiscreteFunctionDPkVector<3, double>>; + + 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>, + "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; +}; + +template <typename ConformTransformationT> +void +_computeEjkMean(const QuadratureFormula<1>& quadrature, + const ConformTransformationT& T, + const TinyVector<1>& Xj, + const double Vi, + const size_t degree, + const size_t dimension, + SmallArray<double>& inv_Vj_wq_detJ_ek, + SmallArray<double>& mean_of_ejk) +{ + using Rd = TinyVector<1>; + 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 double detT = T.jacobianDeterminant(); + + const Rd X_Xj = T(xi_q) - Xj; + + const double x_xj = X_Xj[0]; + + { + size_t k = 0; + inv_Vj_wq_detJ_ek[k++] = wq * detT / Vi; + for (; k <= degree; ++k) { + inv_Vj_wq_detJ_ek[k] = x_xj * inv_Vj_wq_detJ_ek[k - 1]; + } + } + + for (size_t k = 1; k < dimension; ++k) { + mean_of_ejk[k - 1] += inv_Vj_wq_detJ_ek[k]; + } + } +} + +template <typename ConformTransformationT> +void +_computeEjkMean(const QuadratureFormula<2>& quadrature, + const ConformTransformationT& T, + const TinyVector<2>& Xj, + const double Vi, + const size_t degree, + const size_t dimension, + SmallArray<double>& inv_Vj_wq_detJ_ek, + SmallArray<double>& mean_of_ejk) +{ + using Rd = TinyVector<2>; + + 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 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 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; + inv_Vj_wq_detJ_ek[k++] = wq * detT / Vi; + for (; k <= degree; ++k) { + inv_Vj_wq_detJ_ek[k] = x_xj * inv_Vj_wq_detJ_ek[k - 1]; + } + + for (size_t i_y = 1; i_y <= degree; ++i_y) { + const size_t begin_i_y_1 = ((i_y - 1) * (2 * degree - i_y + 4)) / 2; + for (size_t l = 0; l <= degree - i_y; ++l) { + inv_Vj_wq_detJ_ek[k++] = y_yj * inv_Vj_wq_detJ_ek[begin_i_y_1 + l]; + } + } + } + + for (size_t k = 1; k < dimension; ++k) { + mean_of_ejk[k - 1] += inv_Vj_wq_detJ_ek[k]; + } + } +} + +template <typename ConformTransformationT> +void +_computeEjkMean(const QuadratureFormula<3>& quadrature, + const ConformTransformationT& T, + const TinyVector<3>& Xj, + const double Vi, + const size_t degree, + const size_t dimension, + SmallArray<double>& inv_Vj_wq_detJ_ek, + SmallArray<double>& mean_of_ejk) +{ + using Rd = TinyVector<3>; + 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 double detT = [&] { + if constexpr (std::is_same_v<TetrahedronTransformation, std::decay_t<decltype(T)>>) { + return T.jacobianDeterminant(); + } else { + return T.jacobianDeterminant(xi_q); + } + }(); + + 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; + inv_Vj_wq_detJ_ek[k++] = wq * detT / Vi; + for (; k <= degree; ++k) { + inv_Vj_wq_detJ_ek[k] = x_xj * inv_Vj_wq_detJ_ek[k - 1]; + } + + for (size_t i_y = 1; i_y <= degree; ++i_y) { + const size_t begin_i_y_1 = ((i_y - 1) * (2 * degree - i_y + 4)) / 2; + for (size_t l = 0; l <= degree - i_y; ++l) { + inv_Vj_wq_detJ_ek[k++] = y_yj * inv_Vj_wq_detJ_ek[begin_i_y_1 + l]; + } + } + + for (size_t i_z = 1; i_z <= degree; ++i_z) { + const size_t begin_i_z_1 = ((i_z - 1) * (3 * (degree + 2) * (degree + 3) + (i_z - (3 * degree + 8)) * i_z)) / 6; + for (size_t i_y = 0; i_y <= degree - i_z; ++i_y) { + const size_t begin_i_y_1 = (i_y * (2 * degree - i_y + 3)) / 2 + begin_i_z_1; + for (size_t l = 0; l <= degree - i_y - i_z; ++l) { + inv_Vj_wq_detJ_ek[k++] = z_zj * inv_Vj_wq_detJ_ek[begin_i_y_1 + l]; + } + } + } + } + + for (size_t k = 1; k < dimension; ++k) { + mean_of_ejk[k - 1] += inv_Vj_wq_detJ_ek[k]; + } + } +} + +template <typename NodeListT, size_t Dimension> +void _computeEjkMean(const TinyVector<Dimension>& Xj, + const NodeValue<const TinyVector<Dimension>>& xr, + const NodeListT& node_list, + const CellType& cell_type, + const double Vi, + const size_t degree, + const size_t basis_dimension, + SmallArray<double>& inv_Vj_wq_detJ_ek, + SmallArray<double>& mean_of_ejk); + +template <typename NodeListT> +void +_computeEjkMean(const TinyVector<1>& Xj, + const NodeValue<const TinyVector<1>>& xr, + const NodeListT& node_list, + const CellType& cell_type, + const double Vi, + const size_t degree, + const size_t basis_dimension, + SmallArray<double>& inv_Vj_wq_detJ_ek, + SmallArray<double>& mean_of_ejk) +{ + if (cell_type == CellType::Line) { + const LineTransformation<1> T{xr[node_list[0]], xr[node_list[1]]}; + + const auto& quadrature = QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor{degree}); + + _computeEjkMean(quadrature, T, Xj, Vi, degree, basis_dimension, inv_Vj_wq_detJ_ek, mean_of_ejk); + + } else { + throw NotImplementedError("unexpected cell type: " + std::string{name(cell_type)}); + } +} + +template <typename NodeListT> +void +_computeEjkMeanInSymmetricCell(const TinyVector<1>& origin, + const TinyVector<1>& normal, + const TinyVector<1>& Xj, + const NodeValue<const TinyVector<1>>& xr, + const NodeListT& node_list, + const CellType& cell_type, + const double Vi, + const size_t degree, + const size_t basis_dimension, + SmallArray<double>& inv_Vj_wq_detJ_ek, + SmallArray<double>& mean_of_ejk) +{ + if (cell_type == CellType::Line) { + const auto x0 = symmetrize_coordinates(origin, normal, xr[node_list[1]]); + const auto x1 = symmetrize_coordinates(origin, normal, xr[node_list[0]]); + + const LineTransformation<1> T{x0, x1}; + + const auto& quadrature = QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor{degree}); + + _computeEjkMean(quadrature, T, Xj, Vi, degree, basis_dimension, inv_Vj_wq_detJ_ek, mean_of_ejk); + + } else { + throw NotImplementedError("unexpected cell type: " + std::string{name(cell_type)}); + } +} + +template <typename ConformTransformationT> +void _computeEjkBoundaryMean(const QuadratureFormula<1>& quadrature, + const ConformTransformationT& T, + const TinyVector<2>& Xj, + const double Vi, + const size_t degree, + const size_t dimension, + SmallArray<double>& inv_Vj_alpha_p_1_wq_X_prime_orth_ek, + SmallArray<double>& mean_of_ejk); + +template <> +void +_computeEjkBoundaryMean(const QuadratureFormula<1>& quadrature, + const LineTransformation<2>& T, + const TinyVector<2>& Xj, + const double Vi, + const size_t degree, + const size_t dimension, + SmallArray<double>& inv_Vj_alpha_p_1_wq_X_prime_orth_ek, + SmallArray<double>& mean_of_ejk) +{ + using Rd = TinyVector<2>; + + const double velocity_perp_e1 = T.velocity()[1]; + + 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; + inv_Vj_alpha_p_1_wq_X_prime_orth_ek[k++] = x_xj * wq * velocity_perp_e1 / Vi; + for (; k <= degree; ++k) { + inv_Vj_alpha_p_1_wq_X_prime_orth_ek[k] = + x_xj * inv_Vj_alpha_p_1_wq_X_prime_orth_ek[k - 1] * ((1. * k) / (k + 1)); + } + + for (size_t i_y = 1; i_y <= degree; ++i_y) { + const size_t begin_i_y_1 = ((i_y - 1) * (2 * degree - i_y + 4)) / 2; + for (size_t l = 0; l <= degree - i_y; ++l) { + inv_Vj_alpha_p_1_wq_X_prime_orth_ek[k++] = y_yj * inv_Vj_alpha_p_1_wq_X_prime_orth_ek[begin_i_y_1 + l]; + } + } + } + + for (size_t k = 1; k < dimension; ++k) { + mean_of_ejk[k - 1] += inv_Vj_alpha_p_1_wq_X_prime_orth_ek[k]; + } + } +} + +template <MeshConcept MeshType> +void +_computeEjkMeanByBoundary(const MeshType& mesh, + const TinyVector<2>& Xj, + const CellId& cell_id, + const auto& cell_to_face_matrix, + const auto& face_to_node_matrix, + const auto& cell_face_is_reversed, + const CellValue<const double>& Vi, + const size_t degree, + const size_t basis_dimension, + SmallArray<double>& inv_Vj_alpha_p_1_wq_X_prime_orth_ek, + SmallArray<double>& mean_of_ejk) +{ + const auto& quadrature = QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor(degree + 1)); + + auto xr = mesh.xr(); + mean_of_ejk.fill(0); + auto cell_face_list = cell_to_face_matrix[cell_id]; + for (size_t i_face = 0; i_face < cell_face_list.size(); ++i_face) { + bool is_reversed = cell_face_is_reversed[cell_id][i_face]; + + const FaceId face_id = cell_face_list[i_face]; + auto face_node_list = face_to_node_matrix[face_id]; + if (is_reversed) { + const LineTransformation<2> T{xr[face_node_list[1]], xr[face_node_list[0]]}; + _computeEjkBoundaryMean(quadrature, T, Xj, Vi[cell_id], degree, basis_dimension, + inv_Vj_alpha_p_1_wq_X_prime_orth_ek, mean_of_ejk); + } else { + const LineTransformation<2> T{xr[face_node_list[0]], xr[face_node_list[1]]}; + _computeEjkBoundaryMean(quadrature, T, Xj, Vi[cell_id], degree, basis_dimension, + inv_Vj_alpha_p_1_wq_X_prime_orth_ek, mean_of_ejk); + } + } +} + +void +_computeEjkMeanByBoundaryInSymmetricCell(const TinyVector<2>& origin, + const TinyVector<2>& normal, + const TinyVector<2>& Xj, + const CellId& cell_id, + const NodeValue<const TinyVector<2>>& xr, + const auto& cell_to_face_matrix, + const auto& face_to_node_matrix, + const auto& cell_face_is_reversed, + const CellValue<const double>& Vi, + const size_t degree, + const size_t basis_dimension, + SmallArray<double>& inv_Vj_alpha_p_1_wq_X_prime_orth_ek, + SmallArray<double>& mean_of_ejk) +{ + const auto& quadrature = QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor(degree + 1)); + + mean_of_ejk.fill(0); + auto cell_face_list = cell_to_face_matrix[cell_id]; + for (size_t i_face = 0; i_face < cell_face_list.size(); ++i_face) { + bool is_reversed = cell_face_is_reversed[cell_id][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, xr[face_node_list[1]]); + const auto x1 = symmetrize_coordinates(origin, normal, xr[face_node_list[0]]); + + if (is_reversed) { + const LineTransformation<2> T{x1, x0}; + _computeEjkBoundaryMean(quadrature, T, Xj, Vi[cell_id], degree, basis_dimension, + inv_Vj_alpha_p_1_wq_X_prime_orth_ek, mean_of_ejk); + } else { + const LineTransformation<2> T{x0, x1}; + _computeEjkBoundaryMean(quadrature, T, Xj, Vi[cell_id], degree, basis_dimension, + inv_Vj_alpha_p_1_wq_X_prime_orth_ek, mean_of_ejk); + } + } +} + +template <typename NodeListT> +void +_computeEjkMean(const TinyVector<2>& Xj, + const NodeValue<const TinyVector<2>>& xr, + const NodeListT& node_list, + const CellType& cell_type, + const double Vi, + const size_t degree, + const size_t basis_dimension, + SmallArray<double>& inv_Vj_wq_detJ_ek, + SmallArray<double>& mean_of_ejk) +{ + switch (cell_type) { + case CellType::Triangle: { + const TriangleTransformation<2> T{xr[node_list[0]], xr[node_list[1]], xr[node_list[2]]}; + const auto& quadrature = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor{degree}); + + _computeEjkMean(quadrature, T, Xj, Vi, degree, basis_dimension, inv_Vj_wq_detJ_ek, mean_of_ejk); + break; + } + case CellType::Quadrangle: { + const SquareTransformation<2> T{xr[node_list[0]], xr[node_list[1]], xr[node_list[2]], xr[node_list[3]]}; + const auto& quadrature = + QuadratureManager::instance().getSquareFormula(GaussLegendreQuadratureDescriptor{degree + 1}); + + _computeEjkMean(quadrature, T, Xj, Vi, degree, basis_dimension, inv_Vj_wq_detJ_ek, mean_of_ejk); + break; + } + default: { + throw NotImplementedError("unexpected cell type: " + std::string{name(cell_type)}); + } + } +} + +template <typename NodeListT> +void +_computeEjkMeanInSymmetricCell(const TinyVector<2>& origin, + const TinyVector<2>& normal, + const TinyVector<2>& Xj, + const NodeValue<const TinyVector<2>>& xr, + const NodeListT& node_list, + const CellType& cell_type, + const double Vi, + const size_t degree, + const size_t basis_dimension, + SmallArray<double>& inv_Vj_wq_detJ_ek, + SmallArray<double>& mean_of_ejk) +{ + switch (cell_type) { + case CellType::Triangle: { + const auto x0 = symmetrize_coordinates(origin, normal, xr[node_list[2]]); + const auto x1 = symmetrize_coordinates(origin, normal, xr[node_list[1]]); + const auto x2 = symmetrize_coordinates(origin, normal, xr[node_list[0]]); + + const TriangleTransformation<2> T{x0, x1, x2}; + const auto& quadrature = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor{degree}); + + _computeEjkMean(quadrature, T, Xj, Vi, degree, basis_dimension, inv_Vj_wq_detJ_ek, mean_of_ejk); + break; + } + case CellType::Quadrangle: { + const auto x0 = symmetrize_coordinates(origin, normal, xr[node_list[3]]); + const auto x1 = symmetrize_coordinates(origin, normal, xr[node_list[2]]); + const auto x2 = symmetrize_coordinates(origin, normal, xr[node_list[1]]); + const auto x3 = symmetrize_coordinates(origin, normal, xr[node_list[0]]); + + const SquareTransformation<2> T{x0, x1, x2, x3}; + const auto& quadrature = + QuadratureManager::instance().getSquareFormula(GaussLegendreQuadratureDescriptor{degree + 1}); + + _computeEjkMean(quadrature, T, Xj, Vi, degree, basis_dimension, inv_Vj_wq_detJ_ek, mean_of_ejk); + break; + } + default: { + throw NotImplementedError("unexpected cell type: " + std::string{name(cell_type)}); + } + } +} + +template <typename NodeListT> +void +_computeEjkMean(const TinyVector<3>& Xj, + const NodeValue<const TinyVector<3>>& xr, + const NodeListT& node_list, + const CellType& cell_type, + const double Vi, + const size_t degree, + const size_t basis_dimension, + SmallArray<double>& inv_Vj_wq_detJ_ek, + SmallArray<double>& mean_of_ejk) +{ + if (cell_type == CellType::Tetrahedron) { + const TetrahedronTransformation T{xr[node_list[0]], xr[node_list[1]], xr[node_list[2]], xr[node_list[3]]}; + + const auto& quadrature = QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor{degree}); + + _computeEjkMean(quadrature, T, Xj, Vi, degree, basis_dimension, inv_Vj_wq_detJ_ek, mean_of_ejk); + + } else if (cell_type == CellType::Prism) { + const PrismTransformation T{xr[node_list[0]], xr[node_list[1]], xr[node_list[2]], // + xr[node_list[3]], xr[node_list[4]], xr[node_list[5]]}; + + const auto& quadrature = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor{degree + 1}); + + _computeEjkMean(quadrature, T, Xj, Vi, degree, basis_dimension, inv_Vj_wq_detJ_ek, mean_of_ejk); + + } else if (cell_type == CellType::Pyramid) { + const PyramidTransformation T{xr[node_list[0]], xr[node_list[1]], xr[node_list[2]], xr[node_list[3]], + xr[node_list[4]]}; + + const auto& quadrature = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor{degree + 1}); + + _computeEjkMean(quadrature, T, Xj, Vi, degree, basis_dimension, inv_Vj_wq_detJ_ek, mean_of_ejk); + + } else if (cell_type == CellType::Hexahedron) { + const CubeTransformation T{xr[node_list[0]], xr[node_list[1]], xr[node_list[2]], xr[node_list[3]], + xr[node_list[4]], xr[node_list[5]], xr[node_list[6]], xr[node_list[7]]}; + + const auto& quadrature = + QuadratureManager::instance().getCubeFormula(GaussLegendreQuadratureDescriptor{degree + 1}); + + _computeEjkMean(quadrature, T, Xj, Vi, degree, basis_dimension, inv_Vj_wq_detJ_ek, mean_of_ejk); + + } else { + throw NotImplementedError("unexpected cell type: " + std::string{name(cell_type)}); + } +} + +template <typename NodeListT> +void +_computeEjkMeanInSymmetricCell(const TinyVector<3>& origin, + const TinyVector<3>& normal, + const TinyVector<3>& Xj, + const NodeValue<const TinyVector<3>>& xr, + const NodeListT& node_list, + const CellType& cell_type, + const double Vi, + const size_t degree, + const size_t basis_dimension, + SmallArray<double>& inv_Vj_wq_detJ_ek, + SmallArray<double>& mean_of_ejk) +{ + if (cell_type == CellType::Tetrahedron) { + const auto x0 = symmetrize_coordinates(origin, normal, xr[node_list[1]]); + const auto x1 = symmetrize_coordinates(origin, normal, xr[node_list[0]]); + const auto x2 = symmetrize_coordinates(origin, normal, xr[node_list[2]]); + const auto x3 = symmetrize_coordinates(origin, normal, xr[node_list[3]]); + + const TetrahedronTransformation T{x0, x1, x2, x3}; + + const auto& quadrature = QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor{degree}); + + _computeEjkMean(quadrature, T, Xj, Vi, degree, basis_dimension, inv_Vj_wq_detJ_ek, mean_of_ejk); + + } else if (cell_type == CellType::Prism) { + const auto x0 = symmetrize_coordinates(origin, normal, xr[node_list[1]]); + const auto x1 = symmetrize_coordinates(origin, normal, xr[node_list[0]]); + const auto x2 = symmetrize_coordinates(origin, normal, xr[node_list[2]]); + const auto x3 = symmetrize_coordinates(origin, normal, xr[node_list[4]]); + const auto x4 = symmetrize_coordinates(origin, normal, xr[node_list[3]]); + const auto x5 = symmetrize_coordinates(origin, normal, xr[node_list[5]]); + + const PrismTransformation T{x0, x1, x2, // + x3, x4, x5}; + + const auto& quadrature = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor{degree + 1}); + + _computeEjkMean(quadrature, T, Xj, Vi, degree, basis_dimension, inv_Vj_wq_detJ_ek, mean_of_ejk); + + } else if (cell_type == CellType::Pyramid) { + const auto x0 = symmetrize_coordinates(origin, normal, xr[node_list[3]]); + const auto x1 = symmetrize_coordinates(origin, normal, xr[node_list[2]]); + const auto x2 = symmetrize_coordinates(origin, normal, xr[node_list[1]]); + const auto x3 = symmetrize_coordinates(origin, normal, xr[node_list[0]]); + const auto x4 = symmetrize_coordinates(origin, normal, xr[node_list[4]]); + const PyramidTransformation T{x0, x1, x2, x3, x4}; + + const auto& quadrature = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor{degree + 1}); + + _computeEjkMean(quadrature, T, Xj, Vi, degree, basis_dimension, inv_Vj_wq_detJ_ek, mean_of_ejk); + + } else if (cell_type == CellType::Hexahedron) { + const auto x0 = symmetrize_coordinates(origin, normal, xr[node_list[3]]); + const auto x1 = symmetrize_coordinates(origin, normal, xr[node_list[2]]); + const auto x2 = symmetrize_coordinates(origin, normal, xr[node_list[1]]); + const auto x3 = symmetrize_coordinates(origin, normal, xr[node_list[0]]); + const auto x4 = symmetrize_coordinates(origin, normal, xr[node_list[7]]); + const auto x5 = symmetrize_coordinates(origin, normal, xr[node_list[6]]); + const auto x6 = symmetrize_coordinates(origin, normal, xr[node_list[5]]); + const auto x7 = symmetrize_coordinates(origin, normal, xr[node_list[4]]); + + const CubeTransformation T{x0, x1, x2, x3, x4, x5, x6, x7}; + + const auto& quadrature = + QuadratureManager::instance().getCubeFormula(GaussLegendreQuadratureDescriptor{degree + 1}); + + _computeEjkMean(quadrature, T, Xj, Vi, degree, basis_dimension, inv_Vj_wq_detJ_ek, mean_of_ejk); + + } else { + throw NotImplementedError("unexpected cell type: " + std::string{name(cell_type)}); + } +} + +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_same_v<data_type, double>) { + 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>) { + return discrete_function.size(); + } 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> +[[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 +{ + const MeshType& mesh = *p_mesh; + + using Rd = TinyVector<MeshType::Dimension>; + + 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 cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix(); + auto cell_to_face_matrix = mesh.connectivity().cellToFaceMatrix(); + + 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()); + + SmallArray<SmallArray<double>> inv_Vj_wq_detJ_ek_pool(Kokkos::DefaultExecutionSpace::concurrency()); + SmallArray<SmallArray<double>> mean_j_of_ejk_pool(Kokkos::DefaultExecutionSpace::concurrency()); + SmallArray<SmallArray<double>> mean_i_of_ejk_pool(Kokkos::DefaultExecutionSpace::concurrency()); + + SmallArray<SmallArray<double>> inv_Vj_alpha_p_1_wq_X_prime_orth_ek_pool(Kokkos::DefaultExecutionSpace::concurrency()); + SmallArray<SmallArray<double>> mean_l_of_ejk_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); + + inv_Vj_wq_detJ_ek_pool[i] = SmallArray<double>(basis_dimension); + mean_j_of_ejk_pool[i] = SmallArray<double>(basis_dimension - 1); + mean_i_of_ejk_pool[i] = SmallArray<double>(basis_dimension - 1); + + inv_Vj_alpha_p_1_wq_X_prime_orth_ek_pool[i] = SmallArray<double>(basis_dimension); + } + + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_j_id) { + if (cell_is_owned[cell_j_id]) { + const int32_t t = tokens.acquire(); + + auto stencil_cell_list = stencil_array[cell_j_id]; + + ShrinkMatrixView B(B_pool[t], full_stencil_size(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 k = 0; k < DataType::NumberOfRows; ++k) { + for (size_t l = 0; l < DataType::NumberOfColumns; ++l) { + B(index, column_begin + k * DataType::NumberOfColumns + l) = qi_qj(k, l); + } + } + } + } + + 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 { + const DataType& qi_qj = discrete_function[cell_i_id] - qj; + 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>) { + if constexpr ((DataType::NumberOfColumns == DataType::NumberOfRows) and + (DataType::NumberOfColumns == MeshType::Dimension)) { + throw NotImplementedError("TinyMatrix symmetries for reconstruction"); + } + const DataType& qi_qj = discrete_function[cell_i_id] - qj; + for (size_t k = 0; k < DataType::NumberOfRows; ++k) { + for (size_t l = 0; l < DataType::NumberOfColumns; ++l) { + B(index, column_begin + k * DataType::NumberOfColumns + l) = qi_qj(k, l); + } + } + } + } + } + + 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]; + 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; + } + } + } + column_begin += qj_vector.size(); + } else { + // LCOV_EXCL_START + throw UnexpectedError("invalid discrete function type"); + // LCOV_EXCL_STOP + } + }, + discrete_function_variant->discreteFunction()); + } + + ShrinkMatrixView A(A_pool[t], full_stencil_size(cell_j_id)); + + if ((m_descriptor.degree() == 1) and + (m_descriptor.integrationMethodType() == IntegrationMethodType::cell_center)) { + 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 Rd Xi_Xj = xj[cell_i_id] - Xj; + for (size_t l = 0; l < basis_dimension - 1; ++l) { + A(index, l) = Xi_Xj[l]; + } + } + 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 Rd Xi_Xj = symmetrize_coordinates(origin, normal, xj[cell_i_id]) - Xj; + for (size_t l = 0; l < basis_dimension - 1; ++l) { + A(index, l) = Xi_Xj[l]; + } + } + } + + } else if ((m_descriptor.integrationMethodType() == IntegrationMethodType::element) or + (m_descriptor.integrationMethodType() == IntegrationMethodType::boundary)) { + if ((m_descriptor.integrationMethodType() == IntegrationMethodType::boundary) and + (MeshType::Dimension == 2)) { + if constexpr (MeshType::Dimension == 2) { + SmallArray<double>& inv_Vj_alpha_p_1_wq_X_prime_orth_ek = inv_Vj_alpha_p_1_wq_X_prime_orth_ek_pool[t]; + SmallArray<double>& mean_j_of_ejk = mean_j_of_ejk_pool[t]; + SmallArray<double>& mean_i_of_ejk = mean_i_of_ejk_pool[t]; + + auto face_to_node_matrix = p_mesh->connectivity().faceToNodeMatrix(); + auto cell_face_is_reversed = p_mesh->connectivity().cellFaceIsReversed(); + const Rd& Xj = xj[cell_j_id]; + + _computeEjkMeanByBoundary(*p_mesh, Xj, cell_j_id, cell_to_face_matrix, face_to_node_matrix, + cell_face_is_reversed, Vj, m_descriptor.degree(), basis_dimension, + inv_Vj_alpha_p_1_wq_X_prime_orth_ek, 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(*p_mesh, Xj, cell_i_id, cell_to_face_matrix, face_to_node_matrix, + cell_face_is_reversed, Vj, m_descriptor.degree(), basis_dimension, + inv_Vj_alpha_p_1_wq_X_prime_orth_ek, mean_i_of_ejk); + + for (size_t l = 0; l < basis_dimension - 1; ++l) { + A(index, l) = mean_i_of_ejk[l] - mean_j_of_ejk[l]; + } + } + 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]; + + _computeEjkMeanByBoundaryInSymmetricCell(origin, normal, // + Xj, cell_i_id, xr, cell_to_face_matrix, face_to_node_matrix, + cell_face_is_reversed, Vj, m_descriptor.degree(), + basis_dimension, inv_Vj_alpha_p_1_wq_X_prime_orth_ek, + mean_i_of_ejk); + + for (size_t l = 0; l < basis_dimension - 1; ++l) { + A(index, l) = mean_i_of_ejk[l] - mean_j_of_ejk[l]; + } + } + } + } else { + throw UnexpectedError("invalid mesh dimension"); + } + } else { + SmallArray<double>& inv_Vj_wq_detJ_ek = inv_Vj_wq_detJ_ek_pool[t]; + SmallArray<double>& mean_j_of_ejk = mean_j_of_ejk_pool[t]; + SmallArray<double>& mean_i_of_ejk = mean_i_of_ejk_pool[t]; + + const Rd& Xj = xj[cell_j_id]; + + _computeEjkMean(Xj, xr, cell_to_node_matrix[cell_j_id], cell_type[cell_j_id], Vj[cell_j_id], + m_descriptor.degree(), basis_dimension, inv_Vj_wq_detJ_ek, 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]; + + _computeEjkMean(Xj, xr, cell_to_node_matrix[cell_i_id], cell_type[cell_i_id], Vj[cell_i_id], + m_descriptor.degree(), basis_dimension, inv_Vj_wq_detJ_ek, mean_i_of_ejk); + + for (size_t l = 0; l < basis_dimension - 1; ++l) { + A(index, l) = mean_i_of_ejk[l] - mean_j_of_ejk[l]; + } + } + 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]; + + _computeEjkMeanInSymmetricCell(origin, normal, // + Xj, xr, cell_to_node_matrix[cell_i_id], cell_type[cell_i_id], + Vj[cell_i_id], m_descriptor.degree(), basis_dimension, inv_Vj_wq_detJ_ek, + mean_i_of_ejk); + + for (size_t l = 0; l < basis_dimension - 1; ++l) { + A(index, l) = mean_i_of_ejk[l] - mean_j_of_ejk[l]; + } + } + } + } + } else { + throw UnexpectedError("invalid integration strategy"); + } + + if (m_descriptor.rowWeighting()) { + // Add row weighting (give more importance to cells that are + // closer to j) + 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 < basis_dimension - 1; ++l) { + A(index, l) *= weight; + } + for (size_t l = 0; l < number_of_columns; ++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 < basis_dimension - 1; ++l) { + A(index, l) *= weight; + } + for (size_t l = 0; l < number_of_columns; ++l) { + B(index, l) *= weight; + } + } + } + } + + const SmallMatrix<double>& X = X_pool[t]; + + if (m_descriptor.preconditioning()) { + // Add column weighting preconditioning (increase the presition) + SmallVector<double>& G = G_pool[t]; + + 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; + } + } + } else { + Givens::solveCollectionInPlace(A, X, B); + } + + 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 (m_descriptor.degree() > 1) { + auto& mean_j_of_ejk = mean_j_of_ejk_pool[t]; + for (size_t i = 0; i < basis_dimension - 1; ++i) { + dpk_j[0] -= X(i, column_begin) * mean_j_of_ejk[i]; + } + } + + for (size_t i = 0; i < basis_dimension - 1; ++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 (m_descriptor.degree() > 1) { + auto& mean_j_of_ejk = mean_j_of_ejk_pool[t]; + for (size_t i = 0; i < basis_dimension - 1; ++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 < basis_dimension - 1; ++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 (m_descriptor.degree() > 1) { + auto& mean_j_of_ejk = mean_j_of_ejk_pool[t]; + for (size_t i = 0; i < basis_dimension - 1; ++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 < basis_dimension - 1; ++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 = basis_dimension; + + 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 (m_descriptor.degree() > 1) { + auto& mean_j_of_ejk = mean_j_of_ejk_pool[t]; + for (size_t i = 0; i < basis_dimension - 1; ++i) { + dpk_j[component_begin] -= X(i, column_begin) * mean_j_of_ejk[i]; + } + } + + for (size_t i = 0; i < basis_dimension - 1; ++i) { + auto& dpk_j_ip1 = dpk_j[component_begin + i + 1]; + dpk_j_ip1 = X(i, column_begin); + } + ++column_begin; + } 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()); + } + + 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; +} + +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..b2b855271d1ed4140ebb51d1b21314602caa0dc6 --- /dev/null +++ b/src/scheme/PolynomialReconstruction.hpp @@ -0,0 +1,63 @@ +#ifndef POLYNOMIAL_RECONSTRUCTION_HPP +#define POLYNOMIAL_RECONSTRUCTION_HPP + +#include <mesh/MeshTraits.hpp> +#include <scheme/PolynomialReconstructionDescriptor.hpp> + +class DiscreteFunctionDPkVariant; +class DiscreteFunctionVariant; + +class PolynomialReconstruction +{ + private: + class MutableDiscreteFunctionDPkVariant; + + const PolynomialReconstructionDescriptor m_descriptor; + + size_t _getNumberOfColumns( + 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 <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/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 f720252fdd7c04b1ddd8e234d890bf5b1956c899..8b7e0a339fd91c3569901ba0d179b0713b6ea391 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; @@ -32,6 +40,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 static GlobalVariableManager& instance() diff --git a/src/utils/PugsTraits.hpp b/src/utils/PugsTraits.hpp index 2e1d409578c9295a1ad032466842c2f336ba272f..cb8f154e3b344ae34bb2b769b5a0419e37d44e4a 100644 --- a/src/utils/PugsTraits.hpp +++ b/src/utils/PugsTraits.hpp @@ -26,6 +26,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> @@ -106,6 +112,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> @@ -114,6 +126,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>>; + // Trais is ItemValue template <typename T> @@ -151,6 +169,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 f89f543f92bc3a175e4f055b3286e4a45d985396..e19e6a29a694d4f9971683a99050c0e41f9ea190 100644 --- a/src/utils/PugsUtils.cpp +++ b/src/utils/PugsUtils.cpp @@ -129,6 +129,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]"); @@ -165,6 +169,7 @@ initialize(int& argc, char* argv[]) CommunicatorManager::setSplitColor(mpi_split_color); } + GlobalVariableManager::instance().setNumberOfGhostLayers(nb_ghost_layers); ExecutionStatManager::getInstance().setPrint(print_exec_stat); BacktraceManager::setShow(show_backtrace); ConsoleManager::setShowPreamble(show_preamble); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e7665f8944202ff0c139ca1ccce1403026891f50..feac4de4a40c420233e87f4d636ccee751e8ac78 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -96,6 +96,7 @@ add_executable (unit_tests test_GaussLegendreQuadratureDescriptor.cpp test_GaussLobattoQuadratureDescriptor.cpp test_GaussQuadratureDescriptor.cpp + test_Givens.cpp test_IfProcessor.cpp test_IncDecExpressionProcessor.cpp test_IntegrateCellArray.cpp @@ -127,6 +128,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 @@ -159,6 +161,9 @@ add_executable (unit_tests 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 @@ -202,7 +207,10 @@ add_executable (mpi_unit_tests test_OFStream.cpp test_ParallelChecker_read.cpp test_Partitioner.cpp + test_PolynomialReconstruction.cpp + test_PolynomialReconstructionDescriptor.cpp test_RandomEngine.cpp + test_StencilBuilder.cpp test_SubItemArrayPerItemVariant.cpp test_SubItemValuePerItem.cpp test_SubItemValuePerItemVariant.cpp diff --git a/tests/mpi_test_main.cpp b/tests/mpi_test_main.cpp index ef3fdfcc00569ea3042de6724171fbd24e2ed680..7947364942fff1b8213a63b1972f910f12dfe63d 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> @@ -99,7 +100,10 @@ main(int argc, char* argv[]) MeshDataManager::create(); DualConnectivityManager::create(); DualMeshManager::create(); + StencilManager::create(); + GlobalVariableManager::create(); + GlobalVariableManager::instance().setNumberOfGhostLayers(1); MeshDataBaseForTests::create(); @@ -111,6 +115,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..abe07cc21e21a1b34ae3f20dfe13ff7f52ac83b9 --- /dev/null +++ b/tests/test_ConnectivityDispatcher.cpp @@ -0,0 +1,214 @@ +#include <catch2/catch_test_macros.hpp> +#include <catch2/matchers/catch_matchers_all.hpp> + +#include <mesh/CartesianMeshBuilder.hpp> +#include <mesh/Connectivity.hpp> +#include <mesh/GmshReader.hpp> +#include <mesh/Mesh.hpp> +#include <mesh/MeshVariant.hpp> +#include <utils/Messenger.hpp> + +#include <MeshDataBaseForTests.hpp> + +#include <filesystem> + +// clazy:excludeall=non-pod-global-static + +class NbGhostLayersTester +{ + private: + const size_t m_original_number_of_ghost_layers; + + public: + 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; + } + + ~NbGhostLayersTester() + { + GlobalVariableManager::instance().m_number_of_ghost_layers = m_original_number_of_ghost_layers; + } +}; + +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); + } + } + } + + 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); + } + + 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); + } + + 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); + } + + 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); + } + + 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); + } + + 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); + } + } + + 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_PolynomialReconstruction.cpp b/tests/test_PolynomialReconstruction.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cdfb532f2cd6a30327f09c5f0b1b8e54e6917a29 --- /dev/null +++ b/tests/test_PolynomialReconstruction.cpp @@ -0,0 +1,983 @@ +#include <catch2/catch_approx.hpp> +#include <catch2/catch_test_macros.hpp> +#include <catch2/matchers/catch_matchers_all.hpp> + +#include <Kokkos_Core.hpp> + +#include <utils/PugsAssert.hpp> +#include <utils/Types.hpp> + +#include <algebra/SmallMatrix.hpp> +#include <algebra/SmallVector.hpp> +#include <mesh/Mesh.hpp> +#include <mesh/MeshDataManager.hpp> +#include <scheme/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("PolynomialReconstruction", "[scheme]") +{ + SECTION("degree 1") + { + std::vector<PolynomialReconstructionDescriptor> descriptor_list = { + PolynomialReconstructionDescriptor{IntegrationMethodType::cell_center, 1}, + PolynomialReconstructionDescriptor{IntegrationMethodType::element, 1}, + }; + + 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_affine = [](const R1& x) { return 2.3 + 1.7 * x[0]; }; + auto xj = MeshDataManager::instance().getMeshData(mesh).xj(); + + DiscreteFunctionP0<double> fh{p_mesh}; + + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { fh[cell_id] = R_affine(xj[cell_id]); }); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(fh); + + auto dpk_fh = reconstructions[0]->get<DiscreteFunctionDPk<1, const double>>(); + + { + double max_mean_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + max_mean_error = + std::max(max_mean_error, std::abs(dpk_fh[cell_id](xj[cell_id]) - R_affine(xj[cell_id]))); + } + REQUIRE(parallel::allReduceMax(max_mean_error) == Catch::Approx(0).margin(1E-14)); + } + + { + double max_slope_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const double reconstructed_slope = + (dpk_fh[cell_id](R1{0.1} + xj[cell_id]) - dpk_fh[cell_id](xj[cell_id] - R1{0.1})) / 0.2; + + max_slope_error = std::max(max_slope_error, std::abs(reconstructed_slope - 1.7)); + } + REQUIRE(parallel::allReduceMax(max_slope_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_affine = [](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 xj = MeshDataManager::instance().getMeshData(mesh).xj(); + + DiscreteFunctionP0<R3> uh{p_mesh}; + + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { uh[cell_id] = R3_affine(xj[cell_id]); }); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(uh); + + auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<1, const R3>>(); + + { + double max_mean_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + max_mean_error = + std::max(max_mean_error, l2Norm(dpk_uh[cell_id](xj[cell_id]) - R3_affine(xj[cell_id]))); + } + REQUIRE(parallel::allReduceMax(max_mean_error) == Catch::Approx(0).margin(1E-14)); + } + + { + double max_slope_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const R3 reconstructed_slope = + (1 / 0.2) * (dpk_uh[cell_id](R1{0.1} + xj[cell_id]) - dpk_uh[cell_id](xj[cell_id] - R1{0.1})); + + max_slope_error = std::max(max_slope_error, l2Norm(reconstructed_slope - R3{1.7, -0.6, 3.1})); + } + REQUIRE(parallel::allReduceMax(max_slope_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_affine = [](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], + }; + }; + auto xj = MeshDataManager::instance().getMeshData(mesh).xj(); + + DiscreteFunctionP0<R3x3> Ah{p_mesh}; + + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { Ah[cell_id] = R3x3_affine(xj[cell_id]); }); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Ah); + + auto dpk_Ah = reconstructions[0]->get<DiscreteFunctionDPk<1, const R3x3>>(); + + { + double max_mean_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + max_mean_error = + std::max(max_mean_error, frobeniusNorm(dpk_Ah[cell_id](xj[cell_id]) - R3x3_affine(xj[cell_id]))); + } + REQUIRE(parallel::allReduceMax(max_mean_error) == Catch::Approx(0).margin(1E-14)); + } + + { + double max_slope_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const R3x3 reconstructed_slope = + (1 / 0.2) * (dpk_Ah[cell_id](R1{0.1} + xj[cell_id]) - dpk_Ah[cell_id](xj[cell_id] - R1{0.1})); + + R3x3 slops = R3x3{+1.7, +2.1, -0.6, // + -2.3, +3.1, -3.6, // + +3.1, +2.9, +2.3}; + + max_slope_error = std::max(max_slope_error, // + frobeniusNorm(reconstructed_slope - slops)); + } + REQUIRE(parallel::allReduceMax(max_slope_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 vector_affine = [](const R1& x) -> R3 { + return R3{+2.3 + 1.7 * x[0], -1.7 + 2.1 * x[0], +1.4 - 0.6 * x[0]}; + }; + auto xj = MeshDataManager::instance().getMeshData(mesh).xj(); + + DiscreteFunctionP0Vector<double> Vh{p_mesh, 3}; + + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { + auto vector = vector_affine(xj[cell_id]); + for (size_t i = 0; i < vector.dimension(); ++i) { + Vh[cell_id][i] = vector[i]; + } + }); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh); + + auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<1, const double>>(); + + { + double max_mean_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + auto vector = vector_affine(xj[cell_id]); + for (size_t i = 0; i < vector.dimension(); ++i) { + max_mean_error = std::max(max_mean_error, std::abs(dpk_Vh(cell_id, i)(xj[cell_id]) - vector[i])); + } + } + REQUIRE(parallel::allReduceMax(max_mean_error) == Catch::Approx(0).margin(1E-14)); + } + + { + double max_slope_error = 0; + const TinyVector<3> slope{+1.7, +2.1, -0.6}; + + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + for (size_t i = 0; i < slope.dimension(); ++i) { + const double reconstructed_slope = (1 / 0.2) * (dpk_Vh(cell_id, i)(R1{0.1} + xj[cell_id]) - + dpk_Vh(cell_id, i)(xj[cell_id] - R1{0.1})); + + max_slope_error = std::max(max_slope_error, std::abs(reconstructed_slope - slope[i])); + } + } + REQUIRE(parallel::allReduceMax(max_slope_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_affine = [](const R1& x) { return 2.3 + 1.7 * x[0]; }; + + auto R3_affine = [](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_affine = [](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], + }; + }; + + auto vector_affine = [](const R1& x) -> R3 { + return R3{+2.3 + 1.7 * x[0], -1.7 + 2.1 * x[0], +1.4 - 0.6 * x[0]}; + }; + + auto xj = MeshDataManager::instance().getMeshData(mesh).xj(); + + DiscreteFunctionP0<double> fh{p_mesh}; + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { fh[cell_id] = R_affine(xj[cell_id]); }); + + DiscreteFunctionP0<R3> uh{p_mesh}; + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { uh[cell_id] = R3_affine(xj[cell_id]); }); + + DiscreteFunctionP0<R3x3> Ah{p_mesh}; + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { Ah[cell_id] = R3x3_affine(xj[cell_id]); }); + + DiscreteFunctionP0Vector<double> Vh{p_mesh, 3}; + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { + auto vector = vector_affine(xj[cell_id]); + for (size_t i = 0; i < vector.dimension(); ++i) { + Vh[cell_id][i] = vector[i]; + } + }); + + 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_mean_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + max_mean_error = + std::max(max_mean_error, std::abs(dpk_fh[cell_id](xj[cell_id]) - R_affine(xj[cell_id]))); + } + REQUIRE(parallel::allReduceMax(max_mean_error) == Catch::Approx(0).margin(1E-14)); + } + + { + double max_slope_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const double reconstructed_slope = + (dpk_fh[cell_id](R1{0.1} + xj[cell_id]) - dpk_fh[cell_id](xj[cell_id] - R1{0.1})) / 0.2; + + max_slope_error = std::max(max_slope_error, std::abs(reconstructed_slope - 1.7)); + } + REQUIRE(parallel::allReduceMax(max_slope_error) == Catch::Approx(0).margin(1E-14)); + } + + auto dpk_uh = reconstructions[1]->get<DiscreteFunctionDPk<1, const R3>>(); + + { + double max_mean_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + max_mean_error = + std::max(max_mean_error, l2Norm(dpk_uh[cell_id](xj[cell_id]) - R3_affine(xj[cell_id]))); + } + REQUIRE(parallel::allReduceMax(max_mean_error) == Catch::Approx(0).margin(1E-14)); + } + + { + double max_slope_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const R3 reconstructed_slope = + (1 / 0.2) * (dpk_uh[cell_id](R1{0.1} + xj[cell_id]) - dpk_uh[cell_id](xj[cell_id] - R1{0.1})); + + max_slope_error = std::max(max_slope_error, l2Norm(reconstructed_slope - R3{1.7, -0.6, 3.1})); + } + REQUIRE(parallel::allReduceMax(max_slope_error) == Catch::Approx(0).margin(1E-14)); + } + + auto dpk_Ah = reconstructions[2]->get<DiscreteFunctionDPk<1, const R3x3>>(); + + { + double max_mean_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + max_mean_error = + std::max(max_mean_error, frobeniusNorm(dpk_Ah[cell_id](xj[cell_id]) - R3x3_affine(xj[cell_id]))); + } + REQUIRE(parallel::allReduceMax(max_mean_error) == Catch::Approx(0).margin(1E-14)); + } + + { + double max_slope_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const R3x3 reconstructed_slope = + (1 / 0.2) * (dpk_Ah[cell_id](R1{0.1} + xj[cell_id]) - dpk_Ah[cell_id](xj[cell_id] - R1{0.1})); + + R3x3 slops = R3x3{+1.7, +2.1, -0.6, // + -2.3, +3.1, -3.6, // + +3.1, +2.9, +2.3}; + + max_slope_error = std::max(max_slope_error, // + frobeniusNorm(reconstructed_slope - slops)); + } + REQUIRE(parallel::allReduceMax(max_slope_error) == Catch::Approx(0).margin(1E-13)); + } + + auto dpk_Vh = reconstructions[3]->get<DiscreteFunctionDPkVector<1, const double>>(); + + { + double max_mean_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + auto vector = vector_affine(xj[cell_id]); + for (size_t i = 0; i < vector.dimension(); ++i) { + max_mean_error = std::max(max_mean_error, std::abs(dpk_Vh(cell_id, i)(xj[cell_id]) - vector[i])); + } + } + REQUIRE(parallel::allReduceMax(max_mean_error) == Catch::Approx(0).margin(1E-14)); + } + + { + double max_slope_error = 0; + const TinyVector<3> slope{+1.7, +2.1, -0.6}; + + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + for (size_t i = 0; i < slope.dimension(); ++i) { + const double reconstructed_slope = (1 / 0.2) * (dpk_Vh(cell_id, i)(R1{0.1} + xj[cell_id]) - + dpk_Vh(cell_id, i)(xj[cell_id] - R1{0.1})); + + max_slope_error = std::max(max_slope_error, std::abs(reconstructed_slope - slope[i])); + } + } + REQUIRE(parallel::allReduceMax(max_slope_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_affine = [](const R2& x) { return 2.3 + 1.7 * x[0] - 1.3 * x[1]; }; + auto xj = MeshDataManager::instance().getMeshData(mesh).xj(); + + DiscreteFunctionP0<double> fh{p_mesh}; + + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { fh[cell_id] = R_affine(xj[cell_id]); }); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(fh); + + auto dpk_fh = reconstructions[0]->get<DiscreteFunctionDPk<2, const double>>(); + + { + double max_mean_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + max_mean_error = + std::max(max_mean_error, std::abs(dpk_fh[cell_id](xj[cell_id]) - R_affine(xj[cell_id]))); + } + REQUIRE(parallel::allReduceMax(max_mean_error) == Catch::Approx(0).margin(1E-14)); + } + + { + double max_x_slope_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const double reconstructed_slope = + (dpk_fh[cell_id](R2{0.1, 0} + xj[cell_id]) - dpk_fh[cell_id](xj[cell_id] - R2{0.1, 0})) / 0.2; + + max_x_slope_error = std::max(max_x_slope_error, std::abs(reconstructed_slope - 1.7)); + } + REQUIRE(parallel::allReduceMax(max_x_slope_error) == Catch::Approx(0).margin(1E-13)); + } + + { + double max_y_slope_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const double reconstructed_slope = + (dpk_fh[cell_id](R2{0, 0.1} + xj[cell_id]) - dpk_fh[cell_id](xj[cell_id] - R2{0, 0.1})) / 0.2; + + max_y_slope_error = std::max(max_y_slope_error, std::abs(reconstructed_slope - (-1.3))); + } + REQUIRE(parallel::allReduceMax(max_y_slope_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_affine = [](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]}; + }; + auto xj = MeshDataManager::instance().getMeshData(mesh).xj(); + + DiscreteFunctionP0<R3> uh{p_mesh}; + + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { uh[cell_id] = R3_affine(xj[cell_id]); }); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(uh); + + auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<2, const R3>>(); + + { + double max_mean_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + max_mean_error = + std::max(max_mean_error, l2Norm(dpk_uh[cell_id](xj[cell_id]) - R3_affine(xj[cell_id]))); + } + REQUIRE(parallel::allReduceMax(max_mean_error) == Catch::Approx(0).margin(1E-14)); + } + + { + double max_x_slope_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const R3 reconstructed_slope = (1 / 0.2) * (dpk_uh[cell_id](R2{0.1, 0} + xj[cell_id]) - + dpk_uh[cell_id](xj[cell_id] - R2{0.1, 0})); + + max_x_slope_error = std::max(max_x_slope_error, l2Norm(reconstructed_slope - R3{1.7, -0.6, 3.1})); + } + REQUIRE(parallel::allReduceMax(max_x_slope_error) == Catch::Approx(0).margin(1E-13)); + } + + { + double max_y_slope_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const R3 reconstructed_slope = (1 / 0.2) * (dpk_uh[cell_id](R2{0, 0.1} + xj[cell_id]) - + dpk_uh[cell_id](xj[cell_id] - R2{0, 0.1})); + + max_y_slope_error = std::max(max_y_slope_error, l2Norm(reconstructed_slope - R3{-2.2, 1.3, -1.1})); + } + REQUIRE(parallel::allReduceMax(max_y_slope_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 R2x2_affine = [](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]}; + }; + auto xj = MeshDataManager::instance().getMeshData(mesh).xj(); + + DiscreteFunctionP0<R2x2> Ah{p_mesh}; + + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { Ah[cell_id] = R2x2_affine(xj[cell_id]); }); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Ah); + + auto dpk_Ah = reconstructions[0]->get<DiscreteFunctionDPk<2, const R2x2>>(); + + { + double max_mean_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + max_mean_error = + std::max(max_mean_error, frobeniusNorm(dpk_Ah[cell_id](xj[cell_id]) - R2x2_affine(xj[cell_id]))); + } + REQUIRE(parallel::allReduceMax(max_mean_error) == Catch::Approx(0).margin(1E-14)); + } + + { + double max_x_slope_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const R2x2 reconstructed_slope = (1 / 0.2) * (dpk_Ah[cell_id](R2{0.1, 0} + xj[cell_id]) - + dpk_Ah[cell_id](xj[cell_id] - R2{0.1, 0})); + + max_x_slope_error = + std::max(max_x_slope_error, frobeniusNorm(reconstructed_slope - R2x2{+1.7, +2.1, // + -0.6, -2.3})); + } + REQUIRE(parallel::allReduceMax(max_x_slope_error) == Catch::Approx(0).margin(1E-13)); + } + + { + double max_y_slope_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const R2x2 reconstructed_slope = (1 / 0.2) * (dpk_Ah[cell_id](R2{0, 0.1} + xj[cell_id]) - + dpk_Ah[cell_id](xj[cell_id] - R2{0, 0.1})); + + max_y_slope_error = + std::max(max_y_slope_error, frobeniusNorm(reconstructed_slope - R2x2{+1.2, -2.2, // + -2.1, +1.3})); + } + REQUIRE(parallel::allReduceMax(max_y_slope_error) == Catch::Approx(0).margin(1E-13)); + } + } + } + } + + SECTION("vector data") + { + using R4 = TinyVector<4>; + + 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 vector_affine = [](const R2& x) -> R4 { + return R4{+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]}; + }; + auto xj = MeshDataManager::instance().getMeshData(mesh).xj(); + + DiscreteFunctionP0Vector<double> Vh{p_mesh, 4}; + + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { + auto vector = vector_affine(xj[cell_id]); + for (size_t i = 0; i < vector.dimension(); ++i) { + Vh[cell_id][i] = vector[i]; + } + }); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh); + + auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<2, const double>>(); + + { + double max_mean_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + auto vector = vector_affine(xj[cell_id]); + for (size_t i = 0; i < vector.dimension(); ++i) { + max_mean_error = std::max(max_mean_error, std::abs(dpk_Vh(cell_id, i)(xj[cell_id]) - vector[i])); + } + } + REQUIRE(parallel::allReduceMax(max_mean_error) == Catch::Approx(0).margin(1E-14)); + } + + { + double max_x_slope_error = 0; + const R4 slope{+1.7, +2.1, -0.6, -2.3}; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + for (size_t i = 0; i < slope.dimension(); ++i) { + const double reconstructed_slope = (1 / 0.2) * (dpk_Vh(cell_id, i)(R2{0.1, 0} + xj[cell_id]) - + dpk_Vh(cell_id, i)(xj[cell_id] - R2{0.1, 0})); + + max_x_slope_error = std::max(max_x_slope_error, std::abs(reconstructed_slope - slope[i])); + } + } + REQUIRE(parallel::allReduceMax(max_x_slope_error) == Catch::Approx(0).margin(1E-13)); + } + + { + double max_y_slope_error = 0; + const R4 slope{+1.2, -2.2, -2.1, +1.3}; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + for (size_t i = 0; i < slope.dimension(); ++i) { + const double reconstructed_slope = (1 / 0.2) * (dpk_Vh(cell_id, i)(R2{0, 0.1} + xj[cell_id]) - + dpk_Vh(cell_id, i)(xj[cell_id] - R2{0, 0.1})); + + max_y_slope_error = std::max(max_y_slope_error, std::abs(reconstructed_slope - slope[i])); + } + } + REQUIRE(parallel::allReduceMax(max_y_slope_error) == Catch::Approx(0).margin(1E-13)); + } + } + } + } + } + + 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_affine = [](const R3& x) { return 2.3 + 1.7 * x[0] - 1.3 * x[1] + 2.1 * x[2]; }; + auto xj = MeshDataManager::instance().getMeshData(mesh).xj(); + + DiscreteFunctionP0<double> fh{p_mesh}; + + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { fh[cell_id] = R_affine(xj[cell_id]); }); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(fh); + + auto dpk_fh = reconstructions[0]->get<DiscreteFunctionDPk<3, const double>>(); + + { + double max_mean_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + max_mean_error = + std::max(max_mean_error, std::abs(dpk_fh[cell_id](xj[cell_id]) - R_affine(xj[cell_id]))); + } + REQUIRE(parallel::allReduceMax(max_mean_error) == Catch::Approx(0).margin(1E-14)); + } + + { + double max_x_slope_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const double reconstructed_slope = + (dpk_fh[cell_id](R3{0.1, 0, 0} + xj[cell_id]) - dpk_fh[cell_id](xj[cell_id] - R3{0.1, 0, 0})) / + 0.2; + + max_x_slope_error = std::max(max_x_slope_error, std::abs(reconstructed_slope - 1.7)); + } + REQUIRE(parallel::allReduceMax(max_x_slope_error) == Catch::Approx(0).margin(1E-13)); + } + + { + double max_y_slope_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const double reconstructed_slope = + (dpk_fh[cell_id](R3{0, 0.1, 0} + xj[cell_id]) - dpk_fh[cell_id](xj[cell_id] - R3{0, 0.1, 0})) / + 0.2; + + max_y_slope_error = std::max(max_y_slope_error, std::abs(reconstructed_slope - (-1.3))); + } + REQUIRE(parallel::allReduceMax(max_y_slope_error) == Catch::Approx(0).margin(1E-13)); + } + + { + double max_z_slope_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const double reconstructed_slope = + (dpk_fh[cell_id](R3{0, 0, 0.1} + xj[cell_id]) - dpk_fh[cell_id](xj[cell_id] - R3{0, 0, 0.1})) / + 0.2; + + max_z_slope_error = std::max(max_z_slope_error, std::abs(reconstructed_slope - 2.1)); + } + REQUIRE(parallel::allReduceMax(max_z_slope_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_affine = [](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]}; + }; + auto xj = MeshDataManager::instance().getMeshData(mesh).xj(); + + DiscreteFunctionP0<R3> uh{p_mesh}; + + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { uh[cell_id] = R3_affine(xj[cell_id]); }); + + auto reconstructions = PolynomialReconstruction{descriptor}.build(uh); + + auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<3, const R3>>(); + + { + double max_mean_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + max_mean_error = + std::max(max_mean_error, l2Norm(dpk_uh[cell_id](xj[cell_id]) - R3_affine(xj[cell_id]))); + } + REQUIRE(parallel::allReduceMax(max_mean_error) == Catch::Approx(0).margin(1E-14)); + } + + { + double max_x_slope_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const R3 reconstructed_slope = (1 / 0.2) * (dpk_uh[cell_id](R3{0.1, 0, 0} + xj[cell_id]) - + dpk_uh[cell_id](xj[cell_id] - R3{0.1, 0, 0})); + + max_x_slope_error = std::max(max_x_slope_error, l2Norm(reconstructed_slope - R3{1.7, -0.6, 3.1})); + } + REQUIRE(parallel::allReduceMax(max_x_slope_error) == Catch::Approx(0).margin(1E-12)); + } + + { + double max_y_slope_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const R3 reconstructed_slope = (1 / 0.2) * (dpk_uh[cell_id](R3{0, 0.1, 0} + xj[cell_id]) - + dpk_uh[cell_id](xj[cell_id] - R3{0, 0.1, 0})); + + max_y_slope_error = std::max(max_y_slope_error, l2Norm(reconstructed_slope - R3{-2.2, 1.3, -1.1})); + } + REQUIRE(parallel::allReduceMax(max_y_slope_error) == Catch::Approx(0).margin(1E-12)); + } + + { + double max_z_slope_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const R3 reconstructed_slope = (1 / 0.2) * (dpk_uh[cell_id](R3{0, 0, 0.1} + xj[cell_id]) - + dpk_uh[cell_id](xj[cell_id] - R3{0, 0, 0.1})); + + max_z_slope_error = std::max(max_z_slope_error, l2Norm(reconstructed_slope - R3{1.8, -3.7, 1.9})); + } + REQUIRE(parallel::allReduceMax(max_z_slope_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_affine = [](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]}; + }; + auto xj = MeshDataManager::instance().getMeshData(mesh).xj(); + + DiscreteFunctionP0<R2x2> Ah{p_mesh}; + + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { Ah[cell_id] = R2x2_affine(xj[cell_id]); }); + + descriptor.setRowWeighting(false); + auto reconstructions = PolynomialReconstruction{descriptor}.build(Ah); + + auto dpk_Ah = reconstructions[0]->get<DiscreteFunctionDPk<3, const R2x2>>(); + + { + double max_mean_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + max_mean_error = + std::max(max_mean_error, frobeniusNorm(dpk_Ah[cell_id](xj[cell_id]) - R2x2_affine(xj[cell_id]))); + } + REQUIRE(parallel::allReduceMax(max_mean_error) == Catch::Approx(0).margin(1E-14)); + } + + { + double max_x_slope_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const R2x2 reconstructed_slope = (1 / 0.2) * (dpk_Ah[cell_id](R3{0.1, 0, 0} + xj[cell_id]) - + dpk_Ah[cell_id](xj[cell_id] - R3{0.1, 0, 0})); + + max_x_slope_error = + std::max(max_x_slope_error, frobeniusNorm(reconstructed_slope - R2x2{+1.7, 2.1, // + -2.3, +3.1})); + } + REQUIRE(parallel::allReduceMax(max_x_slope_error) == Catch::Approx(0).margin(1E-13)); + } + + { + double max_y_slope_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const R2x2 reconstructed_slope = (1 / 0.2) * (dpk_Ah[cell_id](R3{0, 0.1, 0} + xj[cell_id]) - + dpk_Ah[cell_id](xj[cell_id] - R3{0, 0.1, 0})); + + max_y_slope_error = + std::max(max_y_slope_error, frobeniusNorm(reconstructed_slope - R2x2{+1.2, -2.2, // + 1.3, +0.8})); + } + REQUIRE(parallel::allReduceMax(max_y_slope_error) == Catch::Approx(0).margin(1E-12)); + } + + { + double max_z_slope_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + const R2x2 reconstructed_slope = (1 / 0.2) * (dpk_Ah[cell_id](R3{0, 0, 0.1} + xj[cell_id]) - + dpk_Ah[cell_id](xj[cell_id] - R3{0, 0, 0.1})); + + max_z_slope_error = + std::max(max_z_slope_error, frobeniusNorm(reconstructed_slope - R2x2{-1.3, -2.4, // + +1.4, -1.8})); + } + REQUIRE(parallel::allReduceMax(max_z_slope_error) == Catch::Approx(0).margin(1E-12)); + } + } + } + } + + SECTION("vector data") + { + using R4 = TinyVector<4>; + + 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 vector_affine = [](const R3& x) -> R4 { + return R4{+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]}; + }; + auto xj = MeshDataManager::instance().getMeshData(mesh).xj(); + + DiscreteFunctionP0Vector<double> Vh{p_mesh, 4}; + + parallel_for( + mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { + auto vector = vector_affine(xj[cell_id]); + for (size_t i = 0; i < vector.dimension(); ++i) { + Vh[cell_id][i] = vector[i]; + } + }); + + descriptor.setPreconditioning(false); + auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh); + + auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<3, const double>>(); + + { + double max_mean_error = 0; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + auto vector = vector_affine(xj[cell_id]); + for (size_t i = 0; i < vector.dimension(); ++i) { + max_mean_error = std::max(max_mean_error, std::abs(dpk_Vh(cell_id, i)(xj[cell_id]) - vector[i])); + } + } + REQUIRE(parallel::allReduceMax(max_mean_error) == Catch::Approx(0).margin(1E-14)); + } + + { + double max_x_slope_error = 0; + const R4 slope{+1.7, 2.1, -2.3, +3.1}; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + for (size_t i = 0; i < slope.dimension(); ++i) { + const double reconstructed_slope = (1 / 0.2) * (dpk_Vh(cell_id, i)(R3{0.1, 0, 0} + xj[cell_id]) - + dpk_Vh(cell_id, i)(xj[cell_id] - R3{0.1, 0, 0})); + + max_x_slope_error = std::max(max_x_slope_error, std::abs(reconstructed_slope - slope[i])); + } + } + REQUIRE(parallel::allReduceMax(max_x_slope_error) == Catch::Approx(0).margin(1E-13)); + } + + { + double max_y_slope_error = 0; + const R4 slope{+1.2, -2.2, 1.3, +0.8}; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + for (size_t i = 0; i < slope.dimension(); ++i) { + const double reconstructed_slope = (1 / 0.2) * (dpk_Vh(cell_id, i)(R3{0, 0.1, 0} + xj[cell_id]) - + dpk_Vh(cell_id, i)(xj[cell_id] - R3{0, 0.1, 0})); + + max_y_slope_error = std::max(max_y_slope_error, std::abs(reconstructed_slope - slope[i])); + } + } + REQUIRE(parallel::allReduceMax(max_y_slope_error) == Catch::Approx(0).margin(1E-12)); + } + + { + double max_z_slope_error = 0; + const R4 slope{-1.3, -2.4, +1.4, -1.8}; + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + for (size_t i = 0; i < slope.dimension(); ++i) { + const double reconstructed_slope = (1 / 0.2) * (dpk_Vh(cell_id, i)(R3{0, 0, 0.1} + xj[cell_id]) - + dpk_Vh(cell_id, i)(xj[cell_id] - R3{0, 0, 0.1})); + + max_z_slope_error = std::max(max_z_slope_error, std::abs(reconstructed_slope - slope[i])); + } + } + REQUIRE(parallel::allReduceMax(max_z_slope_error) == Catch::Approx(0).margin(1E-13)); + } + } + } + } + } + + 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"); + } + } + } + } +} 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_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.cpp b/tests/test_StencilBuilder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b99ba218aa1e3eb5d8ec884e974cfad2f4a157db --- /dev/null +++ b/tests/test_StencilBuilder.cpp @@ -0,0 +1,349 @@ +#include <catch2/catch_test_macros.hpp> +#include <catch2/matchers/catch_matchers_all.hpp> + +#include <MeshDataBaseForTests.hpp> +#include <mesh/Connectivity.hpp> +#include <mesh/ConnectivityUtils.hpp> +#include <mesh/ItemValue.hpp> +#include <mesh/ItemValueUtils.hpp> +#include <mesh/Mesh.hpp> +#include <mesh/MeshFlatNodeBoundary.hpp> +#include <mesh/MeshVariant.hpp> +#include <mesh/NamedBoundaryDescriptor.hpp> +#include <mesh/StencilManager.hpp> +#include <utils/Messenger.hpp> + +// clazy:excludeall=non-pod-global-static + +TEST_CASE("StencilBuilder", "[mesh]") +{ + SECTION("inner stencil") + { + auto is_valid = [](const auto& connectivity, const auto& stencil_array) { + auto cell_to_node_matrix = connectivity.cellToNodeMatrix(); + auto node_to_cell_matrix = connectivity.nodeToCellMatrix(); + + auto cell_is_owned = connectivity.cellIsOwned(); + auto cell_number = connectivity.cellNumber(); + + for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) { + if (cell_is_owned[cell_id]) { + 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]; }); + auto cell_nodes = cell_to_node_matrix[cell_id]; + for (size_t i_node = 0; i_node < cell_nodes.size(); ++i_node) { + const NodeId node_id = cell_nodes[i_node]; + auto node_cells = node_to_cell_matrix[node_id]; + for (size_t i_node_cell = 0; i_node_cell < node_cells.size(); ++i_node_cell) { + const CellId node_cell_id = node_cells[i_node_cell]; + if (node_cell_id != cell_id) { + cell_set.insert(node_cell_id); + } + } + } + + auto cell_stencil = stencil_array[cell_id]; + + auto i_set_cell = cell_set.begin(); + 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("1D") + { + 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}); + + REQUIRE(is_valid(connectivity, stencil_array)); + } + + 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}); + + REQUIRE(is_valid(connectivity, stencil_array)); + } + } + + SECTION("2D") + { + SECTION("cartesian") + { + const auto& mesh = *MeshDataBaseForTests::get().cartesian2DMesh()->get<Mesh<2>>(); + + const Connectivity<2>& connectivity = mesh.connectivity(); + REQUIRE( + is_valid(connectivity, + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}))); + } + + SECTION("hybrid") + { + const auto& mesh = *MeshDataBaseForTests::get().hybrid2DMesh()->get<Mesh<2>>(); + + const Connectivity<2>& connectivity = mesh.connectivity(); + REQUIRE( + is_valid(connectivity, + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}))); + } + } + + SECTION("3D") + { + SECTION("carteian") + { + const auto& mesh = *MeshDataBaseForTests::get().cartesian3DMesh()->get<Mesh<3>>(); + + const Connectivity<3>& connectivity = mesh.connectivity(); + REQUIRE( + is_valid(connectivity, + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}))); + } + + SECTION("hybrid") + { + const auto& mesh = *MeshDataBaseForTests::get().hybrid3DMesh()->get<Mesh<3>>(); + + const Connectivity<3>& connectivity = mesh.connectivity(); + REQUIRE( + is_valid(connectivity, + StencilManager::instance() + .getCellToCellStencilArray(connectivity, + StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}))); + } + } + } + + 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 symmetry_stencils_are_valid = [](const auto& stencil_array, const auto& mesh) { + bool is_valid = true; + + auto node_to_cell_matrix = mesh.connectivity().nodeToCellMatrix(); + auto cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix(); + auto cell_is_owned = mesh.connectivity().cellIsOwned(); + auto cell_number = mesh.connectivity().cellNumber(); + + 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_node_list = getMeshFlatNodeBoundary(mesh, boundary_descriptor); + + CellValue<bool> boundary_cell{mesh.connectivity()}; + boundary_cell.fill(false); + auto node_list = boundary_node_list.nodeList(); + for (size_t i_node = 0; i_node < node_list.size(); ++i_node) { + const NodeId node_id = node_list[i_node]; + auto node_cell_list = node_to_cell_matrix[node_id]; + for (size_t i_cell = 0; i_cell < node_cell_list.size(); ++i_cell) { + const CellId cell_id = node_cell_list[i_cell]; + boundary_cell[cell_id] = true; + } + } + + std::set<NodeId> symmetry_node; + for (size_t i_node = 0; i_node < node_list.size(); ++i_node) { + const NodeId node_id = node_list[i_node]; + symmetry_node.insert(node_id); + } + + for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) { + if ((not boundary_cell[cell_id]) or (not cell_is_owned[cell_id])) { + is_valid &= (boundary_stencil[cell_id].size() == 0); + } else { + auto cell_node_list = cell_to_node_matrix[cell_id]; + std::set<CellId, std::function<bool(CellId, CellId)>> cell_set( + [&](CellId cell0_id, CellId cell1_id) { return cell_number[cell0_id] < cell_number[cell1_id]; }); + for (size_t i_node = 0; i_node < cell_node_list.size(); ++i_node) { + const NodeId node_id = cell_node_list[i_node]; + if (symmetry_node.contains(node_id)) { + auto node_cell_list = node_to_cell_matrix[node_id]; + for (size_t i_node_cell = 0; i_node_cell < node_cell_list.size(); ++i_node_cell) { + const CellId node_cell_id = node_cell_list[i_node_cell]; + cell_set.insert(node_cell_id); + } + } + } + + if (cell_set.size() == boundary_stencil[cell_id].size()) { + size_t i = 0; + for (auto&& id : cell_set) { + is_valid &= (id == boundary_stencil[cell_id][i++]); + } + } else { + is_valid = false; + } + } + } + } + + return is_valid; + }; + + 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(symmetry_stencils_are_valid(stencil_array, mesh)); + } + + SECTION("hybrid") + { + const auto& mesh = *MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>(); + + const Connectivity<1>& connectivity = mesh.connectivity(); + auto stencil = + StencilManager::instance() + .getCellToCellStencilArray(connectivity, StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}, + list); + + REQUIRE(stencil.symmetryBoundaryStencilArrayList().size() == 2); + REQUIRE(check_ghost_cells_have_empty_stencils(stencil, connectivity)); + REQUIRE(symmetry_stencils_are_valid(stencil, mesh)); + } + } + + 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(symmetry_stencils_are_valid(stencil_array, mesh)); + } + + SECTION("hybrid") + { + const auto& mesh = *MeshDataBaseForTests::get().hybrid2DMesh()->get<Mesh<2>>(); + + const Connectivity<2>& connectivity = mesh.connectivity(); + auto stencil = + StencilManager::instance() + .getCellToCellStencilArray(connectivity, StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}, + list); + + REQUIRE(stencil.symmetryBoundaryStencilArrayList().size() == 4); + REQUIRE(check_ghost_cells_have_empty_stencils(stencil, connectivity)); + REQUIRE(symmetry_stencils_are_valid(stencil, mesh)); + } + } + + 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 = + StencilManager::instance() + .getCellToCellStencilArray(connectivity, StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}, + list); + + REQUIRE(stencil.symmetryBoundaryStencilArrayList().size() == 6); + REQUIRE(check_ghost_cells_have_empty_stencils(stencil, connectivity)); + REQUIRE(symmetry_stencils_are_valid(stencil, mesh)); + } + + SECTION("hybrid") + { + const auto& mesh = *MeshDataBaseForTests::get().hybrid3DMesh()->get<Mesh<3>>(); + + const Connectivity<3>& connectivity = mesh.connectivity(); + auto stencil = + StencilManager::instance() + .getCellToCellStencilArray(connectivity, StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}, + list); + + REQUIRE(stencil.symmetryBoundaryStencilArrayList().size() == 6); + REQUIRE(check_ghost_cells_have_empty_stencils(stencil, connectivity)); + REQUIRE(symmetry_stencils_are_valid(stencil, mesh)); + } + } + } +} diff --git a/tests/test_TinyMatrix.cpp b/tests/test_TinyMatrix.cpp index 80159a021b54ebb162d339be2bbd5195aec815a0..7bade539c5cd153efb9fde5cab2f5a2323dd26ca 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 @@ -209,6 +210,38 @@ TEST_CASE("TinyMatrix", "[algebra]") REQUIRE(trace(TinyMatrix<4>(1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 2, 0, 0, 2, 2)) == 1 + 0 + 1 + 2); } + SECTION("checking for dot product calculation") + { + { + TinyMatrix<1, 1, int> M(6); + TinyMatrix<1, 1, int> N(7); + REQUIRE(dot(M, N) == trace(M * transpose(N))); + } + + { + TinyMatrix<2, 3, int> M(6, 3, -2, // + 5, -1, 4); + TinyMatrix<2, 3, int> N(7, 8, -6, // + -3, 4, 2); + REQUIRE(dot(M, N) == trace(M * transpose(N))); + REQUIRE(dot(M, N) == trace(N * transpose(M))); + } + } + + SECTION("checking for Frobenius norm calculation") + { + { + TinyMatrix<1, 1, int> M(-6); + REQUIRE(frobeniusNorm(M) == 6); + } + + { + TinyMatrix<2, 3, int> M(6, 3, -2, // + 5, -1, 4); + REQUIRE(frobeniusNorm(M) == std::sqrt(dot(M, M))); + } + } + SECTION("checking for inverse calculations") { { diff --git a/tests/test_main.cpp b/tests/test_main.cpp index d8641d318f243eb624915882a92fc15d57a71346..504541ee0951895bd73899eb8aa0b5d89f7d46f6 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> @@ -58,7 +59,10 @@ main(int argc, char* argv[]) MeshDataManager::create(); DualConnectivityManager::create(); DualMeshManager::create(); + StencilManager::create(); + GlobalVariableManager::create(); + GlobalVariableManager::instance().setNumberOfGhostLayers(1); MeshDataBaseForTests::create(); @@ -71,6 +75,7 @@ main(int argc, char* argv[]) MeshDataBaseForTests::destroy(); GlobalVariableManager::destroy(); + StencilManager::destroy(); DualMeshManager::destroy(); DualConnectivityManager::destroy(); MeshDataManager::destroy();