// MFEM Example 24 // // Compile with: make ex24 // // Sample runs: ex24 // ex24 -c // ex24 -p 0 // ex24 -p 0 -c // ex24 -p 1 // ex24 -p 1 -c // ex24 -p 2 // ex24 -p 2 -c // ex24 -p 3 // ex24 -p 3 -c // ex24 -p 4 // ex24 -p 4 -c // ex24 -p 5 // ex24 -p 5 -c // ex24 -p 6 // ex24 -p 6 -c // // Device sample runs: // ex24 -pa // ex24 -pa -c // ex24 -p 0 -pa // ex24 -p 0 -pa -c // ex24 -p 1 -pa // ex24 -p 1 -pa -c // ex24 -p 2 -pa // ex24 -p 2 -pa -c // ex24 -p 3 -pa // ex24 -p 3 -pa -c // ex24 -p 4 -pa // ex24 -p 4 -pa -c // ex24 -p 5 -pa // ex24 -p 5 -pa -c // ex24 -p 6 -pa // ex24 -p 6 -pa -c #include "mfem.hpp" #include "../general/dbg.hpp" #include #include #include using namespace std; using namespace mfem; // Constant variables const double pi = M_PI; const double eps = 1.e-12; static socketstream glvis; const int visport = 19916; const char vishost[] = "localhost"; // Forward declaration void SnapNodes(Mesh *mesh); // Surface mesh class template class Surface: public Mesh { protected: const int Order, Nx, Ny, RefLvl; const double Sx; const double Sy; const int SpaceDim; const Element::Type ElType; const bool GenerateEdges; const bool SpaceFillingCurves; const bool Discontinuous; Type *S; public: Surface(const int order, const int nx = 4, const int ny = 4, const double sx = 1.0, const double sy = 1.0, const int sdim = 3, const Element::Type type = Element::QUADRILATERAL, const bool edges = true, const bool space_filling_curves = true, const bool discontinuous = false): Mesh(nx, ny, type, edges, sx, sy, space_filling_curves), Order(order), Nx(nx), Ny(ny), RefLvl(0), Sx(sx), Sy(sy), SpaceDim(sdim), ElType(type), GenerateEdges(edges), SpaceFillingCurves(space_filling_curves), Discontinuous(discontinuous), S(static_cast(this)) { EnsureNodes(); S->Prefix(); S->Equation(); S->Postfix(); RemoveUnusedVertices(); RemoveInternalBoundaries(); SetCurvature(order, discontinuous, sdim, Ordering::byVDIM); GridFunction &nodes = *GetNodes(); for (int i = 0; i < nodes.Size(); i++) { if (std::abs(nodes(i)) < eps) { nodes(i) = 0.0; } } } Surface(const bool snap, const int order, const int ref_level, const int dim, const int NVert, const int NElem, const int NBdrElem, const int sdim): Mesh(dim, NVert, NElem, NBdrElem, sdim), Order(order), Nx(NVert), Ny(NElem), RefLvl(ref_level), Sx(), Sy(), SpaceDim(sdim), ElType(Element::QUADRILATERAL), GenerateEdges(true), SpaceFillingCurves(true), Discontinuous(false), S(static_cast(this)) { S->Equation(); } void Prefix() { SetCurvature(Order, Discontinuous, SpaceDim, Ordering::byNODES); } void Equation() { Transform(Type::Parametrization); } void Postfix() { dbg("Postfix");} }; // Helicoid surface struct Helicoid: public Surface { Helicoid(int order, int nx, int ny): Surface(order, nx, ny) {} static void Parametrization(const Vector &x, Vector &p) { p.SetSize(3); const double a = 1.0; // u in [0,2π] and v in [-2π/3,2π/3] const double u = 2.0*pi*x[0]; const double v = 2.0*pi*(2.0*x[1]-1.0)/3.0; p[0] = a*cos(u)*sinh(v); p[1] = a*sin(u)*sinh(v); p[2] = a*u; } }; // Catenoid surface struct Catenoid: public Surface { Catenoid(int order, int nx, int ny): Surface(order, nx, ny) {} static void Parametrization(const Vector &x, Vector &p) { p.SetSize(3); // u in [0,2π] and v in [-2π/3,2π/3] const double u = 2.0*pi*x[0]; const double v = 2.0*pi*(2.0*x[1]-1.0)/3.0; p[0] = cos(u)*cosh(v); p[1] = sin(u)*cosh(v); p[2] = v; } // Postfix of the Catenoid surface void Postfix() { Array v2v(GetNV()); for (int i = 0; i < v2v.Size(); i++) { v2v[i] = i; } // identify vertices on vertical lines for (int j = 0; j <= Ny; j++) { const int v_old = Nx + j * (Nx + 1); const int v_new = j * (Nx + 1); v2v[v_old] = v_new; } // renumber elements for (int i = 0; i < GetNE(); i++) { Element *el = GetElement(i); int *v = el->GetVertices(); const int nv = el->GetNVertices(); for (int j = 0; j < nv; j++) { v[j] = v2v[v[j]]; } } // renumber boundary elements for (int i = 0; i < GetNBE(); i++) { Element *el = GetBdrElement(i); int *v = el->GetVertices(); const int nv = el->GetNVertices(); for (int j = 0; j < nv; j++) { v[j] = v2v[v[j]]; } } } }; // Enneper's surface struct Enneper: public Surface { Enneper(int order, int nx, int ny): Surface(order, nx, ny) {} static void Parametrization(const Vector &x, Vector &p) { p.SetSize(3); // (u,v) in [-2, +2] const double u = 2.0*(2.0*x[0]-1.0); const double v = 2.0*(2.0*x[1]-1.0); p[0] = +u - u*u*u/3.0 + u*v*v; p[1] = -v - u*u*v + v*v*v/3.0; p[2] = u*u - v*v; } }; // Parametrization of Scherk's surface struct Scherk: public Surface { Scherk(int order, int nx, int ny): Surface(order, nx, ny) {} static void Parametrization(const Vector &x, Vector &p) { p.SetSize(3); const double alpha = 0.49; // (u,v) in [-απ, +απ] const double u = alpha*pi*(2.0*x[0]-1.0); const double v = alpha*pi*(2.0*x[1]-1.0); p[0] = u; p[1] = v; p[2] = log(cos(u)/cos(v)); } }; // Shell surface model struct Shell: public Surface { Shell(int order, int nx, int ny): Surface(order, nx, ny) {} static void Parametrization(const Vector &x, Vector &p) { p.SetSize(3); // u in [0,2π] and v in [-15, 6] const double u = 2.0*pi*x[0]; const double v = 21.0*x[1]-15.0; p[0] = +1.0*pow(1.16,v)*cos(v)*(1.0+cos(u)); p[1] = -1.0*pow(1.16,v)*sin(v)*(1.0+cos(u)); p[2] = -2.0*pow(1.16,v)*(1.0+sin(u)); } }; // Hold surface struct Hold: public Surface { Hold(int order, int nx, int ny): Surface(order, nx, ny) {} static void Parametrization(const Vector &x, Vector &p) { p.SetSize(3); // u in [0,2π] and v in [0,1] const double u = 2.0*pi*x[0]; const double v = x[1]; p[0] = cos(u)*(1.0 + 0.3*sin(5.*u + pi*v)); p[1] = sin(u)*(1.0 + 0.3*sin(5.*u + pi*v)); p[2] = v; } }; // 1/4th Peach street model // Could set BC: ess_bdr[0] = false; with attribute set to '1' from postfix struct QPeach: public Surface { QPeach(int order, int nx, int ny): Surface(order, nx, ny) {} static void Parametrization(const Vector &X, Vector &p) { p = X; const double x = 2.0*X[0]-1.0; const double y = X[1]; const double r = sqrt(x*x + y*y); const double t = (x==0.0) ? pi/2.0 : (y==0.0 && x>0.0) ? 0. : (y==0.0 && x<0.0) ? pi : acos(x/r); const double sqrtx = sqrt(1.0 + x*x); const double sqrty = sqrt(1.0 + y*y); const bool yaxis = pi/4.0 vertices; GetFaceVertices(fn, vertices); const GridFunction *nodes = GetNodes(); Vector nval; double R[2], X[2][3]; for (int v = 0; v < 2; v++) { R[v] = 0.0; const int iv = vertices[v]; for (int d = 0; d < 3; d++) { nodes->GetNodalValues(nval, d+1); const double x = X[v][d] = nval[iv]; if (d < 2) { R[v] += x*x; } } } if (fabs(X[0][1])<=eps && fabs(X[1][1])<=eps && (R[0]>0.1 || R[1]>0.1)) { el->SetAttribute(1); } else { el->SetAttribute(2); } } } }; // Full Peach street model struct FPeach: public Surface { FPeach(const int order, const int ref_level): // Snap, order, ref_level, dim, Nvert, Nelem, NBdrElem, sdim Surface(true, order, ref_level, 2, 8, 6, 0, 3) { } void Equation() { const double quad_v[8][3] = { {-1, -1, -1}, {+1, -1, -1}, {+1, +1, -1}, {-1, +1, -1}, {-1, -1, +1}, {+1, -1, +1}, {+1, +1, +1}, {-1, +1, +1} }; const int quad_e[6][4] = { {3, 2, 1, 0}, {0, 1, 5, 4}, {1, 2, 6, 5}, {2, 3, 7, 6}, {3, 0, 4, 7}, {4, 5, 6, 7} }; for (int j = 0; j < Nx; j++) { AddVertex(quad_v[j]); } for (int j = 0; j < Ny; j++) { AddQuad(quad_e[j], j+1); } FinalizeQuadMesh(1, 1, true); SetCurvature(Order, Discontinuous, SpaceDim, Ordering::byNODES); UniformRefinement(); SnapNodes(this); } }; // Visualize some solution on the given mesh static void Visualize(Mesh *mesh, const int order, const bool pause, const char *keys = NULL, const int width = 0, const int height = 0) { const H1_FECollection fec(2, 2); FiniteElementSpace *sfes = new FiniteElementSpace(mesh, &fec); GridFunction K(sfes); const int NE = mesh->GetNE(); const Element::Type type = Element::QUADRILATERAL; const IntegrationRule *ir = &IntRules.Get(type, order); for (int i = 0; i < NE; i++) { ElementTransformation *tr = mesh->GetElementTransformation(i); for (int j = 0; j < ir->GetNPoints(); j++) { tr->SetIntPoint(&ir->IntPoint(j)); K(i) = tr->Jacobian().Weight(); } } //glvis << "mesh\n" << *mesh << flush; glvis.precision(8); glvis << "solution\n" << *mesh << K << flush; if (keys) { glvis << "keys " << keys << "\n" << flush; } if (width * height > 0) { glvis << "window_size " << width << " " << height <<"\n" << flush; } if (pause) { glvis << "pause\n" << flush; } } // Surface solver class template class SurfaceSolver { protected: bool pa, visualization, pause; int niter, sdim, order; Mesh *mesh; Vector X, B; OperatorPtr A; FiniteElementSpace *fes; BilinearForm a; Array bc; GridFunction x, b, *nodes, solution; ConstantCoefficient one; Type *solver; public: SurfaceSolver(const bool p, const bool v, const int n, const bool w, const int o, Mesh *m, FiniteElementSpace *f, Array dbc): pa(p), visualization(v), pause(w), niter(n), sdim(m->SpaceDimension()), order(o), mesh(m), fes(f), a(fes), bc(dbc), x(fes), b(fes), nodes(mesh->GetNodes()), solution(*nodes), one(1.0), solver(static_cast(this)) { Solve(); } void Solve() { solver->Solve(); } }; // Surface solver 'by compnents' class ByComponent: public SurfaceSolver { public: ByComponent(const bool pa, const bool vis, const int niter, const bool wait, const int order, Mesh *mesh, FiniteElementSpace *fes, Array dbc): SurfaceSolver(pa, vis, niter, wait, order, mesh, fes, dbc) { dbg(""); } void Solve() { if (pa) { a.SetAssemblyLevel(AssemblyLevel::PARTIAL); } a.AddDomainIntegrator(new DiffusionIntegrator(one)); for (int iiter=0; iiter(*A)); PCG(*A, M, B, X, 3, 2000, eps, 0.0); } else { CG(*A, B, X, 3, 2000, eps, 0.0); } // Recover the solution as a finite element grid function. a.RecoverFEMSolution(X, b, x); SetComponent(solution, x, i); } *nodes = solution; // Send the solution by socket to a GLVis server. if (visualization) { Visualize(mesh, order, pause); } a.Update(); } } private: void SetComponent(GridFunction &X, const GridFunction &Xi, const int d) { const int ndof = fes->GetNDofs(); for (int i = 0; i < ndof; i++) { X[d*ndof + i] = Xi[i]; } } void GetComponent(const GridFunction &X, GridFunction &Xi, const int d) { const int ndof = fes->GetNDofs(); for (int i = 0; i < ndof; i++) { Xi[i] = X[d*ndof + i]; } } }; // Surface solver 'by vector' class ByVector: public SurfaceSolver { public: ByVector(const bool pa, const bool vis, const int niter, const bool wait, const int order, Mesh *mesh, FiniteElementSpace *fes, Array dbc): SurfaceSolver(pa, vis, niter, wait, order, mesh, fes, dbc) { dbg(""); } void Solve() { if (pa) { a.SetAssemblyLevel(AssemblyLevel::PARTIAL); } a.AddDomainIntegrator(new VectorDiffusionIntegrator(one)); for (int iiter=0; iiter(*A)); PCG(*A, M, B, X, 3, 2000, eps, 0.0); } else { CG(*A, B, X, 3, 2000, eps, 0.0); } // Recover the solution as a finite element grid function. a.RecoverFEMSolution(X, b, x); *nodes = x; // Send the solution by socket to a GLVis server. if (visualization) { Visualize(mesh, order, pause); } a.Update(); } } }; int main(int argc, char *argv[]) { int nx = 4; int ny = 4; int order = 3; int niter = 4; int surface = -1; int ref_levels = 2; bool pa = true; bool vis = true; bool amr = false; bool byc = false; bool wait = false; const char *keys = "gAmaaa"; const char *device_config = "cpu"; const char *mesh_file = "../data/mobius-strip.mesh"; // 1. Parse command-line options. OptionsParser args(argc, argv); args.AddOption(&mesh_file, "-m", "--mesh", "Mesh file to use."); args.AddOption(&surface, "-s", "--surface", "Choice of the surface."); args.AddOption(&wait, "-w", "--wait", "-no-w", "--no-wait", "Enable or disable a GLVis pause."); args.AddOption(&nx, "-nx", "--num-elements-x", "Number of elements in x-direction."); args.AddOption(&ny, "-ny", "--num-elements-y", "Number of elements in y-direction."); args.AddOption(&order, "-o", "--order", "Finite element order."); args.AddOption(&ref_levels, "-r", "--ref-levels", "Refinement"); args.AddOption(&niter, "-n", "--niter", "Number of iterations"); args.AddOption(&pa, "-pa", "--partial-assembly", "-no-pa", "--no-partial-assembly", "Enable Partial Assembly."); args.AddOption(&amr, "-amr", "--adaptive-mesh-refinement", "-no-amr", "--no-adaptive-mesh-refinement", "Enable AMR."); args.AddOption(&device_config, "-d", "--device", "Device configuration string, see Device::Configure()."); args.AddOption(&keys, "-k", "--keys", "GLVis configuration keys."); args.AddOption(&vis, "-vis", "--visualization", "-no-vis", "--no-visualization", "Enable or disable visualization."); args.AddOption(&byc, "-c", "--components", "-no-c", "--no-components", "Enable or disable the 'by component' solver"); args.Parse(); if (!args.Good()) { args.PrintUsage(cout); return 1; } args.PrintOptions(cout); MFEM_VERIFY(!amr, "WIP"); // 2. Enable hardware devices such as GPUs, and programming models such as // CUDA, OCCA, RAJA and OpenMP based on command line options. Device device(device_config); device.Print(); // Initialize GLVis server if 'visualization' is set. if (vis) { glvis.open(vishost, visport); } // Initialize our surface mesh from command line option. Mesh *mesh = nullptr; if (surface < 0) { mesh = new Mesh(mesh_file, true); } else if (surface == 0) { mesh = new Catenoid(order, nx, ny); } else if (surface == 1) { mesh = new Helicoid(order, nx, ny); } else if (surface == 2) { mesh = new Enneper(order, nx, ny); } else if (surface == 3) { mesh = new Scherk(order, nx, ny); } else if (surface == 4) { mesh = new Shell(order, nx, ny); } else if (surface == 5) { mesh = new Hold(order, nx, ny); } else if (surface == 6) { mesh = new QPeach(order, nx, ny); } else if (surface == 7) { mesh = new FPeach(order, ref_levels); } else { mfem_error("Not a valid surface, p should be in ]-infty, 7]"); } const bool discontinuous = false; const int mdim = mesh->Dimension(); const int sdim = mesh->SpaceDimension(); const int vdim = byc ? 1 : sdim; mesh->SetCurvature(order, discontinuous, sdim, Ordering::byNODES); // Refine the mesh to increase the resolution. for (int l = 0; l < ref_levels; l++) { mesh->UniformRefinement(); } // Adaptive mesh refinement if (amr) { for (int l = 0; l < 1; l++) { mesh->RandomRefinement(0.5); } } // Define a finite element space on the mesh. const H1_FECollection fec(order, mdim); FiniteElementSpace *fes = new FiniteElementSpace(mesh, &fec, vdim); cout << "Number of true DOFs: " << fes->GetTrueVSize() << endl; // Determine the list of true (i.e. conforming) essential boundary dofs. Array dbc; if (mesh->bdr_attributes.Size()) { Array ess_bdr(mesh->bdr_attributes.Max()); ess_bdr = 1; fes->GetEssentialTrueDofs(ess_bdr, dbc); } else { double X[3]; Array cdofs; Array ess_cdofs, ess_tdofs; const FiniteElementSpace *nfes = mesh->GetNodalFESpace(); ess_cdofs.SetSize(nfes->GetVSize()); ess_cdofs = 0; for (int e = 0; e < nfes->GetNE(); e++) { nfes->GetElementDofs(e, cdofs); for (int c = 0; c < cdofs.Size(); c++) { int k = cdofs[c]; if (k < 0) { k = -1 - k; } mesh->GetNode(k, X); const bool hX = fabs(X[0]) <= eps && X[1] <= 0; const bool hY = fabs(X[2]) <= eps && X[1] >= 0; const bool is_on_bc = hX || hY; for (int d = 0; d < vdim; d++) { ess_cdofs[nfes->DofToVDof(k, d)] = is_on_bc; } } } const SparseMatrix *R = nfes->GetConformingRestriction(); if (!R) { ess_tdofs.MakeRef(ess_cdofs); } else { R->BooleanMult(ess_cdofs, ess_tdofs); } FiniteElementSpace::MarkerToList(ess_tdofs, dbc); } // Send to GLVis the first mesh and set the 'keys' options. if (vis) { Visualize(mesh, order, wait, keys, 800, 800); } // Instanciate and launch the surface solver. if (byc) { ByComponent Solve(pa, vis, niter, wait, order, mesh, fes, dbc); } else { ByVector Solve(pa, vis, niter, wait, order, mesh, fes, dbc); } // Free the used memory. delete fes; delete mesh; return 0; } void SnapNodes(Mesh *mesh) { const int sdim = mesh->SpaceDimension(); GridFunction &nodes = *mesh->GetNodes(); Vector node(sdim); for (int i = 0; i < nodes.FESpace()->GetNDofs(); i++) { for (int d = 0; d < sdim; d++) { node(d) = nodes(nodes.FESpace()->DofToVDof(i, d)); } node /= node.Norml2(); for (int d = 0; d < sdim; d++) { nodes(nodes.FESpace()->DofToVDof(i, d)) = node(d); } } }