Files
mfem/tests/unit/mesh/test_ncmesh.cpp
T

2896 lines
98 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 "mesh_test_utils.hpp"
#include "unit_tests.hpp"
#include <array>
namespace mfem
{
constexpr real_t EPS = 1e-10;
// Test case: Verify that a conforming mesh yields the same norm for the
// assembled diagonal with PA when using the standard (conforming)
// Mesh vs. the corresponding (non-conforming) NCMesh. (note:
// permutations of the values in the diagonal are expected)
TEST_CASE("NCMesh PA diagonal", "[NCMesh]")
{
SECTION("Quad mesh")
{
int ne = 2;
Mesh mesh = Mesh::MakeCartesian2D(
ne, ne, Element::QUADRILATERAL, 1, 1.0, 1.0);
Mesh nc_mesh = Mesh::MakeCartesian2D(
ne, ne, Element::QUADRILATERAL, 1, 1.0, 1.0);
nc_mesh.EnsureNCMesh();
mesh.UniformRefinement();
nc_mesh.UniformRefinement();
int dim = 2;
for (int order = 1; order <= 3; ++order)
{
ND_FECollection fec(order, dim);
FiniteElementSpace fes(&mesh, &fec);
FiniteElementSpace nc_fes(&nc_mesh, &fec);
BilinearForm a(&fes);
BilinearForm nc_a(&nc_fes);
a.SetAssemblyLevel(AssemblyLevel::PARTIAL);
nc_a.SetAssemblyLevel(AssemblyLevel::PARTIAL);
ConstantCoefficient coef(1.0);
a.AddDomainIntegrator(new CurlCurlIntegrator(coef));
nc_a.AddDomainIntegrator(new CurlCurlIntegrator(coef));
a.Assemble();
nc_a.Assemble();
Vector diag(fes.GetTrueVSize());
Vector nc_diag(nc_fes.GetTrueVSize());
a.AssembleDiagonal(diag);
nc_a.AssembleDiagonal(nc_diag);
real_t error = fabs(diag.Norml2() - nc_diag.Norml2());
CAPTURE(order, error);
REQUIRE(error == MFEM_Approx(0.0, EPS));
}
}
SECTION("Hexa mesh")
{
int ne = 2;
Mesh mesh = Mesh::MakeCartesian3D(
ne, ne, ne, Element::HEXAHEDRON, 1.0, 1.0, 1.0);
Mesh nc_mesh = Mesh::MakeCartesian3D(
ne, ne, ne, Element::HEXAHEDRON, 1.0, 1.0, 1.0);
nc_mesh.EnsureNCMesh();
mesh.UniformRefinement();
nc_mesh.UniformRefinement();
int dim = 3;
for (int order = 1; order <= 3; ++order)
{
ND_FECollection fec(order, dim);
FiniteElementSpace fes(&mesh, &fec);
FiniteElementSpace nc_fes(&nc_mesh, &fec);
BilinearForm a(&fes);
BilinearForm nc_a(&nc_fes);
a.SetAssemblyLevel(AssemblyLevel::PARTIAL);
nc_a.SetAssemblyLevel(AssemblyLevel::PARTIAL);
ConstantCoefficient coef(1.0);
a.AddDomainIntegrator(new CurlCurlIntegrator(coef));
nc_a.AddDomainIntegrator(new CurlCurlIntegrator(coef));
a.Assemble();
nc_a.Assemble();
Vector diag(fes.GetTrueVSize());
Vector nc_diag(nc_fes.GetTrueVSize());
a.AssembleDiagonal(diag);
nc_a.AssembleDiagonal(nc_diag);
real_t error = fabs(diag.Sum() - nc_diag.Sum());
CAPTURE(order, error);
REQUIRE(error == MFEM_Approx(0.0, EPS));
}
}
} // test case
TEST_CASE("NCMesh 3D Refined Volume", "[NCMesh]")
{
auto mesh_fname = GENERATE("../../data/ref-tetrahedron.mesh",
"../../data/ref-cube.mesh",
"../../data/ref-prism.mesh",
"../../data/ref-pyramid.mesh"
);
auto ref_type = GENERATE(Refinement::X,
Refinement::Y,
Refinement::Z,
Refinement::XY,
Refinement::XZ,
Refinement::YZ,
Refinement::XYZ);
const real_t scale = GENERATE(0.5, 0.25); // Only affects hex mesh so far
if (scale != 0.5 && std::strcmp(mesh_fname, "../../data/ref-cube.mesh") != 0)
{
return;
}
Mesh mesh(mesh_fname, 1, 1);
mesh.EnsureNCMesh(true);
real_t original_volume = mesh.GetElementVolume(0);
Array<Refinement> ref(1);
ref[0].Set(0, ref_type, scale);
mesh.GeneralRefinement(ref, 1);
real_t summed_volume = 0.0;
for (int i = 0; i < mesh.GetNE(); ++i)
{
summed_volume += mesh.GetElementVolume(i);
}
REQUIRE(summed_volume == MFEM_Approx(original_volume));
} // test case
TEST_CASE("NCMesh 3D Derefined Volume", "[NCMesh]")
{
auto mesh_fname = GENERATE("../../data/ref-tetrahedron.mesh",
"../../data/ref-cube.mesh",
"../../data/ref-prism.mesh",
"../../data/ref-pyramid.mesh"
);
auto ref_type = GENERATE(Refinement::XYZ);
Mesh mesh(mesh_fname, 1, 1);
mesh.EnsureNCMesh(true);
real_t original_volume = mesh.GetElementVolume(0);
Array<Refinement> ref(1);
ref[0].Set(0, ref_type);
mesh.GeneralRefinement(ref, 1);
Array<real_t> elem_error(mesh.GetNE());
for (int i = 0; i < mesh.GetNE(); ++i)
{
elem_error[i] = 0.0;
}
mesh.DerefineByError(elem_error, 1.0);
real_t derefined_volume = mesh.GetElementVolume(0);
REQUIRE(derefined_volume == MFEM_Approx(original_volume));
} // test case
#ifdef MFEM_USE_MPI
// Test case: Verify that a conforming mesh yields the same norm for the
// assembled diagonal with PA when using the standard (conforming)
// Mesh vs. the corresponding (non-conforming) NCMesh. (note:
// permutations of the values in the diagonal are expected)
TEST_CASE("pNCMesh PA diagonal", "[Parallel], [NCMesh]")
{
int rank;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
SECTION("Quad pmesh")
{
int ne = 2;
Mesh mesh = Mesh::MakeCartesian2D(
ne, ne, Element::QUADRILATERAL, 1, 1.0, 1.0);
Mesh nc_mesh = Mesh::MakeCartesian2D(
ne, ne, Element::QUADRILATERAL, 1, 1.0, 1.0);
nc_mesh.EnsureNCMesh();
mesh.UniformRefinement();
nc_mesh.UniformRefinement();
ParMesh pmesh(MPI_COMM_WORLD, mesh);
ParMesh nc_pmesh(MPI_COMM_WORLD, nc_mesh);
int dim = 2;
for (int order = 1; order <= 3; ++order)
{
ND_FECollection fec(order, dim);
ParFiniteElementSpace pfes(&pmesh, &fec);
ParFiniteElementSpace nc_pfes(&nc_pmesh, &fec);
ParBilinearForm a(&pfes);
ParBilinearForm nc_a(&nc_pfes);
a.SetAssemblyLevel(AssemblyLevel::PARTIAL);
nc_a.SetAssemblyLevel(AssemblyLevel::PARTIAL);
ConstantCoefficient coef(1.0);
a.AddDomainIntegrator(new CurlCurlIntegrator(coef));
nc_a.AddDomainIntegrator(new CurlCurlIntegrator(coef));
a.Assemble();
nc_a.Assemble();
Vector diag(pfes.GetTrueVSize());
Vector nc_diag(nc_pfes.GetTrueVSize());
a.AssembleDiagonal(diag);
nc_a.AssembleDiagonal(nc_diag);
real_t diag_lsum = diag.Sum(), nc_diag_lsum = nc_diag.Sum();
real_t diag_gsum = 0.0, nc_diag_gsum = 0.0;
MPI_Allreduce(&diag_lsum, &diag_gsum, 1,
MPITypeMap<real_t>::mpi_type, MPI_SUM, MPI_COMM_WORLD);
MPI_Allreduce(&nc_diag_lsum, &nc_diag_gsum, 1,
MPITypeMap<real_t>::mpi_type, MPI_SUM, MPI_COMM_WORLD);
real_t error = fabs(diag_gsum - nc_diag_gsum);
CAPTURE(order, error);
REQUIRE(error == MFEM_Approx(0.0, EPS));
MPI_Barrier(MPI_COMM_WORLD);
}
}
SECTION("Hexa pmesh")
{
int ne = 2;
Mesh mesh = Mesh::MakeCartesian3D(
ne, ne, ne, Element::HEXAHEDRON, 1.0, 1.0, 1.0);
Mesh nc_mesh = Mesh::MakeCartesian3D(
ne, ne, ne, Element::HEXAHEDRON, 1.0, 1.0, 1.0);
nc_mesh.EnsureNCMesh();
mesh.UniformRefinement();
nc_mesh.UniformRefinement();
ParMesh pmesh(MPI_COMM_WORLD, mesh);
ParMesh nc_pmesh(MPI_COMM_WORLD, nc_mesh);
int dim = 3;
for (int order = 1; order <= 3; ++order)
{
ND_FECollection fec(order, dim);
ParFiniteElementSpace pfes(&pmesh, &fec);
ParFiniteElementSpace nc_pfes(&nc_pmesh, &fec);
ParBilinearForm a(&pfes);
ParBilinearForm nc_a(&nc_pfes);
a.SetAssemblyLevel(AssemblyLevel::PARTIAL);
nc_a.SetAssemblyLevel(AssemblyLevel::PARTIAL);
ConstantCoefficient coef(1.0);
a.AddDomainIntegrator(new CurlCurlIntegrator(coef));
nc_a.AddDomainIntegrator(new CurlCurlIntegrator(coef));
a.Assemble();
nc_a.Assemble();
Vector diag(pfes.GetTrueVSize());
Vector nc_diag(nc_pfes.GetTrueVSize());
a.AssembleDiagonal(diag);
nc_a.AssembleDiagonal(nc_diag);
real_t diag_lsum = diag.Sum(), nc_diag_lsum = nc_diag.Sum();
real_t diag_gsum = 0.0, nc_diag_gsum = 0.0;
MPI_Allreduce(&diag_lsum, &diag_gsum, 1, MPITypeMap<real_t>::mpi_type,
MPI_SUM, MPI_COMM_WORLD);
MPI_Allreduce(&nc_diag_lsum, &nc_diag_gsum, 1,
MPITypeMap<real_t>::mpi_type, MPI_SUM, MPI_COMM_WORLD);
CAPTURE(order, diag_gsum, nc_diag_gsum);
REQUIRE(nc_diag_gsum == MFEM_Approx(diag_gsum));
MPI_Barrier(MPI_COMM_WORLD);
}
}
} // test case
TEST_CASE("EdgeFaceConstraint", "[Parallel], [NCMesh]")
{
auto exact_soln = [](const Vector& x)
{
// sin(|| x - d ||^2) -> non polynomial but very smooth.
Vector d(3);
d[0] = -0.5; d[1] = -1; d[2] = -2; // arbitrary
d -= x;
return std::sin(d * d);
};
SECTION("ReferenceTet")
{
constexpr int refining_rank = 0;
auto smesh = Mesh("../../data/ref-tetrahedron.mesh");
REQUIRE(smesh.GetNE() == 1);
{
// Start the test with two tetrahedra attached by triangle.
auto single_edge_refine = Array<Refinement>(1);
single_edge_refine[0].Set(0, Refinement::X);
smesh.GeneralRefinement(single_edge_refine, 0); // conformal
}
REQUIRE(smesh.GetNE() == 2);
smesh.EnsureNCMesh(true);
smesh.Finalize();
auto partition = std::unique_ptr<int[]>(new int[smesh.GetNE()]);
partition[0] = 0;
partition[1] = Mpi::WorldSize() > 1 ? 1 : 0;
auto pmesh = ParMesh(MPI_COMM_WORLD, smesh, partition.get());
// Construct the NC refined mesh in parallel and serial. Once constructed
// a global L2 projected solution should match exactly on each.
Array<int> refines, serial_refines(1);
if (Mpi::WorldRank() == refining_rank)
{
refines.Append(0);
}
// Must be called on all ranks as it uses MPI calls internally. All ranks
// will use the global element number dictated by rank 0 though.
serial_refines[0] = pmesh.GetGlobalElementNum(0);
MPI_Bcast(&serial_refines[0], 1, MPI_INT, refining_rank, MPI_COMM_WORLD);
// Rank 0 refines the parallel mesh, all ranks refine the serial mesh
smesh.GeneralRefinement(serial_refines, 1); // nonconformal
pmesh.GeneralRefinement(refines, 1); // nonconformal
REQUIRE(pmesh.GetGlobalNE() == 8 + 1);
REQUIRE(smesh.GetNE() == 8 + 1);
// Each pair of indices here represents sequential element indices to
// refine. First the i element is refined, then in the resulting mesh the
// j element is refined. These pairs were arrived at by looping over all
// possible i,j pairs and checking for the addition of a face-edge
// constraint.
std::vector<std::pair<int,int>> indices{{2,13}, {3,13}, {6,2}, {6,3}};
// Rank 0 has all but one element in the parallel mesh. The remaining
// element is owned by another processor if the number of ranks is greater
// than one.
for (const auto &ij : indices)
{
int i = ij.first;
int j = ij.second;
if (Mpi::WorldRank() == refining_rank)
{
refines[0] = i;
}
// Inform all ranks of the serial mesh
serial_refines[0] = pmesh.GetGlobalElementNum(i);
MPI_Bcast(&serial_refines[0], 1, MPI_INT, 0, MPI_COMM_WORLD);
ParMesh tmp(pmesh);
tmp.GeneralRefinement(refines);
REQUIRE(tmp.GetGlobalNE() == 1 + 8 - 1 + 8); // 16 elements
Mesh stmp(smesh);
stmp.GeneralRefinement(serial_refines);
REQUIRE(stmp.GetNE() == 1 + 8 - 1 + 8); // 16 elements
if (Mpi::WorldRank() == refining_rank)
{
refines[0] = j;
}
// Inform all ranks of the serial mesh
serial_refines[0] = tmp.GetGlobalElementNum(j);
MPI_Bcast(&serial_refines[0], 1, MPI_INT, 0, MPI_COMM_WORLD);
ParMesh ttmp(tmp);
ttmp.GeneralRefinement(refines);
REQUIRE(ttmp.GetGlobalNE() == 1 + 8 - 1 + 8 - 1 + 8); // 23 elements
Mesh sttmp(stmp);
sttmp.GeneralRefinement(serial_refines);
REQUIRE(sttmp.GetNE() == 1 + 8 - 1 + 8 - 1 + 8); // 23 elements
// Loop over interior faces, fill and check face transform on the
// serial.
for (int iface = 0; iface < sttmp.GetNumFaces(); ++iface)
{
const auto face_transform = sttmp.GetFaceElementTransformations(iface);
CHECK(face_transform->CheckConsistency(0) < 1e-12);
}
for (int iface = 0; iface < ttmp.GetNumFacesWithGhost(); ++iface)
{
const auto face_transform = ttmp.GetFaceElementTransformations(iface);
CHECK(face_transform->CheckConsistency(0) < 1e-12);
}
// Use P4 to ensure there's a few fully interior DOF.
{
auto error = CheckL2Projection(ttmp, sttmp, 4, exact_soln);
real_t constexpr tol = 1e-9;
CHECK(std::abs(error[1] - error[0]) < tol);
}
ttmp.ExchangeFaceNbrData();
ttmp.Rebalance();
{
auto error = CheckL2Projection(ttmp, sttmp, 4, exact_soln);
real_t constexpr tol = 1e-9;
CHECK(std::abs(error[1] - error[0]) < tol);
}
}
}
auto check_serial_parallel_h1_equivalance = [](Mesh &smesh)
{
constexpr int dim = 3;
constexpr int order = 2;
H1_FECollection nd_fec(order, dim);
FiniteElementSpace fes(&smesh, &nd_fec);
const auto serial_ntdof = fes.GetTrueVSize();
ParMesh mesh(MPI_COMM_WORLD, smesh);
ParFiniteElementSpace pfes(&mesh, &nd_fec);
const auto parallel_ntdof = pfes.GlobalTrueVSize();
// If nc constraints have been observed correctly, the number of true dof
// in parallel should match the number of true dof in serial. If the
// number of parallel dofs is greater, then a slave constraint has not
// been fully labeled.
CHECK(serial_ntdof == parallel_ntdof);
};
auto check_serial_parallel_nd_equivalence = [](Mesh &smesh)
{
constexpr int dim = 3;
constexpr int order = 1;
ND_FECollection nd_fec(order, dim);
FiniteElementSpace fes(&smesh, &nd_fec);
const auto serial_ntdof = fes.GetTrueVSize();
ParMesh mesh(MPI_COMM_WORLD, smesh);
ParFiniteElementSpace pfes(&mesh, &nd_fec);
const auto parallel_ntdof = pfes.GlobalTrueVSize();
// If nc constraints have been observed correctly, the number of true dof
// in parallel should match the number of true dof in serial. If the
// number of parallel dofs is greater, then a slave constraint has not
// been fully labeled.
CHECK(serial_ntdof == parallel_ntdof);
};
SECTION("LevelTwoRefinement")
{
Mesh smesh("../../data/ref-tetrahedron.mesh");
Array<Refinement> aniso_ref(1);
aniso_ref[0].Set(0, Refinement::X);
smesh.GeneralRefinement(aniso_ref);
smesh.UniformRefinement();
smesh.EnsureNCMesh(true);
Array<int> el_to_refine(1);
for (int n = 0; n < smesh.GetNE(); n++)
{
Mesh smesh2(smesh);
el_to_refine[0] = n;
smesh2.GeneralRefinement(el_to_refine);
for (int m = 0; m < smesh2.GetNE(); m++)
{
Mesh smesh3(smesh2);
el_to_refine[0] = m;
smesh3.GeneralRefinement(el_to_refine);
CAPTURE(n,m);
check_serial_parallel_nd_equivalence(smesh3);
check_serial_parallel_h1_equivalance(smesh3);
}
}
}
SECTION("EdgeCasePartition")
{
Mesh smesh("../../data/ref-tetrahedron.mesh");
smesh.UniformRefinement();
smesh.EnsureNCMesh(true);
Array<int> el_to_refine(1);
el_to_refine[0] = 0;
smesh.GeneralRefinement(el_to_refine);
// This particular partition was found by brute force search. The default
// rebalancing can in rare cases produce similar local patterns,
// particularly for highly adapted meshes.
auto partition = std::unique_ptr<int[]>(new int[smesh.GetNE()]);
if (Mpi::WorldSize() > 1)
{
auto bad_partition = std::vector<int> {0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0};
std::copy(bad_partition.begin(), bad_partition.end(), partition.get());
}
else
{
for (int i = 0; i < smesh.GetNE(); i++)
{
partition[i] = 0;
}
}
ParMesh pmesh(MPI_COMM_WORLD, smesh, partition.get());
{
constexpr int dim = 3;
constexpr int order = 1;
ND_FECollection nd_fec(order, dim);
FiniteElementSpace fes(&smesh, &nd_fec);
const auto serial_ntdof = fes.GetTrueVSize();
ParFiniteElementSpace pfes(&pmesh, &nd_fec);
pfes.ExchangeFaceNbrData();
const auto parallel_ntdof = pfes.GlobalTrueVSize();
CHECK(serial_ntdof == parallel_ntdof);
}
for (int order = 1; order <= 4; order++)
{
CAPTURE(order);
auto error = CheckL2Projection(pmesh, smesh, order, exact_soln);
real_t constexpr tol = 1e-9;
CHECK(std::abs(error[1] - error[0]) < tol);
}
}
} // test case
TEST_CASE("P2Q1PureTetHexPri", "[Parallel], [NCMesh]")
{
auto exact_soln = [](const Vector& x)
{
// sin(|| x - d ||^2) -> non polynomial but very smooth.
Vector d(3);
d[0] = -0.5; d[1] = -1; d[2] = -2; // arbitrary
d -= x;
return std::sin(d * d);
};
auto el_type = GENERATE(Geometry::TETRAHEDRON,
Geometry::CUBE,
Geometry::PRISM);
int variant = GENERATE(0,1,2);
if (variant > 0 && el_type != Geometry::PRISM)
{
return;
}
CAPTURE(el_type, variant);
auto smesh = CylinderMesh(el_type, false, variant);
for (auto ref : {0,1,2})
{
if (ref == 1) { smesh.UniformRefinement(); }
smesh.EnsureNCMesh(true);
if (ref == 2) { smesh.UniformRefinement(); }
smesh.Finalize();
auto pmesh = ParMesh(MPI_COMM_WORLD, smesh);
// P2 ensures there are triangles without dofs
auto error = CheckL2Projection(pmesh, smesh, 2, exact_soln);
CHECK(std::abs(error[1] - error[0]) < 1e-9);
}
} // test case
TEST_CASE("PNQ2PureTetHexPri", "[Parallel], [NCMesh]")
{
auto exact_soln = [](const Vector& x)
{
// sin(|| x - d ||^2) -> non polynomial but very smooth.
Vector d(3);
d[0] = -0.5; d[1] = -1; d[2] = -2; // arbitrary
d -= x;
return std::sin(d * d);
};
auto el_type = GENERATE(Geometry::TETRAHEDRON,
Geometry::CUBE,
Geometry::PRISM);
int variant = GENERATE(0,1,2);
if (variant > 0 && el_type != Geometry::PRISM)
{
return;
}
CAPTURE(el_type, variant);
auto smesh = CylinderMesh(el_type, true);
for (auto ref : {0,1,2})
{
if (ref == 1) { smesh.UniformRefinement(); }
smesh.EnsureNCMesh(true);
if (ref == 2) { smesh.UniformRefinement(); }
smesh.Finalize();
auto pmesh = ParMesh(MPI_COMM_WORLD, smesh);
for (int p = 1; p < 3; ++p)
{
auto error = CheckL2Projection(pmesh, smesh, p, exact_soln);
CHECK(std::abs(error[1] - error[0]) < 1e-9);
}
}
} // test case
TEST_CASE("GetVectorValueInFaceNeighborElement", "[Parallel], [NCMesh]")
{
// The aim of this test is to verify the correct behaviour of the
// GetVectorValue method when called on face neighbor elements in a non
// conforming mesh.
auto smesh = Mesh("../../data/beam-tet.mesh");
for (int nc_level : {0,1,2,3})
{
for (int skip : {1,2})
{
for (bool use_ND : {false, true})
{
TestVectorValueInVolume(smesh, nc_level, skip, use_ND);
}
}
}
}
TEST_CASE("TetCornerRefines", "[Parallel], [NCMesh]")
{
auto p = GENERATE(1,2,3);
CAPTURE(p);
auto smesh = OrientedTriFaceMesh(1, true);
smesh.EnsureNCMesh(true);
auto pmesh = CheckParMeshNBE(smesh);
pmesh->FinalizeTopology();
pmesh->Finalize();
pmesh->ExchangeFaceNbrData();
REQUIRE(pmesh->Nonconforming());
std::map<int, int> local_to_shared;
for (int i = 0; i < pmesh->GetNSharedFaces(); ++i)
{
local_to_shared[pmesh->GetSharedFace(i)] = i;
}
// Count the number of internal faces via the boundary elements
int num_internal = 0;
for (int n = 0; n < pmesh->GetNBE(); ++n)
{
int f, o;
pmesh->GetBdrElementFace(n, &f, &o);
if (CheckFaceInternal(*pmesh, f, local_to_shared))
{
++num_internal;
}
}
MPI_Allreduce(MPI_IN_PLACE, &num_internal, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
CHECK(num_internal == 1);
CheckPoisson(*pmesh, p, pmesh->bdr_attributes.Max());
CheckPoisson(*pmesh, p);
}
TEST_CASE("InteriorBoundaryReferenceTets", "[Parallel], [NCMesh]")
{
constexpr auto seed = 314159;
srand(seed);
auto p = GENERATE(1,2,3);
CAPTURE(p);
auto smesh = OrientedTriFaceMesh(1, true);
smesh.EnsureNCMesh(true);
auto pmesh = CheckParMeshNBE(smesh);
pmesh->FinalizeTopology();
pmesh->Finalize();
pmesh->ExchangeFaceNbrData();
REQUIRE(pmesh->Nonconforming());
std::map<int, int> local_to_shared;
for (int i = 0; i < pmesh->GetNSharedFaces(); ++i)
{
local_to_shared[pmesh->GetSharedFace(i)] = i;
}
// Count the number of internal faces via the boundary elements
int num_internal = 0;
for (int n = 0; n < pmesh->GetNBE(); ++n)
{
int f, o;
pmesh->GetBdrElementFace(n, &f, &o);
if (CheckFaceInternal(*pmesh, f, local_to_shared))
{
++num_internal;
}
}
MPI_Allreduce(MPI_IN_PLACE, &num_internal, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
CHECK(num_internal == 1);
CheckPoisson(*pmesh, p, pmesh->bdr_attributes.Max());
CheckPoisson(*pmesh, p);
int num_initial_ess_tdof = CountEssentialDof<H1_FECollection>(*pmesh, p,
smesh.bdr_attributes.Max());
if (Mpi::Root())
{
REQUIRE(num_initial_ess_tdof > 0);
}
// Level of refinement difference across the processor boundary from root
// zero to the others
auto ref_level = GENERATE(1,2,3);
auto refined_attribute = GENERATE(1,2);
CAPTURE(ref_level);
CAPTURE(refined_attribute);
Mesh modified_smesh(smesh);
for (int r = 0; r < ref_level; r++)
{
Array<int> el_to_refine;
for (int n = 0; n < modified_smesh.GetNE(); n++)
{
if (modified_smesh.GetAttribute(n) == refined_attribute)
{
el_to_refine.Append(n);
}
}
modified_smesh.GeneralRefinement(el_to_refine);
}
// There should now be some internal boundary elements, where there was one
// before.
CHECK(modified_smesh.GetNBE() == 3 /* external boundaries of unrefined */
+ std::pow(4, ref_level) /* internal boundaries */
+ (3 * std::pow(4, ref_level)) /* external boundaries of refined */);
// Force the partition to have the edge case of a parent and child being
// divided across the processor boundary.
auto partition = std::unique_ptr<int[]>(new int[modified_smesh.GetNE()]);
for (int i = 0; i < modified_smesh.GetNE(); i++)
{
// Randomly assign to any processor but zero.
partition[i] = Mpi::WorldSize() > 1 ? 1 + rand() % (Mpi::WorldSize() - 1) : 0;
}
if (Mpi::WorldSize() > 0)
{
// Make sure rank 0 has the non-refined attribute. This ensures it will
// have a parent face with only ghost children.
const int unrefined_attribute = refined_attribute == 1 ? 2 : 1;
Array<int> root_element;
for (int n = 0; n < modified_smesh.GetNE(); n++)
{
if (modified_smesh.GetAttribute(n) == unrefined_attribute)
{
root_element.Append(n);
}
}
REQUIRE(root_element.Size() == 1);
partition[root_element[0]] = 0;
}
pmesh = CheckParMeshNBE(modified_smesh, partition);
pmesh->Finalize();
pmesh->FinalizeTopology();
pmesh->ExchangeFaceNbrData();
auto check_faces = [&]()
{
// repopulate the local to shared map.
local_to_shared.clear();
for (int i = 0; i < pmesh->GetNSharedFaces(); ++i)
{
local_to_shared[pmesh->GetSharedFace(i)] = i;
}
// Count the number of internal faces via the boundary elements
num_internal = 0;
for (int n = 0; n < pmesh->GetNBE(); ++n)
{
int f, o;
pmesh->GetBdrElementFace(n, &f, &o);
if (CheckFaceInternal(*pmesh, f, local_to_shared))
{
++num_internal;
}
}
MPI_Allreduce(MPI_IN_PLACE, &num_internal, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
CHECK(num_internal == std::pow(4, ref_level));
CheckPoisson(*pmesh, p, smesh.bdr_attributes.Max());
CheckPoisson(*pmesh, p);
};
check_faces();
pmesh->Rebalance();
pmesh->ExchangeFaceNbrData();
check_faces();
}
TEST_CASE("InteriorBoundaryInlineRefines", "[Parallel], [NCMesh]")
{
const auto use_tet = GENERATE(false, true);
const int p = use_tet ? GENERATE(1,2,3) : GENERATE(1,2);
CAPTURE(p);
const auto fname = use_tet ? "../../data/inline-tet.mesh" :
"../../data/inline-hex.mesh";
auto smesh = Mesh(fname);
smesh.FinalizeTopology();
smesh.Finalize();
// Mark even and odd elements with different attributes
const auto num_attributes = 3;
for (int i = 0; i < smesh.GetNE(); ++i)
{
smesh.SetAttribute(i, (i % num_attributes) + 1);
}
smesh.SetAttributes();
const int initial_nbe = smesh.GetNBE();
// Introduce internal boundary elements
const int new_attribute = smesh.bdr_attributes.Max() + 1;
for (int f = 0; f < smesh.GetNumFaces(); ++f)
{
int e1, e2;
smesh.GetFaceElements(f, &e1, &e2);
if (e1 >= 0 && e2 >= 0 && smesh.GetAttribute(e1) != smesh.GetAttribute(e2))
{
// This is the internal face between attributes.
auto *new_elem = smesh.GetFace(f)->Duplicate(&smesh);
new_elem->SetAttribute(new_attribute);
smesh.AddBdrElement(new_elem);
}
}
smesh.FinalizeTopology(); // Finalize to build relevant tables
smesh.Finalize();
smesh.EnsureNCMesh(true);
// Boundary elements must've been added to make the test valid
int num_internal_serial = smesh.GetNBE() - initial_nbe;
REQUIRE(num_internal_serial > 0);
auto partition = std::unique_ptr<int[]>(new int[smesh.GetNE()]);
for (int i = 0; i < smesh.GetNE(); ++i)
{
partition[i] = i % Mpi::WorldSize(); // checkerboard partition
}
auto pmesh = CheckParMeshNBE(smesh, partition);
pmesh->FinalizeTopology();
pmesh->Finalize();
pmesh->ExchangeFaceNbrData();
std::map<int, int> local_to_shared;
for (int i = 0; i < pmesh->GetNSharedFaces(); ++i)
{
local_to_shared[pmesh->GetSharedFace(i)] = i;
}
// Count the number of internal faces via the boundary elements
int num_internal = 0;
for (int n = 0; n < pmesh->GetNBE(); ++n)
{
int f, o;
pmesh->GetBdrElementFace(n, &f, &o);
if (CheckFaceInternal(*pmesh, f, local_to_shared))
{
++num_internal;
}
}
MPI_Allreduce(MPI_IN_PLACE, &num_internal, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
CHECK(num_internal == num_internal_serial);
CheckPoisson(*pmesh, p, new_attribute);
CheckPoisson(*pmesh, p);
// Mark all elements of a given attribute for refinement to a given depth.
const auto ref_level = GENERATE(1,2);
const auto marked_attribute = GENERATE(1,2,3);
REQUIRE(marked_attribute <= num_attributes);
CAPTURE(ref_level);
CAPTURE(marked_attribute);
for (int r = 0; r < ref_level; r++)
{
Array<int> elem_to_refine;
for (int i = 0; i < smesh.GetNE(); ++i)
{
if (smesh.GetAttribute(i) == marked_attribute)
{
elem_to_refine.Append(i);
}
}
smesh.GeneralRefinement(elem_to_refine);
}
pmesh = CheckParMeshNBE(smesh);
pmesh->FinalizeTopology();
pmesh->Finalize();
pmesh->ExchangeFaceNbrData();
// Count the number of internal boundary elements
num_internal_serial = 0;
for (int n = 0; n < smesh.GetNBE(); ++n)
{
int f, o;
smesh.GetBdrElementFace(n, &f, &o);
int e1, e2;
smesh.GetFaceElements(f, &e1, &e2);
if (e1 >= 0 && e2 >= 0 && smesh.GetAttribute(e1) != smesh.GetAttribute(e2))
{
++num_internal_serial;
}
}
auto check_faces = [&]()
{
// repopulate the local to shared map.
local_to_shared.clear();
for (int i = 0; i < pmesh->GetNSharedFaces(); ++i)
{
local_to_shared[pmesh->GetSharedFace(i)] = i;
}
num_internal = 0;
for (int n = 0; n < pmesh->GetNBE(); ++n)
{
int f, o;
pmesh->GetBdrElementFace(n, &f, &o);
if (CheckFaceInternal(*pmesh, f, local_to_shared))
{
++num_internal;
}
}
MPI_Allreduce(MPI_IN_PLACE, &num_internal, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
CHECK(num_internal == num_internal_serial);
CheckPoisson(*pmesh, p, pmesh->bdr_attributes.Max());
CheckPoisson(*pmesh, p);
};
check_faces();
pmesh->Rebalance();
pmesh->ExchangeFaceNbrData();
check_faces();
}
TEST_CASE("InteriorBoundaryReferenceCubes", "[Parallel], [NCMesh]")
{
auto p = GENERATE(1,2);
CAPTURE(p);
auto smesh = DividingPlaneMesh(false, true);
auto pmesh = CheckParMeshNBE(smesh);
pmesh->FinalizeTopology();
pmesh->Finalize();
pmesh->ExchangeFaceNbrData();
REQUIRE(pmesh->Conforming());
std::map<int, int> local_to_shared;
for (int i = 0; i < pmesh->GetNSharedFaces(); ++i)
{
local_to_shared[pmesh->GetSharedFace(i)] = i;
}
// Count the number of internal faces via the boundary elements
int num_internal = 0;
for (int n = 0; n < pmesh->GetNBE(); ++n)
{
int f, o;
pmesh->GetBdrElementFace(n, &f, &o);
if (CheckFaceInternal(*pmesh, f, local_to_shared))
{
++num_internal;
}
}
MPI_Allreduce(MPI_IN_PLACE, &num_internal, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
CHECK(num_internal == 1);
CheckPoisson(*pmesh, p, pmesh->bdr_attributes.Max());
CheckPoisson(*pmesh, p);
for (int refined_elem : {0, 1})
{
// Now NC refine one of the attached elements, this should result in 4
// internal boundary elements.
Array<int> el_to_refine;
el_to_refine.Append(refined_elem);
Mesh modified_smesh(smesh);
modified_smesh.GeneralRefinement(el_to_refine);
// There should now be four internal boundary elements, where there was
// one before.
CHECK(modified_smesh.GetNBE() == 5 /* external boundaries of unrefined */
+ 4 /* internal boundaries */
+ (5 * 4) /* external boundaries of refined */);
// Force the partition to have the edge case of a parent and child being
// divided across the processor boundary. This necessitates the
// GhostBoundaryElement treatment.
auto partition = std::unique_ptr<int[]>(new int[modified_smesh.GetNE()]);
srand(314159);
for (int i = 0; i < modified_smesh.GetNE(); ++i)
{
// Randomly assign to any processor but zero.
partition[i] = Mpi::WorldSize() > 1 ? 1 + rand() % (Mpi::WorldSize() - 1) : 0;
}
if (Mpi::WorldSize() > 0)
{
// Make sure on rank 1 there is a parent face with only ghost child
// faces. This can cause issues with higher order dofs being
// uncontrolled.
partition[refined_elem == 0 ? modified_smesh.GetNE() - 1 : 0] = 0;
}
pmesh = CheckParMeshNBE(modified_smesh, partition);
pmesh->Finalize();
pmesh->FinalizeTopology();
pmesh->ExchangeFaceNbrData();
auto check_faces = [&]()
{
// repopulate the local to shared map.
local_to_shared.clear();
for (int i = 0; i < pmesh->GetNSharedFaces(); ++i)
{
local_to_shared[pmesh->GetSharedFace(i)] = i;
}
// Count the number of internal faces via the boundary elements
num_internal = 0;
for (int n = 0; n < pmesh->GetNBE(); ++n)
{
int f, o;
pmesh->GetBdrElementFace(n, &f, &o);
if (CheckFaceInternal(*pmesh, f, local_to_shared))
{
++num_internal;
}
}
MPI_Allreduce(MPI_IN_PLACE, &num_internal, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
CHECK(num_internal == 4);
CAPTURE(refined_elem);
CheckPoisson(*pmesh, p, smesh.bdr_attributes.Max());
CheckPoisson(*pmesh, p);
};
check_faces();
pmesh->Rebalance();
pmesh->ExchangeFaceNbrData();
check_faces();
}
}
TEST_CASE("ParMeshInternalBoundaryTetStarMesh", "[Parallel], [NCMesh]")
{
auto smesh = TetStarMesh();
smesh.EnsureNCMesh(true);
if (Mpi::WorldSize() < 5) { return; }
auto partition = std::unique_ptr<int[]>(new int[5]);
for (int i = 0; i < 5; i++)
{
partition[i] = i;
}
auto pmesh = CheckParMeshNBE(smesh, partition);
pmesh->FinalizeTopology();
pmesh->Finalize();
pmesh->ExchangeFaceNbrData();
REQUIRE(pmesh->Nonconforming());
std::map<int, int> local_to_shared;
for (int i = 0; i < pmesh->GetNSharedFaces(); ++i)
{
local_to_shared[pmesh->GetSharedFace(i)] = i;
}
// Count the number of internal faces via the boundary elements
int num_internal = 0;
for (int n = 0; n < pmesh->GetNBE(); ++n)
{
int f, o;
pmesh->GetBdrElementFace(n, &f, &o);
if (CheckFaceInternal(*pmesh, f, local_to_shared))
{
++num_internal;
}
}
const int rank = Mpi::WorldRank();
SECTION("Unrefined")
{
MPI_Allreduce(MPI_IN_PLACE, &num_internal, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
CHECK(num_internal == 4);
CHECK(CountEssentialDof<H1_FECollection>(*pmesh, 1,
smesh.bdr_attributes.Max()) == (rank == 0 ? 4 : 0));
CHECK(CountEssentialDof<H1_FECollection>(*pmesh, 2,
smesh.bdr_attributes.Max()) == (rank == 0 ? 4 + 6 : 0));
CHECK(CountEssentialDof<H1_FECollection>(*pmesh, 3,
smesh.bdr_attributes.Max()) == (rank == 0 ? 4 + 6*2 + 4*1 : 0));
CHECK(CountEssentialDof<H1_FECollection>(*pmesh, 4,
smesh.bdr_attributes.Max()) == (rank == 0 ? 4 + 6*3 + 4*3 : 0));
CHECK(CountEssentialDof<ND_FECollection>(*pmesh, 1,
smesh.bdr_attributes.Max()) == (rank == 0 ? 6 : 0));
CHECK(CountEssentialDof<ND_FECollection>(*pmesh, 2,
smesh.bdr_attributes.Max()) == (rank == 0 ? 20 : 0));
CHECK(CountEssentialDof<ND_FECollection>(*pmesh, 3,
smesh.bdr_attributes.Max()) == (rank == 0 ? 42 : 0));
CHECK(CountEssentialDof<ND_FECollection>(*pmesh, 4,
smesh.bdr_attributes.Max()) == (rank == 0 ? 72 : 0));
CHECK(pmesh->GetNBE() == (rank == 0 ? 4 : (rank < 5 ? 3 : 0)));
}
SECTION("Refinement")
{
// Refining an element attached to the core should not change the number
// of essential DOF, or the owner of them.
const int refined_attribute = GENERATE(1,2,3,4,5); // equal to rank of owner + 1
int ref_level = GENERATE(0, 1, 2, 3);
for (int r = 0; r < ref_level; r++)
{
Array<int> el_to_refine;
for (int n = 0; n < pmesh->GetNE(); n++)
{
if (pmesh->GetAttribute(n) == refined_attribute)
{
el_to_refine.Append(n);
}
}
pmesh->GeneralRefinement(el_to_refine);
}
pmesh->ExchangeFaceNbrData();
CAPTURE(rank);
CAPTURE(refined_attribute);
CAPTURE(ref_level);
CHECK(pmesh->GetNE() == (rank == refined_attribute - 1 ? std::pow(8,
ref_level) : 1));
CHECK(pmesh->GetNBE() == (rank == refined_attribute - 1
? std::pow(4, ref_level + 1)
: (ref_level == 0 && rank == 0 ? 4 : 3)));
// Refining on only one side of the boundary face should not change the
// number of essential true dofs, which should match the number within the
// original face.
CHECK(ParCountEssentialDof<H1_FECollection>(*pmesh, 1,
smesh.bdr_attributes.Max()) == 4);
CHECK(ParCountEssentialDof<H1_FECollection>(*pmesh, 2,
smesh.bdr_attributes.Max()) == 4 + 6);
CHECK(ParCountEssentialDof<H1_FECollection>(*pmesh, 3,
smesh.bdr_attributes.Max()) == 4 + 6*2 + 4*1);
CHECK(ParCountEssentialDof<H1_FECollection>(*pmesh, 4,
smesh.bdr_attributes.Max()) == 4 + 6*3 + 4*3);
CHECK(ParCountEssentialDof<ND_FECollection>(*pmesh, 1,
smesh.bdr_attributes.Max()) == (rank == 0 ? 6 : 0));
CHECK(ParCountEssentialDof<ND_FECollection>(*pmesh, 2,
smesh.bdr_attributes.Max()) == (rank == 0 ? 20 : 0));
CHECK(ParCountEssentialDof<ND_FECollection>(*pmesh, 3,
smesh.bdr_attributes.Max()) == (rank == 0 ? 42 : 0));
CHECK(ParCountEssentialDof<ND_FECollection>(*pmesh, 4,
smesh.bdr_attributes.Max()) == (rank == 0 ? 72 : 0));
}
}
TEST_CASE("ParDividingPlaneMesh", "[Parallel], [NCMesh]")
{
auto refine_attribute = [](Mesh& mesh, int attr, int ref_level)
{
for (int r = 0; r < ref_level; r++)
{
Array<int> el_to_refine;
for (int n = 0; n < mesh.GetNE(); n++)
{
if (mesh.GetAttribute(n) == attr)
{
el_to_refine.Append(n);
}
}
mesh.GeneralRefinement(el_to_refine);
}
};
SECTION("Hex")
{
auto mesh = DividingPlaneMesh(false);
mesh.EnsureNCMesh(true);
CHECK(mesh.GetNBE() == 2 * 5 + 1);
CHECK(mesh.GetNE() == 2);
SECTION("H1Hex")
{
mesh.UniformRefinement();
CHECK(CountEssentialDof<H1_FECollection>(mesh, 1,
mesh.bdr_attributes.Max()) == 3*3);
CHECK(CountEssentialDof<H1_FECollection>(mesh, 2,
mesh.bdr_attributes.Max()) == 5*5);
CHECK(CountEssentialDof<H1_FECollection>(mesh, 3,
mesh.bdr_attributes.Max()) == 7*7);
CHECK(CountEssentialDof<H1_FECollection>(mesh, 4,
mesh.bdr_attributes.Max()) == 9*9);
auto attr = GENERATE(1,2);
auto ref_level = GENERATE(1,2);
refine_attribute(mesh, attr, ref_level);
CHECK(CountEssentialDof<H1_FECollection>(mesh, 1,
mesh.bdr_attributes.Max()) == 3*3);
CHECK(CountEssentialDof<H1_FECollection>(mesh, 2,
mesh.bdr_attributes.Max()) == 5*5);
CHECK(CountEssentialDof<H1_FECollection>(mesh, 3,
mesh.bdr_attributes.Max()) == 7*7);
CHECK(CountEssentialDof<H1_FECollection>(mesh, 4,
mesh.bdr_attributes.Max()) == 9*9);
}
}
SECTION("Tet")
{
auto mesh = DividingPlaneMesh(true, true);
mesh.EnsureNCMesh(true);
auto pmesh = CheckParMeshNBE(mesh);
pmesh->FinalizeTopology();
pmesh->Finalize();
pmesh->ExchangeFaceNbrData();
CHECK(pmesh->bdr_attributes.Max() == mesh.bdr_attributes.Max());
auto attr = GENERATE(1,2);
auto ref_level = GENERATE(1,2);
CAPTURE(attr);
CAPTURE(ref_level);
const int initial_num_vert = 4;
const int initial_num_edge = 5;
const int initial_num_face = 2;
SECTION("H1Tet")
{
CHECK(ParCountEssentialDof<H1_FECollection>(*pmesh, 1,
mesh.bdr_attributes.Max()) == initial_num_vert);
CHECK(ParCountEssentialDof<H1_FECollection>(*pmesh, 2,
mesh.bdr_attributes.Max()) == initial_num_vert + initial_num_edge);
CHECK(ParCountEssentialDof<H1_FECollection>(*pmesh, 3,
mesh.bdr_attributes.Max()) == initial_num_vert + 2*initial_num_edge +
initial_num_face);
CHECK(ParCountEssentialDof<H1_FECollection>(*pmesh, 4,
mesh.bdr_attributes.Max()) == initial_num_vert + 3*initial_num_edge +
3*initial_num_face);
refine_attribute(*pmesh, attr, ref_level);
CHECK(ParCountEssentialDof<H1_FECollection>(*pmesh, 1,
mesh.bdr_attributes.Max()) == initial_num_vert);
CHECK(ParCountEssentialDof<H1_FECollection>(*pmesh, 2,
mesh.bdr_attributes.Max()) == initial_num_vert + initial_num_edge);
CHECK(ParCountEssentialDof<H1_FECollection>(*pmesh, 3,
mesh.bdr_attributes.Max()) == initial_num_vert + 2*initial_num_edge +
initial_num_face);
CHECK(ParCountEssentialDof<H1_FECollection>(*pmesh, 4,
mesh.bdr_attributes.Max()) == initial_num_vert + 3*initial_num_edge +
3*initial_num_face);
}
SECTION("NDTet")
{
CHECK(ParCountEssentialDof<ND_FECollection>(*pmesh, 1,
mesh.bdr_attributes.Max()) == 5);
CHECK(ParCountEssentialDof<ND_FECollection>(*pmesh, 2,
mesh.bdr_attributes.Max()) == 14);
CHECK(ParCountEssentialDof<ND_FECollection>(*pmesh, 3,
mesh.bdr_attributes.Max()) == 27);
CHECK(ParCountEssentialDof<ND_FECollection>(*pmesh, 4,
mesh.bdr_attributes.Max()) == 44);
refine_attribute(*pmesh, attr, ref_level);
CHECK(ParCountEssentialDof<ND_FECollection>(*pmesh, 1,
mesh.bdr_attributes.Max()) == 5);
CHECK(ParCountEssentialDof<ND_FECollection>(*pmesh, 2,
mesh.bdr_attributes.Max()) == 14);
CHECK(ParCountEssentialDof<ND_FECollection>(*pmesh, 3,
mesh.bdr_attributes.Max()) == 27);
CHECK(ParCountEssentialDof<ND_FECollection>(*pmesh, 4,
mesh.bdr_attributes.Max()) == 44);
}
}
}
TEST_CASE("ParTetFaceFlips", "[Parallel], [NCMesh]")
{
/*
1. Define an ND space, and project a smooth non polynomial function onto
the space.
2. Compute y-z components in the face, and check that they are equal when
evaluated from either side of the face. Tangential continuity of the ND
space should ensure they are identical, if orientations are correctly
accounted for.
3. Mark the mesh as NC, build a new FESpace, and repeat. There should be
no change as the faces are "conformal" though they are within the NC
structure.
3. Partition the mesh, create a ParFESpace and repeat the above. There
should be no difference in conformal parallel.
4. Construct the ParMesh from the NCMesh and repeat. As above, there
should be no change.
5. Perform NC refinement on one side of the internal face, the number of
conformal dof in the face will not change, so there should also be no
difference. This will be complicated by ensuring the slave evaluations
are at the same points.
*/
auto orientation = GENERATE(1,3,5);
auto smesh = OrientedTriFaceMesh(orientation);
smesh.EnsureNodes();
CHECK(smesh.GetNBE() == 1);
// A smooth function in each vector component
constexpr int order = 3, dim = 3, quadrature_order = 4;
constexpr real_t kappa = 2 * M_PI;
auto E_exact = [=](const Vector &x, Vector &E)
{
E(0) = cos(kappa * x(1));
E(1) = cos(kappa * x(2));
E(2) = cos(kappa * x(0));
};
VectorFunctionCoefficient E_coeff(dim, E_exact);
// Helper for evaluating the ND grid function on either side of the first
// conformal shared face. Specific to the pair of tet mesh described above,
// but can be generalized.
auto check_parallel_nc_conformal = [&](ParMesh &mesh)
{
ND_FECollection fe_collection(order, dim);
ParFiniteElementSpace fe_space(&mesh, &fe_collection);
ParGridFunction E(&fe_space);
E.ProjectCoefficient(E_coeff);
E.ExchangeFaceNbrData();
auto *P = fe_space.GetProlongationMatrix();
if (P != nullptr)
{
// Projection does not respect the non-conformal constraints. Extract
// the true (conformal) and prolongate to get the NC respecting
// projection.
auto E_true = E.GetTrueVector();
P->Mult(E_true, E);
E.ExchangeFaceNbrData();
}
ParGridFunction * const coords = dynamic_cast<ParGridFunction*>
(mesh.GetNodes());
const auto &ir = IntRules.Get(Geometry::Type::TRIANGLE, quadrature_order);
IntegrationRule left_eir(ir.GetNPoints()),
right_eir(ir.GetNPoints()); // element integration rules
bool y_valid = true, z_valid = true;
for (int n = 0; n < mesh.GetNBE(); n++)
{
auto f = mesh.GetBdrElementFaceIndex(n);
auto finfo = mesh.GetFaceInformation(f);
auto &face_element_transform = finfo.IsShared()
? *mesh.GetSharedFaceTransformationsByLocalIndex(f, true)
: *mesh.GetFaceElementTransformations(f);
face_element_transform.Loc1.Transform(ir, left_eir);
face_element_transform.Loc2.Transform(ir, right_eir);
constexpr real_t tol = 1e-14;
REQUIRE(left_eir.GetNPoints() == ir.GetNPoints());
REQUIRE(right_eir.GetNPoints() == ir.GetNPoints());
Vector left_val, right_val;
for (int i = 0; i < ir.GetNPoints(); i++)
{
face_element_transform.Elem1->SetIntPoint(&left_eir[i]);
coords->GetVectorValue(*face_element_transform.Elem1, left_eir[i], left_val);
coords->GetVectorValue(*face_element_transform.Elem1, left_eir[i], right_val);
REQUIRE(std::abs(left_val(0) - right_val(0)) < tol);
REQUIRE(std::abs(left_val(1) - right_val(1)) < tol);
REQUIRE(std::abs(left_val(2) - right_val(2)) < tol);
E.GetVectorValue(*face_element_transform.Elem1, left_eir[i], left_val);
face_element_transform.Elem2->SetIntPoint(&right_eir[i]);
E.GetVectorValue(*face_element_transform.Elem2, right_eir[i], right_val);
// Check that the second and third rows agree. The y and z should
// agree as the normal is in the x direction.
y_valid &= (std::abs(left_val(1) - right_val(1)) < tol);
z_valid &= (std::abs(left_val(2) - right_val(2)) < tol);
}
}
CHECK(y_valid);
CHECK(z_valid);
return fe_space.GlobalTrueVSize();
};
SECTION("Conformal")
{
auto partition_flag = GENERATE(false, true);
CAPTURE(partition_flag);
auto partition = std::unique_ptr<int[]>(new int[2]);
if (Mpi::WorldSize() > 1)
{
partition[0] = partition_flag ? 0 : 1; partition[1] = partition_flag ? 1 : 0;
}
else
{
partition[0] = 0; partition[1] = 0;
}
auto pmesh = CheckParMeshNBE(smesh);
pmesh->Finalize();
pmesh->ExchangeFaceNbrData();
check_parallel_nc_conformal(*pmesh);
}
SECTION("ConformalSerialUniformRefined")
{
smesh.UniformRefinement();
auto pmesh = CheckParMeshNBE(smesh);
pmesh->Finalize();
pmesh->ExchangeFaceNbrData();
check_parallel_nc_conformal(*pmesh);
}
SECTION("ConformalParallelUniformRefined")
{
auto partition_flag = GENERATE(false, true);
CAPTURE(partition_flag);
auto partition = std::unique_ptr<int[]>(new int[2]);
if (Mpi::WorldSize() > 1)
{
partition[0] = partition_flag ? 0 : 1; partition[1] = partition_flag ? 1 : 0;
}
else
{
partition[0] = 0; partition[1] = 0;
}
auto pmesh = CheckParMeshNBE(smesh);
pmesh->UniformRefinement();
pmesh->Finalize();
pmesh->ExchangeFaceNbrData();
check_parallel_nc_conformal(*pmesh);
}
SECTION("Nonconformal")
{
auto partition_flag = GENERATE(false, true);
CAPTURE(partition_flag);
auto partition = std::unique_ptr<int[]>(new int[2]);
if (Mpi::WorldSize() > 1)
{
partition[0] = partition_flag ? 0 : 1; partition[1] = partition_flag ? 1 : 0;
}
else
{
partition[0] = 0; partition[1] = 0;
}
smesh.EnsureNCMesh(true);
auto pmesh = CheckParMeshNBE(smesh);
pmesh->Finalize();
pmesh->ExchangeFaceNbrData();
check_parallel_nc_conformal(*pmesh);
}
SECTION("NonconformalSerialUniformRefined")
{
smesh.UniformRefinement();
smesh.EnsureNCMesh(true);
auto pmesh = CheckParMeshNBE(smesh);
pmesh->Finalize();
pmesh->ExchangeFaceNbrData();
check_parallel_nc_conformal(*pmesh);
}
SECTION("NonconformalSerialRefined")
{
smesh.EnsureNCMesh(true);
int ref_level = GENERATE(1, 2);
for (int r = 0; r < ref_level; r++)
{
Array<int> el_to_refine;
for (int n = 0; n < smesh.GetNE(); n++)
{
if (smesh.GetAttribute(n) == 2)
{
el_to_refine.Append(n);
}
}
smesh.GeneralRefinement(el_to_refine);
}
auto pmesh = CheckParMeshNBE(smesh);
pmesh->Finalize();
pmesh->ExchangeFaceNbrData();
check_parallel_nc_conformal(*pmesh);
}
SECTION("NonconformalParallelUniformRefined")
{
auto partition_flag = GENERATE(false, true);
CAPTURE(partition_flag);
auto partition = std::unique_ptr<int[]>(new int[2]);
if (Mpi::WorldSize() > 1)
{
partition[0] = partition_flag ? 0 : 1; partition[1] = partition_flag ? 1 : 0;
}
else
{
partition[0] = 0; partition[1] = 0;
}
smesh.EnsureNCMesh(true);
auto pmesh = CheckParMeshNBE(smesh);
pmesh->UniformRefinement();
pmesh->Finalize();
pmesh->ExchangeFaceNbrData();
check_parallel_nc_conformal(*pmesh);
}
SECTION("NonconformalParallelRefined")
{
auto partition_flag = GENERATE(false, true);
CAPTURE(partition_flag);
auto partition = std::unique_ptr<int[]>(new int[2]);
if (Mpi::WorldSize() > 1)
{
partition[0] = partition_flag ? 0 : 1; partition[1] = partition_flag ? 1 : 0;
}
else
{
partition[0] = 0; partition[1] = 0;
}
smesh.EnsureNCMesh(true);
auto pmesh = CheckParMeshNBE(smesh);
int ref_level = GENERATE(1, 2);
for (int r = 0; r < ref_level; r++)
{
Array<int> el_to_refine;
for (int n = 0; n < pmesh->GetNE(); n++)
{
if (pmesh->GetAttribute(n) == 2)
{
el_to_refine.Append(n);
}
}
pmesh->GeneralRefinement(el_to_refine);
}
pmesh->Finalize();
pmesh->ExchangeFaceNbrData();
check_parallel_nc_conformal(*pmesh);
}
SECTION("NonconformalLevelTwoRefined")
{
smesh.EnsureNCMesh(true);
smesh.UniformRefinement();
Array<int> el_to_refine(1);
for (int n = 0; n < smesh.GetNE(); n++)
{
if (smesh.GetAttribute(n) == 2)
{
CAPTURE(n);
Mesh smesh2(smesh);
el_to_refine[0] = n;
smesh2.GeneralRefinement(el_to_refine);
for (int m = 0; m < smesh2.GetNE(); m++)
{
if (smesh2.GetAttribute(m) == 2)
{
CAPTURE(m);
Mesh smesh3(smesh2);
el_to_refine[0] = m;
smesh3.GeneralRefinement(el_to_refine);
check_parallel_nc_conformal(*CheckParMeshNBE(smesh3));
}
}
}
}
}
}
TEST_CASE("Parallel RP=I", "[Parallel], [NCMesh]")
{
const int order = GENERATE(1, 2, 3);
CAPTURE(order);
const int dim = 3;
SECTION("Hex")
{
auto smesh = DividingPlaneMesh(false, true);
Array<int> refinements(1);
refinements[0] = 0;
smesh.GeneralRefinement(refinements);
ParMesh mesh(MPI_COMM_WORLD, smesh);
SECTION("ND")
{
ND_FECollection fec(order, dim);
ParFiniteElementSpace fespace(&mesh, &fec);
CHECK(CheckRPIdentity(fespace));
}
SECTION("H1")
{
H1_FECollection fec(order, dim);
ParFiniteElementSpace fespace(&mesh, &fec);
CHECK(CheckRPIdentity(fespace));
}
}
SECTION("Tet")
{
auto orientation = GENERATE(1,3,5);
Array<int> refinements(1);
refinements[0] = 0;
auto smesh = OrientedTriFaceMesh(orientation, true);
smesh.EnsureNCMesh(true); // Always checking NC
ParMesh mesh(MPI_COMM_WORLD, smesh);
SECTION("ND")
{
ND_FECollection fec(order, dim);
ParFiniteElementSpace fespace(&mesh, &fec);
CHECK(CheckRPIdentity(fespace));
}
SECTION("H1")
{
H1_FECollection fec(order, dim);
ParFiniteElementSpace fespace(&mesh, &fec);
CHECK(CheckRPIdentity(fespace));
}
}
}
#endif // MFEM_USE_MPI
TEST_CASE("ReferenceCubeInternalBoundaries", "[NCMesh]")
{
auto p = GENERATE(1,2,3);
CAPTURE(p);
auto smesh = DividingPlaneMesh(false, true);
smesh.EnsureNCMesh();
CHECK(smesh.GetNBE() == 2 * 5 + 1);
auto with_internal = CheckPoisson(smesh, p); // Include the internal boundary
auto without_internal = CheckPoisson(smesh, p,
smesh.bdr_attributes.Max()); // Exclude the internal boundary
switch (p)
{
case 1:
CHECK(with_internal == without_internal); break;
case 2:
CHECK(with_internal == without_internal + 1); break;
case 3:
CHECK(with_internal == without_internal + 4); break;
}
auto ref_type = char(GENERATE(Refinement::XYZ));
Array<Refinement> refs(1);
for (auto ref : {0,1})
{
auto ssmesh = Mesh(smesh);
CAPTURE(ref_type);
// Now NC refine one of the attached elements, this should result in 2
// internal boundary elements.
refs[0].index = ref;
refs[0].SetType(ref_type);
ssmesh.GeneralRefinement(refs);
// There should now be four internal boundary elements, where there was
// one before.
if (ref_type == 2 /* Y */ || ref_type == 4 /* Z */)
{
CHECK(ssmesh.GetNBE() == 5 /* external boundaries of unrefined element */
+ 2 /* internal boundaries */
+ (2 * 4) /* external boundaries of refined elements */);
}
else if (ref_type == 6)
{
CHECK(ssmesh.GetNBE() == 5 /* external boundaries of unrefined element */
+ 4 /* internal boundaries */
+ (4 * 3) /* external boundaries of refined elements */);
}
else if (ref_type == 7)
{
CHECK(ssmesh.GetNBE() == 5 /* external boundaries of unrefined element */
+ 4 /* internal boundaries */
+ (4 * 3 + 4 * 2) /* external boundaries of refined elements */);
}
else
{
MFEM_ABORT("!");
}
// Count the number of internal boundary elements
int num_internal = 0;
for (int n = 0; n < ssmesh.GetNBE(); ++n)
{
int f, o;
ssmesh.GetBdrElementFace(n, &f, &o);
int e1, e2;
ssmesh.GetFaceElements(f, &e1, &e2);
if (e1 >= 0 && e2 >= 0 && ssmesh.GetAttribute(e1) != ssmesh.GetAttribute(e2))
{
++num_internal;
}
}
CHECK(num_internal == (ref_type <= 4 ? 2 : 4));
ssmesh.FinalizeTopology();
ssmesh.Finalize();
without_internal = CheckPoisson(ssmesh, p,
ssmesh.bdr_attributes.Max()); // Exclude the internal boundary
with_internal = CheckPoisson(ssmesh, p); // Include the internal boundary
// All slaves dofs that are introduced on the face are constrained by the
// master dofs, thus the additional constraints on the internal boundary
// are purely on the master face, which matches the initial unrefined
// case.
switch (p)
{
case 1:
CHECK(with_internal == without_internal); break;
case 2:
CHECK(with_internal == without_internal + 1); break;
case 3:
CHECK(with_internal == without_internal + 4); break;
}
}
}
TEST_CASE("RefinedCubesInternalBoundaries", "[NCMesh]")
{
auto p = GENERATE(1,2,3);
CAPTURE(p);
auto smesh = DividingPlaneMesh(false, true);
smesh.UniformRefinement();
smesh.FinalizeTopology();
smesh.Finalize();
// Exactly four boundary elements must be added
CHECK(smesh.GetNBE() == 2 * 5 * 4 + 4);
smesh.EnsureNCMesh();
CHECK(smesh.GetNBE() == 2 * 5 * 4 + 4);
int without_internal = CheckPoisson(smesh, p,
7); // Exclude the internal boundary
int with_internal = CheckPoisson(smesh, p); // Include the internal boundary
switch (p)
{
case 1:
CHECK(with_internal == without_internal + 1); break;
case 2:
CHECK(with_internal == without_internal + 3 * 3); break;
case 3:
CHECK(with_internal == without_internal + 5 * 5); break;
}
// Mark all elements on one side of the attribute boundary to refine
Array<Refinement> refs;
for (int n = 0; n < smesh.GetNE(); ++n)
{
if (smesh.GetAttribute(n) == 2)
{
refs.Append(Refinement{n});
}
}
smesh.GeneralRefinement(refs);
smesh.FinalizeTopology();
smesh.Finalize();
// There should now be 16 internal boundary elements, where there were 4
// before.
CHECK(smesh.GetNBE() == 5 * 4 /* external boundaries of unrefined domain */
+ 4 * 4 /* internal boundaries */
+ 5 * 16 /* external boundaries of refined elements */);
// Count the number of internal boundary elements
int num_internal = 0;
for (int n = 0; n < smesh.GetNBE(); ++n)
{
int f, o;
smesh.GetBdrElementFace(n, &f, &o);
int e1, e2;
smesh.GetFaceElements(f, &e1, &e2);
if (e1 >= 0 && e2 >= 0 && smesh.GetAttribute(e1) != smesh.GetAttribute(e2))
{
++num_internal;
}
}
CHECK(num_internal == 16);
without_internal = CheckPoisson(smesh, p,
smesh.bdr_attributes.Max()); // Exclude the internal boundary
with_internal = CheckPoisson(smesh, p); // Include the internal boundary
switch (p)
{
case 1:
CHECK(with_internal == without_internal + 1); break;
case 2:
CHECK(with_internal == without_internal + 3 * 3); break;
case 3:
CHECK(with_internal == without_internal + 5 * 5); break;
}
}
TEST_CASE("ReferenceTetInternalBoundaries", "[NCMesh]")
{
auto p = GENERATE(1,2,3);
CAPTURE(p);
auto smesh = Mesh("../../data/ref-tetrahedron.mesh");
Array<Refinement> refs;
refs.Append(Refinement(0, Refinement::X));
smesh.GeneralRefinement(refs);
// Now have a pair of elements, make the second element a different
// attribute.
smesh.SetAttribute(1, 2);
REQUIRE(smesh.GetNE() == 2);
REQUIRE(smesh.GetNBE() == 2 * 3);
// Introduce an internal boundary element
for (int f = 0; f < smesh.GetNumFaces(); ++f)
{
int e1, e2;
smesh.GetFaceElements(f, &e1, &e2);
if (e1 >= 0 && e2 >= 0 && smesh.GetAttribute(e1) != smesh.GetAttribute(e2))
{
// This is the internal face between attributes.
auto *new_elem = smesh.GetFace(f)->Duplicate(&smesh);
new_elem->SetAttribute(5);
smesh.AddBdrElement(new_elem);
}
}
// Exactly one boundary element must be added
CHECK(smesh.GetNBE() == 2 * 3 + 1);
smesh.EnsureNCMesh(true);
// Still exactly one boundary element must be added
CHECK(smesh.GetNBE() == 2 * 3 + 1);
smesh.FinalizeTopology();
smesh.Finalize();
auto without_internal = CheckPoisson(smesh, p,
5); // Exclude the internal boundary
auto with_internal = CheckPoisson(smesh, p); // Include the internal boundary
switch (p)
{
case 1:
CHECK(with_internal == without_internal); break;
case 2:
CHECK(with_internal == without_internal); break;
case 3:
CHECK(with_internal == without_internal + 1); break;
}
// Now NC refine one of the attached elements, this should result in 2
// internal boundary elements.
for (int ref : {0, 1})
{
refs[0].Set(ref, Refinement::XYZ);
auto ssmesh = Mesh(smesh);
ssmesh.GeneralRefinement(refs);
// There should now be four internal boundary elements, where there was
// one before.
CHECK(ssmesh.GetNBE() == 3 /* external boundaries of unrefined element */
+ 4 /* internal boundaries */
+ (3 * 4) /* external boundaries of refined element */);
// Count the number of internal boundary elements
int num_internal = 0;
for (int n = 0; n < ssmesh.GetNBE(); ++n)
{
int f, o;
ssmesh.GetBdrElementFace(n, &f, &o);
int e1, e2;
ssmesh.GetFaceElements(f, &e1, &e2);
if (e1 >= 0 && e2 >= 0 && ssmesh.GetAttribute(e1) != ssmesh.GetAttribute(e2))
{
++num_internal;
}
}
CHECK(num_internal == 4);
without_internal = CheckPoisson(ssmesh, p, 5); // Exclude the internal boundary
with_internal = CheckPoisson(ssmesh, p); // Include the internal boundary
switch (p)
{
case 1:
CHECK(with_internal == without_internal); break;
case 2:
CHECK(with_internal == without_internal); break;
case 3:
CHECK(with_internal == without_internal + 1); break;
}
}
}
TEST_CASE("RefinedTetsInternalBoundaries", "[NCMesh]")
{
auto p = GENERATE(1,2,3);
CAPTURE(p);
auto smesh = Mesh("../../data/ref-tetrahedron.mesh");
Array<Refinement> refs;
refs.Append(Refinement(0, Refinement::X));
smesh.GeneralRefinement(refs);
// Now have a pair of elements, make the second element a different
// attribute.
smesh.SetAttribute(1, 2);
REQUIRE(smesh.GetNE() == 2);
REQUIRE(smesh.GetNBE() == 2 * 3);
smesh.UniformRefinement();
CHECK(smesh.GetNBE() == 2 * 3 * 4);
// Introduce internal boundary elements
for (int f = 0; f < smesh.GetNumFaces(); ++f)
{
int e1, e2;
smesh.GetFaceElements(f, &e1, &e2);
if (e1 >= 0 && e2 >= 0 && smesh.GetAttribute(e1) != smesh.GetAttribute(e2))
{
// This is the internal face between attributes.
auto *new_elem = smesh.GetFace(f)->Duplicate(&smesh);
new_elem->SetAttribute(5);
smesh.AddBdrElement(new_elem);
}
}
// Exactly four boundary elements must be added
CHECK(smesh.GetNBE() == 2 * 3 * 4 + 4);
smesh.EnsureNCMesh(true);
// Still exactly one boundary element must be added
CHECK(smesh.GetNBE() == 2 * 3 * 4 + 4);
smesh.FinalizeTopology();
smesh.Finalize();
auto without_internal = CheckPoisson(smesh, p,
5); // Exclude the internal boundary
auto with_internal = CheckPoisson(smesh, p); // Include the internal boundary
switch (p)
{
case 1:
CHECK(with_internal == without_internal); break;
case 2:
CHECK(with_internal == without_internal + 3); break;
case 3:
CHECK(with_internal == without_internal + 10); break;
}
// Now NC refine all elements with the 2 attribute.
// Mark all elements on one side of the attribute boundary to refine
refs.DeleteAll();
for (int n = 0; n < smesh.GetNE(); ++n)
{
if (smesh.GetAttribute(n) == 2)
{
refs.Append(Refinement{n});
}
}
smesh.GeneralRefinement(refs);
// There should now be four internal boundary elements, where there was one
// before.
CHECK(smesh.GetNBE() == 3 * 4 /* external boundaries of unrefined elements */
+ 4 * 4 /* internal boundaries */
+ (3 * 4 * 4) /* external boundaries of refined elements */);
// Count the number of internal boundary elements
int num_internal = 0;
for (int n = 0; n < smesh.GetNBE(); ++n)
{
int f, o;
smesh.GetBdrElementFace(n, &f, &o);
int e1, e2;
smesh.GetFaceElements(f, &e1, &e2);
if (e1 >= 0 && e2 >= 0 && smesh.GetAttribute(e1) != smesh.GetAttribute(e2))
{
++num_internal;
}
}
CHECK(num_internal == 4 * 4);
without_internal = CheckPoisson(smesh, p, 5); // Exclude the internal boundary
with_internal = CheckPoisson(smesh, p); // Include the internal boundary
switch (p)
{
case 1:
CHECK(with_internal == without_internal); break;
case 2:
CHECK(with_internal == without_internal + 3); break;
case 3:
CHECK(with_internal == without_internal + 10); break;
}
}
TEST_CASE("PoissonOnReferenceCubeNC", "[NCMesh]")
{
auto smesh = DividingPlaneMesh(false, true);
auto p = GENERATE(1, 2, 3);
CAPTURE(p);
// Check that Poisson can be solved on the domain
CheckPoisson(smesh, p);
auto ref_type = char(GENERATE(Refinement::X, Refinement::Y, Refinement::Z,
Refinement::XY, Refinement::XZ, Refinement::YZ,
Refinement::XYZ));
CAPTURE(ref_type);
const real_t scale = GENERATE(0.5, 0.25);
CAPTURE(scale);
Array<Refinement> refs(1);
for (auto refined_elem : {0}) // The left or the right element
{
auto ssmesh = Mesh(smesh);
refs[0].Set(refined_elem, ref_type, scale);
ssmesh.GeneralRefinement(refs);
ssmesh.FinalizeTopology();
ssmesh.Finalize();
CAPTURE(refined_elem);
CheckPoisson(ssmesh, p);
}
}
TEST_CASE("PoissonOnReferenceTetNC", "[NCMesh]")
{
auto p = GENERATE(1, 2, 3);
CAPTURE(p);
auto orientation = GENERATE(1,3,5);
auto smesh = OrientedTriFaceMesh(orientation, true);
smesh.EnsureNCMesh(true);
CheckPoisson(smesh, p);
Array<int> refs(1);
for (auto refined_elem : {0, 1})
{
auto ssmesh = Mesh(smesh);
refs[0] = refined_elem;
ssmesh.GeneralRefinement(refs);
ssmesh.FinalizeTopology();
ssmesh.Finalize();
CAPTURE(refined_elem);
CheckPoisson(ssmesh, p);
}
}
TEST_CASE("TetBoundaryRefinement", "[NCMesh]")
{
auto smesh = Mesh("../../data/ref-tetrahedron.mesh");
smesh.FinalizeTopology();
smesh.Finalize(true);
smesh.UniformRefinement();
smesh.EnsureNCMesh(true);
CHECK(smesh.GetNBE() == 4 * 4);
// Loop over elements and mark for refinement if any vertices match the
// original
auto refine_corners = [&]()
{
Array<int> vertices, elements;
// reference vertices of (0,0,0), (1,0,0), (0,1,0), (0,0,1) are [0,3]
auto original_vert = [](int i) { return i >= 0 && i <= 3; };
for (int n = 0; n < smesh.GetNE(); ++n)
{
smesh.GetElementVertices(n, vertices);
if (std::any_of(vertices.begin(), vertices.end(), original_vert))
{
elements.Append(n);
}
}
smesh.GeneralRefinement(elements);
smesh.FinalizeTopology();
smesh.Finalize();
};
constexpr int max_ref_levels = 4;
for (int r = 0; r < max_ref_levels; r++)
{
refine_corners();
CHECK(smesh.GetNBE() == 4 * (4 + 3 * 3 * (r + 1)));
}
}
TEST_CASE("TetInternalBoundaryRefinement", "[NCMesh]")
{
auto orientation = GENERATE(1,3,5);
auto smesh = OrientedTriFaceMesh(orientation, true);
smesh.FinalizeTopology();
smesh.Finalize(true);
smesh.UniformRefinement();
smesh.EnsureNCMesh(true);
CHECK(smesh.GetNBE() == (2*3 + 1) * 4);
CHECK(CountEssentialDof<H1_FECollection>(smesh, 1,
smesh.bdr_attributes.Max()) == 6);
CHECK(CountEssentialDof<H1_FECollection>(smesh, 2,
smesh.bdr_attributes.Max()) == 6 + 3 * 3);
CHECK(CountEssentialDof<H1_FECollection>(smesh, 3,
smesh.bdr_attributes.Max()) == 10 + 3 * 6);
int refined_attribute = GENERATE(1,2);
int ref_level = GENERATE(1,2,3);
for (int r = 0; r < ref_level; r++)
{
Array<int> el_to_refine;
for (int n = 0; n < smesh.GetNE(); n++)
{
if (smesh.GetAttribute(n) == refined_attribute)
{
el_to_refine.Append(n);
}
}
smesh.GeneralRefinement(el_to_refine);
}
// Refining on only one side of the boundary face should not change the
// number of essential true dofs
CHECK(CountEssentialDof<H1_FECollection>(smesh, 1,
smesh.bdr_attributes.Max()) == 6);
CHECK(CountEssentialDof<H1_FECollection>(smesh, 2,
smesh.bdr_attributes.Max()) == 6 + 3 * 3);
CHECK(CountEssentialDof<H1_FECollection>(smesh, 3,
smesh.bdr_attributes.Max()) == 10 + 3 * 6);
// The number of boundary faces should have increased.
CHECK(smesh.GetNBE() == 3 * 4 + (3 + 1) * std::pow(4, 1+ref_level));
}
TEST_CASE("TetInternalBoundaryTetStarMesh", "[NCMesh]")
{
auto smesh = TetStarMesh();
smesh.EnsureNCMesh(true);
SECTION("Unrefined")
{
CHECK(smesh.GetNBE() == 4 * 3 + 4);
CHECK(CountEssentialDof<H1_FECollection>(smesh, 1,
smesh.bdr_attributes.Max()) == 4);
CHECK(CountEssentialDof<H1_FECollection>(smesh, 2,
smesh.bdr_attributes.Max()) == 4 + 6);
CHECK(CountEssentialDof<H1_FECollection>(smesh, 3,
smesh.bdr_attributes.Max()) == 4 + 6*2 + 4*1);
CHECK(CountEssentialDof<H1_FECollection>(smesh, 4,
smesh.bdr_attributes.Max()) == 4 + 6*3 + 4*3);
CHECK(CountEssentialDof<ND_FECollection>(smesh, 1,
smesh.bdr_attributes.Max()) == 6);
CHECK(CountEssentialDof<ND_FECollection>(smesh, 2,
smesh.bdr_attributes.Max()) == 20);
CHECK(CountEssentialDof<ND_FECollection>(smesh, 3,
smesh.bdr_attributes.Max()) == 42);
CHECK(CountEssentialDof<ND_FECollection>(smesh, 4,
smesh.bdr_attributes.Max()) == 72);
}
SECTION("Refined")
{
int refined_attribute = GENERATE(1,2,3,4,5);
int ref_level = GENERATE(1,2,3);
for (int r = 0; r < ref_level; r++)
{
Array<int> el_to_refine;
for (int n = 0; n < smesh.GetNE(); n++)
{
if (smesh.GetAttribute(n) == refined_attribute)
{
el_to_refine.Append(n);
}
}
smesh.GeneralRefinement(el_to_refine);
}
// Refining on only one side of the boundary face should not change the
// number of essential true dofs
CHECK(CountEssentialDof<H1_FECollection>(smesh, 1,
smesh.bdr_attributes.Max()) == 4);
CHECK(CountEssentialDof<H1_FECollection>(smesh, 2,
smesh.bdr_attributes.Max()) == 4 + 6);
CHECK(CountEssentialDof<H1_FECollection>(smesh, 3,
smesh.bdr_attributes.Max()) == 4 + 6*2 + 4*1);
CHECK(CountEssentialDof<H1_FECollection>(smesh, 4,
smesh.bdr_attributes.Max()) == 4 + 6*3 + 4*3);
CHECK(CountEssentialDof<ND_FECollection>(smesh, 1,
smesh.bdr_attributes.Max()) == 6);
CHECK(CountEssentialDof<ND_FECollection>(smesh, 2,
smesh.bdr_attributes.Max()) == 6 * 2 + 4 * 2); // 2 per edge, 2 per face
CHECK(CountEssentialDof<ND_FECollection>(smesh, 3,
smesh.bdr_attributes.Max()) == 42);
CHECK(CountEssentialDof<ND_FECollection>(smesh, 4,
smesh.bdr_attributes.Max()) == 72);
// The number of boundary faces should have increased.
CHECK(smesh.GetNBE() == 3 * 4 + 4 * std::pow(4,ref_level));
}
}
TEST_CASE("DividingPlaneMesh", "[NCMesh]")
{
auto refine_attribute = [](Mesh& mesh, int attr, int ref_level)
{
for (int r = 0; r < ref_level; r++)
{
Array<int> el_to_refine;
for (int n = 0; n < mesh.GetNE(); n++)
{
if (mesh.GetAttribute(n) == attr)
{
el_to_refine.Append(n);
}
}
mesh.GeneralRefinement(el_to_refine);
}
};
SECTION("Hex")
{
auto mesh = DividingPlaneMesh(false);
mesh.EnsureNCMesh(true);
CHECK(mesh.GetNBE() == 2 * 5 + 1);
CHECK(mesh.GetNE() == 2);
auto attr = GENERATE(1,2);
auto ref_level = GENERATE(1,2);
const int num_vert = ref_level == 1 ? 5*5 : 9*9;
const int num_edge = ref_level == 1 ? 2*4*5 : 2*8*9;
const int num_face = ref_level == 1 ? 4*4 : 8*8;
SECTION("H1Hex")
{
mesh.UniformRefinement();
CHECK(CountEssentialDof<H1_FECollection, true>(mesh, 1,
mesh.bdr_attributes.Max()) == 3*3);
CHECK(CountEssentialDof<H1_FECollection, true>(mesh, 2,
mesh.bdr_attributes.Max()) == 5*5);
CHECK(CountEssentialDof<H1_FECollection, true>(mesh, 3,
mesh.bdr_attributes.Max()) == 7*7);
CHECK(CountEssentialDof<H1_FECollection, false>(mesh, 1,
mesh.bdr_attributes.Max()) == 3*3);
CHECK(CountEssentialDof<H1_FECollection, false>(mesh, 2,
mesh.bdr_attributes.Max()) == 5*5);
CHECK(CountEssentialDof<H1_FECollection, false>(mesh, 3,
mesh.bdr_attributes.Max()) == 7*7);
refine_attribute(mesh, attr, ref_level);
CHECK(CountEssentialDof<H1_FECollection>(mesh, 1,
mesh.bdr_attributes.Max()) == 3*3);
CHECK(CountEssentialDof<H1_FECollection>(mesh, 2,
mesh.bdr_attributes.Max()) == 5*5);
CHECK(CountEssentialDof<H1_FECollection>(mesh, 3,
mesh.bdr_attributes.Max()) == 7*7);
// Add the slave face dofs, then subtract off the vertex dofs which are
// double counted due to being shared.
CHECK(CountEssentialDof<H1_FECollection, false>(mesh, 1,
mesh.bdr_attributes.Max()) == 3*3 + num_vert - 3*3);
CHECK(CountEssentialDof<H1_FECollection, false>(mesh, 2,
mesh.bdr_attributes.Max()) == 5*5 + num_vert + num_edge + num_face - 3*3);
CHECK(CountEssentialDof<H1_FECollection, false>(mesh, 3,
mesh.bdr_attributes.Max()) == 7*7 + num_vert + 2*num_edge + 4*num_face - 3*3);
}
SECTION("NDHex")
{
CHECK(CountEssentialDof<ND_FECollection, true>(mesh, 1,
mesh.bdr_attributes.Max()) == 4);
CHECK(CountEssentialDof<ND_FECollection, true>(mesh, 2,
mesh.bdr_attributes.Max()) == 4*2 + 2*2);
CHECK(CountEssentialDof<ND_FECollection, true>(mesh, 3,
mesh.bdr_attributes.Max()) == 4*3 + 2*2*3);
CHECK(CountEssentialDof<ND_FECollection, false>(mesh, 1,
mesh.bdr_attributes.Max()) == 4);
CHECK(CountEssentialDof<ND_FECollection, false>(mesh, 2,
mesh.bdr_attributes.Max()) == 4*2 + 2*2);
CHECK(CountEssentialDof<ND_FECollection, false>(mesh, 3,
mesh.bdr_attributes.Max()) == 4*3 + 2*2*3);
mesh.UniformRefinement();
const int initial_num_edge = 12;
const int initial_num_face = 4;
CHECK(CountEssentialDof<ND_FECollection, true>(mesh, 1,
mesh.bdr_attributes.Max()) == initial_num_edge);
CHECK(CountEssentialDof<ND_FECollection, true>(mesh, 2,
mesh.bdr_attributes.Max()) == initial_num_edge*2 + initial_num_face*2*2);
CHECK(CountEssentialDof<ND_FECollection, true>(mesh, 3,
mesh.bdr_attributes.Max()) == initial_num_edge*3 + initial_num_face*2*2*3);
CHECK(CountEssentialDof<ND_FECollection, false>(mesh, 1,
mesh.bdr_attributes.Max()) == initial_num_edge);
CHECK(CountEssentialDof<ND_FECollection, false>(mesh, 2,
mesh.bdr_attributes.Max()) == initial_num_edge*2 + initial_num_face*2*2);
CHECK(CountEssentialDof<ND_FECollection, false>(mesh, 3,
mesh.bdr_attributes.Max()) == initial_num_edge*3 + initial_num_face*2*2*3);
refine_attribute(mesh, attr, ref_level);
CHECK(CountEssentialDof<ND_FECollection, true>(mesh, 1,
mesh.bdr_attributes.Max()) == initial_num_edge);
CHECK(CountEssentialDof<ND_FECollection, true>(mesh, 2,
mesh.bdr_attributes.Max()) == initial_num_edge*2 + initial_num_face*2*2);
CHECK(CountEssentialDof<ND_FECollection, true>(mesh, 3,
mesh.bdr_attributes.Max()) == initial_num_edge*3 + initial_num_face*2*2*3);
CHECK(CountEssentialDof<ND_FECollection, false>(mesh, 1,
mesh.bdr_attributes.Max()) == (num_edge+initial_num_edge));
CHECK(CountEssentialDof<ND_FECollection, false>(mesh, 2,
mesh.bdr_attributes.Max()) == (num_edge+initial_num_edge)*2 +
(num_face+initial_num_face)*2*2);
CHECK(CountEssentialDof<ND_FECollection, false>(mesh, 3,
mesh.bdr_attributes.Max()) == (num_edge+initial_num_edge)*3 +
(num_face+initial_num_face)*2*2*3);
}
}
SECTION("Tet")
{
auto mesh = DividingPlaneMesh(true);
mesh.EnsureNCMesh(true);
CHECK(mesh.GetNBE() == 2 * 5 * 2 + 2);
CHECK(mesh.GetNE() == 2 * 6);
auto attr = GENERATE(1,2);
auto ref_level = GENERATE(1,2);
CAPTURE(attr);
CAPTURE(ref_level);
const int initial_num_vert = 4;
const int initial_num_edge = 5;
const int initial_num_face = 2;
const int num_vert = ref_level == 1 ? 9 : 25;
const int num_edge = ref_level == 1 ? 16 : 56;
const int num_face = ref_level == 1 ? 8 : 32;
SECTION("H1Tet")
{
CHECK(CountEssentialDof<H1_FECollection>(mesh, 1,
mesh.bdr_attributes.Max()) == initial_num_vert);
CHECK(CountEssentialDof<H1_FECollection>(mesh, 2,
mesh.bdr_attributes.Max()) == initial_num_vert + initial_num_edge);
CHECK(CountEssentialDof<H1_FECollection>(mesh, 3,
mesh.bdr_attributes.Max()) == initial_num_vert + 2*initial_num_edge +
initial_num_face);
CHECK(CountEssentialDof<H1_FECollection>(mesh, 4,
mesh.bdr_attributes.Max()) == initial_num_vert + 3*initial_num_edge +
3*initial_num_face);
CHECK(CountEssentialDof<H1_FECollection, false>(mesh, 1,
mesh.bdr_attributes.Max()) == initial_num_vert);
CHECK(CountEssentialDof<H1_FECollection, false>(mesh, 2,
mesh.bdr_attributes.Max()) == initial_num_vert + initial_num_edge);
CHECK(CountEssentialDof<H1_FECollection, false>(mesh, 3,
mesh.bdr_attributes.Max()) == initial_num_vert + 2*initial_num_edge +
initial_num_face);
CHECK(CountEssentialDof<H1_FECollection, false>(mesh, 4,
mesh.bdr_attributes.Max()) == initial_num_vert + 3*initial_num_edge +
3*initial_num_face);
refine_attribute(mesh, attr, ref_level);
CHECK(CountEssentialDof<H1_FECollection>(mesh, 1,
mesh.bdr_attributes.Max()) == initial_num_vert);
CHECK(CountEssentialDof<H1_FECollection>(mesh, 2,
mesh.bdr_attributes.Max()) == initial_num_vert + initial_num_edge);
CHECK(CountEssentialDof<H1_FECollection>(mesh, 3,
mesh.bdr_attributes.Max()) == initial_num_vert + 2*initial_num_edge +
initial_num_face);
CHECK(CountEssentialDof<H1_FECollection>(mesh, 4,
mesh.bdr_attributes.Max()) == initial_num_vert + 3*initial_num_edge +
3*initial_num_face);
CHECK(CountEssentialDof<H1_FECollection, false>(mesh, 1,
mesh.bdr_attributes.Max()) == num_vert);
CHECK(CountEssentialDof<H1_FECollection, false>(mesh, 2,
mesh.bdr_attributes.Max()) == num_vert + num_edge + initial_num_edge);
CHECK(CountEssentialDof<H1_FECollection, false>(mesh, 3,
mesh.bdr_attributes.Max()) == num_vert + 2*num_edge + num_face +
2*initial_num_edge + initial_num_face);
CHECK(CountEssentialDof<H1_FECollection, false>(mesh, 4,
mesh.bdr_attributes.Max()) == num_vert + 3*num_edge + 3*num_face +
3*initial_num_edge + 3*initial_num_face);
}
SECTION("NDTet")
{
CHECK(CountEssentialDof<ND_FECollection>(mesh, 1,
mesh.bdr_attributes.Max()) == 5);
CHECK(CountEssentialDof<ND_FECollection>(mesh, 2,
mesh.bdr_attributes.Max()) == 14);
CHECK(CountEssentialDof<ND_FECollection>(mesh, 3,
mesh.bdr_attributes.Max()) == 27);
CHECK(CountEssentialDof<ND_FECollection>(mesh, 4,
mesh.bdr_attributes.Max()) == 44);
CHECK(CountEssentialDof<ND_FECollection, false>(mesh, 1,
mesh.bdr_attributes.Max()) == 5);
CHECK(CountEssentialDof<ND_FECollection, false>(mesh, 2,
mesh.bdr_attributes.Max()) == 14);
CHECK(CountEssentialDof<ND_FECollection, false>(mesh, 3,
mesh.bdr_attributes.Max()) == 27);
CHECK(CountEssentialDof<ND_FECollection, false>(mesh, 4,
mesh.bdr_attributes.Max()) == 44);
refine_attribute(mesh, attr, ref_level);
CHECK(CountEssentialDof<ND_FECollection>(mesh, 1,
mesh.bdr_attributes.Max()) == 5);
CHECK(CountEssentialDof<ND_FECollection>(mesh, 2,
mesh.bdr_attributes.Max()) == 14);
CHECK(CountEssentialDof<ND_FECollection>(mesh, 3,
mesh.bdr_attributes.Max()) == 27);
CHECK(CountEssentialDof<ND_FECollection>(mesh, 4,
mesh.bdr_attributes.Max()) == 44);
CHECK(CountEssentialDof<ND_FECollection, false>(mesh, 1,
mesh.bdr_attributes.Max()) == 5 + num_edge);
CHECK(CountEssentialDof<ND_FECollection, false>(mesh, 2,
mesh.bdr_attributes.Max()) == 14 + 2 * num_edge + 2*num_face);
CHECK(CountEssentialDof<ND_FECollection, false>(mesh, 3,
mesh.bdr_attributes.Max()) == 27 + 3 * num_edge + 2*3*num_face);
}
}
}
TEST_CASE("TetFaceFlips", "[NCMesh]")
{
auto orientation = GENERATE(1,3,5);
CAPTURE(orientation);
auto smesh = OrientedTriFaceMesh(orientation, true);
// A smooth function in each vector component
constexpr int order = 3, dim = 3, quadrature_order = 4;
constexpr real_t kappa = 2 * M_PI;
auto E_exact = [=](const Vector &x, Vector &E)
{
E(0) = cos(kappa * x(1));
E(1) = cos(kappa * x(2));
E(2) = cos(kappa * x(0));
};
VectorFunctionCoefficient E_coeff(dim, E_exact);
auto check_serial_nd_conformal = [&](Mesh &mesh, int num_essential_tdof,
int num_essential_vdof)
{
ND_FECollection fe_collection(order, dim);
FiniteElementSpace fe_space(&mesh, &fe_collection);
GridFunction E(&fe_space);
E.ProjectCoefficient(E_coeff);
auto *P = fe_space.GetProlongationMatrix();
if (P != nullptr)
{
// Projection does not respect the non-conformal constraints. Extract
// the true (conformal) and prolongate to get the NC respecting
// projection.
auto E_true = E.GetTrueVector();
P->Mult(E_true, E);
}
mesh.EnsureNodes();
GridFunction * const coords = mesh.GetNodes();
const auto &ir = IntRules.Get(Geometry::Type::TRIANGLE, quadrature_order);
IntegrationRule left_eir(ir.GetNPoints()),
right_eir(ir.GetNPoints()); // element integration rules
Array<int> bdr_attr_is_ess = mesh.bdr_attributes, tdof_list;
bdr_attr_is_ess = 0;
bdr_attr_is_ess.Last() = 1;
fe_space.GetEssentialTrueDofs(bdr_attr_is_ess, tdof_list);
Array<int> ess_vdof_marker, vdof_list;
fe_space.GetEssentialVDofs(bdr_attr_is_ess, ess_vdof_marker);
fe_space.MarkerToList(ess_vdof_marker, vdof_list);
CHECK(num_essential_tdof == tdof_list.Size());
if (num_essential_vdof != -1)
{
CHECK(num_essential_vdof == vdof_list.Size());
}
bool y_valid = true, z_valid = true;
for (int n = 0; n < mesh.GetNBE(); n++)
{
// NOTE: only works for internal boundaries
if (bdr_attr_is_ess[mesh.GetBdrAttribute(n) - 1])
{
auto f = mesh.GetBdrElementFaceIndex(n);
auto &face_element_transform = *mesh.GetFaceElementTransformations(f);
if (face_element_transform.Elem2 == nullptr)
{
// not internal, nothing to check.
continue;
}
face_element_transform.Loc1.Transform(ir, left_eir);
face_element_transform.Loc2.Transform(ir, right_eir);
constexpr real_t tol = 1e-14;
REQUIRE(left_eir.GetNPoints() == ir.GetNPoints());
REQUIRE(right_eir.GetNPoints() == ir.GetNPoints());
Vector left_val, right_val;
for (int i = 0; i < ir.GetNPoints(); i++)
{
face_element_transform.Elem1->SetIntPoint(&left_eir[i]);
coords->GetVectorValue(*face_element_transform.Elem1, left_eir[i], left_val);
coords->GetVectorValue(*face_element_transform.Elem1, left_eir[i], right_val);
REQUIRE(std::abs(left_val(0) - right_val(0)) < tol);
REQUIRE(std::abs(left_val(1) - right_val(1)) < tol);
REQUIRE(std::abs(left_val(2) - right_val(2)) < tol);
E.GetVectorValue(*face_element_transform.Elem1, left_eir[i], left_val);
face_element_transform.Elem2->SetIntPoint(&right_eir[i]);
E.GetVectorValue(*face_element_transform.Elem2, right_eir[i], right_val);
// Check that the second and third rows agree. The y and z should
// agree as the normal is in the x direction
y_valid &= (std::abs(left_val(1) - right_val(1)) < tol);
z_valid &= (std::abs(left_val(2) - right_val(2)) < tol);
}
}
}
CHECK(y_valid);
CHECK(z_valid);
};
SECTION("Conformal")
{
const int ntdof = 3*3 + 3*2;
const int nvdof = ntdof;
check_serial_nd_conformal(smesh, ntdof, nvdof);
}
SECTION("Nonconformal")
{
smesh.EnsureNCMesh(true);
const int ntdof = 3*3 + 3*2;
const int nvdof = ntdof;
check_serial_nd_conformal(smesh, ntdof, nvdof);
}
SECTION("ConformalUniformRefined")
{
smesh.UniformRefinement();
const int ntdof = 9*3 + 4*3*2;
const int nvdof = ntdof;
check_serial_nd_conformal(smesh, ntdof, nvdof);
}
SECTION("NonconformalUniformRefined")
{
smesh.EnsureNCMesh(true);
smesh.UniformRefinement();
const int ntdof = 9*3 + 4*3*2;
const int nvdof = ntdof;
check_serial_nd_conformal(smesh, ntdof, nvdof);
}
SECTION("NonconformalRefined")
{
smesh.EnsureNCMesh(true);
int ref_level = GENERATE(1, 2);
CAPTURE(ref_level);
for (int r = 0; r < ref_level; r++)
{
Array<int> el_to_refine;
for (int n = 0; n < smesh.GetNE(); n++)
{
if (smesh.GetAttribute(n) == 2)
{
el_to_refine.Append(n);
}
}
smesh.GeneralRefinement(el_to_refine);
}
const int ntdof = 3*3 + 3*2;
const int nvdof = ntdof + (ref_level == 1 ? 9*3 + 4*3*2 : 30*3 + 16*3*2);
check_serial_nd_conformal(smesh, ntdof, nvdof);
}
SECTION("NonconformalLevelTwoRefined")
{
smesh.EnsureNCMesh(true);
Array<int> el_to_refine;
smesh.UniformRefinement();
const int ntdof = 9*3 + 4*3*2;
el_to_refine.SetSize(1);
auto n = GENERATE(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15);
auto m = GENERATE(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22);
if (n < smesh.GetNE() && smesh.GetAttribute(n) == 2)
{
el_to_refine[0] = n;
CAPTURE(n);
smesh.GeneralRefinement(el_to_refine);
check_serial_nd_conformal(smesh, ntdof, -1);
if (smesh.GetAttribute(m) == 2)
{
el_to_refine[0] = m;
CAPTURE(m);
smesh.GeneralRefinement(el_to_refine);
check_serial_nd_conformal(smesh, ntdof, -1);
}
}
}
}
TEST_CASE("RP=I", "[NCMesh]")
{
auto check_fespace = [](const FiniteElementSpace& fespace)
{
auto * const R = fespace.GetConformingRestriction();
auto * const P = fespace.GetConformingProlongation();
REQUIRE(R != nullptr);
REQUIRE(P != nullptr);
// Vector notation
Vector e_i(R->Height()), e_j(P->Width());
Vector Rrow(R->Width()), Pcol(P->Height());
bool valid = true;
for (int i = 0; i < R->Height(); i++)
{
e_i = 0.0;
e_i.HostReadWrite();
e_i(i) = 1.0;
R->MultTranspose(e_i, Rrow);
for (int j = 0; j < P->Width(); j++)
{
e_j = 0.0;
e_j.HostReadWrite();
e_j(j) = 1.0;
P->Mult(e_j, Pcol);
valid &= (Rrow * Pcol == (i == j ? 1.0 : 0.0));
}
}
CHECK(valid);
// Index notation
CHECK(R->Height() == P->Width());
CHECK(R->Width() == P->Height());
valid = true;
for (int i = 0; i < R->Height(); i++)
for (int j = 0; j < P->Width(); j++)
{
real_t dot = 0.0;
for (int k = 0; k < R->Width(); k++)
{
dot += (*R)(i,k)*(*P)(k,j);
}
CHECK(dot == (i == j ? 1.0 : 0.0));
valid &= (dot == (i == j ? 1.0 : 0.0));
}
CHECK(valid);
};
SECTION("Hex")
{
const int dim = 3;
const int order = GENERATE(1, 2);
// Split the hex into a pair, then isotropically refine one of them.
Mesh mesh("../../data/ref-cube.mesh");
Array<Refinement> refinements(1);
refinements[0].Set(0, Refinement::X);
mesh.GeneralRefinement(refinements);
refinements[0].SetType(Refinement::XYZ);
mesh.GeneralRefinement(refinements);
SECTION("ND")
{
ND_FECollection fec(order, dim);
FiniteElementSpace fespace(&mesh, &fec);
check_fespace(fespace);
}
SECTION("H1")
{
H1_FECollection fec(order, dim);
FiniteElementSpace fespace(&mesh, &fec);
check_fespace(fespace);
}
}
SECTION("Tet")
{
const int dim = 3;
const int order = GENERATE(1, 2);
// Split the hex into a pair, then isotropically refine one of them.
Mesh mesh("../../data/ref-tetrahedron.mesh");
Array<Refinement> refinements(1);
refinements[0].Set(0, Refinement::X);
mesh.GeneralRefinement(refinements);
mesh.EnsureNCMesh(true);
refinements[0].SetType(Refinement::XYZ);
mesh.GeneralRefinement(refinements);
SECTION("ND")
{
ND_FECollection fec(order, dim);
FiniteElementSpace fespace(&mesh, &fec);
check_fespace(fespace);
}
SECTION("H1")
{
H1_FECollection fec(order, dim);
FiniteElementSpace fespace(&mesh, &fec);
check_fespace(fespace);
}
}
}
TEST_CASE("InternalBoundaryProjectBdrCoefficient", "[NCMesh]")
{
auto test_project_H1 = [](Mesh &mesh, int order, double coef)
{
MFEM_ASSERT(std::abs(coef) > 0,
"Non zero coef value required for meaningful test.");
H1_FECollection fe_collection(order, mesh.SpaceDimension());
FiniteElementSpace fe_space(&mesh, &fe_collection);
GridFunction x(&fe_space);
x = -coef;
ConstantCoefficient c(coef);
// Check projecting on the internal face sets essential dof.
Array<int> ess_bdr(mesh.bdr_attributes.Max());
ess_bdr = 0;
ess_bdr.Last() = 1; // internal boundary
x.ProjectBdrCoefficient(c, ess_bdr);
Array<int> ess_vdofs_list, ess_vdofs_marker;
fe_space.GetEssentialVDofs(ess_bdr, ess_vdofs_marker);
fe_space.MarkerToList(ess_vdofs_marker, ess_vdofs_list);
for (auto ess_dof : ess_vdofs_list)
{
CHECK(x[ess_dof] == Approx(coef).epsilon(1e-8));
}
int iess = 0;
for (int i = 0; i < x.Size(); i++)
{
if (iess < ess_vdofs_list.Size() && i == ess_vdofs_list[iess])
{
iess++;
continue;
}
CHECK(x[i] == Approx(-coef).epsilon(1e-8));
}
};
auto OneSidedNCRefine = [](Mesh &mesh)
{
// Pick one element attached to the new boundary attribute and refine.
const auto interface_attr = mesh.bdr_attributes.Max();
Array<int> el_to_ref;
for (int nbe = 0; nbe < mesh.GetNBE(); nbe++)
{
if (mesh.GetBdrAttribute(nbe) == interface_attr)
{
int f, o, e1, e2;
mesh.GetBdrElementFace(nbe, &f, &o);
mesh.GetFaceElements(f, &e1, &e2);
el_to_ref.Append(e1);
}
}
mesh.GeneralRefinement(el_to_ref);
return;
};
SECTION("Hex")
{
auto smesh = DividingPlaneMesh(false, true);
smesh.EnsureNCMesh(true);
OneSidedNCRefine(smesh);
test_project_H1(smesh, 2, 0.25);
}
SECTION("Tet")
{
auto smesh = DividingPlaneMesh(true, true);
smesh.EnsureNCMesh(true);
OneSidedNCRefine(smesh);
test_project_H1(smesh, 3, 0.25);
}
}
} // namespace mfem