// Copyright (c) 2010-2022, Lawrence Livermore National Security, LLC. Produced // at the Lawrence Livermore National Laboratory. All Rights reserved. See files // LICENSE and NOTICE for details. LLNL-CODE-806117. // // This file is part of the MFEM library. For more information and source code // availability visit https://mfem.org. // // MFEM is free software; you can redistribute it and/or modify it under the // terms of the BSD-3 license. We welcome feedback and contributions, see file // CONTRIBUTING.md for details. #include "mfem.hpp" #include "unit_tests.hpp" namespace mfem { constexpr double 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); double 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); double 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); Mesh mesh(mesh_fname, 1, 1); mesh.EnsureNCMesh(true); double original_volume = mesh.GetElementVolume(0); Array ref(1); ref[0].ref_type = ref_type; ref[0].index = 0; mesh.GeneralRefinement(ref, 1); double 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); double original_volume = mesh.GetElementVolume(0); Array ref(1); ref[0].ref_type = ref_type; ref[0].index = 0; mesh.GeneralRefinement(ref, 1); Array elem_error(mesh.GetNE()); for (int i = 0; i < mesh.GetNE(); ++i) { elem_error[i] = 0.0; } mesh.DerefineByError(elem_error, 1.0); double 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); double diag_lsum = diag.Sum(), nc_diag_lsum = nc_diag.Sum(); double diag_gsum = 0.0, nc_diag_gsum = 0.0; MPI_Allreduce(&diag_lsum, &diag_gsum, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD); MPI_Allreduce(&nc_diag_lsum, &nc_diag_gsum, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD); double 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); double diag_lsum = diag.Sum(), nc_diag_lsum = nc_diag.Sum(); double diag_gsum = 0.0, nc_diag_gsum = 0.0; MPI_Allreduce(&diag_lsum, &diag_gsum, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD); MPI_Allreduce(&nc_diag_lsum, &nc_diag_gsum, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD); double error = fabs(diag_gsum - nc_diag_gsum); CAPTURE(order, error); REQUIRE(error == MFEM_Approx(0.0, EPS)); MPI_Barrier(MPI_COMM_WORLD); } } } // test case #endif // MFEM_USE_MPI } // namespace mfem