229 lines
6.8 KiB
C++
229 lines
6.8 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
|
|
{
|
|
#ifdef MFEM_USE_MPI
|
|
|
|
TEST_CASE("ParMeshGlobalIndices", "[Parallel], [ParMesh]")
|
|
{
|
|
const int ne = 5;
|
|
|
|
for (int dimension = 1; dimension < 4; ++dimension)
|
|
{
|
|
for (int amr=0; amr < 1 + (dimension > 1); ++amr)
|
|
{
|
|
Mesh mesh;
|
|
if (dimension == 1)
|
|
{
|
|
mesh = Mesh::MakeCartesian1D(ne, 1.0);
|
|
}
|
|
else if (dimension == 2)
|
|
{
|
|
if (amr)
|
|
{
|
|
const char *mesh_file = "../../data/amr-quad.mesh";
|
|
mesh = Mesh::LoadFromFile(mesh_file, 1, 1);
|
|
}
|
|
else
|
|
{
|
|
mesh = Mesh::MakeCartesian2D(ne, ne, Element::QUADRILATERAL, 1, 1.0, 1.0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (amr)
|
|
{
|
|
const char *mesh_file = "../../data/amr-hex.mesh";
|
|
mesh = Mesh::LoadFromFile(mesh_file, 1, 1);
|
|
}
|
|
else
|
|
{
|
|
mesh = Mesh::MakeCartesian3D(ne, ne, ne, Element::HEXAHEDRON, 1.0, 1.0, 1.0);
|
|
}
|
|
}
|
|
|
|
|
|
ParMesh pmesh(MPI_COMM_WORLD, mesh);
|
|
|
|
int globalN = 0;
|
|
|
|
enum EntityType { VERTEX, EDGE, FACE, ELEMENT };
|
|
// Loop over all types of mesh entities
|
|
for (int e=EntityType::VERTEX; e<=EntityType::ELEMENT; ++e)
|
|
{
|
|
if (amr && dimension > 1 && e != EntityType::ELEMENT)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Array<HYPRE_BigInt> gi;
|
|
|
|
switch (e)
|
|
{
|
|
case EntityType::VERTEX:
|
|
globalN = mesh.GetNV();
|
|
pmesh.GetGlobalVertexIndices(gi);
|
|
break;
|
|
case EntityType::EDGE:
|
|
globalN = dimension == 1 ? mesh.GetNV() : mesh.GetNEdges();
|
|
pmesh.GetGlobalEdgeIndices(gi);
|
|
break;
|
|
case EntityType::FACE:
|
|
globalN = mesh.GetNumFaces();
|
|
pmesh.GetGlobalFaceIndices(gi);
|
|
break;
|
|
case EntityType::ELEMENT:
|
|
globalN = mesh.GetNE();
|
|
pmesh.GetGlobalElementIndices(gi);
|
|
break;
|
|
}
|
|
|
|
// Verify that the local entities do not share a global index.
|
|
{
|
|
std::set<HYPRE_BigInt> localGI;
|
|
for (int i=0; i<gi.Size(); ++i)
|
|
{
|
|
localGI.insert(gi[i]);
|
|
}
|
|
|
|
REQUIRE(localGI.size() == (std::size_t) gi.Size());
|
|
}
|
|
|
|
// Verify that the global indices range from 0 to globalN-1.
|
|
{
|
|
const HYPRE_BigInt localMin = gi.Size() > 0 ? gi.Min() :
|
|
std::numeric_limits<HYPRE_BigInt>::max();
|
|
const HYPRE_BigInt localMax = gi.Size() > 0 ? gi.Max() :
|
|
std::numeric_limits<HYPRE_BigInt>::min();
|
|
|
|
HYPRE_BigInt globalMin, globalMax;
|
|
MPI_Allreduce(&localMin, &globalMin, 1, HYPRE_MPI_BIG_INT, MPI_MIN,
|
|
MPI_COMM_WORLD);
|
|
MPI_Allreduce(&localMax, &globalMax, 1, HYPRE_MPI_BIG_INT, MPI_MAX,
|
|
MPI_COMM_WORLD);
|
|
|
|
REQUIRE((globalMin == 0 && globalMax == globalN-1));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("ParMeshSharedFaces", "[Parallel], [ParMesh]")
|
|
{
|
|
const char *mesh_file = "../../data/fichera-amr.mesh";
|
|
|
|
Mesh mesh(mesh_file);
|
|
ParMesh pmesh(MPI_COMM_WORLD, mesh);
|
|
pmesh.ExchangeFaceNbrData();
|
|
|
|
const int nshared = pmesh.GetNSharedFaces();
|
|
int local_ghosts_nonmatching = 0;
|
|
for (int sf = 0; sf < nshared; sf++)
|
|
{
|
|
const int f = pmesh.GetSharedFace(sf);
|
|
FaceElementTransformations *ftr =
|
|
pmesh.GetSharedFaceTransformationsByLocalIndex(f, false);
|
|
if (f != ftr->ElementNo) { local_ghosts_nonmatching++; }
|
|
}
|
|
|
|
int global_ghosts_nonmatching = 0;
|
|
MPI_Allreduce(&local_ghosts_nonmatching, &global_ghosts_nonmatching, 1, MPI_INT,
|
|
MPI_SUM, pmesh.GetComm());
|
|
|
|
REQUIRE(global_ghosts_nonmatching == 0);
|
|
}
|
|
|
|
namespace simplicial
|
|
{
|
|
|
|
double exact(const Vector &xvec)
|
|
{
|
|
// The exact solution is linear and is harmonic
|
|
return xvec[0] + xvec[1] + xvec[2];
|
|
}
|
|
|
|
void SolveDiffusionProblem(ParMesh &mesh, Vector &x_out)
|
|
{
|
|
H1_FECollection fec(1, mesh.Dimension());
|
|
ParFiniteElementSpace fes(&mesh, &fec);
|
|
|
|
// Right-hand side is zero since exact solution is harmonic
|
|
ParLinearForm b(&fes);
|
|
b.Assemble();
|
|
|
|
ParBilinearForm a(&fes);
|
|
a.AddDomainIntegrator(new DiffusionIntegrator);
|
|
a.Assemble();
|
|
|
|
Array<int> ess_tdof_list, ess_bdr;
|
|
if (mesh.bdr_attributes.Size())
|
|
{
|
|
ess_bdr.SetSize(mesh.bdr_attributes.Max());
|
|
ess_bdr = 1;
|
|
fes.GetEssentialTrueDofs(ess_bdr, ess_tdof_list);
|
|
}
|
|
|
|
// Use the exact solution as boundary conditions
|
|
ParGridFunction x(&fes);
|
|
FunctionCoefficient exact_coeff(exact);
|
|
x.ProjectBdrCoefficient(exact_coeff, ess_bdr);
|
|
|
|
OperatorPtr A;
|
|
Vector B, X;
|
|
a.FormLinearSystem(ess_tdof_list, x, b, A, X, B);
|
|
|
|
CGSolver cg(MPI_COMM_WORLD);
|
|
cg.SetRelTol(1e-12);
|
|
cg.SetMaxIter(2000);
|
|
cg.SetPrintLevel(1);
|
|
cg.SetOperator(*A);
|
|
// X = 0.0;
|
|
cg.Mult(B, X);
|
|
x_out = X;
|
|
}
|
|
|
|
}
|
|
|
|
TEST_CASE("ParMeshMakeSimplicial", "[Parallel], [ParMesh]")
|
|
{
|
|
// Test that the parallel mesh obtained by ParMesh::MakeSimplicial is valid.
|
|
// This test solves a Poisson problem on a 3x3x3 hex mesh, and on the tet
|
|
// mesh obtained by splitting the hexes into tets. The finite element space
|
|
// is linear in both cases, and the exact solution is also linear, so it will
|
|
// be recovered exactly in both cases. The vertices of both meshes are the
|
|
// same, so we check that the resulting discrete solutions are identical up
|
|
// to solver tolerance.
|
|
|
|
Mesh mesh = Mesh::MakeCartesian3D(3, 3, 3, Element::HEXAHEDRON);
|
|
if (GENERATE(false,true))
|
|
{
|
|
mesh.SetCurvature(2);
|
|
}
|
|
ParMesh pmesh(MPI_COMM_WORLD, mesh);
|
|
ParMesh pmesh_tet = ParMesh::MakeSimplicial(pmesh);
|
|
|
|
Vector x, x_tet;
|
|
simplicial::SolveDiffusionProblem(pmesh, x);
|
|
simplicial::SolveDiffusionProblem(pmesh_tet, x_tet);
|
|
|
|
x -= x_tet;
|
|
REQUIRE(x.Normlinf() == MFEM_Approx(0.0));
|
|
}
|
|
|
|
#endif // MFEM_USE_MPI
|
|
|
|
} // namespace mfem
|