// Copyright (c) 2010-2023, 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. // // ------------------------------------------------- // Overlapping Grids Miniapp: Poisson problem (ex1p) // ------------------------------------------------- // // This example code demonstrates use of MFEM to solve the Poisson problem: // // -Delta u = 1 \in [0, 1]^2, u_b = 0 \in \dO // // on two overlapping grids. Using simultaneous Schwarz iterations, the Poisson // equation is solved iteratively, with boundary data interpolated between the // overlapping boundaries for each grid. The overlapping Schwarz method was // introduced by H. A. Schwarz in 1870, see also Section 2.2 of "Stability // analysis of a singlerate and multirate predictor-corrector scheme for // overlapping grids" by Mittal, Dutta and Fischer, arXiv:2010.00118. // // Compile with: make schwarz_ex1p // // Sample runs: mpirun -np 4 schwarz_ex1p -nm 3 -np1 2 -np2 1 -np3 1 // mpirun -np 4 schwarz_ex1p -nm 2 -np1 2 -np2 2 // mpirun -np 4 schwarz_ex1p -nm 2 -np1 2 -np2 2 -m1 ../../data/star.mesh -m2 ../../data/beam-quad.mesh #include "mfem.hpp" #include #include using namespace std; using namespace mfem; // Method to use FindPointsGSLIB to determine the boundary points of a mesh that // are interior to another mesh. void GetInterdomainBoundaryPoints(OversetFindPointsGSLIB &finder, Vector &vxyz, int color, Array ess_tdof_list, Array &ess_tdof_list_int, int dim) { int number_boundary = ess_tdof_list.Size(), number_true = vxyz.Size()/dim; Vector bnd(number_boundary*dim); Array colorv(number_boundary); for (int i = 0; i < number_boundary; i++) { int idx = ess_tdof_list[i]; for (int d = 0; d < dim; d++) { bnd(i+d*number_boundary) = vxyz(idx + d*number_true); } colorv[i] = (unsigned int)color; } finder.FindPoints(bnd, colorv); const Array &code_out = finder.GetCode(); // Setup ess_tdof_list_int for (int i = 0; i < number_boundary; i++) { int idx = ess_tdof_list[i]; if (code_out[i] != 2) { ess_tdof_list_int.Append(idx); } } } int main(int argc, char *argv[]) { // Initialize MPI and HYPRE. Mpi::Init(argc, argv); int num_procs = Mpi::WorldSize(); int myid = Mpi::WorldRank(); Hypre::Init(); // Parse command-line options. int lim_meshes = 3; // should be greater than nmeshes Array mesh_file_list(lim_meshes); Array np_list(lim_meshes), rs_levels(lim_meshes), rp_levels(lim_meshes); mesh_file_list[0] = "../../data/square-disc.mesh"; mesh_file_list[1] = "../../data/inline-quad.mesh"; mesh_file_list[2] = "../../data/inline-quad.mesh"; int order = 2; bool visualization = true; rs_levels = 0; rp_levels = 0; np_list = 0; double rel_tol = 1.e-8; int nmeshes = 3; OptionsParser args(argc, argv); args.AddOption(&mesh_file_list[0], "-m1", "--mesh", "Mesh file to use."); args.AddOption(&mesh_file_list[1], "-m2", "--mesh", "Mesh file to use."); args.AddOption(&mesh_file_list[2], "-m3", "--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(&rs_levels[0], "-r1", "--refine-serial", "Number of times to refine the mesh 1 uniformly in serial."); args.AddOption(&rs_levels[1], "-r2", "--refine-serial", "Number of times to refine the mesh 2 uniformly in serial."); args.AddOption(&rs_levels[2], "-r3", "--refine-serial", "Number of times to refine the mesh 3 uniformly in serial."); args.AddOption(&rp_levels[0], "-rp1", "--refine-parallel", "Number of times to refine the mesh 1 uniformly in parallel."); args.AddOption(&rp_levels[1], "-rp2", "--refine-parallel", "Number of times to refine the mesh 2 uniformly in parallel."); args.AddOption(&rp_levels[2], "-rp3", "--refine-parallel", "Number of times to refine the mesh 3 uniformly in parallel."); args.AddOption(&np_list[0], "-np1", "--np1", "number of MPI ranks for mesh 1"); args.AddOption(&np_list[1], "-np2", "--np2", "number of MPI ranks for mesh 2"); args.AddOption(&np_list[2], "-np3", "--np3", "number of MPI ranks for mesh 3"); args.AddOption(&nmeshes, "-nm", "--nm", "number of meshes"); args.AddOption(&rel_tol, "-rt", "--relative tolerance", "Tolerance for Schwarz iteration convergence criterion."); args.Parse(); if (!args.Good()) { args.PrintUsage(cout); return 1; } if (myid == 0) { args.PrintOptions(cout); } // Check number of mpi ranks specified for each mesh. If individual mpi ranks // are not specified for all the meshes, set some default values. MFEM_VERIFY(num_procs >= nmeshes, "Not enough MPI ranks."); if (np_list.Sum() == 0) { int np_per_mesh = num_procs/nmeshes; for (int i = 0; i < nmeshes; i++) { np_list[i] = np_per_mesh; } np_list[nmeshes-1] += num_procs - nmeshes*np_per_mesh; } MFEM_VERIFY(np_list.Sum() == num_procs, " The individual mpi ranks for each" " of the meshes do not add up to the total ranks specified."); // Setup MPI communicator for each mesh by splitting MPI_COMM_WORLD. MPI_Comm *comml = new MPI_Comm; int color = 0; int npsum = 0; for (int i = 0; i < nmeshes; i++) { npsum += np_list[i]; if (myid < npsum) { color = i; break; } } MPI_Comm_split(MPI_COMM_WORLD, color, myid, comml); int myidlocal, numproclocal; MPI_Comm_rank(*comml, &myidlocal); MPI_Comm_size(*comml, &numproclocal); // Read the mesh from the given mesh file. We can handle triangular, // quadrilateral, tetrahedral, hexahedral, surface and volume meshes with // the same code. Mesh *mesh = new Mesh(mesh_file_list[color], 1, 1); int dim = mesh->Dimension(); // Refine the mesh to increase the resolution. In this example we do // 'ref_levels' of uniform refinement. We choose 'ref_levels' to be the // largest number that gives a final mesh with no more than 50,000 elements. for (int lev = 0; lev < rs_levels[color]; lev++) { mesh->UniformRefinement(); } // Define a parallel mesh by a partitioning of the serial mesh. Refine this // mesh further in parallel to increase the resolution. Once the parallel // mesh is defined, the serial mesh can be deleted. ParMesh *pmesh; pmesh = new ParMesh(*comml, *mesh); for (int l = 0; l < rp_levels[color]; l++) { pmesh->UniformRefinement(); } delete mesh; // Define a finite element space on the mesh. Here we use continuous // Lagrange finite elements of the specified order. If order < 1, we // instead use an isoparametric/isogeometric space. FiniteElementCollection *fec; if (order > 0) { fec = new H1_FECollection(order, dim); } else if (pmesh->GetNodes()) { fec = pmesh->GetNodes()->OwnFEC(); if (myid == 0) { cout << "Using isoparametric FEs: " << fec->Name() << endl; } } else { fec = new H1_FECollection(order = 1, dim); } ParFiniteElementSpace *fespace = new ParFiniteElementSpace(pmesh, fec); HYPRE_BigInt size = fespace->GlobalTrueVSize(); if (myid == 0) { cout << "Number of finite element unknowns: " << size << endl; } // Determine the list of true (i.e. conforming) essential boundary dofs. // In this example, the boundary conditions are defined by marking all // the boundary attributes from the mesh as essential (Dirichlet) and // converting them to a list of true dofs. Array ess_tdof_list; if (pmesh->bdr_attributes.Size()) { Array ess_bdr(pmesh->bdr_attributes.Max()); ess_bdr = 1; fespace->GetEssentialTrueDofs(ess_bdr, ess_tdof_list); } // Set up the linear form b(.) which corresponds to the right-hand side of // the FEM linear system, which in this case is (1,phi_i) where phi_i are // the basis functions in the finite element fespace1. ParLinearForm *b = new ParLinearForm(fespace); ConstantCoefficient one(1.0); b->AddDomainIntegrator(new DomainLFIntegrator(one)); b->Assemble(); // Define the solution vector x as a finite element grid function // corresponding to fespace1. Initialize x with initial guess of zero, // which satisfies the boundary conditions. ParGridFunction x(fespace); x = 0.0; x.SetTrueVector(); // Setup FindPointsGSLIB and determine points on each mesh's boundary that // are interior to another mesh. pmesh->SetCurvature(order, false, dim, Ordering::byNODES); { Vector vxyz = *pmesh->GetNodes(); // For the default mesh inputs, we need to rescale inline-quad.mesh such // that it does not cover the entire domain [0, 1]^2 and still has a // non-trivial overlap with the other mesh. if (strcmp(mesh_file_list[0], "../../data/square-disc.mesh") == 0 && strcmp(mesh_file_list[1], "../../data/inline-quad.mesh") == 0 && strcmp(mesh_file_list[2], "../../data/inline-quad.mesh") == 0 ) { if (nmeshes == 2) { if (color == 1) // rescale from [0, 1]^2 to [0.25, 0.75]^2 { for (int i = 0; i < vxyz.Size(); i++) { vxyz(i) = 0.5 + 0.5*(vxyz(i)-0.5); } } } else if (nmeshes == 3) { if (color == 1) { // rescale from [0, 1]^2 to [0.21, 0.61] in x and [0.25, 0.75] in y const int pts_cnt = vxyz.Size()/dim; for (int i = 0; i < pts_cnt; i++) { vxyz(i) = 0.41 + 0.4*(vxyz(i)-0.5); } for (int i = 0; i < pts_cnt; i++) { vxyz(i+pts_cnt) = 0.5 + 0.5*(vxyz(i+pts_cnt)-0.5); } } else if (color == 2) { // rescale from [0, 1]^2 to [0.4, 0.8] in x and [0.2, 0.8] in y const int pts_cnt = vxyz.Size()/dim; for (int i = 0; i < pts_cnt; i++) { vxyz(i) = 0.6 + 0.4*(vxyz(i)-0.5); } for (int i = 0; i < pts_cnt; i++) { vxyz(i+pts_cnt) = 0.5 + 0.6*(vxyz(i+pts_cnt)-0.5); } } } } pmesh->SetNodes(vxyz); } pmesh->GetNodes()->SetTrueVector(); Vector vxyz = pmesh->GetNodes()->GetTrueVector(); OversetFindPointsGSLIB finder(MPI_COMM_WORLD); finder.Setup(*pmesh, color); Array ess_tdof_list_int; GetInterdomainBoundaryPoints(finder, vxyz, color, ess_tdof_list, ess_tdof_list_int, dim); // Use FindPointsGSLIB to interpolate the solution at interdomain boundary // points. const int number_boundary = ess_tdof_list_int.Size(), number_true = vxyz.Size()/dim; int number_boundary_g = number_boundary; MPI_Allreduce(&number_boundary, &number_boundary_g, 1, MPI_INT, MPI_SUM, *comml); MFEM_VERIFY(number_boundary_g != 0, " Please use overlapping grids."); Array colorv; colorv.SetSize(number_boundary); MPI_Barrier(MPI_COMM_WORLD); Vector bnd(number_boundary*dim); for (int i = 0; i < number_boundary; i++) { int idx = ess_tdof_list_int[i]; for (int d = 0; d < dim; d++) { bnd(i+d*number_boundary) = vxyz(idx + d*number_true); } colorv[i] = (unsigned int)color; } Vector interp_vals1(number_boundary); finder.Interpolate(bnd, colorv, x, interp_vals1); // Set up the bilinear form a(.,.) on the finite element space corresponding // to the Laplacian operator -Delta, by adding a Diffusion integrator. ParBilinearForm *a = new ParBilinearForm(fespace); a->AddDomainIntegrator(new DiffusionIntegrator(one)); // Assemble the bilinear form and the corresponding linear system, // applying any necessary transformations such as: eliminating boundary // conditions, applying conforming constraints for non-conforming AMR, // static condensation, etc. a->Assemble(); delete b; // Use simultaneous Schwarz iterations to iteratively solve the PDE and // interpolate interdomain boundary data to impose Dirichlet boundary // conditions. int NiterSchwarz = 100; for (int schwarz = 0; schwarz < NiterSchwarz; schwarz++) { b = new ParLinearForm(fespace); b->AddDomainIntegrator(new DomainLFIntegrator(one)); b->Assemble(); OperatorPtr A; Vector B, X; a->FormLinearSystem(ess_tdof_list, x, *b, A, X, B); // Solve the linear system A X = B. // Use a simple symmetric Gauss-Seidel preconditioner with PCG. Solver *prec = NULL; prec = new HypreBoomerAMG; dynamic_cast(prec)->SetPrintLevel(-1); CGSolver cg(*comml); cg.SetRelTol(1e-12); cg.SetMaxIter(2000); cg.SetPrintLevel(0); cg.SetPreconditioner(*prec); cg.SetOperator(*A); cg.Mult(B, X); delete prec; // Recover the solution as a finite element grid function. a->RecoverFEMSolution(X, *b, x); // Interpolate boundary condition finder.Interpolate(x, interp_vals1); double dxmax = std::numeric_limits::min(); double xinf = x.Normlinf(); double xinfg = xinf; MPI_Allreduce(&xinf, &xinfg, 1, MPI_DOUBLE, MPI_MAX, MPI_COMM_WORLD); x.SetTrueVector(); Vector xt = x.GetTrueVector(); for (int i = 0; i < number_boundary; i++) { int idx = ess_tdof_list_int[i]; double dx = std::abs(xt(idx)-interp_vals1(i))/xinfg; if (dx > dxmax) { dxmax = dx; } xt(idx) = interp_vals1(i); } x.SetFromTrueDofs(xt); double dxmaxg = dxmax; MPI_Allreduce(&dxmax, &dxmaxg, 1, MPI_DOUBLE, MPI_MAX, MPI_COMM_WORLD); delete b; if (myid == 0) { std::cout << std::setprecision(8) << "Iteration: " << schwarz << ", Relative residual: " << dxmaxg << endl; } if (dxmaxg < rel_tol) { break; } } // Send the solution by socket to a GLVis server. if (visualization) { char vishost[] = "localhost"; int visport = 19916; socketstream sol_sock(vishost, visport); sol_sock << "parallel " << num_procs << " " << myid << "\n"; sol_sock.precision(8); sol_sock << "solution\n" << *pmesh << x << flush; } // 15. Free the used memory. finder.FreeData(); delete a; delete fespace; if (order > 0) { delete fec; } delete pmesh; delete comml; return 0; }