// Copyright (c) 2010-2020, 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. // // ----------------------------------------------------- // Mesh Explorer Miniapp: Explore and manipulate meshes // ----------------------------------------------------- // // This miniapp is a handy tool to examine, visualize and manipulate a given // mesh. Some of its features are: // // - visualizing of mesh materials and individual mesh elements // - mesh scaling, randomization, and general transformation // - manipulation of the mesh curvature // - the ability to simulate parallel partitioning // - quantitative and visual reports of mesh quality // // Compile with: make mesh-explorer // // Sample runs: mesh-explorer // mesh-explorer -m ../../data/beam-tri.mesh // mesh-explorer -m ../../data/star-q2.mesh // mesh-explorer -m ../../data/disc-nurbs.mesh // mesh-explorer -m ../../data/escher-p3.mesh // mesh-explorer -m ../../data/mobius-strip.mesh #include "mfem.hpp" #include #include #include using namespace mfem; using namespace std; // This tranformation can be applied to a mesh with the 't' menu option. void transformation(const Vector &p, Vector &v) { // simple shear transformation double s = 0.1; if (p.Size() == 3) { v(0) = p(0) + s*p(1) + s*p(2); v(1) = p(1) + s*p(2) + s*p(0); v(2) = p(2); } else if (p.Size() == 2) { v(0) = p(0) + s*p(1); v(1) = p(1) + s*p(0); } else { v = p; } } // This function is used with the 'r' menu option, sub-option 'l' to refine a // mesh locally in a region, defined by return values <= region_eps. double region_eps = 1e-8; double region(const Vector &p) { const double x = p(0), y = p(1); // here we describe the region: (x <= 1/4) && (y >= 0) && (y <= 1) return std::max(std::max(x - 0.25, -y), y - 1.0); } Mesh *read_par_mesh(int np, const char *mesh_prefix) { Mesh *mesh; Array mesh_array; mesh_array.SetSize(np); for (int p = 0; p < np; p++) { ostringstream fname; fname << mesh_prefix << '.' << setfill('0') << setw(6) << p; ifgzstream meshin(fname.str().c_str()); if (!meshin) { cerr << "Can not open mesh file: " << fname.str().c_str() << '!' << endl; for (p--; p >= 0; p--) { delete mesh_array[p]; } return NULL; } mesh_array[p] = new Mesh(meshin, 1, 0); // set element and boundary attributes to be the processor number + 1 if (1) { for (int i = 0; i < mesh_array[p]->GetNE(); i++) { mesh_array[p]->GetElement(i)->SetAttribute(p+1); } for (int i = 0; i < mesh_array[p]->GetNBE(); i++) { mesh_array[p]->GetBdrElement(i)->SetAttribute(p+1); } } } mesh = new Mesh(mesh_array, np); for (int p = 0; p < np; p++) { delete mesh_array[np-1-p]; } mesh_array.DeleteAll(); return mesh; } // Given a 3D mesh, produce a 2D mesh consisting of its boundary elements. Mesh *skin_mesh(Mesh *mesh) { // Determine mapping from vertex to boundary vertex Array v2v(mesh->GetNV()); v2v = -1; for (int i = 0; i < mesh->GetNBE(); i++) { Element *el = mesh->GetBdrElement(i); int *v = el->GetVertices(); int nv = el->GetNVertices(); for (int j = 0; j < nv; j++) { v2v[v[j]] = 0; } } int nbvt = 0; for (int i = 0; i < v2v.Size(); i++) { if (v2v[i] == 0) { v2v[i] = nbvt++; } } // Create a new mesh for the boundary Mesh * bmesh = new Mesh(mesh->Dimension() - 1, nbvt, mesh->GetNBE(), 0, mesh->SpaceDimension()); // Copy vertices to the boundary mesh nbvt = 0; for (int i = 0; i < v2v.Size(); i++) { if (v2v[i] >= 0) { double *c = mesh->GetVertex(i); bmesh->AddVertex(c); nbvt++; } } // Copy elements to the boundary mesh int bv[4]; for (int i = 0; i < mesh->GetNBE(); i++) { Element *el = mesh->GetBdrElement(i); int *v = el->GetVertices(); int nv = el->GetNVertices(); for (int j = 0; j < nv; j++) { bv[j] = v2v[v[j]]; } switch (el->GetGeometryType()) { case Geometry::SEGMENT: bmesh->AddSegment(bv, el->GetAttribute()); break; case Geometry::TRIANGLE: bmesh->AddTriangle(bv, el->GetAttribute()); break; case Geometry::SQUARE: bmesh->AddQuad(bv, el->GetAttribute()); break; default: break; /// This should not happen } } bmesh->FinalizeTopology(); // Copy GridFunction describing nodes if present if (mesh->GetNodes()) { FiniteElementSpace *fes = mesh->GetNodes()->FESpace(); const FiniteElementCollection *fec = fes->FEColl(); if (dynamic_cast(fec)) { FiniteElementCollection *fec_copy = FiniteElementCollection::New(fec->Name()); FiniteElementSpace *fes_copy = new FiniteElementSpace(*fes, bmesh, fec_copy); GridFunction *bdr_nodes = new GridFunction(fes_copy); bdr_nodes->MakeOwner(fec_copy); bmesh->NewNodes(*bdr_nodes, true); Array vdofs; Array bvdofs; Vector v; for (int i=0; iGetNBE(); i++) { fes->GetBdrElementVDofs(i, vdofs); mesh->GetNodes()->GetSubVector(vdofs, v); fes_copy->GetElementVDofs(i, bvdofs); bdr_nodes->SetSubVector(bvdofs, v); } } else { cout << "\nDiscontinuous nodes not yet supported" << endl; } } return bmesh; } int main (int argc, char *argv[]) { int np = 0; const char *mesh_file = "../../data/beam-hex.mesh"; bool refine = true; OptionsParser args(argc, argv); args.AddOption(&mesh_file, "-m", "--mesh", "Mesh file to visualize."); args.AddOption(&np, "-np", "--num-proc", "Load mesh from multiple processors."); args.AddOption(&refine, "-ref", "--refinement", "-no-ref", "--no-refinement", "Prepare the mesh for refinement or not."); args.Parse(); if (!args.Good()) { if (!args.Help()) { args.PrintError(cout); cout << endl; } cout << "Visualize and manipulate a serial mesh:\n" << " mesh-explorer -m \n" << "Visualize and manipulate a parallel mesh:\n" << " mesh-explorer -np <#proc> -m \n" << endl << "All Options:\n"; args.PrintHelp(cout); return 1; } args.PrintOptions(cout); Mesh *mesh; Mesh *bdr_mesh = NULL; if (np <= 0) { mesh = new Mesh(mesh_file, 1, refine); } else { mesh = read_par_mesh(np, mesh_file); if (mesh == NULL) { return 3; } } int dim = mesh->Dimension(); int sdim = mesh->SpaceDimension(); FiniteElementCollection *bdr_attr_fec = NULL; FiniteElementCollection *attr_fec; if (dim == 2) { attr_fec = new Const2DFECollection; } else { bdr_attr_fec = new Const2DFECollection; attr_fec = new Const3DFECollection; } int print_char = 1; while (1) { if (print_char) { cout << endl; mesh->PrintCharacteristics(); cout << "boundary attribs :"; for (int i = 0; i < mesh->bdr_attributes.Size(); i++) { cout << ' ' << mesh->bdr_attributes[i]; } cout << '\n' << "material attribs :"; for (int i = 0; i < mesh->attributes.Size(); i++) { cout << ' ' << mesh->attributes[i]; } cout << endl; cout << "mesh curvature : "; if (mesh->GetNodalFESpace() != NULL) { cout << mesh->GetNodalFESpace()->FEColl()->Name() << endl; } else { cout << "NONE" << endl; } } print_char = 0; cout << endl; cout << "What would you like to do?\n" "r) Refine\n" "c) Change curvature\n" "s) Scale\n" "t) Transform\n" "j) Jitter\n" "v) View\n" "m) View materials\n" "b) View boundary\n" "e) View elements\n" "h) View element sizes, h\n" "k) View element ratios, kappa\n" "x) Print sub-element stats\n" "f) Find physical point in reference space\n" "p) Generate a partitioning\n" "o) Reorder elements\n" "S) Save in MFEM format\n" "V) Save in VTK format (only linear and quadratic meshes)\n" "q) Quit\n" #ifdef MFEM_USE_ZLIB "Z) Save in MFEM format with compression\n" #endif "--> " << flush; char mk; cin >> mk; if (!cin) { break; } if (mk == 'q') { break; } if (mk == 'r') { cout << "Choose type of refinement:\n" "s) standard refinement with Mesh::UniformRefinement()\n" "b) Mesh::UniformRefinement() (bisection for tet meshes)\n" "u) uniform refinement with a factor\n" "g) non-uniform refinement (Gauss-Lobatto) with a factor\n" "l) refine locally using the region() function\n" "--> " << flush; char sk; cin >> sk; switch (sk) { case 's': mesh->UniformRefinement(); // Make sure tet-only meshes are marked for local refinement. mesh->Finalize(true); break; case 'b': mesh->UniformRefinement(1); // ref_algo = 1 break; case 'u': case 'g': { cout << "enter refinement factor --> " << flush; int ref_factor; cin >> ref_factor; if (ref_factor <= 1 || ref_factor > 32) { break; } int ref_type = (sk == 'u') ? BasisType::ClosedUniform : BasisType::GaussLobatto; Mesh *rmesh = new Mesh(mesh, ref_factor, ref_type); delete mesh; mesh = rmesh; break; } case 'l': { Vector pt; Array marked_elements; for (int i = 0; i < mesh->GetNE(); i++) { // check all nodes of the element IsoparametricTransformation T; mesh->GetElementTransformation(i, &T); for (int j = 0; j < T.GetPointMat().Width(); j++) { T.GetPointMat().GetColumnReference(j, pt); if (region(pt) <= region_eps) { marked_elements.Append(i); break; } } } mesh->GeneralRefinement(marked_elements); break; } } print_char = 1; } if (mk == 'c') { int p; cout << "enter new order for mesh curvature --> " << flush; cin >> p; mesh->SetCurvature(p > 0 ? p : -p, p <= 0); print_char = 1; } if (mk == 's') { double factor; cout << "scaling factor ---> " << flush; cin >> factor; GridFunction *nodes = mesh->GetNodes(); if (nodes == NULL) { for (int i = 0; i < mesh->GetNV(); i++) { double *v = mesh->GetVertex(i); v[0] *= factor; v[1] *= factor; if (dim == 3) { v[2] *= factor; } } } else { *nodes *= factor; } print_char = 1; } if (mk == 't') { mesh->Transform(transformation); print_char = 1; } if (mk == 'j') { double jitter; cout << "jitter factor ---> " << flush; cin >> jitter; GridFunction *nodes = mesh->GetNodes(); if (nodes == NULL) { cerr << "The mesh should have nodes, introduce curvature first!\n"; } else { FiniteElementSpace *fespace = nodes->FESpace(); GridFunction rdm(fespace); rdm.Randomize(); rdm -= 0.5; // shift to random values in [-0.5,0.5] rdm *= jitter; // compute minimal local mesh size Vector h0(fespace->GetNDofs()); h0 = infinity(); { Array dofs; for (int i = 0; i < fespace->GetNE(); i++) { fespace->GetElementDofs(i, dofs); for (int j = 0; j < dofs.Size(); j++) { h0(dofs[j]) = std::min(h0(dofs[j]), mesh->GetElementSize(i)); } } } // scale the random values to be of order of the local mesh size for (int i = 0; i < fespace->GetNDofs(); i++) { for (int d = 0; d < dim; d++) { rdm(fespace->DofToVDof(i,d)) *= h0(i); } } char move_bdr = 'n'; cout << "move boundary nodes? [y/n] ---> " << flush; cin >> move_bdr; // don't perturb the boundary if (move_bdr == 'n') { Array vdofs; for (int i = 0; i < fespace->GetNBE(); i++) { fespace->GetBdrElementVDofs(i, vdofs); for (int j = 0; j < vdofs.Size(); j++) { rdm(vdofs[j]) = 0.0; } } } *nodes += rdm; } print_char = 1; } if (mk == 'x') { int sd, nz = 0; DenseMatrix J(dim); double min_det_J, max_det_J, min_det_J_z, max_det_J_z; double min_kappa, max_kappa, max_ratio_det_J_z; min_det_J = min_kappa = infinity(); max_det_J = max_kappa = max_ratio_det_J_z = -infinity(); cout << "subdivision factor ---> " << flush; cin >> sd; Array bad_elems_by_geom(Geometry::NumGeom); bad_elems_by_geom = 0; for (int i = 0; i < mesh->GetNE(); i++) { Geometry::Type geom = mesh->GetElementBaseGeometry(i); ElementTransformation *T = mesh->GetElementTransformation(i); RefinedGeometry *RefG = GlobGeometryRefiner.Refine(geom, sd, 1); IntegrationRule &ir = RefG->RefPts; min_det_J_z = infinity(); max_det_J_z = -infinity(); for (int j = 0; j < ir.GetNPoints(); j++) { T->SetIntPoint(&ir.IntPoint(j)); Geometries.JacToPerfJac(geom, T->Jacobian(), J); double det_J = J.Det(); double kappa = J.CalcSingularvalue(0) / J.CalcSingularvalue(dim-1); min_det_J_z = fmin(min_det_J_z, det_J); max_det_J_z = fmax(max_det_J_z, det_J); min_kappa = fmin(min_kappa, kappa); max_kappa = fmax(max_kappa, kappa); } max_ratio_det_J_z = fmax(max_ratio_det_J_z, max_det_J_z/min_det_J_z); min_det_J = fmin(min_det_J, min_det_J_z); max_det_J = fmax(max_det_J, max_det_J_z); if (min_det_J_z <= 0.0) { nz++; bad_elems_by_geom[geom]++; } } cout << "\nbad elements = " << nz; if (nz) { cout << " -- "; Mesh::PrintElementsByGeometry(dim, bad_elems_by_geom, cout); } cout << "\nmin det(J) = " << min_det_J << "\nmax det(J) = " << max_det_J << "\nglobal ratio = " << max_det_J/min_det_J << "\nmax el ratio = " << max_ratio_det_J_z << "\nmin kappa = " << min_kappa << "\nmax kappa = " << max_kappa << endl; } if (mk == 'f') { DenseMatrix point_mat(sdim,1); cout << "\npoint in physical space ---> " << flush; for (int i = 0; i < sdim; i++) { cin >> point_mat(i,0); } Array elem_ids; Array ips; // physical -> reference space mesh->FindPoints(point_mat, elem_ids, ips); cout << "point in reference space:"; if (elem_ids[0] == -1) { cout << " NOT FOUND!\n"; } else { cout << " element " << elem_ids[0] << ", ip ="; cout << " " << ips[0].x; if (sdim > 1) { cout << " " << ips[0].y; if (sdim > 2) { cout << " " << ips[0].z; } } cout << endl; } } if (mk == 'o') { cout << "What type of reordering?\n" "g) Gecko edge-product minimization\n" "h) Hilbert spatial sort\n" "--> " << flush; char rk; cin >> rk; Array ordering, tentative; if (rk == 'h') { mesh->GetHilbertElementOrdering(ordering); mesh->ReorderElements(ordering); } else if (rk == 'g') { int outer, inner, window, period; cout << "Enter number of outer iterations (default 5): " << flush; cin >> outer; cout << "Enter number of inner iterations (default 4): " << flush; cin >> inner; cout << "Enter window size (default 4, beware of exponential cost): " << flush; cin >> window; cout << "Enter period for window size increment (default 2): " << flush; cin >> period; double best_cost = infinity(); for (int i = 0; i < outer; i++) { int seed = i+1; double cost = mesh->GetGeckoElementOrdering( tentative, inner, window, period, seed, true); if (cost < best_cost) { ordering = tentative; best_cost = cost; } } cout << "Final cost: " << best_cost << endl; mesh->ReorderElements(ordering); } } // These are the cases that open a new GLVis window if (mk == 'm' || mk == 'b' || mk == 'e' || mk == 'v' || mk == 'h' || mk == 'k' || mk == 'p') { Array bdr_part; Array part(mesh->GetNE()); FiniteElementSpace *bdr_attr_fespace = NULL; FiniteElementSpace *attr_fespace = new FiniteElementSpace(mesh, attr_fec); GridFunction bdr_attr; GridFunction attr(attr_fespace); if (mk == 'm') { for (int i = 0; i < mesh->GetNE(); i++) { part[i] = (attr(i) = mesh->GetAttribute(i)) - 1; } } if (mk == 'b') { if (dim == 3) { delete bdr_mesh; bdr_mesh = skin_mesh(mesh); bdr_attr_fespace = new FiniteElementSpace(bdr_mesh, bdr_attr_fec); bdr_part.SetSize(bdr_mesh->GetNE()); bdr_attr.SetSpace(bdr_attr_fespace); for (int i = 0; i < bdr_mesh->GetNE(); i++) { bdr_part[i] = (bdr_attr(i) = bdr_mesh->GetAttribute(i)) - 1; } } else { attr = 1.0; } } if (mk == 'v') { attr = 1.0; } if (mk == 'e') { Array coloring; srand(time(0)); double a = double(rand()) / (double(RAND_MAX) + 1.); int el0 = (int)floor(a * mesh->GetNE()); cout << "Generating coloring starting with element " << el0+1 << " / " << mesh->GetNE() << endl; mesh->GetElementColoring(coloring, el0); for (int i = 0; i < coloring.Size(); i++) { attr(i) = coloring[i]; } cout << "Number of colors: " << attr.Max() + 1 << endl; for (int i = 0; i < mesh->GetNE(); i++) { // part[i] = i; // checkerboard element coloring attr(i) = part[i] = i; // coloring by element number } } if (mk == 'h') { DenseMatrix J(dim); double h_min, h_max; h_min = infinity(); h_max = -h_min; for (int i = 0; i < mesh->GetNE(); i++) { int geom = mesh->GetElementBaseGeometry(i); ElementTransformation *T = mesh->GetElementTransformation(i); T->SetIntPoint(&Geometries.GetCenter(geom)); Geometries.JacToPerfJac(geom, T->Jacobian(), J); attr(i) = J.Det(); if (attr(i) < 0.0) { attr(i) = -pow(-attr(i), 1.0/double(dim)); } else { attr(i) = pow(attr(i), 1.0/double(dim)); } h_min = min(h_min, attr(i)); h_max = max(h_max, attr(i)); } cout << "h_min = " << h_min << ", h_max = " << h_max << endl; } if (mk == 'k') { DenseMatrix J(dim); for (int i = 0; i < mesh->GetNE(); i++) { int geom = mesh->GetElementBaseGeometry(i); ElementTransformation *T = mesh->GetElementTransformation(i); T->SetIntPoint(&Geometries.GetCenter(geom)); Geometries.JacToPerfJac(geom, T->Jacobian(), J); attr(i) = J.CalcSingularvalue(0) / J.CalcSingularvalue(dim-1); } } if (mk == 'p') { int *partitioning = NULL, np; cout << "What type of partitioning?\n" "c) Cartesian\n" "s) Simple 1D split of the element sequence\n" "0) METIS_PartGraphRecursive (sorted neighbor lists)\n" "1) METIS_PartGraphKway (sorted neighbor lists)" " (default)\n" "2) METIS_PartGraphVKway (sorted neighbor lists)\n" "3) METIS_PartGraphRecursive\n" "4) METIS_PartGraphKway\n" "5) METIS_PartGraphVKway\n" "--> " << flush; char pk; cin >> pk; if (pk == 'c') { int nxyz[3]; cout << "Enter nx: " << flush; cin >> nxyz[0]; np = nxyz[0]; if (mesh->Dimension() > 1) { cout << "Enter ny: " << flush; cin >> nxyz[1]; np *= nxyz[1]; if (mesh->Dimension() > 2) { cout << "Enter nz: " << flush; cin >> nxyz[2]; np *= nxyz[2]; } } partitioning = mesh->CartesianPartitioning(nxyz); } else if (pk == 's') { cout << "Enter number of processors: " << flush; cin >> np; partitioning = new int[mesh->GetNE()]; for (int i = 0; i < mesh->GetNE(); i++) { partitioning[i] = i * np / mesh->GetNE(); } } else { int part_method = pk - '0'; if (part_method < 0 || part_method > 5) { continue; } cout << "Enter number of processors: " << flush; cin >> np; partitioning = mesh->GeneratePartitioning(np, part_method); } if (partitioning) { const char part_file[] = "partitioning.txt"; ofstream opart(part_file); opart << "number_of_elements " << mesh->GetNE() << '\n' << "number_of_processors " << np << '\n'; for (int i = 0; i < mesh->GetNE(); i++) { opart << partitioning[i] << '\n'; } cout << "Partitioning file: " << part_file << endl; Array proc_el(np); proc_el = 0; for (int i = 0; i < mesh->GetNE(); i++) { proc_el[partitioning[i]]++; } int min_el = proc_el[0], max_el = proc_el[0]; for (int i = 1; i < np; i++) { if (min_el > proc_el[i]) { min_el = proc_el[i]; } if (max_el < proc_el[i]) { max_el = proc_el[i]; } } cout << "Partitioning stats:\n" << " " << setw(12) << "minimum" << setw(12) << "average" << setw(12) << "maximum" << setw(12) << "total" << '\n'; cout << " elements " << setw(12) << min_el << setw(12) << double(mesh->GetNE())/np << setw(12) << max_el << setw(12) << mesh->GetNE() << endl; } else { continue; } for (int i = 0; i < mesh->GetNE(); i++) { attr(i) = part[i] = partitioning[i]; } delete [] partitioning; } char vishost[] = "localhost"; int visport = 19916; socketstream sol_sock(vishost, visport); if (sol_sock.is_open()) { sol_sock.precision(14); if (sdim == 2) { sol_sock << "fem2d_gf_data_keys\n"; if (mk != 'p') { mesh->Print(sol_sock); } else { // NURBS meshes do not support PrintWithPartitioning if (mesh->NURBSext) { mesh->Print(sol_sock); for (int i = 0; i < mesh->GetNE(); i++) { attr(i) = part[i]; } } else { mesh->PrintWithPartitioning(part, sol_sock, 1); } } attr.Save(sol_sock); sol_sock << "RjlmAb***********"; if (mk == 'v') { sol_sock << "e"; } else { sol_sock << "\n"; } } else { sol_sock << "fem3d_gf_data_keys\n"; if (mk == 'v' || mk == 'h' || mk == 'k') { mesh->Print(sol_sock); } else if (mk == 'b') { bdr_mesh->Print(sol_sock); bdr_attr.Save(sol_sock); sol_sock << "mcaaA"; // Switch to a discrete color scale sol_sock << "pppppp" << "pppppp" << "pppppp"; } else { // NURBS meshes do not support PrintWithPartitioning if (mesh->NURBSext) { mesh->Print(sol_sock); for (int i = 0; i < mesh->GetNE(); i++) { attr(i) = part[i]; } } else { mesh->PrintWithPartitioning(part, sol_sock); } } if (mk != 'b') { attr.Save(sol_sock); sol_sock << "maaA"; if (mk == 'v') { sol_sock << "aa"; } else { sol_sock << "\n"; } } } sol_sock << flush; } else { cout << "Unable to connect to " << vishost << ':' << visport << endl; } delete attr_fespace; delete bdr_attr_fespace; } if (mk == 'S') { const char mesh_file[] = "mesh-explorer.mesh"; ofstream omesh(mesh_file); omesh.precision(14); mesh->Print(omesh); cout << "New mesh file: " << mesh_file << endl; } if (mk == 'V') { const char mesh_file[] = "mesh-explorer.vtk"; ofstream omesh(mesh_file); omesh.precision(14); mesh->PrintVTK(omesh); cout << "New VTK mesh file: " << mesh_file << endl; } #ifdef MFEM_USE_ZLIB if (mk == 'Z') { const char mesh_file[] = "mesh-explorer.mesh.gz"; ofgzstream omesh(mesh_file, "zwb9"); omesh.precision(14); mesh->Print(omesh); cout << "New mesh file: " << mesh_file << endl; } #endif } delete bdr_attr_fec; delete attr_fec; delete bdr_mesh; delete mesh; return 0; }