// 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. // // ----------------------------------------------------------------- // 3:1 Refinement Miniapp: Perform 3:1 anisotropic mesh refinements // ----------------------------------------------------------------- // // This miniapp performs random 3:1 refinements of a quadrilateral or hexahedral // mesh. A diffusion equation is solved in an H1 finite element space defined on // the refined mesh, and its continuity is verified. // // Compile with: make ref321 // // Sample runs: ref321 -mm -dim 2 -o 2 -r 100 // ref321 -mm -dim 3 -o 2 -r 100 // ref321 -m ../../data/star.mesh -o 2 -r 100 #include "mfem.hpp" #include #include using namespace std; using namespace mfem; real_t CheckH1Continuity(GridFunction & x); // Find the two children of parent element `elem` after its refinement in one // direction. void FindChildren(const Mesh & mesh, int elem, Array & children) { const CoarseFineTransformations& cf = mesh.ncmesh->GetRefinementTransforms(); MFEM_ASSERT(mesh.GetNE() == cf.embeddings.Size(), ""); // Note that row `elem` of the table constructed by cf.MakeCoarseToFineTable // is an alternative to this global loop, but constructing the table is also // a global operation with global storage. for (int i = 0; i < mesh.GetNE(); i++) { const int p = cf.embeddings[i].parent; if (p == elem) { children.Append(i); } } } // Refine 3:1 via 2 refinements with scalings 2/3 and 1/2. void Refine31(Mesh & mesh, int elem, char type) { Array refs; // Refinement is defined in ncmesh.hpp refs.Append(Refinement(elem, type, 2.0 / 3.0)); mesh.GeneralRefinement(refs); // Find the elements with parent `elem` Array children; FindChildren(mesh, elem, children); MFEM_ASSERT(children.Size() == 2, ""); const int elem1 = children[0]; refs.SetSize(0); refs.Append(Refinement(elem1, type)); // Default scaling of 0.5 mesh.GeneralRefinement(refs); } // Deterministic, somewhat random integer generator int MyRand(int & s) { s++; const double a = 1000 * sin(s * 1.1234 * M_PI); return int(std::abs(a)); } // Randomly select elements for 3:1 refinements in random directions. void TestAnisoRefRandom(int iter, int dim, Mesh & mesh) { int seed = 0; for (int i = 0; i < iter; i++) { const int elem = MyRand(seed) % mesh.GetNE(); const int t = MyRand(seed) % dim; auto type = t == 0 ? Refinement::X : (t == 1 ? Refinement::Y : Refinement::Z); Refine31(mesh, elem, type); } mesh.EnsureNodes(); mesh.SetScaledNCMesh(); } int main(int argc, char *argv[]) { // 1. Parse command-line options. const char *mesh_file = "../../data/star.mesh"; int order = 1; bool visualization = true; bool makeMesh = false; int num_refs = 1; int tdim = 2; // Mesh dimension OptionsParser args(argc, argv); args.AddOption(&mesh_file, "-m", "--mesh", "Mesh file to use."); args.AddOption(&order, "-o", "--order", "Finite element order (polynomial degree) or -1 for" " isoparametric space."); args.AddOption(&visualization, "-vis", "--visualization", "-no-vis", "--no-visualization", "Enable or disable GLVis visualization."); args.AddOption(&makeMesh, "-mm", "--make-mesh", "-no-mm", "--no-make-mesh", "Create Cartesian mesh"); args.AddOption(&tdim, "-dim", "--dimension", "Dimension for Cartesian mesh"); args.AddOption(&num_refs, "-r", "--refs", "Number of 3:1 refinements"); args.Parse(); if (!args.Good()) { args.PrintUsage(cout); return 1; } args.PrintOptions(cout); // 2. Create or read the mesh from the given mesh file. Mesh mesh; if (makeMesh) { mesh = tdim == 3 ? Mesh::MakeCartesian3D(2, 2, 2, Element::HEXAHEDRON) : Mesh::MakeCartesian2D(2, 2, Element::QUADRILATERAL); } else { mesh = Mesh::LoadFromFile(mesh_file, 1, 1); } const int dim = mesh.Dimension(); // 3. Randomly perform 3:1 refinements in the mesh. TestAnisoRefRandom(num_refs, tdim, mesh); // 4. Define a finite element space on the mesh. Here we use continuous // Lagrange finite elements of the specified order. H1_FECollection fec(order, dim); FiniteElementSpace fespace(&mesh, &fec); cout << "Number of finite element unknowns: " << fespace.GetTrueVSize() << endl; // 5. Define the solution vector x as a finite element grid function // corresponding to fespace. Solve the Poisson problem, as in ex1. GridFunction x(&fespace); { x = 0.0; LinearForm b(&fespace); ConstantCoefficient one(1.0); b.AddDomainIntegrator(new DomainLFIntegrator(one)); b.Assemble(); BilinearForm a(&fespace); a.AddDomainIntegrator(new DiffusionIntegrator()); a.Assemble(); OperatorPtr A; Vector B, X; Array ess_tdof_list; if (mesh.bdr_attributes.Size()) { Array ess_bdr(mesh.bdr_attributes.Max()); ess_bdr = 1; fespace.GetEssentialTrueDofs(ess_bdr, ess_tdof_list); } a.FormLinearSystem(ess_tdof_list, x, b, A, X, B); GSSmoother M((SparseMatrix&)(*A)); PCG(*A, M, B, X, 1, 2000, 1e-12, 0.0); a.RecoverFEMSolution(X, b, x); } // 6. Verify the continuity of the projected function in H1. const real_t h1err = CheckH1Continuity(x); cout << "Error of H1 continuity: " << h1err << endl; MFEM_VERIFY(h1err < 1.0e-7, ""); // 7. Save the refined mesh and the solution. This output can be viewed later // using GLVis: "glvis -m ref321.mesh -g sol.gf". ofstream mesh_ofs("ref321.mesh"); mesh_ofs.precision(8); mesh.Print(mesh_ofs); ofstream sol_ofs("sol.gf"); sol_ofs.precision(8); x.Save(sol_ofs); // 8. Send the solution by socket to a GLVis server. if (visualization) { char vishost[] = "localhost"; int visport = 19916; socketstream sol_sock(vishost, visport); sol_sock.precision(8); sol_sock << "solution\n" << mesh << x << flush; } return 0; } real_t CheckH1Continuity(GridFunction & x) { const FiniteElementSpace *fes = x.FESpace(); Mesh *mesh = fes->GetMesh(); const int dim = mesh->Dimension(); // Following the example of KellyErrorEstimator::ComputeEstimates(), we loop // over interior faces and then shared faces. // Compute error contribution from local interior faces real_t errorMax = 0.0; for (int f = 0; f < mesh->GetNumFaces(); f++) { if (mesh->FaceIsInterior(f)) { int Inf1, Inf2, NCFace; mesh->GetFaceInfos(f, &Inf1, &Inf2, &NCFace); auto FT = mesh->GetFaceElementTransformations(f); const int faceOrder = dim == 3 ? fes->GetFaceOrder(f) : fes->GetEdgeOrder(f); auto &int_rule = IntRules.Get(FT->FaceGeom, 2 * faceOrder); const auto nip = int_rule.GetNPoints(); // Convention: // * Conforming face: Face side with smaller element id handles the // integration // * Non-conforming face: The slave handles the integration. // See FaceInfo documentation for details. bool isNCSlave = FT->Elem2No >= 0 && NCFace >= 0; bool isConforming = FT->Elem2No >= 0 && NCFace == -1; if ((FT->Elem1No < FT->Elem2No && isConforming) || isNCSlave) { for (int i = 0; i < nip; i++) { const auto &fip = int_rule.IntPoint(i); IntegrationPoint ip; FT->Loc1.Transform(fip, ip); const real_t v1 = x.GetValue(FT->Elem1No, ip); FT->Loc2.Transform(fip, ip); const real_t v2 = x.GetValue(FT->Elem2No, ip); const real_t err_i = std::abs(v1 - v2); errorMax = std::max(errorMax, err_i); } } } } return errorMax; }