// 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 #include #include "unit_tests.hpp" #include "mesh_test_utils.hpp" using namespace mfem; enum class FieldType { SCALAR, VECTOR }; enum class TransferType { ParentToSub, SubToParent }; void test_2d(Element::Type element_type, FECType fec_type, FieldType field_type, int polynomial_order, int mesh_polynomial_order, TransferType transfer_type, SubMesh::From from) { constexpr int dim = 2; const int vdim = (field_type == FieldType::SCALAR || fec_type == FECType::ND) ? 1 : dim; real_t Hy = 1.0; Mesh mesh = Mesh::MakeCartesian2D(5, 5, element_type, true, 1.0, Hy, false); if (from == SubMesh::From::Boundary) { for (int i = 0; i < mesh.GetNBE(); i++) { Element *el = mesh.GetBdrElement(i); el->SetAttribute(1); Array vertices; el->GetVertices(vertices); bool all_vtx_inside = true; for (int j = 0; j < vertices.Size(); j++) { if (mesh.GetVertex(vertices[j])[1] < 1.0) { all_vtx_inside = false; } } if (all_vtx_inside) { el->SetAttribute(2); } } } else if (from == SubMesh::From::Domain) { for (int i = 0; i < mesh.GetNE(); i++) { Element *el = mesh.GetElement(i); el->SetAttribute(1); Array vertices; el->GetVertices(vertices); for (int j = 0; j < vertices.Size(); j++) { real_t *coords = mesh.GetVertex(vertices[j]); if (coords[0] >= 0.25 && coords[0] <= 0.75 && coords[1] >= 0.25 && coords[1] <= 0.75) { el->SetAttribute(2); } } } } mesh.SetAttributes(); // Deform original mesh mesh.EnsureNodes(); mesh.SetCurvature(mesh_polynomial_order); auto node_movement_coeff = VectorFunctionCoefficient(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); }); mesh.Transform(node_movement_coeff); FiniteElementCollection *fec = create_fec(fec_type, polynomial_order, dim); FiniteElementSpace parent_fes(&mesh, fec, vdim); GridFunction parent_gf(&parent_fes); parent_gf = 0.0; 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); }); Array subdomain_attributes(1); subdomain_attributes[0] = 2; SubMesh* submesh = nullptr; if (from == SubMesh::From::Domain) { submesh = new SubMesh(SubMesh::CreateFromDomain(mesh, subdomain_attributes)); } else { submesh = new SubMesh(SubMesh::CreateFromBoundary(mesh, subdomain_attributes)); } REQUIRE(submesh->GetNE() != 0); FiniteElementCollection *sub_fec = create_fec(fec_type, polynomial_order, submesh->Dimension()); FiniteElementSpace sub_fes(submesh, sub_fec, vdim); GridFunction sub_gf(&sub_fes); sub_gf = 0.0; if (transfer_type == TransferType::ParentToSub) { GridFunction sub_ex_gf(&sub_fes); if (vdim == 1 && (fec_type == FECType::H1 || fec_type == FECType::L2)) { parent_gf.ProjectCoefficient(coeff); sub_ex_gf.ProjectCoefficient(coeff); } else { parent_gf.ProjectCoefficient(vcoeff); sub_ex_gf.ProjectCoefficient(vcoeff); } SubMesh::Transfer(parent_gf, sub_gf); REQUIRE(sub_gf.Norml2() != 0.0); sub_gf -= sub_ex_gf; REQUIRE(sub_gf.Norml2() < 1e-10); } else if (transfer_type == TransferType::SubToParent) { GridFunction parent_ex_gf(&parent_fes); if (vdim == 1 && (fec_type == FECType::H1 || fec_type == FECType::L2)) { parent_gf.ProjectCoefficient(coeff); sub_gf.ProjectCoefficient(coeff); parent_ex_gf.ProjectCoefficient(coeff); } else { parent_gf.ProjectCoefficient(vcoeff); sub_gf.ProjectCoefficient(vcoeff); parent_ex_gf.ProjectCoefficient(vcoeff); } SubMesh::Transfer(sub_gf, parent_gf); REQUIRE(parent_gf.Norml2() != 0.0); parent_gf -= parent_ex_gf; REQUIRE(parent_gf.Norml2() < 1e-10); } delete submesh; delete sub_fec; delete fec; } void test_3d(Element::Type element_type, FECType fec_type, FieldType field_type, int polynomial_order, int mesh_polynomial_order, TransferType transfer_type, SubMesh::From from) { constexpr int dim = 3; const int vdim = (field_type == FieldType::SCALAR || fec_type == FECType::ND) ? 1 : dim; real_t Hy = 1.0; Mesh mesh = Mesh::MakeCartesian3D(5, 5, 5, element_type, 1.0, Hy, 1.0, false); if (from == SubMesh::From::Boundary) { for (int i = 0; i < mesh.GetNBE(); i++) { Element *el = mesh.GetBdrElement(i); el->SetAttribute(1); Array vertices; el->GetVertices(vertices); bool all_vtx_inside = true; for (int j = 0; j < vertices.Size(); j++) { if (mesh.GetVertex(vertices[j])[1] < Hy) { all_vtx_inside = false; } } if (all_vtx_inside) { el->SetAttribute(2); } } } else if (from == SubMesh::From::Domain) { for (int i = 0; i < mesh.GetNE(); i++) { Element *el = mesh.GetElement(i); el->SetAttribute(1); Array vertices; el->GetVertices(vertices); bool all_vtx_inside = true; for (int j = 0; j < vertices.Size(); j++) { if (mesh.GetVertex(vertices[j])[1] > 0.5 * Hy) { all_vtx_inside = false; } } if (all_vtx_inside) { el->SetAttribute(2); } } } mesh.SetAttributes(); // Deform original mesh mesh.EnsureNodes(); mesh.SetCurvature(mesh_polynomial_order); auto node_movement_coeff = VectorFunctionCoefficient(mesh.Dimension(), [](const Vector &coords, Vector &u) { real_t x = coords(0); real_t y = coords(1); real_t z = coords(2); u(0) = x; u(1) = y + 0.05 * sin(x * 2.0 * M_PI); u(2) = z; }); mesh.Transform(node_movement_coeff); FiniteElementCollection *fec = create_fec(fec_type, polynomial_order, dim); FiniteElementSpace parent_fes(&mesh, fec, vdim); GridFunction parent_gf(&parent_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; }); Array subdomain_attributes(1); subdomain_attributes[0] = 2; SubMesh* submesh = nullptr; if (from == SubMesh::From::Domain) { submesh = new SubMesh(SubMesh::CreateFromDomain(mesh, subdomain_attributes)); } else { submesh = new SubMesh(SubMesh::CreateFromBoundary(mesh, subdomain_attributes)); } REQUIRE(submesh->GetNE() != 0); FiniteElementCollection *sub_fec = create_fec(fec_type, polynomial_order, submesh->Dimension()); FiniteElementSpace sub_fes(submesh, sub_fec, vdim); GridFunction sub_gf(&sub_fes); sub_gf = 0.0; if (transfer_type == TransferType::ParentToSub) { GridFunction sub_ex_gf(&sub_fes); if (vdim == 1 && (fec_type == FECType::H1 || fec_type == FECType::L2)) { parent_gf.ProjectCoefficient(coeff); sub_ex_gf.ProjectCoefficient(coeff); } else { parent_gf.ProjectCoefficient(vcoeff); sub_ex_gf.ProjectCoefficient(vcoeff); } SubMesh::Transfer(parent_gf, sub_gf); REQUIRE(sub_gf.Norml2() != 0.0); sub_gf -= sub_ex_gf; REQUIRE(sub_gf.Norml2() < 1e-10); } else if (transfer_type == TransferType::SubToParent) { GridFunction parent_ex_gf(&parent_fes); if (vdim == 1 && (fec_type == FECType::H1 || fec_type == FECType::L2)) { parent_gf.ProjectCoefficient(coeff); sub_gf.ProjectCoefficient(coeff); parent_ex_gf.ProjectCoefficient(coeff); } else { parent_gf.ProjectCoefficient(vcoeff); sub_gf.ProjectCoefficient(vcoeff); parent_ex_gf.ProjectCoefficient(vcoeff); } SubMesh::Transfer(sub_gf, parent_gf); REQUIRE(parent_gf.Norml2() != 0.0); parent_gf -= parent_ex_gf; REQUIRE(parent_gf.Norml2() < 1e-10); } delete submesh; delete sub_fec; delete fec; } TEST_CASE("SubMesh", "[SubMesh]") { int polynomial_order = 4; int mesh_polynomial_order = 2; auto fec_type = GENERATE(FECType::H1, FECType::ND, FECType::L2); auto field_type = GENERATE(FieldType::SCALAR, FieldType::VECTOR); auto transfer_type = GENERATE(TransferType::ParentToSub, TransferType::SubToParent); auto from = GENERATE(SubMesh::From::Domain, SubMesh::From::Boundary); if (fec_type == FECType::ND && field_type == FieldType::VECTOR) { return; } SECTION("2D") { auto element = GENERATE(Element::QUADRILATERAL, Element::TRIANGLE); if (fec_type == FECType::L2 && from == SubMesh::From::Boundary && false) { return; } test_2d(element, fec_type, field_type, polynomial_order, mesh_polynomial_order, transfer_type, from); } SECTION("3D") { auto element = GENERATE(Element::HEXAHEDRON, Element::TETRAHEDRON, Element::WEDGE); if (fec_type == FECType::L2 && from == SubMesh::From::Boundary && false) { return; } test_3d(element, fec_type, field_type, polynomial_order, mesh_polynomial_order, transfer_type, from); } } TEST_CASE("InterfaceTransferSolve", "[SubMesh]") { // Solve Poisson on a pair of cubes fully coupled, transfer to the interface // then solve on subdomains using the 2D solution as the boundary condition. int polynomial_order = 4; auto fec_type = FECType::H1; // 1. Define meshes auto mesh = DividingPlaneMesh(false, true); mesh.UniformRefinement(); Array subdomain_attributes(1); subdomain_attributes[0] = 1; auto left_vol = SubMesh::CreateFromDomain(mesh, subdomain_attributes); subdomain_attributes[0] = 2; auto right_vol = SubMesh::CreateFromDomain(mesh, subdomain_attributes); subdomain_attributes[0] = mesh.bdr_attributes.Max(); auto interface = SubMesh::CreateFromBoundary(mesh, subdomain_attributes); // 2. Define fespaces auto vol_fec = std::unique_ptr(create_fec(fec_type, polynomial_order, 3)); auto surf_fec = std::unique_ptr(create_fec(fec_type, polynomial_order, 2)); auto fespace = FiniteElementSpace(&mesh, vol_fec.get()); auto left_fespace = FiniteElementSpace(&left_vol, vol_fec.get()); auto right_fespace = FiniteElementSpace(&right_vol, vol_fec.get()); auto interface_fespace = FiniteElementSpace(&interface, surf_fec.get()); // 3. Solve full problem with homogeneous boundary conditions and transfer to interface space. ConstantCoefficient one(1.0); // Use a simple symmetric Gauss-Seidel preconditioner with PCG. OperatorPtr A; Vector B, X; // Manufactured solution u = sin(pi x) sin(pi y) sin(pi z). // f = - (u_xx + u_yy + u_zz) = d * pi^2 sin(pi x) sin(pi y) sin(pi z) auto f = FunctionCoefficient([](const Vector &x) { double c = M_PI * M_PI * 3; for (int i = 0; i < 3; ++i) { c *= std::sin(M_PI * x(i)); } return c; }); auto SolveHomogeneous = [&](FiniteElementSpace &fespace) { Array ess_tdof_list, ess_bdr; ess_bdr.SetSize(fespace.GetMesh()->bdr_attributes.Max()); ess_bdr = 1; if (fespace.GetMesh()->Dimension() > 2) { // The interior of the volume has an extra bc ess_bdr.Last() = 0; } fespace.GetEssentialTrueDofs(ess_bdr, ess_tdof_list); LinearForm b(&fespace); b.AddDomainIntegrator(new DomainLFIntegrator(f)); b.Assemble(); GridFunction x(&fespace); x = 0.0; BilinearForm a(&fespace); a.AddDomainIntegrator(new DiffusionIntegrator(one)); a.Assemble(); a.FormLinearSystem(ess_tdof_list, x, b, A, X, B); GSSmoother M((SparseMatrix&)(*A)); PCG(*A, M, B, X, 1, 1e3, 1e-16, 0.0); a.RecoverFEMSolution(X, b, x); return x; }; auto x_vol = SolveHomogeneous(fespace); GridFunction x_int(&interface_fespace); SubMesh::Transfer(x_vol, x_int); // 4. Transfer solution to left and right subproblems and solve auto SolveOnSubVolume = [&](FiniteElementSpace &fespace) { Array ess_tdof_list, ess_bdr; ess_bdr.SetSize(fespace.GetMesh()->bdr_attributes.Max()); ess_bdr = 1; fespace.GetEssentialTrueDofs(ess_bdr, ess_tdof_list); LinearForm b(&fespace); b.AddDomainIntegrator(new DomainLFIntegrator(f)); b.Assemble(); GridFunction x(&fespace); x = 0.0; SubMesh::Transfer(x_int, x); BilinearForm a(&fespace); a.AddDomainIntegrator(new DiffusionIntegrator(one)); a.Assemble(); a.FormLinearSystem(ess_tdof_list, x, b, A, X, B); GSSmoother M((SparseMatrix&)(*A)); PCG(*A, M, B, X, 1, 1e3, 1e-16, 0.0); a.RecoverFEMSolution(X, b, x); return x; }; auto x_right = SolveOnSubVolume(right_fespace); auto x_left = SolveOnSubVolume(left_fespace); // 5. Transfer the left and right solutions onto a duplicate of the full solve // and compare. Given the choice of boundary conditions, should match exactly. auto x_sub = x_vol; x_sub = 0.0; SubMesh::Transfer(x_left, x_sub); SubMesh::Transfer(x_right, x_sub); x_sub -= x_vol; CHECK((x_sub.Norml2() / x_sub.Size()) == MFEM_Approx(0.0, 1e-7, 1e-7)); } /** * @brief Helper class for testing a NCMesh * */ struct NCMeshExposed : public NCMesh { NCMeshExposed(const NCMesh &ncmesh) : NCMesh(ncmesh) {} using NCMesh::elements; using NCMesh::leaf_elements; int CountUniqueLeafElements() const { int local = 0; for (auto i : leaf_elements) { if (elements[i].rank == MyRank) { ++local; } } return local; } }; void CHECK_NORM(Vector &v, bool small = true) { if (small) { REQUIRE(v.Norml2() < 1e-8); } else { REQUIRE(v.Norml2() > 1e-8); } }; void CheckProjectMatch(Mesh &mesh, SubMesh &submesh, FECType fec_type, bool check_pr = true) { int p = 3; auto fec = std::unique_ptr(create_fec(fec_type, p, mesh.Dimension())); auto sub_fec = std::unique_ptr(create_fec(fec_type, p, submesh.Dimension())); FiniteElementSpace fes(&mesh, fec.get()); FiniteElementSpace sub_fes(&submesh, sub_fec.get()); GridFunction gf(&fes), gf_ext(&fes); GridFunction 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 SubMesh::Transfer(gf, sub_gf); auto tmp = sub_gf_ext; tmp -= sub_gf; CHECK_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); } SubMesh::Transfer(gf, sub_gf); tmp = sub_gf_ext; tmp -= sub_gf; CHECK_NORM(tmp, check_pr); } } } TEST_CASE("VolumeNCSubMesh", "[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(); SECTION("SingleAttribute") { Array subdomain_attributes(1); subdomain_attributes[0] = GENERATE(range(1,2)); auto submesh = SubMesh::CreateFromDomain(mesh, subdomain_attributes); // Cast to an exposed variant to explore the internals. auto ncmesh_exposed = NCMeshExposed(*submesh.ncmesh); CHECK(ncmesh_exposed.GetNumRootElements() == 1); CHECK(ncmesh_exposed.CountUniqueLeafElements() == 8*8); for (auto fec_type : {FECType::H1, FECType::L2, FECType::ND, FECType::RT}) { CheckProjectMatch(mesh, submesh, fec_type); } } SECTION("UniformRefineTwoAttribute") { Array subdomain_attributes(2); subdomain_attributes[0] = 1; subdomain_attributes[1] = 2; auto submesh = SubMesh::CreateFromDomain(mesh, subdomain_attributes); // Cast to an exposed variant to explore the internals. auto ncmesh_exposed = NCMeshExposed(*submesh.ncmesh); CHECK(ncmesh_exposed.GetNumRootElements() == mesh.ncmesh->GetNumRootElements()); CHECK(ncmesh_exposed.CountUniqueLeafElements() == 2*8*8); for (auto fec_type : {FECType::H1, FECType::L2, FECType::ND, FECType::RT}) { CheckProjectMatch(mesh, submesh, fec_type); } } } SECTION("Nonconformal") { mesh.UniformRefinement(); Array subdomain_attributes{GENERATE(1,2)}; auto backwards = GENERATE(false, true); SECTION("ConsistentWithParent") { RefineSingleUnattachedElement(mesh, subdomain_attributes[0], mesh.bdr_attributes.Max(), backwards); { auto submesh = SubMesh::CreateFromDomain(mesh, subdomain_attributes); auto ncmesh_exposed = NCMeshExposed(*submesh.ncmesh); CHECK(ncmesh_exposed.GetNumRootElements() == 1); CHECK(ncmesh_exposed.CountUniqueLeafElements() == 8 - 1 + 8); for (auto fec_type : {FECType::H1, FECType::L2, FECType::ND, FECType::RT}) { CheckProjectMatch(mesh, submesh, fec_type, true); } } RefineSingleUnattachedElement(mesh, subdomain_attributes[0], mesh.bdr_attributes.Max(), backwards); { auto submesh = SubMesh::CreateFromDomain(mesh, subdomain_attributes); auto ncmesh_exposed = NCMeshExposed(*submesh.ncmesh); CHECK(ncmesh_exposed.GetNumRootElements() == 1); CHECK(ncmesh_exposed.CountUniqueLeafElements() == 8 - 1 + 8 - 1 + 8); for (auto fec_type : {FECType::H1, FECType::L2, FECType::ND, FECType::RT}) { CheckProjectMatch(mesh, submesh, fec_type, true); } } } SECTION("InconsistentWithParent") { RefineSingleAttachedElement(mesh, subdomain_attributes[0], mesh.bdr_attributes.Max(), backwards); { auto submesh = SubMesh::CreateFromDomain(mesh, subdomain_attributes); auto ncmesh_exposed = NCMeshExposed(*submesh.ncmesh); CHECK(ncmesh_exposed.GetNumRootElements() == 1); CHECK(ncmesh_exposed.CountUniqueLeafElements() == 8 - 1 + 8); for (auto fec_type : {FECType::H1, FECType::L2, FECType::ND, FECType::RT}) { CheckProjectMatch(mesh, submesh, fec_type, false); } } RefineSingleAttachedElement(mesh, subdomain_attributes[0], mesh.bdr_attributes.Max(), backwards); { auto submesh = SubMesh::CreateFromDomain(mesh, subdomain_attributes); auto ncmesh_exposed = NCMeshExposed(*submesh.ncmesh); CHECK(ncmesh_exposed.GetNumRootElements() == 1); CHECK(ncmesh_exposed.CountUniqueLeafElements() == 8 - 1 + 8 - 1 + 8); for (auto fec_type : {FECType::H1, FECType::L2, FECType::ND, FECType::RT}) { CheckProjectMatch(mesh, submesh, fec_type, false); } } } } } TEST_CASE("ExteriorSurfaceNCSubMesh", "[SubMesh]") { SECTION("Hex") { auto mesh = Mesh("../../data/ref-cube.mesh", 1, 1); mesh.EnsureNCMesh(true); SECTION("UniformRefinement2") { mesh.UniformRefinement(); mesh.UniformRefinement(); SECTION("SingleAttribute") { Array subdomain_attributes{GENERATE(range(1,6))}; auto submesh = SubMesh::CreateFromBoundary(mesh, subdomain_attributes); auto ncmesh_exposed = NCMeshExposed(*submesh.ncmesh); CHECK(ncmesh_exposed.GetNumRootElements() == 1); CHECK(ncmesh_exposed.CountUniqueLeafElements() == 4*4); for (auto fec_type : {FECType::H1, FECType::L2, FECType::ND, FECType::RT}) { CheckProjectMatch(mesh, submesh, fec_type); } } SECTION("UniformRefineTwoAttribute") { Array subdomain_attributes(2); subdomain_attributes[0] = GENERATE(range(1,6)); subdomain_attributes[1] = 1 + (subdomain_attributes[0] % 6); auto submesh = SubMesh::CreateFromBoundary(mesh, subdomain_attributes); auto ncmesh_exposed = NCMeshExposed(*submesh.ncmesh); CHECK(ncmesh_exposed.GetNumRootElements() == 2); CHECK(ncmesh_exposed.CountUniqueLeafElements() == 2*4*4); for (auto fec_type : {FECType::H1, FECType::L2, FECType::ND, FECType::RT}) { CheckProjectMatch(mesh, submesh, fec_type); } } } SECTION("NonconformalRefine") { Array subdomain_attributes{GENERATE(range(1,6))}; mesh.UniformRefinement(); RefineSingleAttachedElement(mesh, 1, subdomain_attributes[0], true); SECTION("Single") { auto submesh = SubMesh::CreateFromBoundary(mesh, subdomain_attributes); auto ncmesh_exposed = NCMeshExposed(*submesh.ncmesh); CHECK(ncmesh_exposed.GetNumRootElements() == 1); CHECK(ncmesh_exposed.CountUniqueLeafElements() == 4 - 1 + 4); for (auto fec_type : {FECType::H1, FECType::L2, FECType::ND, FECType::RT}) { CheckProjectMatch(mesh, submesh, fec_type); } } SECTION("Double") { RefineSingleAttachedElement(mesh, 1, subdomain_attributes[0], false); auto submesh = SubMesh::CreateFromBoundary(mesh, subdomain_attributes); auto ncmesh_exposed = NCMeshExposed(*submesh.ncmesh); CHECK(ncmesh_exposed.GetNumRootElements() == 1); CHECK(ncmesh_exposed.CountUniqueLeafElements() == 4 - 1 + 4 - 1 + 4); for (auto fec_type : {FECType::H1, FECType::L2, FECType::ND, FECType::RT}) { CheckProjectMatch(mesh, submesh, fec_type); } } } } SECTION("Tet") { auto mesh = Mesh("../../data/ref-tetrahedron.mesh"); mesh.EnsureNCMesh(true); SECTION("UniformRefinement2") { mesh.UniformRefinement(); mesh.UniformRefinement(); SECTION("SingleAttribute") { Array subdomain_attributes{GENERATE(range(1,4))}; auto submesh = SubMesh::CreateFromBoundary(mesh, subdomain_attributes); auto ncmesh_exposed = NCMeshExposed(*submesh.ncmesh); CHECK(ncmesh_exposed.GetNumRootElements() == 1); CHECK(ncmesh_exposed.CountUniqueLeafElements() == 4*4); for (auto fec_type : {FECType::H1, FECType::L2, FECType::ND, FECType::RT}) { CheckProjectMatch(mesh, submesh, fec_type); } } SECTION("UniformRefineTwoAttribute") { Array subdomain_attributes(2); subdomain_attributes[0] = GENERATE(range(1,4)); subdomain_attributes[1] = 1 + (subdomain_attributes[0] % 4); auto submesh = SubMesh::CreateFromBoundary(mesh, subdomain_attributes); auto ncmesh_exposed = NCMeshExposed(*submesh.ncmesh); CHECK(ncmesh_exposed.GetNumRootElements() == 2); CHECK(ncmesh_exposed.CountUniqueLeafElements() == 2*4*4); for (auto fec_type : {FECType::H1, FECType::L2, FECType::ND, FECType::RT}) { CheckProjectMatch(mesh, submesh, fec_type); } } } SECTION("NonconformalRefine") { Array subdomain_attributes{GENERATE(range(1,4))}; mesh.UniformRefinement(); RefineSingleAttachedElement(mesh, 1, subdomain_attributes[0], true); SECTION("Single") { auto submesh = SubMesh::CreateFromBoundary(mesh, subdomain_attributes); auto ncmesh_exposed = NCMeshExposed(*submesh.ncmesh); CHECK(ncmesh_exposed.GetNumRootElements() == 1); CHECK(ncmesh_exposed.CountUniqueLeafElements() == 4 - 1 + 4); for (auto fec_type : {FECType::H1, FECType::L2, FECType::ND, FECType::RT}) { CheckProjectMatch(mesh, submesh, fec_type); } } SECTION("Double") { RefineSingleAttachedElement(mesh, 1, subdomain_attributes[0], false); auto submesh = SubMesh::CreateFromBoundary(mesh, subdomain_attributes); auto ncmesh_exposed = NCMeshExposed(*submesh.ncmesh); CHECK(ncmesh_exposed.GetNumRootElements() == 1); CHECK(ncmesh_exposed.CountUniqueLeafElements() == 4 - 1 + 4 - 1 + 4); for (auto fec_type : {FECType::H1, FECType::L2, FECType::ND, FECType::RT}) { CheckProjectMatch(mesh, submesh, fec_type); } } } } }