1121 lines
30 KiB
C++
1121 lines
30 KiB
C++
// 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"
|
|
|
|
namespace mfem
|
|
{
|
|
|
|
static real_t exact_sln(const Vector &p);
|
|
static void TestSolve(FiniteElementSpace &fespace);
|
|
static void TestSolveVec(FiniteElementSpace &fespace);
|
|
#ifdef MFEM_USE_MPI
|
|
static void TestSolvePar(ParFiniteElementSpace &fespace);
|
|
static void TestSolveParVec(ParFiniteElementSpace &fespace);
|
|
static void TestRandomPRefinement(Mesh & mesh);
|
|
#endif
|
|
|
|
namespace var_order_test { enum class SpaceType {RT, ND}; }
|
|
|
|
Mesh MakeCartesianMesh(int nx, int dim)
|
|
{
|
|
if (dim == 2)
|
|
{
|
|
return Mesh::MakeCartesian2D(nx, nx, Element::QUADRILATERAL, true);
|
|
}
|
|
else
|
|
{
|
|
return Mesh::MakeCartesian3D(nx, nx, nx, Element::HEXAHEDRON);
|
|
}
|
|
}
|
|
|
|
// Check basic functioning of variable order spaces, hp interpolation and
|
|
// some corner cases.
|
|
TEST_CASE("Variable Order FiniteElementSpace",
|
|
"[FiniteElementCollection]"
|
|
"[FiniteElementSpace]"
|
|
"[NCMesh]")
|
|
{
|
|
SECTION("Quad mesh")
|
|
{
|
|
// 2-element quad mesh
|
|
Mesh mesh = Mesh::MakeCartesian2D(2, 1, Element::QUADRILATERAL);
|
|
mesh.EnsureNCMesh();
|
|
|
|
// standard H1 space with order 1 elements
|
|
H1_FECollection fec(1, mesh.Dimension());
|
|
FiniteElementSpace fespace(&mesh, &fec);
|
|
|
|
REQUIRE(fespace.GetNDofs() == 6);
|
|
REQUIRE(fespace.GetNConformingDofs() == 6);
|
|
|
|
// convert to variable order space: p-refine second element
|
|
fespace.SetElementOrder(1, 2);
|
|
fespace.Update(false);
|
|
|
|
REQUIRE(fespace.GetNDofs() == 11);
|
|
REQUIRE(fespace.GetNConformingDofs() == 10);
|
|
|
|
// h-refine first element in the y axis
|
|
Array<Refinement> refs;
|
|
refs.Append(Refinement(0, 2));
|
|
mesh.GeneralRefinement(refs);
|
|
fespace.Update();
|
|
|
|
REQUIRE(fespace.GetNDofs() == 13);
|
|
REQUIRE(fespace.GetNConformingDofs() == 11);
|
|
|
|
// relax the master edge to be quadratic
|
|
fespace.SetRelaxedHpConformity(true);
|
|
|
|
REQUIRE(fespace.GetNDofs() == 13);
|
|
REQUIRE(fespace.GetNConformingDofs() == 12);
|
|
|
|
// increase order
|
|
for (int i = 0; i < mesh.GetNE(); i++)
|
|
{
|
|
fespace.SetElementOrder(i, fespace.GetElementOrder(i) + 1);
|
|
}
|
|
fespace.Update(false);
|
|
|
|
// 15 quadratic + 16 cubic DOFs - 2 shared vertices:
|
|
REQUIRE(fespace.GetNDofs() == 29);
|
|
// 3 constrained DOFs on slave side, inexact interpolation
|
|
REQUIRE(fespace.GetNConformingDofs() == 26);
|
|
|
|
// relaxed off
|
|
fespace.SetRelaxedHpConformity(false);
|
|
|
|
// new quadratic DOF on master edge:
|
|
REQUIRE(fespace.GetNDofs() == 30);
|
|
// 3 constrained DOFs on slave side, 2 on master side:
|
|
REQUIRE(fespace.GetNConformingDofs() == 25);
|
|
|
|
TestSolve(fespace);
|
|
|
|
// refine
|
|
mesh.UniformRefinement();
|
|
fespace.Update();
|
|
|
|
REQUIRE(fespace.GetNDofs() == 93);
|
|
REQUIRE(fespace.GetNConformingDofs() == 83);
|
|
|
|
TestSolve(fespace);
|
|
}
|
|
|
|
SECTION("Quad/hex mesh projection")
|
|
{
|
|
for (int dim=2; dim<=3; ++dim)
|
|
{
|
|
// 2-element mesh
|
|
Mesh mesh = dim == 2 ? Mesh::MakeCartesian2D(2, 1, Element::QUADRILATERAL) :
|
|
Mesh::MakeCartesian3D(2, 1, 1, Element::HEXAHEDRON);
|
|
mesh.EnsureNCMesh();
|
|
|
|
// h-refine element 1
|
|
Array<Refinement> refinements;
|
|
refinements.Append(Refinement(1));
|
|
|
|
int nonconformity_limit = 0; // 0 meaning allow unlimited ratio
|
|
mesh.GeneralRefinement(refinements, 1, nonconformity_limit); // h-refinement
|
|
|
|
// standard H1 space with order 2 elements
|
|
H1_FECollection fec(2, mesh.Dimension());
|
|
FiniteElementSpace fespace(&mesh, &fec);
|
|
|
|
GridFunction x(&fespace);
|
|
|
|
// p-refine element 0
|
|
fespace.SetElementOrder(0, 3);
|
|
|
|
fespace.Update(false);
|
|
x.SetSpace(&fespace);
|
|
|
|
// Test projection of the coefficient
|
|
FunctionCoefficient exsol(exact_sln);
|
|
x.ProjectCoefficient(exsol);
|
|
|
|
// Enforce space constraints on locally interpolated GridFunction x
|
|
const SparseMatrix *R = fespace.GetHpRestrictionMatrix();
|
|
const SparseMatrix *P = fespace.GetConformingProlongation();
|
|
Vector y(fespace.GetTrueVSize());
|
|
R->Mult(x, y);
|
|
P->Mult(y, x);
|
|
|
|
const real_t error = x.ComputeL2Error(exsol);
|
|
REQUIRE(error == MFEM_Approx(0.0));
|
|
}
|
|
}
|
|
|
|
SECTION("Hex mesh")
|
|
{
|
|
// 2-element hex mesh
|
|
Mesh mesh = Mesh::MakeCartesian3D(2, 1, 1, Element::HEXAHEDRON);
|
|
mesh.EnsureNCMesh();
|
|
|
|
// standard H1 space with order 1 elements
|
|
H1_FECollection fec(1, mesh.Dimension());
|
|
FiniteElementSpace fespace(&mesh, &fec);
|
|
|
|
REQUIRE(fespace.GetNDofs() == 12);
|
|
REQUIRE(fespace.GetNConformingDofs() == 12);
|
|
|
|
// convert to variable order space: p-refine second element
|
|
fespace.SetElementOrder(1, 2);
|
|
fespace.Update(false);
|
|
|
|
REQUIRE(fespace.GetNDofs() == 31);
|
|
REQUIRE(fespace.GetNConformingDofs() == 26);
|
|
|
|
// h-refine first element in the z axis
|
|
Array<Refinement> refs;
|
|
refs.Append(Refinement(0, 4));
|
|
mesh.GeneralRefinement(refs);
|
|
fespace.Update();
|
|
|
|
REQUIRE(fespace.GetNDofs() == 35);
|
|
REQUIRE(fespace.GetNConformingDofs() == 28);
|
|
|
|
// relax the master face to be quadratic
|
|
fespace.SetRelaxedHpConformity(true);
|
|
|
|
REQUIRE(fespace.GetNDofs() == 35);
|
|
REQUIRE(fespace.GetNConformingDofs() == 31);
|
|
|
|
// increase order
|
|
for (int i = 0; i < mesh.GetNE(); i++)
|
|
{
|
|
fespace.SetElementOrder(i, fespace.GetElementOrder(i) + 1);
|
|
}
|
|
fespace.Update(false);
|
|
|
|
REQUIRE(fespace.GetNDofs() == 105);
|
|
REQUIRE(fespace.GetNConformingDofs() == 92);
|
|
|
|
// relaxed off
|
|
fespace.SetRelaxedHpConformity(false);
|
|
|
|
REQUIRE(fespace.GetNDofs() == 108);
|
|
REQUIRE(fespace.GetNConformingDofs() == 87);
|
|
|
|
// refine one of the small elements into four
|
|
refs[0].SetType(3);
|
|
mesh.GeneralRefinement(refs);
|
|
fespace.Update();
|
|
|
|
REQUIRE(fespace.GetNDofs() == 162);
|
|
REQUIRE(fespace.GetNConformingDofs() == 115);
|
|
|
|
TestSolve(fespace);
|
|
|
|
// lower the order of one of the four new elements to 1 - this minimum
|
|
// order will propagate through two master faces and severely constrain
|
|
// the space (since relaxed hp is off)
|
|
fespace.SetElementOrder(0, 1);
|
|
fespace.Update(false);
|
|
|
|
REQUIRE(fespace.GetNDofs() == 152);
|
|
REQUIRE(fespace.GetNConformingDofs() == 92);
|
|
}
|
|
|
|
SECTION("Prism mesh")
|
|
{
|
|
// 2-element prism mesh
|
|
Mesh mesh = Mesh::MakeCartesian3D(1, 1, 1, Element::WEDGE);
|
|
mesh.EnsureNCMesh();
|
|
|
|
// standard H1 space with order 2 elements
|
|
H1_FECollection fec(2, mesh.Dimension());
|
|
FiniteElementSpace fespace(&mesh, &fec);
|
|
|
|
REQUIRE(fespace.GetNDofs() == 27);
|
|
REQUIRE(fespace.GetNConformingDofs() == 27);
|
|
|
|
// convert to variable order space: p-refine first element
|
|
fespace.SetElementOrder(0, 3);
|
|
fespace.Update(false);
|
|
|
|
REQUIRE(fespace.GetNDofs() == 54);
|
|
REQUIRE(fespace.GetNConformingDofs() == 42);
|
|
|
|
// refine to form an edge-face constraint similar to
|
|
// https://github.com/mfem/mfem/pull/713#issuecomment-495786362
|
|
Array<Refinement> refs;
|
|
refs.Append(Refinement(1, 3));
|
|
mesh.GeneralRefinement(refs);
|
|
fespace.Update(false);
|
|
|
|
refs[0].SetType(4);
|
|
refs.Append(Refinement(2, 4));
|
|
mesh.GeneralRefinement(refs);
|
|
fespace.Update(false);
|
|
|
|
REQUIRE(fespace.GetNDofs() == 113);
|
|
REQUIRE(fespace.GetNConformingDofs() == 67);
|
|
|
|
TestSolve(fespace);
|
|
}
|
|
|
|
SECTION("Quad/hex mesh ND/RT")
|
|
{
|
|
using namespace var_order_test;
|
|
|
|
const auto space_type = GENERATE(SpaceType::RT, SpaceType::ND);
|
|
const int dim = GENERATE(2, 3);
|
|
|
|
Mesh mesh = MakeCartesianMesh(dim == 2 ? 4 : 2, dim);
|
|
mesh.EnsureNCMesh();
|
|
|
|
int ndof0, ncdof1, ncdof2, ndof1;
|
|
std::unique_ptr<FiniteElementCollection> fec;
|
|
if (space_type == SpaceType::RT)
|
|
{
|
|
// Standard RT space with order 0 elements
|
|
fec.reset(new RT_FECollection(0, dim));
|
|
|
|
if (dim == 2)
|
|
{
|
|
ndof0 = 40;
|
|
ndof1 = 62;
|
|
ncdof1 = 56;
|
|
ncdof2 = 312;
|
|
}
|
|
else
|
|
{
|
|
ndof0 = 36;
|
|
ndof1 = 141;
|
|
ncdof1 = 114;
|
|
ncdof2 = 756;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Standard ND space with order 1 elements
|
|
fec.reset(new ND_FECollection(1, dim));
|
|
|
|
if (dim == 2)
|
|
{
|
|
ndof0 = 40;
|
|
ndof1 = 50;
|
|
ncdof1 = 46;
|
|
ncdof2 = 144;
|
|
}
|
|
else
|
|
{
|
|
ndof0 = 54;
|
|
ndof1 = 105;
|
|
ncdof1 = 75;
|
|
ncdof2 = 300;
|
|
}
|
|
}
|
|
|
|
FiniteElementSpace fespace(&mesh, fec.get());
|
|
|
|
REQUIRE(fespace.GetNDofs() == ndof0);
|
|
REQUIRE(fespace.GetNConformingDofs() == ndof0);
|
|
|
|
// Convert to variable order space: p-refine first element
|
|
fespace.SetElementOrder(0, 2);
|
|
fespace.Update(false);
|
|
|
|
REQUIRE(fespace.GetNDofs() == ndof1);
|
|
REQUIRE(fespace.GetNConformingDofs() == ncdof1);
|
|
|
|
// p-refine all elements
|
|
for (int i = 1; i < mesh.GetNE(); i++)
|
|
{
|
|
fespace.SetElementOrder(i, fespace.GetElementOrder(i) + 1);
|
|
}
|
|
fespace.Update(false);
|
|
|
|
REQUIRE(fespace.GetNConformingDofs() == ncdof2);
|
|
|
|
TestSolveVec(fespace);
|
|
}
|
|
}
|
|
|
|
#ifdef MFEM_USE_MPI
|
|
TEST_CASE("Parallel Variable Order FiniteElementSpace",
|
|
"[FiniteElementCollection]"
|
|
"[FiniteElementSpace]"
|
|
"[NCMesh][Parallel]")
|
|
{
|
|
SECTION("Quad mesh")
|
|
{
|
|
// 2-by-2 element quad mesh
|
|
Mesh mesh = MakeCartesianMesh(2, 2);
|
|
mesh.EnsureNCMesh();
|
|
|
|
ParMesh pmesh(MPI_COMM_WORLD, mesh);
|
|
mesh.Clear();
|
|
|
|
// Standard H1 space with order 1 elements
|
|
H1_FECollection fe_coll(1, pmesh.Dimension());
|
|
ParFiniteElementSpace pfes(&pmesh, &fe_coll);
|
|
|
|
REQUIRE(pfes.GlobalTrueVSize() == 9);
|
|
|
|
// Convert to variable order space by p-refinement
|
|
// Increase order on all elements
|
|
for (int i = 0; i < pmesh.GetNE(); i++)
|
|
{
|
|
pfes.SetElementOrder(i, pfes.GetElementOrder(i) + 1);
|
|
}
|
|
pfes.Update(false);
|
|
|
|
// DOFs for vertices + edges + elements = 9 + 12 + 4 = 25
|
|
REQUIRE(pfes.GlobalTrueVSize() == 25);
|
|
|
|
int rank;
|
|
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
|
|
|
|
if (rank == 0) { pfes.SetElementOrder(0, 4); }
|
|
pfes.Update(false);
|
|
|
|
Array<Refinement> refs;
|
|
if (rank == 0) { refs.Append(Refinement(0)); }
|
|
pmesh.GeneralRefinement(refs);
|
|
pfes.Update(false);
|
|
|
|
TestSolvePar(pfes);
|
|
}
|
|
|
|
SECTION("Hex mesh")
|
|
{
|
|
// 2^3 element hex mesh
|
|
Mesh mesh = MakeCartesianMesh(2, 3);
|
|
mesh.EnsureNCMesh();
|
|
|
|
ParMesh pmesh(MPI_COMM_WORLD, mesh);
|
|
mesh.Clear();
|
|
|
|
// Standard H1 space with order 1 elements
|
|
H1_FECollection fe_coll(1, pmesh.Dimension());
|
|
ParFiniteElementSpace pfes(&pmesh, &fe_coll);
|
|
|
|
REQUIRE(pfes.GlobalTrueVSize() == 27); // 3^3
|
|
|
|
// Convert to variable order space by p-refinement
|
|
for (int i = 0; i < pmesh.GetNE(); i++)
|
|
{
|
|
pfes.SetElementOrder(i, pfes.GetElementOrder(i) + 1);
|
|
}
|
|
pfes.Update(false);
|
|
|
|
// DOFs for vertices + edges + faces + elements = 27 + 54 + 36 + 8 = 125
|
|
REQUIRE(pfes.GlobalTrueVSize() == 125); // 5^3
|
|
|
|
int rank;
|
|
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
|
|
|
|
if (rank == 0) { pfes.SetElementOrder(0, 4); }
|
|
pfes.Update(false);
|
|
|
|
Array<Refinement> refs;
|
|
if (rank == 0) { refs.Append(Refinement(0)); }
|
|
pmesh.GeneralRefinement(refs);
|
|
pfes.Update(false);
|
|
|
|
TestSolvePar(pfes);
|
|
}
|
|
|
|
SECTION("Hex mesh with intermediate orders")
|
|
{
|
|
// Test ParFiniteElementSpace::MarkIntermediateEntityDofs
|
|
// This test is designed for 2 MPI ranks. If more than 2 ranks are used,
|
|
// the test is run on only the first 2 ranks via a split communicator.
|
|
int numprocs, rank;
|
|
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
|
|
MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
|
|
|
|
MPI_Comm comm2;
|
|
MPI_Comm_split(MPI_COMM_WORLD, rank < 2 ? 0 : 1, rank, &comm2);
|
|
|
|
if (rank < 2)
|
|
{
|
|
// 2x1x1 element hex mesh
|
|
Mesh mesh = Mesh::MakeCartesian3D(2, 1, 1, Element::HEXAHEDRON);
|
|
mesh.EnsureNCMesh();
|
|
|
|
Array<int> partition(2);
|
|
partition = 0;
|
|
if (numprocs > 1) { partition[1] = 1; }
|
|
|
|
ParMesh pmesh(comm2, mesh, partition.GetData());
|
|
mesh.Clear();
|
|
|
|
// Standard H1 space with order 1 elements
|
|
H1_FECollection fe_coll(1, pmesh.Dimension());
|
|
ParFiniteElementSpace fespace(&pmesh, &fe_coll);
|
|
|
|
{
|
|
Array<Refinement> refs;
|
|
if (rank == 1) { refs.Append(Refinement(0)); }
|
|
pmesh.GeneralRefinement(refs);
|
|
fespace.Update(false);
|
|
}
|
|
|
|
{
|
|
Array<Refinement> refs;
|
|
if (rank == 1) { refs.Append(Refinement(4)); }
|
|
pmesh.GeneralRefinement(refs);
|
|
fespace.Update(false);
|
|
}
|
|
|
|
if (rank == 1)
|
|
{
|
|
for (int elem=0; elem<pmesh.GetNE(); ++elem)
|
|
{
|
|
const int p_elem = fespace.GetElementOrder(elem);
|
|
fespace.SetElementOrder(elem, p_elem + 1);
|
|
}
|
|
}
|
|
fespace.Update(false);
|
|
|
|
{
|
|
Array<Refinement> refs;
|
|
if (rank == 1) { refs.Append(Refinement(6)); }
|
|
if (rank == 0) { refs.Append(Refinement(0)); }
|
|
pmesh.GeneralRefinement(refs);
|
|
fespace.Update(false);
|
|
}
|
|
|
|
{
|
|
Array<Refinement> refs;
|
|
if (rank == 1) { refs.Append(Refinement(10)); }
|
|
pmesh.GeneralRefinement(refs);
|
|
fespace.Update(false);
|
|
}
|
|
|
|
if (rank == 1) { fespace.SetElementOrder(3, 3); }
|
|
fespace.Update(false);
|
|
|
|
// Set at least order 2 everywhere
|
|
for (int elem=0; elem<pmesh.GetNE(); ++elem)
|
|
{
|
|
const int p_elem = fespace.GetElementOrder(elem);
|
|
if (p_elem < 2) { fespace.SetElementOrder(elem, 2); }
|
|
}
|
|
fespace.Update(false);
|
|
|
|
TestSolvePar(fespace);
|
|
}
|
|
}
|
|
|
|
SECTION("Quad/hex mesh ND/RT")
|
|
{
|
|
using namespace var_order_test;
|
|
|
|
const auto space_type = GENERATE(SpaceType::RT, SpaceType::ND);
|
|
const int dim = GENERATE(2, 3);
|
|
|
|
Mesh mesh = MakeCartesianMesh(dim == 2 ? 4 : 2, dim);
|
|
|
|
mesh.EnsureNCMesh();
|
|
|
|
ParMesh pmesh(MPI_COMM_WORLD, mesh);
|
|
mesh.Clear();
|
|
|
|
int ndof0, ncdof2;
|
|
|
|
std::unique_ptr<FiniteElementCollection> fec;
|
|
if (space_type == SpaceType::RT)
|
|
{
|
|
// Standard RT space with order 0 elements
|
|
fec.reset(new RT_FECollection(0, dim));
|
|
|
|
if (dim == 2)
|
|
{
|
|
ndof0 = 40;
|
|
ncdof2 = 312;
|
|
}
|
|
else
|
|
{
|
|
ndof0 = 36;
|
|
ncdof2 = 756;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Standard ND space with order 1 elements
|
|
fec.reset(new ND_FECollection(1, dim));
|
|
|
|
if (dim == 2)
|
|
{
|
|
ndof0 = 40;
|
|
ncdof2 = 144;
|
|
}
|
|
else
|
|
{
|
|
ndof0 = 54;
|
|
ncdof2 = 300;
|
|
}
|
|
}
|
|
|
|
ParFiniteElementSpace fespace(&pmesh, fec.get());
|
|
|
|
REQUIRE(fespace.GlobalTrueVSize() == ndof0);
|
|
|
|
// Convert to variable order space by p-refinement
|
|
// Increase order on all elements
|
|
for (int i = 0; i < pmesh.GetNE(); i++)
|
|
{
|
|
fespace.SetElementOrder(i, fespace.GetElementOrder(i) + 1);
|
|
}
|
|
fespace.Update(false);
|
|
|
|
REQUIRE(fespace.GlobalTrueVSize() == ncdof2);
|
|
|
|
TestSolveParVec(fespace);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Serial-parallel Comparison for Variable Order FiniteElementSpace",
|
|
"[FiniteElementCollection]"
|
|
"[FiniteElementSpace]"
|
|
"[NCMesh][Parallel]")
|
|
{
|
|
|
|
int dimension = GENERATE(2, 3);
|
|
Mesh mesh = MakeCartesianMesh(4, dimension);
|
|
TestRandomPRefinement(mesh);
|
|
}
|
|
#endif // MFEM_USE_MPI
|
|
|
|
// Exact solution: x^2 + y^2 + z^2
|
|
static real_t exact_sln(const Vector &p)
|
|
{
|
|
real_t x = p(0), y = p(1);
|
|
if (p.Size() == 3)
|
|
{
|
|
real_t z = p(2);
|
|
return x*x + y*y + z*z;
|
|
}
|
|
else
|
|
{
|
|
return x*x + y*y;
|
|
}
|
|
}
|
|
|
|
static real_t exact_rhs(const Vector &p)
|
|
{
|
|
return (p.Size() == 3) ? -6.0 : -4.0;
|
|
}
|
|
|
|
static void TestSolve(FiniteElementSpace &fespace)
|
|
{
|
|
Mesh *mesh = fespace.GetMesh();
|
|
|
|
// exact solution and RHS for the problem -\Delta u = 1
|
|
FunctionCoefficient exsol(exact_sln);
|
|
FunctionCoefficient rhs(exact_rhs);
|
|
|
|
// set up Dirichlet BC on the boundary
|
|
Array<int> ess_attr(mesh->bdr_attributes.Max());
|
|
ess_attr = 1;
|
|
|
|
Array<int> ess_tdof_list;
|
|
fespace.GetEssentialTrueDofs(ess_attr, ess_tdof_list);
|
|
|
|
GridFunction x(&fespace);
|
|
x = 0.0;
|
|
x.ProjectBdrCoefficient(exsol, ess_attr);
|
|
|
|
// assemble the linear form
|
|
LinearForm lf(&fespace);
|
|
lf.AddDomainIntegrator(new DomainLFIntegrator(rhs));
|
|
lf.Assemble();
|
|
|
|
// assemble the bilinear form.
|
|
BilinearForm bf(&fespace);
|
|
bf.AddDomainIntegrator(new DiffusionIntegrator());
|
|
bf.Assemble();
|
|
|
|
OperatorPtr A;
|
|
Vector B, X;
|
|
bf.FormLinearSystem(ess_tdof_list, x, lf, A, X, B);
|
|
|
|
// solve
|
|
GSSmoother M((SparseMatrix&)(*A));
|
|
PCG(*A, M, B, X, 0, 500, 1e-30, 0.0);
|
|
|
|
bf.RecoverFEMSolution(X, lf, x);
|
|
|
|
// compute L2 error from the exact solution
|
|
const real_t error = x.ComputeL2Error(exsol);
|
|
REQUIRE(error == MFEM_Approx(0.0));
|
|
|
|
// visualize
|
|
#ifdef MFEM_UNIT_DEBUG_VISUALIZE
|
|
const char vishost[] = "localhost";
|
|
const int visport = 19916;
|
|
std::unique_ptr<GridFunction> vis_x = x.ProlongToMaxOrder();
|
|
socketstream sol_sock(vishost, visport);
|
|
sol_sock.precision(8);
|
|
sol_sock << "solution\n" << *mesh << *vis_x;
|
|
#endif
|
|
}
|
|
|
|
// Quadratic exact solution for vector-valued spaces
|
|
void exact_sln_vec(const Vector &x, Vector &f)
|
|
{
|
|
if (f.Size() == 3)
|
|
{
|
|
f(0) = x(1)*x(2);
|
|
f(1) = x(0)*x(2);
|
|
f(2) = x(0)*x(1);
|
|
}
|
|
else
|
|
{
|
|
f(0) = x(0)*x(1);
|
|
f(1) = x(0)*x(1);
|
|
}
|
|
}
|
|
|
|
static void TestSolveVec(FiniteElementSpace &fespace)
|
|
{
|
|
Mesh *mesh = fespace.GetMesh();
|
|
const int sdim = mesh->SpaceDimension();
|
|
|
|
// Exact solution and RHS for the mass-matrix problem E = f
|
|
VectorFunctionCoefficient exsol(sdim, exact_sln_vec);
|
|
|
|
// No boundary conditions
|
|
Array<int> ess_attr(mesh->bdr_attributes.Max());
|
|
ess_attr = 0;
|
|
|
|
Array<int> ess_tdof_list;
|
|
fespace.GetEssentialTrueDofs(ess_attr, ess_tdof_list);
|
|
|
|
GridFunction x(&fespace);
|
|
x = 0.0;
|
|
x.ProjectBdrCoefficient(exsol, ess_attr);
|
|
|
|
// Assemble the linear form
|
|
LinearForm lf(&fespace);
|
|
lf.AddDomainIntegrator(new VectorFEDomainLFIntegrator(exsol));
|
|
lf.Assemble();
|
|
|
|
// Assemble the bilinear form
|
|
BilinearForm bf(&fespace);
|
|
bf.AddDomainIntegrator(new VectorFEMassIntegrator());
|
|
bf.Assemble();
|
|
|
|
OperatorPtr A;
|
|
Vector B, X;
|
|
bf.FormLinearSystem(ess_tdof_list, x, lf, A, X, B);
|
|
|
|
// Solve
|
|
GSSmoother M((SparseMatrix&)(*A));
|
|
PCG(*A, M, B, X, 0, 500, 1e-30, 0.0);
|
|
|
|
bf.RecoverFEMSolution(X, lf, x);
|
|
|
|
// Compute L2 error from the exact solution
|
|
const real_t error = x.ComputeL2Error(exsol);
|
|
|
|
REQUIRE(error == MFEM_Approx(0.0));
|
|
}
|
|
|
|
#ifdef MFEM_USE_MPI
|
|
static void TestSolvePar(ParFiniteElementSpace &pfes)
|
|
{
|
|
ParMesh *pmesh = pfes.GetParMesh();
|
|
|
|
// exact solution and RHS for the problem -\Delta u = 1
|
|
FunctionCoefficient exsol(exact_sln);
|
|
FunctionCoefficient rhs(exact_rhs);
|
|
|
|
// set up Dirichlet BC on the boundary
|
|
Array<int> ess_attr(pmesh->bdr_attributes.Max());
|
|
ess_attr = 1;
|
|
|
|
Array<int> ess_tdof_list;
|
|
pfes.GetEssentialTrueDofs(ess_attr, ess_tdof_list);
|
|
|
|
ParGridFunction x(&pfes);
|
|
x = 0.0;
|
|
x.ProjectBdrCoefficient(exsol, ess_attr);
|
|
|
|
// assemble the linear form
|
|
ParLinearForm lf(&pfes);
|
|
lf.AddDomainIntegrator(new DomainLFIntegrator(rhs));
|
|
lf.Assemble();
|
|
|
|
// assemble the bilinear form.
|
|
ParBilinearForm bf(&pfes);
|
|
bf.AddDomainIntegrator(new DiffusionIntegrator());
|
|
bf.Assemble();
|
|
|
|
OperatorPtr A;
|
|
Vector B, X;
|
|
bf.FormLinearSystem(ess_tdof_list, x, lf, A, X, B);
|
|
|
|
// solve
|
|
HypreBoomerAMG prec;
|
|
CGSolver cg(pfes.GetComm());
|
|
cg.SetRelTol(1e-30);
|
|
cg.SetMaxIter(100);
|
|
cg.SetPrintLevel(1);
|
|
cg.SetPreconditioner(prec);
|
|
cg.SetOperator(*A);
|
|
cg.Mult(B, X);
|
|
|
|
bf.RecoverFEMSolution(X, lf, x);
|
|
|
|
// compute L2 error from the exact solution
|
|
const real_t error = x.ComputeL2Error(exsol);
|
|
REQUIRE(error == MFEM_Approx(0.0));
|
|
}
|
|
|
|
void TestSolveSerial1(const Mesh & mesh, GridFunction & x)
|
|
{
|
|
FiniteElementSpace *fespace = x.FESpace();
|
|
|
|
Array<int> ess_attr(mesh.bdr_attributes.Max());
|
|
ess_attr = 1; // Dirichlet BC everywhere
|
|
|
|
Array<int> ess_tdof_list;
|
|
fespace->GetEssentialTrueDofs(ess_attr, ess_tdof_list);
|
|
|
|
// assemble the linear form
|
|
LinearForm lf(fespace);
|
|
ConstantCoefficient one(1.0);
|
|
lf.AddDomainIntegrator(new DomainLFIntegrator(one));
|
|
lf.Assemble();
|
|
|
|
// assemble the bilinear form.
|
|
BilinearForm bf(fespace);
|
|
bf.SetDiagonalPolicy(Operator::DIAG_ONE);
|
|
|
|
bf.AddDomainIntegrator(new DiffusionIntegrator());
|
|
bf.Assemble();
|
|
|
|
OperatorPtr A;
|
|
Vector B, X;
|
|
bf.FormLinearSystem(ess_tdof_list, x, lf, A, X, B);
|
|
|
|
GSSmoother M((SparseMatrix&)(*A));
|
|
PCG(*A, M, B, X, 10, 500, 1e-30, 0.0);
|
|
std::cout << std::flush;
|
|
|
|
bf.RecoverFEMSolution(X, lf, x);
|
|
}
|
|
|
|
void TestSolveParallel1(ParMesh &pmesh, ParGridFunction &x)
|
|
{
|
|
ParFiniteElementSpace *pfes = x.ParFESpace();
|
|
|
|
Array<int> ess_attr(pmesh.bdr_attributes.Max());
|
|
ess_attr = 1; // Dirichlet BC
|
|
|
|
Array<int> ess_tdof_list;
|
|
pfes->GetEssentialTrueDofs(ess_attr, ess_tdof_list);
|
|
|
|
// assemble the linear form
|
|
ParLinearForm lf(pfes);
|
|
ConstantCoefficient one(1.0);
|
|
lf.AddDomainIntegrator(new DomainLFIntegrator(one));
|
|
lf.Assemble();
|
|
|
|
// assemble the bilinear form.
|
|
ParBilinearForm bf(pfes);
|
|
bf.AddDomainIntegrator(new DiffusionIntegrator());
|
|
bf.Assemble();
|
|
|
|
OperatorPtr A;
|
|
Vector B, X;
|
|
bf.FormLinearSystem(ess_tdof_list, x, lf, A, X, B);
|
|
|
|
HypreBoomerAMG prec;
|
|
CGSolver cg(MPI_COMM_WORLD);
|
|
cg.SetRelTol(1e-30);
|
|
cg.SetMaxIter(100);
|
|
cg.SetPrintLevel(10);
|
|
cg.SetPreconditioner(prec);
|
|
cg.SetOperator(*A);
|
|
cg.Mult(B, X);
|
|
|
|
bf.RecoverFEMSolution(X, lf, x);
|
|
}
|
|
|
|
GridFunction *TestRandomPRefinement_serial(Mesh & mesh)
|
|
{
|
|
// standard H1 space with order 1 elements
|
|
auto *fec = new H1_FECollection(1, mesh.Dimension());
|
|
auto *fespace = new FiniteElementSpace(&mesh, fec);
|
|
|
|
for (int i=0; i<mesh.GetNE(); ++i)
|
|
{
|
|
const int p = mesh.GetAttribute(i);
|
|
if (p > 1) { fespace->SetElementOrder(i, p); }
|
|
}
|
|
|
|
fespace->Update(false);
|
|
|
|
auto *sol = new GridFunction(fespace);
|
|
sol->MakeOwner(fec);
|
|
*sol = 0.0; // Essential DOF value
|
|
TestSolveSerial1(mesh, *sol);
|
|
return sol;
|
|
}
|
|
|
|
ParGridFunction *TestRandomPRefinement_parallel(Mesh & mesh)
|
|
{
|
|
// standard H1 space with order 1 elements
|
|
|
|
auto *pmsh = new ParMesh(MPI_COMM_WORLD, mesh);
|
|
auto *pfec = new H1_FECollection(1, mesh.Dimension());
|
|
auto *pfes = new ParFiniteElementSpace(pmsh, pfec);
|
|
|
|
for (int i=0; i<pmsh->GetNE(); ++i)
|
|
{
|
|
const int p = pmsh->GetAttribute(i);
|
|
if (p > 1) { pfes->SetElementOrder(i, p); }
|
|
}
|
|
|
|
pfes->Update(false);
|
|
|
|
auto *sol = new ParGridFunction(pfes);
|
|
sol->MakeOwner(pfec);
|
|
*sol = 0.0; // Essential DOF value
|
|
TestSolveParallel1(*pmsh, *sol);
|
|
return sol;
|
|
}
|
|
|
|
// This function is based on the assumption that each element has attribute
|
|
// equal to its index in the serial mesh. This assumption enables easily
|
|
// identifying serial and parallel elements, for element-wise comparisons.
|
|
real_t ErrorSerialParallel(const GridFunction & xser,
|
|
const ParGridFunction & xpar)
|
|
{
|
|
const FiniteElementSpace *fespace = xser.FESpace();
|
|
const ParFiniteElementSpace *pfespace = xpar.ParFESpace();
|
|
|
|
Mesh *mesh = fespace->GetMesh();
|
|
ParMesh *pmesh = pfespace->GetParMesh();
|
|
|
|
const int npe = pmesh->GetNE();
|
|
|
|
int numprocs, rank;
|
|
MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
|
|
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
|
|
|
|
Array<int> allnpe(numprocs);
|
|
MPI_Allgather(&npe, 1, MPI_INT, allnpe.GetData(), 1, MPI_INT, MPI_COMM_WORLD);
|
|
|
|
int eos = 0;
|
|
for (int i=0; i<rank; ++i)
|
|
{
|
|
eos += allnpe[i];
|
|
}
|
|
|
|
bool elemsMatch = true;
|
|
real_t error = 0.0;
|
|
|
|
xser.HostRead();
|
|
xpar.HostRead();
|
|
|
|
// Loop over only the local elements in the parallel mesh.
|
|
for (int e=0; e<pmesh->GetNE(); ++e)
|
|
{
|
|
if (pmesh->GetAttribute(e) != mesh->GetAttribute(eos + e))
|
|
{
|
|
elemsMatch = false;
|
|
}
|
|
|
|
Array<int> sdofs, pdofs;
|
|
|
|
fespace->GetElementDofs(eos + e, sdofs);
|
|
pfespace->GetElementDofs(e, pdofs);
|
|
|
|
if (sdofs.Size() != pdofs.Size())
|
|
{
|
|
elemsMatch = false;
|
|
}
|
|
|
|
for (int i=0; i<sdofs.Size(); ++i)
|
|
{
|
|
const real_t d = xser[sdofs[i]] - xpar[pdofs[i]];
|
|
error += d * d;
|
|
}
|
|
}
|
|
|
|
REQUIRE(elemsMatch);
|
|
|
|
MPI_Allreduce(MPI_IN_PLACE, &error, 1, MPITypeMap<real_t>::mpi_type,
|
|
MPI_SUM, MPI_COMM_WORLD);
|
|
return error;
|
|
}
|
|
|
|
real_t CheckH1Continuity(ParGridFunction & x)
|
|
{
|
|
x.ExchangeFaceNbrData();
|
|
|
|
const ParFiniteElementSpace *fes = x.ParFESpace();
|
|
ParMesh *mesh = fes->GetParMesh();
|
|
|
|
const int dim = mesh->Dimension();
|
|
|
|
// Following the example of KellyErrorEstimator::ComputeEstimates(),
|
|
// we loop over interior faces and then shared faces.
|
|
|
|
// Compute error contribution from local interior faces
|
|
real_t errorMax = 0.0;
|
|
for (int f = 0; f < mesh->GetNumFaces(); f++)
|
|
{
|
|
if (mesh->FaceIsInterior(f))
|
|
{
|
|
int Inf1, Inf2, NCFace;
|
|
mesh->GetFaceInfos(f, &Inf1, &Inf2, &NCFace);
|
|
|
|
auto FT = mesh->GetFaceElementTransformations(f);
|
|
|
|
const int faceOrder = dim == 3 ? fes->GetFaceOrder(f) :
|
|
fes->GetEdgeOrder(f);
|
|
auto &int_rule = IntRules.Get(FT->FaceGeom, 2 * faceOrder);
|
|
const auto nip = int_rule.GetNPoints();
|
|
|
|
// Convention
|
|
// * Conforming face: Face side with smaller element id handles
|
|
// the integration
|
|
// * Non-conforming face: The slave handles the integration.
|
|
// See FaceInfo documentation for details.
|
|
bool isNCSlave = FT->Elem2No >= 0 && NCFace >= 0;
|
|
bool isConforming = FT->Elem2No >= 0 && NCFace == -1;
|
|
if ((FT->Elem1No < FT->Elem2No && isConforming) || isNCSlave)
|
|
{
|
|
for (int i = 0; i < nip; i++)
|
|
{
|
|
const auto &fip = int_rule.IntPoint(i);
|
|
IntegrationPoint ip;
|
|
|
|
FT->Loc1.Transform(fip, ip);
|
|
const real_t v1 = x.GetValue(FT->Elem1No, ip);
|
|
|
|
FT->Loc2.Transform(fip, ip);
|
|
const real_t v2 = x.GetValue(FT->Elem2No, ip);
|
|
|
|
const real_t err_i = std::abs(v1 - v2);
|
|
errorMax = std::max(errorMax, err_i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compute error contribution from shared interior faces
|
|
for (int sf = 0; sf < mesh->GetNSharedFaces(); sf++)
|
|
{
|
|
const int f = mesh->GetSharedFace(sf);
|
|
const bool trueInterior = mesh->FaceIsTrueInterior(f);
|
|
if (!trueInterior) { continue; }
|
|
|
|
auto FT = mesh->GetSharedFaceTransformations(sf, true);
|
|
const int faceOrder = dim == 3 ? fes->GetFaceOrder(f) : fes->GetEdgeOrder(f);
|
|
const auto &int_rule = IntRules.Get(FT->FaceGeom, 2 * faceOrder);
|
|
const auto nip = int_rule.GetNPoints();
|
|
|
|
for (int i = 0; i < nip; i++)
|
|
{
|
|
const auto &fip = int_rule.IntPoint(i);
|
|
IntegrationPoint ip;
|
|
|
|
FT->Loc1.Transform(fip, ip);
|
|
const real_t v1 = x.GetValue(FT->Elem1No, ip);
|
|
|
|
FT->Loc2.Transform(fip, ip);
|
|
const real_t v2 = x.GetValue(FT->Elem2No, ip);
|
|
|
|
const real_t err_i = std::abs(v1 - v2);
|
|
errorMax = std::max(errorMax, err_i);
|
|
}
|
|
}
|
|
|
|
return errorMax;
|
|
}
|
|
|
|
static void TestRandomPRefinement(Mesh & mesh)
|
|
{
|
|
for (int i=0; i<mesh.GetNE(); ++i)
|
|
{
|
|
mesh.SetAttribute(i, 1 + (i % 3)); // Order is 1, 2, or 3
|
|
}
|
|
mesh.EnsureNCMesh();
|
|
|
|
GridFunction *solSerial = TestRandomPRefinement_serial(mesh);
|
|
ParGridFunction *solParallel = TestRandomPRefinement_parallel(mesh);
|
|
const real_t error = ErrorSerialParallel(*solSerial, *solParallel);
|
|
REQUIRE(error == MFEM_Approx(0.0));
|
|
|
|
// Check H1 continuity for the parallel solution.
|
|
const real_t discontinuity = CheckH1Continuity(*solParallel);
|
|
REQUIRE(discontinuity == MFEM_Approx(0.0));
|
|
|
|
delete solParallel->ParFESpace()->GetParMesh();
|
|
delete solSerial;
|
|
delete solParallel;
|
|
}
|
|
|
|
static void TestSolveParVec(ParFiniteElementSpace &fespace)
|
|
{
|
|
ParMesh *pmesh = fespace.GetParMesh();
|
|
const int sdim = pmesh->SpaceDimension();
|
|
|
|
// Exact solution and RHS for the mass-matrix problem E = f
|
|
VectorFunctionCoefficient exsol(sdim, exact_sln_vec);
|
|
|
|
// No boundary conditions
|
|
Array<int> ess_attr(pmesh->bdr_attributes.Max());
|
|
ess_attr = 0;
|
|
|
|
Array<int> ess_tdof_list;
|
|
fespace.GetEssentialTrueDofs(ess_attr, ess_tdof_list);
|
|
|
|
ParGridFunction x(&fespace);
|
|
x = 0.0;
|
|
x.ProjectBdrCoefficient(exsol, ess_attr);
|
|
|
|
// Assemble the linear form
|
|
ParLinearForm lf(&fespace);
|
|
lf.AddDomainIntegrator(new VectorFEDomainLFIntegrator(exsol));
|
|
lf.Assemble();
|
|
|
|
// Assemble the bilinear form
|
|
ParBilinearForm bf(&fespace);
|
|
bf.AddDomainIntegrator(new VectorFEMassIntegrator());
|
|
bf.Assemble();
|
|
|
|
OperatorPtr A;
|
|
Vector B, X;
|
|
bf.FormLinearSystem(ess_tdof_list, x, lf, A, X, B);
|
|
|
|
// Solve
|
|
HypreBoomerAMG prec;
|
|
CGSolver cg(MPI_COMM_WORLD);
|
|
cg.SetRelTol(1e-30);
|
|
cg.SetMaxIter(100);
|
|
cg.SetPrintLevel(1);
|
|
cg.SetPreconditioner(prec);
|
|
cg.SetOperator(*A);
|
|
cg.Mult(B, X);
|
|
|
|
bf.RecoverFEMSolution(X, lf, x);
|
|
|
|
// Compute L2 error from the exact solution
|
|
const real_t error = x.ComputeL2Error(exsol);
|
|
REQUIRE(error == MFEM_Approx(0.0));
|
|
}
|
|
|
|
#endif // MFEM_USE_MPI
|
|
|
|
}
|