1129 lines
38 KiB
C++
1129 lines
38 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"
|
|
#include "mesh_test_utils.hpp"
|
|
|
|
using namespace mfem;
|
|
|
|
#ifdef MFEM_USE_MPI
|
|
|
|
namespace ParSubMeshTests
|
|
{
|
|
|
|
void CHECK_GLOBAL_NORM(Vector &v, bool small = true)
|
|
{
|
|
real_t norm_local = v.Norml2(), norm_global = 0.0;
|
|
MPI_Allreduce(&norm_local, &norm_global, 1, MPITypeMap<real_t>::mpi_type,
|
|
MPI_SUM, MPI_COMM_WORLD);
|
|
if (small)
|
|
{
|
|
REQUIRE(norm_global < 1e-8);
|
|
}
|
|
else
|
|
{
|
|
REQUIRE(norm_global > 1e-8);
|
|
}
|
|
};
|
|
|
|
|
|
FiniteElementCollection *create_surf_fec(FECType fectype, int p, int dim)
|
|
{
|
|
switch (fectype)
|
|
{
|
|
case FECType::H1:
|
|
return new H1_FECollection(p, dim);
|
|
case FECType::ND:
|
|
return new ND_FECollection(p, dim);
|
|
case FECType::RT:
|
|
return new L2_FECollection(p - 1, dim, BasisType::GaussLegendre,
|
|
FiniteElement::INTEGRAL);
|
|
case FECType::L2:
|
|
return new L2_FECollection(p, dim, BasisType::GaussLobatto);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
class SurfaceNormalCoef : public VectorCoefficient
|
|
{
|
|
public:
|
|
SurfaceNormalCoef(int dim) : VectorCoefficient(dim) {}
|
|
|
|
using VectorCoefficient::Eval;
|
|
|
|
void Eval(Vector &V, ElementTransformation &T,
|
|
const IntegrationPoint &ip) override
|
|
{
|
|
V.SetSize(vdim);
|
|
CalcOrtho(T.Jacobian(), V);
|
|
V /= V.Norml2();
|
|
}
|
|
|
|
};
|
|
|
|
void multidomain_test_2d(FECType fec_type)
|
|
{
|
|
constexpr int dim = 2;
|
|
const int p = 2;
|
|
real_t Hy = 1.0;
|
|
Mesh serial_parent_mesh = Mesh::MakeCartesian2D(5, 5,
|
|
Element::QUADRILATERAL,
|
|
true, 1.0, Hy,
|
|
false);
|
|
|
|
for (int i = 0; i < serial_parent_mesh.GetNBE(); i++)
|
|
{
|
|
Element *el = serial_parent_mesh.GetBdrElement(i);
|
|
el->SetAttribute(1);
|
|
|
|
Array<int> vertices;
|
|
el->GetVertices(vertices);
|
|
|
|
bool all_vtx_inside = true;
|
|
for (int j = 0; j < vertices.Size(); j++)
|
|
{
|
|
if (serial_parent_mesh.GetVertex(vertices[j])[1] < 1.0)
|
|
{
|
|
all_vtx_inside = false;
|
|
}
|
|
}
|
|
if (all_vtx_inside)
|
|
{
|
|
el->SetAttribute(2);
|
|
}
|
|
}
|
|
for (int i = 0; i < serial_parent_mesh.GetNE(); i++)
|
|
{
|
|
Element *el = serial_parent_mesh.GetElement(i);
|
|
el->SetAttribute(1);
|
|
|
|
Array<int> vertices;
|
|
el->GetVertices(vertices);
|
|
|
|
for (int j = 0; j < vertices.Size(); j++)
|
|
{
|
|
real_t *coords = serial_parent_mesh.GetVertex(vertices[j]);
|
|
|
|
if (coords[0] >= 0.25 &&
|
|
coords[0] <= 0.75 &&
|
|
coords[1] >= 0.25 &&
|
|
coords[1] <= 0.75)
|
|
{
|
|
el->SetAttribute(2);
|
|
}
|
|
}
|
|
}
|
|
serial_parent_mesh.SetAttributes();
|
|
serial_parent_mesh.EnsureNodes();
|
|
serial_parent_mesh.SetCurvature(p);
|
|
|
|
auto node_movement_coeff = VectorFunctionCoefficient(
|
|
serial_parent_mesh.Dimension(),
|
|
[](const Vector &coords, Vector &u)
|
|
{
|
|
real_t x = coords(0);
|
|
real_t y = coords(1);
|
|
|
|
u(0) = x;
|
|
u(1) = y + 0.05 * sin(x * 2.0 * M_PI);
|
|
});
|
|
|
|
serial_parent_mesh.Transform(node_movement_coeff);
|
|
|
|
ParMesh parent_mesh(MPI_COMM_WORLD, serial_parent_mesh);
|
|
|
|
Array<int> domain1(1);
|
|
domain1[0] = 1;
|
|
|
|
Array<int> boundary1(1);
|
|
boundary1[0] = 2;
|
|
|
|
auto domain_submesh = ParSubMesh::CreateFromDomain(parent_mesh,
|
|
domain1);
|
|
|
|
auto boundary_submesh = ParSubMesh::CreateFromBoundary(parent_mesh,
|
|
boundary1);
|
|
|
|
FiniteElementCollection *fec = create_fec(fec_type, p,
|
|
parent_mesh.Dimension());
|
|
|
|
ParFiniteElementSpace parent_fes(&parent_mesh, fec);
|
|
ParGridFunction parent_gf(&parent_fes);
|
|
ParGridFunction parent_gf_ex(&parent_fes);
|
|
|
|
ParFiniteElementSpace domain1_fes(&domain_submesh, fec);
|
|
ParGridFunction domain1_gf(&domain1_fes);
|
|
ParGridFunction domain1_gf_ex(&domain1_fes);
|
|
|
|
FiniteElementCollection *surface_fec =
|
|
create_surf_fec(fec_type, p, boundary_submesh.Dimension());
|
|
ParFiniteElementSpace boundary1_fes(&boundary_submesh, surface_fec);
|
|
ParGridFunction boundary1_gf(&boundary1_fes);
|
|
ParGridFunction boundary1_gf_ex(&boundary1_fes);
|
|
|
|
auto coeff = FunctionCoefficient([](const Vector &coords)
|
|
{
|
|
real_t x = coords(0);
|
|
real_t y = coords(1);
|
|
return y + 0.05 * sin(x * 2.0 * M_PI);
|
|
});
|
|
|
|
auto vcoeff = VectorFunctionCoefficient(dim, [](const Vector &coords,
|
|
Vector &V)
|
|
{
|
|
V.SetSize(2);
|
|
real_t x = coords(0);
|
|
real_t y = coords(1);
|
|
|
|
V(0) = y + 0.05 * sin(x * 2.0 * M_PI);
|
|
V(1) = x + 0.05 * sin(y * 2.0 * M_PI);
|
|
});
|
|
|
|
SurfaceNormalCoef normalcoeff(dim);
|
|
InnerProductCoefficient nvcoeff(normalcoeff, vcoeff);
|
|
|
|
if (fec_type == FECType::H1 || fec_type == FECType::L2)
|
|
{
|
|
parent_gf.ProjectCoefficient(coeff);
|
|
parent_gf_ex.ProjectCoefficient(coeff);
|
|
domain1_gf_ex.ProjectCoefficient(coeff);
|
|
boundary1_gf_ex.ProjectCoefficient(coeff);
|
|
}
|
|
else if (fec_type == FECType::ND)
|
|
{
|
|
parent_gf.ProjectCoefficient(vcoeff);
|
|
parent_gf_ex.ProjectCoefficient(vcoeff);
|
|
domain1_gf_ex.ProjectCoefficient(vcoeff);
|
|
boundary1_gf_ex.ProjectCoefficient(vcoeff);
|
|
}
|
|
else
|
|
{
|
|
parent_gf.ProjectCoefficient(vcoeff);
|
|
parent_gf_ex.ProjectCoefficient(vcoeff);
|
|
domain1_gf_ex.ProjectCoefficient(vcoeff);
|
|
boundary1_gf_ex.ProjectCoefficient(nvcoeff);
|
|
}
|
|
|
|
Vector tmp;
|
|
|
|
|
|
SECTION("ParentToSubMesh")
|
|
{
|
|
SECTION("Volume to matching volume")
|
|
{
|
|
ParSubMesh::Transfer(parent_gf, domain1_gf);
|
|
tmp = domain1_gf_ex;
|
|
tmp -= domain1_gf;
|
|
CHECK_GLOBAL_NORM(tmp);
|
|
}
|
|
SECTION("Volume to matching surface")
|
|
{
|
|
ParSubMesh::Transfer(parent_gf, boundary1_gf);
|
|
tmp = boundary1_gf_ex;
|
|
tmp -= boundary1_gf;
|
|
CHECK_GLOBAL_NORM(tmp);
|
|
}
|
|
}
|
|
SECTION("SubMeshToParent")
|
|
{
|
|
SECTION("Volume to matching volume")
|
|
{
|
|
if (fec_type == FECType::H1 || fec_type == FECType::L2)
|
|
{
|
|
parent_gf.ProjectCoefficient(coeff);
|
|
domain1_gf.ProjectCoefficient(coeff);
|
|
}
|
|
else
|
|
{
|
|
parent_gf.ProjectCoefficient(vcoeff);
|
|
domain1_gf.ProjectCoefficient(vcoeff);
|
|
}
|
|
ParSubMesh::Transfer(domain1_gf, parent_gf);
|
|
tmp = parent_gf_ex;
|
|
tmp -= parent_gf;
|
|
CHECK_GLOBAL_NORM(tmp);
|
|
}
|
|
SECTION("Surface to matching surface in volume")
|
|
{
|
|
if (fec_type == FECType::H1 || fec_type == FECType::L2)
|
|
{
|
|
boundary1_gf.ProjectCoefficient(coeff);
|
|
}
|
|
else if (fec_type == FECType::ND)
|
|
{
|
|
boundary1_gf.ProjectCoefficient(vcoeff);
|
|
}
|
|
else
|
|
{
|
|
boundary1_gf.ProjectCoefficient(nvcoeff);
|
|
}
|
|
ParSubMesh::Transfer(boundary1_gf, parent_gf);
|
|
tmp = parent_gf_ex;
|
|
tmp -= parent_gf;
|
|
CHECK_GLOBAL_NORM(tmp);
|
|
}
|
|
}
|
|
delete surface_fec;
|
|
delete fec;
|
|
}
|
|
|
|
void multidomain_test_3d(FECType fec_type)
|
|
{
|
|
constexpr int dim = 3;
|
|
const int p = 2;
|
|
// Circle: sideset 1
|
|
// Domain boundary: sideset 2
|
|
Mesh *serial_parent_mesh = new
|
|
Mesh("../../miniapps/multidomain/multidomain-hex.mesh");
|
|
ParMesh parent_mesh(MPI_COMM_WORLD, *serial_parent_mesh);
|
|
delete serial_parent_mesh;
|
|
|
|
Array<int> cylinder_domain_attributes(1);
|
|
cylinder_domain_attributes[0] = 1;
|
|
|
|
Array<int> outer_domain_attributes(1);
|
|
outer_domain_attributes[0] = 2;
|
|
|
|
Array<int> cylinder_surface_attributes(1);
|
|
cylinder_surface_attributes[0] = 9;
|
|
|
|
auto cylinder_submesh =
|
|
ParSubMesh::CreateFromDomain(parent_mesh, cylinder_domain_attributes);
|
|
|
|
auto outer_submesh =
|
|
ParSubMesh::CreateFromDomain(parent_mesh, outer_domain_attributes);
|
|
|
|
auto cylinder_surface_submesh =
|
|
ParSubMesh::CreateFromBoundary(parent_mesh, cylinder_surface_attributes);
|
|
|
|
Array<int> cylinder_cyl_surf_marker(cylinder_submesh.bdr_attributes.Max());
|
|
cylinder_cyl_surf_marker = 0;
|
|
cylinder_cyl_surf_marker[8] = 1;
|
|
|
|
Array<int> outer_cyl_surf_marker(outer_submesh.bdr_attributes.Max());
|
|
outer_cyl_surf_marker = 0;
|
|
outer_cyl_surf_marker[8] = 1;
|
|
|
|
int num_local_be = cylinder_surface_submesh.GetNBE();
|
|
int num_global_be = 0;
|
|
MPI_Allreduce(&num_local_be, &num_global_be, 1, MPI_INT, MPI_SUM,
|
|
MPI_COMM_WORLD);
|
|
REQUIRE(num_global_be == 16);
|
|
REQUIRE(cylinder_surface_submesh.bdr_attributes[0] ==
|
|
parent_mesh.bdr_attributes.Max() + 1);
|
|
|
|
FiniteElementCollection *fec = create_fec(fec_type, p,
|
|
parent_mesh.Dimension());
|
|
|
|
ParFiniteElementSpace parent_fes(&parent_mesh, fec);
|
|
ParGridFunction parent_gf(&parent_fes);
|
|
ParGridFunction parent_gf_ex(&parent_fes);
|
|
|
|
ParFiniteElementSpace cylinder_fes(&cylinder_submesh, fec);
|
|
ParGridFunction cylinder_gf(&cylinder_fes);
|
|
ParGridFunction cylinder_gf_ex(&cylinder_fes);
|
|
|
|
ParFiniteElementSpace outer_fes(&outer_submesh, fec);
|
|
ParGridFunction outer_gf(&outer_fes);
|
|
ParGridFunction outer_gf_ex(&outer_fes);
|
|
|
|
FiniteElementCollection *surface_fec =
|
|
create_surf_fec(fec_type, p, cylinder_surface_submesh.Dimension());
|
|
ParFiniteElementSpace cylinder_surface_fes(&cylinder_surface_submesh,
|
|
surface_fec);
|
|
ParGridFunction cylinder_surface_gf(&cylinder_surface_fes);
|
|
ParGridFunction cylinder_surface_gf_ex(&cylinder_surface_fes);
|
|
|
|
auto coeff = FunctionCoefficient([](const Vector &coords)
|
|
{
|
|
real_t x = coords(0);
|
|
real_t y = coords(1);
|
|
real_t z = coords(2);
|
|
return y + 0.05 * sin(x * 2.0 * M_PI) + z;
|
|
});
|
|
|
|
auto vcoeff = VectorFunctionCoefficient(dim, [](const Vector &coords,
|
|
Vector &V)
|
|
{
|
|
V.SetSize(3);
|
|
real_t x = coords(0);
|
|
real_t y = coords(1);
|
|
real_t z = coords(2);
|
|
|
|
V(0) = y + 0.05 * sin(x * 2.0 * M_PI) + z;
|
|
V(1) = z + 0.05 * sin(y * 2.0 * M_PI) + x;
|
|
V(2) = x + 0.05 * sin(z * 2.0 * M_PI) + y;
|
|
});
|
|
|
|
auto vzerocoeff = VectorFunctionCoefficient(dim, [](const Vector &,
|
|
Vector &V)
|
|
{
|
|
V.SetSize(3);
|
|
V = 0.0;
|
|
});
|
|
|
|
SurfaceNormalCoef normalcoeff(dim);
|
|
InnerProductCoefficient nvcoeff(normalcoeff, vcoeff);
|
|
|
|
if (fec_type == FECType::H1 || fec_type == FECType::L2)
|
|
{
|
|
parent_gf.ProjectCoefficient(coeff);
|
|
parent_gf_ex.ProjectCoefficient(coeff);
|
|
cylinder_gf_ex.ProjectCoefficient(coeff);
|
|
cylinder_surface_gf_ex.ProjectCoefficient(coeff);
|
|
outer_gf_ex.ProjectCoefficient(coeff);
|
|
}
|
|
else if (fec_type == FECType::ND)
|
|
{
|
|
parent_gf.ProjectCoefficient(vcoeff);
|
|
parent_gf_ex.ProjectCoefficient(vcoeff);
|
|
cylinder_gf_ex.ProjectCoefficient(vcoeff);
|
|
cylinder_surface_gf_ex.ProjectCoefficient(vcoeff);
|
|
outer_gf_ex.ProjectCoefficient(vcoeff);
|
|
}
|
|
else
|
|
{
|
|
parent_gf.ProjectCoefficient(vcoeff);
|
|
parent_gf_ex.ProjectCoefficient(vcoeff);
|
|
cylinder_gf_ex.ProjectCoefficient(vcoeff);
|
|
cylinder_surface_gf_ex.ProjectCoefficient(nvcoeff);
|
|
outer_gf_ex.ProjectCoefficient(vcoeff);
|
|
}
|
|
|
|
Vector tmp;
|
|
|
|
SECTION("ParentToSubMesh")
|
|
{
|
|
SECTION("Volume to matching volume")
|
|
{
|
|
ParSubMesh::Transfer(parent_gf, cylinder_gf);
|
|
tmp = cylinder_gf_ex;
|
|
tmp -= cylinder_gf;
|
|
CHECK_GLOBAL_NORM(tmp);
|
|
}
|
|
SECTION("Volume to matching surface")
|
|
{
|
|
ParSubMesh::Transfer(parent_gf, cylinder_surface_gf);
|
|
tmp = cylinder_surface_gf_ex;
|
|
tmp -= cylinder_surface_gf;
|
|
CHECK_GLOBAL_NORM(tmp);
|
|
}
|
|
}
|
|
SECTION("SubMeshToParent")
|
|
{
|
|
SECTION("Volume to matching volume")
|
|
{
|
|
if (fec_type == FECType::H1 || fec_type == FECType::L2)
|
|
{
|
|
parent_gf.ProjectCoefficient(coeff);
|
|
cylinder_gf.ProjectCoefficient(coeff);
|
|
}
|
|
else
|
|
{
|
|
parent_gf.ProjectCoefficient(vcoeff);
|
|
cylinder_gf.ProjectCoefficient(vcoeff);
|
|
}
|
|
ParSubMesh::Transfer(cylinder_gf, parent_gf);
|
|
tmp = parent_gf_ex;
|
|
tmp -= parent_gf;
|
|
CHECK_GLOBAL_NORM(tmp);
|
|
}
|
|
SECTION("Volume to matching volume")
|
|
{
|
|
if (fec_type == FECType::H1 || fec_type == FECType::L2)
|
|
{
|
|
outer_gf.ProjectCoefficient(coeff);
|
|
}
|
|
else
|
|
{
|
|
outer_gf.ProjectCoefficient(vcoeff);
|
|
}
|
|
ParSubMesh::Transfer(outer_gf, parent_gf);
|
|
tmp = parent_gf_ex;
|
|
tmp -= parent_gf;
|
|
CHECK_GLOBAL_NORM(tmp);
|
|
}
|
|
SECTION("Surface to matching surface in volume")
|
|
{
|
|
if (fec_type == FECType::H1 || fec_type == FECType::L2)
|
|
{
|
|
cylinder_surface_gf.ProjectCoefficient(coeff);
|
|
}
|
|
else if (fec_type == FECType::ND)
|
|
{
|
|
cylinder_surface_gf.ProjectCoefficient(vcoeff);
|
|
}
|
|
else
|
|
{
|
|
cylinder_surface_gf.ProjectCoefficient(nvcoeff);
|
|
}
|
|
ParSubMesh::Transfer(cylinder_surface_gf, parent_gf);
|
|
tmp = parent_gf_ex;
|
|
tmp -= parent_gf;
|
|
CHECK_GLOBAL_NORM(tmp);
|
|
}
|
|
}
|
|
SECTION("SubMeshToSubMesh")
|
|
{
|
|
SECTION("Volume to matching volume")
|
|
{
|
|
if (fec_type == FECType::H1 || fec_type == FECType::L2)
|
|
{
|
|
cylinder_gf.ProjectCoefficient(coeff);
|
|
outer_gf.ProjectCoefficient(coeff);
|
|
outer_gf_ex.ProjectCoefficient(coeff);
|
|
}
|
|
else
|
|
{
|
|
cylinder_gf.ProjectCoefficient(vcoeff);
|
|
outer_gf.ProjectCoefficient(vcoeff);
|
|
outer_gf.ProjectBdrCoefficient(vzerocoeff,
|
|
outer_cyl_surf_marker);
|
|
outer_gf_ex.ProjectCoefficient(vcoeff);
|
|
}
|
|
ParSubMesh::Transfer(cylinder_gf, outer_gf);
|
|
tmp = outer_gf_ex;
|
|
tmp -= outer_gf;
|
|
CHECK_GLOBAL_NORM(tmp);
|
|
}
|
|
SECTION("Volume to matching volume (reversed)")
|
|
{
|
|
if (fec_type == FECType::H1 || fec_type == FECType::L2)
|
|
{
|
|
cylinder_gf.ProjectCoefficient(coeff);
|
|
outer_gf.ProjectCoefficient(coeff);
|
|
outer_gf_ex.ProjectCoefficient(coeff);
|
|
}
|
|
else
|
|
{
|
|
outer_gf.ProjectCoefficient(vcoeff);
|
|
cylinder_gf.ProjectCoefficient(vcoeff);
|
|
cylinder_gf.ProjectBdrCoefficient(vzerocoeff,
|
|
cylinder_cyl_surf_marker);
|
|
cylinder_gf_ex.ProjectCoefficient(vcoeff);
|
|
}
|
|
ParSubMesh::Transfer(outer_gf, cylinder_gf);
|
|
tmp = cylinder_gf_ex;
|
|
tmp -= cylinder_gf;
|
|
CHECK_GLOBAL_NORM(tmp);
|
|
}
|
|
SECTION("Volume to matching surface on volume")
|
|
{
|
|
if (fec_type == FECType::H1 || fec_type == FECType::L2)
|
|
{
|
|
cylinder_gf.ProjectCoefficient(coeff);
|
|
outer_gf.ProjectCoefficient(coeff);
|
|
cylinder_gf_ex.ProjectCoefficient(coeff);
|
|
}
|
|
else
|
|
{
|
|
cylinder_gf.ProjectCoefficient(vcoeff);
|
|
outer_gf.ProjectCoefficient(vcoeff);
|
|
cylinder_gf_ex.ProjectCoefficient(vcoeff);
|
|
}
|
|
ParSubMesh::Transfer(outer_gf, cylinder_gf);
|
|
tmp = cylinder_gf_ex;
|
|
tmp -= cylinder_gf;
|
|
CHECK_GLOBAL_NORM(tmp);
|
|
}
|
|
|
|
SECTION("Volume to matching surface")
|
|
{
|
|
if (fec_type == FECType::H1 || fec_type == FECType::L2)
|
|
{
|
|
cylinder_gf.ProjectCoefficient(coeff);
|
|
cylinder_surface_gf_ex.ProjectCoefficient(coeff);
|
|
}
|
|
else if (fec_type == FECType::ND)
|
|
{
|
|
cylinder_gf.ProjectCoefficient(vcoeff);
|
|
cylinder_surface_gf_ex.ProjectCoefficient(vcoeff);
|
|
}
|
|
else
|
|
{
|
|
cylinder_gf.ProjectCoefficient(vcoeff);
|
|
cylinder_surface_gf_ex.ProjectCoefficient(nvcoeff);
|
|
}
|
|
ParSubMesh::Transfer(cylinder_gf, cylinder_surface_gf);
|
|
tmp = cylinder_surface_gf_ex;
|
|
tmp -= cylinder_surface_gf;
|
|
CHECK_GLOBAL_NORM(tmp);
|
|
}
|
|
}
|
|
delete surface_fec;
|
|
delete fec;
|
|
}
|
|
|
|
TEST_CASE("ParSubMesh", "[Parallel],[SubMesh]")
|
|
{
|
|
auto fec_type = GENERATE(FECType::H1, FECType::ND, FECType::RT, FECType::L2);
|
|
multidomain_test_2d(fec_type);
|
|
multidomain_test_3d(fec_type);
|
|
}
|
|
|
|
Array<int> count_be(ParMesh &mesh)
|
|
{
|
|
const int bdr_max = mesh.bdr_attributes.Size() > 0 ?
|
|
mesh.bdr_attributes.Max() : 6;
|
|
|
|
Array<int> counts(bdr_max + 1);
|
|
counts = 0;
|
|
|
|
for (int i=0; i<mesh.GetNBE(); i++)
|
|
{
|
|
counts[mesh.GetBdrAttribute(i)]++;
|
|
}
|
|
|
|
Array<int> glb_counts(bdr_max + 1);
|
|
glb_counts = 0;
|
|
MPI_Reduce(counts, glb_counts, bdr_max + 1,
|
|
MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);
|
|
|
|
return glb_counts;
|
|
}
|
|
|
|
TEST_CASE("ParSubMesh Interior Boundaries", "[Parallel],[SubMesh]")
|
|
{
|
|
// whether to NC refine the attribute 1 elements
|
|
auto make_nc = GENERATE(false, true);
|
|
int num_procs = Mpi::WorldSize();
|
|
Mesh serial_mesh = Mesh::MakeCartesian3D(num_procs, num_procs, 1,
|
|
Element::HEXAHEDRON,
|
|
1.0, 1.0, 0.1, false);
|
|
|
|
// Assign alternating element attributes to each element to create a
|
|
// checkerboard pattern
|
|
for (int i=0; i < serial_mesh.GetNE(); i++)
|
|
{
|
|
int attr = (i + (1 + num_procs % 2) * (i / num_procs)) % 2 + 1;
|
|
serial_mesh.SetAttribute(i, attr);
|
|
}
|
|
int bdr_max = serial_mesh.bdr_attributes.Max();
|
|
|
|
// Label all interior faces as boundary elements
|
|
Array<int> v(4);
|
|
for (int i=0; i < serial_mesh.GetNumFaces(); i++)
|
|
{
|
|
if (serial_mesh.FaceIsInterior(i))
|
|
{
|
|
serial_mesh.GetFaceVertices(i, v);
|
|
serial_mesh.AddBdrQuad(v, bdr_max + i + 1);
|
|
}
|
|
}
|
|
serial_mesh.FinalizeMesh();
|
|
serial_mesh.SetAttributes();
|
|
|
|
// Create an intentionally bad partitioning
|
|
Array<int> partitioning(num_procs * num_procs);
|
|
for (int i = 0; i < num_procs * num_procs; i++)
|
|
{
|
|
// The following creates a shifting pattern where neighboring elements are
|
|
// never owned by the same processor
|
|
partitioning[i] = (2 * num_procs - 1 - (i % num_procs) -
|
|
i / num_procs) % num_procs;
|
|
}
|
|
if (make_nc)
|
|
{
|
|
serial_mesh.EnsureNCMesh(true);
|
|
}
|
|
ParMesh parent_mesh(MPI_COMM_WORLD, serial_mesh, partitioning);
|
|
|
|
if (make_nc)
|
|
{
|
|
// Refine after partitioning so that the checkerboard pattern persists.
|
|
Array<int> el_to_refine;
|
|
for (int i = 0; i < parent_mesh.GetNE(); i++)
|
|
{
|
|
if (parent_mesh.GetAttribute(i) == 1)
|
|
{
|
|
el_to_refine.Append(i);
|
|
}
|
|
}
|
|
parent_mesh.GeneralRefinement(el_to_refine);
|
|
}
|
|
|
|
// Create a pair of domain-based sub meshes
|
|
Array<int> domain1(1);
|
|
domain1[0] = 1;
|
|
Array<int> domain2(1);
|
|
domain2[0] = 2;
|
|
|
|
auto domain1_submesh = ParSubMesh::CreateFromDomain(parent_mesh,
|
|
domain1);
|
|
auto domain2_submesh = ParSubMesh::CreateFromDomain(parent_mesh,
|
|
domain2);
|
|
|
|
// Create histograms of boundary attributes in each sub-domain
|
|
auto be1 = count_be(domain1_submesh);
|
|
auto be2 = count_be(domain2_submesh);
|
|
REQUIRE(((be1.Size() >= 7) && (be2.Size() >= 7)));
|
|
|
|
// Only the root process has valid histograms
|
|
if (Mpi::Root())
|
|
{
|
|
// Verify that all exterior boundary elements were accounted for. If an NC
|
|
// refine has occurred, there will be extra faces on half the checkerboard
|
|
const int num_top_refined = make_nc ? (num_procs/2)*(num_procs/2)
|
|
+ ((num_procs+1)/2)*((num_procs+1)/2) : 0;
|
|
const int num_side_refined = make_nc ? (num_procs+1)/2 : 0;
|
|
CHECK(be1[1] + be2[1] == num_procs * num_procs + 3 * num_top_refined);
|
|
CHECK(be1[2] + be2[2] == num_procs + 3 * num_side_refined);
|
|
CHECK(be1[3] + be2[3] == num_procs + 3 * num_side_refined);
|
|
CHECK(be1[4] + be2[4] == num_procs + 3 * num_side_refined);
|
|
CHECK(be1[5] + be2[5] == num_procs + 3 * num_side_refined);
|
|
CHECK(be1[6] + be2[6] == num_procs * num_procs + 3 * num_top_refined);
|
|
|
|
// Verify that all interior boundary elements of serial mesh appear
|
|
// correct number of times in each submesh
|
|
for (int i=0; i < serial_mesh.GetNumFaces(); i++)
|
|
{
|
|
if (serial_mesh.FaceIsInterior(i))
|
|
{
|
|
const int attr = bdr_max + i + 1;
|
|
REQUIRE(attr < be1.Size());
|
|
REQUIRE(attr < be2.Size());
|
|
CAPTURE(make_nc, i, attr, bdr_max, be1[attr], be2[attr]);
|
|
CHECK(be1[attr] == (make_nc ? 4 : 1));
|
|
CHECK(be2[attr] == 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* @brief Helper class for testing a ParNCMesh
|
|
*
|
|
*/
|
|
struct ParNCMeshExposed : public ParNCMesh
|
|
{
|
|
ParNCMeshExposed(const ParNCMesh &ncmesh) : ParNCMesh(ncmesh) {}
|
|
using ParNCMesh::elements;
|
|
using ParNCMesh::leaf_elements;
|
|
int CountUniqueLeafElements() const
|
|
{
|
|
int local = 0;
|
|
for (auto i : leaf_elements)
|
|
{
|
|
if (elements[i].rank == MyRank)
|
|
{
|
|
local++;
|
|
}
|
|
}
|
|
int global = 0;
|
|
MPI_Allreduce(&local, &global, 1, MPI_INT, MPI_SUM, GetGlobalMPI_Comm());
|
|
return global;
|
|
}
|
|
};
|
|
|
|
void CheckProjectMatch(ParMesh &mesh, ParSubMesh &submesh, FECType fec_type,
|
|
bool check_pr = true)
|
|
{
|
|
int p = 3;
|
|
CAPTURE(fec_type);
|
|
auto fec = std::unique_ptr<FiniteElementCollection>(create_fec(fec_type, p,
|
|
mesh.Dimension()));
|
|
auto sub_fec = std::unique_ptr<FiniteElementCollection>(create_fec(fec_type, p,
|
|
submesh.Dimension()));
|
|
|
|
ParFiniteElementSpace fes(&mesh, fec.get());
|
|
ParFiniteElementSpace sub_fes(&submesh, sub_fec.get());
|
|
ParGridFunction gf(&fes), gf_ext(&fes);
|
|
ParGridFunction sub_gf(&sub_fes), sub_gf_ext(&sub_fes);
|
|
auto coeff = FunctionCoefficient([](const Vector &coords)
|
|
{
|
|
real_t x = coords(0);
|
|
real_t y = coords(1);
|
|
real_t z = coords(2);
|
|
return 0.02 * sin(y * 5.0 * M_PI)
|
|
+ 0.03 * sin(x * 5.0 * M_PI)
|
|
+ 0.05 * sin(z * 5.0 * M_PI);
|
|
});
|
|
|
|
auto vcoeff = VectorFunctionCoefficient(mesh.SpaceDimension(),
|
|
[](const Vector &coords, Vector &V)
|
|
{
|
|
V.SetSize(3);
|
|
real_t x = coords(0);
|
|
real_t y = coords(1);
|
|
real_t z = coords(2);
|
|
|
|
V(0) = 0.02 * sin(y * 3.0 * M_PI)
|
|
+ 0.03 * sin(x * 2.0 * M_PI)
|
|
+ 0.05 * sin(z * 4.0 * M_PI);
|
|
V(1) = 0.02 * sin(z * 3.0 * M_PI)
|
|
+ 0.03 * sin(y * 2.0 * M_PI)
|
|
+ 0.05 * sin(x * 4.0 * M_PI);
|
|
V(2) = 0.02 * sin(x * 3.0 * M_PI)
|
|
+ 0.03 * sin(y * 2.0 * M_PI)
|
|
+ 0.05 * sin(z * 4.0 * M_PI);
|
|
});
|
|
|
|
if (fec_type == FECType::H1 || fec_type == FECType::L2)
|
|
{
|
|
gf.ProjectCoefficient(coeff);
|
|
sub_gf.ProjectCoefficient(coeff);
|
|
}
|
|
else
|
|
{
|
|
gf.ProjectCoefficient(vcoeff);
|
|
sub_gf.ProjectCoefficient(vcoeff);
|
|
}
|
|
gf_ext = gf;
|
|
sub_gf_ext = sub_gf;
|
|
|
|
SECTION("ParentToSubMesh")
|
|
{
|
|
// Direct transfer should be identical
|
|
ParSubMesh::Transfer(gf, sub_gf);
|
|
auto tmp = sub_gf_ext;
|
|
tmp -= sub_gf;
|
|
CHECK_GLOBAL_NORM(tmp);
|
|
}
|
|
SECTION("PRConstraint")
|
|
{
|
|
// Application of PR should be identical in mesh and submesh for an
|
|
// external boundary.
|
|
if (mesh.Nonconforming())
|
|
{
|
|
Vector tmp;
|
|
if (const auto *P = fes.GetProlongationMatrix())
|
|
{
|
|
const auto *R = fes.GetRestrictionMatrix();
|
|
tmp.SetSize(R->Height());
|
|
R->Mult(gf, tmp);
|
|
P->Mult(tmp, gf);
|
|
}
|
|
if (const auto *P = sub_fes.GetProlongationMatrix())
|
|
{
|
|
const auto *R = sub_fes.GetRestrictionMatrix();
|
|
tmp.SetSize(R->Height());
|
|
R->Mult(sub_gf_ext, tmp);
|
|
P->Mult(tmp, sub_gf_ext);
|
|
}
|
|
ParSubMesh::Transfer(gf, sub_gf);
|
|
tmp = sub_gf_ext;
|
|
tmp -= sub_gf;
|
|
CHECK_GLOBAL_NORM(tmp, check_pr);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("VolumeParNCSubMesh", "[Parallel],[SubMesh]")
|
|
{
|
|
bool use_tet = GENERATE(false,true);
|
|
|
|
auto mesh = use_tet ? OrientedTriFaceMesh(1, true) : DividingPlaneMesh(false,
|
|
true);
|
|
mesh.EnsureNCMesh(true);
|
|
SECTION("UniformRefinement2")
|
|
{
|
|
mesh.UniformRefinement();
|
|
mesh.UniformRefinement();
|
|
ParMesh pmesh(MPI_COMM_WORLD, mesh);
|
|
SECTION("SingleAttribute")
|
|
{
|
|
Array<int> subdomain_attributes(1);
|
|
subdomain_attributes[0] = GENERATE(range(1,2));
|
|
auto submesh = ParSubMesh::CreateFromDomain(pmesh, subdomain_attributes);
|
|
|
|
// Cast to an exposed variant to explore the internals.
|
|
auto pncmesh_exposed = ParNCMeshExposed(*submesh.pncmesh);
|
|
CHECK(pncmesh_exposed.GetNumRootElements() == 1);
|
|
CHECK(pncmesh_exposed.CountUniqueLeafElements() == 8*8);
|
|
for (auto fec_type : {FECType::H1, FECType::L2, FECType::ND, FECType::RT})
|
|
{
|
|
CheckProjectMatch(pmesh, submesh, fec_type);
|
|
}
|
|
}
|
|
|
|
SECTION("UniformRefineTwoAttribute")
|
|
{
|
|
Array<int> subdomain_attributes(2);
|
|
subdomain_attributes[0] = 1;
|
|
subdomain_attributes[1] = 2;
|
|
auto submesh = ParSubMesh::CreateFromDomain(pmesh, subdomain_attributes);
|
|
|
|
// Cast to an exposed variant to explore the internals.
|
|
auto pncmesh_exposed = ParNCMeshExposed(*submesh.pncmesh);
|
|
CHECK(pncmesh_exposed.GetNumRootElements() ==
|
|
pmesh.ncmesh->GetNumRootElements());
|
|
CHECK(pncmesh_exposed.CountUniqueLeafElements() == 2*8*8);
|
|
for (auto fec_type : {FECType::H1, FECType::L2, FECType::ND, FECType::RT})
|
|
{
|
|
CheckProjectMatch(pmesh, submesh, fec_type);
|
|
}
|
|
}
|
|
}
|
|
|
|
SECTION("Nonconformal")
|
|
{
|
|
mesh.UniformRefinement();
|
|
Array<int> subdomain_attributes{GENERATE(1,2)};
|
|
auto backwards = GENERATE(false, true);
|
|
SECTION("ConsistentWithParent")
|
|
{
|
|
RefineSingleUnattachedElement(mesh, subdomain_attributes[0],
|
|
mesh.bdr_attributes.Max(), backwards);
|
|
{
|
|
ParMesh pmesh(MPI_COMM_WORLD, mesh);
|
|
auto submesh = ParSubMesh::CreateFromDomain(pmesh, subdomain_attributes);
|
|
|
|
// Cast to an exposed variant to explore the internals.
|
|
auto pncmesh_exposed = ParNCMeshExposed(*submesh.pncmesh);
|
|
CHECK(pncmesh_exposed.GetNumRootElements() == 1);
|
|
CHECK(pncmesh_exposed.CountUniqueLeafElements() == 8 - 1 + 8);
|
|
for (auto fec_type : {FECType::H1, FECType::L2, FECType::ND, FECType::RT})
|
|
{
|
|
CheckProjectMatch(pmesh, submesh, fec_type, true);
|
|
}
|
|
}
|
|
RefineSingleUnattachedElement(mesh, subdomain_attributes[0],
|
|
mesh.bdr_attributes.Max(), backwards);
|
|
{
|
|
ParMesh pmesh(MPI_COMM_WORLD, mesh);
|
|
auto submesh = ParSubMesh::CreateFromDomain(pmesh, subdomain_attributes);
|
|
|
|
// Cast to an exposed variant to explore the internals.
|
|
auto pncmesh_exposed = ParNCMeshExposed(*submesh.pncmesh);
|
|
CHECK(pncmesh_exposed.GetNumRootElements() == 1);
|
|
CHECK(pncmesh_exposed.CountUniqueLeafElements() == 8 - 1 + 8 - 1 + 8);
|
|
for (auto fec_type : {FECType::H1, FECType::L2, FECType::ND, FECType::RT})
|
|
{
|
|
CheckProjectMatch(pmesh, submesh, fec_type, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
SECTION("InconsistentWithParent")
|
|
{
|
|
RefineSingleAttachedElement(mesh, subdomain_attributes[0],
|
|
mesh.bdr_attributes.Max(), backwards);
|
|
{
|
|
ParMesh pmesh(MPI_COMM_WORLD, mesh);
|
|
auto submesh = ParSubMesh::CreateFromDomain(pmesh, subdomain_attributes);
|
|
|
|
// Cast to an exposed variant to explore the internals.
|
|
auto pncmesh_exposed = ParNCMeshExposed(*submesh.pncmesh);
|
|
CHECK(pncmesh_exposed.GetNumRootElements() == 1);
|
|
CHECK(pncmesh_exposed.CountUniqueLeafElements() == 8 - 1 + 8);
|
|
for (auto fec_type : {FECType::H1, FECType::L2, FECType::ND, FECType::RT})
|
|
{
|
|
CheckProjectMatch(pmesh, submesh, fec_type, false);
|
|
}
|
|
}
|
|
RefineSingleAttachedElement(mesh, subdomain_attributes[0],
|
|
mesh.bdr_attributes.Max(), backwards);
|
|
{
|
|
ParMesh pmesh(MPI_COMM_WORLD, mesh);
|
|
auto submesh = ParSubMesh::CreateFromDomain(pmesh, subdomain_attributes);
|
|
|
|
// Cast to an exposed variant to explore the internals.
|
|
auto pncmesh_exposed = ParNCMeshExposed(*submesh.pncmesh);
|
|
CHECK(pncmesh_exposed.GetNumRootElements() == 1);
|
|
CHECK(pncmesh_exposed.CountUniqueLeafElements() == 8 - 1 + 8 - 1 + 8);
|
|
for (auto fec_type : {FECType::H1, FECType::L2, FECType::ND, FECType::RT})
|
|
{
|
|
CheckProjectMatch(pmesh, submesh, fec_type, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("ExteriorSurfaceParNCSubMesh", "[Parallel],[SubMesh]")
|
|
{
|
|
SECTION("Hex")
|
|
{
|
|
auto mesh = Mesh("../../data/ref-cube.mesh", 1, 1);
|
|
mesh.EnsureNCMesh(true);
|
|
SECTION("UniformRefinement2")
|
|
{
|
|
mesh.UniformRefinement();
|
|
mesh.UniformRefinement();
|
|
ParMesh pmesh(MPI_COMM_WORLD, mesh);
|
|
SECTION("SingleAttribute")
|
|
{
|
|
Array<int> subdomain_attributes(1);
|
|
subdomain_attributes[0] = GENERATE(range(1,6));
|
|
auto submesh = ParSubMesh::CreateFromBoundary(pmesh, subdomain_attributes);
|
|
|
|
// Cast to an exposed variant to explore the internals.
|
|
auto pncmesh_exposed = ParNCMeshExposed(*submesh.pncmesh);
|
|
CHECK(pncmesh_exposed.GetNumRootElements() == 1);
|
|
CHECK(pncmesh_exposed.CountUniqueLeafElements() == 4*4);
|
|
CHECK(submesh.bdr_attributes.Size() == 1);
|
|
CHECK(submesh.bdr_attributes[0] == mesh.bdr_attributes.Max() + 1);
|
|
for (auto fec_type : {FECType::H1, FECType::L2, FECType::ND, FECType::RT})
|
|
{
|
|
CheckProjectMatch(pmesh, submesh, fec_type);
|
|
}
|
|
}
|
|
|
|
SECTION("UniformRefineTwoAttribute")
|
|
{
|
|
Array<int> subdomain_attributes(2);
|
|
subdomain_attributes[0] = GENERATE(range(1,6));
|
|
subdomain_attributes[1] = 1 + (subdomain_attributes[0] % 6);
|
|
auto submesh = ParSubMesh::CreateFromBoundary(pmesh, subdomain_attributes);
|
|
|
|
// Cast to an exposed variant to explore the internals.
|
|
auto pncmesh_exposed = ParNCMeshExposed(*submesh.pncmesh);
|
|
CHECK(pncmesh_exposed.GetNumRootElements() == 2);
|
|
CHECK(pncmesh_exposed.CountUniqueLeafElements() == 2*4*4);
|
|
CHECK(submesh.bdr_attributes.Size() == 1);
|
|
CHECK(submesh.bdr_attributes[0] == mesh.bdr_attributes.Max() + 1);
|
|
for (auto fec_type : {FECType::H1, FECType::L2, FECType::ND, FECType::RT})
|
|
{
|
|
CheckProjectMatch(pmesh, submesh, fec_type);
|
|
}
|
|
}
|
|
}
|
|
|
|
SECTION("NonconformalRefine")
|
|
{
|
|
Array<int> subdomain_attributes(1);
|
|
subdomain_attributes[0] = GENERATE(range(1,6));
|
|
mesh.UniformRefinement();
|
|
RefineSingleAttachedElement(mesh, 1, subdomain_attributes[0], true);
|
|
SECTION("Single")
|
|
{
|
|
ParMesh pmesh(MPI_COMM_WORLD, mesh);
|
|
auto submesh = ParSubMesh::CreateFromBoundary(pmesh, subdomain_attributes);
|
|
|
|
// Cast to an exposed variant to explore the internals.
|
|
auto pncmesh_exposed = ParNCMeshExposed(*submesh.pncmesh);
|
|
CHECK(pncmesh_exposed.GetNumRootElements() == 1);
|
|
CHECK(pncmesh_exposed.CountUniqueLeafElements() == 4 - 1 + 4);
|
|
CHECK(submesh.bdr_attributes.Size() == 1);
|
|
CHECK(submesh.bdr_attributes[0] == mesh.bdr_attributes.Max() + 1);
|
|
for (auto fec_type : {FECType::H1, FECType::L2, FECType::ND, FECType::RT})
|
|
{
|
|
CheckProjectMatch(pmesh, submesh, fec_type);
|
|
}
|
|
|
|
}
|
|
SECTION("Double")
|
|
{
|
|
RefineSingleAttachedElement(mesh, 1, subdomain_attributes[0], false);
|
|
ParMesh pmesh(MPI_COMM_WORLD, mesh);
|
|
auto submesh = ParSubMesh::CreateFromBoundary(pmesh, subdomain_attributes);
|
|
|
|
// Cast to an exposed variant to explore the internals.
|
|
auto pncmesh_exposed = ParNCMeshExposed(*submesh.pncmesh);
|
|
CHECK(pncmesh_exposed.GetNumRootElements() == 1);
|
|
CHECK(pncmesh_exposed.CountUniqueLeafElements() == 4 - 1 + 4 - 1 + 4);
|
|
CHECK(submesh.bdr_attributes.Size() == 1);
|
|
CHECK(submesh.bdr_attributes[0] == mesh.bdr_attributes.Max() + 1);
|
|
for (auto fec_type : {FECType::H1, FECType::L2, FECType::ND, FECType::RT})
|
|
{
|
|
CheckProjectMatch(pmesh, submesh, fec_type);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SECTION("Tet")
|
|
{
|
|
auto mesh = Mesh("../../data/ref-tetrahedron.mesh");
|
|
mesh.EnsureNCMesh(true);
|
|
SECTION("UniformRefinement2")
|
|
{
|
|
mesh.UniformRefinement();
|
|
mesh.UniformRefinement();
|
|
ParMesh pmesh(MPI_COMM_WORLD, mesh);
|
|
SECTION("SingleAttribute")
|
|
{
|
|
Array<int> subdomain_attributes(1);
|
|
subdomain_attributes[0] = GENERATE(range(1,4));
|
|
auto submesh = ParSubMesh::CreateFromBoundary(pmesh, subdomain_attributes);
|
|
|
|
// Cast to an exposed variant to explore the internals.
|
|
auto pncmesh_exposed = ParNCMeshExposed(*submesh.pncmesh);
|
|
CHECK(pncmesh_exposed.GetNumRootElements() == 1);
|
|
CHECK(pncmesh_exposed.CountUniqueLeafElements() == 4*4);
|
|
CHECK(submesh.bdr_attributes.Size() == 1);
|
|
CHECK(submesh.bdr_attributes[0] == mesh.bdr_attributes.Max() + 1);
|
|
for (auto fec_type : {FECType::H1, FECType::L2, FECType::ND, FECType::RT})
|
|
{
|
|
CheckProjectMatch(pmesh, submesh, fec_type);
|
|
}
|
|
}
|
|
|
|
SECTION("UniformRefineTwoAttribute")
|
|
{
|
|
Array<int> subdomain_attributes(2);
|
|
subdomain_attributes[0] = GENERATE(range(1,4));
|
|
subdomain_attributes[1] = 1 + (subdomain_attributes[0] % 4);
|
|
auto submesh = ParSubMesh::CreateFromBoundary(pmesh, subdomain_attributes);
|
|
|
|
// Cast to an exposed variant to explore the internals.
|
|
auto pncmesh_exposed = ParNCMeshExposed(*submesh.pncmesh);
|
|
CHECK(pncmesh_exposed.GetNumRootElements() == 2);
|
|
CHECK(pncmesh_exposed.CountUniqueLeafElements() == 2*4*4);
|
|
CHECK(submesh.bdr_attributes.Size() == 1);
|
|
CHECK(submesh.bdr_attributes[0] == mesh.bdr_attributes.Max() + 1);
|
|
for (auto fec_type : {FECType::H1, FECType::L2, FECType::ND, FECType::RT})
|
|
{
|
|
CheckProjectMatch(pmesh, submesh, fec_type);
|
|
}
|
|
}
|
|
}
|
|
|
|
SECTION("NonconformalRefine")
|
|
{
|
|
Array<int> subdomain_attributes(1);
|
|
subdomain_attributes[0] = GENERATE(range(1,4));
|
|
mesh.UniformRefinement();
|
|
RefineSingleAttachedElement(mesh, 1, subdomain_attributes[0], true);
|
|
SECTION("Single")
|
|
{
|
|
ParMesh pmesh(MPI_COMM_WORLD, mesh);
|
|
auto submesh = ParSubMesh::CreateFromBoundary(pmesh, subdomain_attributes);
|
|
|
|
// Cast to an exposed variant to explore the internals.
|
|
auto pncmesh_exposed = ParNCMeshExposed(*submesh.pncmesh);
|
|
CHECK(pncmesh_exposed.GetNumRootElements() == 1);
|
|
CHECK(pncmesh_exposed.CountUniqueLeafElements() == 4 - 1 + 4);
|
|
CHECK(submesh.bdr_attributes.Size() == 1);
|
|
CHECK(submesh.bdr_attributes[0] == mesh.bdr_attributes.Max() + 1);
|
|
for (auto fec_type : {FECType::H1, FECType::L2, FECType::ND, FECType::RT})
|
|
{
|
|
CheckProjectMatch(pmesh, submesh, fec_type);
|
|
}
|
|
}
|
|
SECTION("Double")
|
|
{
|
|
RefineSingleAttachedElement(mesh, 1, subdomain_attributes[0], false);
|
|
ParMesh pmesh(MPI_COMM_WORLD, mesh);
|
|
auto submesh = ParSubMesh::CreateFromBoundary(pmesh, subdomain_attributes);
|
|
|
|
// Cast to an exposed variant to explore the internals.
|
|
auto pncmesh_exposed = ParNCMeshExposed(*submesh.pncmesh);
|
|
CHECK(pncmesh_exposed.GetNumRootElements() == 1);
|
|
CHECK(pncmesh_exposed.CountUniqueLeafElements() == 4 - 1 + 4 - 1 + 4);
|
|
CHECK(submesh.bdr_attributes.Size() == 1);
|
|
CHECK(submesh.bdr_attributes[0] == mesh.bdr_attributes.Max() + 1);
|
|
for (auto fec_type : {FECType::H1, FECType::L2, FECType::ND, FECType::RT})
|
|
{
|
|
CheckProjectMatch(pmesh, submesh, fec_type);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} // namespace ParSubMeshTests
|
|
|
|
#endif // MFEM_USE_MPI
|