diff --git a/.gitignore b/.gitignore index 6c28c2e5a9..ac93357a35 100644 --- a/.gitignore +++ b/.gitignore @@ -369,6 +369,7 @@ miniapps/shifted/lsf_integral miniapps/tools/display-basis miniapps/tools/load-dc miniapps/tools/convert-dc +miniapps/tools/compare-dc miniapps/tools/gridfunction-bounds miniapps/tools/lor-transfer miniapps/tools/plor-transfer diff --git a/CHANGELOG b/CHANGELOG index 2bcbb4f3c8..356afb5692 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -17,11 +17,19 @@ Discretization improvements Vector and VectorFE, also NURBS versions. Optionally different types of projections can be selected, default behaviour has not changed. +- Added methods to estimate function extremum using piecewise linear bounds + + recursive subdivision. + Meshing improvements -------------------- - Improved support for 1D NURBS meshes with variable order, including using the patches construct for 1D NURBS meshes. +New and updated examples and miniapps +------------------------------------- +- Electromagnetics/lorentz miniapp has been updated to leverage the ParticleSet + capability. + Version 4.9, released on Dec 11, 2025 ===================================== @@ -106,6 +114,23 @@ Linear and nonlinear solvers Filtering (AMGF), providing robust preconditioning for linear systems arising in constrained optimization problems such as frictionless contact. + Added 'GetResiduals' and 'GetFinalAbsResidualNorm' to 'HyprePCG', + 'HypreGMRES', and 'HypreFGMRES' to get 'r' and '|r|_p'. Note that the latter + computes '|r|_p' from 'r' instead of returning a cached value like the + relative 'GetFinalResidualNorm'. These require Hypre >= 2.15.0. + + Changed the default solver parameters for 'HyprePCG' to 'tol=1e-6' and + 'max_iter=1000'. This matches the default parameters in Hypre 3.0. + + Added various helper functions for querying/modifying Hypre solvers: + 'HypreSmoother::GetType', 'HypreSmoother::GetSOROptions', + 'HypreSmoother::GetPolyOptions', 'HypreSmoother::GetWindowParameters', + 'HypreSmoother::IsOperatorSymmetric', 'HyprePCG::GetTol', + 'HyprePCG::GetAbsTol', 'HyprePCG::GetMaxIter', 'HyprePCG::SetUseTwoNorm', + 'HypreGMRES::GetTol', 'HypreGMRES::GetAbsTol', 'HypreGMRES::GetMaxIter', + 'HypreGMRES::GetKDim', 'HypreFGMRES::GetTol', 'HypreFGMRES::GetMaxIter', + 'HypreFGMRES::GetKDim', and 'HypreBoomerAMG::GetMaxIter'. + GPU computing ------------- - Added the 'gpu', 'raja-gpu', and 'ceed-gpu' backend aliases/shortcuts which diff --git a/config/defaults.mk b/config/defaults.mk index e18df1560b..3a283935c5 100644 --- a/config/defaults.mk +++ b/config/defaults.mk @@ -18,6 +18,7 @@ # Some choices below are based on the OS type: NOTMAC := $(subst Darwin,,$(shell uname -s)) +ASTYLE_BIN = astyle ETAGS_BIN = $(shell command -v etags 2> /dev/null) EGREP_BIN = $(shell command -v egrep 2> /dev/null) diff --git a/examples/ex4.cpp b/examples/ex4.cpp index e892bfcbfb..45fb827c27 100644 --- a/examples/ex4.cpp +++ b/examples/ex4.cpp @@ -9,6 +9,7 @@ // ex4 -m ../data/beam-hex.mesh -o 2 -pa // ex4 -m ../data/escher.mesh // ex4 -m ../data/fichera.mesh -o 2 -hb +// ex4 -m ../data/fichera.mesh -o 2 -hb -ea // ex4 -m ../data/fichera-q2.vtk // ex4 -m ../data/fichera-q3.mesh -o 2 -sc // ex4 -m ../data/square-disc-nurbs.mesh @@ -18,6 +19,7 @@ // ex4 -m ../data/amr-quad.mesh // ex4 -m ../data/amr-hex.mesh // ex4 -m ../data/amr-hex.mesh -o 2 -hb +// ex4 -m ../data/amr-hex.mesh -o 2 -hb -ea // ex4 -m ../data/fichera-amr.mesh -o 2 -sc // ex4 -m ../data/ref-prism.mesh -o 1 // ex4 -m ../data/octahedron.mesh -o 1 @@ -25,6 +27,8 @@ // // Device sample runs: // ex4 -m ../data/star.mesh -pa -d cuda +// ex4 -m ../data/star.mesh -hb -ea -d cuda +// ex4 -m ../data/amr-quad.mesh -hb -ea -d cuda // ex4 -m ../data/star.mesh -pa -d raja-cuda // ex4 -m ../data/star.mesh -pa -d raja-omp // ex4 -m ../data/beam-hex.mesh -pa -d cuda @@ -193,7 +197,7 @@ int main(int argc, char *argv[]) cout << "Size of linear system: " << A->Height() << endl; // 11. Solve the linear system A X = B. - if (!pa) + if (!pa && (!ea || hybridization)) { #ifndef MFEM_USE_SUITESPARSE // Use a simple symmetric Gauss-Seidel preconditioner with PCG. diff --git a/examples/ex4p.cpp b/examples/ex4p.cpp index 575fbb3369..bf72f96976 100644 --- a/examples/ex4p.cpp +++ b/examples/ex4p.cpp @@ -9,6 +9,7 @@ // mpirun -np 4 ex4p -m ../data/beam-hex.mesh -o 2 -pa // mpirun -np 4 ex4p -m ../data/escher.mesh -o 2 -sc // mpirun -np 4 ex4p -m ../data/fichera.mesh -o 2 -hb +// mpirun -np 4 ex4p -m ../data/fichera.mesh -o 2 -hb -ea // mpirun -np 4 ex4p -m ../data/fichera-q2.vtk // mpirun -np 4 ex4p -m ../data/fichera-q3.mesh -o 2 -sc // mpirun -np 4 ex4p -m ../data/square-disc-nurbs.mesh -o 3 @@ -17,14 +18,18 @@ // mpirun -np 4 ex4p -m ../data/periodic-cube.mesh -no-bc // mpirun -np 4 ex4p -m ../data/amr-quad.mesh // mpirun -np 3 ex4p -m ../data/amr-quad.mesh -o 2 -hb +// mpirun -np 3 ex4p -m ../data/amr-quad.mesh -o 2 -hb -ea // mpirun -np 4 ex4p -m ../data/amr-hex.mesh -o 2 -sc // mpirun -np 4 ex4p -m ../data/amr-hex.mesh -o 2 -hb +// mpirun -np 4 ex4p -m ../data/amr-hex.mesh -o 2 -hb -ea // mpirun -np 4 ex4p -m ../data/ref-prism.mesh -o 1 // mpirun -np 4 ex4p -m ../data/octahedron.mesh -o 1 // mpirun -np 4 ex4p -m ../data/star-surf.mesh -o 3 -hb // // Device sample runs: // mpirun -np 4 ex4p -m ../data/star.mesh -pa -d cuda +// mpirun -np 4 ex4p -m ../data/star.mesh -ea -hb -d cuda +// mpirun -np 4 ex4p -m ../data/amr-hex.mesh -ea -hb -d cuda // mpirun -np 4 ex4p -m ../data/star.mesh -pa -d raja-cuda // mpirun -np 4 ex4p -m ../data/star.mesh -pa -d raja-omp // mpirun -np 4 ex4p -m ../data/beam-hex.mesh -pa -d cuda @@ -230,7 +235,7 @@ int main(int argc, char *argv[]) pcg->SetMaxIter(2000); pcg->SetPrintLevel(1); if (hybridization) { prec = new HypreBoomerAMG(*A.As()); } - else if (pa) { prec = new OperatorJacobiSmoother(*a, ess_tdof_list); } + else if (pa || ea) { prec = new OperatorJacobiSmoother(*a, ess_tdof_list); } else { ParFiniteElementSpace *prec_fespace = diff --git a/fem/bilinearform.cpp b/fem/bilinearform.cpp index 3c50e64b28..02c24989d5 100644 --- a/fem/bilinearform.cpp +++ b/fem/bilinearform.cpp @@ -825,14 +825,46 @@ void BilinearForm::FormLinearSystem(const Array &ess_tdof_list, Vector &x, Vector &b, OperatorHandle &A, Vector &X, Vector &B, int copy_interior) { + const SparseMatrix *P = fes->GetConformingProlongation(); + const SparseMatrix *R = fes->GetConformingRestriction(); if (ext) { if (hybridization) { FormSystemMatrix(ess_tdof_list, A); - ConstrainedOperator A_constrained(this, ess_tdof_list); - A_constrained.EliminateRHS(x, b); - hybridization->ReduceRHS(b, B); + + std::unique_ptr A_constrained([&]() + { + Operator *op; + Operator::FormSystemOperator(ess_tdof_list, op); + return dynamic_cast(op); + }()); + MFEM_ASSERT(A_constrained != nullptr, ""); + + Vector conf_b, conf_x; + if (P) + { + // Nonconforming + conf_b.SetSize(P->Width()); + conf_x.SetSize(P->Width()); + P->MultTranspose(b, conf_b); + R->Mult(x, conf_x); + } + else + { + // Conforming + conf_b.MakeRef(b, 0, b.Size()); + conf_x.MakeRef(x, 0, x.Size()); + } + + A_constrained->EliminateRHS(conf_x, conf_b); + + if (P) + { + R->MultTranspose(conf_b, b); // store eliminated rhs in b + } + + hybridization->ReduceRHS(conf_b, B); X.SetSize(B.Size()); X = 0.0; } @@ -842,7 +874,6 @@ void BilinearForm::FormLinearSystem(const Array &ess_tdof_list, Vector &x, } return; } - const SparseMatrix *P = fes->GetConformingProlongation(); FormSystemMatrix(ess_tdof_list, A); // Transform the system and perform the elimination in B, based on the @@ -878,7 +909,6 @@ void BilinearForm::FormLinearSystem(const Array &ess_tdof_list, Vector &x, if (hybridization) { // Reduction to the Lagrange multipliers system - const SparseMatrix *R = fes->GetConformingRestriction(); Vector conf_b(P->Width()), conf_x(P->Width()); P->MultTranspose(b, conf_b); R->Mult(x, conf_x); @@ -891,7 +921,6 @@ void BilinearForm::FormLinearSystem(const Array &ess_tdof_list, Vector &x, else { // Variational restriction with P - const SparseMatrix *R = fes->GetConformingRestriction(); B.SetSize(P->Width()); P->MultTranspose(b, B); X.SetSize(R->Height()); diff --git a/fem/bounds.cpp b/fem/bounds.cpp index e4c1ec7850..8cdd7a8c87 100644 --- a/fem/bounds.cpp +++ b/fem/bounds.cpp @@ -39,8 +39,8 @@ void PLBound::Setup(const int nb_i, const int ncp_i, b_type = b_type_i; cp_type = cp_type_i; tol = tol_i; - lbound.SetSize(nb, ncp); - ubound.SetSize(nb, ncp); + lbound.SetSize(ncp, nb); + ubound.SetSize(ncp, nb); nodes.SetSize(nb); weights.SetSize(nb); control_points.SetSize(ncp); @@ -125,21 +125,25 @@ void PLBound::Setup(const int nb_i, const int ncp_i, { if (j == 0) { - lbound(i, j) = bv(i); - ubound(i, j) = bv(i); + lbound(j,i) = bv(i); + ubound(j,i) = bv(i); } else if (j == ncp-1) { - lbound(i, j) = bv(i); - ubound(i, j) = bv(i); + lbound(j,i) = bv(i); + ubound(j,i) = bv(i); } else { vals(0) = bv(i); vals(1) = bmv(i) + dm*bdmv(i); vals(2) = bpv(i) + dp*bdpv(i); - lbound(i, j) = vals.Min()-tol; // tolerance for good measure - ubound(i, j) = vals.Max()+tol; // tolerance for good measure + lbound(j,i) = vals.Min()-tol; // tolerance for good measure + ubound(j,i) = vals.Max()+tol; // tolerance for good measure + if (b_type == 2) + { + lbound(j,i) = std::max(lbound(j,i),0_r); + } } } } @@ -273,8 +277,7 @@ void PLBound::Get1DBounds(const Vector &coeff, Vector &intmin, intmax.SetSize(ncp); intmin = 0.0; intmax = 0.0; - Vector coeffm(nb); - coeffm = 0.0; + Vector coeffm; real_t a0 = 0.0; real_t a1 = 0.0; @@ -302,6 +305,8 @@ void PLBound::Get1DBounds(const Vector &coeff, Vector &intmin, // compute L2 projection for linear bases: a0 + a1*x if (proj) { + coeffm.SetSize(nb); + coeffm = 0.0; for (int i = 0; i < nb; i++) { x = 2.0*nodes_int(i)-1; @@ -342,8 +347,8 @@ void PLBound::Get1DBounds(const Vector &coeff, Vector &intmin, real_t c = coeffm(i); for (int j = 0; j < ncp; j++) { - intmin(j) += min(lbound(i,j)*c, ubound(i,j)*c); - intmax(j) += max(lbound(i,j)*c, ubound(i,j)*c); + intmin(j) += min(lbound(j,i)*c, ubound(j,i)*c); + intmax(j) += max(lbound(j,i)*c, ubound(j,i)*c); } } } @@ -474,10 +479,10 @@ void PLBound::Get2DBounds(const Vector &coeff, Vector &intmin, real_t w1 = intmaxT(id2++); for (int k = 0; k < ncp; k++) // kth row { - vals(0) = w0*lbound(j,k); - vals(1) = w0*ubound(j,k); - vals(2) = w1*lbound(j,k); - vals(3) = w1*ubound(j,k); + vals(0) = w0*lbound(k,j); + vals(1) = w0*ubound(k,j); + vals(2) = w1*lbound(k,j); + vals(3) = w1*ubound(k,j); intmin(k*ncp+i) += vals.Min(); intmax(k*ncp+i) += vals.Max(); } @@ -553,17 +558,17 @@ void PLBound::Get3DBounds(const Vector &coeff, Vector &intmin, for (int i = 0; i < nb; i++) { x = 2.0*nodes(i)-1; // x-coordinate - minBounds(i) -= a0V(j) + a1V(j)*x; - maxBounds(i) -= a0V(j) + a1V(j)*x; + minNodalVals(i) -= a0V(j) + a1V(j)*x; + maxNodalVals(i) -= a0V(j) + a1V(j)*x; } // Compute Bernstein coefficients LUFactors lu(basisMatLU.GetData(), lu_ip.GetData()); - lu.Solve(nb, 1, minBounds.GetData()); - lu.Solve(nb, 1, maxBounds.GetData()); + lu.Solve(nb, 1, minNodalVals.GetData()); + lu.Solve(nb, 1, maxNodalVals.GetData()); for (int i = 0; i < nb; i++) { - intminT(i*ncp2+j) = minBounds(i); - intmaxT(i*ncp2+j) = maxBounds(i); + intminT(i*ncp2+j) = minNodalVals(i); + intmaxT(i*ncp2+j) = maxNodalVals(i); } } } @@ -617,10 +622,10 @@ void PLBound::Get3DBounds(const Vector &coeff, Vector &intmin, real_t w1 = intmaxT(id2++); for (int k = 0; k < ncp; k++) // kth slice { - vals(0) = w0*lbound(j,k); - vals(1) = w0*ubound(j,k); - vals(2) = w1*lbound(j,k); - vals(3) = w1*ubound(j,k); + vals(0) = w0*lbound(k,j); + vals(1) = w0*ubound(k,j); + vals(2) = w1*lbound(k,j); + vals(3) = w1*ubound(k,j); intmin(k*ncp2+i) += vals.Min(); intmax(k*ncp2+i) += vals.Max(); } @@ -653,7 +658,8 @@ void PLBound::SetupBernsteinBasisMat(DenseMatrix &basisMat, Vector &nodesBern) const { const int nbern = nodesBern.Size(); - L2_SegmentElement el(nbern-1, 2); // we use L2 to leverage lexicographic order + L2_SegmentElement el(nbern-1, 2); + // we use L2 to leverage lexicographic order Array ordering = el.GetLexicographicOrdering(); basisMat.SetSize(nbern, nbern); Vector shape(nbern); @@ -666,6 +672,39 @@ void PLBound::SetupBernsteinBasisMat(DenseMatrix &basisMat, } } +DenseMatrix PLBound::GetBoundingMatrix(int dim, bool is_lower) const +{ + if (dim > 1) + { + const int ncpd = static_cast(std::pow(ncp, dim)); + const int nbd = static_cast(std::pow(nb, dim)); + DenseMatrix boundND(ncpd, nbd); + Vector phimin, phimax, col; + Vector coeffs(nbd); + coeffs = 0.0; + for (int j = 0; j < nbd; j++) + { + coeffs(j) = 1.0; + boundND.GetColumnReference(j, col); + GetNDBounds(dim, coeffs, phimin, phimax); + col = is_lower ? phimin : phimax; + coeffs(j) = 0.0; + } + return boundND; + } + return is_lower ? lbound : ubound; +} + +DenseMatrix PLBound::GetLowerBoundMatrix(int dim) const +{ + return GetBoundingMatrix(dim, true); +} + +DenseMatrix PLBound::GetUpperBoundMatrix(int dim) const +{ + return GetBoundingMatrix(dim, false); +} + constexpr int PLBound::min_ncp_gl_x[2][11]; constexpr int PLBound::min_ncp_gll_x[2][11]; constexpr int PLBound::min_ncp_pos_x[2][11]; @@ -716,4 +755,4 @@ void PLBound::Print(std::ostream &outp) const ubound.Print(outp); } -} +} \ No newline at end of file diff --git a/fem/bounds.hpp b/fem/bounds.hpp index 66ed78614e..67cbb89432 100644 --- a/fem/bounds.hpp +++ b/fem/bounds.hpp @@ -19,14 +19,18 @@ namespace mfem { /** @name Piecewise linear bounds of bases - \brief Piecewise linear bounds of bases can be used to compute bounds on the grid function in each element. The bounds for the bases are constructed based on the following parameters: + \brief Piecewise linear bounds of bases can be used to compute bounds on + the grid function in each element. The bounds for the bases are constructed + based on the following parameters: (i) @b nb: number of bases/nodes in 1D (i.e. polynomial order+1), - (ii) @b b_type: bases type, 0 - Lagrange interpolants on Gauss-Legendre nodes, 1 - Lagrange interpolants on Gauss-Lobatto-Legendre nodes, and + (ii) @b b_type: bases type, 0 - Lagrange interpolants on Gauss-Legendre + nodes, 1 - Lagrange interpolants on Gauss-Lobatto-Legendre nodes, and 2 - Positive/Bernstein bases on uniformly distributed nodes, - (iii) @b ncp: number of control points used to construct the piecewise linear bounds + (iii) @b ncp: number of control points used to construct the piecewise + linear bounds (iv) @b cp_type: control point distribution. 0 - GL + end-points, 1 - Chebyshev. @@ -35,7 +39,9 @@ namespace mfem If the user does not specify @b ncp and @b cp_type, the minimum value of @b ncp is used that would bound the bases for the @b cp_type. We default - to @b cp_type = 0 as it requires fewer number of points to bound the bases. Typically, @b ncp = 2 @b nb is sufficient to get fairly compact bounds, and increasing @b ncp results in tighter bounds. + to @b cp_type = 0 as it requires fewer number of points to bound the bases. + Typically, @b ncp = 2 @b nb is sufficient to get fairly compact bounds, and + increasing @b ncp results in tighter bounds. Finally, only tensor-product elements are currently supported. @@ -54,7 +60,7 @@ private: bool proj = true; // Use linear projection to compute bounds. real_t tol = 0.0; // offset bounds to avoid round-off errors Vector nodes, weights, control_points; - DenseMatrix lbound, ubound; // nb x ncp matrices with bounds of all bases + DenseMatrix lbound, ubound; // ncp x nb matrices with bounds of all bases // Some auxillary storage for computing the bounds with Bernstein DenseMatrix basisMatNodes; // Bernstein bases at equispaced nodes DenseMatrix basisMatInt; // Bernstein bases at GLL nodes @@ -80,6 +86,9 @@ private: {3,5,8,9,11,12,13,13,14,15,16} }; + /// Helper function to extract lower or upper bounding matrix + DenseMatrix GetBoundingMatrix(int dim, bool is_lower) const; + public: // Constructor PLBound(const int nb_i, const int ncp_i, const int b_type_i, @@ -92,40 +101,82 @@ public: PLBound(const FiniteElementSpace *fes, const int ncp_i = -1, const int cp_type_i = 0); - // Get minimum number of control points needed to bound the given bases + /// Get minimum number of control points needed to bound the given bases int GetMinimumPointsForGivenBases(int nb_i, int b_type_i, int cp_type_i) const; - // Print information about the bounds + /// Print information about the bounds void Print(std::ostream &outp = mfem::out) const; - // Enable (default) or disable linear projection before bounding. - // This projection increases the computational cost but results in tighter - // bounds. + /** @brief Enable (default) or disable linear projection before bounding. + * + * @details This projection increases the computational cost but results in + * tighter bounds. + */ void SetProjectionFlagForBounding(bool proj_) { proj = proj_; } - /// Compute piecewise linear bounds for the lexicographically-ordered - /// coefficients in @a coeff in 1D/2D/3D. + /** @brief Compute piecewise linear bounds for the lexicographically-ordered + * nodal coefficients in @a coeff in 1D/2D/3D. + * + * @param[in] rdim The spatial dimension of the element (1, 2, or 3). + * @param[in] coeff The vector of lexicographically-ordered coefficients. + * Should be of size nb^rdim, where nb is the number of + * bases/nodes in 1D. These coefficients must correspond + * to the bases type and number of bases, used in the + * constructor of PLBound. + * + * @param[out] intmin The vector of minimum bound for all control points. + * @param[out] intmax The vector of maximum bound for all control points. + * Both intmin and intmax are of size ncp^rdim, where + * ncp is the number of control points in 1D, and are + * ordered lexicographically. + */ void GetNDBounds(const int rdim, const Vector &coeff, Vector &intmin, Vector &intmax) const; /// Get number of control points used to compute the bounds. int GetNControlPoints() const { return ncp; } + + /// Get 1D control point locations (lexicographic order) in [0,1]. + const Vector &GetControlPoints() const { return control_points; } + + /** @brief Get lower and upper bounding matrix (ncp^dim x nb^dim) + * + * @details The matrices can be used to compute the bounds at control points + * by a simple matrix-vector product with the + * lexicographically-ordered nodal coefficients. + * The resulting output is also lexicographically-ordered. + * + * @note These matrices do not account for the linear projection step that + * is optionally done in GetNDBounds before bounding the function. + */ + ///@{ + DenseMatrix GetLowerBoundMatrix(int dim = 1) const; + DenseMatrix GetUpperBoundMatrix(int dim = 1) const; + ///@} + private: - /// Compute piecewise linear bounds for the lexicographically-ordered - /// coefficients in @a coeff in 1D. + /** @brief Compute piecewise linear bounds for the lexicographically-ordered + * nodal coefficients in @a coeff in 1D. + * See GetNDBounds for details of the input and output parameters. + */ void Get1DBounds(const Vector &coeff, Vector &intmin, Vector &intmax) const; - /// Compute piecewise linear bounds for the lexicographically-ordered - /// coefficients in @a coeff in 2D. + /** @brief Compute piecewise linear bounds for the lexicographically-ordered + * nodal coefficients in @a coeff in 2D. + * See GetNDBounds for details of the input and output parameters. + */ void Get2DBounds(const Vector &coeff, Vector &intmin, Vector &intmax) const; - /// Compute piecewise linear bounds for the lexicographically-ordered - /// coefficients in @a coeff in 3D. + /** @brief Compute piecewise linear bounds for the lexicographically-ordered + * nodal coefficients in @a coeff in 3D. + * See GetNDBounds for details of the input and output parameters. + */ void Get3DBounds(const Vector &coeff, Vector &intmin, Vector &intmax) const; - /// Setup matrix used to compute values at given 1D locations in [0,1] - /// for Bernstein bases. + /** @brief Setup matrix used to compute values at given 1D locations in [0,1] + * for Bernstein bases. + */ void SetupBernsteinBasisMat(DenseMatrix &basisMat, Vector &nodesBern) const; void Setup(const int nb_i, const int ncp_i, const int b_type_i, diff --git a/fem/complex_fem.hpp b/fem/complex_fem.hpp index 9b66b6c177..1f74c33d4d 100644 --- a/fem/complex_fem.hpp +++ b/fem/complex_fem.hpp @@ -82,6 +82,25 @@ public: /// underlying #fes int VectorDim() const; + /// Copy assignment. Only the data of the base class Vector is copied. + /** It is assumed that this object and @a rhs use FiniteElementSpace%s that + have the same size. + + @note Defining this method overwrites the implicitly defined copy + assignment operator. */ + ComplexGridFunction &operator=(const ComplexGridFunction &rhs) + { return operator=((const Vector &)rhs); } + + /// Copy the data from @a v. + /** The size of @a v must be equal to double of the size of the associated + FiniteElementSpace #fes. */ + ComplexGridFunction &operator=(const Vector &v) + { + MFEM_ASSERT(fes && v.Size() == 2*fes->GetVSize(), ""); + Vector::operator=(v); + return *this; + } + /// Assign constant values to the ComplexGridFunction data. ComplexGridFunction &operator=(const std::complex & value) { *gfr = value.real(); *gfi = value.imag(); return *this; } diff --git a/fem/dfem/integrate.hpp b/fem/dfem/integrate.hpp index 3731d922b5..d23a6f37c6 100644 --- a/fem/dfem/integrate.hpp +++ b/fem/dfem/integrate.hpp @@ -90,8 +90,8 @@ void map_quadrature_data_to_fields_impl( } else { - MFEM_ABORT("quadrature data mapping to field is not implemented for" - " this field descriptor"); + MFEM_ABORT_KERNEL("quadrature data mapping to field is not implemented" + " for this field descriptor"); } } @@ -169,8 +169,9 @@ void map_quadrature_data_to_fields_tensor_impl_1d( } else { - MFEM_ABORT("quadrature data mapping to field is not implemented for" - " this field descriptor with sum factorization on tensor product elements"); + MFEM_ABORT_KERNEL("quadrature data mapping to field is not implemented" + "for this field descriptor with sum factorization on" + " tensor product elements"); } } @@ -306,8 +307,9 @@ void map_quadrature_data_to_fields_tensor_impl_2d( } else { - MFEM_ABORT("quadrature data mapping to field is not implemented for" - " this field descriptor with sum factorization on tensor product elements"); + MFEM_ABORT_KERNEL("quadrature data mapping to field is not implemented" + " for this field descriptor with sum factorization on" + " tensor product elements"); } } @@ -492,8 +494,9 @@ void map_quadrature_data_to_fields_tensor_impl_3d( } else { - MFEM_ABORT("quadrature data mapping to field is not implemented for" - " this field descriptor with sum factorization on tensor product elements"); + MFEM_ABORT_KERNEL("quadrature data mapping to field is not implemented" + " for this field descriptor with sum factorization on" + " tensor product elements"); } } diff --git a/fem/dgmassinv_kernels.hpp b/fem/dgmassinv_kernels.hpp index 089afb5b40..b22dd877e6 100644 --- a/fem/dgmassinv_kernels.hpp +++ b/fem/dgmassinv_kernels.hpp @@ -387,7 +387,7 @@ void DGMassInverse::DGMassCGIteration(const Vector &b_, Vector &u_) const static constexpr int NB = Q1D ? Q1D : 1; // block size - mfem::forall_2D(NE, NB, NB, [=] MFEM_HOST_DEVICE (int e) + mfem::forall_2D(NE, NB, NB, [=] MFEM_HOST_DEVICE (int e) { // Perform change of basis if needed if (CHANGE_BASIS) diff --git a/fem/fe/face_map_utils.hpp b/fem/fe/face_map_utils.hpp index ac921811ba..33970c127f 100644 --- a/fem/fe/face_map_utils.hpp +++ b/fem/fe/face_map_utils.hpp @@ -69,9 +69,9 @@ inline int ToLexOrdering2D(const int face_id, const int size1d, const int i) } /// @brief Given a face DOF index on a shared face, ordered lexicographically -/// relative to element the element (where the local face is face_id), and -/// return the corresponding face DOF index ordered lexicographically relative -/// to the face itself. +/// relative to the element (where the local face is face_id), return the +/// corresponding face DOF index ordered lexicographically relative to the face +/// itself. MFEM_HOST_DEVICE inline int PermuteFace2D(const int face_id, const int orientation, const int size1d, const int index) diff --git a/fem/fe/fe_base.cpp b/fem/fe/fe_base.cpp index 811ed75538..53b8741cfc 100644 --- a/fem/fe/fe_base.cpp +++ b/fem/fe/fe_base.cpp @@ -231,7 +231,7 @@ void FiniteElement::CalcPhysLaplacian(ElementTransformation &Trans, { for (int nd = 0; nd < dof; nd++) { - Laplacian[nd] = hess(nd,0) + hess(nd,4) + hess(nd,5); + Laplacian[nd] = hess(nd,0) + hess(nd,3) + hess(nd,5); } } else if (dim == 2) @@ -268,11 +268,9 @@ void FiniteElement::CalcPhysLinLaplacian(ElementTransformation &Trans, scale[0] = Gij(0,0); scale[1] = 2*Gij(0,1); scale[2] = 2*Gij(0,2); - - scale[3] = 2*Gij(1,2); - scale[4] = Gij(2,2); - - scale[5] = Gij(1,1); + scale[3] = Gij(1,1); + scale[4] = 2*Gij(1,2); + scale[5] = Gij(2,2); } else if (dim == 2) { @@ -309,12 +307,12 @@ void FiniteElement::CalcPhysHessian(ElementTransformation &Trans, map[2] = 2; map[3] = 1; - map[4] = 5; - map[5] = 3; + map[4] = 3; + map[5] = 4; map[6] = 2; - map[7] = 3; - map[8] = 4; + map[7] = 4; + map[8] = 5; } else if (dim == 2) { @@ -382,11 +380,7 @@ const DofToQuad &FiniteElement::GetDofToQuad(const IntegrationRule &ir, #pragma omp critical (DofToQuad) #endif { - for (int i = 0; i < dof2quad_array.Size(); i++) - { - d2q = dof2quad_array[i]; - if (d2q->IntRule != &ir || d2q->mode != mode) { d2q = nullptr; } - } + d2q = DofToQuad::SearchArray(dof2quad_array, ir, mode); if (!d2q) { #ifdef MFEM_THREAD_SAFE @@ -661,58 +655,67 @@ void ScalarFiniteElement::ScalarLocalL2Restriction( void NodalFiniteElement::CreateLexicographicFullMap(const IntegrationRule &ir) const { + // Get the FULL version of the map. This call contains omp critical region, + // so it is done before the critical region below. + auto &d2q = GetDofToQuad(ir, DofToQuad::FULL); #if defined(MFEM_THREAD_SAFE) && defined(MFEM_USE_OPENMP) #pragma omp critical (DofToQuad) #endif { - // Get the FULL version of the map. - auto &d2q = GetDofToQuad(ir, DofToQuad::FULL); - //Undo the native ordering which is what FiniteElement::GetDofToQuad returns. - auto *d2q_new = new DofToQuad(d2q); - d2q_new->mode = DofToQuad::LEXICOGRAPHIC_FULL; - const int nqpt = ir.GetNPoints(); - - const int b_dim = (range_type == VECTOR) ? dim : 1; - - for (int i = 0; i < nqpt; i++) + // Do not run if the new Dof2Quad is already present, e.g. added in a + // previous call or added by another omp thread. + if (DofToQuad::SearchArray(dof2quad_array, ir, + DofToQuad::LEXICOGRAPHIC_FULL) == nullptr) { - for (int d = 0; d < b_dim; d++) + // Undo the native ordering which is what FiniteElement::GetDofToQuad + // returns. + auto *d2q_new = new DofToQuad(d2q); + d2q_new->mode = DofToQuad::LEXICOGRAPHIC_FULL; + const int nqpt = ir.GetNPoints(); + + const int b_dim = (range_type == VECTOR) ? dim : 1; + + for (int i = 0; i < nqpt; i++) { - for (int j = 0; j < dof; j++) + for (int d = 0; d < b_dim; d++) { - const double val = d2q.B[i + nqpt*(d+b_dim*lex_ordering[j])]; - d2q_new->B[i+nqpt*(d+b_dim*j)] = val; - d2q_new->Bt[j+dof*(i+nqpt*d)] = val; + for (int j = 0; j < dof; j++) + { + const double val = d2q.B[i + nqpt*(d+b_dim*lex_ordering[j])]; + d2q_new->B[i+nqpt*(d+b_dim*j)] = val; + d2q_new->Bt[j+dof*(i+nqpt*d)] = val; + } } } - } - const int g_dim = [this]() - { - switch (deriv_type) + const int g_dim = [this]() { - case GRAD: return dim; - case DIV: return 1; - case CURL: return cdim; - default: return 0; - } - }(); - - for (int i = 0; i < nqpt; i++) - { - for (int d = 0; d < g_dim; d++) - { - for (int j = 0; j < dof; j++) + switch (deriv_type) { - const double val = d2q.G[i + nqpt*(d+g_dim*lex_ordering[j])]; - d2q_new->G[i+nqpt*(d+g_dim*j)] = val; - d2q_new->Gt[j+dof*(i+nqpt*d)] = val; + case GRAD: return dim; + case DIV: return 1; + case CURL: return cdim; + default: return 0; + } + }(); + + for (int i = 0; i < nqpt; i++) + { + for (int d = 0; d < g_dim; d++) + { + for (int j = 0; j < dof; j++) + { + const double val = d2q.G[i + nqpt*(d+g_dim*lex_ordering[j])]; + d2q_new->G[i+nqpt*(d+g_dim*j)] = val; + d2q_new->Gt[j+dof*(i+nqpt*d)] = val; + } } } + + dof2quad_array.Append(d2q_new); } - dof2quad_array.Append(d2q_new); } } @@ -724,13 +727,7 @@ const DofToQuad &NodalFiniteElement::GetDofToQuad(const IntegrationRule &ir, #pragma omp critical (DofToQuad) #endif { - //Should make this loop a function of FiniteElement - for (int i = 0; i < dof2quad_array.Size(); i++) - { - d2q = dof2quad_array[i]; - if (d2q->IntRule == &ir && d2q->mode == mode) { break; } - d2q = nullptr; - } + d2q = DofToQuad::SearchArray(dof2quad_array, ir, mode); } if (d2q) { return *d2q; } if (mode != DofToQuad::LEXICOGRAPHIC_FULL) @@ -2708,15 +2705,7 @@ const DofToQuad &TensorBasisElement::GetTensorDofToQuad( #pragma omp critical (DofToQuad) #endif { - for (int i = 0; i < dof2quad_array.Size(); i++) - { - auto* d2q_ = dof2quad_array[i]; - if (d2q_->IntRule == &ir && d2q_->mode == mode) - { - d2q = d2q_; - break; - } - } + d2q = DofToQuad::SearchArray(dof2quad_array, ir, mode); if (!d2q) { d2q = new DofToQuad; diff --git a/fem/fe/fe_base.hpp b/fem/fe/fe_base.hpp index b5e8c3de02..f900e8e5f8 100644 --- a/fem/fe/fe_base.hpp +++ b/fem/fe/fe_base.hpp @@ -222,6 +222,12 @@ public: /// Returns absolute value of the maps DofToQuad Abs() const; + + /// Auxiliary function for searching DofToQuad arrays. + static inline DofToQuad *SearchArray( + const Array &dof2quad_array, + const IntegrationRule &ir, + DofToQuad::Mode mode); }; /// Describes the function space on each element @@ -433,6 +439,7 @@ public: /** Each row of the result DenseMatrix @a Hessian contains upper triangular part of the Hessian of one shape function. The order in 2D is {u_xx, u_xy, u_yy}. + The order in 3D is {u_xx, u_xy, u_xz, u_yy, u_yz, u_zz}. The size (#dof x (#dim (#dim+1)/2) of @a Hessian must be set in advance.*/ virtual void CalcHessian(const IntegrationPoint &ip, DenseMatrix &Hessian) const; @@ -1404,6 +1411,21 @@ public: void InvertLinearTrans(ElementTransformation &trans, const IntegrationPoint &pt, Vector &x); + +// static inline method +inline DofToQuad *DofToQuad::SearchArray( + const Array &dof2quad_array, + const IntegrationRule &ir, + DofToQuad::Mode mode) +{ + for (int i = 0; i < dof2quad_array.Size(); i++) + { + DofToQuad *d2q = dof2quad_array[i]; + if (d2q->IntRule == &ir && d2q->mode == mode) { return d2q; } + } + return nullptr; +} + } // namespace mfem #endif diff --git a/fem/fe/fe_fixed_order.cpp b/fem/fe/fe_fixed_order.cpp index 4f43f6262c..02b3c2781f 100644 --- a/fem/fe/fe_fixed_order.cpp +++ b/fem/fe/fe_fixed_order.cpp @@ -60,6 +60,12 @@ void Linear1DFiniteElement::CalcDShape(const IntegrationPoint &ip, dshape(1,0) = 1.; } +void Linear1DFiniteElement::CalcHessian(const IntegrationPoint &ip, + DenseMatrix &h) const +{ + h = 0.0; +} + Linear2DFiniteElement::Linear2DFiniteElement() : NodalFiniteElement(2, Geometry::TRIANGLE, 3, 1) { @@ -87,6 +93,11 @@ void Linear2DFiniteElement::CalcDShape(const IntegrationPoint &ip, dshape(2,0) = 0.; dshape(2,1) = 1.; } +void Linear2DFiniteElement::CalcHessian(const IntegrationPoint &ip, + DenseMatrix &h) const +{ + h = 0.0; +} BiLinear2DFiniteElement::BiLinear2DFiniteElement() : NodalFiniteElement(2, Geometry::SQUARE, 4, 1, FunctionSpace::Qk) @@ -1256,6 +1267,12 @@ void Linear3DFiniteElement::CalcDShape(const IntegrationPoint &ip, } } +void Linear3DFiniteElement::CalcHessian(const IntegrationPoint &ip, + DenseMatrix &h) const +{ + h = 0.0; +} + void Linear3DFiniteElement::GetFaceDofs (int face, int **dofs, int *ndofs) const { @@ -1632,6 +1649,37 @@ void TriLinear3DFiniteElement::CalcDShape(const IntegrationPoint &ip, dshape(7,2) = ox * y; } +void TriLinear3DFiniteElement::CalcHessian(const IntegrationPoint &ip, + DenseMatrix &h) const +{ + real_t x = ip.x, y = ip.y, z = ip.z; + real_t ox = 1.-x, oy = 1.-y, oz = 1.-z; + + h(0,0) = 0.; h(0,1) = oz; h(0,2) = oy; + h(0,3) = 0.; h(0,4) = ox; h(0,5) = 0.; + + h(1,0) = 0.; h(1,1) = -oz; h(1,2) = -oy; + h(1,3) = 0.; h(1,4) = x; h(1,5) = 0.; + + h(2,0) = 0.; h(2,1) = oz; h(2,2) = -y; + h(2,3) = 0.; h(2,4) = -x; h(2,5) = 0.; + + h(3,0) = 0.; h(3,1) = -oz; h(3,2) = y; + h(3,3) = 0.; h(3,4) = -ox; h(3,5) = 0.; + + h(4,0) = 0.; h(4,1) = z; h(4,2) = -oy; + h(4,3) = 0.; h(4,4) = -ox; h(4,5) = 0.; + + h(5,0) = 0.; h(5,1) = -z; h(5,2) = oy; + h(5,3) = 0.; h(5,4) = -x; h(5,5) = 0.; + + h(6,0) = 0.; h(6,1) = z; h(6,2) = y; + h(6,3) = 0.; h(6,4) = x; h(6,5) = 0.; + + h(7,0) = 0.; h(7,1) = -z; h(7,2) = -y; + h(7,3) = 0.; h(7,4) = ox; h(7,5) = 0.; +} + P0SegmentFiniteElement::P0SegmentFiniteElement(int Ord) : NodalFiniteElement(1, Geometry::SEGMENT, 1, Ord) // default Ord = 0 diff --git a/fem/fe/fe_fixed_order.hpp b/fem/fe/fe_fixed_order.hpp index 7a47896ca7..97e081740c 100644 --- a/fem/fe/fe_fixed_order.hpp +++ b/fem/fe/fe_fixed_order.hpp @@ -50,6 +50,8 @@ public: contains the derivative of one shape function */ void CalcDShape(const IntegrationPoint &ip, DenseMatrix &dshape) const override; + void CalcHessian(const IntegrationPoint &ip, + DenseMatrix &h) const override; }; /// A 2D linear element on triangle with nodes at the vertices of the triangle @@ -70,6 +72,8 @@ public: so that each row contains the derivatives of one shape function */ void CalcDShape(const IntegrationPoint &ip, DenseMatrix &dshape) const override; + void CalcHessian(const IntegrationPoint &ip, + DenseMatrix &h) const override; void ProjectDelta(int vertex, Vector &dofs) const override { dofs = 0.0; dofs(vertex) = 1.0; } }; @@ -404,6 +408,9 @@ public: void CalcDShape(const IntegrationPoint &ip, DenseMatrix &dshape) const override; + void CalcHessian(const IntegrationPoint &ip, + DenseMatrix &h) const override; + void ProjectDelta(int vertex, Vector &dofs) const override { dofs = 0.0; dofs(vertex) = 1.0; } @@ -445,7 +452,8 @@ public: so that each row contains the derivatives of one shape function */ void CalcDShape(const IntegrationPoint &ip, DenseMatrix &dshape) const override; - + void CalcHessian(const IntegrationPoint &ip, + DenseMatrix &h) const override; void ProjectDelta(int vertex, Vector &dofs) const override { dofs = 0.0; dofs(vertex) = 1.0; } }; diff --git a/fem/fe/fe_h1.cpp b/fem/fe/fe_h1.cpp index b164cb94f0..7380244500 100644 --- a/fem/fe/fe_h1.cpp +++ b/fem/fe/fe_h1.cpp @@ -589,7 +589,7 @@ void H1_TriangleElement::CalcHessian(const IntegrationPoint &ip, Vector shape_x(p + 1), shape_y(p + 1), shape_l(p + 1); Vector dshape_x(p + 1), dshape_y(p + 1), dshape_l(p + 1); Vector ddshape_x(p + 1), ddshape_y(p + 1), ddshape_l(p + 1); - DenseMatrix ddu(dof, dim); + DenseMatrix ddu(dof, (dim*(dim+1))/2); #endif poly1d.CalcBasis(p, ip.x, shape_x, dshape_x, ddshape_x); diff --git a/fem/fe/fe_nurbs.cpp b/fem/fe/fe_nurbs.cpp index af7f9c73e5..4bf5b9ea23 100644 --- a/fem/fe/fe_nurbs.cpp +++ b/fem/fe/fe_nurbs.cpp @@ -445,11 +445,10 @@ void NURBS3DFiniteElement::CalcHessian (const IntegrationPoint &ip, d2sum[0] += ( hessian(o,0) = d2sx*sy*sz*weights(o) ); d2sum[1] += ( hessian(o,1) = dsx*dsy*sz*weights(o) ); d2sum[2] += ( hessian(o,2) = dsx*sy*dsz*weights(o) ); + d2sum[3] += ( hessian(o,3) = sx*d2sy*sz*weights(o) ); + d2sum[4] += ( hessian(o,4) = sx*dsy*dsz*weights(o) ); + d2sum[5] += ( hessian(o,5) = sx*sy*d2sz*weights(o) ); - d2sum[3] += ( hessian(o,3) = sx*dsy*dsz*weights(o) ); - - d2sum[4] += ( hessian(o,4) = sx*sy*d2sz*weights(o) ); - d2sum[5] += ( hessian(o,5) = sx*d2sy*sz*weights(o) ); } } } diff --git a/fem/fespace.cpp b/fem/fespace.cpp index f342f25cbe..413e0df9b5 100644 --- a/fem/fespace.cpp +++ b/fem/fespace.cpp @@ -282,14 +282,7 @@ int FiniteElementSpace::DofToVDof(int dof, int vd, int ndofs_) const void FiniteElementSpace::AdjustVDofs(Array &vdofs) { int n = vdofs.Size(), *vdof = vdofs; - for (int i = 0; i < n; i++) - { - int j; - if ((j = vdof[i]) < 0) - { - vdof[i] = -1-j; - } - } + for (int i = 0; i < n; i++) { vdof[i] = UnsignIndex(vdof[i]); } } void FiniteElementSpace::GetElementVDofs(int i, Array &vdofs, @@ -483,13 +476,14 @@ void FiniteElementSpace::ReorderElementToDofTable() for (int k = 0, dof_counter = 0; k < nnz; k++) { const int sdof = J[k]; // signed dof - const int dof = (sdof < 0) ? -1-sdof : sdof; + const int dof = UnsignIndex(sdof); int new_dof = dof_marker[dof]; if (new_dof < 0) { dof_marker[dof] = new_dof = dof_counter++; } - J[k] = (sdof < 0) ? -1-new_dof : new_dof; // preserve the sign of sdof + // Preserve the sign of sdof + J[k] = (sdof < 0) ? FlipIndexSign(new_dof) : new_dof; } } @@ -547,7 +541,7 @@ void MarkDofs(const Array &dofs, Array &mark_array) { for (auto d : dofs) { - mark_array[d >= 0 ? d : -1 - d] = -1; + mark_array[UnsignIndex(d)] = -1; } } @@ -931,7 +925,7 @@ void FiniteElementSpace::AddDependencies( if (std::abs(coef) > 1e-12) { const int mdof = master_dofs[j]; - if (mdof != sdof && mdof != (-1-sdof)) + if (mdof != sdof && mdof != FlipIndexSign(sdof)) { deps.Add(sdof, mdof, coef); } @@ -1024,7 +1018,7 @@ int FiniteElementSpace::GetDegenerateFaceDofs(int index, Array &dofs, // FiniteElementSpace::AddDependencies. Array edof; - int order = GetEdgeDofs(-1 - index, edof, variant); + int order = GetEdgeDofs(FlipIndexSign(index), edof, variant); int nv = fec->DofForGeometry(Geometry::POINT); int ne = fec->DofForGeometry(Geometry::SEGMENT); @@ -1516,36 +1510,76 @@ const FaceRestriction *FiniteElementSpace::GetFaceRestriction( const bool is_dg_space = IsDGSpace(); const L2FaceValues m = (is_dg_space && mul==L2FaceValues::DoubleValued) ? L2FaceValues::DoubleValued : L2FaceValues::SingleValued; - key_face key = std::make_tuple(is_dg_space, f_ordering, type, m); + auto key = std::make_tuple(is_dg_space, f_ordering, type, m); auto itr = L2F.find(key); if (itr != L2F.end()) { - return itr->second; + return itr->second.get(); } else { - FaceRestriction *res; + std::unique_ptr res; if (is_dg_space) { if (Conforming()) { - res = new L2FaceRestriction(*this, f_ordering, type, m); + res.reset(new L2FaceRestriction(*this, f_ordering, type, m)); } else { - res = new NCL2FaceRestriction(*this, f_ordering, type, m); + res.reset(new NCL2FaceRestriction(*this, f_ordering, type, m)); } } else if (dynamic_cast(fec)) { - res = new L2InterfaceFaceRestriction(*this, f_ordering, type); + res.reset(new L2InterfaceFaceRestriction(*this, f_ordering, type)); } else { - res = new ConformingFaceRestriction(*this, f_ordering, type); + res.reset(new ConformingFaceRestriction(*this, f_ordering, type)); } - L2F[key] = res; - return res; + return L2F.emplace(key, std::move(res)).first->second.get(); + } +} + +const InterpolationManager &FiniteElementSpace::GetInterpolationManager( + ElementDofOrdering f_ordering, FaceType type) const +{ + const auto key = make_tuple(f_ordering, type); + + auto it = interpolations.find(key); + if (it != interpolations.end()) + { + return *it->second; + } + else + { + auto interp = make_unique(*this, f_ordering, type); + + int face_idx = 0; + for (int f = 0; f < mesh->GetNumFacesWithGhost(); ++f) + { + Mesh::FaceInformation face = mesh->GetFaceInformation(f); + if (!face.IsOfFaceType(type) || face.IsNonconformingCoarse()) + { + continue; + } + if (face.IsConforming() || face.IsBoundary()) + { + interp->RegisterFaceConformingInterpolation(face, face_idx); + } + else + { + interp->RegisterFaceCoarseToFineInterpolation(face, face_idx); + } + ++face_idx; + } + + // Transform the interpolation matrix map into contiguous memory. + interp->LinearizeInterpolatorMapIntoVector(); + interp->InitializeNCInterpConfig(); + + return *interpolations.emplace(key, std::move(interp)).first->second; } } @@ -1670,8 +1704,8 @@ SparseMatrix *FiniteElementSpace::RefinementMatrix_main( for (int i = 0; i < fine_ldof; i++) { - int r = DofToVDof(dofs[i], vd); - int m = (r >= 0) ? r : (-1 - r); + const int r = DofToVDof(dofs[i], vd); + const int m = UnsignIndex(r); if (!mark[m]) { @@ -1732,7 +1766,7 @@ SparseMatrix *FiniteElementSpace::VariableOrderRefinementMatrix( for (int i = 0; i < fine_ldof; i++) { const int r = DofToVDof(dofs[i], vd); - int m = (r >= 0) ? r : (-1 - r); + const int m = UnsignIndex(r); if (!mark[m]) { @@ -2442,8 +2476,8 @@ SparseMatrix* FiniteElementSpace::DerefinementMatrix(int old_ndofs, { if (!std::isfinite(lR(i, 0))) { continue; } - int r = DofToVDof(dofs[i], vd); - int m = (r >= 0) ? r : (-1 - r); + const int r = DofToVDof(dofs[i], vd); + const int m = UnsignIndex(r); if (is_dg || !mark[m]) { @@ -3161,7 +3195,7 @@ void FiniteElementSpace::CalcEdgeFaceVarOrders( else { // degenerate face (i.e., edge-face constraint) - slave_orders |= edge_orders[-1 - slave.index]; + slave_orders |= edge_orders[FlipIndexSign(slave.index)]; } } @@ -3969,11 +4003,8 @@ void FiniteElementSpace::Destroy() delete E2Q_array[i]; } E2Q_array.SetSize(0); - for (auto &x : L2F) - { - delete x.second; - } L2F.clear(); + interpolations.clear(); for (int i = 0; i < E2IFQ_array.Size(); i++) { delete E2IFQ_array[i]; diff --git a/fem/fespace.hpp b/fem/fespace.hpp index 19aa7bdc8a..c60e51a354 100644 --- a/fem/fespace.hpp +++ b/fem/fespace.hpp @@ -13,6 +13,7 @@ #define MFEM_FESPACE #include "../config/config.hpp" +#include "../general/hash_util.hpp" #include "../linalg/ordering.hpp" #include "../linalg/sparsemat.hpp" #include "../mesh/mesh.hpp" @@ -320,18 +321,11 @@ protected: mutable OperatorHandle L2E_nat, L2E_lex; /// The face restriction operators, see GetFaceRestriction(). using key_face = std::tuple; - struct key_hash - { - std::size_t operator()(const key_face& k) const - { - return std::get<0>(k) - + 2 * (int)std::get<1>(k) - + 4 * (int)std::get<2>(k) - + 8 * (int)std::get<3>(k); - } - }; - using map_L2F = std::unordered_map; - mutable map_L2F L2F; + mutable std::unordered_map, + TupleHasher> L2F; + + mutable std::unordered_map, + std::unique_ptr, TupleHasher> interpolations; mutable Array E2Q_array; mutable Array E2IFQ_array; @@ -751,6 +745,9 @@ public: ElementDofOrdering f_ordering, FaceType, L2FaceValues mul = L2FaceValues::DoubleValued) const; + const InterpolationManager &GetInterpolationManager( + ElementDofOrdering f_ordering, FaceType type) const; + /** @brief Return a QuadratureInterpolator that interpolates E-vectors to quadrature point values and/or derivatives (Q-vectors). */ /** An E-vector represents the element-wise discontinuous version of the FE @@ -1153,7 +1150,7 @@ public: /// Helper to return the DOF associated with a sign encoded DOF static inline int DecodeDof(int dof) - { return (dof >= 0) ? dof : (-1 - dof); } + { return UnsignIndex(dof); } /// Helper to determine the DOF and sign of a sign encoded DOF static inline int DecodeDof(int dof, real_t& sign) diff --git a/fem/gridfunc.cpp b/fem/gridfunc.cpp index a16cf4d0f9..54b9f0cf11 100644 --- a/fem/gridfunc.cpp +++ b/fem/gridfunc.cpp @@ -30,6 +30,7 @@ #include #include #include +#include namespace mfem { @@ -5117,6 +5118,103 @@ void GridFunction::GetElementBoundsAtControlPoints(const int elem, } } +void GridFunction::GetElementBoundsAtControlPoints(const int elem, + const PLBound &plb, + const Vector &ref_range, + const int vdim, + Vector &lower, Vector &upper, + Vector &control_pos) const +{ + const FiniteElement *fe = fes->GetFE(elem); + const IntegrationRule ir_in = fe->GetNodes(); + IntegrationRule ir_new(ir_in.GetNPoints()); + const int dim = fes->GetMesh()->Dimension(); + const L2_FECollection *l2fec = dynamic_cast + (fes->FEColl()); + + const TensorBasisElement *tbe = + dynamic_cast(fe); + MFEM_VERIFY(tbe != NULL, "TensorBasis FiniteElement expected."); + + const Array &dof_map = tbe->GetDofMap(); + bool lexico = (dof_map.Size() == 0); + bool bern = (tbe->GetBasisType() == BasisType::Positive); + bool h1 = (l2fec == nullptr); + + Vector loc_data; // gridfunction values + // Construct an integration rule to evaluate the gridfunction in + // subinterval. + for (int i = 0; i < ir_in.GetNPoints(); i++) + { + IntegrationPoint &ip_new = ir_new.IntPoint(i); + const IntegrationPoint &ip_old = + ir_in.IntPoint((lexico || bern) ? i : dof_map[i]); + Vector ip_coord(dim); + ip_old.Get(ip_coord.GetData(), dim); + for (int d = 0; d < dim; d++) + { + ip_coord(d) = ref_range(d) + + (ref_range(dim+d) - ref_range(d)) * ip_coord(d); + } + ip_new.Set(ip_coord.GetData(), dim); + } + GetValues(elem, ir_new, loc_data, vdim); + // At this point, the loc_data contains function values ordered + // lexicographically, unless we are using Bernstein bases. + // For Bernstein, we need to project and get coefficients first. + + // For bernstein, we get coefficients corresponding to these function values + if (bern) + { + int bt = 4; // BasisType::ClosedUniform + int o = fe->GetOrder(); + DenseMatrix projmat; + NodalTensorFiniteElement *ntfe = nullptr; + if (dim == 1) + { + if (h1) { ntfe = new H1_SegmentElement(o, bt); } + else { ntfe = new L2_SegmentElement(o, bt); } + } + else if (dim == 2) + { + if (h1) { ntfe = new H1_QuadrilateralElement(o, bt); } + else { ntfe = new L2_QuadrilateralElement(o, bt); } + } + else if (dim == 3) + { + if (h1) { ntfe = new H1_HexahedronElement(o, bt); } + else { ntfe = new L2_HexahedronElement(o, bt); } + } + // projection matrix from H1 to Positive + ElementTransformation *eltran = fes->GetElementTransformation(elem); + fe->Project(*ntfe, *eltran, projmat); + Vector loc_data_temp(loc_data.Size()); + projmat.Mult(loc_data, loc_data_temp); + for (int i = 0; i < dof_map.Size(); i++) + { + loc_data(i) = loc_data_temp(dof_map[i]); + } + if (dof_map.Size() == 0) { loc_data = loc_data_temp; } + delete ntfe; + } + + // Get bounds at control points + plb.GetNDBounds(dim, loc_data, lower, upper); + + // Save control point positions + int ncp = plb.GetNControlPoints(); + control_pos.SetSize(dim * ncp); + const Vector control_pos_1D = plb.GetControlPoints(); + for (int i = 0; i < ncp; i++) + { + for (int d = 0; d < dim; d++) + { + control_pos(i + d*ncp) = + ref_range(d) + (ref_range(dim+d)-ref_range(d))*control_pos_1D(i); + } + } +} + void GridFunction::GetElementBounds(const int elem, const PLBound &plb, Vector &lower, Vector &upper, const int vdim) const @@ -5197,6 +5295,467 @@ PLBound GridFunction::GetBounds(Vector &lower, Vector &upper, return plb; } +struct IntervalNode +{ + real_t val_min; + real_t val_max; + Array child; + IntervalNode(real_t vmin, real_t vmax) + : val_min(vmin), val_max(vmax) + { + child.SetSize(0); + } + void AddChild(IntervalNode *ch) { child.Append(ch); } + real_t GetChildMinLower() + { + if (child.Size() == 0) + { + return val_min; + } + real_t valmin = numeric_limits::max(); + for (int i = 0; i < child.Size(); i++) + { + real_t candidate = child[i]->GetChildMinLower(); + valmin = std::min(valmin, candidate); + } + return valmin; + } + real_t GetChildMinUpper() + { + if (child.Size() == 0) + { + return val_max; + } + real_t valmax = numeric_limits::max(); + for (int i = 0; i < child.Size(); i++) + { + real_t candidate = child[i]->GetChildMinUpper(); + valmax = std::min(valmax, candidate); + } + return valmax; + } + real_t GetChildMaxLower() + { + if (child.Size() == 0) + { + return val_min; + } + real_t valmin = numeric_limits::lowest(); + for (int i = 0; i < child.Size(); i++) + { + real_t candidate = child[i]->GetChildMaxLower(); + valmin = std::max(valmin, candidate); + } + return valmin; + } + real_t GetChildMaxUpper() + { + if (child.Size() == 0) + { + return val_max; + } + real_t valmax = numeric_limits::lowest(); + for (int i = 0; i < child.Size(); i++) + { + real_t candidate = child[i]->GetChildMaxUpper(); + valmax = std::max(valmax, candidate); + } + return valmax; + } + void DeleteChildren() + { + for (int i = 0; i < child.Size(); i++) + { + child[i]->DeleteChildren(); + delete child[i]; + } + child.SetSize(0); + } +}; + +struct SearchInterval +{ + Vector ref_range; + int depth; + IntervalNode *node; + SearchInterval(const Vector &ref_range_in, int d, IntervalNode *n) + : ref_range(ref_range_in), depth(d), node(n) + { } +}; + +struct IntervalCompareMin +{ + bool operator()(const SearchInterval *a, const SearchInterval *b) const + { + return a->node->val_min > b->node->val_min; + } +}; + +struct IntervalCompareMax +{ + bool operator()(const SearchInterval *a, const SearchInterval *b) const + { + return a->node->val_max < b->node->val_max; + } +}; + +std::pair GridFunction::EstimateFunctionMinimum( + const int elem, const PLBound &plb, const int vdim, + const int max_depth, const real_t tol) const +{ + real_t min_threshold = std::numeric_limits::max(); + return EstimateFunctionMinimum(elem, plb, vdim, max_depth, tol, + min_threshold); } +std::pair GridFunction::EstimateFunctionMinimum( + const int elem, const PLBound &plb, const int vdim, + const int max_depth, const real_t tol, real_t &min_threshold) const +{ + const int dim = this->FESpace()->GetMesh()->Dimension(); + const int ncp = plb.GetNControlPoints(); + Vector pos_range(2*dim); pos_range = 0.0; + for (int d = 0; d < dim; d++) { pos_range(d+dim) = 1.0; } + Vector lower, upper, cp_ref_loc; + GetElementBoundsAtControlPoints(elem, plb, lower, upper, vdim); + real_t val_min = lower.Min(); + real_t val_max = upper.Min(); + + min_threshold = std::min(min_threshold, val_max); + + // Pruning: if the element's lower bound is greater than the current global + // upper bound, this element cannot contain the global minimum. + if (val_min >= min_threshold) + { + return std::make_pair(val_min, val_max); + } + + if (val_min == val_max || max_depth == 0) + { + min_threshold = std::min(min_threshold, val_min); + return std::make_pair(val_min, val_max); + } + real_t abs_tol = tol*(val_max-val_min); + + IntervalNode *initial_node = new IntervalNode(val_min, val_max); + SearchInterval *initial_interval = new SearchInterval(pos_range, 0, + initial_node); + + std::priority_queue, IntervalCompareMin> pq; + pq.push(initial_interval); + + real_t min_upper_bound = upper.Min(); + real_t min_lower_bound = lower.Min(); + + while (!pq.empty()) + { + SearchInterval *current = pq.top(); + pq.pop(); + int curr_depth = current->depth; + + // Reached max depth or this interval cannot contain the global minimum + if (current->node->val_min >= min_threshold || curr_depth >= max_depth) + { + delete current; + continue; + } + + min_lower_bound = initial_node->GetChildMinLower(); + if (min_upper_bound - min_lower_bound < abs_tol) + { + delete current; + break; + } + + // Subdivide the interval and get bounds on it + GetElementBoundsAtControlPoints(elem, plb, current->ref_range, + vdim, lower, upper, cp_ref_loc); + + // process the bounds and create sub-intervals + for (int k = 0; k < (dim == 3 ? ncp-1 : 1); k++) + { + for (int j = 0; j < (dim >= 2 ? ncp-1 : 1); j++) + { + for (int i = 0; i < ncp-1; i++) + { + real_t lv = 0.0, uv = 0.0; + if (dim == 1) + { + lv = std::min(lower(i), lower(i+1)); + uv = std::min(upper(i), upper(i+1)); + } + else if (dim == 2) + { + lv = std::min({lower(i + j*ncp), lower((i+1) + j*ncp), + lower(i + (j+1)*ncp), + lower((i+1) + (j+1)*ncp)}); + uv = std::min({upper(i + j*ncp), upper((i+1) + j*ncp), + upper(i + (j+1)*ncp), + upper((i+1) + (j+1)*ncp)}); + } + else if (dim == 3) + { + lv = std::min({lower(i + j*ncp + k*ncp*ncp), + lower((i+1) + j*ncp + k*ncp*ncp), + lower(i + (j+1)*ncp + k*ncp*ncp), + lower((i+1) + (j+1)*ncp + k*ncp*ncp), + lower(i + j*ncp + (k+1)*ncp*ncp), + lower((i+1) + j*ncp + (k+1)*ncp*ncp), + lower(i + (j+1)*ncp + (k+1)*ncp*ncp), + lower((i+1) + (j+1)*ncp + (k+1)*ncp*ncp)}); + uv = std::min({upper(i + j*ncp + k*ncp*ncp), + upper((i+1) + j*ncp + k*ncp*ncp), + upper(i + (j+1)*ncp + k*ncp*ncp), + upper((i+1) + (j+1)*ncp + k*ncp*ncp), + upper(i + j*ncp + (k+1)*ncp*ncp), + upper((i+1) + j*ncp + (k+1)*ncp*ncp), + upper(i + (j+1)*ncp + (k+1)*ncp*ncp), + upper((i+1) + (j+1)*ncp + (k+1)*ncp*ncp)}); + } + IntervalNode *child_node = new IntervalNode(lv, uv); + current->node->AddChild(child_node); + + if (lv < min_threshold) + { + min_upper_bound = std::min(min_upper_bound, uv); + min_threshold = std::min(min_threshold, uv); + if (curr_depth < max_depth) + { + pos_range(0) = cp_ref_loc(i); + pos_range(0+dim) = cp_ref_loc(i+1); + if (dim >= 2) + { + pos_range(1) = cp_ref_loc(ncp + j); + pos_range(1+dim) = cp_ref_loc(ncp + j+1); + } + if (dim == 3) + { + pos_range(2) = cp_ref_loc(2*ncp + k); + pos_range(2+dim) = cp_ref_loc(2*ncp + k+1); + } + SearchInterval *child_interval = + new SearchInterval(pos_range, curr_depth + 1, + child_node); + pq.push(child_interval); + } + } + } + } + } + delete current; + } + + // clean up remaining intervals in queue + while (!pq.empty()) + { + delete pq.top(); + pq.pop(); + } + + min_lower_bound = initial_node->GetChildMinLower(); + initial_node->DeleteChildren(); + delete initial_node; + + min_threshold = std::min(min_threshold, min_lower_bound); + return std::make_pair(min_lower_bound, min_upper_bound); +} + +std::pair GridFunction::EstimateFunctionMaximum( + const int elem, const PLBound &plb, const int vdim, + const int max_depth, const real_t tol) const +{ + real_t max_threshold = std::numeric_limits::lowest(); + return EstimateFunctionMaximum(elem, plb, vdim, max_depth, tol, + max_threshold); +} + +std::pair GridFunction::EstimateFunctionMaximum( + const int elem, const PLBound &plb, const int vdim, + const int max_depth, const real_t tol, real_t &max_threshold) const +{ + const int dim = this->FESpace()->GetMesh()->Dimension(); + const int ncp = plb.GetNControlPoints(); + Vector pos_range(2*dim); pos_range = 0.0; + for (int d = 0; d < dim; d++) { pos_range(d+dim) = 1.0; } + Vector lower, upper, cp_ref_loc; + + GetElementBoundsAtControlPoints(elem, plb, lower, upper, vdim); + real_t val_min = lower.Max(); + real_t val_max = upper.Max(); + + max_threshold = std::max(max_threshold, val_min); + + // Pruning: if the element's upper bound is less than the current global + // lower bound, this element cannot contain the global maximum. + if (val_max <= max_threshold) + { + return std::make_pair(val_min, val_max); + } + + if (val_min == val_max || max_depth == 0) + { + max_threshold = std::max(max_threshold, val_max); + return std::make_pair(val_min, val_max); + } + real_t abs_tol = tol*(val_max-val_min); + + IntervalNode *initial_node = new IntervalNode(val_min, val_max); + SearchInterval *initial_interval = new SearchInterval(pos_range, 0, + initial_node); + + std::priority_queue, IntervalCompareMax> pq; + pq.push(initial_interval); + + real_t max_lower_bound = val_min; + real_t max_upper_bound = val_max; + + while (!pq.empty()) + { + SearchInterval *current = pq.top(); + pq.pop(); + int curr_depth = current->depth; + + // Reached max depth or this interval cannot contain the global maximum. + if (current->node->val_max <= max_threshold || curr_depth >= max_depth) + { + delete current; + continue; + } + + max_upper_bound = initial_node->GetChildMaxUpper(); + if (max_upper_bound - max_lower_bound < abs_tol) + { + delete current; + break; + } + + // Subdivide the interval and get bounds on it + GetElementBoundsAtControlPoints(elem, plb, current->ref_range, + vdim, lower, upper, cp_ref_loc); + + // process the bounds and create sub-intervals + for (int k = 0; k < (dim == 3 ? ncp-1 : 1); k++) + { + for (int j = 0; j < (dim >= 2 ? ncp-1 : 1); j++) + { + for (int i = 0; i < ncp-1; i++) + { + real_t lv = 0.0, uv = 0.0; + if (dim == 1) + { + lv = std::max(lower(i), lower(i+1)); + uv = std::max(upper(i), upper(i+1)); + } + else if (dim == 2) + { + lv = std::max({lower(i + j*ncp), lower((i+1) + j*ncp), + lower(i + (j+1)*ncp), + lower((i+1) + (j+1)*ncp)}); + uv = std::max({upper(i + j*ncp), upper((i+1) + j*ncp), + upper(i + (j+1)*ncp), + upper((i+1) + (j+1)*ncp)}); + } + else if (dim == 3) + { + lv = std::max({lower(i + j*ncp + k*ncp*ncp), + lower((i+1) + j*ncp + k*ncp*ncp), + lower(i + (j+1)*ncp + k*ncp*ncp), + lower((i+1) + (j+1)*ncp + k*ncp*ncp), + lower(i + j*ncp + (k+1)*ncp*ncp), + lower((i+1) + j*ncp + (k+1)*ncp*ncp), + lower(i + (j+1)*ncp + (k+1)*ncp*ncp), + lower((i+1) + (j+1)*ncp + (k+1)*ncp*ncp)}); + uv = std::max({upper(i + j*ncp + k*ncp*ncp), + upper((i+1) + j*ncp + k*ncp*ncp), + upper(i + (j+1)*ncp + k*ncp*ncp), + upper((i+1) + (j+1)*ncp + k*ncp*ncp), + upper(i + j*ncp + (k+1)*ncp*ncp), + upper((i+1) + j*ncp + (k+1)*ncp*ncp), + upper(i + (j+1)*ncp + (k+1)*ncp*ncp), + upper((i+1) + (j+1)*ncp + (k+1)*ncp*ncp)}); + } + IntervalNode *child_node = new IntervalNode(lv, uv); + current->node->AddChild(child_node); + + if (uv > max_threshold) + { + max_lower_bound = std::max(max_lower_bound, lv); + max_threshold = std::max(max_threshold, lv); + if (curr_depth < max_depth) + { + pos_range(0) = cp_ref_loc(i); + pos_range(0+dim) = cp_ref_loc(i+1); + if (dim >= 2) + { + pos_range(1) = cp_ref_loc(ncp + j); + pos_range(1+dim) = cp_ref_loc(ncp + j+1); + } + if (dim == 3) + { + pos_range(2) = cp_ref_loc(2*ncp + k); + pos_range(2+dim) = cp_ref_loc(2*ncp + k+1); + } + SearchInterval *child_interval = + new SearchInterval(pos_range, curr_depth + 1, + child_node); + pq.push(child_interval); + } + } + } + } + } + delete current; + } + // clean up remaining intervals in queue + while (!pq.empty()) + { + delete pq.top(); + pq.pop(); + } + + max_upper_bound = initial_node->GetChildMaxUpper(); + initial_node->DeleteChildren(); + delete initial_node; + max_threshold = std::max(max_threshold, max_upper_bound); + + return std::make_pair(max_lower_bound, max_upper_bound); +} + +std::pair GridFunction::EstimateFunctionMinimum( + const int vdim, const PLBound &plb, const int max_depth, + const real_t tol) const +{ + real_t global_min_lower = std::numeric_limits::max(); + real_t global_min_upper = std::numeric_limits::max(); + + for (int i = 0; i < fes->GetNE(); i++) + { + std::pair min_pair = + EstimateFunctionMinimum(i, plb, vdim, max_depth, tol, + global_min_lower); + global_min_upper = std::min(global_min_upper, min_pair.second); + } + return std::make_pair(global_min_lower, global_min_upper); +} + +std::pair GridFunction::EstimateFunctionMaximum( + const int vdim, const PLBound &plb, const int max_depth, + const real_t tol) const +{ + real_t global_max_lower = std::numeric_limits::lowest(); + real_t global_max_upper = std::numeric_limits::lowest(); + + for (int i = 0; i < fes->GetNE(); i++) + { + std::pair max_pair = + EstimateFunctionMaximum(i, plb, vdim, max_depth, tol, + global_max_upper); + global_max_lower = std::max(global_max_lower, max_pair.first); + } + return std::make_pair(global_max_lower, global_max_upper); +} + +} \ No newline at end of file diff --git a/fem/gridfunc.hpp b/fem/gridfunc.hpp index 2814e97912..5f72bc90bd 100644 --- a/fem/gridfunc.hpp +++ b/fem/gridfunc.hpp @@ -564,6 +564,70 @@ protected: /// P-refinement version of Update(). void UpdatePRef(); + /** @brief Estimate the minimum value of the GridFunction in element @a elem + * if it is below a certain @a min_threshold. + * + * @details For a given element \p elem and grid function component \p vdim + * an estimate of the function minimum is the minimum of the piecewise + * linear lower bound obtained using the given PLBound object. The actual + * minimum is between [minimum lower bound, minimum upper bound]. We + * improve the estimate of the function minimum by recursively + * subdividing the interval with the lowest lower bound, and computing + * bounds on the sub-intervals. + * This process continues until (i) the maximum recursion depth is reached + * or (ii) the difference between the minimum upper bound and minimum lower + * bound is less than a certain tolerance (\p tol * [initial maximum + * upper bound - initial minimum lower bound]). + * The function also terminates if the lowest minima estimate is found + * to be above the given threshold \p min_threshold. This is useful when + * we are interested in computing the global minimum of the function + * over all elements. In this case we can reject elements where the lowest + * bound is above the current global minimum. In case the function + * minimum on the element is below the global minimum, we update + * \p min_threshold. + * + * We return a pair of values that bracket the actual minimum, i.e. + * [min_lower_bound, min_upper_bound]. + */ + std::pair EstimateFunctionMinimum(const int elem, + const PLBound &plb, + const int vdim, + const int max_depth, + const real_t tol, + real_t &min_threshold)const; + + /** @brief Estimate the maximum value of the GridFunction in element @a elem + * if it is below a certain @a max_threshold. + * + * @details For a given element \p elem and grid function component \p vdim + * an estimate of the function maximum is the maximum of the piecewise + * linear upper bound obtained using the given PLBound object. The actual + * maximum is between [maximum lower bound, maximum upper bound]. We + * improve the estimate of the function maximum by recursively + * subdividing the interval with the highest upper bound, and computing + * bounds on the sub-intervals. + * This process continues until (i) the maximum recursion depth is reached + * or (ii) the difference between the maximum upper bound and maximum lower + * bound is less than a certain tolerance (\p tol * [initial maximum + * upper bound - initial maximum lower bound]). + * The function also terminates if the highest maxima estimate is found + * to be below the given threshold \p max_threshold. This is useful when + * we are interested in computing the global maximum of the function + * over all elements. In this case we can reject elements where the upper + * bound is below the current global maximum. In case the function + * maximum on the element is above the global maximum, we update + * \p max_threshold. + * + * We return a pair of values that bracket the actual maximum, i.e. + * [max_lower_bound, max_upper_bound]. + */ + std::pair EstimateFunctionMaximum(const int elem, + const PLBound &plb, + const int vdim, + const int max_depth, + const real_t tol, + real_t &max_threshold)const; + public: /** @brief For each vdof, counts how many elements contain the vdof, as containment is determined by FiniteElementSpace::GetElementVDofs(). */ @@ -1662,21 +1726,21 @@ public: */ ///@{ /// Computes the \ref PLBound for the gridfunction with number of control - /// points based on @a ref_factor, and returns the overall bounds for each - /// vdim (across all elements) in @b lower and @b upper. We also return the + /// points based on \p ref_factor, and returns the overall bounds for each + /// vdim (across all elements) in \p lower and \p upper. We also return the /// PLBound object used to compute the bounds. - /// We compute the bounds for each vdim if @a vdim < 1. + /// We compute the bounds for each vdim if \p vdim < 1. /// Note: For most cases, this method/interface will be sufficient. virtual PLBound GetBounds(Vector &lower, Vector &upper, const int ref_factor=1, const int vdim=-1) const; /// Computes the \ref PLBound for the gridfunction with number of control - /// points based on @a ref_factor, and returns the bounds for each element - /// ordered byVDim: + /// points based on \p ref_factor, and returns the bounds for each element + /// ordered byNodes: /// lower_{0,0}, lower_{1,0}, ..., lower_{ne-1,0}, /// lower_{0,1}, ..., lower_{ne-1,vdim-1}. We also return the /// PLBound object used to compute the bounds. - /// We compute the bounds for each vdim if @a vdim < 1. + /// We compute the bounds for each vdim if \p vdim < 1. PLBound GetElementBounds(Vector &lower, Vector &upper, const int ref_factor=1, const int vdim=-1) const; @@ -1687,6 +1751,18 @@ public: Vector &lower, Vector &upper, const int vdim = -1) const; + /** @brief Gets the bounds on given reference range inside an element. + * + * @details @a ref_range is a vector of size 2*dim that specifies the + * lower and upper limits in each dimension of the reference element. + * For example, in 2D, ref_range = [rmin, smin, rmax, smax]. + */ + void GetElementBoundsAtControlPoints(const int elem, const PLBound &plb, + const Vector &ref_range, + const int vdim, + Vector &lower, Vector &upper, + Vector &control_pos) const; + /// Compute bounds on the grid function for the given element. /// The bounds are stored in @b lower and @b upper. void GetElementBounds(const int elem, const PLBound &plb, @@ -1694,11 +1770,45 @@ public: const int vdim = -1) const; /// Compute bounds on the grid function for all the elements. The bounds - /// are returned in @b lower and @b upper, ordered byVDim: + /// are returned in @b lower and @b upper, ordered byNodes: /// lower_{0,0}, lower_{1,0}, ..., lower_{ne-1,0}, /// lower_{0,1}, ..., lower_{ne-1,vdim-1} void GetElementBounds(const PLBound &plb, Vector &lower, Vector &upper, const int vdim=-1) const; + + /** @brief Estimate the minimum value of the GridFunction in element @a elem. + * + * @details See the protected version of EstimateFunctionMinimum for + * details. + */ + std::pair EstimateFunctionMinimum(const int elem, + const PLBound &plb, + const int vdim, + const int max_depth, + const real_t tol) const; + + /** @brief Estimate the minimum value of the GridFunction in element @a elem. + * + * @details See the protected version of EstimateFunctionMaximum for + * details. + */ + std::pair EstimateFunctionMaximum(const int elem, + const PLBound &plb, + const int vdim, + const int max_depth, + const real_t tol) const; + + /** @brief Estimate the GridFunction minimum across all elements. */ + virtual std::pair EstimateFunctionMinimum(const int vdim, + const PLBound &plb, + const int max_depth, + const real_t tol) const; + + /** @brief Estimate the GridFunction maximum across all elements. */ + virtual std::pair EstimateFunctionMaximum(const int vdim, + const PLBound &plb, + const int max_depth, + const real_t tol) const; ///@} /// Destroys grid function. diff --git a/fem/gslib.cpp b/fem/gslib.cpp index a4ee5ff4a4..176a356fbe 100644 --- a/fem/gslib.cpp +++ b/fem/gslib.cpp @@ -234,7 +234,7 @@ void FindPointsGSLIB::Setup(Mesh &m, const double bb_t, const double newt_tol, } void FindPointsGSLIB::FindPoints(const Vector &point_pos, - int point_pos_ordering) + const int point_pos_ordering) { MFEM_VERIFY(setupflag, "Use FindPointsGSLIB::Setup before finding points."); bool dev_mode = (point_pos.UseDevice() && Device::IsEnabled()); @@ -482,7 +482,7 @@ void FindPointsGSLIB::SetupDevice() } void FindPointsGSLIB::FindPointsOnDevice(const Vector &point_pos, - int point_pos_ordering) + const int point_pos_ordering) { if (!DEV.setup_device) { @@ -505,13 +505,13 @@ void FindPointsGSLIB::FindPointsOnDevice(const Vector &point_pos, if (dim == 2) { - FindPointsLocal2(point_pos, point_pos_ordering, gsl_code, gsl_elem, gsl_ref, - gsl_dist, points_cnt); + FindPointsLocal2(point_pos, point_pos_ordering, gsl_code, gsl_elem, + gsl_ref, gsl_dist, points_cnt); } else { - FindPointsLocal3(point_pos, point_pos_ordering, gsl_code, gsl_elem, gsl_ref, - gsl_dist, points_cnt); + FindPointsLocal3(point_pos, point_pos_ordering, gsl_code, gsl_elem, + gsl_ref, gsl_dist, points_cnt); } // Sync from device to host @@ -1085,7 +1085,7 @@ void FindPointsGSLIB::InterpolateOnDevice(const Vector &field_in_evec, #else void FindPointsGSLIB::SetupDevice() {}; void FindPointsGSLIB::FindPointsOnDevice(const Vector &point_pos, - int point_pos_ordering) {}; + const int point_pos_ordering) {}; void FindPointsGSLIB::InterpolateOnDevice(const Vector &field_in_evec, Vector &field_out, const int nel, const int ncomp, @@ -1094,7 +1094,8 @@ void FindPointsGSLIB::InterpolateOnDevice(const Vector &field_in_evec, #endif void FindPointsGSLIB::FindPoints(Mesh &m, const Vector &point_pos, - int point_pos_ordering, const double bb_t, + const int point_pos_ordering, + const double bb_t, const double newt_tol, const int npt_max) { if (!setupflag || (mesh != &m) ) @@ -1105,16 +1106,28 @@ void FindPointsGSLIB::FindPoints(Mesh &m, const Vector &point_pos, } void FindPointsGSLIB::Interpolate(const Vector &point_pos, - const GridFunction &field_in, Vector &field_out, - int point_pos_ordering) + const GridFunction &field_in, + Vector &field_out, + const int point_pos_ordering) { FindPoints(point_pos, point_pos_ordering); Interpolate(field_in, field_out); } +void FindPointsGSLIB::Interpolate(const Vector &point_pos, + const GridFunction &field_in, + Vector &field_out, + const int point_pos_ordering, + const int field_out_ordering) +{ + FindPoints(point_pos, point_pos_ordering); + Interpolate(field_in, field_out, field_out_ordering); +} + void FindPointsGSLIB::Interpolate(Mesh &m, const Vector &point_pos, - const GridFunction &field_in, Vector &field_out, - int point_pos_ordering) + const GridFunction &field_in, + Vector &field_out, + const int point_pos_ordering) { FindPoints(m, point_pos, point_pos_ordering); Interpolate(field_in, field_out); @@ -1470,7 +1483,7 @@ void FindPointsGSLIB::SetupSplitMeshesAndIntegrationRules(const int order) } void FindPointsGSLIB::GetNodalValues(const GridFunction *gf_in, - Vector &node_vals) + Vector &node_vals) const { const GridFunction *nodes = gf_in; const FiniteElementSpace *fes = nodes->FESpace(); @@ -1758,6 +1771,13 @@ void FindPointsGSLIB::MapRefPosAndElemIndices() void FindPointsGSLIB::Interpolate(const GridFunction &field_in, Vector &field_out) +{ + Interpolate(field_in, field_out, field_in.FESpace()->GetOrdering()); +} + +void FindPointsGSLIB::Interpolate(const GridFunction &field_in, + Vector &field_out, + const int field_out_ordering) { const int gf_order = field_in.FESpace()->GetMaxElementOrder(), mesh_order = mesh->GetNodalFESpace()->GetMaxElementOrder(); @@ -1800,7 +1820,7 @@ void FindPointsGSLIB::Interpolate(const GridFunction &field_in, const int maxOrder = field_in.FESpace()->GetMaxElementOrder(); InterpolateOnDevice(node_vals, field_out, NE_split_total, ncomp, - maxOrder+1, field_in.FESpace()->GetOrdering()); + maxOrder+1, field_out_ordering); return; #endif } @@ -1812,12 +1832,13 @@ void FindPointsGSLIB::Interpolate(const GridFunction &field_in, field_in.FESpace()->IsVariableOrder() == mesh->GetNodalFESpace()->IsVariableOrder()) { - InterpolateH1(field_in, field_out); + InterpolateH1(field_in, field_out, field_out_ordering); return; } else { - InterpolateGeneral(field_in, field_out); + InterpolateGeneral(field_in, field_out, + field_out_ordering); if (!fec_l2 || avgtype == AvgType::NONE) { return; } } @@ -1861,11 +1882,11 @@ void FindPointsGSLIB::Interpolate(const GridFunction &field_in, if (gf_order_h1 == mesh_order) // basis is GaussLobatto by default { - InterpolateH1(field_in_h1, field_out_l2); + InterpolateH1(field_in_h1, field_out_l2, field_out_ordering); } else { - InterpolateGeneral(field_in_h1, field_out_l2); + InterpolateGeneral(field_in_h1, field_out_l2, field_out_ordering); } // Copy interpolated values for the points on element border @@ -1873,7 +1894,7 @@ void FindPointsGSLIB::Interpolate(const GridFunction &field_in, { for (int i = 0; i < indl2.Size(); i++) { - int idx = field_in_h1.FESpace()->GetOrdering() == Ordering::byNODES? + int idx = field_out_ordering == Ordering::byNODES? indl2[i] + j*points_cnt: indl2[i]*ncomp + j; field_out(idx) = field_out_l2(idx); @@ -1883,7 +1904,8 @@ void FindPointsGSLIB::Interpolate(const GridFunction &field_in, } void FindPointsGSLIB::InterpolateH1(const GridFunction &field_in, - Vector &field_out) + Vector &field_out, + const int field_out_ordering) { FiniteElementSpace ind_fes(mesh, field_in.FESpace()->FEColl()); if (field_in.FESpace()->IsVariableOrder()) @@ -1913,7 +1935,8 @@ void FindPointsGSLIB::InterpolateH1(const GridFunction &field_in, dataptrout = i*points_cnt; if (field_in.FESpace()->GetOrdering() == Ordering::byNODES) { - field_in_scalar.NewDataAndSize(field_in.GetData()+dataptrin, points_fld); + field_in_scalar.NewDataAndSize(field_in.GetData()+dataptrin, + points_fld); } else { @@ -1945,7 +1968,7 @@ void FindPointsGSLIB::InterpolateH1(const GridFunction &field_in, (gslib::findpts_data_3 *)this->fdataD); } } - if (field_in.FESpace()->GetOrdering() == Ordering::byVDIM) + if (field_out_ordering == Ordering::byVDIM) { Vector field_out_temp = field_out; for (int i = 0; i < ncomp; i++) @@ -1959,7 +1982,8 @@ void FindPointsGSLIB::InterpolateH1(const GridFunction &field_in, } void FindPointsGSLIB::InterpolateGeneral(const GridFunction &field_in, - Vector &field_out) + Vector &field_out, + const int field_out_ordering) { int ncomp = field_in.VectorDim(), nptorig = points_cnt, @@ -1979,7 +2003,7 @@ void FindPointsGSLIB::InterpolateGeneral(const GridFunction &field_in, if (dim == 3) { ip.z = gsl_mfem_ref(index*dim + 2); } Vector localval(ncomp); field_in.GetVectorValue(gsl_mfem_elem[index], ip, localval); - if (field_in.FESpace()->GetOrdering() == Ordering::byNODES) + if (field_out_ordering == Ordering::byNODES) { for (int i = 0; i < ncomp; i++) { @@ -2014,7 +2038,10 @@ void FindPointsGSLIB::InterpolateGeneral(const GridFunction &field_in, for (int index = 0; index < npt; index++) { if (gsl_code[index] == 2) { continue; } - for (int d = 0; d < dim; ++d) { pt->r[d]= gsl_mfem_ref(index*dim + d); } + for (int d = 0; d < dim; ++d) + { + pt->r[d]= gsl_mfem_ref(index*dim + d); + } pt->index = index; pt->proc = gsl_proc[index]; pt->el = gsl_mfem_elem[index]; @@ -2104,7 +2131,7 @@ void FindPointsGSLIB::InterpolateGeneral(const GridFunction &field_in, sdpt = (struct send_pt *)sendpt->ptr; for (int index = 0; index < static_cast(sendpt->n); index++) { - int idx = field_in.FESpace()->GetOrdering() == Ordering::byNODES ? + int idx = field_out_ordering == Ordering::byNODES ? sdpt->index + j*nptorig : sdpt->index*ncomp + j; field_out(idx) = sdpt->ival; @@ -2246,7 +2273,7 @@ void FindPointsGSLIB::DistributeInterpolatedValues(const Vector &int_vals, } } -void FindPointsGSLIB::GetAxisAlignedBoundingBoxes(Vector &aabb) +void FindPointsGSLIB::GetAxisAlignedBoundingBoxes(Vector &aabb) const { MFEM_VERIFY(setupflag, "Call FindPointsGSLIB::Setup method first"); auto *findptsData3 = (gslib::findpts_data_3 *)this->fdataD; @@ -2317,7 +2344,7 @@ void FindPointsGSLIB::GetAxisAlignedBoundingBoxes(Vector &aabb) } void FindPointsGSLIB::GetOrientedBoundingBoxes(DenseTensor &obbA, Vector &obbC, - Vector &obbV) + Vector &obbV) const { MFEM_VERIFY(setupflag, "Call FindPointsGSLIB::Setup method first"); auto *findptsData3 = (gslib::findpts_data_3 *)this->fdataD; @@ -2502,8 +2529,8 @@ void OversetFindPointsGSLIB::Setup(Mesh &m, const int meshid, } void OversetFindPointsGSLIB::FindPoints(const Vector &point_pos, - Array &point_id, - int point_pos_ordering) + const Array &point_id, + const int point_pos_ordering) { MFEM_VERIFY(setupflag, "Use OversetFindPointsGSLIB::Setup before " "finding points."); @@ -2582,10 +2609,10 @@ void OversetFindPointsGSLIB::FindPoints(const Vector &point_pos, } void OversetFindPointsGSLIB::Interpolate(const Vector &point_pos, - Array &point_id, + const Array &point_id, const GridFunction &field_in, Vector &field_out, - int point_pos_ordering) + const int point_pos_ordering) { FindPoints(point_pos, point_id, point_pos_ordering); Interpolate(field_in, field_out); diff --git a/fem/gslib.hpp b/fem/gslib.hpp index 03db29822c..531791e2a5 100644 --- a/fem/gslib.hpp +++ b/fem/gslib.hpp @@ -119,11 +119,13 @@ protected: } DEV; /// Use GSLIB for communication and interpolation - virtual void InterpolateH1(const GridFunction &field_in, Vector &field_out); + virtual void InterpolateH1(const GridFunction &field_in, Vector &field_out, + const int field_out_ordering); /// Uses GSLIB Crystal Router for communication followed by MFEM's /// interpolation functions virtual void InterpolateGeneral(const GridFunction &field_in, - Vector &field_out); + Vector &field_out, + const int field_out_ordering); /// Since GSLIB is designed to work with quads/hexes, we split every /// triangle/tet/prism/pyramid element into quads/hexes. @@ -140,7 +142,7 @@ protected: virtual void SetupSplitMeshesAndIntegrationRules(const int order); /// Get GridFunction value at the points expected by GSLIB. - virtual void GetNodalValues(const GridFunction *gf_in, Vector &node_vals); + virtual void GetNodalValues(const GridFunction *gf_in, Vector &node_vals) const; /// Map {r,s,t} coordinates from [-1,1] to [0,1] for MFEM. For simplices, /// find the original element number (that was split into micro quads/hexes) @@ -182,7 +184,7 @@ protected: These positions can be ordered byNodes: (XXX...,YYY...,ZZZ) or byVDim: (XYZ,XYZ,....XYZ) specified by @a point_pos_ordering. */ void FindPointsOnDevice(const Vector &point_pos, - int point_pos_ordering = Ordering::byNODES); + const int point_pos_ordering = Ordering::byNODES); /** Interpolation of field values at prescribed reference space positions. @param[in] field_in_evec E-vector of grid function to be interpolated. @@ -253,10 +255,15 @@ public: #gsl_dist Distance between the sought and the found point in physical space. */ void FindPoints(const Vector &point_pos, - int point_pos_ordering = Ordering::byNODES); + const int point_pos_ordering = Ordering::byNODES); + /// Convenience function when point positions are in a ParticleVector + void FindPoints(const ParticleVector &point_pos) + { + FindPoints(point_pos, point_pos.GetOrdering()); + } /// Setup FindPoints and search positions void FindPoints(Mesh &m, const Vector &point_pos, - int point_pos_ordering = Ordering::byNODES, + const int point_pos_ordering = Ordering::byNODES, const double bb_t = 0.1, const double newt_tol = 1.0e-12, const int npt_max = 256); @@ -266,20 +273,28 @@ public: \p field_in is in H1 and in the same space as the mesh that was given to Setup(). @param[out] field_out Interpolated values. For points that are not found - the value is set to #default_interp_value. */ + the value is set to #default_interp_value. + The output ordering is determined from field_in.*/ virtual void Interpolate(const GridFunction &field_in, Vector &field_out); + /// Interpolation of field values, with output ordering specification. + virtual void Interpolate(const GridFunction &field_in, Vector &field_out, + const int field_out_ordering); /** Search positions and interpolate. The ordering (byNODES or byVDIM) of the output values in \p field_out corresponds to the ordering used in the input GridFunction \p field_in. */ void Interpolate(const Vector &point_pos, const GridFunction &field_in, Vector &field_out, - int point_pos_ordering = Ordering::byNODES); + const int point_pos_ordering = Ordering::byNODES); + /// Search positions and interpolate with given point and output ordering. + void Interpolate(const Vector &point_pos, const GridFunction &field_in, + Vector &field_out, const int point_pos_ordering, + const int field_out_ordering); /** Setup FindPoints, search positions and interpolate. The ordering (byNODES or byVDIM) of the output values in \p field_out corresponds to the ordering used in the input GridFunction \p field_in. */ void Interpolate(Mesh &m, const Vector &point_pos, const GridFunction &field_in, Vector &field_out, - int point_pos_ordering = Ordering::byNODES); + const int point_pos_ordering = Ordering::byNODES); /// Average type to be used for L2 functions in-case a point is located at /// an element boundary where the function might be multi-valued. @@ -376,7 +391,7 @@ public: /// The size of the returned vector is (nel x nverts x dim), where nel is the /// number of elements (after splitting for simplcies), nverts is number of /// vertices (4 in 2D, 8 in 3D), and dim is the spatial dimension. - void GetAxisAlignedBoundingBoxes(Vector &aabb); + void GetAxisAlignedBoundingBoxes(Vector &aabb) const; /// Return the oriented bounding boxes (OBB) computed during \ref Setup. /// Each OBB is represented using the inverse transformation (A^{-1}) and @@ -386,7 +401,8 @@ public: /// size (dim x dim x nel), and the OBB centers are returned in \p obbC, /// a vector of size (nel x dim). The vertices of the OBBs are returned in /// \p obbV, a vector of size (nel x nverts x dim) . - void GetOrientedBoundingBoxes(DenseTensor &obbA, Vector &obbC, Vector &obbV); + void GetOrientedBoundingBoxes(DenseTensor &obbA, Vector &obbC, + Vector &obbV) const; }; /** \brief OversetFindPointsGSLIB enables use of findpts for arbitrary number of @@ -446,13 +462,14 @@ public: byNodes: (XXX...,YYY...,ZZZ) or byVDim: (XYZ,XYZ,....XYZ) */ void FindPoints(const Vector &point_pos, - Array &point_id, - int point_pos_ordering = Ordering::byNODES); + const Array &point_id, + const int point_pos_ordering = Ordering::byNODES); /** Search positions and interpolate */ - void Interpolate(const Vector &point_pos, Array &point_id, + void Interpolate(const Vector &point_pos, + const Array &point_id, const GridFunction &field_in, Vector &field_out, - int point_pos_ordering = Ordering::byNODES); + const int point_pos_ordering = Ordering::byNODES); using FindPointsGSLIB::Interpolate; }; diff --git a/fem/hybridization.cpp b/fem/hybridization.cpp index f589e92a1b..942e75a029 100644 --- a/fem/hybridization.cpp +++ b/fem/hybridization.cpp @@ -789,7 +789,6 @@ void Hybridization::ComputeH() } else { - // TODO: add ones on the diagonal of zero rows V->Finalize(); Array V_J(V->NumNonZeroElems()); MFEM_ASSERT(c_pfes, ""); @@ -823,6 +822,13 @@ void Hybridization::ComputeH() MFEM_VERIFY(pH.Type() != Operator::PETSC_MATIS, "To be implemented"); pH.MakePtAP(plpH, pP); delete lpH; + + HypreParMatrix *hH = pH.As(); + MFEM_ASSERT(hH, ""); + + SparseMatrix H_diag; + hH->GetDiag(H_diag); + H_diag.SetDiagIdentity(); } #endif } diff --git a/fem/hybridization_ext.cpp b/fem/hybridization_ext.cpp index 8123806d8a..0fd2cee34c 100644 --- a/fem/hybridization_ext.cpp +++ b/fem/hybridization_ext.cpp @@ -34,14 +34,26 @@ static int GetNFacesPerElement(const Mesh &mesh) } } +static bool IsParFESpace(const FiniteElementSpace &fes) +{ +#ifdef MFEM_USE_MPI + return dynamic_cast(&fes) != nullptr; +#else + return false; +#endif +} + +const Operator &HybridizationExtension::GetProlongation() const +{ + return P_pc ? *P_pc : *h.c_fes.GetProlongationMatrix(); +} + void HybridizationExtension::ConstructC() { Mesh &mesh = *h.fes.GetMesh(); - const int ne = mesh.GetNE(); const int nf = mesh.GetNFbyType(FaceType::Interior); const int m = h.fes.GetFE(0)->GetDof(); // num hat dofs per el const int n = h.c_fes.GetFaceElement(0)->GetDof(); // num c dofs per face - const int n_faces_per_el = GetNFacesPerElement(mesh); // Assemble Ct_mat using EA Vector emat(m * n * 2 * nf); @@ -56,16 +68,13 @@ void HybridizationExtension::ConstructC() // pointer is valid. Array dof_map = tbe->GetDofMap(); - Ct_mat.SetSize(m * n * n_faces_per_el * ne); + Ct_mat.SetSize(m * n * n_el_face); const auto d_emat = Reshape(emat.Read(), m, n, 2, nf); const int *d_dof_map = dof_map.Read(); const auto d_face_to_el = Reshape(face_to_el.Read(), 2, 2, nf); - auto d_Ct_mat = Reshape(Ct_mat.Write(), m, n, n_faces_per_el, ne); + auto d_Ct_mat = Reshape(Ct_mat.Write(), m, n, n_el_face); - mfem::forall(Ct_mat.Size(), [=] MFEM_HOST_DEVICE (int i) - { - d_Ct_mat[i] = 0.0; - }); + Ct_mat = 0.0; // On device, since previous call to Write() mfem::forall(m*n*2*nf, [=] MFEM_HOST_DEVICE (int idx) { @@ -74,18 +83,27 @@ void HybridizationExtension::ConstructC() const int ie = (idx / m / n) % 2; const int f = idx / m / n / 2; - const int e = d_face_to_el(0, ie, f); const int fi = d_face_to_el(1, ie, f); // Skip elements belonging to face neighbors of shared faces - if (e < ne) + if (fi >= 0) { // Convert to back to native MFEM ordering in the volume const int i_s = d_dof_map[i_lex]; const int i = (i_s >= 0) ? i_s : -1 - i_s; - d_Ct_mat(i, j, fi, e) = d_emat(i_lex, j, ie, f); + d_Ct_mat(i, j, fi) = d_emat(i_lex, j, ie, f); } }); + +#ifdef MFEM_USE_MPI + if (auto pc_fes = dynamic_cast(&h.c_fes)) + { + if (pc_fes->Nonconforming()) + { + P_pc.reset(pc_fes->GetPartialConformingInterpolation()); + } + } +#endif } namespace internal @@ -109,197 +127,196 @@ void HybridizationExtension::FactorElementMatrices(Vector &AhatInvCt_mat) { const Mesh &mesh = *h.fes.GetMesh(); const int ne = mesh.GetNE(); - const int n_faces_per_el = GetNFacesPerElement(mesh); const int m = h.fes.GetFE(0)->GetDof(); const int n = h.c_fes.GetFaceElement(0)->GetDof(); AhatInvCt_mat.SetSize(Ct_mat.Size()); - auto d_AhatInvCt = Reshape(AhatInvCt_mat.Write(), m, n, n_faces_per_el, ne); + auto d_AhatInvCt = Reshape(AhatInvCt_mat.Write(), m, n, n_el_face); + const int nidofs = idofs.Size(); + const int nbdofs = bdofs.Size(); + + MFEM_VERIFY(nidofs <= MID, ""); + MFEM_VERIFY(nbdofs <= MBD, ""); + + Ahat_ii.SetSize(nidofs*nidofs*ne); + Ahat_ib.SetSize(nidofs*nbdofs*ne); + Ahat_bi.SetSize(nbdofs*nidofs*ne); + Ahat_bb.SetSize(nbdofs*nbdofs*ne); + + Ahat_ii_piv.SetSize(nidofs*ne); + Ahat_bb_piv.SetSize(nbdofs*ne); + + const auto *d_idofs = idofs.Read(); + const auto *d_bdofs = bdofs.Read(); + + const auto d_hat_dof_marker = Reshape(hat_dof_marker.Read(), m, ne); + auto d_Ahat = Reshape(Ahat.Read(), m, m, ne); + + auto d_A_ii = Reshape(Ahat_ii.Write(), nidofs, nidofs, ne); + auto d_A_ib_all = Reshape(Ahat_ib.Write(), nidofs*nbdofs, ne); + auto d_A_bi_all = Reshape(Ahat_bi.Write(), nbdofs*nidofs, ne); + auto d_A_bb_all = Reshape(Ahat_bb.Write(), nbdofs*nbdofs, ne); + + auto d_ipiv_ii = Reshape(Ahat_ii_piv.Write(), nidofs, ne); + auto d_ipiv_bb = Reshape(Ahat_bb_piv.Write(), nbdofs, ne); + + const auto d_Ct_mat = Reshape(Ct_mat.Read(), m, n, n_el_face); + const auto d_el_face_offsets = el_face_offsets.Read(); + + static constexpr bool GLOBAL = (MID == 0 && MBD == 0); + + using internal::LocalMemory; + + mfem::forall(ne, [=] MFEM_HOST_DEVICE (int e) { - const int nidofs = idofs.Size(); - const int nbdofs = bdofs.Size(); + constexpr int MD1D = DofQuadLimits::HDIV_MAX_D1D; + constexpr int MAX_DOFS = 3*MD1D*(MD1D-1)*(MD1D-1); + constexpr int MAX_IDOFS = (MID == 0 && MBD == 0) ? MAX_DOFS : MID; + constexpr int MAX_BDOFS = (MID == 0 && MBD == 0) ? MAX_DOFS : MBD; - MFEM_VERIFY(nidofs <= MID, ""); - MFEM_VERIFY(nbdofs <= MBD, ""); + LocalMemory idofs_loc; + LocalMemory bdofs_loc; + for (int i = 0; i < nidofs; i++) { idofs_loc[i] = d_idofs[i]; } + for (int i = 0; i < nbdofs; i++) { bdofs_loc[i] = d_bdofs[i]; } - Ahat_ii.SetSize(nidofs*nidofs*ne); - Ahat_ib.SetSize(nidofs*nbdofs*ne); - Ahat_bi.SetSize(nbdofs*nidofs*ne); - Ahat_bb.SetSize(nbdofs*nbdofs*ne); - - Ahat_ii_piv.SetSize(nidofs*ne); - Ahat_bb_piv.SetSize(nbdofs*ne); - - const auto *d_idofs = idofs.Read(); - const auto *d_bdofs = bdofs.Read(); - - const auto d_hat_dof_marker = Reshape(hat_dof_marker.Read(), m, ne); - auto d_Ahat = Reshape(Ahat.Read(), m, m, ne); - - auto d_A_ii = Reshape(Ahat_ii.Write(), nidofs, nidofs, ne); - auto d_A_ib_all = Reshape(Ahat_ib.Write(), nidofs*nbdofs, ne); - auto d_A_bi_all = Reshape(Ahat_bi.Write(), nbdofs*nidofs, ne); - auto d_A_bb_all = Reshape(Ahat_bb.Write(), nbdofs*nbdofs, ne); - - auto d_ipiv_ii = Reshape(Ahat_ii_piv.Write(), nidofs, ne); - auto d_ipiv_bb = Reshape(Ahat_bb_piv.Write(), nbdofs, ne); - - const auto d_Ct_mat = Reshape(Ct_mat.Read(), m, n, n_faces_per_el, ne); - - static constexpr bool GLOBAL = (MID == 0 && MBD == 0); - - using internal::LocalMemory; - - mfem::forall(ne, [=] MFEM_HOST_DEVICE (int e) + LocalMemory essdofs_loc; + int nbfdofs = 0; + int nessdofs = 0; + for (int i = 0; i < nbdofs; i++) { - constexpr int MD1D = DofQuadLimits::HDIV_MAX_D1D; - constexpr int MAX_DOFS = 3*MD1D*(MD1D-1)*(MD1D-1); - constexpr int MAX_IDOFS = (MID == 0 && MBD == 0) ? MAX_DOFS : MID; - constexpr int MAX_BDOFS = (MID == 0 && MBD == 0) ? MAX_DOFS : MBD; - - LocalMemory idofs_loc; - LocalMemory bdofs_loc; - for (int i = 0; i < nidofs; i++) { idofs_loc[i] = d_idofs[i]; } - for (int i = 0; i < nbdofs; i++) { bdofs_loc[i] = d_bdofs[i]; } - - LocalMemory essdofs_loc; - int nbfdofs = 0; - int nessdofs = 0; - for (int i = 0; i < nbdofs; i++) + const int dof_idx = bdofs_loc[i]; + if (d_hat_dof_marker(dof_idx, e) == ESSENTIAL) { - const int dof_idx = bdofs_loc[i]; - if (d_hat_dof_marker(dof_idx, e) == ESSENTIAL) + essdofs_loc[nessdofs] = dof_idx; + nessdofs += 1; + } + else + { + bdofs_loc[nbfdofs] = dof_idx; + nbfdofs += 1; + } + } + + LocalMemory A_ii_loc; + LocalMemory A_bi_loc; + LocalMemory A_ib_loc; + LocalMemory A_bb_loc; + + DeviceMatrix A_ii(GLOBAL ? &d_A_ii(0,0,e) : A_ii_loc, nidofs, nidofs); + DeviceMatrix A_ib(GLOBAL ? &d_A_ib_all(0,e) : A_ib_loc, nidofs, nbfdofs); + DeviceMatrix A_bi(GLOBAL ? &d_A_bi_all(0,e) : A_bi_loc, nbfdofs, nidofs); + DeviceMatrix A_bb(GLOBAL ? &d_A_bb_all(0,e) : A_bb_loc, nbfdofs, nbfdofs); + + for (int j = 0; j < nidofs; j++) + { + const int jj = idofs_loc[j]; + for (int i = 0; i < nidofs; i++) + { + A_ii(i,j) = d_Ahat(idofs_loc[i], jj, e); + } + for (int i = 0; i < nbfdofs; i++) + { + A_bi(i,j) = d_Ahat(bdofs_loc[i], jj, e); + } + } + for (int j = 0; j < nbfdofs; j++) + { + const int jj = bdofs_loc[j]; + for (int i = 0; i < nidofs; i++) + { + A_ib(i,j) = d_Ahat(idofs_loc[i], jj, e); + } + for (int i = 0; i < nbfdofs; i++) + { + A_bb(i,j) = d_Ahat(bdofs_loc[i], jj, e); + } + } + + LocalMemory ipiv_ii_loc; + LocalMemory ipiv_bb_loc; + + auto ipiv_ii = GLOBAL ? &d_ipiv_ii(0,e) : ipiv_ii_loc; + auto ipiv_bb = GLOBAL ? &d_ipiv_ii(0,e) : ipiv_bb_loc; + + kernels::LUFactor(A_ii, nidofs, ipiv_ii); + kernels::BlockFactor(A_ii, nidofs, ipiv_ii, nbfdofs, A_ib, A_bi, A_bb); + kernels::LUFactor(A_bb, nbfdofs, ipiv_bb); + + const int begin = d_el_face_offsets[e]; + const int end = d_el_face_offsets[e + 1]; + for (int f = begin; f < end; ++f) + { + for (int j = 0; j < n; ++j) + { + LocalMemory Sb_inv_Cb_t; + for (int i = 0; i < nbfdofs; ++i) { - essdofs_loc[nessdofs] = dof_idx; - nessdofs += 1; + Sb_inv_Cb_t[i] = d_Ct_mat(bdofs_loc[i], j, f); } - else + kernels::LUSolve(A_bb, nbfdofs, ipiv_bb, Sb_inv_Cb_t); + for (int i = 0; i < nbfdofs; ++i) { - bdofs_loc[nbfdofs] = dof_idx; - nbfdofs += 1; + const int b_i = bdofs_loc[i]; + d_AhatInvCt(b_i, j, f) = Sb_inv_Cb_t[i]; + } + for (int i = 0; i < nidofs; ++i) + { + d_AhatInvCt(idofs_loc[i], j, f) = 0.0; + } + for (int i = 0; i < nessdofs; ++i) + { + d_AhatInvCt(essdofs_loc[i], j, f) = 0.0; } } + } - LocalMemory A_ii_loc; - LocalMemory A_bi_loc; - LocalMemory A_ib_loc; - LocalMemory A_bb_loc; - - DeviceMatrix A_ii(GLOBAL ? &d_A_ii(0,0,e) : A_ii_loc, nidofs, nidofs); - DeviceMatrix A_ib(GLOBAL ? &d_A_ib_all(0,e) : A_ib_loc, nidofs, nbfdofs); - DeviceMatrix A_bi(GLOBAL ? &d_A_bi_all(0,e) : A_bi_loc, nbfdofs, nidofs); - DeviceMatrix A_bb(GLOBAL ? &d_A_bb_all(0,e) : A_bb_loc, nbfdofs, nbfdofs); + // Write out to global memory + if (!GLOBAL) + { + // Note: in the following constructors, avoid using index 0 in + // d_A_{bi,ib,bb}_all when their size is 0. + DeviceMatrix d_A_bi((nbfdofs && nidofs) ? + &d_A_bi_all(0,e) : nullptr, + nbfdofs, nidofs); + DeviceMatrix d_A_ib((nbfdofs && nidofs) ? + &d_A_ib_all(0,e) : nullptr, + nidofs, nbfdofs); + DeviceMatrix d_A_bb((nbfdofs) ? &d_A_bb_all(0,e) : nullptr, + nbfdofs, nbfdofs); for (int j = 0; j < nidofs; j++) { - const int jj = idofs_loc[j]; + d_ipiv_ii(j,e) = ipiv_ii[j]; for (int i = 0; i < nidofs; i++) { - A_ii(i,j) = d_Ahat(idofs_loc[i], jj, e); + d_A_ii(i,j,e) = A_ii(i,j); } for (int i = 0; i < nbfdofs; i++) { - A_bi(i,j) = d_Ahat(bdofs_loc[i], jj, e); + d_A_bi(i,j) = A_bi(i,j); } } for (int j = 0; j < nbfdofs; j++) { - const int jj = bdofs_loc[j]; + d_ipiv_bb(j,e) = ipiv_bb[j]; for (int i = 0; i < nidofs; i++) { - A_ib(i,j) = d_Ahat(idofs_loc[i], jj, e); + d_A_ib(i,j) = A_ib(i,j); } for (int i = 0; i < nbfdofs; i++) { - A_bb(i,j) = d_Ahat(bdofs_loc[i], jj, e); + d_A_bb(i,j) = A_bb(i,j); } } - - LocalMemory ipiv_ii_loc; - LocalMemory ipiv_bb_loc; - - auto ipiv_ii = GLOBAL ? &d_ipiv_ii(0,e) : ipiv_ii_loc; - auto ipiv_bb = GLOBAL ? &d_ipiv_ii(0,e) : ipiv_bb_loc; - - kernels::LUFactor(A_ii, nidofs, ipiv_ii); - kernels::BlockFactor(A_ii, nidofs, ipiv_ii, nbfdofs, A_ib, A_bi, A_bb); - kernels::LUFactor(A_bb, nbfdofs, ipiv_bb); - - for (int f = 0; f < n_faces_per_el; ++f) - { - for (int j = 0; j < n; ++j) - { - LocalMemory Sb_inv_Cb_t; - for (int i = 0; i < nbfdofs; ++i) - { - Sb_inv_Cb_t[i] = d_Ct_mat(bdofs_loc[i], j, f, e); - } - kernels::LUSolve(A_bb, nbfdofs, ipiv_bb, Sb_inv_Cb_t); - for (int i = 0; i < nbfdofs; ++i) - { - const int b_i = bdofs_loc[i]; - d_AhatInvCt(b_i, j, f, e) = Sb_inv_Cb_t[i]; - } - for (int i = 0; i < nidofs; ++i) - { - d_AhatInvCt(idofs_loc[i], j, f, e) = 0.0; - } - for (int i = 0; i < nessdofs; ++i) - { - d_AhatInvCt(essdofs_loc[i], j, f, e) = 0.0; - } - } - } - - // Write out to global memory - if (!GLOBAL) - { - // Note: in the following constructors, avoid using index 0 in - // d_A_{bi,ib,bb}_all when their size is 0. - DeviceMatrix d_A_bi((nbfdofs && nidofs) ? - &d_A_bi_all(0,e) : nullptr, - nbfdofs, nidofs); - DeviceMatrix d_A_ib((nbfdofs && nidofs) ? - &d_A_ib_all(0,e) : nullptr, - nidofs, nbfdofs); - DeviceMatrix d_A_bb((nbfdofs) ? &d_A_bb_all(0,e) : nullptr, - nbfdofs, nbfdofs); - - for (int j = 0; j < nidofs; j++) - { - d_ipiv_ii(j,e) = ipiv_ii[j]; - for (int i = 0; i < nidofs; i++) - { - d_A_ii(i,j,e) = A_ii(i,j); - } - for (int i = 0; i < nbfdofs; i++) - { - d_A_bi(i,j) = A_bi(i,j); - } - } - for (int j = 0; j < nbfdofs; j++) - { - d_ipiv_bb(j,e) = ipiv_bb[j]; - for (int i = 0; i < nidofs; i++) - { - d_A_ib(i,j) = A_ib(i,j); - } - for (int i = 0; i < nbfdofs; i++) - { - d_A_bb(i,j) = A_bb(i,j); - } - } - } - }); - } + } + }); } void HybridizationExtension::ConstructH() { const Mesh &mesh = *h.fes.GetMesh(); const int ne = mesh.GetNE(); - const int n_faces_per_el = GetNFacesPerElement(mesh); const int m = h.fes.GetFE(0)->GetDof(); const int n = h.c_fes.GetFaceElement(0)->GetDof(); @@ -334,44 +351,45 @@ void HybridizationExtension::ConstructH() } const auto d_AhatInvCt = - Reshape(AhatInvCt_mat.Read(), m, n, n_faces_per_el, ne); + Reshape(AhatInvCt_mat.Read(), m, n, n_el_face); const int nf = h.fes.GetNFbyType(FaceType::Interior); - const int n_face_connections = 2*n_faces_per_el - 1; - Array face_to_face(nf * n_face_connections); + Array face_to_face(n_face_face); - Array CAhatInvCt(nf*n_face_connections*n*n); + Vector CAhatInvCt(n_face_face*n*n); - const auto d_Ct = Reshape(Ct_mat.Read(), m, n, n_faces_per_el, ne); + const auto d_Ct = Reshape(Ct_mat.Read(), m, n, n_el_face); const auto d_face_to_el = Reshape(face_to_el.Read(), 2, 2, nf); - const auto d_el_to_face = Reshape(el_to_face.Read(), n_faces_per_el, ne); - auto d_CAhatInvCt = Reshape(CAhatInvCt.Write(), n, n, n_face_connections, nf); - auto d_face_to_face = Reshape(face_to_face.Write(), n_face_connections, nf); + const auto d_el_to_face = el_to_face.Read(); + const auto d_el_face_offsets = el_face_offsets.Read(); + auto d_CAhatInvCt = Reshape(CAhatInvCt.Write(), n, n, n_face_face); + auto d_face_to_face = Reshape(face_to_face.Write(), n_face_face); + auto d_face_face_offsets = face_face_offsets.Read(); - mfem::forall(n*n*n_face_connections*nf, [=] MFEM_HOST_DEVICE (int i) - { - d_CAhatInvCt[i] = 0.0; - }); + CAhatInvCt = 0.0; mfem::forall(nf, [=] MFEM_HOST_DEVICE (int fi) { + const int begin_f = d_face_face_offsets[fi]; + int idx = 0; for (int ei = 0; ei < 2; ++ei) { const int e = d_face_to_el(0, ei, fi); if (e < 0 || e >= ne) { continue; } - for (int fj_i = 0; fj_i < n_faces_per_el; ++fj_i) + const int begin_i = d_el_face_offsets[e]; + const int end_i = d_el_face_offsets[e + 1]; + for (int fj_i = begin_i; fj_i < end_i; ++fj_i) { - const int fj = d_el_to_face(fj_i, e); - // Explicitly allow fi == fj (self-connections) - if (fj < 0) { continue; } + const int fj = d_el_to_face[fj_i]; + // Allow fi == fj (self-connections) // Have we seen this face before? It is possible in some // configurations to encounter the same neighboring face twice int idx_j = idx; for (int i = 0; i < idx; ++i) { - if (d_face_to_face(i, fi) == fj) + if (d_face_to_face[begin_f + i] == fj) { idx_j = i; break; @@ -380,56 +398,74 @@ void HybridizationExtension::ConstructH() // This is a new face, record it and increment the counter if (idx_j == idx) { - d_face_to_face(idx, fi) = fj; + d_face_to_face[begin_f + idx] = fj; idx++; } } } - // Fill unused entries with -1 to indicate invalid - for (; idx < n_face_connections; ++idx) - { - d_face_to_face(idx, fi) = -1; - } }); - mfem::forall(nf*n_face_connections, [=] MFEM_HOST_DEVICE (int idx) + mfem::forall(nf, [=] MFEM_HOST_DEVICE (int fi) { - const int idx_j = idx % n_face_connections; - const int fi = idx / n_face_connections; - - const int fj = d_face_to_face(idx_j, fi); - if (fj < 0) { return; } - - for (int ei = 0; ei < 2; ++ei) + const int begin = d_face_face_offsets[fi]; + const int end = d_face_face_offsets[fi + 1]; + for (int idx_j = begin; idx_j < end; ++idx_j) { - const int e = d_face_to_el(0, ei, fi); - if (e < 0 || e >= ne) { continue; } - const int fi_i = d_face_to_el(1, ei, fi); - - int fj_i = -1; - for (int ej = 0; ej < 2; ++ej) + const int fj = d_face_to_face[idx_j]; + for (int ei = 0; ei < 2; ++ei) { - if (d_face_to_el(0, ej, fj) == e) + const int e = d_face_to_el(0, ei, fi); + if (e < 0 || e >= ne) { continue; } + const int fi_i = d_face_to_el(1, ei, fi); + + int fj_i = -1; + for (int ej = 0; ej < 2; ++ej) { - fj_i = d_face_to_el(1, ej, fj); - break; + if (d_face_to_el(0, ej, fj) == e) + { + fj_i = d_face_to_el(1, ej, fj); + break; + } + } + if (fj_i >= 0) + { + const real_t *Ct_i = &d_Ct(0, 0, fi_i); + const real_t *AhatInvCt_i = &d_AhatInvCt(0, 0, fj_i); + real_t *CAhatInvCt_i = &d_CAhatInvCt(0, 0, idx_j); + kernels::AddMultAtB(m, n, n, Ct_i, AhatInvCt_i, CAhatInvCt_i); } } - if (fj_i >= 0) - { - const real_t *Ct_i = &d_Ct(0, 0, fi_i, e); - const real_t *AhatInvCt_i = &d_AhatInvCt(0, 0, fj_i, e); - real_t *CAhatInvCt_i = &d_CAhatInvCt(0, 0, idx_j, fi); - kernels::AddMultAtB(m, n, n, Ct_i, AhatInvCt_i, CAhatInvCt_i); - } } }); - const int ncdofs = h.c_fes.GetVSize(); + +#ifdef MFEM_USE_MPI + auto *c_pfes = dynamic_cast(&h.c_fes); +#endif + + const int ncdofs_face_nbr = [&]() + { +#ifdef MFEM_USE_MPI + // Only need to handle face neighbor DOFs when there are nonconforming + // (ghost) faces. + if (c_pfes && c_pfes->Nonconforming()) + { + c_pfes->ExchangeFaceNbrData(); + return c_pfes->GetFaceNbrVSize(); + } +#endif + return 0; + }(); + + const int ncdofs_local = h.c_fes.GetVSize(); + const int ncdofs = ncdofs_local + ncdofs_face_nbr; const ElementDofOrdering ordering = ElementDofOrdering::NATIVE; const FaceRestriction *face_restr = h.c_fes.GetFaceRestriction(ordering, FaceType::Interior); - const auto c_gather_map = Reshape(face_restr->GatherMap().Read(), n, nf); + const auto *l2_face_restr = + dynamic_cast(face_restr); + MFEM_ASSERT(l2_face_restr, ""); + const auto c_scatter_map = Reshape(l2_face_restr->ScatterMap().Read(), n, nf); h.H.reset(new SparseMatrix); h.H->OverrideSize(ncdofs, ncdofs); @@ -445,15 +481,15 @@ void HybridizationExtension::ConstructH() { const int i = idx_i % n; const int fi = idx_i / n; - const int ii = c_gather_map(i, fi); + const int ii = c_scatter_map(i, fi); - for (int idx = 0; idx < n_face_connections; ++idx) + const int begin = d_face_face_offsets[fi]; + const int end = d_face_face_offsets[fi + 1]; + for (int idx = begin; idx < end; ++idx) { - const int fj = d_face_to_face(idx, fi); - if (fj < 0) { break; } for (int j = 0; j < n; ++j) { - if (d_CAhatInvCt(i, j, idx, fi) != 0) + if (d_CAhatInvCt(i, j, idx) != 0) { I[ii]++; } @@ -465,13 +501,14 @@ void HybridizationExtension::ConstructH() // At this point, I[i] contains the number of nonzeros in row I. Perform a // partial sum to get I in CSR format. This is serial, so perform on host. // - // At the same time, we find any empty rows and add a single nonzero (we will - // put 1 on the diagonal) and record the row index. + // At the same time, we find any empty rows (corresponding to non-ghost DOFs) + // and add a single nonzero (we will put 1 on the diagonal) and record the + // row index. Array empty_rows; { int *I = h.H->HostReadWriteI(); int empty_row_count = 0; - for (int i = 0; i < ncdofs; i++) + for (int i = 0; i < ncdofs_local; i++) { if (I[i] == 0) { empty_row_count++; } } @@ -482,7 +519,7 @@ void HybridizationExtension::ConstructH() for (int i = 0; i < ncdofs; i++) { int nnz = I[i]; - if (nnz == 0) + if (nnz == 0 && i < ncdofs_local) { empty_rows[empty_row_idx] = i; empty_row_idx++; @@ -507,18 +544,19 @@ void HybridizationExtension::ConstructH() { const int i = idx_i % n; const int fi = idx_i / n; - const int ii = c_gather_map[i + fi*n]; - for (int idx = 0; idx < n_face_connections; ++idx) + const int ii = c_scatter_map[i + fi*n]; + const int begin = d_face_face_offsets[fi]; + const int end = d_face_face_offsets[fi + 1]; + for (int idx = begin; idx < end; ++idx) { - const int fj = d_face_to_face(idx, fi); - if (fj < 0) { break; } + const int fj = d_face_to_face[idx]; for (int j = 0; j < n; ++j) { - const real_t val = d_CAhatInvCt(i, j, idx, fi); + const real_t val = d_CAhatInvCt(i, j, idx); if (val != 0) { const int k = I[ii]; - const int jj = c_gather_map(j, fj); + const int jj = c_scatter_map(j, fj); I[ii]++; J[k] = jj; V[k] = val; @@ -549,13 +587,68 @@ void HybridizationExtension::ConstructH() } #ifdef MFEM_USE_MPI - auto *c_pfes = dynamic_cast(&h.c_fes); if (c_pfes) { - OperatorHandle pP(h.pH.Type()), dH(h.pH.Type()); - pP.ConvertFrom(c_pfes->Dof_TrueDof_Matrix()); - dH.MakeSquareBlockDiag(c_pfes->GetComm(),c_pfes->GlobalVSize(), - c_pfes->GetDofOffsets(), h.H.get()); + OperatorHandle dH(h.pH.Type()); + + if (ncdofs_face_nbr > 0) + { + // Build the "face neighbor prolongation matrix" P_nbr, which maps from + // VDOFs (i.e. L-vector) to L-vectors with face neighbor DOFs. The + // action of P_nbr is equivalent to calling ExchangeFaceNbrData on a + // ParGridFunction. We compute P^t A P with P = P_nbr to assemble the + // face neighbor contributions into a parallel matrix. + ParMesh &pmesh = *c_pfes->GetParMesh(); + + HYPRE_BigInt ncdofs_bigint = ncdofs; + const HYPRE_BigInt global_ncdofs = pmesh.ReduceInt(ncdofs); + + Array rows; + Array *offsets[1] = { &rows }; + pmesh.GenerateOffsets(1, &ncdofs_bigint, offsets); + + Array I(ncdofs + 1); + auto d_I = I.Write(); + mfem::forall(ncdofs + 1, [=] MFEM_HOST_DEVICE (int i) { d_I[i] = i; }); + + HYPRE_BigInt offset = c_pfes->GetMyDofOffset(); + Array J(ncdofs); + auto d_J = J.Write(); + mfem::forall(ncdofs_local, [=] MFEM_HOST_DEVICE (int i) + { + d_J[i] = offset + i; + }); + const HYPRE_BigInt *map = c_pfes->GetFaceNbrGlobalDofMapArray().Read(); + mfem::forall(ncdofs_face_nbr, [=] MFEM_HOST_DEVICE (int i) + { + d_J[ncdofs_local + i] = map[i]; + }); + + Vector V(ncdofs); + V.UseDevice(); + V = 1.0; + + auto P_face_nbr = + std::make_unique( + c_pfes->GetComm(), ncdofs, global_ncdofs, c_pfes->GlobalVSize(), + I.HostReadWrite(), J.HostReadWrite(), V.HostReadWrite(), rows, + c_pfes->GetDofOffsets()); + HypreParMatrix H_diag(c_pfes->GetComm(), global_ncdofs, rows, h.H.get()); + + dH.Reset(RAP(&H_diag, P_face_nbr.get())); + + P_nbr = std::move(P_face_nbr); + } + else + { + dH.MakeSquareBlockDiag(c_pfes->GetComm(),c_pfes->GlobalVSize(), + c_pfes->GetDofOffsets(), h.H.get()); + } + + OperatorHandle pP(h.pH.Type()); + auto P_hyp = static_cast( + P_pc ? P_pc.get() : c_pfes->Dof_TrueDof_Matrix()); + pP.ConvertFrom(P_hyp); h.pH.MakePtAP(dH, pP); h.H.reset(); } @@ -570,7 +663,6 @@ void HybridizationExtension::MultCt(const Vector &x, Vector &y) const const int n_hat_dof_per_el = h.fes.GetFE(0)->GetDof(); const int n_c_dof_per_face = h.c_fes.GetFaceElement(0)->GetDof(); - const int n_faces_per_el = GetNFacesPerElement(mesh); const ElementDofOrdering ordering = ElementDofOrdering::NATIVE; const FaceRestriction *face_restr = @@ -580,8 +672,9 @@ void HybridizationExtension::MultCt(const Vector &x, Vector &y) const face_restr->Mult(x, x_evec); const int *d_el_to_face = el_to_face.Read(); + const int *d_el_face_offsets = el_face_offsets.Read(); const auto d_Ct = Reshape(Ct_mat.Read(), n_hat_dof_per_el, n_c_dof_per_face, - n_faces_per_el, ne); + n_el_face); const auto d_x_evec = Reshape(x_evec.Read(), n_c_dof_per_face, nf); auto d_y = Reshape(y.Write(), n_hat_dof_per_el, ne); @@ -590,13 +683,14 @@ void HybridizationExtension::MultCt(const Vector &x, Vector &y) const const int e = idx / n_hat_dof_per_el; const int i = idx % n_hat_dof_per_el; d_y(i, e) = 0.0; - for (int fi = 0; fi < n_faces_per_el; ++fi) + const int begin = d_el_face_offsets[e]; + const int end = d_el_face_offsets[e+1]; + for (int fi = begin; fi < end; ++fi) { - const int f = d_el_to_face[e*n_faces_per_el + fi]; - if (f < 0) { continue; } + const int f = d_el_to_face[fi]; for (int j = 0; j < n_c_dof_per_face; ++j) { - d_y(i, e) += d_Ct(i, j, fi, e)*d_x_evec(j, f); + d_y(i, e) += d_Ct(i, j, fi)*d_x_evec(j, f); } } }); @@ -608,9 +702,8 @@ void HybridizationExtension::MultC(const Vector &x, Vector &y) const const int ne = mesh.GetNE(); const int nf = mesh.GetNFbyType(FaceType::Interior); - const int n_hat_dof_per_el = h.fes.GetFE(0)->GetDof(); - const int n_c_dof_per_face = h.c_fes.GetFaceElement(0)->GetDof(); - const int n_faces_per_el = GetNFacesPerElement(mesh); + const int n_hat_dof_per_el = h.fes.GetTypicalFE()->GetDof(); + const int n_c_dof_per_face = h.c_fes.GetTypicalTraceElement()->GetDof(); const ElementDofOrdering ordering = ElementDofOrdering::NATIVE; const FaceRestriction *face_restr = h.c_fes.GetFaceRestriction( @@ -619,7 +712,7 @@ void HybridizationExtension::MultC(const Vector &x, Vector &y) const Vector y_evec(face_restr->Height()); const auto d_face_to_el = Reshape(face_to_el.Read(), 2, 2, nf); const auto d_Ct = Reshape(Ct_mat.Read(), n_hat_dof_per_el, n_c_dof_per_face, - n_faces_per_el, ne); + n_el_face); auto d_x = Reshape(x.Read(), n_hat_dof_per_el, ne); auto d_y_evec = Reshape(y_evec.Write(), n_c_dof_per_face, nf); @@ -638,13 +731,27 @@ void HybridizationExtension::MultC(const Vector &x, Vector &y) const for (int i = 0; i < n_hat_dof_per_el; ++i) { - d_y_evec(j, f) += d_Ct(i, j, fi, e)*d_x(i, e); + d_y_evec(j, f) += d_Ct(i, j, fi)*d_x(i, e); } } }); y.SetSize(face_restr->Width()); - face_restr->MultTranspose(y_evec, y); + + if (P_nbr) + { + auto l2_face_restr = + dynamic_cast(face_restr); + MFEM_ASSERT(l2_face_restr != nullptr, ""); + + Vector y_s(P_nbr->Height()); + l2_face_restr->MultTransposeShared(y_evec, y_s); + P_nbr->MultTranspose(y_s, y); + } + else + { + face_restr->MultTranspose(y_evec, y); + } } void HybridizationExtension::AssembleMatrix(int el, const DenseMatrix &elmat) @@ -721,12 +828,12 @@ void HybridizationExtension::Init(const Array &ess_tdof_list) const Mesh &mesh = *h.fes.GetMesh(); const int dim = mesh.Dimension(); const int ne = h.fes.GetNE(); + const int nf = mesh.GetNFbyType(FaceType::Interior); const int ndof_per_el = h.fes.GetFE(0)->GetDof(); const int ndof_per_face = h.c_fes.GetFaceElement(0)->GetDof(); MFEM_VERIFY(!h.fes.IsVariableOrder(), ""); MFEM_VERIFY(dim == 2 || dim == 3, ""); - MFEM_VERIFY(mesh.Conforming(), ""); MFEM_VERIFY(UsesTensorBasis(h.fes), ""); // Set up array for idofs and bdofs @@ -765,36 +872,96 @@ void HybridizationExtension::Init(const Array &ess_tdof_list) } // Set up face info arrays - const int n_faces_per_el = GetNFacesPerElement(mesh); - el_to_face.SetSize(ne * n_faces_per_el); - face_to_el.SetSize(4 * mesh.GetNFbyType(FaceType::Interior)); - el_to_face = -1; + el_face_offsets.SetSize(ne + 1); + el_face_offsets = 0; + // Count faces per element + for (int f = 0; f < mesh.GetNumFacesWithGhost(); ++f) + { + const Mesh::FaceInformation info = mesh.GetFaceInformation(f); + if (!info.IsInterior() || info.IsNonconformingCoarse()) { continue; } + el_face_offsets[info.element[0].index + 1] += 1; + if (!info.IsShared()) + { + el_face_offsets[info.element[1].index + 1] += 1; + } + } + el_face_offsets.PartialSum(); + // Set up element-to-face and face-to-element arrays + n_el_face = el_face_offsets.Last(); + el_to_face.SetSize(n_el_face); + face_to_el.SetSize(4 * nf); { + Array el_face_counter(ne); + el_face_counter = 0; + int face_idx = 0; - for (int f = 0; f < mesh.GetNumFaces(); ++f) + for (int f = 0; f < mesh.GetNumFacesWithGhost(); ++f) { const Mesh::FaceInformation info = mesh.GetFaceInformation(f); - if (!info.IsInterior()) { continue; } + if (!info.IsInterior() || info.IsNonconformingCoarse()) { continue; } const int el1 = info.element[0].index; - const int fi1 = info.element[0].local_face_id; - el_to_face[el1 * n_faces_per_el + fi1] = face_idx; + int &offset1 = el_face_offsets[el1]; + el_to_face[offset1] = face_idx; + face_to_el[0 + 4*face_idx] = el1; + face_to_el[1 + 4*face_idx] = offset1; + + offset1 += 1; const int el2 = info.element[1].index; - const int fi2 = info.element[1].local_face_id; if (!info.IsShared()) { - el_to_face[el2 * n_faces_per_el + fi2] = face_idx; + int &offset2 = el_face_offsets[el2]; + el_to_face[offset2] = face_idx; + face_to_el[2 + 4*face_idx] = el2; + face_to_el[3 + 4*face_idx] = offset2; + offset2 += 1; + } + else + { + face_to_el[2 + 4*face_idx] = ne + el2; + face_to_el[3 + 4*face_idx] = -1; } - - face_to_el[0 + 4*face_idx] = el1; - face_to_el[1 + 4*face_idx] = fi1; - face_to_el[2 + 4*face_idx] = info.IsShared() ? ne + el2 : el2; - face_to_el[3 + 4*face_idx] = fi2; ++face_idx; } + + for (int i = ne; i > 0; i--) + { + el_face_offsets[i] = el_face_offsets[i-1]; + } + el_face_offsets[0] = 0; + } + + // Create the face-to-face connectivity + { + face_face_offsets.SetSize(nf + 1); + const auto d_face_to_el = Reshape(face_to_el.Read(), 2, 2, nf); + const auto d_el_face_offsets = el_face_offsets.Read(); + auto d_face_face_offsets = face_face_offsets.Write(); + mfem::forall(nf + 1, [=] MFEM_HOST_DEVICE (int i) { d_face_face_offsets[i] = 0; }); + mfem::forall(nf, [=] MFEM_HOST_DEVICE (int f) + { + int n_connections = 0; + for (int ie = 0; ie < 2; ++ie) + { + // Number of faces adjacent to e + const int e = d_face_to_el(0, ie, f); + if (e < ne) + { + n_connections += d_el_face_offsets[e + 1] - d_el_face_offsets[e]; + // Subtract 1 since we are double-counting the face 'f' (it + // belongs to both adjacent elements). + if (ie > 0) { n_connections -= 1; } + } + } + d_face_face_offsets[f + 1] = n_connections; + }); + // TODO: parallel scan on device? + face_face_offsets.HostReadWrite(); + face_face_offsets.PartialSum(); + n_face_face = face_face_offsets.Last(); } // Count the number of dofs in the discontinuous version of fes: @@ -851,12 +1018,19 @@ void HybridizationExtension::Init(const Array &ess_tdof_list) 0, free_vdofs_marker.HostWrite()); } else - { - free_vdofs_marker.MakeRef(free_tdof_marker); - } -#else - free_vdofs_marker.MakeRef(free_tdof_marker); #endif + { + const SparseMatrix *cP = h.fes.GetConformingProlongation(); + if (cP) + { + free_vdofs_marker.SetSize(cP->Height()); + cP->BooleanMult(free_tdof_marker, free_vdofs_marker); + } + else + { + free_vdofs_marker.MakeRef(free_tdof_marker); + } + } hat_dof_marker.SetSize(num_hat_dofs); { @@ -866,7 +1040,8 @@ void HybridizationExtension::Init(const Array &ess_tdof_list) const int *gather_map = R->GatherMap().Read(); const int *d_free_vdofs_marker = free_vdofs_marker.Read(); const auto d_Ct_mat = Reshape(Ct_mat.Read(), ndof_per_el, - ndof_per_face, n_faces_per_el, ne); + ndof_per_face, n_el_face); + const int *d_el_face_offsets = el_face_offsets.Read(); DofType *d_hat_dof_marker = hat_dof_marker.Write(); // Set the hat_dofs_marker to 1 or 0 according to whether the DOF is @@ -882,11 +1057,13 @@ void HybridizationExtension::Init(const Array &ess_tdof_list) const int i_loc = i % ndof_per_el; const int e = i / ndof_per_el; d_hat_dof_marker[i] = INTERIOR; - for (int f = 0; f < n_faces_per_el; ++f) + const int begin = d_el_face_offsets[e]; + const int end = d_el_face_offsets[e + 1]; + for (int f = begin; f < end; ++f) { for (int k = 0; k < ndof_per_face; ++k) { - if (d_Ct_mat(i_loc, k, f, e) != 0.0) + if (d_Ct_mat(i_loc, k, f) != 0.0) { d_hat_dof_marker[i] = BOUNDARY; break; @@ -1105,13 +1282,14 @@ void HybridizationExtension::ReduceRHS(const Vector &b, Vector &b_r) const }); } MultAhatInv(b_hat); - const Operator *P = h.c_fes.GetProlongationMatrix(); - if (P) + + if (IsParFESpace(h.c_fes)) { - Vector bl(P->Height()); - b_r.SetSize(P->Width()); + const Operator &P = GetProlongation(); + Vector bl(P.Height()); + b_r.SetSize(P.Width()); MultC(b_hat, bl); - P->MultTranspose(bl, b_r); + P.MultTranspose(bl, b_r); } else { @@ -1127,17 +1305,19 @@ void HybridizationExtension::ComputeSolution( MultRt(b, b_hat); tmp1.SetSize(num_hat_dofs); - const Operator *P = h.c_fes.GetProlongationMatrix(); - if (P) + + if (IsParFESpace(h.c_fes)) { - Vector sol_l(P->Height()); - P->Mult(sol_r, sol_l); + const Operator &P = GetProlongation(); + Vector sol_l(P.Height()); + P.Mult(sol_r, sol_l); MultCt(sol_l, tmp1); } else { MultCt(sol_r, tmp1); } + add(b_hat, -1.0, tmp1, tmp1); // Eliminate essential DOFs const auto *d_hat_dof_marker = hat_dof_marker.Read(); diff --git a/fem/hybridization_ext.hpp b/fem/hybridization_ext.hpp index 5bc44e6bbe..ee38fa33bb 100644 --- a/fem/hybridization_ext.hpp +++ b/fem/hybridization_ext.hpp @@ -14,8 +14,11 @@ #include "../config/config.hpp" #include "../general/array.hpp" +#include "../linalg/operator.hpp" #include "../linalg/vector.hpp" +#include + namespace mfem { @@ -45,15 +48,30 @@ protected: Array hat_dof_gather_map; Array hat_dof_marker; - Array el_to_face; - Array face_to_el; + Array el_to_face; ///< Element to face connectivity. + Array el_face_offsets; ///< Per-element offsets into @a el_to_face. + Array face_to_el; ///< Face-to-element connectivity. + Array face_face_offsets; ///< Face-to-face offsets. + + int n_el_face; ///< Total number of element-to-face connections. + int n_face_face; ///< Total number of face-to-face connections. + Vector Ct_mat; ///< Constraint matrix (transposed) stored element-wise. + /// @name For parallel non-conforming meshes + ///@{ + std::unique_ptr P_pc; ///< Partially conforming prolongation. + std::unique_ptr P_nbr; ///< Face-neighbor prolongation. + ///@} + Array idofs, bdofs; Vector Ahat, Ahat_ii, Ahat_ib, Ahat_bi, Ahat_bb; Array Ahat_ii_piv, Ahat_bb_piv; + /// Return the (partially) conforming prolongation on the constraint space. + const Operator &GetProlongation() const; + public: /// Construct the constraint matrix. void ConstructC(); diff --git a/fem/integ/bilininteg_diffusion_kernels.hpp b/fem/integ/bilininteg_diffusion_kernels.hpp index 551e8e4d86..e611c1a748 100644 --- a/fem/integ/bilininteg_diffusion_kernels.hpp +++ b/fem/integ/bilininteg_diffusion_kernels.hpp @@ -1004,13 +1004,16 @@ inline void SmemPADiffusionApply3D(const int NE, const int max_d1d = T_D1D ? T_D1D : DeviceDofQuadLimits::Get().MAX_D1D; MFEM_VERIFY(D1D <= max_d1d, ""); MFEM_VERIFY(Q1D <= max_q1d, ""); - auto b = Reshape(b_.Read(), Q1D, D1D); - auto g = Reshape(g_.Read(), Q1D, D1D); - auto d = Reshape(d_.Read(), Q1D, Q1D, Q1D, symmetric ? 6 : 9, NE); - auto x = Reshape(x_.Read(), D1D, D1D, D1D, NE); + const auto b = Reshape(b_.Read(), Q1D, D1D); + const auto g = Reshape(g_.Read(), Q1D, D1D); + const auto d = Reshape(d_.Read(), Q1D, Q1D, Q1D, symmetric ? 6 : 9, NE); + const auto x = Reshape(x_.Read(), D1D, D1D, D1D, NE); auto y = Reshape(y_.ReadWrite(), D1D, D1D, D1D, NE); MFEM_VERIFY(D1D <= Q1D, "THREAD_DIRECT requires D1D <= Q1D"); - mfem::forall_3D(NE, Q1D, Q1D, Q1D, [=] MFEM_HOST_DEVICE (int e) + + mfem::forall_3D(NE, + Q1D, Q1D, Q1D, + [=] MFEM_HOST_DEVICE (int e) { const int D1D = T_D1D ? T_D1D : d1d; const int Q1D = T_Q1D ? T_Q1D : q1d; diff --git a/fem/integ/bilininteg_mass_kernels.hpp b/fem/integ/bilininteg_mass_kernels.hpp index ecc875e13b..7aea5e9ea2 100644 --- a/fem/integ/bilininteg_mass_kernels.hpp +++ b/fem/integ/bilininteg_mass_kernels.hpp @@ -1133,11 +1133,11 @@ inline void SmemPAMassApply3D(const int NE, const int max_d1d = T_D1D ? T_D1D : DeviceDofQuadLimits::Get().MAX_D1D; MFEM_VERIFY(D1D <= max_d1d, ""); MFEM_VERIFY(Q1D <= max_q1d, ""); - auto b = b_.Read(); - auto d = d_.Read(); - auto x = x_.Read(); + const auto b = b_.Read(); + const auto d = d_.Read(); + const auto x = x_.Read(); auto y = y_.ReadWrite(); - mfem::forall_2D(NE, Q1D, Q1D, [=] MFEM_HOST_DEVICE (int e) + mfem::forall_2D(NE, Q1D, Q1D, [=] MFEM_HOST_DEVICE (int e) { internal::SmemPAMassApply3D_Element(e, NE, b, d, x, y, d1d, q1d); }); @@ -1156,8 +1156,8 @@ inline void EAMassAssemble1D(const int NE, const int Q1D = T_Q1D ? T_Q1D : q1d; MFEM_VERIFY(D1D <= DeviceDofQuadLimits::Get().MAX_D1D, ""); MFEM_VERIFY(Q1D <= DeviceDofQuadLimits::Get().MAX_Q1D, ""); - auto B = Reshape(basis.Read(), Q1D, D1D); - auto D = Reshape(padata.Read(), Q1D, NE); + const auto B = Reshape(basis.Read(), Q1D, D1D); + const auto D = Reshape(padata.Read(), Q1D, NE); auto M = Reshape(add ? eadata.ReadWrite() : eadata.Write(), D1D, D1D, NE); mfem::forall_2D(NE, D1D, D1D, [=] MFEM_HOST_DEVICE (int e) { diff --git a/fem/integ/bilininteg_trace_jump_ea.cpp b/fem/integ/bilininteg_trace_jump_ea.cpp index 26909d5414..e118e0dc29 100644 --- a/fem/integ/bilininteg_trace_jump_ea.cpp +++ b/fem/integ/bilininteg_trace_jump_ea.cpp @@ -28,7 +28,7 @@ void NormalTraceJumpIntegrator::AssembleEAInteriorFaces( const FaceType ftype = FaceType::Interior; const int nf = mesh.GetNFbyType(ftype); - const Geometry::Type geom = mesh.GetFaceGeometry(0); + const Geometry::Type geom = mesh.GetTypicalFaceGeometry(); const int trial_order = trial_fes.GetMaxElementOrder(); const int test_order = test_fes.GetMaxElementOrder(); const int qorder = test_order + trial_order - 1; @@ -47,7 +47,7 @@ void NormalTraceJumpIntegrator::AssembleEAInteriorFaces( }); } - const FiniteElement &trial_face_el = *trial_fes.GetFaceElement(0); + const FiniteElement &trial_face_el = *trial_fes.GetTypicalTraceElement(); const auto maps = &trial_face_el.GetDofToQuad(ir, DofToQuad::TENSOR); const int ndof_face = trial_face_el.GetDof(); @@ -72,7 +72,7 @@ void NormalTraceJumpIntegrator::AssembleEAInteriorFaces( MFEM_ABORT("Unknown kernel."); } - const FiniteElement &test_el = *test_fes.GetFE(0); + const FiniteElement &test_el = *test_fes.GetTypicalFE(); const int n_faces_per_el = 2*dim; // assuming tensor product // Get all the local face maps (mapping from lexicographic face index to // lexicographic volume index, depending on the local face index). @@ -90,10 +90,10 @@ void NormalTraceJumpIntegrator::AssembleEAInteriorFaces( Array face_info(nf * 4); { int fidx = 0; - for (int f = 0; f < mesh.GetNumFaces(); ++f) + for (int f = 0; f < mesh.GetNumFacesWithGhost(); ++f) { Mesh::FaceInformation finfo = mesh.GetFaceInformation(f); - if (!finfo.IsInterior()) { continue; } + if (!finfo.IsInterior() || finfo.IsNonconformingCoarse()) { continue; } face_info[0 + fidx*4] = finfo.element[0].local_face_id; face_info[1 + fidx*4] = finfo.element[0].orientation; face_info[2 + fidx*4] = finfo.element[1].local_face_id; @@ -114,7 +114,7 @@ void NormalTraceJumpIntegrator::AssembleEAInteriorFaces( else { d_emat = emat.Write(); - mfem::forall(emat.Size(), [=] MFEM_HOST_DEVICE (int i) { d_emat[i] = 0.0; }); + emat = 0.0; // Will execute on device, since Write() sets the device flag } const auto face_mats = Reshape(mass_emat.Read(), ndof_face, ndof_face, nf); @@ -133,26 +133,104 @@ void NormalTraceJumpIntegrator::AssembleEAInteriorFaces( } }; - mfem::forall_3D(nf, ndof_face, ndof_face, 2, [=] MFEM_HOST_DEVICE (int f) + auto permute_face_2 = [=] MFEM_HOST_DEVICE(int local_face_1, int local_face_2, + int orient, int size1d, int index) { - MFEM_FOREACH_THREAD(el_i, z, 2) + if (dim == 2) { - const int lf_i = d_face_info(0, el_i, f); - const int orient = d_face_info(1, el_i, f); - // Loop over face indices in "native ordering" - MFEM_FOREACH_THREAD(i_lex, x, ndof_face) + return internal::PermuteFace2D(local_face_1, local_face_2, orient, + size1d, index); + } + else // dim == 3 + { + return internal::PermuteFace3D(local_face_1, local_face_2, orient, + size1d, index); + } + }; + + if (mesh.Conforming()) + { + mfem::forall_3D(nf, ndof_face, ndof_face, 2, [=] MFEM_HOST_DEVICE (int f) + { + MFEM_FOREACH_THREAD(el_i, z, 2) { - // Convert to lexicographic relative to the face itself - const int i_face = permute_face(lf_i, orient, d1d, i_lex); - // Convert from lexicographic face DOF to volume DOF - const int i = d_face_maps(i_lex, lf_i); - MFEM_FOREACH_THREAD(j, y, ndof_face) + const int lf_i = d_face_info(0, el_i, f); + const int orient = d_face_info(1, el_i, f); + // Loop over face indices in "native ordering" + MFEM_FOREACH_THREAD(i_lex, x, ndof_face) { - el_mats(i, j, el_i, f) += face_mats(i_face, j, f); + // Convert to lexicographic relative to the face itself + const int i_face = permute_face(lf_i, orient, d1d, i_lex); + // Convert from lexicographic face DOF to volume DOF + const int i = d_face_maps(i_lex, lf_i); + MFEM_FOREACH_THREAD(j, y, ndof_face) + { + el_mats(i, j, el_i, f) += face_mats(i_face, j, f); + } } } - } - }); + }); + } + else + { + const InterpolationManager &interp = + test_fes.GetInterpolationManager(ElementDofOrdering::LEXICOGRAPHIC, ftype); + + auto interp_configs = interp.GetFaceInterpConfig().Read(); + const int nc_size = interp.GetNumInterpolators(); + auto d_interp = Reshape(interp.GetInterpolators().Read(), + ndof_face, ndof_face, nc_size); + + mfem::forall(nf, [=] MFEM_HOST_DEVICE (int f) + { + const InterpConfig conf = interp_configs[f]; + const int master_side = conf.master_side; + const int interp_index = conf.index; + + const int lf_0 = d_face_info(0, 0, f); + + for (int el_i = 0; el_i < 2; ++el_i) + { + const int lf_i = d_face_info(0, el_i, f); + const int orient = d_face_info(1, el_i, f); + + for (int j = 0; j < ndof_face; j++) + { + for (int i_lex = 0; i_lex < ndof_face; i_lex++) + { + real_t val = 0.0; + if (conf.is_non_conforming && el_i == master_side) + { + // Interpolate from el_i (coarse element) to the fine face. + // The mapping is given by d_interp, which uses indices + // relative to element 0. + + // i0 is lexicographic relative to element 0 + const int i0 = permute_face_2(lf_i, lf_0, orient, d1d, i_lex); + + // k0 is lexicographic relative to element 0 + for (int k0 = 0; k0 < ndof_face; k0++) + { + // k is relative to the face itself + const int k = permute_face(lf_0, orient, d1d, k0); + val += d_interp(k0, i0, interp_index) + * face_mats(k, j, f); + } + } + else + { + // Convert to lexicographic relative to the face itself + const int i_face = permute_face(lf_i, orient, d1d, i_lex); + val = face_mats(i_face, j, f); + } + // Convert from lexicographic face DOF to volume DOF + const int i = d_face_maps(i_lex, lf_i); + el_mats(i, j, el_i, f) += val; + } + } + } + }); + } } } diff --git a/fem/integ/bilininteg_vecdiffusion_pa.hpp b/fem/integ/bilininteg_vecdiffusion_pa.hpp index 8fc388e6c0..ee3eaec00a 100644 --- a/fem/integ/bilininteg_vecdiffusion_pa.hpp +++ b/fem/integ/bilininteg_vecdiffusion_pa.hpp @@ -54,7 +54,7 @@ void SmemPAVectorDiffusionApply2D(const int NE, const auto XE = Reshape(x.Read(), D1D, D1D, SDIM, NE); auto YE = Reshape(y.ReadWrite(), D1D, D1D, SDIM, NE); - mfem::forall_2D(NE, Q1D, Q1D, [=] MFEM_HOST_DEVICE(int e) + mfem::forall_2D(NE, Q1D, Q1D, [=] MFEM_HOST_DEVICE(int e) { constexpr int MD1 = T_D1D > 0 ? SetMaxOf(T_D1D) : DofQuadLimits::MAX_T1D; constexpr int MQ1 = T_Q1D > 0 ? SetMaxOf(T_Q1D) : DofQuadLimits::MAX_T1D; @@ -120,7 +120,7 @@ void SmemPAVectorDiffusionApply3D(const int NE, const auto XE = Reshape(x.Read(), D1D, D1D, D1D, SDIM, NE); auto YE = Reshape(y.ReadWrite(), D1D, D1D, D1D, SDIM, NE); - mfem::forall_2D(NE, Q1D, Q1D, [=] MFEM_HOST_DEVICE(int e) + mfem::forall_2D(NE, Q1D, Q1D, [=] MFEM_HOST_DEVICE(int e) { constexpr int MD1 = T_D1D > 0 ? SetMaxOf(T_D1D) : DofQuadLimits::MAX_T1D; constexpr int MQ1 = T_Q1D > 0 ? SetMaxOf(T_Q1D) : DofQuadLimits::MAX_T1D; @@ -171,15 +171,15 @@ template VectorDiffusionIntegrator::ApplyKernelType VectorDiffusionIntegrator::ApplyPAKernels::Kernel() { - if (DIM == 2) + if constexpr (DIM == 2) { return internal::SmemPAVectorDiffusionApply2D; } - else if (DIM == 3) + else if constexpr (DIM == 3) { return internal::SmemPAVectorDiffusionApply3D; } - else { MFEM_ABORT("Unsupported kernel"); } + MFEM_ABORT("Unsupported kernel"); } inline VectorDiffusionIntegrator::ApplyKernelType diff --git a/fem/integ/bilininteg_vecmass_pa.hpp b/fem/integ/bilininteg_vecmass_pa.hpp index 09fc4bf755..cc94c1c6d9 100644 --- a/fem/integ/bilininteg_vecmass_pa.hpp +++ b/fem/integ/bilininteg_vecmass_pa.hpp @@ -51,7 +51,7 @@ void SmemPAVectorMassApply2D(const int NE, const auto X = Reshape(x.Read(), D1D, D1D, VDIM, NE); auto Y = Reshape(y.ReadWrite(), D1D, D1D, VDIM, NE); - mfem::forall_2D(NE, Q1D, Q1D, [=] MFEM_HOST_DEVICE(int e) + mfem::forall_2D(NE, Q1D, Q1D, [=] MFEM_HOST_DEVICE(int e) { constexpr int MD1 = T_D1D > 0 ? SetMaxOf(T_D1D) : DofQuadLimits::MAX_T1D; constexpr int MQ1 = T_Q1D > 0 ? SetMaxOf(T_Q1D) : DofQuadLimits::MAX_T1D; @@ -119,7 +119,7 @@ void SmemPAVectorMassApply3D(const int NE, const auto X = Reshape(x.Read(), D1D, D1D, D1D, VDIM, NE); auto Y = Reshape(y.ReadWrite(), D1D, D1D, D1D, VDIM, NE); - mfem::forall_2D(NE, Q1D, Q1D, [=] MFEM_HOST_DEVICE(int e) + mfem::forall_2D(NE, Q1D, Q1D, [=] MFEM_HOST_DEVICE(int e) { constexpr int MD1 = T_D1D > 0 ? SetMaxOf(T_D1D) : DofQuadLimits::MAX_T1D; constexpr int MQ1 = T_Q1D > 0 ? SetMaxOf(T_Q1D) : DofQuadLimits::MAX_T1D; @@ -182,15 +182,15 @@ template VectorMassIntegrator::VectorMassAddMultPAType VectorMassIntegrator::VectorMassAddMultPA::Kernel() { - if (DIM == 2) + if constexpr (DIM == 2) { return internal::SmemPAVectorMassApply2D; } - else if (DIM == 3) + else if constexpr (DIM == 3) { return internal::SmemPAVectorMassApply3D; } - else { MFEM_ABORT("Unsupported kernel"); } + MFEM_ABORT("Unsupported kernel"); } inline VectorMassIntegrator::VectorMassAddMultPAType diff --git a/fem/integ/lininteg_domain_kernels.hpp b/fem/integ/lininteg_domain_kernels.hpp index ec28ed1a7e..6985bff490 100644 --- a/fem/integ/lininteg_domain_kernels.hpp +++ b/fem/integ/lininteg_domain_kernels.hpp @@ -301,18 +301,14 @@ template DomainLFIntegrator::AssembleKernelType DomainLFIntegrator::AssembleKernels::Kernel() { - switch (DIM) - { - case 1: - return DLFEvalAssemble1D; - case 2: - return DLFEvalAssemble2D; - case 3: - return DLFEvalAssemble3D; - } + if constexpr (DIM == 1) { return DLFEvalAssemble1D; } + if constexpr (DIM == 2) { return DLFEvalAssemble2D; } + if constexpr (DIM == 3) { return DLFEvalAssemble3D; } MFEM_ABORT(""); } + /// \endcond DO_NOT_DOCUMENT } // namespace mfem -#endif + +#endif // MFEM_LININTEG_DOMAIN_KERNELS_HPP diff --git a/fem/kernel_dispatch.hpp b/fem/kernel_dispatch.hpp index cdd16a3138..5fb5a3b613 100644 --- a/fem/kernel_dispatch.hpp +++ b/fem/kernel_dispatch.hpp @@ -14,6 +14,7 @@ #include "../config/config.hpp" #include "kernel_reporter.hpp" +#include "../general/hash_util.hpp" #include #include #include @@ -86,35 +87,6 @@ namespace mfem } \ } -/// @brief Hashes variadic packs for which each type contained in the variadic -/// pack has a specialization of `std::hash` available. -/// -/// For example, packs containing int, bool, enum values, etc. -template -struct KernelDispatchKeyHash -{ -private: - template - size_t operator()(std::tuple value) const { return 0; } - - // The hashing formula here is taken directly from the Boost library, with - // the magic number 0x9e3779b9 chosen to minimize hashing collisions. - template - size_t operator()(std::tuple value) const - { - constexpr int Index = N - sizeof...(TTail) - 1; - auto lhs_hash = std::hash()(std::get(value)); - auto rhs_hash = operator()(value); - return lhs_hash^(rhs_hash + 0x9e3779b9 + (lhs_hash<<6) + (lhs_hash>>2)); - } -public: - /// Returns the hash of the given @a value. - size_t operator()(std::tuple value) const - { - return operator()(value); - } -}; - namespace internal { template struct KernelTypeList { }; } template class KernelDispatchTable { }; @@ -128,8 +100,8 @@ class KernelDispatchTable, internal::KernelTypeList> { - using TableType = std::unordered_map, - Signature, KernelDispatchKeyHash>; + using TableType = + std::unordered_map, Signature, TupleHasher>; TableType table; /// @brief Call function @a f with arguments @a args (perfect forwaring). diff --git a/fem/lor/lor.cpp b/fem/lor/lor.cpp index 02785051b5..1626e9e261 100644 --- a/fem/lor/lor.cpp +++ b/fem/lor/lor.cpp @@ -158,15 +158,16 @@ void LORBase::ConstructLocalDofPermutation(Array &perm_) const int i; i = dofmap_lor[off_lor + i1 + i2*2]; int s1 = i < 0 ? -1 : 1; - int idof_lor = vdof_lor[absdof(i)]; + int idof_lor = vdof_lor[UnsignIndex(i)]; i = dofmap_ho[off_ho + i1*n1 + i2*n2]; int s2 = i < 0 ? -1 : 1; - int idof_ho = vdof_ho[absdof(i)]; + int idof_ho = vdof_ho[UnsignIndex(i)]; int s3 = idof_lor < 0 ? -1 : 1; int s4 = idof_ho < 0 ? -1 : 1; int s = s1*s2*s3*s4; - i = absdof(idof_ho); - perm_[absdof(idof_lor)] = s < 0 ? -1-absdof(i) : absdof(i); + i = UnsignIndex(idof_ho); + perm_[UnsignIndex(idof_lor)] = s < 0 ? -1-UnsignIndex(i) : + UnsignIndex(i); } } }; @@ -232,7 +233,7 @@ void LORBase::ConstructDofPermutation() const int j = l_perm[i]; int s = j < 0 ? -1 : 1; int t_i = pfes_lor->GetLocalTDofNumber(i); - int t_j = pfes_ho->GetLocalTDofNumber(absdof(j)); + int t_j = pfes_ho->GetLocalTDofNumber(UnsignIndex(j)); // Either t_i and t_j both -1, or both non-negative if ((t_i < 0 && t_j >=0) || (t_j < 0 && t_i >= 0)) { diff --git a/fem/lor/lor.hpp b/fem/lor/lor.hpp index c611f083ca..efc0562b5f 100644 --- a/fem/lor/lor.hpp +++ b/fem/lor/lor.hpp @@ -57,8 +57,6 @@ private: /// values (after temporarily changing them for LOR assembly). void ResetIntegrationRules(GetIntegratorsFn get_integrators); - static inline int absdof(int i) { return i < 0 ? -1-i : i; } - protected: enum FESpaceType { H1, ND, RT, L2, INVALID }; diff --git a/fem/pbilinearform.cpp b/fem/pbilinearform.cpp index e948942903..4473c9cef4 100644 --- a/fem/pbilinearform.cpp +++ b/fem/pbilinearform.cpp @@ -488,10 +488,16 @@ void ParBilinearForm::FormLinearSystem( R.Mult(x, true_X); FormSystemMatrix(ess_tdof_list, A); - ConstrainedOperator *A_constrained; - Operator::FormConstrainedSystemOperator(ess_tdof_list, A_constrained); + + std::unique_ptr A_constrained([&]() + { + Operator *op; + Operator::FormSystemOperator(ess_tdof_list, op); + return dynamic_cast(op); + }()); + MFEM_ASSERT(A_constrained != nullptr, ""); + A_constrained->EliminateRHS(true_X, true_B); - delete A_constrained; R.MultTranspose(true_B, b); hybridization->ReduceRHS(true_B, B); X.SetSize(B.Size()); diff --git a/fem/pfespace.cpp b/fem/pfespace.cpp index cd20172f26..7bf4ea162a 100644 --- a/fem/pfespace.cpp +++ b/fem/pfespace.cpp @@ -424,7 +424,7 @@ void ParFiniteElementSpace::GetGroupComm( { if (ind[l] < 0) { - dofs[l] = m + (-1-ind[l]); + dofs[l] = m + FlipIndexSign(ind[l]); if (g_ldof_sign) { (*g_ldof_sign)[dofs[l]] = -1; @@ -462,7 +462,7 @@ void ParFiniteElementSpace::GetGroupComm( { if (ind[l] < 0) { - dofs[l] = m + (-1-ind[l]); + dofs[l] = m + FlipIndexSign(ind[l]); if (g_ldof_sign) { (*g_ldof_sign)[dofs[l]] = -1; @@ -500,7 +500,7 @@ void ParFiniteElementSpace::GetGroupComm( { if (ind[l] < 0) { - dofs[l] = m + (-1-ind[l]); + dofs[l] = m + FlipIndexSign(ind[l]); if (g_ldof_sign) { (*g_ldof_sign)[dofs[l]] = -1; @@ -538,16 +538,16 @@ void ParFiniteElementSpace::ApplyLDofSigns(Array &dofs) const { if (dofs[i] < 0) { - if (ldof_sign[-1-dofs[i]] < 0) + if (ldof_sign[FlipIndexSign(dofs[i])] < 0) { - dofs[i] = -1-dofs[i]; + dofs[i] = FlipIndexSign(dofs[i]); } } else { if (ldof_sign[dofs[i]] < 0) { - dofs[i] = -1-dofs[i]; + dofs[i] = FlipIndexSign(dofs[i]); } } } @@ -646,39 +646,38 @@ const FaceRestriction *ParFiniteElementSpace::GetFaceRestriction( auto itr = L2F.find(key); if (itr != L2F.end()) { - return itr->second; + return itr->second.get(); } else { - FaceRestriction *res; + std::unique_ptr res; if (is_dg_space) { if (Conforming()) { - res = new ParL2FaceRestriction(*this, f_ordering, type, m); + res.reset(new ParL2FaceRestriction(*this, f_ordering, type, m)); } else { - res = new ParNCL2FaceRestriction(*this, f_ordering, type, m); + res.reset(new ParNCL2FaceRestriction(*this, f_ordering, type, m)); } } else if (dynamic_cast(fec)) { - res = new L2InterfaceFaceRestriction(*this, f_ordering, type); + res.reset(new L2InterfaceFaceRestriction(*this, f_ordering, type)); } else { if (Conforming()) { - res = new ConformingFaceRestriction(*this, f_ordering, type); + res.reset(new ConformingFaceRestriction(*this, f_ordering, type)); } else { - res = new ParNCH1FaceRestriction(*this, f_ordering, type); + res.reset(new ParNCH1FaceRestriction(*this, f_ordering, type)); } } - L2F[key] = res; - return res; + return L2F.emplace(key, std::move(res)).first->second.get(); } } @@ -700,7 +699,8 @@ void ParFiniteElementSpace::GetSharedEdgeDofs( for (int i = 0; i < dofs.Size(); i++) { const int di = dofs[i]; - dofs[i] = (di >= 0) ? rdofs[di] : -1-rdofs[-1-di]; + dofs[i] = di >= 0 ? rdofs[di] : + FlipIndexSign(rdofs[FlipIndexSign(di)]); } } } @@ -724,7 +724,8 @@ void ParFiniteElementSpace::GetSharedTriangleDofs( for (int i = 0; i < dofs.Size(); i++) { const int di = dofs[i]; - dofs[i] = (di >= 0) ? rdofs[di] : -1-rdofs[-1-di]; + dofs[i] = di >= 0 ? rdofs[di] : + FlipIndexSign(rdofs[FlipIndexSign(di)]); } } } @@ -748,7 +749,8 @@ void ParFiniteElementSpace::GetSharedQuadrilateralDofs( for (int i = 0; i < dofs.Size(); i++) { const int di = dofs[i]; - dofs[i] = (di >= 0) ? rdofs[di] : -1-rdofs[-1-di]; + dofs[i] = (di >= 0) ? rdofs[di] : + FlipIndexSign(rdofs[FlipIndexSign(di)]); } } } @@ -1488,7 +1490,7 @@ void ParFiniteElementSpace::ExchangeFaceNbrData() GetElementVDofs(my_elems[i], ldofs); for (int j = 0; j < ldofs.Size(); j++) { - int ldof = (ldofs[j] >= 0 ? ldofs[j] : -1-ldofs[j]); + int ldof = UnsignIndex(ldofs[j]); if (ldof_marker[ldof] != fn) { @@ -1549,7 +1551,7 @@ void ParFiniteElementSpace::ExchangeFaceNbrData() GetElementVDofs(my_elems[i], ldofs); for (int j = 0; j < ldofs.Size(); j++) { - int ldof = (ldofs[j] >= 0 ? ldofs[j] : -1-ldofs[j]); + int ldof = UnsignIndex(ldofs[j]); if (ldof_marker[ldof] != fn) { @@ -1574,14 +1576,15 @@ void ParFiniteElementSpace::ExchangeFaceNbrData() for (int i = 0; i < num_ldofs; i++) { - int ldof = (ldofs_fn[i] >= 0 ? ldofs_fn[i] : -1-ldofs_fn[i]); + int ldof = UnsignIndex(ldofs_fn[i]); ldof_marker[ldof] = i; } for ( ; j < j_end; j++) { - int ldof = (send_J[j] >= 0 ? send_J[j] : -1-send_J[j]); - send_J[j] = (send_J[j] >= 0 ? ldof_marker[ldof] : -1-ldof_marker[ldof]); + const int ldof = UnsignIndex(send_J[j]); + send_J[j] = (send_J[j] >= 0 ? ldof_marker[ldof] : + FlipIndexSign(ldof_marker[ldof])); } } @@ -1673,12 +1676,7 @@ void ParFiniteElementSpace::ExchangeFaceNbrData() { for (int j_end = face_nbr_ldof.GetI()[fn+1]; j < j_end; j++) { - int ldof = face_nbr_ldof.GetJ()[j]; - if (ldof < 0) - { - ldof = -1-ldof; - } - + const int ldof = UnsignIndex(face_nbr_ldof.GetJ()[j]); face_nbr_glob_dof_map[j] = dof_face_nbr_offsets[fn] + ldof; } } @@ -1722,7 +1720,7 @@ void ParFiniteElementSpace::GetFaceNbrFaceVDofs(int i, Array &vdofs) const MFEM_ASSERT(Nonconforming() && i >= pmesh->GetNumFaces(), ""); int el1, el2, inf1, inf2; pmesh->GetFaceElements(i, &el1, &el2); - el2 = -1 - el2; + el2 = FlipIndexSign(el2); pmesh->GetFaceInfos(i, &inf1, &inf2); MFEM_ASSERT(0 <= el2 && el2 < face_nbr_element_dof.Size(), ""); const int nd = face_nbr_element_dof.RowSize(el2); @@ -1738,7 +1736,8 @@ void ParFiniteElementSpace::GetFaceNbrFaceVDofs(int i, Array &vdofs) const for (int j = 0; j < vdofs.Size(); j++) { const int ldof = vdofs[j]; - vdofs[j] = (ldof >= 0) ? vol_vdofs[ldof] : -1-vol_vdofs[-1-ldof]; + vdofs[j] = (ldof >= 0) ? vol_vdofs[ldof] : + FlipIndexSign(vol_vdofs[FlipIndexSign(ldof)]); } } @@ -2062,8 +2061,8 @@ void ParFiniteElementSpace::GetGhostFaceDofs(const MeshId &face_id, for (int j = 0; j < ne; j++) { - dofs[offset++] = (ind[j] >= 0) ? (first + ind[j]) - /* */ : (-1 - (first + (-1 - ind[j]))); + dofs[offset++] = (ind[j] >= 0) ? (first + ind[j]) : + FlipIndexSign(first + FlipIndexSign(ind[j])); } } else @@ -2073,8 +2072,8 @@ void ParFiniteElementSpace::GetGhostFaceDofs(const MeshId &face_id, const int *ind = fec->DofOrderForOrientation(Geometry::SEGMENT, Eo[i]); for (int j = 0; j < ne; j++) { - dofs[offset++] = (ind[j] >= 0) ? (first + ind[j]) - /* */ : (-1 - (first + (-1 - ind[j]))); + dofs[offset++] = (ind[j] >= 0) ? (first + ind[j]) : + FlipIndexSign(first + FlipIndexSign(ind[j])); } } } @@ -2867,7 +2866,7 @@ void NeighborRowMessage::Encode(int rank) if (ind && (edof = ind[edof]) < 0) { - edof = -1 - edof; + edof = FlipIndexSign(edof); s = -1; } @@ -3068,10 +3067,10 @@ void NeighborRowMessage::Decode(int rank) // If edof arrived with a negative index, flip it, and the scaling. real_t s = (edof < 0) ? -1.0 : 1.0; - edof = (edof < 0) ? -1 - edof : edof; + edof = UnsignIndex(edof); if (ind && (edof = ind[edof]) < 0) { - edof = -1 - edof; + edof = FlipIndexSign(edof); s *= -1.0; } @@ -3122,10 +3121,10 @@ void NeighborRowMessage::Decode(int rank) // If edof arrived with a negative index, flip it, and the scaling. s = (edof < 0) ? -1.0 : 1.0; - edof = (edof < 0) ? -1 - edof : edof; + edof = UnsignIndex(edof); if (ind && (edof = ind[edof]) < 0) { - edof = -1 - edof; + edof = FlipIndexSign(edof); s *= -1.0; } @@ -4406,12 +4405,9 @@ ParFiniteElementSpace::RebalanceMatrix(int old_ndofs, { for (int j = 0; j < dofs.Size(); j++) { - int row = DofToVDof(dofs[j], vd); - if (row < 0) { row = -1 - row; } - - int col = DofToVDof(old_dofs[j], vd, old_ndofs); - if (col < 0) { col = -1 - col; } - + const int row = UnsignIndex(DofToVDof(dofs[j], vd)); + const int col = UnsignIndex(DofToVDof(old_dofs[j], vd, + old_ndofs)); i_diag[row] = col; } } @@ -4436,9 +4432,7 @@ ParFiniteElementSpace::RebalanceMatrix(int old_ndofs, { for (int j = 0; j < dofs.Size(); j++) { - int row = DofToVDof(dofs[j], vd); - if (row < 0) { row = -1 - row; } - + const int row = UnsignIndex(DofToVDof(dofs[j], vd)); if (i_diag[row] == i_diag[row+1]) // diag row empty? { i_offd[row] = old_dofs[j + vd * dofs.Size()]; @@ -4547,9 +4541,9 @@ ParFiniteElementSpace::ParallelDerefinementMatrix(int old_ndofs, { const Embedding &emb = dtrans.embeddings[k]; - int fine_rank = old_ranks[k]; - int coarse_rank = (emb.parent < 0) ? (-1 - emb.parent) - : old_pncmesh->ElementRank(emb.parent); + const int fine_rank = old_ranks[k]; + const int coarse_rank = (emb.parent < 0) ? FlipIndexSign(emb.parent) + : old_pncmesh->ElementRank(emb.parent); if (coarse_rank != MyRank && fine_rank == MyRank) { @@ -4637,8 +4631,8 @@ ParFiniteElementSpace::ParallelDerefinementMatrix(int old_ndofs, { if (!std::isfinite(lR(i, 0))) { continue; } - int r = DofToVDof(dofs[i], vd); - int m = (r >= 0) ? r : (-1 - r); + const int r = DofToVDof(dofs[i], vd); + const int m = UnsignIndex(r); if (is_dg || !mark[m]) { @@ -4687,8 +4681,7 @@ ParFiniteElementSpace::ParallelDerefinementMatrix(int old_ndofs, { if (!std::isfinite(lR(i, 0))) { continue; } - int r = DofToVDof(dofs[i], vd); - int m = (r >= 0) ? r : (-1 - r); + const int m = UnsignIndex(DofToVDof(dofs[i], vd)); if (is_dg || !mark[m]) { diff --git a/fem/pfespace.hpp b/fem/pfespace.hpp index e190e43d1c..4dc1fec409 100644 --- a/fem/pfespace.hpp +++ b/fem/pfespace.hpp @@ -483,6 +483,8 @@ public: const FiniteElement *GetFaceNbrFaceFE(int i) const; const Array &GetFaceNbrGlobalDofMapArray() { return face_nbr_glob_dof_map; } const HYPRE_BigInt *GetFaceNbrGlobalDofMap() { return face_nbr_glob_dof_map; } + const Array &GetFaceNbrGlobalDofMapArray() const + { return face_nbr_glob_dof_map; } ElementTransformation *GetFaceNbrElementTransformation(int i) const { return pmesh->GetFaceNbrElementTransformation(i); } diff --git a/fem/pgridfunc.cpp b/fem/pgridfunc.cpp index 2bcbc31542..6bf10dcba7 100644 --- a/fem/pgridfunc.cpp +++ b/fem/pgridfunc.cpp @@ -1568,6 +1568,39 @@ PLBound ParGridFunction::GetBounds(Vector &lower, Vector &upper, return plb; } +std::pair ParGridFunction::EstimateFunctionMinimum( + const int vdim, const PLBound &plb, const int max_depth, + const real_t tol) const +{ + std::pair minmax = + GridFunction::EstimateFunctionMinimum(vdim, plb, max_depth, tol); + + real_t glob_min_lower = minmax.first; + real_t glob_min_upper = minmax.second; + MPI_Allreduce(MPI_IN_PLACE, &glob_min_lower, 1, + MFEM_MPI_REAL_T, MPI_MIN, pfes->GetComm()); + MPI_Allreduce(MPI_IN_PLACE, &glob_min_upper, 1, + MFEM_MPI_REAL_T, MPI_MIN, pfes->GetComm()); + + return std::make_pair(glob_min_lower, glob_min_upper); +} + +std::pair ParGridFunction::EstimateFunctionMaximum( + const int vdim, const PLBound &plb, const int max_depth, + const real_t tol) const +{ + std::pair minmax = + GridFunction::EstimateFunctionMaximum(vdim, plb, max_depth, tol); + + real_t glob_max_lower = minmax.first; + real_t glob_max_upper = minmax.second; + MPI_Allreduce(MPI_IN_PLACE, &glob_max_lower, 1, + MFEM_MPI_REAL_T, MPI_MAX, pfes->GetComm()); + MPI_Allreduce(MPI_IN_PLACE, &glob_max_upper, 1, + MFEM_MPI_REAL_T, MPI_MAX, pfes->GetComm()); + return std::make_pair(glob_max_lower, glob_max_upper); +} + } // namespace mfem #endif // MFEM_USE_MPI diff --git a/fem/pgridfunc.hpp b/fem/pgridfunc.hpp index b998adabab..668a0fa38a 100644 --- a/fem/pgridfunc.hpp +++ b/fem/pgridfunc.hpp @@ -609,6 +609,18 @@ public: PLBound GetBounds(Vector &lower, Vector &upper, const int ref_factor=1, const int vdim=-1) const override; + /** @brief Estimate the GridFunction minimum across all elements. */ + std::pair EstimateFunctionMinimum(const int vdim, + const PLBound &plb, + const int max_depth, + const real_t tol) const override; + + /** @brief Estimate the GridFunction maximum across all elements. */ + std::pair EstimateFunctionMaximum(const int vdim, + const PLBound &plb, + const int max_depth, + const real_t tol) const override; + /** Save the local portion of the ParGridFunction. This differs from the serial GridFunction::Save in that it takes into account the signs of the local dofs. */ diff --git a/fem/prestriction.cpp b/fem/prestriction.cpp index 8b089cf89f..2bc2ede749 100644 --- a/fem/prestriction.cpp +++ b/fem/prestriction.cpp @@ -994,7 +994,6 @@ void ParNCL2FaceRestriction::ComputeScatterIndicesAndOffsets() { if ( face.IsConforming() ) { - interpolations.RegisterFaceConformingInterpolation(face,f_ind); SetFaceDofsScatterIndices1(face,f_ind); if ( m==L2FaceValues::DoubleValued ) { @@ -1010,7 +1009,6 @@ void ParNCL2FaceRestriction::ComputeScatterIndicesAndOffsets() } else // Non-conforming face { - interpolations.RegisterFaceCoarseToFineInterpolation(face,f_ind); SetFaceDofsScatterIndices1(face,f_ind); if ( m==L2FaceValues::DoubleValued ) { @@ -1028,7 +1026,6 @@ void ParNCL2FaceRestriction::ComputeScatterIndicesAndOffsets() } else if (type==FaceType::Boundary && face.IsBoundary()) { - interpolations.RegisterFaceConformingInterpolation(face,f_ind); SetFaceDofsScatterIndices1(face,f_ind); if ( m==L2FaceValues::DoubleValued ) { @@ -1046,10 +1043,6 @@ void ParNCL2FaceRestriction::ComputeScatterIndicesAndOffsets() { gather_offsets[i] += gather_offsets[i - 1]; } - - // Transform the interpolation matrix map into a contiguous memory structure. - interpolations.LinearizeInterpolatorMapIntoVector(); - interpolations.InitializeNCInterpConfig(); } void ParNCL2FaceRestriction::ComputeGatherIndices() diff --git a/fem/prestriction.hpp b/fem/prestriction.hpp index 0fb55190c1..6ee4fbf6cd 100644 --- a/fem/prestriction.hpp +++ b/fem/prestriction.hpp @@ -326,9 +326,7 @@ public: @param[in] keep_nbr_block When set to true the SparseMatrix will include the rows (in addition to the columns) corresponding to face-neighbor dofs. The - default behavior is to disregard those rows. - - @warning This method is not implemented yet. */ + default behavior is to disregard those rows. */ void FillI(SparseMatrix &mat, const bool keep_nbr_block = false) const override; @@ -364,9 +362,7 @@ public: @param[in] keep_nbr_block When set to true the SparseMatrix will include the rows (in addition to the columns) corresponding to face-neighbor dofs. The - default behavior is to disregard those rows. - - @warning This method is not implemented yet. */ + default behavior is to disregard those rows. */ void FillJAndData(const Vector &fea_data, SparseMatrix &mat, const bool keep_nbr_block = false) const override; diff --git a/fem/qfunction.hpp b/fem/qfunction.hpp index 6545f4fa1e..328da87991 100644 --- a/fem/qfunction.hpp +++ b/fem/qfunction.hpp @@ -271,10 +271,7 @@ inline void QuadratureFunction::GetValues( const int s_offset = qspace->Offset(idx); const int sl_size = qspace->Offset(idx + 1) - s_offset; // Make the values matrix memory an alias of the quadrature function memory - Memory &values_mem = values.GetMemory(); - values_mem.Delete(); - values_mem.MakeAlias(GetMemory(), vdim*s_offset, vdim*sl_size); - values.SetSize(vdim, sl_size); + values.MakeRef(GetMemory(), vdim*s_offset, vdim, sl_size); } inline void QuadratureFunction::GetValues( diff --git a/fem/qinterp/det.cpp b/fem/qinterp/det.cpp index 304bfb8561..0501156928 100644 --- a/fem/qinterp/det.cpp +++ b/fem/qinterp/det.cpp @@ -50,7 +50,13 @@ QuadratureInterpolator::DetKernelType QuadratureInterpolator::DetKernels::Fallback( int DIM, int SDIM, int D1D, int Q1D) { - if (DIM == 1) { return internal::quadrature_interpolator::Det1D; } + if (DIM == 1) + { + if (SDIM == 1) { return internal::quadrature_interpolator::Det1D; } + else if (SDIM == 2) { return internal::quadrature_interpolator::Det1DSurface<0,0,2>; } + else if (SDIM == 3) { return internal::quadrature_interpolator::Det1DSurface<0,0,3>; } + else { MFEM_ABORT(""); } + } else if (DIM == 2 && SDIM == 2) { return internal::quadrature_interpolator::Det2D; } else if (DIM == 2 && SDIM == 3) { return internal::quadrature_interpolator::Det2DSurface; } else if (DIM == 3) diff --git a/fem/qinterp/det.hpp b/fem/qinterp/det.hpp index 403683de75..1c3c29e5c9 100644 --- a/fem/qinterp/det.hpp +++ b/fem/qinterp/det.hpp @@ -56,6 +56,50 @@ inline void Det1D(const int NE, }); } +template +inline void Det1DSurface(const int NE, + const real_t *b, + const real_t *g, + const real_t *x, + real_t *y, + const int d1d = 0, + const int q1d = 0, + Vector *d_buff = nullptr) +{ + MFEM_CONTRACT_VAR(b); + MFEM_CONTRACT_VAR(d_buff); + + const int D1D = T_D1D ? T_D1D : d1d; + const int Q1D = T_Q1D ? T_Q1D : q1d; + + const auto G = Reshape(g, Q1D, D1D); + const auto X = Reshape(x, D1D, T_SDIM, NE); + auto Y = Reshape(y, Q1D, NE); + + mfem::forall(NE, [=] MFEM_HOST_DEVICE (int e) + { + for (int q = 0; q < Q1D; q++) + { + real_t grad[T_SDIM]; + for (int s = 0; s < T_SDIM; s++) { grad[s] = 0.0; } + for (int d = 0; d < D1D; d++) + { + const real_t gval = G(q, d); + for (int s = 0; s < T_SDIM; s++) + { + grad[s] += gval * X(d, s, e); + } + } + real_t norm2 = 0.0; + for (int s = 0; s < T_SDIM; s++) + { + norm2 += grad[s] * grad[s]; + } + Y(q, e) = std::sqrt(norm2); + } + }); +} + template inline void Det2D(const int NE, const real_t *b, @@ -290,11 +334,16 @@ template QuadratureInterpolator::DetKernelType QuadratureInterpolator::DetKernels::Kernel() { - if (DIM == 1) { return internal::quadrature_interpolator::Det1D; } - else if (DIM == 2 && SDIM == 2) { return internal::quadrature_interpolator::Det2D; } - else if (DIM == 2 && SDIM == 3) { return internal::quadrature_interpolator::Det2DSurface; } - else if (DIM == 3) { return internal::quadrature_interpolator::Det3D; } - else { MFEM_ABORT(""); } + if constexpr (DIM == 1) + { + if constexpr (SDIM == 1) { return internal::quadrature_interpolator::Det1D; } + else if constexpr (SDIM == 2) { return internal::quadrature_interpolator::Det1DSurface; } + else if constexpr (SDIM == 3) { return internal::quadrature_interpolator::Det1DSurface; } + } + else if constexpr (DIM == 2 && SDIM == 2) { return internal::quadrature_interpolator::Det2D; } + else if constexpr (DIM == 2 && SDIM == 3) { return internal::quadrature_interpolator::Det2DSurface; } + else if constexpr (DIM == 3) { return internal::quadrature_interpolator::Det3D; } + MFEM_ABORT(""); } /// @endcond diff --git a/fem/qinterp/eval.hpp b/fem/qinterp/eval.hpp index 1d7da86342..22dd049373 100644 --- a/fem/qinterp/eval.hpp +++ b/fem/qinterp/eval.hpp @@ -203,10 +203,10 @@ template; } - else if (DIM == 2) { return internal::quadrature_interpolator::Values2D; } - else if (DIM == 3) { return internal::quadrature_interpolator::Values3D; } - else { MFEM_ABORT(""); } + if constexpr (DIM == 1) { return internal::quadrature_interpolator::Values1D; } + else if constexpr (DIM == 2) { return internal::quadrature_interpolator::Values2D; } + else if constexpr (DIM == 3) { return internal::quadrature_interpolator::Values3D; } + MFEM_ABORT(""); } /// @endcond diff --git a/fem/qinterp/eval_hdiv.hpp b/fem/qinterp/eval_hdiv.hpp index 8b5a750752..3237612be7 100644 --- a/fem/qinterp/eval_hdiv.hpp +++ b/fem/qinterp/eval_hdiv.hpp @@ -453,8 +453,15 @@ QuadratureInterpolator::TensorEvalHDivKernels::Kernel() { using namespace internal::quadrature_interpolator; static_assert(DIM == 2 || DIM == 3, "only DIM=2 and DIM=3 are implemented!"); - if (DIM == 2) { return EvalHDiv2D; } - return EvalHDiv3D; + if constexpr (DIM == 2) + { + return EvalHDiv2D; + } + else if constexpr (DIM == 3) + { + return EvalHDiv3D; + } + MFEM_ABORT("only DIM=2 and DIM=3 are implemented!"); } /// @endcond diff --git a/fem/qinterp/grad.hpp b/fem/qinterp/grad.hpp index 5226c21d44..4ec0714433 100644 --- a/fem/qinterp/grad.hpp +++ b/fem/qinterp/grad.hpp @@ -592,10 +592,10 @@ template; } - else if (DIM == 2) { return internal::quadrature_interpolator::Derivatives2D; } - else if (DIM == 3) { return internal::quadrature_interpolator::Derivatives3D; } - else { MFEM_ABORT(""); } + if constexpr (DIM == 1) { return internal::quadrature_interpolator::Derivatives1D; } + else if constexpr (DIM == 2) { return internal::quadrature_interpolator::Derivatives2D; } + else if constexpr (DIM == 3) { return internal::quadrature_interpolator::Derivatives3D; } + MFEM_ABORT(""); } template; } - else if (DIM == 2) { return internal::quadrature_interpolator::CollocatedDerivatives2D; } - else if (DIM == 3) { return internal::quadrature_interpolator::CollocatedDerivatives3D; } - else { MFEM_ABORT(""); } + if constexpr (DIM == 1) { return internal::quadrature_interpolator::CollocatedDerivatives1D; } + else if constexpr (DIM == 2) { return internal::quadrature_interpolator::CollocatedDerivatives2D; } + else if constexpr (DIM == 3) { return internal::quadrature_interpolator::CollocatedDerivatives3D; } + MFEM_ABORT(""); } /// @endcond diff --git a/fem/quadinterpolator.cpp b/fem/quadinterpolator.cpp index 9f53cf8bd7..a6df4b20cf 100644 --- a/fem/quadinterpolator.cpp +++ b/fem/quadinterpolator.cpp @@ -542,7 +542,8 @@ void QuadratureInterpolator::Mult(const Vector &e_vec, } MFEM_ASSERT(!(eval_flags & DETERMINANTS) || dim == vdim || - (dim == 2 && vdim == 3), "Invalid dimensions for determinants."); + (dim == 2 && vdim == 3) || (dim == 1 && vdim == 2) || + (dim == 1 && vdim == 3), "Invalid dimensions for determinants."); MFEM_ASSERT(fespace->GetMesh()->GetNumGeometries( fespace->GetMesh()->Dimension()) == 1, "mixed meshes are not supported"); @@ -751,10 +752,10 @@ template EvalKernel QuadratureInterpolator::EvalKernels::Kernel() { using namespace internal::quadrature_interpolator; - if (DIM == 1) { return Eval1D; } - else if (DIM == 2) { return Eval2D; } - else if (DIM == 3) { return Eval3D; } - else { MFEM_ABORT(""); } + if constexpr (DIM == 1) { return Eval1D; } + else if constexpr (DIM == 2) { return Eval2D; } + else if constexpr (DIM == 3) { return Eval3D; } + MFEM_ABORT(""); } template diff --git a/fem/restriction.cpp b/fem/restriction.cpp index 7680f8ed78..96a06c6b40 100644 --- a/fem/restriction.cpp +++ b/fem/restriction.cpp @@ -844,8 +844,6 @@ void ConformingFaceRestriction::ComputeGatherIndices( gather_offsets[0] = 0; } -static inline int absdof(int i) { return i < 0 ? -1-i : i; } - void ConformingFaceRestriction::SetFaceDofsScatterIndices( const Mesh::FaceInformation &face, const int face_index, @@ -868,9 +866,9 @@ void ConformingFaceRestriction::SetFaceDofsScatterIndices( { const int lex_volume_dof = face_map[face_dof]; const int s_volume_dof = AsConst(vol_dof_map)[lex_volume_dof]; // signed - const int volume_dof = absdof(s_volume_dof); + const int volume_dof = UnsignIndex(s_volume_dof); const int s_global_dof = elem_map[elem_index*elem_dofs + volume_dof]; - const int global_dof = absdof(s_global_dof); + const int global_dof = UnsignIndex(s_global_dof); const int restriction_dof = face_dofs*face_index + face_dof; scatter_indices[restriction_dof] = s_global_dof; ++gather_offsets[global_dof + 1]; @@ -897,10 +895,10 @@ void ConformingFaceRestriction::SetFaceDofsGatherIndices( { const int lex_volume_dof = face_map[face_dof]; const int s_volume_dof = AsConst(vol_dof_map)[lex_volume_dof]; - const int volume_dof = absdof(s_volume_dof); + const int volume_dof = UnsignIndex(s_volume_dof); const int s_global_dof = elem_map[elem_index*elem_dofs + volume_dof]; const int sgn = (s_global_dof >= 0) ? 1 : -1; - const int global_dof = absdof(s_global_dof); + const int global_dof = UnsignIndex(s_global_dof); const int restriction_dof = face_dofs*face_index + face_dof; const int s_restriction_dof = (sgn >= 0) ? restriction_dof : -1 - restriction_dof; @@ -1506,12 +1504,12 @@ void L2FaceRestriction::EnsureNormalDerivativeRestriction() const } } -InterpolationManager::InterpolationManager(const FiniteElementSpace &fes, - ElementDofOrdering ordering, +InterpolationManager::InterpolationManager(const FiniteElementSpace &fes_, + ElementDofOrdering ordering_, FaceType type) - : fes(fes), - ordering(ordering), - interp_config( fes.GetNFbyType(type) ), + : fes(fes_), + ordering(ordering_), + interp_config(fes.GetNFbyType(type)), nc_cpt(0) { } @@ -1536,7 +1534,8 @@ void InterpolationManager::RegisterFaceCoarseToFineInterpolation( face.element[0].local_face_id + 6*face.element[1].local_face_id + 36*face.element[1].orientation ; - // Unfortunately we can't trust unicity of the ptMat to identify the transformation. + // Unfortunately we can't trust uniqueness of the ptMat to identify the + // transformation. Key key(ptMat, face_key); auto itr = interp_map.find(key); if ( itr == interp_map.end() ) @@ -1583,17 +1582,27 @@ const DenseMatrix* InterpolationManager::GetCoarseToFineInterpolation( IsoparametricTransformation isotr; isotr.SetIdentityTransformation(trace_fe->GetGeomType()); isotr.SetPointMat(*ptMat); - DenseMatrix& trans_pt_mat = isotr.GetPointMat(); - // PointMatrix needs to be flipped in 2D - if ( trace_fe->GetGeomType()==Geometry::SEGMENT && !is_ghost_slave ) - { - std::swap(trans_pt_mat(0,0),trans_pt_mat(0,1)); - } DenseMatrix native_interpolator(face_dofs,face_dofs); trace_fe->GetLocalInterpolation(isotr, native_interpolator); + + if (trace_fe->GetMapType() == FiniteElement::INTEGRAL) + { + // Handle potentially inverted Jacobian matrix + isotr.SetIntPoint(&Geometries.GetCenter(trace_fe->GetGeomType())); + native_interpolator *= (isotr.Weight() >= 0) ? 1.0 : -1.0; + } + const int dim = trace_fe->GetDim()+1; const int dof1d = trace_fe->GetOrder()+1; - const int orientation = face.element[1].orientation; + int orientation_i = face.element[1].orientation; + const int orientation_j = face.element[1].orientation; + + // In 2D, need to flip orientation of the segments` + if (trace_fe->GetGeomType() == Geometry::SEGMENT && !is_ghost_slave) + { + orientation_i = 1; + } + for (int i = 0; i < face_dofs; i++) { const int ni = (dof_map.Size()==0) ? i : dof_map[i]; @@ -1602,7 +1611,7 @@ const DenseMatrix* InterpolationManager::GetCoarseToFineInterpolation( { // master side is elem 2, so we permute to order dofs as elem 1. li = PermuteFaceL2(dim, face_id2, face_id1, - orientation, dof1d, li); + orientation_i, dof1d, li); } for (int j = 0; j < face_dofs; j++) { @@ -1611,7 +1620,7 @@ const DenseMatrix* InterpolationManager::GetCoarseToFineInterpolation( { // master side is elem 2, so we permute to order dofs as elem 1. lj = PermuteFaceL2(dim, face_id2, face_id1, - orientation, dof1d, lj); + orientation_j, dof1d, lj); } const int nj = (dof_map.Size()==0) ? j : dof_map[j]; (*interpolator)(li,lj) = native_interpolator(ni,nj); @@ -1676,7 +1685,7 @@ NCL2FaceRestriction::NCL2FaceRestriction(const FiniteElementSpace &fes, const L2FaceValues m, bool build) : L2FaceRestriction(fes, f_ordering, type, m, false), - interpolations(fes, f_ordering, type) + interpolations(fes.GetInterpolationManager(ordering, type)) { if (!build) { return; } x_interp.UseDevice(true); @@ -2202,14 +2211,6 @@ void NCL2FaceRestriction::ComputeScatterIndicesAndOffsets() { PermuteAndSetFaceDofsScatterIndices2(face,f_ind); } - if ( face.IsConforming() ) - { - interpolations.RegisterFaceConformingInterpolation(face,f_ind); - } - else // Non-conforming face - { - interpolations.RegisterFaceCoarseToFineInterpolation(face,f_ind); - } f_ind++; } else if ( type==FaceType::Boundary && face.IsBoundary() ) @@ -2219,7 +2220,6 @@ void NCL2FaceRestriction::ComputeScatterIndicesAndOffsets() { SetBoundaryDofsScatterIndices2(face,f_ind); } - interpolations.RegisterFaceConformingInterpolation(face,f_ind); f_ind++; } } @@ -2232,10 +2232,6 @@ void NCL2FaceRestriction::ComputeScatterIndicesAndOffsets() { gather_offsets[i] += gather_offsets[i - 1]; } - - // Transform the interpolation matrix map into a contiguous memory structure. - interpolations.LinearizeInterpolatorMapIntoVector(); - interpolations.InitializeNCInterpConfig(); } void NCL2FaceRestriction::ComputeGatherIndices() @@ -2278,6 +2274,18 @@ void NCL2FaceRestriction::ComputeGatherIndices() gather_offsets[0] = 0; } +static int GetSharedVSize(const FiniteElementSpace &fes) +{ +#ifdef MFEM_USE_MPI + if (auto pfes = dynamic_cast(&fes)) + { + const_cast(pfes)->ExchangeFaceNbrData(); + return pfes->GetFaceNbrVSize(); + } +#endif + return 0; +} + L2InterfaceFaceRestriction::L2InterfaceFaceRestriction( const FiniteElementSpace& fes_, const ElementDofOrdering ordering_, @@ -2288,25 +2296,54 @@ L2InterfaceFaceRestriction::L2InterfaceFaceRestriction( nfaces(fes.GetNFbyType(type)), vdim(fes.GetVDim()), byvdim(fes.GetOrdering() == Ordering::byVDIM), - face_dofs(nfaces > 0 ? fes.GetFaceElement(0)->GetDof() : 0), + face_dofs(fes.GetTypicalTraceElement()->GetDof()), nfdofs(face_dofs*nfaces), - ndofs(fes.GetNDofs()) + ndofs(fes.GetNDofs()), + nsdofs(GetSharedVSize(fes)) { height = nfdofs; width = ndofs; +#ifdef MFEM_USE_MPI + auto pfes = dynamic_cast(&fes); +#endif + const Table &face2dof = fes.GetFaceToDofTable(); const Mesh &mesh = *fes.GetMesh(); int face_idx = 0; - gather_map.SetSize(nfdofs); - for (int f = 0; f < mesh.GetNumFaces(); ++f) + scatter_map.SetSize(nfdofs); + gather_map.SetSize(ndofs + nsdofs); + gather_map = -1; + + Array dofs; + for (int f = 0; f < mesh.GetNumFacesWithGhost(); ++f) { Mesh::FaceInformation face = mesh.GetFaceInformation(f); - if (!face.IsOfFaceType(type)) { continue; } - for (int i = 0; i < face_dofs; ++i) + if (!face.IsOfFaceType(type) || face.IsNonconformingCoarse()) { continue; } + + if (f < mesh.GetNumFaces()) { - gather_map[i + face_idx*face_dofs] = face2dof.GetJ()[i + f*face_dofs]; + // Local face + face2dof.GetRow(f, dofs); + for (int i = 0; i < face_dofs; ++i) + { + scatter_map[i + face_idx*face_dofs] = dofs[i]; + gather_map[dofs[i]] = i + face_idx*face_dofs; + } + } + else + { + // Shared (non-conforming) ghost face +#ifdef MFEM_USE_MPI + MFEM_ASSERT(pfes != nullptr, ""); + pfes->GetFaceNbrFaceVDofs(f, dofs); + for (int i = 0; i < face_dofs; ++i) + { + scatter_map[i + face_idx*face_dofs] = ndofs + dofs[i]; + gather_map[ndofs + dofs[i]] = i + face_idx*face_dofs; + } +#endif } ++face_idx; } @@ -2314,13 +2351,19 @@ L2InterfaceFaceRestriction::L2InterfaceFaceRestriction( void L2InterfaceFaceRestriction::Mult(const Vector &x, Vector &y) const { + const int NDOFS = ndofs; const int nd = face_dofs; const int nf = nfaces; const int vd = vdim; const bool t = byvdim; - const int *map = gather_map.Read(); + const int *map = scatter_map.Read(); + + Vector face_nbr_data = GetLVectorFaceNbrData(fes, x, type); + MFEM_ASSERT(face_nbr_data.Size() / vd == nsdofs, ""); const auto d_x = Reshape(x.Read(), t?vd:ndofs, t?ndofs:vd); + const auto d_x_shared = Reshape(face_nbr_data.Read(), + t?vd:nsdofs, t?nsdofs:vd); auto d_y = Reshape(y.Write(), nd, vd, nf); mfem::forall(nd*nf, [=] MFEM_HOST_DEVICE (int i) @@ -2328,7 +2371,8 @@ void L2InterfaceFaceRestriction::Mult(const Vector &x, Vector &y) const const int j = map[i]; for (int c = 0; c < vd; ++c) { - d_y(i % nd, c, i / nd) = d_x(t?c:j, t?j:c); + if (j < NDOFS) { d_y(i % nd, c, i / nd) = d_x(t?c:j, t?j:c); } + else { d_y(i % nd, c, i / nd) = d_x_shared(t?c:(j-NDOFS), t?(j-NDOFS):c); } } }); } @@ -2343,15 +2387,39 @@ void L2InterfaceFaceRestriction::AddMultTranspose( const int *map = gather_map.Read(); const auto d_x = Reshape(x.Read(), nd, vd, nf); - auto d_y = Reshape(y.Write(), t?vd:ndofs, t?ndofs:vd); + auto d_y = Reshape(y.ReadWrite(), t?vd:ndofs, t?ndofs:vd); - mfem::forall(ndofs, [=] MFEM_HOST_DEVICE (int i) { d_y[i] = 0.0; }); - mfem::forall(nd*nf, [=] MFEM_HOST_DEVICE (int i) + mfem::forall(ndofs, [=] MFEM_HOST_DEVICE (int i) { const int j = map[i]; + if (j < 0) { return; } for (int c = 0; c < vd; ++c) { - d_y(t?c:j, t?j:c) = d_x(i % nd, c, i / nd); + d_y(t?c:i, t?i:c) += a*d_x(j % nd, c, j / nd); + } + }); +} + +void L2InterfaceFaceRestriction::MultTransposeShared( + const Vector &x, Vector &y) const +{ + const int nd = face_dofs; + const int nf = nfaces; + const int vd = vdim; + const bool t = byvdim; + const int *map = gather_map.Read(); + + const auto d_x = Reshape(x.Read(), nd, vd, nf); + auto d_y = Reshape(y.Write(), t?vd:(ndofs+nsdofs), t?(ndofs+nsdofs):vd); + y = 0.0; + + mfem::forall(ndofs + nsdofs, [=] MFEM_HOST_DEVICE (int i) + { + const int j = map[i]; + if (j < 0) { return; } + for (int c = 0; c < vd; ++c) + { + d_y(t?c:i, t?i:c) = d_x(j % nd, c, j / nd); } }); } @@ -2361,6 +2429,11 @@ const Array &L2InterfaceFaceRestriction::GatherMap() const return gather_map; } +const Array &L2InterfaceFaceRestriction::ScatterMap() const +{ + return scatter_map; +} + Vector GetLVectorFaceNbrData( const FiniteElementSpace &fes, const Vector &x, FaceType ftype) { @@ -2382,6 +2455,7 @@ Vector GetLVectorFaceNbrData( { ParGridFunction gf(pfes, const_cast(x)); gf.ExchangeFaceNbrData(); + x.SyncMemory(gf); return std::move(gf.FaceNbrData()); } } diff --git a/fem/restriction.hpp b/fem/restriction.hpp index 279717c357..39e2320fe1 100644 --- a/fem/restriction.hpp +++ b/fem/restriction.hpp @@ -812,13 +812,12 @@ protected: PointMatrix and a local face identifier. */ using Key = std::pair; /// The temporary map used to store the different interpolators. - using Map = std::map>; + using Map = + std::unordered_map, PairHasher>; Map interp_map; // The temporary map that stores the interpolators. public: - InterpolationManager() = delete; - - /** @brief main constructor. + /** @brief Constructor. @param[in] fes The FiniteElementSpace on which this operates @param[in] ordering Request a specific element ordering. @@ -909,7 +908,7 @@ private: class NCL2FaceRestriction : virtual public L2FaceRestriction { protected: - InterpolationManager interpolations; + const InterpolationManager &interpolations; mutable Vector x_interp; /** @brief Constructs an NCL2FaceRestriction, this is a specialization of a @@ -996,9 +995,7 @@ public: @param[in] keep_nbr_block When set to true the SparseMatrix will include the rows (in addition to the columns) corresponding to face-neighbor dofs. The - default behavior is to disregard those rows. - - @warning This method is not implemented yet. */ + default behavior is to disregard those rows. */ void FillI(SparseMatrix &mat, const bool keep_nbr_block = false) const override; @@ -1016,9 +1013,7 @@ public: @param[in] keep_nbr_block When set to true the SparseMatrix will include the rows (in addition to the columns) corresponding to face-neighbor dofs. The - default behavior is to disregard those rows. - - @warning This method is not implemented yet. */ + default behavior is to disregard those rows. */ void FillJAndData(const Vector &fea_data, SparseMatrix &mat, const bool keep_nbr_block = false) const override; @@ -1036,9 +1031,7 @@ public: added the face contributions. The format is: dofs x dofs x ne, where dofs is the number of dofs per element and ne the number of - elements. - - @warning This method is not implemented yet. */ + elements. */ void AddFaceMatricesToElementMatrices(const Vector &fea_data, Vector &ea_data) const override; @@ -1130,7 +1123,9 @@ protected: const int face_dofs; ///< Number of dofs on each face const int nfdofs; ///< Total number of dofs on the faces (E-vector size) const int ndofs; ///< Number of dofs in the space (L-vector size) + const int nsdofs; ///< Number of shared face neighbor (ghost) dofs Array gather_map; ///< Gather map + Array scatter_map; ///< Scatter map public: /** @brief Constructs an L2InterfaceFaceRestriction. @@ -1168,7 +1163,24 @@ public: void AddMultTranspose(const Vector &x, Vector &y, const real_t a = 1.0) const override; + /// @brief Gather degrees of freedom, from face E-vector to L-vector and + /// shared (ghost) DOFs. + /// + /// @param[in] x The face E-Vector degrees of freedom with size + /// (face_dofs, vdim, nf), where nf is the number of + /// interior or boundary faces requested by @a type in the + /// constructor. The face_dofs should be ordered according + /// to the given ElementDofOrdering + /// @param[out] y Vector of length vsize + face neighbor vsize + void MultTransposeShared(const Vector &x, Vector &y) const; + const Array &GatherMap() const override; + + /// @brief Return the low-level mapping from L-dofs to E-dofs. + /// + /// L-dofs that do not correspond to an E-dof (e.g. that lie on a face of a + /// different type) are given index -1. + const Array &ScatterMap() const; }; /** @brief Convert a dof face index from Native ordering to lexicographic diff --git a/fem/tmop.cpp b/fem/tmop.cpp index 3b3bb7628c..7bc6b4b61e 100644 --- a/fem/tmop.cpp +++ b/fem/tmop.cpp @@ -4102,8 +4102,11 @@ void TMOP_Integrator::GetSurfaceFittingErrors(const Vector &d_loc, #ifdef MFEM_USE_MPI // Don't count the overlapping DOFs in parallel. // The pfes might be ordered byVDIM, while the loop goes consecutively. - const int dof_i = pfes->DofToVDof(i, 0); - if (parallel && pfes->GetLocalTDofNumber(dof_i) < 0) { continue; } + if (parallel) + { + const int dof_i = pfes->DofToVDof(i, 0); + if (pfes->GetLocalTDofNumber(dof_i) < 0) { continue; } + } #endif dof_cnt++; diff --git a/fem/transfer.cpp b/fem/transfer.cpp index befd455d1c..c252b70db0 100644 --- a/fem/transfer.cpp +++ b/fem/transfer.cpp @@ -333,6 +333,12 @@ void L2ProjectionGridTransfer::L2Projection::MixedMassEA( int nel_ho = mesh_ho->GetNE(); int nel_lor = mesh_lor->GetNE(); + if (nel_ho == 0) + { + M_LH.SetSize(0); + return; + } + const CoarseFineTransformations& cf_tr = mesh_lor->GetRefinementTransforms(); int nref_max = 0; @@ -831,11 +837,17 @@ void L2ProjectionGridTransfer::L2ProjectionL2Space::Mult( void L2ProjectionGridTransfer::L2ProjectionL2Space::EAMult( const Vector &x, Vector &y) const { + const int nel_ho = fes_ho.GetMesh()->GetNE(); + + if (nel_ho == 0) + { + return; + } + const int iho = 0; const int nref = ho2lor.RowSize(iho); const int ndof_ho = fes_ho.GetFE(iho)->GetDof(); const int ndof_lor = fes_lor.GetFE(ho2lor.GetRow(iho)[0])->GetDof(); - const int nel_ho = fes_ho.GetMesh()->GetNE(); DenseTensor R_dt; R_dt.NewMemoryAndSize(R.GetMemory(), ndof_lor*nref, ndof_ho, nel_ho, false); @@ -887,11 +899,17 @@ void L2ProjectionGridTransfer::L2ProjectionL2Space::MultTranspose( void L2ProjectionGridTransfer::L2ProjectionL2Space::EAMultTranspose( const Vector &x, Vector &y) const { + const int nel_ho = fes_ho.GetMesh()->GetNE(); + + if (nel_ho == 0) + { + return; + } + const int iho = 0; const int nref = ho2lor.RowSize(iho); const int ndof_ho = fes_ho.GetFE(iho)->GetDof(); const int ndof_lor = fes_lor.GetFE(ho2lor.GetRow(iho)[0])->GetDof(); - const int nel_ho = fes_ho.GetMesh()->GetNE(); DenseTensor R_dt; R_dt.NewMemoryAndSize(R.GetMemory(), ndof_lor*nref, ndof_ho, nel_ho, false); @@ -901,7 +919,6 @@ void L2ProjectionGridTransfer::L2ProjectionL2Space::EAMultTranspose( void L2ProjectionGridTransfer::L2ProjectionL2Space::Prolongate( const Vector &x, Vector &y) const { - if (fes_ho.GetNE() == 0) { return; } if (use_ea) @@ -960,14 +977,13 @@ void L2ProjectionGridTransfer::L2ProjectionL2Space::EAProlongate( void L2ProjectionGridTransfer::L2ProjectionL2Space::ProlongateTranspose( const Vector &x, Vector &y) const { + if (fes_ho.GetNE() == 0) { return; } if (use_ea) { return EAProlongateTranspose(x,y); } - - if (fes_ho.GetNE() == 0) { return; } MFEM_VERIFY(P.Size() > 0, "Prolongation not supported for these spaces.") int vdim = fes_ho.GetVDim(); Array vdofs; @@ -1244,13 +1260,6 @@ void L2ProjectionGridTransfer::L2ProjectionH1Space::EAL2ProjectionH1Space int ndof_ho = pfes_ho.GetNDofs(); int ndof_lor = pfes_lor.GetNDofs(); - - // If the local mesh is empty, skip all computations - if (nel_ho == 0) - { - return; - } - const CoarseFineTransformations& cf_tr = mesh_lor->GetRefinementTransforms(); int nref_max = 0; @@ -1860,6 +1869,11 @@ L2ProjectionGridTransfer::H1SpaceMixedMassOperator::H1SpaceMixedMassOperator( void L2ProjectionGridTransfer::H1SpaceMixedMassOperator::Mult(const Vector &x, Vector &y) const { + if (fes_ho->GetNE() == 0) + { + return; + } + const Operator* elem_restrict_ho = fes_ho->GetElementRestriction( ElementDofOrdering::NATIVE); const Operator* elem_restrict_lor = fes_lor->GetElementRestriction( @@ -1906,6 +1920,11 @@ void L2ProjectionGridTransfer::H1SpaceMixedMassOperator::Mult(const Vector &x, void L2ProjectionGridTransfer::H1SpaceMixedMassOperator::MultTranspose( const Vector &x, Vector &y) const { + if (fes_ho->GetNE() == 0) + { + return; + } + const Operator* elem_restrict_ho = fes_ho->GetElementRestriction( ElementDofOrdering::NATIVE); const Operator* elem_restrict_lor = fes_lor->GetElementRestriction( diff --git a/general/CMakeLists.txt b/general/CMakeLists.txt index 1dced95090..e34dfa7ea4 100644 --- a/general/CMakeLists.txt +++ b/general/CMakeLists.txt @@ -18,6 +18,7 @@ list(APPEND SRCS gecko.cpp globals.cpp hash.cpp + hash_util.cpp isockstream.cpp mem_manager.cpp occa.cpp @@ -46,6 +47,7 @@ list(APPEND HDRS globals.hpp zstr.hpp hash.hpp + hash_util.hpp isockstream.hpp kdtree.hpp mem_alloc.hpp diff --git a/general/array.hpp b/general/array.hpp index 8ea01dbd8b..ed998c6506 100644 --- a/general/array.hpp +++ b/general/array.hpp @@ -114,10 +114,22 @@ public: Array &operator=(const Array &src) { src.Copy(*this); return *this; } /// Move assignment operator + /** If *this is a non-owning view (e.g., from MakeRef()), the data is copied + so that the base is also modified. */ Array &operator=(Array &&src) { if (this == &src) { return *this; } - Swap(src); // Swap does not use move assignment! + // If *this is a non-owning view (alias), and its capacity is sufficient + // to contain src, then copy into *this so that the alias's base memory is + // modified. + if (!OwnsData() && Capacity() >= src.Size()) + { + *this = src; // Copy assignment. + } + else + { + Swap(src); // Swap the pointers only. + } src.DeleteAll(); return *this; } @@ -251,6 +263,9 @@ public: /// Make this Array a reference to 'master'. inline void MakeRef(const Array &master); + /// Make this Array a reference to the given sub-Memory of @a base. + inline void MakeRef(Memory &base, int offset, int size_); + /// Reset the Array to use the given external Memory @a mem and size @a s. /** If @a own_mem is false, the Array will not own any of the pointers of @a mem. @@ -1073,6 +1088,14 @@ inline void Array::MakeRef(const Array &master) data.MakeAlias(master.GetMemory(), 0, size); } +template +inline void Array::MakeRef(Memory &base, int offset, int size_) +{ + data.Delete(); + size = size_; + data.MakeAlias(base, offset, size_); +} + template inline void Array::NewMemoryAndSize( const Memory &mem, int s, bool own_mem) diff --git a/general/backends.hpp b/general/backends.hpp index 2778feed38..9ff8a60799 100644 --- a/general/backends.hpp +++ b/general/backends.hpp @@ -44,6 +44,7 @@ #endif #if !defined(MFEM_USE_CUDA_OR_HIP) +constexpr bool mfem_use_gpu = false; #define MFEM_DEVICE #define MFEM_HOST #define MFEM_LAMBDA @@ -52,6 +53,7 @@ #define MFEM_DEVICE_SYNC // MFEM_STREAM_SYNC is used for UVM and MPI GPU-Aware kernels #define MFEM_STREAM_SYNC +#define MFEM_LAUNCH_BOUNDS(...) #endif #if !((defined(MFEM_USE_CUDA) && defined(__CUDA_ARCH__)) || \ diff --git a/general/cuda.hpp b/general/cuda.hpp index d41557cc9a..5234757bf0 100644 --- a/general/cuda.hpp +++ b/general/cuda.hpp @@ -20,9 +20,11 @@ #if defined(MFEM_USE_CUDA) && defined(__CUDACC__) #define MFEM_USE_CUDA_OR_HIP +constexpr bool mfem_use_gpu = true; #define MFEM_DEVICE __device__ #define MFEM_HOST __host__ #define MFEM_LAMBDA __host__ +#define MFEM_LAUNCH_BOUNDS __launch_bounds__ // #define MFEM_HOST_DEVICE __host__ __device__ // defined in config/config.hpp #define MFEM_DEVICE_SYNC MFEM_GPU_CHECK(cudaDeviceSynchronize()) #define MFEM_STREAM_SYNC MFEM_GPU_CHECK(cudaStreamSynchronize(0)) diff --git a/general/forall.hpp b/general/forall.hpp index 09561b7434..60e13963a2 100644 --- a/general/forall.hpp +++ b/general/forall.hpp @@ -295,11 +295,12 @@ using hip_threads_z = #endif #if defined(MFEM_USE_RAJA) && defined(RAJA_ENABLE_CUDA) && defined(__CUDACC__) -template +template void RajaCuWrap1D(const int N, DBODY &&d_body) { //true denotes asynchronous kernel - RAJA::forall>(RAJA::RangeSegment(0,N),d_body); + RAJA::forall>(RAJA::RangeSegment(0,N), + d_body); } template @@ -362,18 +363,18 @@ struct RajaCuWrap; template <> struct RajaCuWrap<1> { - template + template static void run(const int N, DBODY &&d_body, const int X, const int Y, const int Z, const int G) { - RajaCuWrap1D(N, d_body); + RajaCuWrap1D(N, d_body); } }; template <> struct RajaCuWrap<2> { - template + template static void run(const int N, DBODY &&d_body, const int X, const int Y, const int Z, const int G) { @@ -384,7 +385,7 @@ struct RajaCuWrap<2> template <> struct RajaCuWrap<3> { - template + template static void run(const int N, DBODY &&d_body, const int X, const int Y, const int Z, const int G) { @@ -395,11 +396,12 @@ struct RajaCuWrap<3> #endif #if defined(MFEM_USE_RAJA) && defined(RAJA_ENABLE_HIP) && defined(__HIP__) -template +template void RajaHipWrap1D(const int N, DBODY &&d_body) { //true denotes asynchronous kernel - RAJA::forall>(RAJA::RangeSegment(0,N),d_body); + RAJA::forall>(RAJA::RangeSegment(0,N), + d_body); } template @@ -462,18 +464,18 @@ struct RajaHipWrap; template <> struct RajaHipWrap<1> { - template + template static void run(const int N, DBODY &&d_body, const int X, const int Y, const int Z, const int G) { - RajaHipWrap1D(N, d_body); + RajaHipWrap1D(N, d_body); } }; template <> struct RajaHipWrap<2> { - template + template static void run(const int N, DBODY &&d_body, const int X, const int Y, const int Z, const int G) { @@ -484,7 +486,7 @@ struct RajaHipWrap<2> template <> struct RajaHipWrap<3> { - template + template static void run(const int N, DBODY &&d_body, const int X, const int Y, const int Z, const int G) { @@ -584,12 +586,31 @@ void CuKernel2D(const int N, BODY body) body(k); } +// __launch_bounds__ second argument is omitted to get the default behavior +template +__global__ +MFEM_LAUNCH_BOUNDS(MAX_THREADS_PER_BLOCK) +static void CuKernel2DLaunchBounds(const int N, BODY body) +{ + const int k = blockIdx.x*blockDim.z + threadIdx.z; + if (k >= N) { return; } + body(k); +} + template __global__ static void CuKernel3D(const int N, BODY body) { for (int k = blockIdx.x; k < N; k += gridDim.x) { body(k); } } +template +__global__ +MFEM_LAUNCH_BOUNDS(MAX_THREADS_PER_BLOCK) +static void CuKernel3DLaunchBounds(const int N, BODY body) +{ + for (int k = blockIdx.x; k < N; k += gridDim.x) { body(k); } +} + template void CuWrap1D(const int N, DBODY &&d_body) { @@ -604,6 +625,8 @@ void CuWrap2D(const int N, DBODY &&d_body, const int X, const int Y, const int BZ) { if (N==0) { return; } + // required for optimized GCC/NVCC builds to prevent runtime + // ODR/linkage violations of inlined templated kernel helpers MFEM_VERIFY(BZ>0, ""); const int GRID = (N+BZ-1)/BZ; const dim3 BLCK(X,Y,BZ); @@ -611,6 +634,19 @@ void CuWrap2D(const int N, DBODY &&d_body, MFEM_GPU_CHECK(cudaGetLastError()); } +template +void CuWrap2DLaunchBounds(const int N, DBODY &&d_body, + const int X, const int Y, const int BZ) +{ + if (N==0) { return; } + MFEM_VERIFY(BZ>0, ""); + const int GRID = (N+BZ-1)/BZ; + const dim3 BLCK(X,Y,BZ); + static_assert(MAX_THREADS_PER_BLOCK > 0); + CuKernel2DLaunchBounds<<>>(N, d_body); + MFEM_GPU_CHECK(cudaGetLastError()); +} + template void CuWrap3D(const int N, DBODY &&d_body, const int X, const int Y, const int Z, const int G) @@ -622,24 +658,35 @@ void CuWrap3D(const int N, DBODY &&d_body, MFEM_GPU_CHECK(cudaGetLastError()); } -template -struct CuWrap; - -template <> -struct CuWrap<1> +template +void CuWrap3DLaunchBounds(const int N, DBODY &&d_body, + const int X, const int Y, const int Z, const int G) { - template + if (N==0) { return; } + const int GRID = G == 0 ? N : G; + const dim3 BLCK(X,Y,Z); + static_assert(MAX_THREADS_PER_BLOCK > 0); + CuKernel3DLaunchBounds<<>>(N, d_body); + MFEM_GPU_CHECK(cudaGetLastError()); +} + +template struct CuWrap; + +template +struct CuWrap<1, MAX_THREADS_PER_BLOCK> +{ + template static void run(const int N, DBODY &&d_body, const int X, const int Y, const int Z, const int G) { - CuWrap1D(N, d_body); + CuWrap1D(N, d_body); } }; template <> -struct CuWrap<2> +struct CuWrap<2, 0> { - template + template static void run(const int N, DBODY &&d_body, const int X, const int Y, const int Z, const int G) { @@ -647,10 +694,22 @@ struct CuWrap<2> } }; -template <> -struct CuWrap<3> +template +struct CuWrap<2, MAX_THREADS_PER_BLOCK> { - template + template + static void run(const int N, DBODY &&d_body, + const int X, const int Y, const int Z, const int G) + { + static_assert(MAX_THREADS_PER_BLOCK > 0); + CuWrap2DLaunchBounds(N, d_body, X, Y, Z); + } +}; + +template <> +struct CuWrap<3, 0> +{ + template static void run(const int N, DBODY &&d_body, const int X, const int Y, const int Z, const int G) { @@ -658,6 +717,17 @@ struct CuWrap<3> } }; +template +struct CuWrap<3, MAX_THREADS_PER_BLOCK> +{ + template + static void run(const int N, DBODY &&d_body, + const int X, const int Y, const int Z, const int G) + { + CuWrap3DLaunchBounds(N, d_body, X, Y, Z, G); + } +}; + #endif // defined(MFEM_USE_CUDA) && defined(__CUDACC__) @@ -680,13 +750,31 @@ void HipKernel2D(const int N, BODY body) body(k); } +template +__global__ +MFEM_LAUNCH_BOUNDS(MAX_THREADS_PER_BLOCK) +static void HipKernel2DLaunchBounds(const int N, BODY body) +{ + const int k = hipBlockIdx_x*hipBlockDim_z + hipThreadIdx_z; + if (k >= N) { return; } + body(k); +} + template __global__ static void HipKernel3D(const int N, BODY body) { for (int k = hipBlockIdx_x; k < N; k += hipGridDim_x) { body(k); } } -template +template +__global__ +MFEM_LAUNCH_BOUNDS(MAX_THREADS_PER_BLOCK) +static void HipKernel3DLaunchBounds(const int N, BODY body) +{ + for (int k = hipBlockIdx_x; k < N; k += hipGridDim_x) { body(k); } +} + +template void HipWrap1D(const int N, DBODY &&d_body) { if (N==0) { return; } @@ -700,12 +788,27 @@ void HipWrap2D(const int N, DBODY &&d_body, const int X, const int Y, const int BZ) { if (N==0) { return; } + MFEM_VERIFY(BZ>0, ""); const int GRID = (N+BZ-1)/BZ; const dim3 BLCK(X,Y,BZ); hipLaunchKernelGGL(HipKernel2D,GRID,BLCK,0,nullptr,N,d_body); MFEM_GPU_CHECK(hipGetLastError()); } +template +void HipWrap2DLaunchBounds(const int N, DBODY &&d_body, + const int X, const int Y, const int BZ) +{ + if (N==0) { return; } + MFEM_VERIFY(BZ>0, ""); + const int GRID = (N+BZ-1)/BZ; + const dim3 BLCK(X,Y,BZ); + static_assert(MAX_THREADS_PER_BLOCK > 0); + HipKernel2DLaunchBounds<<>> + (N, d_body); + MFEM_GPU_CHECK(hipGetLastError()); +} + template void HipWrap3D(const int N, DBODY &&d_body, const int X, const int Y, const int Z, const int G) @@ -717,24 +820,36 @@ void HipWrap3D(const int N, DBODY &&d_body, MFEM_GPU_CHECK(hipGetLastError()); } -template -struct HipWrap; - -template <> -struct HipWrap<1> +template +void HipWrap3DLaunchBounds(const int N, DBODY &&d_body, + const int X, const int Y, const int Z, const int G) { - template + if (N==0) { return; } + const int GRID = G == 0 ? N : G; + const dim3 BLCK(X,Y,Z); + static_assert(MAX_THREADS_PER_BLOCK > 0); + HipKernel3DLaunchBounds<<>> + (N, d_body); + MFEM_GPU_CHECK(hipGetLastError()); +} + +template struct HipWrap; + +template +struct HipWrap<1, MAX_THREADS_PER_BLOCK> +{ + template static void run(const int N, DBODY &&d_body, const int X, const int Y, const int Z, const int G) { - HipWrap1D(N, d_body); + HipWrap1D(N, d_body); } }; template <> -struct HipWrap<2> +struct HipWrap<2, 0> { - template + template static void run(const int N, DBODY &&d_body, const int X, const int Y, const int Z, const int G) { @@ -742,10 +857,21 @@ struct HipWrap<2> } }; -template <> -struct HipWrap<3> +template +struct HipWrap<2, MAX_THREADS_PER_BLOCK> { - template + template + static void run(const int N, DBODY &&d_body, + const int X, const int Y, const int Z, const int G) + { + HipWrap2DLaunchBounds(N, d_body, X, Y, Z); + } +}; + +template <> +struct HipWrap<3, 0> +{ + template static void run(const int N, DBODY &&d_body, const int X, const int Y, const int Z, const int G) { @@ -753,11 +879,24 @@ struct HipWrap<3> } }; +template +struct HipWrap<3, MAX_THREADS_PER_BLOCK> +{ + template + static void run(const int N, DBODY &&d_body, + const int X, const int Y, const int Z, const int G) + { + HipWrap3DLaunchBounds(N, d_body, X, Y, Z, G); + } +}; + #endif // defined(MFEM_USE_HIP) && defined(__HIP__) -/// The forall kernel body wrapper -template +/////////////////////////////////////////////////////////////////////////////// +/// Forall host & device kernel dispatch +template inline void ForallWrap(const bool use_dev, const int N, d_lambda &&d_body, h_lambda &&h_body, const int X=0, const int Y=0, const int Z=0, @@ -790,7 +929,7 @@ inline void ForallWrap(const bool use_dev, const int N, // If Backend::CUDA is allowed, use it if (Device::Allows(Backend::CUDA)) { - return CuWrap::run(N, d_body, X, Y, Z, G); + return CuWrap::run(N, d_body, X, Y, Z, G); } #endif @@ -798,7 +937,7 @@ inline void ForallWrap(const bool use_dev, const int N, // If Backend::HIP is allowed, use it if (Device::Allows(Backend::HIP)) { - return HipWrap::run(N, d_body, X, Y, Z, G); + return HipWrap::run(N, d_body, X, Y, Z, G); } #endif @@ -827,7 +966,9 @@ backend_cpu: for (int k = 0; k < N; k++) { h_body(k); } } -template +/////////////////////////////////////////////////////////////////////////////// +/// Forall host & device kernel wrappers +template inline void ForallWrap(const bool use_dev, const int N, lambda &&body, const int X=0, const int Y=0, const int Z=0, const int G=0) @@ -835,6 +976,16 @@ inline void ForallWrap(const bool use_dev, const int N, lambda &&body, ForallWrap(use_dev, N, body, body, X, Y, Z, G); } +template +inline void ForallWrap(const bool use_dev, const int N, lambda &&body, + const int X=0, const int Y=0, const int Z=0, + const int G=0) +{ + ForallWrap(use_dev, N, body, body, X, Y, Z, G); +} + +/////////////////////////////////////////////////////////////////////////////// +// forall interfaces template inline void forall(int N, lambda &&body) { ForallWrap<1>(true, N, body); } @@ -843,7 +994,7 @@ inline void forall(int Nx, int Ny, lambda &&body) { if (Device::Allows(Backend::DEVICE_MASK)) { - forall(Nx * Ny, [=] MFEM_HOST_DEVICE(int idx) + mfem::forall(Nx * Ny, [=] MFEM_HOST_DEVICE(int idx) { int j = idx / Nx; int i = idx % Nx; @@ -879,7 +1030,7 @@ inline void forall(int Nx, int Ny, int Nz, lambda &&body) { if (Device::Allows(Backend::DEVICE_MASK)) { - forall(Nx * Ny * Nz, [=] MFEM_HOST_DEVICE(int idx) + mfem::forall(Nx * Ny * Nz, [=] MFEM_HOST_DEVICE(int idx) { int i = idx % Nx; int j = idx / Nx; @@ -927,6 +1078,12 @@ inline void forall_2D(int N, int X, int Y, lambda &&body) ForallWrap<2>(true, N, body, X, Y, 1); } +template +inline void forall_2D(int N, int X, int Y, lambda &&body) +{ + ForallWrap<2, MAX_THREADS_PER_BLOCK>(true, N, body, X, Y, 1); +} + template inline void forall_2D_batch(int N, int X, int Y, int BZ, lambda &&body) { @@ -939,6 +1096,12 @@ inline void forall_3D(int N, int X, int Y, int Z, lambda &&body) ForallWrap<3>(true, N, body, X, Y, Z, 0); } +template +inline void forall_3D(int N, int X, int Y, int Z, lambda &&body) +{ + ForallWrap<3, MAX_THREADS_PER_BLOCK>(true, N, body, X, Y, Z, 0); +} + template inline void forall_3D_grid(int N, int X, int Y, int Z, int G, lambda &&body) { diff --git a/general/globals.hpp b/general/globals.hpp index 10d01aedab..ac559f71a6 100644 --- a/general/globals.hpp +++ b/general/globals.hpp @@ -113,6 +113,10 @@ void SetGlobalMPI_Comm(MPI_Comm comm); /// to suppress the warning. const char* GetEnv(const char* name); +/// Signed indices i -> -1 - i are used as a convention to encode orientation. +inline MFEM_HOST_DEVICE int FlipIndexSign(int i) { return -1 - i; } +inline MFEM_HOST_DEVICE int UnsignIndex(int i) { return i < 0 ? -1 - i : i; } + } // namespace mfem #endif diff --git a/general/hash.cpp b/general/hash.cpp index f2655a2a43..a1f8c108dc 100644 --- a/general/hash.cpp +++ b/general/hash.cpp @@ -80,159 +80,4 @@ std::string HashFunction::GetHash() const return hash; } -constexpr static uint64_t rotl64(uint64_t x, int r) -{ - return (x << r) | (x >> (64 - r)); -} - -void Hasher::init(uint64_t seed) -{ - data[0] = seed; - data[1] = seed; - nbytes = 0; -} - -void Hasher::add_block(uint64_t k1, uint64_t k2) -{ - constexpr uint64_t c1 = 0x87c37b91114253d5ull; - constexpr uint64_t c2 = 0x4cf5ad432745937full; - - k1 *= c1; - k1 = rotl64(k1, 31); - k1 *= c2; - data[0] ^= k1; - - data[0] = rotl64(data[0], 27); - data[0] += data[1]; - data[0] = data[0] * 5 + 0x52dce729ull; - - k2 *= c2; - k2 = rotl64(k2, 33); - k2 *= c1; - data[1] ^= k2; - - data[1] = rotl64(data[1], 31); - data[1] += data[0]; - data[1] = data[1] * 5 + 0x38495ab5ull; -} - -static uint64_t fmix64(uint64_t k) -{ - // http://zimbry.blogspot.com/2011/09/better-bit-mixing-improving-on.html - // mix13 - k ^= k >> 30; - k *= 0xbf58476d1ce4e5b9ull; - k ^= k >> 27; - k *= 0x94d049bb133111ebull; - k ^= k >> 31; - return k; -} - -void Hasher::append(const uint8_t *vs, uint64_t bytes) -{ - if (bytes == 0) - { - return; - } - auto rem = nbytes % 16; - nbytes += bytes; - uint8_t *tmp = reinterpret_cast(buf_); - while (true) - { - if (bytes + rem >= 16) - { - std::copy(vs, vs + 16 - rem, tmp + rem); - add_block(buf_[0], buf_[1]); - vs += (16 - rem); - bytes -= (16 - rem); - rem = 0; - } - else - { - std::copy(vs, vs + bytes, tmp + rem); - return; - } - } -} - -void Hasher::finalize() -{ - auto rem = nbytes % 16; - if (rem > 0) - { - nbytes -= rem; - if (rem <= 8) - { - finalize(buf_[0], rem); - } - else - { - finalize(buf_[0], buf_[1], rem); - } - return; - } - data[0] ^= nbytes; - data[1] ^= nbytes; - - data[0] += data[1]; - data[1] += data[0]; - - data[0] = fmix64(data[0]); - data[1] = fmix64(data[1]); - - data[0] += data[1]; - data[1] += data[0]; -} - -void Hasher::finalize(uint64_t k1, int num) -{ - constexpr uint64_t c1 = 0x87c37b91114253d5ull; - constexpr uint64_t c2 = 0x4cf5ad432745937full; - nbytes += num; - k1 *= c1; - k1 = rotl64(k1, 31); - k1 *= c2; - data[0] ^= k1; - - data[0] ^= nbytes; - data[1] ^= nbytes; - - data[0] += data[1]; - data[1] += data[0]; - - data[0] = fmix64(data[0]); - data[1] = fmix64(data[1]); - - data[0] += data[1]; - data[1] += data[0]; -} - -void Hasher::finalize(uint64_t k1, uint64_t k2, int num) -{ - constexpr uint64_t c1 = 0x87c37b91114253d5ull; - constexpr uint64_t c2 = 0x4cf5ad432745937full; - nbytes += num; - k2 *= c2; - k2 = rotl64(k2, 33); - k2 *= c1; - data[1] ^= k2; - - k1 *= c1; - k1 = rotl64(k1, 31); - k1 *= c2; - data[0] ^= k1; - - data[0] ^= nbytes; - data[1] ^= nbytes; - - data[0] += data[1]; - data[1] += data[0]; - - data[0] = fmix64(data[0]); - data[1] = fmix64(data[1]); - - data[0] += data[1]; - data[1] += data[0]; -} - } // namespace mfem diff --git a/general/hash.hpp b/general/hash.hpp index 70b6af6624..fc19f0a6f1 100644 --- a/general/hash.hpp +++ b/general/hash.hpp @@ -15,8 +15,8 @@ #include "../config/config.hpp" #include "array.hpp" #include "globals.hpp" +#include "hash_util.hpp" -#include #include #include #include @@ -457,75 +457,6 @@ protected: int BinSize(int idx) const; }; -/// -/// @brief streaming implementation for murmurhash3 128 (x64). -/// Constructs the hash in 3 stages: init, append, finalize. -/// -struct Hasher -{ - /// where the final hash result is stored after finalize. Use data[1] when - /// only 64 bits are required. - uint64_t data[2] = {0, 0}; - -private: - uint64_t nbytes = 0; - - uint64_t buf_[2] = {0, 0}; - -public: - - /// resets this hasher back to an initial seed - void init(uint64_t seed = 0); - void append(const uint8_t *vs, uint64_t bytes); - - void finalize(); - -private: - // add 16 bytes - void add_block(uint64_t k1, uint64_t k2); - - // add [1-8] more bytes, then finalize - void finalize(uint64_t k1, int num); - - // add [1-15] more bytes, then finalize - // 0 < num < 16 - void finalize(uint64_t k1, uint64_t k2, int num); -}; - -/// Helper class for hashing std::pair. Usable in place of std::hash> -struct PairHasher -{ - template - size_t operator()(const std::pair &v) const noexcept - { - Hasher hash; - // chosen randomly with a 2^64-sided dice - hash.init(0xfebd1fe69813c14full); - hash.append(reinterpret_cast(&v.first), sizeof(T)); - hash.append(reinterpret_cast(&v.second), sizeof(V)); - hash.finalize(); - return hash.data[1]; - } -}; - -/// Helper class for hashing std::array. Usable in place of std::hash> -struct ArrayHasher -{ - template - size_t operator()(const std::array &v) const noexcept - { - Hasher hash; - // chosen randomly with a 2^64-sided dice - hash.init(0xfebd1fe69813c14full); - for (size_t i = 0; i < N; ++i) - { - hash.append(reinterpret_cast(&v[i]), sizeof(T)); - } - hash.finalize(); - return hash.data[1]; - } -}; - /// Hash function for data sequences. /** Depends on GnuTLS for SHA-256 hashing. */ class HashFunction diff --git a/general/hash_util.cpp b/general/hash_util.cpp new file mode 100644 index 0000000000..45d7763013 --- /dev/null +++ b/general/hash_util.cpp @@ -0,0 +1,172 @@ +// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced +// at the Lawrence Livermore National Laboratory. All Rights reserved. See files +// LICENSE and NOTICE for details. LLNL-CODE-806117. +// +// This file is part of the MFEM library. For more information and source code +// availability visit https://mfem.org. +// +// MFEM is free software; you can redistribute it and/or modify it under the +// terms of the BSD-3 license. We welcome feedback and contributions, see file +// CONTRIBUTING.md for details. + +#include "hash_util.hpp" + +namespace mfem +{ + +constexpr static uint64_t rotl64(uint64_t x, int r) +{ + return (x << r) | (x >> (64 - r)); +} + +void Hasher::init(uint64_t seed) +{ + data[0] = seed; + data[1] = seed; + nbytes = 0; +} + +void Hasher::add_block(uint64_t k1, uint64_t k2) +{ + constexpr uint64_t c1 = 0x87c37b91114253d5ull; + constexpr uint64_t c2 = 0x4cf5ad432745937full; + + k1 *= c1; + k1 = rotl64(k1, 31); + k1 *= c2; + data[0] ^= k1; + + data[0] = rotl64(data[0], 27); + data[0] += data[1]; + data[0] = data[0] * 5 + 0x52dce729ull; + + k2 *= c2; + k2 = rotl64(k2, 33); + k2 *= c1; + data[1] ^= k2; + + data[1] = rotl64(data[1], 31); + data[1] += data[0]; + data[1] = data[1] * 5 + 0x38495ab5ull; +} + +static uint64_t fmix64(uint64_t k) +{ + // http://zimbry.blogspot.com/2011/09/better-bit-mixing-improving-on.html + // mix13 + k ^= k >> 30; + k *= 0xbf58476d1ce4e5b9ull; + k ^= k >> 27; + k *= 0x94d049bb133111ebull; + k ^= k >> 31; + return k; +} + +void Hasher::append(const std::byte *vs, uint64_t bytes) +{ + if (bytes == 0) + { + return; + } + auto rem = nbytes % 16; + nbytes += bytes; + std::byte *tmp = reinterpret_cast(buf_); + while (true) + { + if (bytes + rem >= 16) + { + std::copy(vs, vs + 16 - rem, tmp + rem); + add_block(buf_[0], buf_[1]); + vs += (16 - rem); + bytes -= (16 - rem); + rem = 0; + } + else + { + std::copy(vs, vs + bytes, tmp + rem); + return; + } + } +} + +void Hasher::finalize() +{ + auto rem = nbytes % 16; + if (rem > 0) + { + nbytes -= rem; + if (rem <= 8) + { + finalize(buf_[0], rem); + } + else + { + finalize(buf_[0], buf_[1], rem); + } + return; + } + data[0] ^= nbytes; + data[1] ^= nbytes; + + data[0] += data[1]; + data[1] += data[0]; + + data[0] = fmix64(data[0]); + data[1] = fmix64(data[1]); + + data[0] += data[1]; + data[1] += data[0]; +} + +void Hasher::finalize(uint64_t k1, int num) +{ + constexpr uint64_t c1 = 0x87c37b91114253d5ull; + constexpr uint64_t c2 = 0x4cf5ad432745937full; + nbytes += num; + k1 *= c1; + k1 = rotl64(k1, 31); + k1 *= c2; + data[0] ^= k1; + + data[0] ^= nbytes; + data[1] ^= nbytes; + + data[0] += data[1]; + data[1] += data[0]; + + data[0] = fmix64(data[0]); + data[1] = fmix64(data[1]); + + data[0] += data[1]; + data[1] += data[0]; +} + +void Hasher::finalize(uint64_t k1, uint64_t k2, int num) +{ + constexpr uint64_t c1 = 0x87c37b91114253d5ull; + constexpr uint64_t c2 = 0x4cf5ad432745937full; + nbytes += num; + k2 *= c2; + k2 = rotl64(k2, 33); + k2 *= c1; + data[1] ^= k2; + + k1 *= c1; + k1 = rotl64(k1, 31); + k1 *= c2; + data[0] ^= k1; + + data[0] ^= nbytes; + data[1] ^= nbytes; + + data[0] += data[1]; + data[1] += data[0]; + + data[0] = fmix64(data[0]); + data[1] = fmix64(data[1]); + + data[0] += data[1]; + data[1] += data[0]; +} + +} diff --git a/general/hash_util.hpp b/general/hash_util.hpp new file mode 100644 index 0000000000..2d86d3e63d --- /dev/null +++ b/general/hash_util.hpp @@ -0,0 +1,172 @@ +// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced +// at the Lawrence Livermore National Laboratory. All Rights reserved. See files +// LICENSE and NOTICE for details. LLNL-CODE-806117. +// +// This file is part of the MFEM library. For more information and source code +// availability visit https://mfem.org. +// +// MFEM is free software; you can redistribute it and/or modify it under the +// terms of the BSD-3 license. We welcome feedback and contributions, see file +// CONTRIBUTING.md for details. + +#ifndef MFEM_HASH_UTIL_HPP +#define MFEM_HASH_UTIL_HPP + +#include +#include +#include +#include +#include +#include + +namespace mfem +{ + +/// @brief streaming implementation for murmurhash3 128 (x64). +/// +/// Constructs the hash in 3 stages: init, append, finalize. +struct Hasher +{ + /// @brief Storage for the final hash result after finalize() is called. + /// + /// Use data[1] when only 64 bits are required. + uint64_t data[2] = {0, 0}; + +private: + uint64_t nbytes = 0; + uint64_t buf_[2] = {0, 0}; + +public: + + /// Resets the Hasher back to an initial seed + void init(uint64_t seed = 0); + + /// Append data @a vs of size @a bytes. + void append(const std::byte *vs, uint64_t bytes); + + void finalize(); + +private: + /// Add a block of 16 bytes. + void add_block(uint64_t k1, uint64_t k2); + + /// @brief Add [1-8] more bytes, then finalize. + /// + /// @a num must satisfy 0 < num < 9. + void finalize(uint64_t k1, int num); + + /// @brief Add [1-15] more bytes, then finalize. + /// + /// @a num must satisfy 0 < num < 16. + void finalize(uint64_t k1, uint64_t k2, int num); +}; + +template struct ChainedHasher +{ + static void Append(Hasher &hasher, const T &value) + { + if constexpr (std::is_fundamental_v || std::is_pointer_v) + { + hasher.append(reinterpret_cast(&value), sizeof(T)); + } + else + { + std::hash h; + auto v = h(value); + hasher.append(reinterpret_cast(&v), sizeof(v)); + } + } +}; + +template struct ChainedHasher> +{ + static void Append(Hasher &hasher, const std::pair &value) + { + ChainedHasher::Append(hasher, value.first); + ChainedHasher::Append(hasher, value.second); + } +}; + +template struct ChainedHasher> +{ + static void Append(Hasher &hasher, const std::array &value) + { + for (size_t i = 0; i < N; ++i) + { + ChainedHasher::Append(hasher, value[i]); + } + } +}; + +template struct ChainedHasher> +{ +private: + template + static void AppendImpl(Hasher &hasher, const std::tuple &value) + { + ChainedHasher(value))>>::Append( + hasher, std::get(value)); + if constexpr (N + 1 < sizeof...(Ts)) + { + AppendImpl(hasher, value); + } + } + +public: + static void Append(Hasher &hasher, const std::tuple &value) + { + if constexpr (sizeof...(Ts)) + { + AppendImpl<0>(hasher, value); + } + } +}; + +/// Helper class for hashing std::pair of hashable types. +struct PairHasher +{ + template + size_t operator()(const std::pair &v) const noexcept + { + Hasher hash; + // chosen randomly with a 2^64-sided dice + hash.init(0xfebd1fe69813c14full); + ChainedHasher>::Append(hash, v); + hash.finalize(); + return hash.data[1]; + } +}; + +/// Helper class for hashing std::array of a hashable type. +struct ArrayHasher +{ + template + size_t operator()(const std::array &v) const noexcept + { + Hasher hash; + // chosen randomly with a 2^64-sided dice + hash.init(0xfebd1fe69813c14full); + ChainedHasher>::Append(hash, v); + hash.finalize(); + return hash.data[1]; + } +}; + +/// Helper class for hashing std::tuple of hashable types. +struct TupleHasher +{ + template + size_t operator()(const T &v) const noexcept + { + Hasher hash; + // chosen randomly with a 2^64-sided dice + hash.init(0xfebd1fe69813c14full); + ChainedHasher::Append(hash, v); + hash.finalize(); + return hash.data[1]; + } +}; + +} // namespace mfem + +#endif diff --git a/general/hip.hpp b/general/hip.hpp index f8f12cad8a..c3b0424d1b 100644 --- a/general/hip.hpp +++ b/general/hip.hpp @@ -20,9 +20,11 @@ #if defined(MFEM_USE_HIP) && defined(__HIP__) #define MFEM_USE_CUDA_OR_HIP +constexpr bool mfem_use_gpu = true; #define MFEM_DEVICE __device__ #define MFEM_HOST __host__ #define MFEM_LAMBDA __host__ __device__ +#define MFEM_LAUNCH_BOUNDS __launch_bounds__ // #define MFEM_HOST_DEVICE __host__ __device__ // defined in config/config.hpp #define MFEM_DEVICE_SYNC MFEM_GPU_CHECK(hipDeviceSynchronize()) #define MFEM_STREAM_SYNC MFEM_GPU_CHECK(hipStreamSynchronize(0)) diff --git a/general/mem_manager.cpp b/general/mem_manager.cpp index 7e828a6886..95db83aa7f 100644 --- a/general/mem_manager.cpp +++ b/general/mem_manager.cpp @@ -759,7 +759,9 @@ private: { switch (mt) { - case MT::HOST_DEBUG: return new MmuHostMemorySpace(); + case MT::HOST_DEBUG: + if (GetEnv("MFEM_MMU_STD")) { return new StdHostMemorySpace(); } + return new MmuHostMemorySpace(); #ifdef MFEM_USE_UMPIRE case MT::HOST_UMPIRE: return new UmpireHostMemorySpace( @@ -788,7 +790,9 @@ private: case MT::DEVICE_UMPIRE: return new NoDeviceMemorySpace(); case MT::DEVICE_UMPIRE_2: return new NoDeviceMemorySpace(); #endif - case MT::DEVICE_DEBUG: return new MmuDeviceMemorySpace(); + case MT::DEVICE_DEBUG: + if (GetEnv("MFEM_MMU_STD")) { return new StdDeviceMemorySpace(); } + return new MmuDeviceMemorySpace(); case MT::DEVICE: { #if defined(MFEM_USE_CUDA) diff --git a/linalg/densemat.hpp b/linalg/densemat.hpp index d968f8992e..98f5ce83bc 100644 --- a/linalg/densemat.hpp +++ b/linalg/densemat.hpp @@ -82,6 +82,15 @@ public: } } + /// Make the DenseMatrix to reference the given sub-Memory of @a base. + /** The DenseMatrix does not assume ownership of the data array, i.e. it will + not delete the @a base Memory. */ + void MakeRef(Memory &base, int offset, int h, int w) + { + data.MakeRef(base, offset, h*w); + height = h; width = w; + } + /// Change the data array and the size of the DenseMatrix. /** The DenseMatrix does not assume ownership of the data array, i.e. it will not delete the data array @a d. */ diff --git a/linalg/hypre.cpp b/linalg/hypre.cpp index 1f2c40f7cb..a766fd7fdf 100644 --- a/linalg/hypre.cpp +++ b/linalg/hypre.cpp @@ -3634,12 +3634,25 @@ void HypreSmoother::SetType(HypreSmoother::Type type_, int relax_times_) relax_times = relax_times_; } +void HypreSmoother::GetType(HypreSmoother::Type &type_, int &relax_times_) const +{ + type_ = static_cast(type); + relax_times_ = relax_times; +} + void HypreSmoother::SetSOROptions(real_t relax_weight_, real_t omega_) { relax_weight = relax_weight_; omega = omega_; } +void HypreSmoother::GetSOROptions(real_t &relax_weight_, real_t &omega_) const +{ + // TODO: are these used for all smoother types? + relax_weight_ = relax_weight; + omega_ = omega; +} + void HypreSmoother::SetPolyOptions(int poly_order_, real_t poly_fraction_, int eig_est_cg_iter_) { @@ -3648,6 +3661,15 @@ void HypreSmoother::SetPolyOptions(int poly_order_, real_t poly_fraction_, eig_est_cg_iter = eig_est_cg_iter_; } +void HypreSmoother::GetPolyOptions(int &poly_order_, real_t &poly_fraction_, + int &eig_est_cg_iter_) const +{ + // TODO: are these used for all smoother types? + poly_order_ = poly_order; + poly_fraction_ = poly_fraction; + eig_est_cg_iter_ = eig_est_cg_iter; +} + void HypreSmoother::SetTaubinOptions(real_t lambda_, real_t mu_, int taubin_iter_) { @@ -3656,6 +3678,14 @@ void HypreSmoother::SetTaubinOptions(real_t lambda_, real_t mu_, taubin_iter = taubin_iter_; } +void HypreSmoother::GetTaubinOptions(real_t &lambda_, real_t &mu_, + int &taubin_iter_) const +{ + lambda_ = lambda; + mu_ = mu; + taubin_iter_ = taubin_iter; +} + void HypreSmoother::SetWindowByName(const char* name) { real_t a = -1, b, c; @@ -3678,6 +3708,13 @@ void HypreSmoother::SetWindowParameters(real_t a, real_t b, real_t c) window_params[2] = c; } +void HypreSmoother::GetWindowParameters(real_t &a, real_t &b, real_t &c) const +{ + a = window_params[0]; + b = window_params[1]; + c = window_params[2]; +} + void HypreSmoother::SetOperator(const Operator &op) { A = const_cast(dynamic_cast(&op)); @@ -4173,12 +4210,20 @@ HypreSolver::~HypreSolver() auxX.Delete(); } +void HyprePCG::SetDefaultOptions() +{ + // Explicitly set just in case past/future versions of hypre change the + // defaults + SetTol(1e-6); + SetMaxIter(1000); +} HyprePCG::HyprePCG(MPI_Comm comm) : precond(NULL) { iterative_mode = true; HYPRE_ParCSRPCGCreate(comm, &pcg_solver); + SetDefaultOptions(); } HyprePCG::HyprePCG(const HypreParMatrix &A_) : HypreSolver(&A_), precond(NULL) @@ -4190,6 +4235,7 @@ HyprePCG::HyprePCG(const HypreParMatrix &A_) : HypreSolver(&A_), precond(NULL) HYPRE_ParCSRMatrixGetComm(*A, &comm); HYPRE_ParCSRPCGCreate(comm, &pcg_solver); + SetDefaultOptions(); } void HyprePCG::SetOperator(const Operator &op) @@ -4214,21 +4260,54 @@ void HyprePCG::SetOperator(const Operator &op) auxX.Delete(); auxX.Reset(); } +void HyprePCG::SetUseTwoNorm(bool val) +{ + HYPRE_PCGSetTwoNorm(pcg_solver, val); +} + +bool HyprePCG::GetUseTwoNorm() const +{ + HYPRE_Int val; + HYPRE_PCGGetTwoNorm(pcg_solver, &val); + return val != 0; +} + void HyprePCG::SetTol(real_t tol) { HYPRE_PCGSetTol(pcg_solver, tol); } +real_t HyprePCG::GetTol() const +{ + HYPRE_Real tol; + HYPRE_PCGGetTol(pcg_solver, &tol); + return tol; +} + void HyprePCG::SetAbsTol(real_t atol) { HYPRE_PCGSetAbsoluteTol(pcg_solver, atol); } +real_t HyprePCG::GetAbsTol() const +{ + HYPRE_Real atol; + hypre_PCGGetAbsoluteTol(pcg_solver, &atol); + return atol; +} + void HyprePCG::SetMaxIter(int max_iter) { HYPRE_PCGSetMaxIter(pcg_solver, max_iter); } +int HyprePCG::GetMaxIter() const +{ + HYPRE_Int max_iter; + HYPRE_PCGGetMaxIter(pcg_solver, &max_iter); + return max_iter; +} + void HyprePCG::SetLogging(int logging) { HYPRE_PCGSetLogging(pcg_solver, logging); @@ -4344,6 +4423,20 @@ HyprePCG::~HyprePCG() HYPRE_ParCSRPCGDestroy(pcg_solver); } +#if MFEM_HYPRE_VERSION >= 21500 +HypreParVector HyprePCG::GetResiduals() const +{ + HYPRE_ParVector r; + HYPRE_ParCSRPCGGetResidual(pcg_solver, &r); + return HypreParVector(r); +} + +void HyprePCG::GetFinalAbsResidualNorm(real_t &final_res_norm, real_t p) const +{ + auto r = GetResiduals(); + ParNormlp(r, p, r.GetComm()); +} +#endif HypreGMRES::HypreGMRES(MPI_Comm comm) : precond(NULL) { @@ -4399,26 +4492,69 @@ void HypreGMRES::SetOperator(const Operator &op) auxX.Delete(); auxX.Reset(); } +#if MFEM_HYPRE_VERSION >= 21500 +HypreParVector HypreGMRES::GetResiduals() const +{ + HYPRE_ParVector r; + HYPRE_ParCSRGMRESGetResidual(gmres_solver, &r); + return HypreParVector(r); +} + +void HypreGMRES::GetFinalAbsResidualNorm(real_t &final_res_norm, real_t p) const +{ + auto r = GetResiduals(); + ParNormlp(r, p, r.GetComm()); +} +#endif + void HypreGMRES::SetTol(real_t tol) { HYPRE_GMRESSetTol(gmres_solver, tol); } +real_t HypreGMRES::GetTol()const +{ + HYPRE_Real tol; + HYPRE_GMRESGetTol(gmres_solver, &tol); + return tol; +} + void HypreGMRES::SetAbsTol(real_t tol) { HYPRE_GMRESSetAbsoluteTol(gmres_solver, tol); } +real_t HypreGMRES::GetAbsTol() const +{ + HYPRE_Real atol; + HYPRE_GMRESGetAbsoluteTol(gmres_solver, &atol); + return atol; +} + void HypreGMRES::SetMaxIter(int max_iter) { HYPRE_GMRESSetMaxIter(gmres_solver, max_iter); } +int HypreGMRES::GetMaxIter() const +{ + HYPRE_Int max_iter; + HYPRE_GMRESGetMaxIter(gmres_solver, &max_iter); + return max_iter; +} + void HypreGMRES::SetKDim(int k_dim) { HYPRE_GMRESSetKDim(gmres_solver, k_dim); } +int HypreGMRES::GetKDim() const +{ + HYPRE_Int k_dim; + HYPRE_GMRESGetKDim(gmres_solver, &k_dim); + return k_dim; +} + void HypreGMRES::SetLogging(int logging) { HYPRE_GMRESSetLogging(gmres_solver, logging); @@ -4576,16 +4712,37 @@ void HypreFGMRES::SetTol(real_t tol) HYPRE_ParCSRFlexGMRESSetTol(fgmres_solver, tol); } +real_t HypreFGMRES::GetTol() const +{ + HYPRE_Real tol; + HYPRE_FlexGMRESGetTol(fgmres_solver, &tol); + return tol; +} + void HypreFGMRES::SetMaxIter(int max_iter) { HYPRE_ParCSRFlexGMRESSetMaxIter(fgmres_solver, max_iter); } +int HypreFGMRES::GetMaxIter() const +{ + HYPRE_Int max_iter; + HYPRE_FlexGMRESGetMaxIter(fgmres_solver, &max_iter); + return max_iter; +} + void HypreFGMRES::SetKDim(int k_dim) { HYPRE_ParCSRFlexGMRESSetKDim(fgmres_solver, k_dim); } +int HypreFGMRES::GetKDim() const +{ + HYPRE_Int k_dim; + HYPRE_FlexGMRESGetKDim(fgmres_solver, &k_dim); + return k_dim; +} + void HypreFGMRES::SetLogging(int logging) { HYPRE_ParCSRFlexGMRESSetLogging(fgmres_solver, logging); @@ -4682,6 +4839,21 @@ HypreFGMRES::~HypreFGMRES() HYPRE_ParCSRFlexGMRESDestroy(fgmres_solver); } +#if MFEM_HYPRE_VERSION >= 21500 +HypreParVector HypreFGMRES::GetResiduals() const +{ + HYPRE_ParVector r; + HYPRE_ParCSRFlexGMRESGetResidual(fgmres_solver, &r); + return HypreParVector(r); +} + +void HypreFGMRES::GetFinalAbsResidualNorm(real_t &final_res_norm, + real_t p) const +{ + auto r = GetResiduals(); + ParNormlp(r, p, r.GetComm()); +} +#endif void HypreDiagScale::SetOperator(const Operator &op) { @@ -5170,6 +5342,13 @@ void HypreBoomerAMG::ResetAMGPrecond() } } +int HypreBoomerAMG::GetMaxIter() const +{ + HYPRE_Int max_iter; + HYPRE_BoomerAMGGetMaxIter(amg_precond, &max_iter); + return max_iter; +} + void HypreBoomerAMG::SetOperator(const Operator &op) { const HypreParMatrix *new_A = dynamic_cast(&op); diff --git a/linalg/hypre.hpp b/linalg/hypre.hpp index eaa0028869..8d81d844ff 100644 --- a/linalg/hypre.hpp +++ b/linalg/hypre.hpp @@ -1160,6 +1160,15 @@ public: return HypreUsingGPU() ? l1Jacobi : l1GS; } + /// Default solver settings: + /// type = DefaultType() + /// relax_times = 1 + /// omega = 1.0 + /// poly_order = 2 + /// poly_fraction = 0.3 + /// lambda = 0.5 + /// mu = -0.5 + /// taubin_iter = 40 HypreSmoother(); HypreSmoother(const HypreParMatrix &A_, int type = DefaultType(), @@ -1169,20 +1178,28 @@ public: /// Set the relaxation type and number of sweeps void SetType(HypreSmoother::Type type, int relax_times = 1); + using Operator::GetType; + void GetType(HypreSmoother::Type &type, int &relax_times) const; /// Set SOR-related parameters void SetSOROptions(real_t relax_weight, real_t omega); + void GetSOROptions(real_t &relax_weight, real_t &omega) const; + /// Set parameters for polynomial smoothing /** By default, 10 iterations of CG are used to estimate the eigenvalues. Setting eig_est_cg_iter = 0 uses hypre's hypre_ParCSRMaxEigEstimate() instead. */ void SetPolyOptions(int poly_order, real_t poly_fraction, int eig_est_cg_iter = 10); + void GetPolyOptions(int &poly_order, real_t &poly_fraction, + int &eig_est_cg_iter) const; /// Set parameters for Taubin's lambda-mu method void SetTaubinOptions(real_t lambda, real_t mu, int iter); + void GetTaubinOptions(real_t &lambda, real_t &mu, int &iter) const; /// Convenience function for setting canonical windowing parameters void SetWindowByName(const char* window_name); /// Set parameters for windowing function for FIR smoother. void SetWindowParameters(real_t a, real_t b, real_t c); + void GetWindowParameters(real_t &a, real_t &b, real_t &c) const; /// Compute window and Chebyshev coefficients for given polynomial order. void SetFIRCoefficients(real_t max_eig); @@ -1190,12 +1207,15 @@ public: /** By default, the l1-norms take their sign from the corresponding diagonal entries in the associated matrix. */ void SetPositiveDiagonal(bool pos = true) { pos_l1_norms = pos; } + bool IsPositiveDiagonal() const { return pos_l1_norms; }; /** Explicitly indicate whether the linear system matrix A is symmetric. If A is symmetric, the smoother will also be symmetric. In this case, calling MultTranspose will be redirected to Mult. (This is also done if the smoother is diagonal.) By default, A is assumed to be nonsymmetric. */ void SetOperatorSymmetry(bool is_sym) { A_is_symmetric = is_sym; } + /// @return true if the smoother assumes A is symmetric, false otherwise + bool IsOperatorSymmetric() const { return A_is_symmetric; } /** Set/update the associated operator. Must be called after setting the HypreSmoother type and options. */ @@ -1327,6 +1347,7 @@ public: #endif /// PCG solver in hypre +/// Defaults to (relative) tol=1e-6, atol=0, max_iter=1000 class HyprePCG : public HypreSolver { private: @@ -1334,6 +1355,9 @@ private: HypreSolver * precond; + /// Default PCG options + void SetDefaultOptions(); + public: HyprePCG(MPI_Comm comm); @@ -1342,8 +1366,11 @@ public: void SetOperator(const Operator &op) override; void SetTol(real_t tol); + real_t GetTol() const; void SetAbsTol(real_t atol); + real_t GetAbsTol() const; void SetMaxIter(int max_iter); + int GetMaxIter() const; void SetLogging(int logging); void SetPrintLevel(int print_lvl); @@ -1368,12 +1395,32 @@ public: num_iterations = internal::to_int(num_it); } + /// Gets the relative residual norm void GetFinalResidualNorm(real_t &final_res_norm) const { HYPRE_ParCSRPCGGetFinalRelativeResidualNorm(pcg_solver, &final_res_norm); } + /// @param[in] use + /// Convergence criterion: + /// - when true: (r, r) < max(r_tol^2 (b, b), a_tol^2) + /// - when false: (r, A r) < max(r_tol^2 (b, A b), a_tol^2) + /// @sa HYPRE_PCGSetTwoNorm + void SetUseTwoNorm(bool use); + + /// @sa HYPRE_PCGGetTwoNorm + bool GetUseTwoNorm() const; + +#if MFEM_HYPRE_VERSION >= 21500 + /// Gets the internal Hypre solver residual vector. + /// @sa HYPRE_ParCSRPCGGetResidual + HypreParVector GetResiduals() const; + + /// Computes the absolute residual p-norm. + void GetFinalAbsResidualNorm(real_t &final_res_norm, real_t p = 2) const; +#endif + /// The typecast to HYPRE_Solver returns the internal pcg_solver operator HYPRE_Solver() const override { return pcg_solver; } @@ -1391,7 +1438,8 @@ public: virtual ~HyprePCG(); }; -/// GMRES solver in hypre +/// GMRES solver in hypre. +/// Defaults to k=50, (relative) tol=1e-6, atol=0, max_iter=100. class HypreGMRES : public HypreSolver { private: @@ -1410,9 +1458,13 @@ public: void SetOperator(const Operator &op) override; void SetTol(real_t tol); + real_t GetTol() const; void SetAbsTol(real_t tol); + real_t GetAbsTol() const; void SetMaxIter(int max_iter); + int GetMaxIter() const; void SetKDim(int dim); + int GetKDim() const; void SetLogging(int logging); void SetPrintLevel(int print_lvl); @@ -1432,12 +1484,22 @@ public: num_iterations = internal::to_int(num_it); } + /// Gets the relative residual norm void GetFinalResidualNorm(real_t &final_res_norm) const { HYPRE_ParCSRGMRESGetFinalRelativeResidualNorm(gmres_solver, &final_res_norm); } +#if MFEM_HYPRE_VERSION >= 21500 + /// Gets the internal Hypre solver residual vector. + /// @sa HYPRE_ParCSRGMRESGetResidual + HypreParVector GetResiduals() const; + + /// Computes the absolute residual p-norm. + void GetFinalAbsResidualNorm(real_t &final_res_norm, real_t p = 2) const; +#endif + /// The typecast to HYPRE_Solver returns the internal gmres_solver operator HYPRE_Solver() const override { return gmres_solver; } @@ -1455,7 +1517,8 @@ public: virtual ~HypreGMRES(); }; -/// Flexible GMRES solver in hypre +/// Flexible GMRES solver in hypre. +/// Defaults to k=50, (relative) tol=1e-6, max_iter=100. class HypreFGMRES : public HypreSolver { private: @@ -1474,8 +1537,11 @@ public: void SetOperator(const Operator &op) override; void SetTol(real_t tol); + real_t GetTol() const; void SetMaxIter(int max_iter); + int GetMaxIter() const; void SetKDim(int dim); + int GetKDim() const; void SetLogging(int logging); void SetPrintLevel(int print_lvl); @@ -1495,12 +1561,22 @@ public: num_iterations = internal::to_int(num_it); } + /// Gets the relative residual norm void GetFinalResidualNorm(real_t &final_res_norm) const { HYPRE_ParCSRFlexGMRESGetFinalRelativeResidualNorm(fgmres_solver, &final_res_norm); } +#if MFEM_HYPRE_VERSION >= 21500 + /// Gets the internal Hypre solver residual vector. + /// @sa HYPRE_ParCSRFlexGMRESGetResidual + HypreParVector GetResiduals() const; + + /// Computes the absolute residual p-norm. + void GetFinalAbsResidualNorm(real_t &final_res_norm, real_t p = 2) const; +#endif + /// The typecast to HYPRE_Solver returns the internal fgmres_solver operator HYPRE_Solver() const override { return fgmres_solver; } @@ -1556,7 +1632,8 @@ public: virtual ~HypreDiagScale() { } }; -/// The ParaSails preconditioner in hypre +/// The ParaSails preconditioner in hypre. +/// See SetDefaultOptions() for default solver options. class HypreParaSails : public HypreSolver { private: @@ -1685,10 +1762,14 @@ public: /** @brief Wrapper for Hypre's native parallel ILU preconditioner. -The default ILU factorization type is ILU(k). If you need to change this, or -any other option, you can use the HYPRE_Solver method to cast the object for use -with Hypre's native functions. For example, if want to use natural ordering -rather than RCM reordering, you can use the following approach: +Default parameters: ILU(k) factorization type, tol=0.0 (for use as a +preconditioner), fill level = 1 (for ILU(k)), reverse Cuthill-McKee (RCM) +re-ordering. + +If you need to change this, or any other option, you can use the HYPRE_Solver +method to cast the object for use with Hypre's native functions. For example, if +want to use natural ordering rather than RCM reordering, you can use the +following approach: @code mfem::HypreILU ilu(); @@ -1829,6 +1910,7 @@ public: void SetMaxIter(int max_iter) { HYPRE_BoomerAMGSetMaxIter(amg_precond, max_iter); } + int GetMaxIter() const; /// Expert option - consult hypre documentation/team void SetMaxLevels(int max_levels) @@ -1853,6 +1935,8 @@ public: /// Expert option - consult hypre documentation/team void SetRelaxType(int relax_type) { HYPRE_BoomerAMGSetRelaxType(amg_precond, relax_type); } + // not implemented in hypre + // int GetRelaxType() const; /// Expert option - consult hypre documentation/team void SetCycleType(int cycle_type) @@ -2153,8 +2237,14 @@ public: ~HypreLOBPCG(); void SetTol(real_t tol); + // not implemented in HYPRE + // real_t GetTol() const; void SetRelTol(real_t rel_tol); + // not implemented in HYPRE + // real_t GetRelTol() const; void SetMaxIter(int max_iter); + // not implemented in HYPRE + // int GetMaxIter() const; void SetPrintLevel(int logging); void SetNumModes(int num_eigs) { nev = num_eigs; } void SetPrecondUsageMode(int pcg_mode); diff --git a/linalg/petsc.cpp b/linalg/petsc.cpp index 2d485707c6..9a94c21697 100644 --- a/linalg/petsc.cpp +++ b/linalg/petsc.cpp @@ -3639,12 +3639,20 @@ void PetscBDDCSolver::BDDCSolverConstructor(const PetscBDDCSolverParams &opts) // make sure ess/nat_dof have been collectively set PetscBool lpr = PETSC_FALSE,pr; if (opts.ess_dof) { lpr = PETSC_TRUE; } +#if PETSC_VERSION_LT(3,24,0) mpiierr = MPI_Allreduce(&lpr,&pr,1,MPIU_BOOL,MPI_LOR,comm); +#else + mpiierr = MPI_Allreduce(&lpr,&pr,1,MPI_C_BOOL,MPI_LOR,comm); +#endif CCHKERRQ(comm,mpiierr); MFEM_VERIFY(lpr == pr,"ess_dof should be collectively set"); lpr = PETSC_FALSE; if (opts.nat_dof) { lpr = PETSC_TRUE; } +#if PETSC_VERSION_LT(3,24,0) mpiierr = MPI_Allreduce(&lpr,&pr,1,MPIU_BOOL,MPI_LOR,comm); +#else + mpiierr = MPI_Allreduce(&lpr,&pr,1,MPI_C_BOOL,MPI_LOR,comm); +#endif CCHKERRQ(comm,mpiierr); MFEM_VERIFY(lpr == pr,"nat_dof should be collectively set"); // make sure fields have been collectively set @@ -4058,8 +4066,13 @@ void PetscNonlinearSolver::SetOperator(const Operator &op) ls = (PetscBool)(height == op.Height() && width == op.Width() && (void*)&op == fctx && (void*)&op == jctx); +#if PETSC_VERSION_LT(3,24,0) mpiierr = MPI_Allreduce(&ls,&gs,1,MPIU_BOOL,MPI_LAND, PetscObjectComm((PetscObject)snes)); +#else + mpiierr = MPI_Allreduce(&ls,&gs,1,MPI_C_BOOL,MPI_LAND, + PetscObjectComm((PetscObject)snes)); +#endif CCHKERRQ(PetscObjectComm((PetscObject)snes),mpiierr); if (!gs) { diff --git a/linalg/sparsemat.cpp b/linalg/sparsemat.cpp index 356cb7083c..4632141756 100644 --- a/linalg/sparsemat.cpp +++ b/linalg/sparsemat.cpp @@ -1066,6 +1066,11 @@ void SparseMatrix::BooleanMultTranspose(const Array &x, y.SetSize(Width()); y = 0; + HostReadI(); + HostReadJ(); + x.HostRead(); + y.HostReadWrite(); + for (int i = 0; i < Height(); i++) { if (x[i]) diff --git a/linalg/superlu.cpp b/linalg/superlu.cpp index 86f11ff7ee..ba15067265 100644 --- a/linalg/superlu.cpp +++ b/linalg/superlu.cpp @@ -363,14 +363,19 @@ void SuperLUSolver::Init(MPI_Comm comm) // Set default options: // options.Fact = DOFACT; // options.Equil = YES; + // options.ParSymbFact = NO; // options.ColPerm = METIS_AT_PLUS_A; // options.RowPerm = LargeDiag_MC64; // options.ReplaceTinyPivot = NO; - // options.Trans = NOTRANS; // options.IterRefine = SLU_DOUBLE; + // options.Trans = NOTRANS; // options.SolveInitialized = NO; // options.RefineInitialized = NO; // options.PrintStat = YES; + // options.lookahead_etree = NO; + // options.num_lookaheads = 10; + // options.superlu_acc_offload = 1; + // options.SymPattern = NO; superlu_dist_options_t *options = (superlu_dist_options_t *)optionsPtr_; set_default_options_dist(options); #if SUPERLU_DIST_MAJOR_VERSION > 7 || \ @@ -472,6 +477,12 @@ void SuperLUSolver::SetFact(superlu::Fact fact) options->Fact = opt; } +void SuperLUSolver::SetDeviceOffload(bool offload) +{ + superlu_dist_options_t *options = (superlu_dist_options_t *)optionsPtr_; + options->superlu_acc_offload = offload; +} + void SuperLUSolver::SetOperator(const Operator &op) { // Verify that we have a compatible operator diff --git a/linalg/superlu.hpp b/linalg/superlu.hpp index 3e1694203c..2f3b8bfb46 100644 --- a/linalg/superlu.hpp +++ b/linalg/superlu.hpp @@ -250,7 +250,8 @@ public: work (default false) */ void SetSymmetricPattern(bool sym); - /** @brief Specify whether to perform parallel symbolic factorization. + /** @brief Specify whether to perform parallel symbolic factorization + (default false) @note If true SuperLU will use superlu::PARMETIS for the Column Permutation regardless of the setting */ void SetParSymbFact(bool par); @@ -263,6 +264,10 @@ public: superlu::FACTORED*/ void SetFact(superlu::Fact fact); + /** @brief Specify whether to offload numerical factorization onto the device + (default true if SuperLU_DIST has been compiled with GPU support) */ + void SetDeviceOffload(bool offload); + // Processor grid for SuperLU_DIST. const int nprow_, npcol_, npdep_; diff --git a/makefile b/makefile index 9d36e46b0c..ec36fc45d1 100644 --- a/makefile +++ b/makefile @@ -126,11 +126,11 @@ EXAMPLE_TEST_DIRS := examples MINIAPP_SUBDIRS = common electromagnetics meshing performance tools \ toys nurbs gslib adjoint solvers shifted mtop parelag tribol autodiff dfem \ hooke multidomain dpg hdiv-linear-solver spde diag-smoothers contact \ - fluids/navier fluids/schrodinger-flow + fluids/navier fluids/schrodinger-flow plasma MINIAPP_DIRS := $(addprefix miniapps/,$(MINIAPP_SUBDIRS)) MINIAPP_TEST_DIRS := $(filter-out %/common,$(MINIAPP_DIRS)) MINIAPP_USE_COMMON := $(addprefix miniapps/,electromagnetics meshing tools \ - toys shifted dpg diag-smoothers fluids/navier) + toys shifted dpg diag-smoothers fluids/navier plasma) EM_DIRS = $(EXAMPLE_DIRS) $(MINIAPP_DIRS) @@ -794,7 +794,6 @@ status info: $(info MFEM_MPI_NP = $(MFEM_MPI_NP)) @true -ASTYLE_BIN = astyle ASTYLE = $(ASTYLE_BIN) --options=$(SRC)config/mfem.astylerc ASTYLE_VER = "Artistic Style Version 3.1" FORMAT_FILES = $(foreach dir,$(DIRS) $(EM_DIRS) config,$(dir)/*.?pp) diff --git a/mesh/mesh.cpp b/mesh/mesh.cpp index c16e739e3a..5327bb6bd5 100644 --- a/mesh/mesh.cpp +++ b/mesh/mesh.cpp @@ -493,8 +493,7 @@ void Mesh::GetBdrElementTransformation(int i, { for (int j = 0; j < n; j++) { - int idx = vdofs[n*k+j]; - pm(k,j) = nodes((idx<0)? -1-idx:idx); + pm(k,j) = nodes(UnsignIndex(vdofs[n*k+j])); } } ElTr->SetFE(bdr_el); @@ -1356,7 +1355,7 @@ Mesh::FaceInformation Mesh::GetFaceInformation(int f) const face.element[0].conformity = ElementConformity::Coincident; face.element[1].conformity = ElementConformity::Coincident; face.element[1].location = ElementLocation::FaceNbr; - face.element[1].index = -1 - e2; + face.element[1].index = FlipIndexSign(e2); face.element[1].orientation = inf2%64; } } @@ -1379,7 +1378,7 @@ Mesh::FaceInformation Mesh::GetFaceInformation(int f) const face.element[1].location = ElementLocation::FaceNbr; face.element[0].conformity = ElementConformity::Coincident; face.element[1].conformity = ElementConformity::Superset; - face.element[1].index = -1 - e2; + face.element[1].index = FlipIndexSign(e2); face.element[1].orientation = inf2%64; } face.point_matrix = nc_faces_info[ncface].PointMatrix; @@ -1405,7 +1404,7 @@ Mesh::FaceInformation Mesh::GetFaceInformation(int f) const face.element[1].location = ElementLocation::FaceNbr; face.element[0].conformity = ElementConformity::Superset; face.element[1].conformity = ElementConformity::Coincident; - face.element[1].index = -1 - e2; + face.element[1].index = FlipIndexSign(e2); face.element[1].orientation = inf2%64; face.point_matrix = nc_faces_info[ncface].PointMatrix; } @@ -1438,7 +1437,7 @@ Mesh::FaceInformation::operator Mesh::FaceInfo() const break; case FaceInfoTag::SharedConforming: res.Elem1No = element[0].index; - res.Elem2No = -1 - element[1].index; + res.Elem2No = FlipIndexSign(element[1].index); res.Elem1Inf = element[0].orientation + element[0].local_face_id*64; res.Elem2Inf = element[1].orientation + element[1].local_face_id*64; break; @@ -1448,7 +1447,7 @@ Mesh::FaceInformation::operator Mesh::FaceInfo() const break; case FaceInfoTag::SharedSlaveNonconforming: res.Elem1No = element[0].index; - res.Elem2No = -1 - element[1].index; + res.Elem2No = FlipIndexSign(element[1].index); res.Elem1Inf = element[0].orientation + element[0].local_face_id*64; res.Elem2Inf = element[1].orientation + element[1].local_face_id*64; break; @@ -1456,7 +1455,7 @@ Mesh::FaceInformation::operator Mesh::FaceInfo() const break; case FaceInfoTag::GhostSlave: res.Elem1No = element[0].index; - res.Elem2No = -1 - element[1].index; + res.Elem2No = FlipIndexSign(element[1].index); res.Elem1Inf = element[0].orientation + element[0].local_face_id*64; res.Elem2Inf = element[1].orientation + element[1].local_face_id*64; break; @@ -6514,7 +6513,7 @@ void Mesh::LoadPatchTopo(std::istream &input, Array &edge_to_ukv) input >> edge_to_ukv[j] >> v[0] >> v[1]; if (v[0] > v[1]) { - edge_to_ukv[j] = -1 - edge_to_ukv[j]; + edge_to_ukv[j] = FlipIndexSign(edge_to_ukv[j]); } } } @@ -6551,9 +6550,6 @@ void Mesh::GetEdgeToUniqueKnotvector(Array &edge_to_ukv, const int NP = NumOfElements; // number of patches const int NPKV = NP * dim; // number of patch knotvectors constexpr int notset = -9999999; - // Sign convention - auto flipSign = [](int i) { return -1 - i; }; - auto unSign = [](int i) { return (i < 0) ? -1 - i : i; }; // Local edge index -> dimension convention auto edge_to_dim = [](int i) { return (i < 8) ? ((i & 1) ? 1 : 0) : 2; }; @@ -6569,7 +6565,7 @@ void Mesh::GetEdgeToUniqueKnotvector(Array &edge_to_ukv, { GetElementVertices(i, v); // Sign is based on the edge's vertex indices - edge_to_ukv[i] = (v[1] > v[0]) ? i : flipSign(i); + edge_to_ukv[i] = (v[1] > v[0]) ? i : FlipIndexSign(i); ukv_to_rpkv[i] = i; } return; @@ -6619,14 +6615,14 @@ void Mesh::GetEdgeToUniqueKnotvector(Array &edge_to_ukv, // We've set this edge already - link this index to it if (edge_to_pkv[edge] != notset) { - const int pkv_other = unSign(edge_to_pkv[edge]); + const int pkv_other = UnsignIndex(edge_to_pkv[edge]); unite(pkv, pkv_other); } else { GetEdgeVertices(edge, v); // Sign is based on the edge's vertex indices - edge_to_pkv[edge] = (v[1] > v[0]) ? pkv : flipSign(pkv); + edge_to_pkv[edge] = (v[1] > v[0]) ? pkv : FlipIndexSign(pkv); } } } @@ -6653,10 +6649,10 @@ void Mesh::GetEdgeToUniqueKnotvector(Array &edge_to_ukv, edge_to_ukv.SetSize(NumOfEdges); for (int i = 0; i < NumOfEdges; i++) { - const int pkv = unSign(edge_to_pkv[i]); + const int pkv = UnsignIndex(edge_to_pkv[i]); const int rpkv = pkv_to_rpkv[pkv]; const int ukv = rpkv_to_ukv[rpkv]; - edge_to_ukv[i] = (edge_to_pkv[i] < 0) ? flipSign(ukv) : ukv; + edge_to_ukv[i] = (edge_to_pkv[i] < 0) ? FlipIndexSign(ukv) : ukv; } CorrectPatchTopoOrientations(edge_to_ukv); @@ -6667,9 +6663,6 @@ void Mesh::CorrectPatchTopoOrientations(Array &edge_to_ukv) const const int dim = Dimension(); // Topological (not physical) dimension if (dim == 1) { return; } - // Sign convention - auto flipSign = [](int i) { return -1 - i; }; - const Table *face2elem = GetFaceToElementTable(); Array pfaces, orient; Array fe, feo; @@ -6688,7 +6681,7 @@ void Mesh::CorrectPatchTopoOrientations(Array &edge_to_ukv) const for (auto e : fe) { const int skv = edge_to_ukv[e]; - if (skv == kv || flipSign(skv) == kv) { hasKV = true; } + if (skv == kv || FlipIndexSign(skv) == kv) { hasKV = true; } } if (hasKV) { @@ -6718,7 +6711,7 @@ void Mesh::CorrectPatchTopoOrientations(Array &edge_to_ukv) const }; } - Array ukvs((dim==2) ? 4 : 12); + Array ukvs((dim == 2) ? 4 : 12); Array pe, oe; bool initKV = false; @@ -6732,7 +6725,7 @@ void Mesh::CorrectPatchTopoOrientations(Array &edge_to_ukv) const for (int i = 0; i < pe.Size(); i++) { ukvs[i] = edge_to_ukv[pe[i]]; - ukvs[i] = (oe[i] < 0) ? flipSign(ukvs[i]) : ukvs[i]; + ukvs[i] = (oe[i] < 0) ? FlipIndexSign(ukvs[i]) : ukvs[i]; } // Find the direction with this kv. @@ -6740,12 +6733,19 @@ void Mesh::CorrectPatchTopoOrientations(Array &edge_to_ukv) const for (int d=0; d= 0, ""); + if (thisDir == -1) + { + return false; + } // For this direction, find any edge already set. If no edge is set, we // arbitrarily take the first. @@ -6777,12 +6777,12 @@ void Mesh::CorrectPatchTopoOrientations(Array &edge_to_ukv) const } const int edge = pe[i]; - if ((dim == 2 && ukvs[i] != flipSign(ukvs[ref_edge0])) || - (dim == 3 && ukvs[i] == flipSign(ukvs[ref_edge0]))) + if ((dim == 2 && ukvs[i] != FlipIndexSign(ukvs[ref_edge0])) || + (dim == 3 && ukvs[i] == FlipIndexSign(ukvs[ref_edge0]))) { // Flip the sign of this edge - MFEM_VERIFY(!edgeSet[edge], ""); - edge_to_ukv[edge] = flipSign(edge_to_ukv[edge]); + MFEM_ASSERT(!edgeSet[edge], ""); + edge_to_ukv[edge] = FlipIndexSign(edge_to_ukv[edge]); } edgeSet[edge] = true; @@ -6827,10 +6827,11 @@ void Mesh::CorrectPatchTopoOrientations(Array &edge_to_ukv) const int unsetDim = -1; for (int d=0; d &edge_to_ukv) const continue; } - const int kv_signed = edge_to_ukv[pe[dir_edges[unsetDim][0]]]; - const int kv = kv_signed < 0 ? flipSign(kv_signed) : kv_signed; - MFEM_VERIFY(!edgeSet[pe[dir_edges[unsetDim][0]]], ""); + const int kv = UnsignIndex(edge_to_ukv[pe[dir_edges[unsetDim][0]]]); initKV = false; @@ -6891,6 +6890,7 @@ void Mesh::CorrectPatchTopoOrientations(Array &edge_to_ukv) const } } +#ifdef MFEM_DEBUG bool allSet = true; for (auto eset : edgeSet) { @@ -6899,7 +6899,8 @@ void Mesh::CorrectPatchTopoOrientations(Array &edge_to_ukv) const allSet = false; } } - MFEM_VERIFY(allSet && unset.size() == 0, "Some edge is not set"); + MFEM_ASSERT(allSet && unset.size() == 0, "Some edge is not set"); +#endif delete face2elem; } @@ -6941,7 +6942,7 @@ void Mesh::LoadNonconformingPatchTopo(std::istream &input, if (v[0] > v[1]) { - ukv = -1 - ukv; + ukv = FlipIndexSign(ukv); } edge_to_ukv[j] = ukv; } @@ -12438,11 +12439,7 @@ void Mesh::PrintTopoEdges(std::ostream &os, const Array &e_to_k, for (int i = 0; i < NumOfEdges; i++) { edge_vertex->GetRow(i, vert); - int ki = e_to_k[i]; - if (ki < 0) - { - ki = -1 - ki; - } + const int ki = UnsignIndex(e_to_k[i]); if (vmap) { @@ -15751,9 +15748,18 @@ Mesh PartitionMPI(int dim, int mpi_cnt, int elem_per_mpi, bool print, { MFEM_VERIFY(dim > 1, "Not implemented for 1D meshes."); - auto factor = [&](int N) + // Closest int divisor to the cubit root, going down. + auto factor3 = [](int N) { - for (int i = static_cast(sqrt(N)); i > 0; i--) + for (int i = static_cast(round(cbrt(N))); i > 0; i--) + { if (N % i == 0) { return i; } } + return 1; + }; + + // Closest int divisor to the square root, going down. + auto factor2 = [](int N) + { + for (int i = static_cast(round(sqrt(N))); i > 0; i--) { if (N % i == 0) { return i; } } return 1; }; @@ -15777,22 +15783,22 @@ Mesh PartitionMPI(int dim, int mpi_cnt, int elem_per_mpi, bool print, int el0_x, el0_y, el0_z; if (dim == 2) { - mpi_x = factor(mpi_cnt); + mpi_x = factor2(mpi_cnt); mpi_y = mpi_cnt / mpi_x; // Switch order for better balance. - el0_y = factor(el0); + el0_y = factor2(el0); el0_x = el0 / el0_y; } else { - mpi_x = factor(mpi_cnt); - mpi_y = factor(mpi_cnt / mpi_x); + mpi_x = factor3(mpi_cnt); + mpi_y = factor2(mpi_cnt / mpi_x); mpi_z = mpi_cnt / mpi_x / mpi_y; // Switch order for better balance. - el0_z = factor(el0); - el0_y = factor(el0 / el0_z); + el0_z = factor3(el0); + el0_y = factor2(el0 / el0_z); el0_x = el0 / el0_y / el0_z; } diff --git a/mesh/mesh.hpp b/mesh/mesh.hpp index 70f1403f91..9c62ab0cb0 100644 --- a/mesh/mesh.hpp +++ b/mesh/mesh.hpp @@ -2078,12 +2078,13 @@ public: contrary to the ones obtained through Mesh::GetFacesElements and can directly be used, e.g., Elem1 and Elem2 indices. Likewise the orientations for Elem1 and Elem2 already take into account - special cases and can be used as is. - */ + special cases and can be used as is. */ struct FaceInformation { + /// The face topology (boundary, conforming, or nonconforming). FaceTopology topology; + /// Information about the adjacent elements. struct { ElementLocation location; @@ -2093,8 +2094,13 @@ public: int orientation; } element[2]; + /// Detailed face information (see FaceInfoTag). FaceInfoTag tag; + + /// If the face is nonconforming, the index of the NC face. -1 otherwise. int ncface; + + /// The point matrix for nonconforming faces. const DenseMatrix* point_matrix; /** @brief Return true if the face is a local interior face which is NOT @@ -2113,21 +2119,20 @@ public: /** @brief return true if the face is an interior face to the computation domain, either a local or shared interior face (not a boundary face) - which is NOT a master nonconforming face. - */ + which is NOT a master nonconforming face. */ bool IsInterior() const { return topology == FaceTopology::Conforming || topology == FaceTopology::Nonconforming; } - /** @brief Return true if the face is a boundary face. */ + /// Return true if the face is a boundary face. bool IsBoundary() const { return topology == FaceTopology::Boundary; } - /// @brief Return true if the face is of the same type as @a type. + /// Return true if the face is of the same type as @a type. bool IsOfFaceType(FaceType type) const { switch (type) @@ -2141,13 +2146,13 @@ public: } } - /// @brief Return true if the face is a conforming face. + /// Return true if the face is a conforming face. bool IsConforming() const { return topology == FaceTopology::Conforming; } - /// @brief Return true if the face is a nonconforming fine face. + /// Return true if the face is a nonconforming fine face. bool IsNonconformingFine() const { return topology == FaceTopology::Nonconforming && @@ -2155,7 +2160,7 @@ public: element[1].conformity == ElementConformity::Superset); } - /// @brief Return true if the face is a nonconforming coarse face. + /// Return true if the face is a nonconforming coarse face. /** Note that ghost nonconforming master faces cannot be clearly identified as such with the currently available information, so this method will return false for such faces. */ @@ -2165,7 +2170,7 @@ public: element[1].conformity == ElementConformity::Subset; } - /// @brief cast operator from FaceInformation to FaceInfo. + /// cast operator from FaceInformation to FaceInfo. operator Mesh::FaceInfo() const; }; diff --git a/mesh/mesh_operators.cpp b/mesh/mesh_operators.cpp index 7564ca993b..2cc81916d5 100644 --- a/mesh/mesh_operators.cpp +++ b/mesh/mesh_operators.cpp @@ -143,7 +143,7 @@ int ThresholdRefiner::ApplyImpl(Mesh &mesh) if (num_marked_elements == 0LL) { return STOP; } mesh.GeneralRefinement(marked_elements, non_conforming, nc_limit); - return CONTINUE + REFINED; + return static_cast(CONTINUE) + static_cast(REFINED); } void ThresholdRefiner::Reset() @@ -162,7 +162,7 @@ int ThresholdDerefiner::ApplyImpl(Mesh &mesh) const Vector &local_err = estimator.GetLocalErrors(); bool derefs = mesh.DerefineByError(local_err, threshold, nc_limit, op); - return derefs ? CONTINUE + DEREFINED : NONE; + return derefs ? static_cast(CONTINUE) + static_cast(DEREFINED) : NONE; } @@ -290,7 +290,7 @@ int CoefficientRefiner::PreprocessMesh(Mesh &mesh, int max_it) } delete l2fes; delete gf; - return CONTINUE + REFINED; + return static_cast(CONTINUE) + static_cast(REFINED); } @@ -310,7 +310,7 @@ int Rebalancer::ApplyImpl(Mesh &mesh) if (pmesh && pmesh->Nonconforming()) { pmesh->Rebalance(); - return CONTINUE + REBALANCED; + return static_cast(CONTINUE) + static_cast(REBALANCED); } #endif return NONE; diff --git a/mesh/ncmesh.cpp b/mesh/ncmesh.cpp index 0c17d7b373..ddcdcfab37 100644 --- a/mesh/ncmesh.cpp +++ b/mesh/ncmesh.cpp @@ -3542,7 +3542,7 @@ void NCMesh::TraverseQuadFace(int vn0, int vn1, int vn2, int vn3, // create a slave face record with a degenerate point matrix face_list.slaves.Append( - Slave(-1 - enode.edge_index, + Slave(FlipIndexSign(enode.edge_index), eid[0].element, eid[0].local, Geometry::SQUARE)); Slave &sl = face_list.slaves.Last(); @@ -3589,7 +3589,7 @@ void NCMesh::TraverseTetEdge(int vn0, int vn1, const Point &p0, const Point &p1, // non-slave edge is really a (face-)slave itself. const MeshId &eid = *eid_and_type.id; face_list.slaves.Append( - Slave(-1 - eid.index, eid.element, eid.local, Geometry::TRIANGLE)); + Slave(FlipIndexSign(eid.index), eid.element, eid.local, Geometry::TRIANGLE)); int v0index = nodes[vn0].vert_index; int v1index = nodes[vn1].vert_index; diff --git a/mesh/ncnurbs.cpp b/mesh/ncnurbs.cpp index 54d8b9a126..6bcf83e6d5 100644 --- a/mesh/ncnurbs.cpp +++ b/mesh/ncnurbs.cpp @@ -93,7 +93,7 @@ void NCNURBSExtension::GetMasterEdgeEntities( } else { - const int auxEdge = -1 - edge_i; + const int auxEdge = FlipIndexSign(edge_i); GetAuxEdgeVertices(auxEdge, sverts); } @@ -159,7 +159,7 @@ void NCNURBSExtension::FindAdditionalFacesSA( { if (edge < 0) { - sideAuxEdges[s].Append(-1 - edge); + sideAuxEdges[s].Append(FlipIndexSign(edge)); } else { @@ -456,7 +456,7 @@ void NCNURBSExtension::FindAdditionalFacesSA( == afverts[j], ""); } - ori_f2 = -1 - ori_f2; + ori_f2 = FlipIndexSign(ori_f2); } else { @@ -468,7 +468,7 @@ void NCNURBSExtension::FindAdditionalFacesSA( } facePairs.emplace_back(FacePairInfo{fverts[vMinID], f, - SlaveFaceInfo{-1 - afid, ori_f2, + SlaveFaceInfo{FlipIndexSign(afid), ori_f2, {fki(vMinID,0), fki(vMinID,1)}, { fki((vMinID + 2) % 4,0) - fki(vMinID,0), @@ -509,7 +509,7 @@ void NCNURBSExtension::FindAdditionalFacesSA( auxFaces.push_back(auxFace); facePairs.emplace_back(FacePairInfo{fverts[vMinID], f, - SlaveFaceInfo{-1 - auxFaceId, ori_f, + SlaveFaceInfo{FlipIndexSign(auxFaceId), ori_f, {fki(vMinID,0), fki(vMinID,1)}, { fki((vMinID + 2) % 4,0) - fki(vMinID,0), @@ -622,7 +622,7 @@ void NCNURBSExtension::GetAuxFaceEdges(int auxFace, Array &edges) const } else // Auxiliary edge { - edges[i] = -1 - auxv2e.at(edge_v); + edges[i] = FlipIndexSign(auxv2e.at(edge_v)); } } } @@ -633,7 +633,7 @@ int OffsetHelper(int i, int j, const Array &a, const Array &b) { if (i < 0) { - return b[-1 - i + j]; + return b[FlipIndexSign(i) + j]; } else if (i + j < a.Size()) { @@ -679,7 +679,7 @@ void NCNURBSExtension::GetMasterEdgeDofs(bool dof, int me, } else // Auxiliary edge { - GetAuxEdgeVertices(-1 - slaveId, svert); + GetAuxEdgeVertices(FlipIndexSign(slaveId), svert); } bool reverse = false; @@ -872,7 +872,7 @@ void ReorderArray2D(int i0, int j0, const Array2D &a, // Set a quadrilateral vertex index permutation for a given orientation. void GetVertexOrdering(int ori, std::array &perm) { - const int oriAbs = ori < 0 ? -1 - ori : ori; + const int oriAbs = UnsignIndex(ori); for (int i=0; i<4; ++i) { @@ -1094,7 +1094,7 @@ void NCNURBSExtension::GetMasterFaceDofs(bool dof, int mf, if (slaveId < 0) { // Auxiliary face - const int auxFace = -1 - slaveId; + const int auxFace = FlipIndexSign(slaveId); // Set slave face entity dimensions. if (dof) @@ -1171,7 +1171,7 @@ void NCNURBSExtension::GetMasterFaceDofs(bool dof, int mf, } else { - const int auxEdge = -1 - edge; + const int auxEdge = FlipIndexSign(edge); GetAuxEdgeVertices(auxEdge, evert); } MFEM_ASSERT(evert[0] == vstart || evert[1] == vstart, ""); @@ -1184,7 +1184,7 @@ void NCNURBSExtension::GetMasterFaceDofs(bool dof, int mf, // dimensions of the master face, by using ori. int e1 = -1, e2 = -1; { - const int aori = ori < 0 ? -1 - ori : ori; + const int aori = UnsignIndex(ori); if (aori % 2 == 0) { e1 = 0; @@ -1416,14 +1416,15 @@ void NCNURBSExtension::ProcessVertexToKnot2D(const VertexToKnotSpan &v2k, { // Create a new auxiliary edge auxv2e[childPair] = auxEdges.size(); - auxEdges.emplace_back(AuxiliaryEdge{pv[0] < pv[1] ? - parentEdge : -1 - parentEdge, + auxEdges.emplace_back(AuxiliaryEdge{pv[0] < pv[1] ? parentEdge : + FlipIndexSign(parentEdge), {childPair.first, childPair.second}, {newParentEdge ? 0 : prevKI, ks}}); } } - const int childEdge = childPairTopo ? v2e[childPair] : -1 - auxv2e[childPair]; + const int childEdge = childPairTopo ? v2e[childPair] : + FlipIndexSign(auxv2e[childPair]); // Check whether this is the final vertex in this parent edge. Note that // the logic for comparing (pv[0],pv[1]) to the next parents assumes the @@ -1460,14 +1461,15 @@ void NCNURBSExtension::ProcessVertexToKnot2D(const VertexToKnotSpan &v2k, // -1 denotes `ne` at endpoint auxEdges.emplace_back(AuxiliaryEdge{pv[0] < pv[1] ? - -1 - parentEdge : parentEdge, + FlipIndexSign(parentEdge) : + parentEdge, {finalChildPair.first, finalChildPair.second}, {ks, -1}}); } } const int finalChildEdge = finalChildPairTopo ? v2e[finalChildPair] : - -1 - auxv2e[finalChildPair]; + FlipIndexSign(auxv2e[finalChildPair]); edgePairs.emplace_back(-1, -1, finalChildEdge, parentEdge); } @@ -1805,7 +1807,7 @@ void NCNURBSExtension::ProcessVertexToKnot3D( auxFaces.push_back(auxFace); facePairs.emplace_back( FacePairInfo{cv[0], parentFace, - SlaveFaceInfo{-1 - auxv2f[childPair], + SlaveFaceInfo{FlipIndexSign(auxv2f[childPair]), 0, {i0, j0}, {d0, d1}}}); } } @@ -2111,7 +2113,7 @@ void NCNURBSExtension::ProcessVertexToKnot3D( auxv2e[childPair] = auxEdges.size(); auxEdges.emplace_back(AuxiliaryEdge{pv0 < pv1 ? parentEdge : - -1 - parentEdge, + FlipIndexSign(parentEdge), {childPair.first, childPair.second}, {knotIndex0, knotIndex1}}); } @@ -2131,7 +2133,8 @@ void NCNURBSExtension::ProcessVertexToKnot3D( const EdgePairInfo ep_e((e_idx == n_d - de) ? -1 : tv, (e_idx == n_d - de) ? -1 : tvki, - -1 - auxv2e[childPair], parentEdge); + FlipIndexSign(auxv2e[childPair]), + parentEdge); const bool unset = !edgePairs[edgePairOS[parentEdge] + e_idx].isSet; if (unset) @@ -2226,7 +2229,7 @@ void NCNURBSExtension::GetAuxFaceToPatchTable(Array2D &auxface2patch) if (s < 0) { // Auxiliary face. - const int aux = -1 - s; + const int aux = FlipIndexSign(s); if (auxface2patch(aux, 0) >= 0) { if (auxface2patch(aux, 1) != -1) { consistent = false; } @@ -2316,7 +2319,7 @@ void NCNURBSExtension::UpdateAuxiliaryKnotSpans(const Array &rf) for (auto auxEdge : auxEdges) { const int p = auxEdge.parent; - const int parent = p < 0 ? -1 - p : p; + const int parent = UnsignIndex(p); const int kv = KnotInd(parent); for (int i=0; i<2; ++i) { @@ -2382,14 +2385,8 @@ int NCNURBSExtension::AuxiliaryEdgeNE(int aux_edge) const int signedParentEdge = auxEdges[aux_edge].parent; const int ki0 = auxEdges[aux_edge].ksi[0]; const int ki1raw = auxEdges[aux_edge].ksi[1]; - int ki1 = ki1raw; - if (ki1raw == -1) - { - const bool rev = signedParentEdge < 0; - const int parentEdge = rev ? -1 - signedParentEdge : signedParentEdge; - ki1 = KnotVec(parentEdge)->GetNE(); - } - + const int ki1 = ki1raw == -1 ? KnotVec(UnsignIndex(signedParentEdge))->GetNE() + : ki1raw; return ki1 - ki0; } @@ -2403,7 +2400,7 @@ void NCNURBSExtension::SlaveEdgeToParent(int se, int parent, Array sev(2); if (se < 0) // Auxiliary edge { - for (int i=0; i<2; ++i) { sev[i] = auxEdges[-1 - se].v[i]; } + for (int i=0; i<2; ++i) { sev[i] = auxEdges[FlipIndexSign(se)].v[i]; } } else { @@ -2459,7 +2456,7 @@ void NCNURBSExtension::GetMasterEdgePieceOffsets(int mid, Array &os) } else { - nes = AuxiliaryEdgeNE(-1 - s); + nes = AuxiliaryEdgeNE(FlipIndexSign(s)); } os[i+1] = os[i] + nes; @@ -2565,7 +2562,7 @@ int NCNURBSExtension::SetPatchFactors(int p) } else // Aux edge { - const int aux_edge = -1 - s; + const int aux_edge = FlipIndexSign(s); if (auxef[aux_edge].Size() == 0) { auxef[aux_edge].SetSize(AuxiliaryEdgeNE(aux_edge)); @@ -2611,7 +2608,7 @@ int NCNURBSExtension::SetPatchFactors(int p) } MFEM_VERIFY(consistent, ""); - return partialChange ? -1 - dirSet : dirSet; + return partialChange ? FlipIndexSign(dirSet) : dirSet; } void NCNURBSExtension::PropagateFactorsForKV(int rf_default) @@ -2715,7 +2712,7 @@ void NCNURBSExtension::PropagateFactorsForKV(int rf_default) if (s < 0) { // Auxiliary face. - const int aux = -1 - s; + const int aux = FlipIndexSign(s); for (int i=0; i<2; ++i) { const int patch = auxface2patch(aux, i); @@ -2763,7 +2760,7 @@ void NCNURBSExtension::PropagateFactorsForKV(int rf_default) const int dirSetSigned = SetPatchFactors(p); const bool partialChange = dirSetSigned < 0; - const int dirSet = partialChange ? -1 - dirSetSigned : dirSetSigned; + const int dirSet = UnsignIndex(dirSetSigned); const bool changed = (patchState[p] != dirSet) || partialChange; patchState[p] = dirSet; @@ -2806,8 +2803,8 @@ void NCNURBSExtension::PropagateFactorsForKV(int rf_default) { const int dirSetSigned_i = SetPatchFactors(i); const bool partialChange_i = dirSetSigned_i < 0; - const int dirSet_i = partialChange_i ? -1 - dirSetSigned_i : - dirSetSigned_i; + const int dirSet_i = partialChange_i ? + FlipIndexSign(dirSetSigned_i) : dirSetSigned_i; const bool changed_i = (patchState[i] != dirSet_i) || partialChange_i; patchState[p] = dirSet_i; @@ -3027,7 +3024,7 @@ int GetFaceOrientation(const Mesh *mesh, const int face, // Check whether ordering is reversed. const bool rev = verts[(s + 1) % 4] != fverts[1]; - if (rev) { s = -1 - s; } // Reversed order is encoded by the sign. + if (rev) { s = FlipIndexSign(s); } // Reversed order is encoded by the sign. return s; } @@ -3040,7 +3037,7 @@ int GetFaceOrientation(const Mesh *mesh, const int face, // see GetFaceOrientation. bool Reorder2D(int ori, std::array &s0) { - const int shift = ori < 0 ? -1 - ori : ori; + const int shift = UnsignIndex(ori); // Shift is an F1 index in the counter-clockwise ordering of 4 quad vertices. // Now find the (i,j) indices of this index, with i,j in {0,1}. @@ -3064,7 +3061,7 @@ void GetInverseShiftedDimensions2D(int signedShift, int sm, int sn, int &m, int &n) { const bool rev = (signedShift < 0); - const int shift = rev ? -1 - signedShift : signedShift; + const int shift = UnsignIndex(signedShift); MFEM_ASSERT(0 <= shift && shift < 4, ""); // We consider 8 cases for the possible values of rev and shift. @@ -3136,7 +3133,7 @@ void GetShiftedGridPoints2D(int m, int n, int i, int j, int signedShift, int& sm, int& sn, int& si, int& sj) { const bool rev = (signedShift < 0); - const int shift = rev ? -1 - signedShift : signedShift; + const int shift = UnsignIndex(signedShift); MFEM_ASSERT(0 <= shift && shift < 4, ""); // (0,0) <= (i,j) < (m,n) are old indices, and old vertex [shift] maps @@ -3798,8 +3795,7 @@ void NCNURBSExtension::GenerateOffsets() const int signedParentEdge = auxEdges[e].parent; const int ki0 = auxEdges[e].ksi[0]; const int ki1raw = auxEdges[e].ksi[1]; - const bool rev = signedParentEdge < 0; - const int parentEdge = rev ? -1 - signedParentEdge : signedParentEdge; + const int parentEdge = UnsignIndex(signedParentEdge); const int masterNE = KnotVec(parentEdge)->GetNE(); const int ki1 = ki1raw == -1 ? masterNE : ki1raw; const int perEdgeCP = GetNCPperEdge(KnotVec(e)); diff --git a/mesh/nurbs.cpp b/mesh/nurbs.cpp index e87c21e73d..e41c891906 100644 --- a/mesh/nurbs.cpp +++ b/mesh/nurbs.cpp @@ -43,13 +43,30 @@ KnotVector::KnotVector(istream &input) KnotVector::KnotVector(int order, int NCP) { + if (NCP == -1) + { + NumOfControlPoints = order + 1; + } + else + { + NumOfControlPoints = NCP; + } Order = order; - NumOfControlPoints = NCP; knot.SetSize(NumOfControlPoints + Order + 1); NumOfElements = 0; coarse = false; - - knot = -1.; + if (NCP == -1) + { + for (int i = 0 ; i < Order + 1; i++) + { + knot[i] = 0.0; + knot[i + Order + 1] = 1.0; + } + } + else + { + knot = -1.; + } } KnotVector::KnotVector(int order, const Vector &k) @@ -3706,10 +3723,7 @@ bool NURBSExtension::CheckPatches() for (int i = 0; i < edges.Size(); i++) { edges[i] = edge_to_ukv[edges[i]]; - if (oedge[i] < 0) - { - edges[i] = -1 - edges[i]; - } + if (oedge[i] < 0) { edges[i] = FlipIndexSign(edges[i]); } } // In 2d - opposite edges must be same knotvector with opposite sign. @@ -3723,7 +3737,7 @@ bool NURBSExtension::CheckPatches() // {7, 6}, {4, 7}, {0, 4}, {1, 5}, {2, 6}, {3, 7} for Geometry::CUBE in 3D // See fem/geom.cpp for these definitions. if ((dim == 2 && - (edges[0] != -1 - edges[2] || edges[1] != -1 - edges[3])) || + (edges[0] != FlipIndexSign(edges[2]) || edges[1] != FlipIndexSign(edges[3]))) || (dim == 3 && (edges[0] != edges[2] || edges[0] != edges[4] || @@ -3752,7 +3766,7 @@ void NURBSExtension::CheckBdrPatches() edges[i] = edge_to_ukv[edges[i]]; if (oedge[i] < 0) { - edges[i] = -1 - edges[i]; + edges[i] = FlipIndexSign(edges[i]); } } @@ -4749,14 +4763,13 @@ void NURBSExtension::GenerateBdrElementDofTable() SetPatchToBdrElements(); int *dof = bel_dof->GetJ(); - int ndof = bel_dof->Size_of_connections(); + const int ndof = bel_dof->Size_of_connections(); for (int i = 0; i < ndof; i++) { - int idx = dof[i]; + const int idx = dof[i]; if (idx < 0) { - dof[i] = -1 - (activeDof[-1-idx] - 1); - dof[i] = -activeDof[-1-idx]; + dof[i] = -activeDof[FlipIndexSign(idx)]; } else { @@ -4841,12 +4854,12 @@ void NURBSExtension::Generate2DBdrElementDofTable() for (int ii = 0; ii <= ord0; ii++) { conn.to = DofMap(p2g[(okv[0] >= 0) ? (i+ii) : (nx-i-ii)]); - if (s == -1) { conn.to = -1 -conn.to; } + if (s == -1) { conn.to = FlipIndexSign(conn.to); } bel_dof_list.Append(conn); } } bel_to_patch[lbe] = b; - bel_to_IJK(lbe,0) = (okv[0] >= 0) ? i : (-1-i); + bel_to_IJK(lbe,0) = (okv[0] >= 0) ? i : FlipIndexSign(i); lbe++; } gbe++; @@ -4919,14 +4932,14 @@ void NURBSExtension::Generate3DBdrElementDofTable() { const int ii_ = (okv[0] >= 0) ? (i+ii) : (nx-i-ii); conn.to = DofMap(p2g(ii_, jj_)); - if (s == -1) { conn.to = -1 -conn.to; } + if (s == -1) { conn.to = FlipIndexSign(conn.to); } bel_dof_list.Append(conn); } } } bel_to_patch[lbe] = b; - bel_to_IJK(lbe,0) = (okv[0] >= 0) ? i : (-1-i); - bel_to_IJK(lbe,1) = (okv[1] >= 0) ? j : (-1-j); + bel_to_IJK(lbe,0) = (okv[0] >= 0) ? i : FlipIndexSign(i); + bel_to_IJK(lbe,1) = (okv[1] >= 0) ? j : FlipIndexSign(j); lbe++; } gbe++; diff --git a/mesh/nurbs.hpp b/mesh/nurbs.hpp index 5c0eb0819b..cf9d1952c5 100644 --- a/mesh/nurbs.hpp +++ b/mesh/nurbs.hpp @@ -74,9 +74,13 @@ public: integers are read, for order and number of control points. */ KnotVector(std::istream &input); - /** @brief Create a KnotVector with undefined knots (initialized to -1) of - order @a order and number of control points @a NCP. */ - KnotVector(int order, int NCP); + /** @brief Create a KnotVector with order @a order. + When @a NCP is not provided the number of control points is set to + @a order + 1, and the first @a order + 1 knots are set to 0 and last + @a order + 1 knots are set to 1. + When @a NCP is given number of control points is @a NCP and + the knots are initialized to -1) */ + KnotVector(int order, int NCP = -1); /** @brief Create a KnotVector with order @a order and knots @a knot. If @a k has the correct number of repeated knots at the begin and end, @@ -88,12 +92,10 @@ public: /** @brief Create a KnotVector by passing in a degree, a Vector of interval lengths of length n, and a list of continuity of length n + 1. - The intervals refer to spans between unique knot values (not counting zero-size intervals at repeated knots), and the continuity values should be >= -1 (discontinuous) and <= order-1 (maximally-smooth for the given - polynomial degree). Periodicity is not supported. - */ + polynomial degree). Periodicity is not supported.*/ KnotVector(int order, const Vector& intervals, const Array& continuity); @@ -218,7 +220,7 @@ public: @a u. The main purpose of this function is its use in FindInterpolant. Use GetBotella instead for each shape function separately, perhaps in - conjuction with GetSpan and GetRefPoint.*/ + conjunction with GetSpan and GetRefPoint.*/ MFEM_DEPRECATED void FindMaxima(Array &ks, Vector &xi, Vector &u) const; /** @brief Global curve interpolation through the points @a x (overwritten). @@ -1396,8 +1398,7 @@ inline const real_t &NURBSPatch::operator()(int i, int j, int k, int l) const inline int NURBSExtension::KnotInd(int edge) const { - const int kv = edge_to_ukv[edge]; - return kv >= 0 ? kv : -1 - kv; + return UnsignIndex(edge_to_ukv[edge]); } inline int NURBSExtension::KnotSign(int edge) const @@ -1427,7 +1428,7 @@ const else { *okv = -oedge; - return knotVectors[-1-kv]; + return knotVectors[FlipIndexSign(kv)]; } } diff --git a/mesh/pncmesh.cpp b/mesh/pncmesh.cpp index e8c15100a8..f3b3831670 100644 --- a/mesh/pncmesh.cpp +++ b/mesh/pncmesh.cpp @@ -400,7 +400,7 @@ void ParNCMesh::MakeSharedList(const NCList &list, NCList &shared) } else // special case: prism edge-face constraint { - if (entity_owner[1][-1-si] != MyRank) + if (entity_owner[1][FlipIndexSign(si)] != MyRank) { master_flag |= 0x2; } @@ -571,9 +571,10 @@ void ParNCMesh::CalculatePMatrixGroups() ranks.SetSize(0); for (int j = master_face.slaves_begin; j < master_face.slaves_end; j++) { - int si = face_list.slaves[j].index; - int owner = (si >= 0) ? entity_owner[2][si] // standard face dependency - /* */ : entity_owner[1][-1 - si]; // prism edge-face dep + const int si = face_list.slaves[j].index; + const int owner = + (si >= 0) ? entity_owner[2][si] : // standard face dependency + entity_owner[1][FlipIndexSign(si)]; // prism edge-face dep ranks.Append(groups[owner][0]); } ranks.Sort(); @@ -1181,7 +1182,7 @@ void ParNCMesh::GetFaceNeighbors(ParMesh &pmesh) if (e[0]->rank == MyRank) { std::swap(e[0], e[1]); } Mesh::FaceInfo &fi = pmesh.faces_info[cf.index]; - fi.Elem2No = -1 - fnbr_index[e[0]->index - NElements]; + fi.Elem2No = FlipIndexSign(fnbr_index[e[0]->index - NElements]); if (Dim == 3) { @@ -1211,7 +1212,6 @@ void ParNCMesh::GetFaceNeighbors(ParMesh &pmesh) if (Dim <= 2) { nfaces = NEdges, nghosts = NGhostEdges; } // enlarge Mesh::faces_info for ghost slaves - MFEM_ASSERT(pmesh.faces_info.Size() == nfaces, ""); MFEM_ASSERT(pmesh.GetNumFaces() == nfaces, ""); pmesh.faces_info.SetSize(nfaces + nghosts); for (int i = nfaces; i < pmesh.faces_info.Size(); i++) @@ -1271,7 +1271,7 @@ void ParNCMesh::GetFaceNeighbors(ParMesh &pmesh) // In other words, side 1 IS the side that generated the face. } MFEM_ASSERT(fi.Elem2No >= NElements, ""); - fi.Elem2No = -1 - fnbr_index[fi.Elem2No - NElements]; + fi.Elem2No = FlipIndexSign(fnbr_index[fi.Elem2No - NElements]); const DenseMatrix* pm = full_list.point_matrices[sf.geom][sf.matrix]; if (!sloc && Dim == 3) @@ -1312,7 +1312,6 @@ void ParNCMesh::GetFaceNeighbors(ParMesh &pmesh) // Mesh::ApplyLocalSlaveTransformation. } - MFEM_ASSERT(fi.NCFace < 0, "fi.NCFace = " << fi.NCFace); fi.NCFace = pmesh.nc_faces_info.Size(); pmesh.nc_faces_info.Append(Mesh::NCFaceInfo(true, sf.master, pm)); } @@ -2288,7 +2287,7 @@ void ParNCMesh::Derefine(const Array &derefs) if (element_type[index] == 0) { // this coarse element will get pruned, encode who owns it now - index = -1 - elements[coarse[i]].rank; + index = FlipIndexSign(elements[coarse[i]].rank); } transforms.embeddings[i].parent = index; } diff --git a/miniapps/CMakeLists.txt b/miniapps/CMakeLists.txt index e4d953c3cb..ba5197f5b0 100644 --- a/miniapps/CMakeLists.txt +++ b/miniapps/CMakeLists.txt @@ -35,6 +35,7 @@ add_subdirectory(multidomain) add_subdirectory(nurbs) add_subdirectory(parelag) add_subdirectory(performance) +add_subdirectory(plasma) add_subdirectory(shifted) add_subdirectory(solvers) add_subdirectory(spde) diff --git a/miniapps/common/particles_extras.cpp b/miniapps/common/particles_extras.cpp index 6e447f3fdc..7dafbf64ce 100644 --- a/miniapps/common/particles_extras.cpp +++ b/miniapps/common/particles_extras.cpp @@ -174,7 +174,6 @@ ParticleTrajectories::ParticleTrajectories(const ParticleSet &particles, void ParticleTrajectories::AddSegmentStart() { - if (!pset.GetNParticles()) { return; } // Create a new mesh for all particle segments for this timestep segment_meshes.emplace_front(1, pset.GetNParticles()*2, pset.GetNParticles(), @@ -200,11 +199,10 @@ void ParticleTrajectories::AddSegmentStart() void ParticleTrajectories::SetSegmentEnd() { - if (segment_meshes.empty()) { return; } // no segments to end - const Array &end_ids = pset.GetIDs(); - // Add all endpoint vertices + segments for all particles + // Add all endpoint vertices + segments for all particles that were in + // SetSegmentStart int num_start = segment_ids.front().Size(); for (int i = 0; i < num_start; i++) { @@ -230,11 +228,6 @@ void ParticleTrajectories::SetSegmentEnd() void ParticleTrajectories::Visualize() { SetSegmentEnd(); - if (segment_meshes.empty() && !mesh) - { - AddSegmentStart(); - return; - } // Create a mesh of all the trajectory segments std::vector all_meshes; @@ -246,8 +239,23 @@ void ParticleTrajectories::Visualize() { all_meshes.push_back(mesh); } + if (mesh_bb) + { + all_meshes.push_back(mesh_bb); + } Mesh trajectories(all_meshes.data(), all_meshes.size()); + bool vis = trajectories.GetNE() > 0; +#ifdef MFEM_USE_MPI + MPI_Allreduce(MPI_IN_PLACE, &vis, 1, MFEM_MPI_CXX_BOOL, + MPI_LOR, pset.GetComm()); +#endif // MFEM_USE_MPI + if (!vis) // if all rank have 0 elements, skip visualization + { + AddSegmentStart(); + return; + } + #ifdef MFEM_USE_MPI VisualizeMesh(sock, vishost, visport, trajectories, comm, @@ -260,5 +268,97 @@ void ParticleTrajectories::Visualize() AddSegmentStart(); } +void ParticleTrajectories::SetVisualizationBoundingBox(const Vector &xmin, + const Vector &xmax) +{ + MFEM_VERIFY(xmin.Size() == pset.GetDim() && + xmax.Size() == pset.GetDim(), + "Bounding box dimension must match ParticleSet dimension."); + + // Create a box mesh for visualization + if (mesh_bb) + { + delete mesh_bb; + mesh_bb = nullptr; + } + + if (pset.GetDim() == 2) + { + int dim = 2; + int nvert = 4; + int nelem = 4; + mesh_bb = new Mesh(1, nvert, nelem, 0, dim); + Vector v0(dim), v1(dim), v2(dim), v3(dim); + v0 = xmin; + v1 = xmax; + v2[0] = xmax[0]; v2[1] = xmin[1]; + v3[0] = xmin[0]; v3[1] = xmax[1]; + + mesh_bb->AddVertex(v0); + mesh_bb->AddVertex(v1); + mesh_bb->AddVertex(v2); + mesh_bb->AddVertex(v3); + + int vi[2] = {0,1}; + mesh_bb->AddSegment(vi); + vi[0] = 1; vi[1] = 2; + mesh_bb->AddSegment(vi); + vi[0] = 2; vi[1] = 3; + mesh_bb->AddSegment(vi); + vi[0] = 3; vi[1] = 0; + mesh_bb->AddSegment(vi); + mesh_bb->FinalizeMesh(); + } + else // dim == 3 + { + int dim = 3; + int nvert = 8; + int nelem = 12; + mesh_bb = new Mesh(1, nvert, nelem, 0, dim); + Vector v(dim); + + // Vertices + v[0] = xmin[0]; v[1] = xmin[1]; v[2] = xmin[2]; + mesh_bb->AddVertex(v); // 0: 000 + v[0] = xmax[0]; v[1] = xmin[1]; v[2] = xmin[2]; + mesh_bb->AddVertex(v); // 1: 100 + v[0] = xmax[0]; v[1] = xmax[1]; v[2] = xmin[2]; + mesh_bb->AddVertex(v); // 2: 110 + v[0] = xmin[0]; v[1] = xmax[1]; v[2] = xmin[2]; + mesh_bb->AddVertex(v); // 3: 010 + + v[0] = xmin[0]; v[1] = xmin[1]; v[2] = xmax[2]; + mesh_bb->AddVertex(v); // 4: 001 + v[0] = xmax[0]; v[1] = xmin[1]; v[2] = xmax[2]; + mesh_bb->AddVertex(v); // 5: 101 + v[0] = xmax[0]; v[1] = xmax[1]; v[2] = xmax[2]; + mesh_bb->AddVertex(v); // 6: 111 + v[0] = xmin[0]; v[1] = xmax[1]; v[2] = xmax[2]; + mesh_bb->AddVertex(v); // 7: 011 + + // Segments + int vi[2]; + // Bottom face + vi[0] = 0; vi[1] = 1; mesh_bb->AddSegment(vi); + vi[0] = 1; vi[1] = 2; mesh_bb->AddSegment(vi); + vi[0] = 2; vi[1] = 3; mesh_bb->AddSegment(vi); + vi[0] = 3; vi[1] = 0; mesh_bb->AddSegment(vi); + + // Top face + vi[0] = 4; vi[1] = 5; mesh_bb->AddSegment(vi); + vi[0] = 5; vi[1] = 6; mesh_bb->AddSegment(vi); + vi[0] = 6; vi[1] = 7; mesh_bb->AddSegment(vi); + vi[0] = 7; vi[1] = 4; mesh_bb->AddSegment(vi); + + // Vertical edges + vi[0] = 0; vi[1] = 4; mesh_bb->AddSegment(vi); + vi[0] = 1; vi[1] = 5; mesh_bb->AddSegment(vi); + vi[0] = 2; vi[1] = 6; mesh_bb->AddSegment(vi); + vi[0] = 3; vi[1] = 7; mesh_bb->AddSegment(vi); + + mesh_bb->FinalizeMesh(); + } +} + } // namespace common } // namespace mfem diff --git a/miniapps/common/particles_extras.hpp b/miniapps/common/particles_extras.hpp index cdd9d96804..f6eda90216 100644 --- a/miniapps/common/particles_extras.hpp +++ b/miniapps/common/particles_extras.hpp @@ -46,7 +46,8 @@ class ParticleTrajectories { protected: const ParticleSet &pset; - Mesh *mesh = nullptr; + Mesh *mesh = nullptr; // optional edge mesh to visualize along with particles + Mesh *mesh_bb = nullptr; // optional bounding box mesh for visualization socketstream sock; /// Track particle IDs that exist at the segment start. @@ -90,10 +91,24 @@ public: const char *keys_=nullptr); /// Add a mesh to be visualized along with the particle trajectories. - void AddMeshForVisualization(Mesh *mesh_) { mesh = mesh_; } + void AddMeshForVisualization(Mesh *mesh_) + { + MFEM_VERIFY(mesh_->Dimension() == 1, + "Mesh dimension must be 1 to match the particle trajectory."); + mesh = mesh_; + } /// Visualize the particle trajectories (and mesh if provided). void Visualize(); + + /// Set the bounding box for visualization. + void SetVisualizationBoundingBox(const Vector &xmin, const Vector &xmax); + + /// Destructor + ~ParticleTrajectories() + { + delete mesh_bb; + } }; diff --git a/miniapps/electromagnetics/CMakeLists.txt b/miniapps/electromagnetics/CMakeLists.txt index d750bc6c78..c498a39625 100644 --- a/miniapps/electromagnetics/CMakeLists.txt +++ b/miniapps/electromagnetics/CMakeLists.txt @@ -34,11 +34,13 @@ if (MFEM_USE_MPI) EXTRA_HEADERS maxwell_solver.hpp ${MFEM_MINIAPPS_COMMON_HEADERS} LIBRARIES mfem-common) - add_mfem_miniapp(lorentz - MAIN lorentz.cpp - EXTRA_HEADERS ${MFEM_MINIAPPS_COMMON_HEADERS} - LIBRARIES mfem-common) - + if (MFEM_USE_GSLIB) + add_mfem_miniapp(lorentz + MAIN lorentz.cpp + EXTRA_HEADERS ${MFEM_MINIAPPS_COMMON_HEADERS} + LIBRARIES mfem-common) +endif() + # Add the corresponding tests to the "test" target if (MFEM_ENABLE_TESTING) add_test(NAME tesla_np=4 diff --git a/miniapps/electromagnetics/lorentz.cpp b/miniapps/electromagnetics/lorentz.cpp index c3c0f30ad1..2d683dfb21 100644 --- a/miniapps/electromagnetics/lorentz.cpp +++ b/miniapps/electromagnetics/lorentz.cpp @@ -13,8 +13,8 @@ // Lorentz Miniapp: Simple Lorentz Force Particle Mover // ----------------------------------------------------- // -// This miniapp computes the trajectory of a single charged particle subject to -// Lorentz forces. +// This miniapp computes the trajectories of a set of charged particles subject +// to Lorentz forces. // // dp/dt = q (E + v x B) // @@ -23,11 +23,14 @@ // // The electric and magnetic fields are read from VisItDataCollection objects // such as those produced by the Volta and Tesla miniapps. It is notable that -// these two fields do not need to be defined on the same mesh. Of course, the -// particle trajectory can only be computed on the intersection of the two -// domains. The starting point of the path must be chosen within in this -// intersection and the trajectory will terminate when it leaves the -// intersection or reaches a specified time duration. +// these two fields do not need to be defined on the same mesh. At least +// one of either an electric field or a magnetic field must be provided. The +// particles' locations and momenta are randomly initialized within a bounding +// box specified by command line input. +// +// This miniapp demonstrates the use of ParticleSet with FindPointsGSLIB. When +// particles leave either domains, they are subject to removal. Redistribution +// of particle data between MPI ranks is also demonstrated. // // Note that the VisItDataCollection objects must have been stored using the // parallel format e.g. visit_dc.SetFormat(DataCollection::PARALLEL_FORMAT);. @@ -37,31 +40,23 @@ // // Sample runs: // -// Free particle moving with constant velocity -// mpirun -np 4 lorentz -p0 '1 1 1' -// -// Particle accelerating in a constant electric field +// Particles accelerating in a constant electric field // mpirun -np 4 volta -m ../../data/inline-hex.mesh -dbcs '1 6' -dbcv '0 1' -// mpirun -np 4 lorentz -er Volta-AMR-Parallel -x0 '0.5 0.5 0.9' -p0 '1 0 0' +// mpirun -np 4 lorentz -er Volta-AMR-Parallel -npt 100 -xmin '0.0 0.0 0.0' -xmax '1.0 1.0 1.0' -pmin '1 0 0' -pmax '1 0 0' -rdf 0 -vt 0 -nt 100 // -// Particle accelerating in a constant magnetic field +// Particles accelerating in a constant magnetic field // mpirun -np 4 tesla -m ../../data/inline-hex.mesh -ubbc '0 0 1' -// mpirun -np 4 lorentz -br Tesla-AMR-Parallel -x0 '0.1 0.5 0.1' -p0 '0 0.4 0.1' -tf 9 +// mpirun -np 4 lorentz -br Tesla-AMR-Parallel -npt 10 -xmin '0.0 0.0 0.0' -xmax '1.0 1.0 1.0' -pmin '0 0.1 0.05' -pmax '0 0.4 0.1' -nt 1000 -rdf 0 -vt 0 // // Magnetic mirror effect near a charged sphere and a bar magnet // mpirun -np 4 volta -m ../../data/ball-nurbs.mesh -dbcs 1 -cs '0 0 0 0.1 2e-11' -rs 2 -maxit 4 // mpirun -np 4 tesla -m ../../data/fichera.mesh -maxit 4 -rs 3 -bm '-0.1 -0.1 -0.1 0.1 0.1 0.1 0.1 -1e10' -// mpirun -np 4 lorentz -er Volta-AMR-Parallel -ec 4 -br Tesla-AMR-Parallel -bc 4 -x0 '0.8 0 0' -p0 '-8 -4 4' -q -10 -tf 0.2 -dt 1e-3 -rf 1e-6 -// -// This miniapp demonstrates the use of the ParMesh::FindPoints functionality -// to evaluate field data from stored DataCollection objects. While this -// miniapp is far from a full particle-in-cell (PIC) code it does demonstrate -// some of the building blocks that might be used to construct the particle -// mover portion of a PIC code. +// mpirun -np 4 lorentz -er Volta-AMR-Parallel -ec 4 -br Tesla-AMR-Parallel -bc 4 -q -10 -dt 1e-4 -nt 2000 -npt 500 -vt 10 -rdf 500 -rdm 1 -vf 10 -pmin '-8 -4 4' -pmax '-8 -4 4' -xmin '-1 -1 -1' -xmax '1 1 1' +// mpirun -np 4 lorentz -er Volta-AMR-Parallel -ec 4 -br Tesla-AMR-Parallel -bc 4 -q -10 -dt 1e-3 -npt 1 -vt 650 -rdf 500 -rdm 1 -vf 2 -pmin '-8 -4 4' -pmax '-8 -4 4' -xmin '0.8 0 0' -xmax '0.8 0 0' -nt 1300 #include "mfem.hpp" -#include "../common/fem_extras.hpp" -#include "../common/pfem_extras.hpp" +#include "../common/particles_extras.hpp" + #include "electromagnetics.hpp" #include #include @@ -71,250 +66,176 @@ using namespace mfem; using namespace mfem::common; using namespace mfem::electromagnetics; -typedef DataCollection::FieldMapType fields_t; - -/// This class implements the Boris algorithm as described in the -/// article `Why is Boris algorithm so good?` by H. Qin et al in -/// Physics of Plasmas, Volume 20 Issue 8, August 2013, -/// https://doi.org/10.1063/1.4818428. -class BorisAlgorithm +struct LorentzContext { -private: - real_t charge_; - real_t mass_; - - ParMesh *E_pmesh_; - ParGridFunction *E_field_; - - ParMesh *B_pmesh_; - ParGridFunction *B_field_; - - mutable Array elem_id_; - mutable Array ip_; - - mutable Vector E_; - mutable Vector B_; - mutable Vector pxB_; - mutable Vector pm_; - mutable Vector pp_; - - // Returns true if a usable V has been found. If @a pgf is NULL, V = 0 is - // returned as a default value. - bool GetValue(ParMesh *pmesh, ParGridFunction *pgf, Vector q, Vector &V) + struct DColl { - DenseMatrix point(q.GetData(), 3, 1); + string coll_name; + string field_name; + int cycle; + int pad_digits_cycle; + int pad_digits_rank; + }; + DColl E{"", "E", 10, 6, 6}; + DColl B{"", "B", 10, 6, 6}; - int pt_found = - (pmesh != NULL) ? pmesh->FindPoints(point, elem_id_, ip_, false) : -1; - - // We have a mesh but the point was not found. The path must be outside - // the domain of interest. - if (pmesh != NULL && pt_found <= 0) { return false; } - - int pt_root = -1; - - if (pt_found > 0 && elem_id_[0] >= 0 && pgf != NULL) - { - pt_root = pmesh->GetMyRank(); - - pgf->GetVectorValue(elem_id_[0], ip_[0], V); - } - else - { - pt_root = 0; - V = 0.0; - } - - // Determine processor which found the field point - int glb_pt_root = -1; - MPI_Allreduce(&pt_root, &glb_pt_root, 1, - MPI_INT, MPI_MAX, MPI_COMM_WORLD); - - // Send the field value to the root processor - if (pmesh != NULL && elem_id_[0] >= 0 && glb_pt_root != 0) - { - MPI_Send(V.GetData(), 3, MPITypeMap::mpi_type, - 0, 1030, MPI_COMM_WORLD); - } - - // Receive the field value on the root processor - if (Mpi::Root() && pmesh != NULL && glb_pt_root != 0) - { - MPI_Status status; - MPI_Recv(V.GetData(), 3, MPITypeMap::mpi_type, - glb_pt_root, 1030, MPI_COMM_WORLD, &status); - } - return true; - } + int ordering = 1; // 0 - byNODES, 1 - byVDIM + int npt = 1; // total number of particles + real_t q = 1.0; // particle charge + real_t m = 1.0; // particle mass + Vector x_min{-1.0,-1.0,-1.0}; // initial position min + Vector x_max{1.0,1.0,1.0}; // initial position max + Vector p_min{-1.0,-1.0,-1.0}; // initial momentum min + Vector p_max{1.0,1.0,1.0}; // initial momentum max + real_t dt = 1e-2; // time step + int nt = 1000; // number of timesteps + int redist_interval = 5; // redistribution interval + int redist_mesh = 0; // redistribution mesh: 0: E mesh, 1: B mesh +} ctx; +/// This class implements the Boris algorithm as described in the article +/// `Why is Boris algorithm so good?` by H. Qin et al in Physics of Plasmas, +/// Volume 20 Issue 8, August 2013, https://doi.org/10.1063/1.4818428. +class Boris +{ public: - BorisAlgorithm(ParGridFunction *E_gf, - ParGridFunction *B_gf, - real_t charge, real_t mass) - : charge_(charge), mass_(mass), - E_field_(E_gf), - B_field_(B_gf), - E_(3), B_(3), pxB_(3), pm_(3), pp_(3) + /// Field indices + /** Allows for convenient access to corresponding ParticleVector from + ParticleSet. */ + enum Fields { - E_pmesh_ = (E_field_) ? E_field_->ParFESpace()->GetParMesh() : NULL; - B_pmesh_ = (B_field_) ? B_field_->ParFESpace()->GetParMesh() : NULL; - } + MASS, // vdim = 1 + CHARGE, // vdim = 1 + MOM, // vdim = dim + EFIELD, // vdim = dim + BFIELD // vdim = dim + }; +protected: + /// Pointers to E and B field GridFunctions + GridFunction *E_gf = nullptr; + GridFunction *B_gf = nullptr; - bool Step(Vector &q, Vector &p, real_t &t, real_t &dt) - { - // Locate current point in each mesh, evaluate the fields, and collect - // field values on the root processor. - if (!GetValue(E_pmesh_, E_field_, q, E_)) { return false; } - if (!GetValue(B_pmesh_, B_field_, q, B_)) { return false; } + /// FindPointsGSLIB objects for E and B field meshes + FindPointsGSLIB E_finder; + FindPointsGSLIB B_finder; - // Compute updated position and momentum using the Boris algorithm - if (Mpi::Root()) - { - // Compute half of the contribution from q E - add(p, 0.5 * dt * charge_, E_, pm_); + /// ParticleSet of charged particles + std::unique_ptr charged_particles; - // Compute the contributiobn from q p x B - const real_t B2 = B_ * B_; + // Temporary vectors for particle computation + mutable Vector pxB_, pm_, pp_; - // ... along pm x B - const real_t a1 = 4.0 * dt * charge_ * mass_; - pm_.cross3D(B_, pxB_); - pp_.Set(a1, pxB_); + /// Single particle Boris step + void ParticleStep(Particle &part, real_t &dt); +public: - // ... along pm - const real_t a2 = 4.0 * mass_ * mass_ - - dt * dt * charge_ * charge_ * B2; - pp_.Add(a2, pm_); + Boris(MPI_Comm comm, GridFunction *E_gf_, GridFunction *B_gf_, + int nparticles, Ordering::Type pdata_ordering); - // ... along B - const real_t a3 = 2.0 * dt * dt * charge_ * charge_ * (B_ * pm_); - pp_.Add(a3, B_); + /// Find Particles in mesh corresponding to E and B fields + void FindParticles(); - // scale by common denominator - const real_t a4 = 4.0 * mass_ * mass_ + - dt * dt * charge_ * charge_ * B2; - pp_ /= a4; + /// Update E and B fields at particle locations. Must be called + /// right after FindParticles has been called. + void EvaluateFieldsAtParticles(); - // Update the momentum - add(pp_, 0.5 * dt * charge_, E_, p); + /// Advance particles one time step using Boris algorithm + void Step(real_t &t, real_t &dt); - // Update the position - q.Add(dt / mass_, p); - } + /// Remove lost particles and return their indices + Array RemoveLostParticles(); - // Update the time - t += dt; + /// Redistribute particles based on \p redist_mesh (0 - E field, 1 - B field) + void Redistribute(int redist_mesh, Array &removed_idxs); - // Broadcast the updated position - MPI_Bcast(q.GetData(), 3, MPITypeMap::mpi_type, - 0, MPI_COMM_WORLD); + /// Get reference to the ParticleSet of charged particles + ParticleSet& GetParticles() { return *charged_particles; } - // Broadcast the updated momentum - MPI_Bcast(p.GetData(), 3, MPITypeMap::mpi_type, - 0, MPI_COMM_WORLD); - - return true; - } + /// Get reference to the E field FindPointsGSLIB object + FindPointsGSLIB& GetEFinder() { return E_finder; } }; -// Open the named VisItDataCollection and read the named field. -// Returns pointers to the two new objects. -int ReadGridFunction(const char * coll_name, const char * field_name, - int pad_digits_cycle, int pad_digits_rank, int cycle, - VisItDataCollection *&dc, ParGridFunction *& gf); - -// By default the initial position will be the center of the intersection -// of the bounding boxes of the meshes containing the E and B fields. -void SetInitialPosition(VisItDataCollection *E_dc, - VisItDataCollection *B_dc, - Vector &x_init); - -// Build a quadrilateral mesh approximating the trajectory as a -// ribbon. One edge of the ribbon follows the trajectory of the -// particle. The opposite edge is offset by the acceleration vector -// (scaled by a constant called the r_factor). -Mesh MakeTrajectoryMesh(int step, real_t m, real_t dt, real_t r_factor, - const DenseMatrix &pos_data, - const DenseMatrix &mom_data); - // Prints the program's logo to the given output stream void display_banner(ostream & os); +// Open the named VisItDataCollection and read the named field. +// Returns pointers to the two new objects. +int ReadGridFunction(std::string coll_name, std::string field_name, + int pad_digits_cycle, int pad_digits_rank, int cycle, + std::unique_ptr &dc, + ParGridFunction *&gf); + +// Initialize particles from user input. +void InitializeChargedParticles(ParticleSet &particles, const Vector &pos_min, + const Vector &pos_max, const Vector &x_init, + const Vector &p_init, real_t m, + real_t q); + int main(int argc, char *argv[]) { Mpi::Init(argc, argv); + int num_ranks = Mpi::WorldSize(); + int rank = Mpi::WorldRank(); Hypre::Init(); if ( Mpi::Root() ) { display_banner(cout); } - const char *E_coll_name = ""; - const char *E_field_name = "E"; - int E_cycle = 10; - int E_pad_digits_cycle = 6; - int E_pad_digits_rank = 6; - - const char *B_coll_name = ""; - const char *B_field_name = "B"; - int B_cycle = 10; - int B_pad_digits_cycle = 6; - int B_pad_digits_rank = 6; - - real_t q = 1.0; - real_t m = 1.0; - real_t dt = 1e-2; - real_t t_init = 0.0; - real_t t_final = 1.0; - real_t r_factor = -1.0; - Vector x_init; - Vector p_init; - int visport = 19916; - bool visualization = true; - bool visit = true; + bool visualization = true; // enable visualization + int vis_tail_size = 5; // particle trajectory tail size + int vis_interval = 4; // visualization interval OptionsParser args(argc, argv); - args.AddOption(&E_coll_name, "-er", "--e-root-file", + args.AddOption(&ctx.E.coll_name, "-er", "--e-root-file", "Set the VisIt data collection E field root file prefix."); - args.AddOption(&E_field_name, "-ef", "--e-field-name", + args.AddOption(&ctx.E.field_name, "-ef", "--e-field-name", "Set the VisIt data collection E field name"); - args.AddOption(&E_cycle, "-ec", "--e-cycle", + args.AddOption(&ctx.E.cycle, "-ec", "--e-cycle", "Set the E field cycle index to read."); - args.AddOption(&E_pad_digits_cycle, "-epdc", "--e-pad-digits-cycle", + args.AddOption(&ctx.E.pad_digits_cycle, "-epdc", "--e-pad-digits-cycle", "Number of digits in E field cycle."); - args.AddOption(&E_pad_digits_rank, "-epdr", "--e-pad-digits-rank", + args.AddOption(&ctx.E.pad_digits_rank, "-epdr", "--e-pad-digits-rank", "Number of digits in E field MPI rank."); - args.AddOption(&B_coll_name, "-br", "--b-root-file", + args.AddOption(&ctx.B.coll_name, "-br", "--b-root-file", "Set the VisIt data collection B field root file prefix."); - args.AddOption(&B_field_name, "-bf", "--b-field-name", + args.AddOption(&ctx.B.field_name, "-bf", "--b-field-name", "Set the VisIt data collection B field name"); - args.AddOption(&B_cycle, "-bc", "--b-cycle", + args.AddOption(&ctx.B.cycle, "-bc", "--b-cycle", "Set the B field cycle index to read."); - args.AddOption(&B_pad_digits_cycle, "-bpdc", "--b-pad-digits-cycle", + args.AddOption(&ctx.B.pad_digits_cycle, "-bpdc", "--b-pad-digits-cycle", "Number of digits in B field cycle."); - args.AddOption(&B_pad_digits_rank, "-bpdr", "--b-pad-digits-rank", + args.AddOption(&ctx.B.pad_digits_rank, "-bpdr", "--b-pad-digits-rank", "Number of digits in B field MPI rank."); - args.AddOption(&q, "-q", "--charge", - "Particle charge."); - args.AddOption(&m, "-m", "--mass", - "Particle mass."); - args.AddOption(&dt, "-dt", "--time-step", - "Time Step."); - args.AddOption(&t_init, "-ti", "--initial-time", - "Initial Time."); - args.AddOption(&t_final, "-tf", "--final-time", - "Final Time."); - args.AddOption(&x_init, "-x0", "--initial-position", - "Initial position."); - args.AddOption(&p_init, "-p0", "--initial-momentum", - "Initial momentum."); - args.AddOption(&r_factor, "-rf", "--ribbon-factor", - "Scale factor for ribbon width (rf * (p1-p0) / (m * dt) " - "where p0 and p1 are computed momenta)."); + args.AddOption(&ctx.redist_interval, "-rdf", "--redist-interval", + "Redistribution after this many timesteps. 0 means " + "no redistribution."); + args.AddOption(&ctx.redist_mesh, "-rdm", "--redistribution-mesh", + "Particle domain mesh for redistribution. 0 for E field mesh." + " 1 for B field mesh."); + args.AddOption(&ctx.ordering, "-o", "--ordering", + "Ordering of particle data. 0 = byNODES, 1 = byVDIM."); + args.AddOption(&ctx.npt, "-npt", "--num-particles", + "Total number of particles."); + args.AddOption(&ctx.m, "-m", "--mass", "Particles' mass."); + args.AddOption(&ctx.q, "-q", "--charge", "Particles' charge."); + args.AddOption(&ctx.x_min, "-xmin", "--x-min", + "Minimum initial particle location."); + args.AddOption(&ctx.x_max, "-xmax", "--x-max", + "Maximum initial particle location."); + args.AddOption(&ctx.p_min, "-pmin", "--p-min", + "Minimum initial particle momentum."); + args.AddOption(&ctx.p_max, "-pmax", "--p-max", + "Maximum initial particle momentum."); + args.AddOption(&ctx.dt, "-dt", "--time-step", "Time Step."); + args.AddOption(&ctx.nt, "-nt", "--num-timesteps", "Number of timesteps."); args.AddOption(&visualization, "-vis", "--visualization", "-no-vis", "--no-visualization", "Enable or disable GLVis visualization."); - args.AddOption(&visit, "-visit", "--visit", "-no-visit", "--no-visit", - "Enable or disable VisIt visualization."); - args.AddOption(&visport, "-p", "--send-port", "Socket for GLVis."); + args.AddOption(&vis_tail_size, "-vt", "--vis-tail-size", + "GLVis visualization trajectory truncation tail size."); + args.AddOption(&vis_interval, "-vf", "--vis-interval", + "GLVis visualization update after this many timesteps. " + "0 means no visualization."); + args.Parse(); if (!args.Good()) { @@ -324,137 +245,310 @@ int main(int argc, char *argv[]) } return 1; } - if (r_factor <= 0.0) - { - r_factor = dt; - } + if (Mpi::Root()) { args.PrintOptions(cout); } - VisItDataCollection *E_dc = NULL; - ParGridFunction *E_gf = NULL; + std::unique_ptr E_dc, B_dc; + ParGridFunction *E_gf = nullptr, *B_gf = nullptr; + Vector bb_xmin, bb_xmax; - if (strcmp(E_coll_name, "")) + // Read E field if provided + if (ctx.E.coll_name != "") { - if (ReadGridFunction(E_coll_name, E_field_name, E_pad_digits_cycle, - E_pad_digits_rank, E_cycle, E_dc, E_gf)) + if (ReadGridFunction(ctx.E.coll_name, ctx.E.field_name, + ctx.E.pad_digits_cycle, ctx.E.pad_digits_rank, + ctx.E.cycle, E_dc, E_gf)) { - mfem::out << "Error loading E field" << endl; + mfem::err << "Error loading E field" << endl; return 1; } + E_gf->ParFESpace()->GetParMesh()->GetBoundingBox(bb_xmin, bb_xmax, 2); + } + + // Read B field if provided + if (ctx.B.coll_name != "") + { + if (ReadGridFunction(ctx.B.coll_name, ctx.B.field_name, + ctx.B.pad_digits_cycle, ctx.B.pad_digits_rank, + ctx.B.cycle, B_dc, B_gf)) + { + mfem::err << "Error loading B field" << endl; + return 1; + } + Vector bb_xmint, bb_xmaxt; + B_gf->ParFESpace()->GetParMesh()->GetBoundingBox(bb_xmint, bb_xmaxt, 2); + if (ctx.E.coll_name != "") + { + // compute intersection of bounding boxes + for (int d = 0; d < bb_xmin.Size(); d++) + { + bb_xmin[d] = std::max(bb_xmin[d], bb_xmint[d]); + bb_xmax[d] = std::min(bb_xmax[d], bb_xmaxt[d]); + } + } + else + { + bb_xmin = bb_xmint; + bb_xmax = bb_xmaxt; + } } - VisItDataCollection *B_dc = NULL; - ParGridFunction *B_gf = NULL; + Ordering::Type ordering_type = ctx.ordering == 0 ? + Ordering::byNODES : Ordering::byVDIM; - if (strcmp(B_coll_name, "")) + // Initialize particles + int num_particles = ctx.npt/num_ranks + + (rank < (ctx.npt % num_ranks) ? 1 : 0); + Boris boris(MPI_COMM_WORLD, E_gf, B_gf, num_particles, ordering_type); + InitializeChargedParticles(boris.GetParticles(), ctx.x_min, ctx.x_max, + ctx.p_min, ctx.p_max, ctx.m, ctx.q); + boris.FindParticles(); + boris.EvaluateFieldsAtParticles(); + + real_t t = 0.0; + real_t dt = ctx.dt; + + // Setup visualization + char vishost[] = "localhost"; + socketstream pre_redist_sock, post_redist_sock; + std::unique_ptr traj_vis; + if (visualization) { - if (ReadGridFunction(B_coll_name, B_field_name, B_pad_digits_cycle, - B_pad_digits_rank, B_cycle, B_dc, B_gf)) - { - mfem::out << "Error loading B field" << endl; - return 1; - } + const char *keys = "baaa"; + traj_vis = std::make_unique(boris.GetParticles(), + vis_tail_size, + vishost, 19916, + "Trajectories", + 0, 0, 600, 600, keys); + traj_vis->SetVisualizationBoundingBox(bb_xmin, bb_xmax); } - if (x_init.Size() < 3) - { - SetInitialPosition(E_dc, B_dc, x_init); - } - if (p_init.Size() < 3) - { - p_init.SetSize(3); p_init = 0.0; - } - if (Mpi::Root()) - { - mfem::out << "Initial position: "; x_init.Print(mfem::out); - mfem::out << "Initial momentum: "; p_init.Print(mfem::out); - } - - BorisAlgorithm boris(E_gf, B_gf, q, m); - Vector pos(x_init); - Vector mom(p_init); - - ofstream ofs("Lorentz.dat"); - ofs.precision(14); - - int nsteps = 1 + (int)ceil((t_final - t_init) / dt); - DenseMatrix pos_data(3, nsteps); - DenseMatrix mom_data(3, nsteps + 1); - mom_data.SetCol(0, p_init); - - if (Mpi::Root()) - { - mfem::out << "Maximum number of steps: " << nsteps << endl; - } - - int step = -1; - real_t t = t_init; - do + for (int step = 1; step <= ctx.nt; step++) { + // Step the Boris algorithm + boris.Step(t, dt); if (Mpi::Root()) { - ofs << t - << '\t' << pos[0] << '\t' << pos[1] << '\t' << pos[2] - << '\t' << mom[0] << '\t' << mom[1] << '\t' << mom[2] - << '\n'; - } - step++; - - pos_data.SetCol(step, pos); - mom_data.SetCol(step + 1, mom); - } - while (boris.Step(pos, mom, t, dt) && step < nsteps - 1); - - if (Mpi::Root() && (visit || visualization)) - { - Mesh trajectory = MakeTrajectoryMesh(step, m, dt, r_factor, - pos_data, mom_data); - - L2_FECollection fec_l2(0, 2); - FiniteElementSpace fes_l2(&trajectory, &fec_l2); - GridFunction traj_time(&fes_l2); - for (int i=0; iVisualize(); } - if (visualization) + // Remove lost particles from particle set and output + Array removed_idxs = boris.RemoveLostParticles(); + + // Redistribute + if (ctx.redist_interval > 0 && step % ctx.redist_interval == 0 && + boris.GetParticles().GetGlobalNParticles() > 0) { - socketstream traj_sock; - traj_sock.precision(8); - - char vishost[] = "localhost"; - - int Wx = 0, Wy = 0; // window position - int Ww = 350, Wh = 350; // window size - - VisualizeField(traj_sock, vishost, visport, - traj_time, "Trajectory", Wx, Wy, Ww, Wh); + // Redistribute particles - prior to redistribution, removed any lost + // particles that were just removed from the set. + boris.Redistribute(ctx.redist_mesh, removed_idxs); } } - if (Mpi::Root()) - { - mfem::out << "Number of steps taken: " << step << endl; - } - - // Clean up - delete E_dc; - delete B_dc; } -// Print the Lorentz ascii logo to the given ostream +void Boris::ParticleStep(Particle &part, real_t &dt) +{ + Vector &x = part.Coords(); + real_t m = part.FieldValue(MASS); + real_t q = part.FieldValue(CHARGE); + Vector &p = part.Field(MOM); + Vector &e = part.Field(EFIELD); + Vector &b = part.Field(BFIELD); + + // Compute half of the contribution from q E + add(p, 0.5 * dt * q, e, pm_); + + // Compute the contribution from q p x B + const real_t B2 = b * b; + + // ... along pm x B + const real_t a1 = 4.0 * dt * q * m; + pm_.cross3D(b, pxB_); + pp_.Set(a1, pxB_); + + // ... along pm + const real_t a2 = 4.0 * m * m - + dt * dt * q * q * B2; + pp_.Add(a2, pm_); + + // ... along B + const real_t a3 = 2.0 * dt * dt * q * q * (b * pm_); + pp_.Add(a3, b); + + // scale by common denominator + const real_t a4 = 4.0 * m * m + + dt * dt * q * q * B2; + pp_ /= a4; + + // Update the momentum + add(pp_, 0.5 * dt * q, e, p); + + // Update the position + x.Add(dt / m, p); +} + +Boris::Boris(MPI_Comm comm, GridFunction *E_gf_, GridFunction *B_gf_, + int nparticles, Ordering::Type pdata_ordering) + : E_gf(E_gf_), + B_gf(B_gf_), + E_finder(comm), + B_finder(comm) +{ + MFEM_VERIFY(E_gf || B_gf, "Must pass an E field or B field to Boris."); + + Mesh *E_mesh = E_gf ? E_gf->FESpace()->GetMesh() : nullptr; + Mesh *B_mesh = B_gf ? B_gf->FESpace()->GetMesh() : nullptr; + if (E_mesh && B_mesh) + { + int E_dim = E_mesh->SpaceDimension(); + int B_dim = B_mesh->SpaceDimension(); + MFEM_VERIFY(E_dim == B_dim, + "E mesh and B mesh must have the same spatial dimension."); + } + if (E_gf) + { + E_mesh->EnsureNodes(); + E_finder.Setup(*E_mesh); + } + if (B_gf) + { + B_mesh->EnsureNodes(); + B_finder.Setup(*B_mesh); + } + + int dim = E_mesh ? E_mesh->SpaceDimension() : B_mesh->SpaceDimension(); + + pxB_.SetSize(dim); pm_.SetSize(dim); pp_.SetSize(dim); + + /// Create particle set: + /// 2 scalars of mass and charge, + /// 3 vectors of size space dim for momentum, e field, and b field + Array field_vdims({1, 1, dim, dim, dim}); + + charged_particles = std::make_unique + (comm, nparticles, dim, field_vdims, 0, pdata_ordering); +} + +void Boris::FindParticles() +{ + ParticleVector &X = charged_particles->Coords(); + + // Find particles in E and B field meshes + if (E_gf) + { + E_finder.FindPoints(X); // X.GetOrdering() used internally + } + if (B_gf) + { + B_finder.FindPoints(X); // X.GetOrdering() used internally + } +} + +void Boris::EvaluateFieldsAtParticles() +{ + ParticleVector &E = charged_particles->Field(EFIELD); + ParticleVector &B = charged_particles->Field(BFIELD); + + // Interpolate E-field + B-field onto particles + if (E_gf) + { + E_finder.Interpolate(*E_gf, E, E.GetOrdering()); + } + else + { + E = 0.0; + } + if (B_gf) + { + B_finder.Interpolate(*B_gf, B, B.GetOrdering()); + } + else + { + B = 0.0; + } +} + +void Boris::Step(real_t &t, real_t &dt) +{ + // Interpolate E and B fields onto particles + EvaluateFieldsAtParticles(); + + // Individually step each particle. If all ParticleSet fields are ordered + // byVDIM, we can use GetParticleRef for better performance. + if (charged_particles->IsParticleRefValid()) + { + for (int i = 0; i < charged_particles->GetNParticles(); i++) + { + Particle p = charged_particles->GetParticleRef(i); + ParticleStep(p, dt); + } + } + else + { + for (int i = 0; i < charged_particles->GetNParticles(); i++) + { + Particle p = charged_particles->GetParticle(i); + ParticleStep(p, dt); + charged_particles->SetParticle(i, p); + } + } + + // Find updated particle locations in E and B field meshes + FindParticles(); + + // Update time + t += dt; +} + +Array Boris::RemoveLostParticles() +{ + Array lost_idxs; + const Array E_lost = E_finder.GetPointsNotFoundIndices(); + const Array B_lost = B_finder.GetPointsNotFoundIndices(); + + for (const int &elem : E_lost) + { + lost_idxs.Union(elem); + } + + for (const int &elem : B_lost) + { + lost_idxs.Union(elem); + } + + charged_particles->RemoveParticles(lost_idxs); + return lost_idxs; +} + +void Boris::Redistribute(int redist_mesh, Array &removed_idxs) +{ + if (redist_mesh == 0 && E_gf) + { + Array proc_list = E_finder.GetProc(); + proc_list.DeleteAt(removed_idxs); + charged_particles->Redistribute(proc_list); + } + else + { + Array proc_list = B_finder.GetProc(); + proc_list.DeleteAt(removed_idxs); + charged_particles->Redistribute(proc_list); + } + + // Find particles again since ParticleSet is not yet synced with + // FindPointsGSLIB objects. + FindParticles(); +} + void display_banner(ostream & os) { os << " ____ __ " @@ -471,29 +565,22 @@ void display_banner(ostream & os) << endl << flush; } -int ReadGridFunction(const char * coll_name, const char * field_name, +int ReadGridFunction(std::string coll_name, std::string field_name, int pad_digits_cycle, int pad_digits_rank, int cycle, - VisItDataCollection *&dc, ParGridFunction *& gf) + std::unique_ptr &dc, ParGridFunction *&gf) { - dc = new VisItDataCollection(MPI_COMM_WORLD, coll_name); + dc = std::make_unique(MPI_COMM_WORLD, coll_name); dc->SetPadDigitsCycle(pad_digits_cycle); dc->SetPadDigitsRank(pad_digits_rank); dc->Load(cycle); if (dc->Error() != DataCollection::No_Error) { - mfem::out << "Error loading VisIt data collection: " + mfem::err << "Error loading VisIt data collection: " << coll_name << endl; return 1; } - if (dc->GetMesh()->Dimension() < 3) - { - mfem::out << "Field must be defined on a three dimensional mesh" - << endl; - return 1; - } - if (dc->HasField(field_name)) { gf = dc->GetParField(field_name); @@ -502,70 +589,58 @@ int ReadGridFunction(const char * coll_name, const char * field_name, return 0; } -void SetInitialPosition(VisItDataCollection *E_dc, - VisItDataCollection *B_dc, - Vector &x_init) +void InitializeChargedParticles(ParticleSet &charged_particles, + const Vector &x_min, const Vector &x_max, const Vector &p_min, + const Vector &p_max, real_t m, real_t q) { - x_init.SetSize(3); x_init = 0.0; + int dim = charged_particles.Coords().GetVDim(); + int rank; + MPI_Comm_rank(charged_particles.GetComm(), &rank); + std::mt19937 gen(rank); - if (E_dc != NULL || B_dc != NULL) + // Set up uniform distribution for position + std::uniform_real_distribution real_dist_x(0_r,1_r); + + // Set up guassian distribution for momentum. Centered between p_min and + // p_max with 3-sigma range covering the box. + Vector p_center(dim); + add(0.5, p_min, p_max, p_center); + Vector dp = p_max; dp -= p_min; dp *= 1_r/6_r; // 3-sigma range + std::vector> norm_dist_p; + for (int d = 0; d < dim; d++) { - Vector E_p_min(3); E_p_min = -infinity(); - Vector E_p_max(3); E_p_max = infinity(); - if (E_dc != NULL) - { - ParMesh * E_pmesh = dynamic_cast(E_dc->GetMesh()); - E_pmesh->GetBoundingBox(E_p_min, E_p_max); - } + norm_dist_p.emplace_back(p_center[d], dp[d] > 0_r ? dp[d] : 1_r); + } - Vector B_p_min(3); B_p_min = -infinity(); - Vector B_p_max(3); B_p_max = infinity(); - if (B_dc != NULL) - { - ParMesh *B_pmesh = dynamic_cast(B_dc->GetMesh()); - B_pmesh->GetBoundingBox(B_p_min, B_p_max); - } + ParticleVector &X = charged_particles.Coords(); + ParticleVector &P = charged_particles.Field(Boris::MOM); + ParticleVector &M = charged_particles.Field(Boris::MASS); + ParticleVector &Q = charged_particles.Field(Boris::CHARGE); - for (int d = 0; d<3; d++) + for (int i = 0; i < charged_particles.GetNParticles(); i++) + { + for (int d = 0; d < dim; d++) { - const real_t p_min = std::max(E_p_min[d], B_p_min[d]); - const real_t p_max = std::min(E_p_max[d], B_p_max[d]); - x_init[d] = 0.5 * (p_min + p_max); + if (x_min[d] >= x_max[d]) { X(i,d) = x_min[d]; } + else + { + X(i,d) = x_min[d] + real_dist_x(gen)*(x_max[d] - x_min[d]); + } + + // Initialize momentum + if (p_min[d] >= p_max[d]) { P(i,d) = p_min[d]; } + else + { + real_t p_val = norm_dist_p[d](gen); + while (p_val < p_min[d] || p_val > p_max[d]) + { + p_val = norm_dist_p[d](gen); + } + P(i,d) = p_val; + } } + // Initialize mass + charge + M(i) = m; + Q(i) = q; } } - -Mesh MakeTrajectoryMesh(int step, real_t m, real_t dt, real_t r_factor, - const DenseMatrix &pos_data, - const DenseMatrix &mom_data) -{ - Mesh trajectory(2, 2 * (step + 1), step, 0, 3); - - for (int i=0; i<=step; i++) - { - trajectory.AddVertex(pos_data(0,i), pos_data(1,i), pos_data(2,i)); - - real_t dpx = (mom_data(0, i + 1) - mom_data(0, i)) / (m * dt); - real_t dpy = (mom_data(1, i + 1) - mom_data(1, i)) / (m * dt); - real_t dpz = (mom_data(2, i + 1) - mom_data(2, i)) / (m * dt); - - trajectory.AddVertex(pos_data(0,i) + r_factor * dpx, - pos_data(1,i) + r_factor * dpy, - pos_data(2,i) + r_factor * dpz); - } - - int v[4]; - for (int i=0; iGetOrdering(), - U().GetOrdering()); + finder.Interpolate(u_gf, U(), U().GetOrdering()); - finder.Interpolate(w_gf, W()); - Ordering::Reorder(W(), W().GetVDim(), w_gf.ParFESpace()->GetOrdering(), - W().GetOrdering()); + finder.Interpolate(w_gf, W(), W().GetOrdering()); } void NavierParticles::DeactivateLostParticles(bool findpts) { if (findpts) { - finder.FindPoints(X(), X().GetOrdering()); + finder.FindPoints(X()); } const Array lost_idxs = finder.GetPointsNotFoundIndices(); diff --git a/miniapps/nurbs/meshes/cube-nurbs-deformed.mesh b/miniapps/nurbs/meshes/cube-nurbs-deformed.mesh new file mode 100644 index 0000000000..8b4d271713 --- /dev/null +++ b/miniapps/nurbs/meshes/cube-nurbs-deformed.mesh @@ -0,0 +1,489 @@ +MFEM NURBS mesh v1.0 + +# +# MFEM Geometry Types (see fem/geom.hpp): +# +# SEGMENT = 1 +# SQUARE = 3 +# CUBE = 5 +# + +dimension +3 + +elements +1 +1 5 0 1 2 3 4 5 6 7 + +boundary +6 +1 3 2 1 0 3 +1 3 4 5 6 7 +1 3 0 1 5 4 +1 3 1 2 6 5 +1 3 2 3 7 6 +1 3 3 0 4 7 + +edges +12 +0 0 1 +0 3 2 +0 4 5 +0 7 6 +1 0 3 +1 1 2 +1 4 7 +1 5 6 +2 0 4 +2 1 5 +2 2 6 +2 3 7 + +vertices +8 + +knotvectors +3 +2 6 0 0 0 0.25 0.5 0.75 1 1 1 +2 6 0 0 0 0.25 0.5 0.75 1 1 1 +2 6 0 0 0 0.25 0.5 0.75 1 1 1 + +weights +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 + +FiniteElementSpace +FiniteElementCollection: NURBS2 +VDim: 3 +Ordering: 1 + +0.0116849 0.100677 0.107741 +0.700841 0.44147 0.344065 +0.437989 1.28285 0.264685 +-0.303721 0.806601 -0.11583 +-0.539413 0.0200674 0.908587 +0.395933 0.494981 1.39817 +0.0117652 1.43411 1.08871 +-0.759897 0.891295 0.785367 +0.0812991 0.127468 0.125781 +0.255008 0.202244 0.188147 +0.448525 0.285255 0.254138 +0.625506 0.391535 0.323138 +0.34086 1.23115 0.189627 +0.135768 1.14038 0.0949877 +-0.0571106 1.04065 -0.00204767 +-0.225607 0.894706 -0.0866114 +-0.3804 0.0431261 0.932761 +-0.121171 0.133223 1.01883 +0.0987705 0.257745 1.15354 +0.299995 0.410817 1.30949 +-0.0928372 1.36692 1.04319 +-0.292608 1.22409 0.954962 +-0.47 1.08105 0.868931 +-0.663376 0.956914 0.807106 +-0.0313904 0.173208 0.0241675 +-0.116453 0.335806 -0.0797544 +-0.197708 0.522984 -0.143019 +-0.268697 0.719567 -0.147642 +0.693218 0.561638 0.333951 +0.642943 0.782839 0.330385 +0.567332 1.00697 0.306227 +0.477659 1.20209 0.287077 +-0.550848 0.145891 0.873495 +-0.617164 0.379504 0.811408 +-0.684941 0.59523 0.782838 +-0.736288 0.802334 0.780498 +0.357892 0.575016 1.31896 +0.285779 0.784367 1.21896 +0.185794 1.01755 1.15046 +0.0653891 1.28559 1.09951 +-0.0537529 0.0832559 0.179502 +-0.188121 0.0603262 0.356393 +-0.323693 0.0343845 0.566552 +-0.463087 0.0213273 0.787315 +0.675777 0.435988 0.449458 +0.610746 0.444971 0.684641 +0.542159 0.473619 0.947334 +0.451733 0.481779 1.235 +0.387085 1.30441 0.330155 +0.300155 1.32346 0.502159 +0.197236 1.33735 0.733646 +0.0778968 1.38129 0.967056 +-0.364395 0.836177 -0.0380904 +-0.499556 0.880443 0.175051 +-0.618562 0.899155 0.419916 +-0.729908 0.894583 0.658954 +-0.191762 0.792322 -0.0838227 +-0.0127656 0.922096 0.0198956 +0.169871 1.04169 0.106131 +0.374421 1.15606 0.221327 +-0.107725 0.590479 -0.0701237 +0.0797398 0.719117 0.052235 +0.248024 0.837672 0.153949 +0.4506 0.951982 0.257914 +-0.014591 0.399524 -0.0133179 +0.169119 0.524685 0.0962864 +0.349821 0.64291 0.208715 +0.547914 0.743218 0.290306 +0.0485967 0.219259 0.0598748 +0.22429 0.311269 0.147327 +0.424681 0.420235 0.239975 +0.603974 0.511425 0.314819 +0.0215703 0.115709 0.220091 +0.202775 0.192498 0.292796 +0.404824 0.300707 0.373618 +0.587761 0.396426 0.424654 +-0.0813987 0.0906892 0.402963 +0.122699 0.181167 0.495311 +0.320206 0.283771 0.586703 +0.513593 0.396941 0.659549 +-0.192037 0.0641737 0.611853 +0.0238142 0.153445 0.708339 +0.217314 0.279993 0.795557 +0.427183 0.403976 0.895618 +-0.321216 0.0526643 0.821777 +-0.0668994 0.132414 0.92639 +0.136671 0.264444 1.03878 +0.346957 0.403474 1.16556 +0.652713 0.556232 0.447968 +0.597625 0.792616 0.438187 +0.516989 1.00686 0.408826 +0.435038 1.2089 0.363187 +0.570735 0.56432 0.677065 +0.500648 0.786806 0.651401 +0.416515 1.01355 0.598304 +0.325952 1.21023 0.541362 +0.493342 0.572126 0.917974 +0.405211 0.78473 0.869844 +0.312137 1.00883 0.809381 +0.239854 1.22498 0.760211 +0.407138 0.581025 1.17298 +0.318312 0.777059 1.09777 +0.225803 1.01555 1.0397 +0.121726 1.25049 0.981676 +0.290702 1.25013 0.278809 +0.0701636 1.14755 0.189221 +-0.119108 1.04886 0.103803 +-0.289182 0.91921 0.00804675 +0.185344 1.27637 0.482987 +-0.0241763 1.17861 0.405915 +-0.228914 1.06728 0.323432 +-0.408653 0.937665 0.225694 +0.0857194 1.30362 0.704401 +-0.122501 1.20271 0.644137 +-0.324492 1.07716 0.554526 +-0.523359 0.958708 0.456627 +-0.0299893 1.32937 0.937238 +-0.231056 1.22639 0.854007 +-0.4277 1.08064 0.77448 +-0.624632 0.962435 0.693615 +-0.335332 0.743245 -0.0361732 +-0.264988 0.556883 -0.0142987 +-0.179867 0.361039 0.028428 +-0.0895202 0.170043 0.122851 +-0.46712 0.787684 0.186278 +-0.399567 0.586191 0.200671 +-0.301046 0.368151 0.251277 +-0.218868 0.158789 0.317644 +-0.59138 0.804574 0.412544 +-0.506716 0.60204 0.430758 +-0.435083 0.376564 0.464743 +-0.356472 0.147414 0.523001 +-0.689157 0.806058 0.647632 +-0.629068 0.593243 0.655094 +-0.555738 0.371114 0.695893 +-0.485726 0.146879 0.754833 +-0.417228 0.17436 0.899793 +-0.169866 0.253389 0.990733 +0.028903 0.366525 1.11301 +0.252098 0.500703 1.24433 +-0.500923 0.414148 0.856523 +-0.284378 0.495056 0.948388 +-0.0843163 0.601653 1.05541 +0.155216 0.712225 1.16544 +-0.58149 0.643576 0.830741 +-0.373319 0.748859 0.924034 +-0.18065 0.845261 1.0198 +0.0460522 0.952923 1.10623 +-0.637619 0.848756 0.803888 +-0.448156 0.969832 0.881928 +-0.260587 1.09929 0.976527 +-0.0459431 1.21968 1.05279 +-0.0137524 0.209301 0.168941 +0.169242 0.299016 0.260497 +0.377537 0.400892 0.353247 +0.570084 0.506608 0.425317 +-0.080203 0.408135 0.0918812 +0.105504 0.518748 0.204877 +0.298886 0.634349 0.305087 +0.493708 0.735607 0.396409 +-0.168586 0.619614 0.0360283 +0.0165872 0.734724 0.158097 +0.205366 0.847514 0.254686 +0.405389 0.959363 0.360348 +-0.258677 0.823089 0.0161381 +-0.0786817 0.945242 0.11815 +0.121369 1.0512 0.208493 +0.324175 1.15154 0.303941 +-0.124414 0.198947 0.367495 +0.0754223 0.285305 0.478578 +0.271456 0.388643 0.569286 +0.47429 0.500746 0.649904 +-0.214578 0.415337 0.300568 +-0.0214509 0.508707 0.419948 +0.191156 0.607363 0.514446 +0.397619 0.729213 0.61556 +-0.302858 0.639951 0.251014 +-0.103789 0.739391 0.357589 +0.110001 0.848732 0.463479 +0.318473 0.955375 0.556868 +-0.385766 0.851741 0.222241 +-0.192006 0.963536 0.318999 +0.024575 1.07461 0.420919 +0.234314 1.16838 0.513436 +-0.239144 0.176376 0.576763 +-0.0256959 0.27234 0.678898 +0.180326 0.386088 0.77861 +0.380616 0.507244 0.864894 +-0.336051 0.4068 0.522487 +-0.134268 0.49507 0.619439 +0.0937046 0.598445 0.720534 +0.295642 0.716778 0.815371 +-0.413699 0.644524 0.471466 +-0.217717 0.723955 0.573321 +0.00422339 0.839902 0.669769 +0.207017 0.943147 0.772738 +-0.496887 0.853244 0.456225 +-0.291644 0.956378 0.557542 +-0.0924407 1.0788 0.643644 +0.1296 1.17577 0.725705 +-0.366417 0.168186 0.785706 +-0.121768 0.262554 0.893007 +0.083928 0.379718 1.0059 +0.297426 0.504658 1.11483 +-0.443726 0.407516 0.735498 +-0.240641 0.495966 0.83729 +-0.0238202 0.596216 0.939116 +0.203993 0.722308 1.04178 +-0.524688 0.636263 0.710265 +-0.331783 0.741903 0.804337 +-0.117384 0.835587 0.896008 +0.103953 0.951344 0.987382 +-0.599196 0.860865 0.697438 +-0.398317 0.964829 0.780767 +-0.195503 1.08864 0.858335 +0.0177754 1.19986 0.938708 diff --git a/miniapps/nurbs/meshes/square-nurbs-deformed.mesh b/miniapps/nurbs/meshes/square-nurbs-deformed.mesh new file mode 100644 index 0000000000..a9240e6b05 --- /dev/null +++ b/miniapps/nurbs/meshes/square-nurbs-deformed.mesh @@ -0,0 +1,118 @@ +MFEM NURBS mesh v1.0 + +# +# MFEM Geometry Types (see fem/geom.hpp): +# +# SEGMENT = 1 +# SQUARE = 3 +# CUBE = 5 +# + +dimension +2 + +elements +1 +1 3 0 1 2 3 + +boundary +4 +1 1 0 1 +2 1 2 3 +3 1 3 0 +4 1 1 2 + +edges +4 +0 0 1 +0 3 2 +1 0 3 +1 1 2 + +vertices +4 + +knotvectors +2 +2 6 0 0 0 0.25 0.5 0.75 1 1 1 +2 6 0 0 0 0.25 0.5 0.75 1 1 1 + +weights +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 + +FiniteElementSpace +FiniteElementCollection: NURBS2 +VDim: 2 +Ordering: 1 + +0.0163925 0.141238 +0.774637 0.626247 +-0.147699 1.3396 +-0.757759 0.550541 +0.121943 0.231571 +0.272418 0.394336 +0.420152 0.532036 +0.635666 0.624585 +-0.261202 1.30275 +-0.454309 1.14438 +-0.593397 0.942458 +-0.710473 0.706781 +-0.111803 0.190859 +-0.313132 0.306672 +-0.51706 0.436229 +-0.67826 0.509765 +0.608023 0.786507 +0.372822 1.01006 +0.159851 1.1653 +-0.0696727 1.29923 +-0.00322359 0.290759 +0.158563 0.459956 +0.321006 0.615434 +0.509901 0.715169 +-0.240232 0.408664 +-0.0626107 0.581669 +0.136422 0.738867 +0.308415 0.910906 +-0.452041 0.542364 +-0.263077 0.727566 +-0.0801052 0.906599 +0.0851199 1.07157 +-0.624784 0.659372 +-0.470927 0.866487 +-0.318204 1.05325 +-0.134756 1.23187 diff --git a/miniapps/nurbs/nurbs_ex1.cpp b/miniapps/nurbs/nurbs_ex1.cpp index 48aa4e95f4..e230001b3e 100644 --- a/miniapps/nurbs/nurbs_ex1.cpp +++ b/miniapps/nurbs/nurbs_ex1.cpp @@ -18,6 +18,10 @@ // nurbs_ex1 -m meshes/two-cubes-nurbs-rot.mesh -o 1 -r 3 -rf meshes/two-cubes.ref // nurbs_ex1 -m meshes/two-cubes-nurbs-autoedge.mesh -o 1 -r 3 -rf meshes/two-cubes.ref // nurbs_ex1 -m ../../data/segment-nurbs.mesh -r 2 -o 2 -lod 3 +// nurbs_ex1 -m meshes/square-nurbs-deformed.mesh -o 2 +// nurbs_ex1 -m meshes/square-nurbs-deformed.mesh -o 2 -no-ibp +// nurbs_ex1 -m meshes/cube-nurbs-deformed.mesh -o 2 +// nurbs_ex1 -m meshes/cube-nurbs-deformed.mesh -o 2 -no-ibp // // Description: This example code demonstrates the use of MFEM to define a // simple finite element discretization of the Poisson problem @@ -553,9 +557,18 @@ int main(int argc, char *argv[]) } // 14. Save data in the VisIt format - VisItDataCollection visit_dc("Example1", mesh); - visit_dc.RegisterField("solution", &x); - visit_dc.Save(); + if (ibp) + { + VisItDataCollection visit_dc("Example1", mesh); + visit_dc.RegisterField("solution", &x); + visit_dc.Save(); + } + else + { + VisItDataCollection visit_dc("Example1_nibp", mesh); + visit_dc.RegisterField("solution", &x); + visit_dc.Save(); + } // 15. Free the used memory. delete a; diff --git a/miniapps/nurbs/nurbs_ex10p.cpp b/miniapps/nurbs/nurbs_ex10p.cpp index 65614af5d3..838f2b0f3f 100644 --- a/miniapps/nurbs/nurbs_ex10p.cpp +++ b/miniapps/nurbs/nurbs_ex10p.cpp @@ -264,7 +264,7 @@ int main(int argc, char *argv[]) // parallel degrees of freedom, with offsets given by array true_offset. FiniteElementCollection *fec = nullptr; NURBSExtension *NURBSext = nullptr; - if (mesh->NURBSext) + if (pmesh->NURBSext) { NURBSext = new NURBSExtension(pmesh->NURBSext, order); fec = new NURBSFECollection(order); diff --git a/miniapps/plasma/CMakeLists.txt b/miniapps/plasma/CMakeLists.txt new file mode 100644 index 0000000000..7e4221c55f --- /dev/null +++ b/miniapps/plasma/CMakeLists.txt @@ -0,0 +1,25 @@ +# Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced +# at the Lawrence Livermore National Laboratory. All Rights reserved. See files +# LICENSE and NOTICE for details. LLNL-CODE-806117. +# +# This file is part of the MFEM library. For more information and source code +# availability visit https://mfem.org. +# +# MFEM is free software; you can redistribute it and/or modify it under the +# terms of the BSD-3 license. We welcome feedback and contributions, see file +# CONTRIBUTING.md for details. + +if (MFEM_USE_MPI) + list(APPEND PLASMA_COMMON_SOURCES) + + list(APPEND PLASMA_COMMON_HEADERS + plasma.hpp) + + convert_filenames_to_full_paths(PLASMA_COMMON_SOURCES) + convert_filenames_to_full_paths(PLASMA_COMMON_HEADERS) + + set(PLASMA_COMMON_FILES + EXTRA_SOURCES ${PLASMA_COMMON_SOURCES} + EXTRA_HEADERS ${PLASMA_COMMON_HEADERS}) + +endif() diff --git a/miniapps/plasma/makefile b/miniapps/plasma/makefile new file mode 100644 index 0000000000..637e14d6e4 --- /dev/null +++ b/miniapps/plasma/makefile @@ -0,0 +1,85 @@ +# Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced +# at the Lawrence Livermore National Laboratory. All Rights reserved. See files +# LICENSE and NOTICE for details. LLNL-CODE-806117. +# +# This file is part of the MFEM library. For more information and source code +# availability visit https://mfem.org. +# +# MFEM is free software; you can redistribute it and/or modify it under the +# terms of the BSD-3 license. We welcome feedback and contributions, see file +# CONTRIBUTING.md for details. + +# Use the MFEM build directory +MFEM_DIR ?= ../.. +MFEM_BUILD_DIR ?= ../.. +SRC = $(if $(MFEM_DIR:../..=),$(MFEM_DIR)/miniapps/plasma/,) +CONFIG_MK = $(MFEM_BUILD_DIR)/config/config.mk +# Use the MFEM install directory +# MFEM_INSTALL_DIR = ../../mfem +# CONFIG_MK = $(MFEM_INSTALL_DIR)/share/mfem/config.mk + +MFEM_LIB_FILE = mfem_is_not_built +-include $(CONFIG_MK) + +SEQ_MINIAPPS = +PAR_MINIAPPS = +ifeq ($(MFEM_USE_MPI),NO) + MINIAPPS = $(SEQ_MINIAPPS) +else + MINIAPPS = $(PAR_MINIAPPS) $(SEQ_MINIAPPS) +endif + +.SUFFIXES: +.SUFFIXES: .o .cpp .mk +.PHONY: all lib-common clean clean-build clean-exec +.PRECIOUS: %.o + +COMMON_LIB = -L$(MFEM_BUILD_DIR)/miniapps/common -lmfem-common + +# If MFEM_SHARED is set, add the ../common rpath +COMMON_LIB += $(if $(MFEM_SHARED:YES=),,\ + $(if $(MFEM_USE_CUDA:YES=),$(CXX_XLINKER),$(CUDA_XLINKER))-rpath,$(abspath\ + $(MFEM_BUILD_DIR)/miniapps/common)) + +COMMON_O= + +# Remove built-in rules +%: %.cpp +%.o: %.cpp + +all: $(MINIAPPS) + +# Rules for building the miniapps +%: $(SRC)%.cpp $(COMMON_O) $(MFEM_LIB_FILE) $(CONFIG_MK) | lib-common + $(MFEM_CXX) $(MFEM_LINK_FLAGS) $< -o $@ $(COMMON_O) $(COMMON_LIB) \ + $(MFEM_LIBS) + +# Rules for compiling miniapp dependencies +$(COMMON_O) $(addsuffix _solver.o,$(MINIAPPS)): \ +%.o: $(SRC)%.cpp $(SRC)%.hpp $(CONFIG_MK) + $(MFEM_CXX) $(MFEM_FLAGS) -c $(<) -o $(@) + +# Rule for building lib-common +lib-common: + $(MAKE) -C $(MFEM_BUILD_DIR)/miniapps/common + +MFEM_TESTS = MINIAPPS +include $(MFEM_TEST_MK) + +# Testing: Specific execution options +RUN_MPI = $(MFEM_MPIEXEC) $(MFEM_MPIEXEC_NP) $(MFEM_MPI_NP) + +# Testing: "test" target and mfem-test* variables are defined in config/test.mk + +# Generate an error message if the MFEM library is not built and exit +$(MFEM_LIB_FILE): + $(error The MFEM library is not built) + +clean: clean-build clean-exec + +clean-build: + rm -f *.o *~ $(SEQ_MINIAPPS) $(PAR_MINIAPPS) + rm -rf *.dSYM *.TVD.*breakpoints + +clean-exec: + diff --git a/miniapps/plasma/plasma.hpp b/miniapps/plasma/plasma.hpp new file mode 100644 index 0000000000..fbae71ae47 --- /dev/null +++ b/miniapps/plasma/plasma.hpp @@ -0,0 +1,62 @@ +// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced +// at the Lawrence Livermore National Laboratory. All Rights reserved. See files +// LICENSE and NOTICE for details. LLNL-CODE-806117. +// +// This file is part of the MFEM library. For more information and source code +// availability visit https://mfem.org. +// +// MFEM is free software; you can redistribute it and/or modify it under the +// terms of the BSD-3 license. We welcome feedback and contributions, see file +// CONTRIBUTING.md for details. + +#ifndef MFEM_PLASMA_HPP +#define MFEM_PLASMA_HPP + +#include +#include + +namespace mfem +{ + +namespace plasma +{ + +// Physical Constants + +// Permittivity of Free Space (units F/m) +static const real_t epsilon0_ = 8.8541878176e-12; + +// Permeability of Free Space (units H/m) +static const real_t mu0_ = 4.0e-7 * M_PI; + +// Speed of light in Free Space (units m/s) +static const real_t c0_ = 1.0 / sqrt(epsilon0_ * mu0_); + +// Impedance of Free Space (units Ohm) +static const real_t Z0_ = sqrt(mu0_ / epsilon0_); + +static const real_t q_ = 1.602176634e-19; // Elementary charge in coulombs +static const real_t eV_ = 1.602176634e-19; // 1 eV in Joules +static const real_t amu_ = 1.660539040e-27; // Atomic mass unit in kilograms +static const real_t me_kg_ = 9.10938356e-31; // Mass of electron in kilograms +static const real_t me_u_ = 5.4857990907e-4; // Mass of electron in a.m.u + +/** + Returns the cyclotron frequency in radians/second + m is the mass in a.m.u + q is the charge in units of elementary electric charge + B is the magnetic field magnitude in tesla + */ +inline real_t cyclotronFrequency(real_t B, real_t m, real_t q) +{ + return fabs(q * q_ * B / (m * amu_)); +} + +typedef std::complex complex_t; + +} // namespace plasma + +} // namespace mfem + +#endif // MFEM_PLASMA_HPP + diff --git a/miniapps/tools/CMakeLists.txt b/miniapps/tools/CMakeLists.txt index e2b7231c2f..3d8c8ca803 100644 --- a/miniapps/tools/CMakeLists.txt +++ b/miniapps/tools/CMakeLists.txt @@ -43,6 +43,9 @@ add_mfem_miniapp(convert-dc add_mfem_miniapp(lor-transfer MAIN lor-transfer.cpp LIBRARIES mfem) +add_mfem_miniapp(compare-dc + MAIN compare-dc.cpp LIBRARIES mfem) + add_mfem_miniapp(tmop-check-metric MAIN tmop-check-metric.cpp LIBRARIES mfem) diff --git a/miniapps/tools/compare-dc.cpp b/miniapps/tools/compare-dc.cpp new file mode 100644 index 0000000000..1b1a056d16 --- /dev/null +++ b/miniapps/tools/compare-dc.cpp @@ -0,0 +1,166 @@ +// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced +// at the Lawrence Livermore National Laboratory. All Rights reserved. See files +// LICENSE and NOTICE for details. LLNL-CODE-806117. +// +// This file is part of the MFEM library. For more information and source code +// availability visit https://mfem.org. +// +// MFEM is free software; you can redistribute it and/or modify it under the +// terms of the BSD-3 license. We welcome feedback and contributions, see file +// CONTRIBUTING.md for details. +// +// ------------------------------------------------------------------- +// Compare DC Miniapp: Compare fields saved via DataCollection classes +// ------------------------------------------------------------------- +// +// This miniapp loads previously saved data and computes the l2 norm of the +// difference. Currently, only the VisItDataCollection class is supported. +// +// Compile with: make compare-dc +// +// Serial sample runs: +// > compare-dc -r0 ../../examples/Example5 -r1 ../../examples/alt/Example5 +// > compare-dc -r0 Example5 -r1 alt/Example5 -tol 1e-6 +// +// Parallel sample runs: +// > mpirun -np 4 compare-dc -r0 ../../examples/Example5-Parallel +// -r1 ../../examples/alt/Example5-Parallel +// +// NB: when no tolerance is provided the difference is simple reported. +// If a tolerance is provided this is compared with the symmetric +// relative difference. An error is given if difference exceeds the tolerance. + +#include "mfem.hpp" + +using namespace std; +using namespace mfem; + +int main(int argc, char *argv[]) +{ +#ifdef MFEM_USE_MPI + Mpi::Init(); + if (!Mpi::Root()) { mfem::out.Disable(); mfem::err.Disable(); } + Hypre::Init(); +#endif + + // Parse command-line options. + const char *coll_name0 = NULL; + const char *coll_name1 = NULL; + int cycle = 0; + int pad_digits_cycle = 6; + int pad_digits_rank = 6; + real_t tol = -1; + + OptionsParser args(argc, argv); + args.AddOption(&coll_name0, "-r0", "--root-file_0", + "Set the VisIt data collection root file prefix.", true); + args.AddOption(&coll_name1, "-r1", "--root-file_1", + "Set the VisIt data collection root file prefix.", true); + args.AddOption(&cycle, "-c", "--cycle", "Set the cycle index to read."); + args.AddOption(&pad_digits_cycle, "-pdc", "--pad-digits-cycle", + "Number of digits in cycle."); + args.AddOption(&pad_digits_rank, "-pdr", "--pad-digits-rank", + "Number of digits in MPI rank."); + args.AddOption(&tol, "-tol", "--tolerance", + "Tolerance for checking the results."); + args.Parse(); + if (!args.Good()) + { + args.PrintUsage(mfem::out); + return 1; + } + args.PrintOptions(mfem::out); + +#ifdef MFEM_USE_MPI + VisItDataCollection dc0(MPI_COMM_WORLD, coll_name0); +#else + VisItDataCollection dc0(coll_name0); +#endif + dc0.SetPadDigitsCycle(pad_digits_cycle); + dc0.SetPadDigitsRank(pad_digits_rank); + dc0.Load(cycle); + + if (dc0.Error() != DataCollection::No_Error) + { + mfem::out << "Error loading VisIt data collection: " << coll_name0 << endl; + return 1; + } + +#ifdef MFEM_USE_MPI + VisItDataCollection dc1(MPI_COMM_WORLD, coll_name1); +#else + VisItDataCollection dc1(coll_name1); +#endif + dc1.SetPadDigitsCycle(pad_digits_cycle); + dc1.SetPadDigitsRank(pad_digits_rank); + dc1.Load(cycle); + + if (dc1.Error() != DataCollection::No_Error) + { + mfem::out << "Error loading VisIt data collection: " << coll_name1 << endl; + return 1; + } + + typedef DataCollection::FieldMapType fields_t; + const fields_t &fields0 = dc0.GetFieldMap(); + // Print the names of all fields. + bool error = false; + for (fields_t::const_iterator it0 = fields0.begin(); + it0 != fields0.end() ; ++it0) + { + GridFunction *gf0 = dc0.GetField(it0->first); + if (!gf0) + { + mfem::out << "Error loading:"<first<< endl; + mfem::out << "From data collection: " << coll_name0 << endl; + return 1; + } + + GridFunction *gf1 = dc1.GetField(it0->first); + if (!gf1) + { + mfem::out << "Error loading:"<first<< endl; + mfem::out << "From data collection: " << coll_name1 << endl; + return 1; + } + if (gf0->Size() != gf1->Size()) + { + mfem::out << "Size error for:"<first<< endl; + mfem::out << "In data collection: " << coll_name0 + <<" size is "<Size()<< endl; + mfem::out << "In data collection: " << coll_name1 + <<" size is "<Size()<< endl; + return 1; + } + + // Norm of vectors + real_t nrm0 = gf0->Norml2(); + real_t nrm1 = gf1->Norml2(); + + // Difference + (*gf0) -= (*gf1); + real_t nrmd = gf0->Norml2(); + real_t rel_sym = 2*nrmd/(nrm0 + nrm1); + if (gf0->Norml2() > rel_sym) { error = true; } + + // Report + mfem::out <<"==========================================="<first<<"_0| = "<first<<"_1| = "<first<<"_0 - "<first<<"_1| = "<first<<"_0 - "<first<<"_1|"<first.length()) + <<" = "<first<<"_0| + |"<first<<"_1|)\n"< 0.0) + { + mfem::out << "Data collections: " << coll_name0 + << " & " << coll_name1 << " are outside of the tolerance!\n"; + return -1; + } + return 0; +} diff --git a/miniapps/tools/gridfunction-bounds.cpp b/miniapps/tools/gridfunction-bounds.cpp index 8f0a18f44c..94a1910f9f 100644 --- a/miniapps/tools/gridfunction-bounds.cpp +++ b/miniapps/tools/gridfunction-bounds.cpp @@ -23,6 +23,8 @@ // (2) Dzanic et al., "A method for bounding high-order finite element // functions: Applications to mesh validity and bounds-preserving limiters". // +// We also use a recursive subdivision strategy to compute tighter estimate of +// the function extremum. // // Compile with: make gridfunction-bounds // @@ -31,9 +33,6 @@ // mpirun -np 4 gridfunction-bounds -nb 100 -ref 5 -bt 2 -l2 #include "mfem.hpp" -#include -#include -#include using namespace mfem; using namespace std; @@ -56,6 +55,8 @@ int main (int argc, char *argv[]) int b_type = -1; bool continuous = true; int nbrute = 0; + int rec_depth = 4; + real_t rel_tol = 1e-4; // Parse command-line options. OptionsParser args(argc, argv); @@ -83,6 +84,12 @@ int main (int argc, char *argv[]) args.AddOption(&nbrute, "-nb", "--nbrute", "Brute force search for minimum in an array of nxnxn points " "in each element."); + args.AddOption(&rec_depth, "-rd", "--rec-depth", + "Maximum depth for recursive subdivision to compute function " + "extremum."); + args.AddOption(&rel_tol, "-rt", "--rel-tol", + "Relative tolerance for termination of recursive " + "subdivision."); args.ParseCheck(); Mesh mesh(mesh_file, 1, 1, false); @@ -151,7 +158,19 @@ int main (int argc, char *argv[]) ParGridFunction lowerb(&fes_pc), upperb(&fes_pc); // Compute bounds - pfunc_proj->GetElementBounds(lowerb, upperb, ref); + PLBound plb = pfunc_proj->GetElementBounds(lowerb, upperb, ref); + + // Compute minimum and maximum bounds via recursion + Vector bound_rec_min(vdim), bound_rec_max(vdim); + for (int d = 0; d < vdim; d++) + { + auto min_interval = pfunc_proj->EstimateFunctionMinimum(d, plb, rec_depth, + rel_tol); + auto max_interval = pfunc_proj->EstimateFunctionMaximum(d, plb, rec_depth, + rel_tol); + bound_rec_min(d) = min_interval.first; + bound_rec_max(d) = max_interval.second; + } Vector bound_min(vdim), bound_max(vdim); for (int d = 0; d < vdim; d++) @@ -236,17 +255,31 @@ int main (int argc, char *argv[]) { for (int d = 0; d < vdim; d++) { - cout << "Brute force and bounding comparison for component " << + cout << "Compare function extremum for component " << d << endl; - cout << "Brute force minimum and minimum bound: " << global_min(d) - << " " << bound_min(d) << endl; - - cout << "Brute force maximum and maximum bound: " << global_max(d) - << " " << bound_max(d) << endl; - - cout << "The difference in bounds is: " << - global_min(d)-bound_min(d) << " " << - bound_max(d)-global_max(d) << endl; + constexpr int w = 20; + cout << left << setw(w) << " " + << setw(w) << "Brute force" + << setw(w) << "PL Bound" + << setw(w) << "PL Bound + recursion" << endl + << left << setw(w) << "Minimum: " + << setw(w) << global_min(d) + << setw(w) << bound_min(d) + << setw(w) << bound_rec_min(d) << endl + << left << setw(w) << "Difference: " + << setw(w) << "-" + << setw(w) << global_min(d)-bound_min(d) + << setw(w) << global_min(d)-bound_rec_min(d) << endl; + cout << endl + << left << setw(w) << "Maximum: " + << setw(w) << global_max(d) + << setw(w) << bound_max(d) + << setw(w) << bound_rec_max(d) << endl + << left << setw(w) << "Difference: " + << setw(w) << "-" + << setw(w) << bound_max(d)-global_max(d) + << setw(w) << bound_rec_max(d)-global_max(d) << endl; + cout << endl; } } } @@ -255,10 +288,19 @@ int main (int argc, char *argv[]) { for (int d = 0; d < vdim; d++) { - cout << "Minimum bound for component " << d << " is " << - bound_min(d) << endl; - cout << "Maximum bound for component " << d << " is " << - bound_max(d) << endl; + cout << "Compare function extremum for component " << + d << endl; + constexpr int w = 20; + cout << left << setw(w) << " " + << setw(w) << "PL Bound" + << setw(w) << "PL Bound + recursion" << endl + << left << setw(w) << "Minimum: " + << setw(w) << bound_min(d) + << setw(w) << bound_rec_min(d) << endl; + cout << endl + << left << setw(w) << "Maximum: " + << setw(w) << bound_max(d) + << setw(w) << bound_rec_max(d) << endl; } } diff --git a/miniapps/tools/makefile b/miniapps/tools/makefile index f367fd803c..795be18a28 100644 --- a/miniapps/tools/makefile +++ b/miniapps/tools/makefile @@ -21,7 +21,7 @@ MFEM_LIB_FILE = mfem_is_not_built -include $(CONFIG_MK) SEQ_MINIAPPS = display-basis load-dc convert-dc get-values lor-transfer \ - tmop-check-metric tmop-metric-magnitude + tmop-check-metric tmop-metric-magnitude compare-dc PAR_MINIAPPS = nodal-transfer plor-transfer gridfunction-bounds @@ -78,7 +78,8 @@ RUN_MPI = $(MFEM_MPIEXEC) $(MFEM_MPIEXEC_NP) $(MFEM_MPI_NP) # Testing: Specific execution options # Do not test: display-basis, load-dc, convert-dc, get-values, lor-transfer, plor-transfer NO_TEST_APPS = display-basis load-dc convert-dc get-values lor-transfer \ - plor-transfer tmop-check-metric tmop-metric-magnitude gridfunction-bounds + plor-transfer tmop-check-metric tmop-metric-magnitude gridfunction-bounds \ + compare-dc $(foreach app,$(NO_TEST_APPS),$(app)-test-seq $(app)-test-par): @true diff --git a/tests/benchmarks/CMakeLists.txt b/tests/benchmarks/CMakeLists.txt index d08124ff17..661694cc34 100644 --- a/tests/benchmarks/CMakeLists.txt +++ b/tests/benchmarks/CMakeLists.txt @@ -31,11 +31,6 @@ function(add_benchmark name) set_property(SOURCE ${${NAME}_BENCH_SRCS} PROPERTY LANGUAGE CUDA) endif(MFEM_USE_CUDA) - if (MFEM_USE_HIP) - set_property(SOURCE ${${NAME}_BENCH_SRCS} PROPERTY LANGUAGE - HIP_SOURCE_PROPERTY_FORMAT TRUE) - endif(MFEM_USE_HIP) - add_executable(bench_${name} ${${NAME}_BENCH_SRCS}) target_link_libraries(bench_${name} mfem pthread) add_dependencies(${MFEM_ALL_BENCHMARKS_TARGET_NAME} bench_${name}) diff --git a/tests/benchmarks/bench_ceed.cpp b/tests/benchmarks/bench_ceed.cpp index 434254af7e..71ca61cb22 100644 --- a/tests/benchmarks/bench_ceed.cpp +++ b/tests/benchmarks/bench_ceed.cpp @@ -8,23 +8,89 @@ // MFEM is free software; you can redistribute it and/or modify it under the // terms of the BSD-3 license. We welcome feedback and contributions, see file // CONTRIBUTING.md for details. +// +// +// This benchmark contains the implementation of the CEED's bake-off problems: +// high-order kernels/benchmarks designed to test and compare the performance +// of high-order codes. +// +// See: https://ceed.exascaleproject.org/bps -#include "bench.hpp" +#include "bench.hpp" // IWYU pragma: keep #ifdef MFEM_USE_BENCHMARK -/* - This benchmark contains the implementation of the CEED's bake-off problems: - high-order kernels/benchmarks designed to test and compare the performance - of high-order codes. +#include +#include - See: ceed.exascaleproject.org/bps and github.com/CEED/benchmarks -*/ -template +#include "fem/qinterp/det.hpp" // IWYU pragma: keep +#include "fem/qinterp/grad.hpp" // IWYU pragma: keep +#include "fem/integ/lininteg_domain_kernels.hpp" // IWYU pragma: keep +#include "fem/integ/bilininteg_vecdiffusion_pa.hpp" // IWYU pragma: keep + +// Custom benchmark arguments generator +static void CustomArguments(bmi::Benchmark *b) noexcept +{ + constexpr int MAX_NDOFS = 16 * 1024 * (mfem_use_gpu ? 1024 : 8); + + const auto orders = { 7, 6, 5, 4, 3, 2, 1 }; + + constexpr auto ndofs = [](int n) constexpr noexcept -> int + { + return (n + 1) * (n + 1) * (n + 1); + }; + + constexpr auto inc = [](int n) constexpr noexcept -> int + { + return n < 160 ? 4 : n < 240 ? 8 : n < 320 ? 16 : 32; + }; + + for (auto p : orders) + { + for (int n = 16; ndofs(n) <= MAX_NDOFS; n += inc(n)) + { + b->Args({p, n}); + } + } +} + +// Register kernel specializations used in the benchmarks +static void AddKernelSpecializations() +{ + using DET = QuadratureInterpolator::DetKernels; + DET::Specialization<3, 3, 2, 2>::Add(); + DET::Specialization<3, 3, 2, 3>::Add(); + DET::Specialization<3, 3, 2, 5>::Add(); + DET::Specialization<3, 3, 2, 6>::Add(); + DET::Specialization<3, 3, 5, 5>::Add(); + // Others might exceed memory limits + + using GRAD = QuadratureInterpolator::GradKernels; + GRAD::Specialization<3, QVectorLayout::byNODES, false, 3, 2, 2>::Add(); + GRAD::Specialization<3, QVectorLayout::byNODES, false, 3, 2, 7>::Add(); + GRAD::Specialization<3, QVectorLayout::byNODES, false, 3, 2, 8>::Add(); + GRAD::Specialization<3, QVectorLayout::byNODES, false, 3, 2, 9>::Add(); + + using LIN = DomainLFIntegrator::AssembleKernels; + LIN::Specialization<3, 7, 7>::Add(); + LIN::Specialization<3, 6, 6>::Add(); + LIN::Specialization<3, 8, 8>::Add(); + + using VDIFF = VectorDiffusionIntegrator::ApplyPAKernels; + VDIFF::Specialization<3, 3, 3, 3>::Add(); + VDIFF::Specialization<3, 3, 4, 4>::Add(); + VDIFF::Specialization<3, 3, 5, 5>::Add(); + VDIFF::Specialization<3, 3, 6, 6>::Add(); + VDIFF::Specialization<3, 3, 7, 7>::Add(); + VDIFF::Specialization<3, 3, 8, 8>::Add(); +} + +// Bake-off base class +template struct BakeOff { - static constexpr int DIM = 3; - const int N, p, q; + inline static constexpr int DIM = 3; + const int p, c, q, n, nx, ny, nz; Mesh mesh; H1_FECollection fec; FiniteElementSpace fes; @@ -38,12 +104,15 @@ struct BakeOff GridFunction x, y; BilinearForm a; double mdofs{}; + BilinearFormIntegrator *bfi; - BakeOff(int p): - N(Device::IsEnabled() ? 32 : 4), - p(p), - q(2 * p + (GLL ? -1 : 3)), - mesh(Mesh::MakeCartesian3D(N, N, N, Element::HEXAHEDRON)), + BakeOff(int p, int side): + p(p), c(side), q(2 * p + (GLL ? -1 : 3)), + n((assert(c >= p), c / p)), + nx(n + (p * (n + 1) * p * n * p * n < c * c * c ? 1 : 0)), + ny(n + (p * (n + 1) * p * (n + 1) * p * n < c * c * c ? 1 : 0)), + nz(n), + mesh(Mesh::MakeCartesian3D(nx, ny, nz, Element::HEXAHEDRON)), fec(p, DIM, BasisType::GaussLobatto), fes(&mesh, &fec, VDIM, VDIM == 3 ? Ordering::byVDIM : Ordering::byNODES), geom_type(mesh.GetTypicalElementGeometry()), @@ -58,22 +127,41 @@ struct BakeOff a(&fes) { x = 0.0; + if constexpr (BFI == 1) + { + bfi = new MassIntegrator(one, ir); + } + else if constexpr (BFI == 2) + { + bfi = new VectorMassIntegrator(one, ir); + } + else if constexpr (BFI == 3 || BFI == 5) + { + bfi = new DiffusionIntegrator(one, ir); + } + else if constexpr (BFI == 4 || BFI == 6) + { + bfi = new VectorDiffusionIntegrator(one, ir); + } + else + { + static_assert(BFI >= 1 && BFI <= 6, "Invalid BilinearFormIntegrator"); + } + a.AddDomainIntegrator(bfi); } virtual void benchmark() = 0; - double SumMdofs() const { return mdofs; } + [[nodiscard]] double SumMdofs() const noexcept { return mdofs; } - double MDofs() const { return 1e-6 * dofs; } + [[nodiscard]] double MDofs() const noexcept { return 1e-6 * dofs; } }; -/// Bake-off Problems (BPs) -template -struct Problem : public BakeOff +// Bake-off Problems (BPs) +template +struct BP : public BakeOff { - const double rtol = 1e-12; - const int max_it = 32; - const int print_lvl = -1; + const int max_it = 32, print_lvl = -1; Array ess_tdof_list; Array ess_bdr; @@ -82,44 +170,56 @@ struct Problem : public BakeOff Vector B, X; CGSolver cg; - using BakeOff::a; - using BakeOff::ir; - using BakeOff::one; - using BakeOff::mesh; - using BakeOff::fes; - using BakeOff::x; - using BakeOff::y; - using BakeOff::mdofs; + using base = BakeOff; + using base::a; + using base::ir; + using base::one; + using base::mesh; + using base::fes; + using base::x; + using base::y; + using base::mdofs; + using base::unit_vec; + using base::bfi; - Problem(int order): - BakeOff(order), + BP(int p, int side) noexcept: base(p, side), ess_bdr(mesh.bdr_attributes.Max()), b(&fes) { ess_bdr = 1; fes.GetEssentialTrueDofs(ess_bdr, ess_tdof_list); - if (VDIM == 1) + + if constexpr (VDIM == 1) { - b.AddDomainIntegrator(new DomainLFIntegrator(this->one)); + b.AddDomainIntegrator(new DomainLFIntegrator(one)); } else { - b.AddDomainIntegrator(new VectorDomainLFIntegrator(this->unit_vec)); + b.AddDomainIntegrator(new VectorDomainLFIntegrator(unit_vec)); } b.UseFastAssembly(true); b.Assemble(); a.SetAssemblyLevel(AssemblyLevel::PARTIAL); - a.AddDomainIntegrator(new BFI(one, ir)); a.Assemble(); a.FormLinearSystem(ess_tdof_list, x, b, A, X, B); - cg.SetRelTol(rtol); cg.SetOperator(*A); + cg.SetAbsTol(0.0); + cg.iterative_mode = false; + { + cg.SetPrintLevel(-1); + cg.SetMaxIter(1000); + cg.SetRelTol(1e-8); + cg.Mult(B, X); + MFEM_VERIFY(cg.GetConverged(), "CG solver did not converge!"); + } + cg.SetRelTol(0.0); cg.SetMaxIter(max_it); cg.SetPrintLevel(print_lvl); - cg.iterative_mode = false; - MFEM_DEVICE_SYNC; + + benchmark(); + mdofs = 0.0; } void benchmark() override @@ -130,104 +230,115 @@ struct Problem : public BakeOff } }; -/// Bake-off Problems (BPs) -#define BakeOff_Problem(i, Kernel, VDIM, p_eq_q) \ - static void BP##i(bm::State &state) \ - { \ - Problem ker(state.range(0)); \ - while (state.KeepRunning()) { ker.benchmark(); } \ - state.counters["MDof/s"] = \ - bm::Counter(ker.SumMdofs(), bm::Counter::kIsRate); \ - } \ - BENCHMARK(BP##i)->DenseRange(1, 6)->Unit(bm::kMillisecond); - -/// BP1: scalar PCG with mass matrix, q=p+2 -BakeOff_Problem(1, Mass, 1, false) - -/// BP2: vector PCG with mass matrix, q=p+2 -BakeOff_Problem(2, VectorMass, 3, false) - -/// BP3: scalar PCG with stiffness matrix, q=p+2 -BakeOff_Problem(3, Diffusion, 1, false) - -/// BP4: vector PCG with stiffness matrix, q=p+2 -BakeOff_Problem(4, VectorDiffusion, 3, false) - -/// BP5: scalar PCG with stiffness matrix, q=p+1 -BakeOff_Problem(5, Diffusion, 1, true) - -/// BP6: vector PCG with stiffness matrix, q=p+1 -BakeOff_Problem(6, VectorDiffusion, 3, true) - -/// Bake-off Kernels (BKs) -template -struct Kernel : public BakeOff +// Bake-off Kernels (BKs) +template +struct BK : public BakeOff { - using BakeOff::a; - using BakeOff::ir; - using BakeOff::one; - using BakeOff::fes; - using BakeOff::x; - using BakeOff::y; - using BakeOff::mdofs; + Vector xe, ye; - Kernel(int order): BakeOff(order) + using base = BakeOff; + using base::ir; + using base::one; + using base::bfi; + using base::fes; + using base::mdofs; + + BK(int order, int side) noexcept: base(order, side) { - x.Randomize(1); - a.SetAssemblyLevel(AssemblyLevel::PARTIAL); - a.AddDomainIntegrator(new BFI(one, ir)); - a.Assemble(); - a.Mult(x, y); - MFEM_DEVICE_SYNC; + bfi->AssemblePA(fes); + + const Table &el2dof = fes.GetElementToDofTable(); + const int e_size = el2dof.Size_of_connections()*fes.GetVDim(); + const auto R = fes.GetElementRestriction(ElementDofOrdering::LEXICOGRAPHIC); + MFEM_VERIFY(e_size == R->Height(), "Input/Output E-vector size mismatch!"); + + xe.SetSize(R->Height()); + ye.SetSize(R->Height()); + xe.UseDevice(true); + ye.UseDevice(true); + + xe.Randomize(1); + xe.Read(); + ye = 0.0; + + benchmark(); + mdofs = 0.0; } void benchmark() override { - a.Mult(x, y); + bfi->AddMultPA(xe, ye); MFEM_DEVICE_SYNC; mdofs += this->MDofs(); } }; -/// Generic CEED BKi -#define BakeOff_Kernel(i, KER, VDIM, GLL) \ - static void BK##i(bm::State &state) \ - { \ - Kernel ker(state.range(0)); \ - while (state.KeepRunning()) { ker.benchmark(); } \ - state.counters["MDof/s"] = \ - bm::Counter(ker.SumMdofs(), bm::Counter::kIsRate); \ - } \ - BENCHMARK(BK##i)->DenseRange(1, 6)->Unit(bm::kMillisecond); +// Benchmarks +template +static void Benchmark(bm::State& state) noexcept +{ + T run(state.range(0), state.range(1)); + while (state.KeepRunning()) { run.benchmark(); } + state.counters["Dofs"] = bm::Counter(run.dofs); + state.counters["MDof/s"] = bm::Counter(run.SumMdofs(), bm::Counter::kIsRate); + state.counters["Order"] = bm::Counter(state.range(0)); +} -/// BK1: scalar E-vector-to-E-vector evaluation of mass matrix, q=p+2 -BakeOff_Kernel(1, Mass, 1, false) +#define REGISTER(PK, BFI, VDIM, GLL) \ + BENCHMARK_TEMPLATE(Benchmark, PK) \ + ->Name(#PK #BFI)->Apply(CustomArguments)->Unit(bm::kMillisecond) -/// BK2: vector E-vector-to-E-vector evaluation of mass matrix, q=p+2 -BakeOff_Kernel(2, VectorMass, 3, false) +// BP1: scalar PCG with mass matrix, q=p+2 +REGISTER(BP, 1, 1, false); -/// BK3: scalar E-vector-to-E-vector evaluation of stiffness matrix, q=p+2 -BakeOff_Kernel(3, Diffusion, 1, false) +// BP2: vector PCG with mass matrix, q=p+2 +REGISTER(BP, 2, 3, false); -/// BK4: vector E-vector-to-E-vector evaluation of stiffness matrix, q=p+2 -BakeOff_Kernel(4, VectorDiffusion, 3, false) +// BP3: scalar PCG with stiffness matrix, q=p+2 +REGISTER(BP, 3, 1, false); -/// BK5: scalar E-vector-to-E-vector evaluation of stiffness matrix, q=p+1 -BakeOff_Kernel(5, Diffusion, 1, true) +// BP4: vector PCG with stiffness matrix, q=p+2 +REGISTER(BP, 4, 3, false); -/// BK6: vector E-vector-to-E-vector evaluation of stiffness matrix, q=p+1 -BakeOff_Kernel(6, VectorDiffusion, 3, true) +// BP5: scalar PCG with stiffness matrix, q=p+1 +REGISTER(BP, 5, 1, true); + +// BP6: vector PCG with stiffness matrix, q=p+1 +REGISTER(BP, 6, 3, true); + +// BK1: scalar E-vector-to-E-vector evaluation of mass matrix, q=p+2 +REGISTER(BK, 1, 1, false); + +// BK2: vector E-vector-to-E-vector evaluation of mass matrix, q=p+2 +REGISTER(BK, 2, 3, false); + +// BK3: scalar E-vector-to-E-vector evaluation of stiffness matrix, q=p+2 +REGISTER(BK, 3, 1, false); + +// BK4: vector E-vector-to-E-vector evaluation of stiffness matrix, q=p+2 +REGISTER(BK, 4, 3, false); + +// BK5: scalar E-vector-to-E-vector evaluation of stiffness matrix, q=p+1 +REGISTER(BK, 5, 1, true); + +// BK6: vector E-vector-to-E-vector evaluation of stiffness matrix, q=p+1 +REGISTER(BK, 6, 3, true); /** - * @brief main entry point - * --benchmark_filter=BK1/6 - * --benchmark_context=device=cpu + * @brief CEED Bake-off Problems main entry point + * Command line options: + * --benchmark_context=device=gpu + * --benchmark_filter=BP1 + * --benchmark_out_format=csv + * --benchmark_out=bp1.csv */ int main(int argc, char *argv[]) { bm::ConsoleReporter CR; bm::Initialize(&argc, argv); + AddKernelSpecializations(); + // Device setup, cpu by default std::string device_config = "cpu"; auto global_context = bmi::GetGlobalContext(); @@ -240,12 +351,16 @@ int main(int argc, char *argv[]) device_config = device->second; } } + Device device(device_config.c_str()); device.Print(); - if (bm::ReportUnrecognizedArguments(argc, argv)) { return 1; } + if (bm::ReportUnrecognizedArguments(argc, argv)) { return EXIT_FAILURE; } + bm::RunSpecifiedBenchmarks(&CR); - return 0; + bm::Shutdown(); + + return EXIT_SUCCESS; } #endif // MFEM_USE_BENCHMARK diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 930695fa3e..a837031781 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -101,6 +101,7 @@ set(UNIT_TESTS_SRCS fem/test_calcdshape.cpp fem/test_calcshape.cpp fem/test_calcvshape.cpp + fem/test_calchessian.cpp fem/test_coefficient.cpp fem/test_col_lag_der.cpp fem/test_datacollection.cpp diff --git a/tests/unit/fem/test_assembly_levels.cpp b/tests/unit/fem/test_assembly_levels.cpp index d1130eab5f..0cfb4d6c8b 100644 --- a/tests/unit/fem/test_assembly_levels.cpp +++ b/tests/unit/fem/test_assembly_levels.cpp @@ -320,8 +320,12 @@ TEST_CASE("NormalTraceJumpIntegrator Element Assembly", "[AssemblyLevel][GPU]") { const auto fname = GENERATE( "../../data/inline-quad.mesh", + "../../data/amr-quad.mesh", + "../../data/beam-quad-amr.mesh", "../../data/star-q3.mesh", "../../data/inline-hex.mesh", + "../../data/amr-hex.mesh", + "../../data/fichera-amr.mesh", "../../data/fichera-q3.mesh" ); const int order = GENERATE(1, 2, 3); @@ -356,7 +360,7 @@ TEST_CASE("NormalTraceJumpIntegrator Element Assembly", "[AssemblyLevel][GPU]") for (int f = 0; f < mesh.GetNumFaces(); ++f) { const Mesh::FaceInformation info = mesh.GetFaceInformation(f); - if (!info.IsInterior()) { continue; } + if (!info.IsInterior() || info.IsNonconformingCoarse()) { continue; } const int el1 = info.element[0].index; const int el2 = info.element[1].index; diff --git a/tests/unit/fem/test_calchessian.cpp b/tests/unit/fem/test_calchessian.cpp new file mode 100644 index 0000000000..297bb65e05 --- /dev/null +++ b/tests/unit/fem/test_calchessian.cpp @@ -0,0 +1,441 @@ +// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced +// at the Lawrence Livermore National Laboratory. All Rights reserved. See files +// LICENSE and NOTICE for details. LLNL-CODE-806117. +// +// This file is part of the MFEM library. For more information and source code +// availability visit https://mfem.org. +// +// MFEM is free software; you can redistribute it and/or modify it under the +// terms of the BSD-3 license. We welcome feedback and contributions, see file +// CONTRIBUTING.md for details. + +#include "mfem.hpp" +#include "unit_tests.hpp" + +#include +#include + +using namespace mfem; + + +/** +* Compute the error of the taylor series expansion of the shapefunctions, upto +* and including the hessian term: +* res = shape(xi) + dshape(xi)*eps*dx + 0.5*hessian(xi)*eps*eps*dx*dx +* - shape(xi + eps*dx) +*/ +real_t TaylorSeriesError(const FiniteElement* fe, + const IntegrationPoint &ip, + const Vector &dx, + const real_t eps) +{ + const int dof = fe->GetDof(); + const int dim = fe->GetDim(); + const int hdim = (dim*(dim+1))/2; + + Vector shape(dof); + DenseMatrix dshape(dof,dim); + DenseMatrix hessian(dof,hdim); + + fe->CalcShape(ip, shape); + fe->CalcDShape(ip, dshape); + fe->CalcHessian(ip, hessian); + + Vector dx2(hdim); + if (dim == 1) + { + dx2[0] = dx[0]*dx[0]; + } + else if (dim == 2) + { + dx2[0] = dx[0]*dx[0]; + dx2[1] = 2*dx[0]*dx[1]; + dx2[2] = dx[1]*dx[1]; + } + else if (dim == 3) + { + dx2[0] = dx[0]*dx[0]; + dx2[1] = 2*dx[0]*dx[1]; + dx2[2] = 2*dx[0]*dx[2]; + dx2[3] = dx[1]*dx[1]; + dx2[4] = 2*dx[1]*dx[2]; + dx2[5] = dx[2]*dx[2]; + } + + Vector res(dof); + res = shape; + dshape.AddMult(dx, res, eps); + hessian.AddMult(dx2, res, 0.5*eps*eps); + + IntegrationPoint ip_eps; + Vector shape_eps(dof); + ip_eps.x = ip.x + eps*dx[0]; + if (dim >= 2 ) { ip_eps.y = ip.y + eps*dx[1]; } + if (dim == 3 ) { ip_eps.z = ip.z + eps*dx[2]; } + + fe->CalcShape(ip_eps, shape_eps); + res -= shape_eps; + return res.Norml2(); +} + +/** +* Check the convergence of the taylor series, of a given element @a fe at +* a given point @a ip in a given direction @a dx. +* For linear and quadratic elements the taylor series is exact. +* For other elements the convergence should be third order. +*/ + +void CheckTaylorSeries(const FiniteElement* fe, + const IntegrationPoint &ip, + const Vector &dx) +{ + real_t eps = 0.1; + constexpr real_t red = 4.0; + constexpr int steps = 100; + constexpr real_t tol = 1e-8; + + real_t error = TaylorSeriesError(fe, ip, dx, eps); + real_t order; + int i; + for (i = 0; i < steps; ++i) + { + eps /= red; + real_t err_new = TaylorSeriesError(fe, ip, dx, eps); + order = log(error/err_new)/log(red); + error = err_new; + if (error < tol) { break; } + } + mfem::out< 2.98); + } +} + +/** +* Test if a given element @a fe has the correct behaviour of the taylor series. +*/ +void TestCalcHessian(const FiniteElement* fe) +{ + const int dim = fe->GetDim(); + + constexpr int check_res = 2; + int num_check_dirs = dim; + + // Get a uniform grid of integration points + RefinedGeometry* ref = GlobGeometryRefiner.Refine(fe->GetGeomType(), + check_res); + const IntegrationRule& intRule = ref->RefPts; + int npoints = intRule.GetNPoints(); + Vector dx(dim); + for (int i=0; i < npoints; ++i) + { + // Get the current integration point from intRule + IntegrationPoint pt = intRule.IntPoint(i); + + for (int j=0; j < num_check_dirs; ++j) + { + dx[0] = sin(2*j + 0.3); + if (dim >= 2) { dx[1] = cos(5*j + 0.2); } + if (dim == 3) { dx[2] = sin(3*j + 0.1); } + CheckTaylorSeries(fe, pt, dx); + } + } +} + + +TEST_CASE("CalcHessian", + "[Linear1DFiniteElement]" + "[Linear2DFiniteElement]" + "[Linear3DFiniteElement]" + "[BiLinear2DFiniteElement]" + "[TriLinear3DFiniteElement]" + "[H1_SegmentElement]" + "[H1_QuadrilateralElement]" + "[H1_HexahedronElement]" + "[H1_TriangleElement]" + "[H1_TetrahedronElement]" + "[NURBS1DFiniteElement]" + "[NURBS2DFiniteElement]" + "[NURBS3DFiniteElement]") +{ + + // Fixed Order Elements + SECTION("Linear1DFiniteElement") + { + mfem::out<<"Linear1DFiniteElement"<Size()/dim; i++) + { + mesh.GetNode(i, x0.GetData()); + Rotate.Mult(x0, x1); + mesh.SetNode(i, x1.GetData()); + } + + // Distort mesh + real_t distort_scale = 0.05; + if (deformed) + { + Vector dx(mesh.GetNodes()->Size()); + dx.Randomize(1234); + dx *= 2.0; dx -= 1.0; dx *= distort_scale; + mesh.MoveNodes(dx); + } + + if (NURBS) + { + // We need a C1 smooth mesh + mesh.DegreeElevate(1); + + // Refine mesh + mesh.UniformRefinement(); + + // Distort mesh + distort_scale = 0.01; + if (deformed) + { + Vector dx(mesh.GetNodes()->Size()); + dx.Randomize(1234); + dx *= 2.0; dx -= 1.0; dx *= distort_scale; + mesh.MoveNodes(dx); + } + } + + // Create Space + FiniteElementCollection *fe_coll = nullptr; + NURBSExtension *ext = nullptr; + if (NURBS) + { + fe_coll = new NURBSFECollection (order); + ext = new NURBSExtension(mesh.NURBSext, order); + } + else + { + fe_coll = new H1_FECollection (order); + } + FiniteElementSpace fes(&mesh, ext, fe_coll); + + // Compute (grad w, grad phi) + (w, laplace phi) = 0 + SparseMatrix gmat(fes.GetNDofs()); + Vector shape, lshape; + DenseMatrix dshape, elmat; + + DofTransformation doftrans; + ElementTransformation *eltrans; + Array vdofs; + for (int e = 0; e < fes.GetNE(); e++) + { + const int dof = fes.GetFE(e)->GetDof(); + shape.SetSize(dof); + dshape.SetSize(dof,dim); + lshape.SetSize(dof); + + elmat.SetSize(dof); + elmat = 0.0; + eltrans = fes.GetElementTransformation (e); + + // Integrand involves non-polynomial mapping + const int intorder = 3*fes.GetFE(e)->GetOrder(); + const IntegrationRule *ir = &IntRules.Get(fes.GetFE(e)->GetGeomType(), + intorder); + + elmat = 0.0; + for (int i = 0; i < ir->GetNPoints(); i++) + { + const IntegrationPoint &ip = ir->IntPoint(i); + eltrans->SetIntPoint(&ip); + const real_t w = ip.weight * eltrans->Weight(); + + fes.GetFE(e)->CalcShape(ip, shape); + fes.GetFE(e)->CalcPhysLaplacian(*eltrans, lshape); + fes.GetFE(e)->CalcPhysDShape(*eltrans, dshape); + + // Check Laplacian + AddMult_a_AAt (w, dshape, elmat); + AddMult_a_VWt (w, shape, lshape, elmat); + } + + // Add to global matrix + fes.GetElementVDofs (e, vdofs); + gmat.AddSubMatrix (vdofs, vdofs, elmat, 1); + } + + // Apply homogeneous essential boundary conditions on entire boundary + Array ess_dofs; + fes.GetBoundaryTrueDofs(ess_dofs); + for (int i=0; iJ.Read(), evec_values.Read(), col_der.Write(), sdim, vdim, nd); + const real_t max_norm = qp_der.Normlinf(); + qp_der -= col_der; - REQUIRE(qp_der.Normlinf() == MFEM_Approx(0.0, 1e-10, 1e-10)); + + const real_t abs_err = qp_der.Normlinf(); + const real_t rel_err = max_norm > 0_r ? + abs_err/max_norm : + abs_err > 0_r ? mfem::infinity() : 0_r; + CAPTURE(rel_err, max_norm); + CHECK(rel_err <= 1e-13); } } diff --git a/tests/unit/fem/test_gslib.cpp b/tests/unit/fem/test_gslib.cpp index 37bb5c1d9f..dcb54893fb 100644 --- a/tests/unit/fem/test_gslib.cpp +++ b/tests/unit/fem/test_gslib.cpp @@ -47,13 +47,14 @@ TEST_CASE("GSLIBInterpolate", "[GSLIBInterpolate][GSLIB]") int point_ordering = GENERATE(0, 1); int ncomp = GENERATE(1, 2); int gf_ordering = GENERATE(0, 1); + int func_out_ordering = GENERATE(0, 1); bool href = GENERATE(true, false); bool pref = GENERATE(true, false); int ne = 4; CAPTURE(space, simplex, dim, func_order, mesh_order, mesh_node_ordering, - point_ordering, ncomp, gf_ordering, href, pref); + point_ordering, ncomp, gf_ordering, func_out_ordering, href, pref); if (ncomp == 1 && gf_ordering == 1) { @@ -145,7 +146,8 @@ TEST_CASE("GSLIBInterpolate", "[GSLIBInterpolate][GSLIB]") FindPointsGSLIB finder; finder.Setup(mesh); finder.SetL2AvgType(FindPointsGSLIB::NONE); - finder.Interpolate(vxyz, field_vals, interp_vals, point_ordering); + finder.Interpolate(vxyz, field_vals, interp_vals, point_ordering, + func_out_ordering); Array code_out = finder.GetCode(); Vector dist_p_out = finder.GetDist(); @@ -168,7 +170,7 @@ TEST_CASE("GSLIBInterpolate", "[GSLIBInterpolate][GSLIB]") { if (code_out[i] < 2) { - err = gf_ordering == Ordering::byNODES ? + err = func_out_ordering == Ordering::byNODES ? fabs(exact_val(j) - interp_vals[i + j*pts_cnt]) : fabs(exact_val(j) - interp_vals[i*ncomp + j]); max_err = std::max(max_err, err); diff --git a/tests/unit/fem/test_quadinterpolator.cpp b/tests/unit/fem/test_quadinterpolator.cpp index 36e77448d4..bd7336a588 100644 --- a/tests/unit/fem/test_quadinterpolator.cpp +++ b/tests/unit/fem/test_quadinterpolator.cpp @@ -464,4 +464,80 @@ TEST_CASE("QuadratureInterpolator", "[QuadratureInterpolator][GPU]") REQUIRE(rel_error_norm == MFEM_Approx(0.0)); } } + + SECTION("Surface Determinants: 1D surface in 2D/3D and 2D surface in 3D") + { + const auto mesh_fname = GENERATE( + "../../data/diag-segment-2d.mesh", // 1D in 2D + "../../data/diag-segment-3d.mesh", // 1D in 3D + "../../data/star-surf.mesh" // 2D in 3D + ); + + // Using order > 1 to ensure curvature is used if supported by mesh + const int order = 3; + + Mesh mesh = Mesh::LoadFromFile(mesh_fname); + const int dim = mesh.Dimension(); + const int sdim = mesh.SpaceDimension(); + + REQUIRE(dim < sdim); + + // Ensure high-order curvature for non-trivial Jacobians where possible + mesh.SetCurvature(order); + + const FiniteElementSpace *fes = mesh.GetNodalFESpace(); + GridFunction *nodes = mesh.GetNodes(); + + // Quadrature space + QuadratureSpace qs(&mesh, 2*order); + const QuadratureInterpolator *qi = fes->GetQuadratureInterpolator(qs); + qi->SetOutputLayout(QVectorLayout::byVDIM); + + // Prepare E-vector from nodes + const ElementDofOrdering ordering = + (mesh.Dimension() == 1 || mesh.MeshGenerator() == 2) ? + ElementDofOrdering::LEXICOGRAPHIC : ElementDofOrdering::NATIVE; + + const Operator *R = fes->GetElementRestriction(ordering); + Vector e_vec(R->Height()); + R->Mult(*nodes, e_vec); + + // Compute determinants (weights) via QI + // Output vector size: qs.GetSize() * 1 (since determinant is scalar) + Vector q_det(qs.GetSize()); + qi->Determinants(e_vec, q_det); + + // Verify against ElementTransformation::Weight() + Vector q_weights(qs.GetSize()); + const int ne = qs.GetNE(); + int idx_counter = 0; + for (int i = 0; i < ne; i++) + { + ElementTransformation *T = mesh.GetElementTransformation(i); + const IntegrationRule &ir = qs.GetIntRule(i); + for (int j = 0; j < ir.GetNPoints(); j++) + { + const IntegrationPoint &ip = ir.IntPoint(j); + T->SetIntPoint(&ip); + q_weights(idx_counter++) = T->Weight(); + } + } + + // Compare + Vector diff = q_det; + diff -= q_weights; + const real_t norm_w = q_weights.Normlinf(); + const real_t norm_d = diff.Normlinf(); + + // If weights are effectively zero (e.g. degenerate), direct comparison might differ + // but for these valid meshes, weight should be > 0. + if (norm_w > 1e-12) + { + REQUIRE(norm_d / norm_w < 1e-12); + } + else + { + REQUIRE(norm_d < 1e-12); + } + } } diff --git a/tests/unit/general/test_array.cpp b/tests/unit/general/test_array.cpp index de88b18e28..efee22ecdc 100644 --- a/tests/unit/general/test_array.cpp +++ b/tests/unit/general/test_array.cpp @@ -125,6 +125,59 @@ TEST_CASE("Array stl-interactions", "[Array]") } } + +TEST_CASE("Array move assignment to view (MakeRef)", "[Array]") +{ + constexpr int n = 5; + + // Helper function that returns an Array by value (rvalue) + auto make_sequence_array = [](int n) + { + Array arr(n); + for (int i = 0; i < n; i++) + { + arr[i] = i + 1; // 1, 2, 3, ... + } + return arr; + }; + + // Create backing storage + Array backing1(n), backing2(n); + backing1 = 0.0; backing2 = 0.0; + + // Create a view into the backing storage + Array view1, view2; + view1.MakeRef(backing1); + view2.MakeRef(backing2); + + auto seq_array = make_sequence_array(n); + view1 = seq_array; // copy assign + view2 = std::move(seq_array); // move assign + + CHECK(seq_array.Size() == 0); // seq_array is invalidated by the move + + // Both assignments should be semantically equivalent. + for (int i = 0; i < n; i++) + { + CHECK(backing1[i] == backing2[i]); + } + + // Create backing storage as raw array + int backing3[n] = {0, 0, 0, 0, 0}; + + // Create a view into the backing storage + Array view3; + view3.MakeRef(backing3, n); + + // Assign from rvalue + view3 = std::move(view2); + + for (int i = 0; i < n; i++) + { + CHECK(backing3[i] == backing1[i]); + } +} + TEST_CASE("Array delete at indices", "[Array],[GPU]") { for (int use_dev = 0; use_dev < 2; use_dev++) diff --git a/tests/unit/linalg/test_matrix_dense.cpp b/tests/unit/linalg/test_matrix_dense.cpp index 0e3422711d..120e90c821 100644 --- a/tests/unit/linalg/test_matrix_dense.cpp +++ b/tests/unit/linalg/test_matrix_dense.cpp @@ -961,3 +961,38 @@ TEST_CASE("NNLS", "[DenseMatrix]") } #endif // if MFEM_USE_LAPACK + +TEST_CASE("DenseTensor slice copy and move assign equivalency", + "[DenseMatrix][DenseTensor]") +{ + auto fill_matrix = [](int n) + { + DenseMatrix M(n, n); + M = 0.0; + for (int i = 0; i < n; i++) + for (int j = 0; j < n; j++) + { + M(i, j) = i+j*n+1; + } + return M; + }; + + constexpr int n = 3; + constexpr int k = 2; + + DenseTensor tensor1(n, n, k), tensor2(n, n, k); + tensor1 = 0.0; tensor2 = 0.0; + + DenseMatrix temp = fill_matrix(n); + tensor1(0) = temp; // copy assign + tensor2(0) = std::move(temp); // move assign + + // Check that the tensor was actually updated + for (int i = 0; i < n; i++) + { + for (int j = 0; j < n; j++) + { + CHECK(tensor1(i, j, 0) == tensor2(i, j, 0)); + } + } +} diff --git a/tests/unit/mesh/test_nurbs.cpp b/tests/unit/mesh/test_nurbs.cpp index ed2ae7c800..11aeee2209 100644 --- a/tests/unit/mesh/test_nurbs.cpp +++ b/tests/unit/mesh/test_nurbs.cpp @@ -222,13 +222,13 @@ TEST_CASE("Greville, Botella and Demko points", "[NURBS]") mfem::out<<"Knotvector : "; kvp.Print(mfem::out); kvp.GetGreville(xi); - mfem::out<<"Greville points : "; xi.Print(std::cout,999); + mfem::out<<"Greville points : "; xi.Print(mfem::out,999); kvp.GetBotella(xi); - mfem::out<<"Botella points : "; xi.Print(std::cout,999); + mfem::out<<"Botella points : "; xi.Print(mfem::out,999); kvp.GetDemko(xi); - mfem::out<<"Demko points : "; xi.Print(std::cout,999); + mfem::out<<"Demko points : "; xi.Print(mfem::out,999); } KnotVector kv(3, Vector({0.0, 0.3, 0.3, 0.3, 0.6, 1.0}));