LCOV - code coverage report
Current view: top level - utils - geodata.cpp (source / functions) Coverage Total Hit
Test: Palace Coverage Report Lines: 43.9 % 937 411
Test Date: 2025-10-23 22:45:05 Functions: 63.0 % 46 29
Legend: Lines: hit not hit

            Line data    Source code
       1              : // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
       2              : // SPDX-License-Identifier: Apache-2.0
       3              : 
       4              : #include "geodata.hpp"
       5              : #include "geodata_impl.hpp"
       6              : 
       7              : #include <algorithm>
       8              : #include <array>
       9              : #include <limits>
      10              : #include <numeric>
      11              : #include <queue>
      12              : #include <sstream>
      13              : #include <string>
      14              : #include <unordered_map>
      15              : #include <unordered_set>
      16              : #include <utility>
      17              : #include <Eigen/Dense>
      18              : #include <fmt/ranges.h>
      19              : #include "fem/interpolator.hpp"
      20              : #include "utils/communication.hpp"
      21              : #include "utils/diagnostic.hpp"
      22              : #include "utils/filesystem.hpp"
      23              : #include "utils/meshio.hpp"
      24              : #include "utils/omp.hpp"
      25              : #include "utils/prettyprint.hpp"
      26              : #include "utils/timer.hpp"
      27              : 
      28              : namespace palace
      29              : {
      30              : 
      31              : using Vector3dMap = Eigen::Map<Eigen::Vector3d>;
      32              : using CVector3dMap = Eigen::Map<const Eigen::Vector3d>;
      33              : 
      34              : namespace
      35              : {
      36              : 
      37              : // Floating point precision for mesh IO. This precision is important, make sure nothing is
      38              : // lost!
      39              : constexpr auto MSH_FLT_PRECISION = std::numeric_limits<double>::max_digits10;
      40              : 
      41              : // Load the serial mesh from disk.
      42              : std::unique_ptr<mfem::Mesh> LoadMesh(const std::string &, bool,
      43              :                                      const config::BoundaryData &);
      44              : 
      45              : // Clean the provided serial mesh by removing unused domain and boundary elements.
      46              : void CleanMesh(std::unique_ptr<mfem::Mesh> &, const std::vector<int> &);
      47              : 
      48              : // Create a new mesh by splitting all elements of the mesh into simplices or hexes
      49              : // (using tet-to-hex). Optionally preserves curvature of the original mesh by interpolating
      50              : // the high-order nodes with GSLIB.
      51              : void SplitMeshElements(std::unique_ptr<mfem::Mesh> &, bool, bool);
      52              : 
      53              : // Optionally reorder mesh elements based on MFEM's internal reordering tools for improved
      54              : // cache usage.
      55              : void ReorderMeshElements(mfem::Mesh &, bool = true);
      56              : 
      57              : // Check that mesh boundary conditions are given for external boundaries.
      58              : std::unordered_map<int, int> CheckMesh(const mfem::Mesh &, const config::BoundaryData &);
      59              : 
      60              : // Adding boundary elements for material interfaces and exterior boundaries, and "crack"
      61              : // desired internal boundary elements to disconnect the elements on either side.
      62              : int AddInterfaceBdrElements(const IoData &, std::unique_ptr<mfem::Mesh> &,
      63              :                             std::unordered_map<int, int> &, MPI_Comm comm);
      64              : 
      65              : // Generate element-based mesh partitioning, using either a provided file or METIS.
      66              : std::unique_ptr<int[]> GetMeshPartitioning(const mfem::Mesh &, int,
      67              :                                            const std::string & = "", bool = true);
      68              : 
      69              : // Given a serial mesh on the root processor and element partitioning, create a parallel
      70              : // mesh over the given communicator. The serial mesh is destroyed when no longer needed.
      71              : std::unique_ptr<mfem::ParMesh> DistributeMesh(MPI_Comm, std::unique_ptr<mfem::Mesh> &,
      72              :                                               const int *, const std::string & = "");
      73              : 
      74              : // Rebalance a conformal mesh across processor ranks, using the MeshPartitioner. Gathers the
      75              : // mesh onto the root rank before scattering the partitioned mesh.
      76              : void RebalanceConformalMesh(std::unique_ptr<mfem::ParMesh> &);
      77              : 
      78              : }  // namespace
      79              : 
      80              : namespace mesh
      81              : {
      82              : 
      83            8 : std::unique_ptr<mfem::ParMesh> ReadMesh(const IoData &iodata, MPI_Comm comm)
      84              : {
      85              :   // If possible on root, read the serial mesh (converting format if necessary), and do all
      86              :   // necessary serial preprocessing. When finished, distribute the mesh to all processes.
      87              :   // Count disk I/O time separately for the mesh read from file.
      88            8 :   BlockTimer bt0(Timer::MESH_PREPROCESS);
      89              : 
      90              :   // If not doing any local adaptation, or performing conformal adaptation, we can use the
      91              :   // mesh partitioner.
      92            8 :   std::unique_ptr<mfem::Mesh> smesh;
      93            8 :   const auto &refinement = iodata.model.refinement;
      94            8 :   const bool use_amr = (refinement.max_it > 0) || [&refinement]()
      95              :   {
      96            8 :     for (const auto &box : refinement.GetBoxes())
      97              :     {
      98            0 :       if (box.ref_levels > 0)
      99              :       {
     100              :         return true;
     101              :       }
     102              :     }
     103            8 :     for (const auto &sphere : refinement.GetSpheres())
     104              :     {
     105            0 :       if (sphere.ref_levels > 0)
     106              :       {
     107              :         return true;
     108              :       }
     109              :     }
     110              :     return false;
     111            8 :   }();
     112              : 
     113            0 :   const bool use_mesh_partitioner = [&]()
     114              :   {
     115              :     // Root must load the mesh to discover if nonconformal, as a previously adapted mesh
     116              :     // might be reused for nonadaptive simulations.
     117            8 :     BlockTimer bt(Timer::IO);
     118            8 :     bool use_mesh_partitioner = !use_amr || !refinement.nonconformal;
     119            8 :     if (Mpi::Root(comm))
     120              :     {
     121            8 :       smesh = LoadMesh(iodata.model.mesh, iodata.model.remove_curvature, iodata.boundaries);
     122            8 :       use_mesh_partitioner &= smesh->Conforming();  // The initial mesh must be conformal
     123              :     }
     124            8 :     Mpi::Broadcast(1, &use_mesh_partitioner, 0, comm);
     125            8 :     return use_mesh_partitioner;
     126           16 :   }();
     127              : 
     128              :   MPI_Comm node_comm;
     129            8 :   if (!use_mesh_partitioner)
     130              :   {
     131            0 :     MPI_Comm_split_type(comm, MPI_COMM_TYPE_SHARED, Mpi::Rank(comm), MPI_INFO_NULL,
     132              :                         &node_comm);
     133              :   }
     134              : 
     135              :   {
     136            8 :     BlockTimer bt1(Timer::IO);
     137            8 :     if (!use_mesh_partitioner && Mpi::Root(node_comm) && !Mpi::Root(comm))
     138              :     {
     139              :       // Only one process per node reads the serial mesh, if not using mesh partitioner.
     140            0 :       smesh = LoadMesh(iodata.model.mesh, iodata.model.remove_curvature, iodata.boundaries);
     141              :       MFEM_VERIFY(!(smesh->Nonconforming() && use_mesh_partitioner),
     142              :                   "Cannot use mesh partitioner on a nonconforming mesh!");
     143              :     }
     144            8 :     Mpi::Barrier(comm);
     145            8 :   }
     146              : 
     147              :   // Do some mesh preprocessing, and generate the partitioning.
     148              :   std::unique_ptr<int[]> partitioning;
     149            8 :   if (smesh)
     150              :   {
     151              :     // Check the the AMR specification and the mesh elements are compatible.
     152            8 :     const auto element_types = CheckElements(*smesh);
     153            8 :     MFEM_VERIFY(!use_amr || iodata.model.make_simplex || !element_types.has_hexahedra ||
     154              :                     refinement.nonconformal,
     155              :                 "If there are tensor elements, AMR must be nonconformal!");
     156            8 :     MFEM_VERIFY(!use_amr || iodata.model.make_simplex || !element_types.has_prisms ||
     157              :                     refinement.nonconformal,
     158              :                 "If there are wedge elements, AMR must be nonconformal!");
     159            8 :     MFEM_VERIFY(!use_amr || iodata.model.make_simplex || !element_types.has_pyramids ||
     160              :                     refinement.nonconformal,
     161              :                 "If there are pyramid elements, AMR must be nonconformal!");
     162            8 :     MFEM_VERIFY(
     163              :         smesh->Conforming() || !use_amr || refinement.nonconformal,
     164              :         "The provided mesh is nonconformal, only nonconformal AMR can be performed!");
     165              : 
     166              :     // Clean up unused domain elements from the mesh.
     167            8 :     if (iodata.model.clean_unused_elements)
     168              :     {
     169              :       std::vector<int> attr_list;
     170            8 :       std::merge(iodata.domains.attributes.begin(), iodata.domains.attributes.end(),
     171              :                  iodata.domains.postpro.attributes.begin(),
     172              :                  iodata.domains.postpro.attributes.end(), std::back_inserter(attr_list));
     173            8 :       attr_list.erase(std::unique(attr_list.begin(), attr_list.end()), attr_list.end());
     174            8 :       CleanMesh(smesh, attr_list);
     175              :     }
     176              : 
     177              :     // Optionally convert mesh elements to simplices, for example in order to enable
     178              :     // conformal mesh refinement, or hexes.
     179            8 :     if (iodata.model.make_simplex || iodata.model.make_hex)
     180              :     {
     181            0 :       SplitMeshElements(smesh, iodata.model.make_simplex, iodata.model.make_hex);
     182              :     }
     183              : 
     184              :     // Optionally reorder elements (and vertices) based on spatial location after loading
     185              :     // the serial mesh.
     186            8 :     if (iodata.model.reorder_elements)
     187              :     {
     188            0 :       ReorderMeshElements(*smesh);
     189              :     }
     190              : 
     191              :     // Refine the serial mesh (not typically used, prefer parallel uniform refinement
     192              :     // instead).
     193              :     {
     194            8 :       int ne = smesh->GetNE();
     195            8 :       for (int l = 0; l < iodata.model.refinement.ser_uniform_ref_levels; l++)
     196              :       {
     197            0 :         smesh->UniformRefinement();
     198              :       }
     199            8 :       if (iodata.model.refinement.ser_uniform_ref_levels > 0)
     200              :       {
     201            0 :         Mpi::Print("Serial uniform mesh refinement levels added {:d} elements (initial = "
     202              :                    "{:d}, final = {:d})\n",
     203            0 :                    smesh->GetNE() - ne, ne, smesh->GetNE());
     204              :       }
     205              :     }
     206              : 
     207              :     // Check the final mesh, throwing warnings if there are exterior boundaries with no
     208              :     // associated boundary condition.
     209            8 :     if (smesh->Conforming())
     210              :     {
     211            8 :       auto face_to_be = CheckMesh(*smesh, iodata.boundaries);
     212              : 
     213              :       // Add new boundary elements for material interfaces if not present (with new unique
     214              :       // boundary attributes). Also duplicate internal boundary elements associated with
     215              :       // cracks if desired.
     216            8 :       if ((iodata.model.crack_bdr_elements || iodata.model.add_bdr_elements))
     217              :       {
     218              :         // Split all internal (non periodic) boundary elements for boundary attributes where
     219              :         // BC are applied (not just postprocessing).
     220            8 :         while (AddInterfaceBdrElements(iodata, smesh, face_to_be, comm) != 1)
     221              :         {
     222              :           // May require multiple calls due to early exit/retry approach.
     223              :         }
     224              :       }
     225              :     }
     226              :     else
     227              :     {
     228            0 :       Mpi::Warning("{} is a nonconformal mesh, assumed from previous AMR iteration.\n"
     229              :                    "Skipping mesh modification preprocessing steps!\n\n",
     230            0 :                    iodata.model.mesh);
     231              :     }
     232              : 
     233              :     // Finally, finalize the serial mesh. Mark tetrahedral meshes for refinement. There
     234              :     // should be no need to fix orientation as this was done during initial mesh loading
     235              :     // from disk.
     236              :     constexpr bool refine = true, fix_orientation = false;
     237            8 :     smesh->Finalize(refine, fix_orientation);
     238              : 
     239              :     // Generate the mesh partitioning.
     240            8 :     partitioning = GetMeshPartitioning(*smesh, Mpi::Size(comm), iodata.model.partitioning);
     241              :   }
     242              : 
     243              :   // Distribute the mesh.
     244            8 :   std::unique_ptr<mfem::ParMesh> pmesh;
     245            8 :   if (use_mesh_partitioner)
     246              :   {
     247           16 :     pmesh = DistributeMesh(comm, smesh, partitioning.get(), iodata.problem.output);
     248              :   }
     249              :   else
     250              :   {
     251              :     // Send the preprocessed serial mesh and partitioning as a byte string.
     252            0 :     constexpr bool generate_edges = false, refine = true, fix_orientation = false;
     253              :     std::string so;
     254            0 :     int slen = 0;
     255            0 :     if (smesh)
     256              :     {
     257            0 :       std::ostringstream fo(std::stringstream::out);
     258              :       // fo << std::fixed;
     259              :       fo << std::scientific;
     260              :       fo.precision(MSH_FLT_PRECISION);
     261            0 :       smesh->Print(fo);
     262              :       smesh.reset();  // Root process needs to rebuild the mesh to ensure consistency with
     263              :                       // the saved serial mesh (refinement marking, for example)
     264            0 :       so = fo.str();
     265              :       // so = zlib::CompressString(fo.str());
     266            0 :       slen = static_cast<int>(so.size());
     267            0 :       MFEM_VERIFY(so.size() == (std::size_t)slen, "Overflow in stringbuffer size!");
     268            0 :     }
     269            0 :     Mpi::Broadcast(1, &slen, 0, node_comm);
     270            0 :     if (so.empty())
     271              :     {
     272            0 :       so.resize(slen);
     273              :     }
     274            0 :     Mpi::Broadcast(slen, so.data(), 0, node_comm);
     275              :     {
     276            0 :       std::istringstream fi(so);
     277              :       // std::istringstream fi(zlib::DecompressString(so));
     278            0 :       smesh = std::make_unique<mfem::Mesh>(fi, generate_edges, refine, fix_orientation);
     279              :       so.clear();
     280            0 :     }
     281            0 :     if (refinement.nonconformal && use_amr)
     282              :     {
     283            0 :       smesh->EnsureNCMesh(true);
     284              :     }
     285            0 :     if (!partitioning)
     286              :     {
     287            0 :       partitioning = std::make_unique<int[]>(smesh->GetNE());
     288              :     }
     289            0 :     Mpi::Broadcast(smesh->GetNE(), partitioning.get(), 0, node_comm);
     290            0 :     MPI_Comm_free(&node_comm);
     291            0 :     pmesh = std::make_unique<mfem::ParMesh>(comm, *smesh, partitioning.get());
     292              :     smesh.reset();
     293              :   }
     294              : 
     295              :   if constexpr (false)
     296              :   {
     297              :     auto tmp = fs::path(iodata.problem.output) / "tmp";
     298              :     if (Mpi::Root(comm) && !fs::exists(tmp))
     299              :     {
     300              :       fs::create_directories(tmp);
     301              :     }
     302              :     int width = 1 + static_cast<int>(std::log10(Mpi::Size(comm) - 1));
     303              :     std::unique_ptr<mfem::Mesh> gsmesh =
     304              :         LoadMesh(iodata.model.mesh, iodata.model.remove_curvature, iodata.boundaries);
     305              :     std::unique_ptr<int[]> gpartitioning = GetMeshPartitioning(*gsmesh, Mpi::Size(comm));
     306              :     mfem::ParMesh gpmesh(comm, *gsmesh, gpartitioning.get(), 0);
     307              :     {
     308              :       std::string pfile =
     309              :           mfem::MakeParFilename(tmp.string() + "part.", Mpi::Rank(comm), ".mesh", width);
     310              :       std::ofstream fo(pfile);
     311              :       // mfem::ofgzstream fo(pfile, true);  // Use zlib compression if available
     312              :       fo.precision(MSH_FLT_PRECISION);
     313              :       gpmesh.ParPrint(fo);
     314              :     }
     315              :     {
     316              :       std::string pfile =
     317              :           mfem::MakeParFilename(tmp.string() + "final.", Mpi::Rank(comm), ".mesh", width);
     318              :       std::ofstream fo(pfile);
     319              :       // mfem::ofgzstream fo(pfile, true);  // Use zlib compression if available
     320              :       fo.precision(MSH_FLT_PRECISION);
     321              :       pmesh->ParPrint(fo);
     322              :     }
     323              :   }
     324              : 
     325            8 :   return pmesh;
     326            8 : }
     327              : 
     328            8 : void RefineMesh(const IoData &iodata, std::vector<std::unique_ptr<mfem::ParMesh>> &mesh)
     329              : {
     330              :   // Prepare for uniform and region-based refinement.
     331            8 :   MFEM_VERIFY(mesh.size() == 1,
     332              :               "Input mesh vector before refinement has more than a single mesh!");
     333            8 :   int uniform_ref_levels = iodata.model.refinement.uniform_ref_levels;
     334              :   int max_region_ref_levels = 0;
     335            8 :   for (const auto &box : iodata.model.refinement.GetBoxes())
     336              :   {
     337            0 :     if (max_region_ref_levels < box.ref_levels)
     338              :     {
     339              :       max_region_ref_levels = box.ref_levels;
     340              :     }
     341              :   }
     342            8 :   for (const auto &sphere : iodata.model.refinement.GetSpheres())
     343              :   {
     344            0 :     if (max_region_ref_levels < sphere.ref_levels)
     345              :     {
     346              :       max_region_ref_levels = sphere.ref_levels;
     347              :     }
     348              :   }
     349            8 :   if (iodata.solver.linear.mg_use_mesh && iodata.solver.linear.mg_max_levels > 1)
     350              :   {
     351            8 :     mesh.reserve(1 + uniform_ref_levels + max_region_ref_levels);
     352              :   }
     353              : 
     354              :   // Prior to MFEM's PR #1046, the tetrahedral mesh required reorientation after all mesh
     355              :   // refinement in order to define higher-order Nedelec spaces on it. This is technically
     356              :   // not required after MFEM's PR #1046, but in case you want to be absolutely sure, we
     357              :   // reorient only the coarse mesh so that the refinements are still true refinements of
     358              :   // the original mesh (required for geometric multigrid). Otherwise, it happens after
     359              :   // refinement.
     360            8 :   if (iodata.model.reorient_tet_mesh && mesh.capacity() > 1)
     361              :   {
     362              :     PalacePragmaDiagnosticPush
     363              :     PalacePragmaDiagnosticDisableDeprecated
     364            0 :     mesh[0]->ReorientTetMesh();
     365              :     PalacePragmaDiagnosticPop
     366              :   }
     367              : 
     368              :   // Uniformly refine the mesh further in parallel, saving the level meshes for geometric
     369              :   // coarsening later on if desired.
     370            8 :   for (int l = 0; l < uniform_ref_levels; l++)
     371              :   {
     372            0 :     if (mesh.capacity() > 1)
     373              :     {
     374            0 :       mesh.emplace_back(std::make_unique<mfem::ParMesh>(*mesh.back()));
     375              :     }
     376            0 :     mesh.back()->UniformRefinement();
     377              :   }
     378              : 
     379              :   // Simplex meshes need to be re-finalized in order to use local refinement (see
     380              :   // the docstring for mfem::Mesh::UniformRefinement).
     381            8 :   const auto element_types = mesh::CheckElements(*mesh.back());
     382            8 :   if (element_types.has_simplices && uniform_ref_levels > 0 &&
     383            0 :       (max_region_ref_levels > 0 || iodata.model.refinement.max_it > 0))
     384              :   {
     385              :     constexpr bool refine = true, fix_orientation = false;
     386            0 :     Mpi::Print("\nFlattening mesh sequence:\n Local mesh refinement will start from the "
     387              :                "final uniformly-refined mesh\n");
     388              :     mesh.erase(mesh.begin(), mesh.end() - 1);
     389            0 :     mesh.back()->Finalize(refine, fix_orientation);
     390              :   }
     391              : 
     392              :   // Proceed with region-based refinement, level-by-level for all regions. Currently support
     393              :   // box and sphere region shapes. Any overlap between regions is ignored (take the union,
     394              :   // don't double-refine).
     395            8 :   MFEM_VERIFY(
     396              :       max_region_ref_levels == 0 ||
     397              :           !(element_types.has_hexahedra || element_types.has_prisms ||
     398              :             element_types.has_pyramids) ||
     399              :           mesh.back()->Nonconforming(),
     400              :       "Region-based refinement for non-simplex meshes requires a nonconformal mesh!");
     401              :   const bool use_nodes = (mesh.back()->GetNodes() != nullptr);
     402            8 :   const int ref = use_nodes ? mesh.back()->GetNodes()->FESpace()->GetMaxElementOrder() : 1;
     403              :   const int dim = mesh.back()->SpaceDimension();
     404              :   int region_ref_level = 0;
     405            8 :   while (region_ref_level < max_region_ref_levels)
     406              :   {
     407              :     // Mark elements for refinement in all regions. An element is marked for refinement if
     408              :     // any of its vertices are inside any refinement region for the given level.
     409              :     mfem::Array<mfem::Refinement> refinements;
     410            0 :     for (int i = 0; i < mesh.back()->GetNE(); i++)
     411              :     {
     412              :       bool refine = false;
     413            0 :       mfem::DenseMatrix pointmat;
     414            0 :       if (use_nodes)
     415              :       {
     416            0 :         mfem::ElementTransformation &T = *mesh.back()->GetElementTransformation(i);
     417              :         mfem::RefinedGeometry *RefG =
     418            0 :             mfem::GlobGeometryRefiner.Refine(T.GetGeometryType(), ref);
     419            0 :         T.Transform(RefG->RefPts, pointmat);
     420              :       }
     421              :       else
     422              :       {
     423            0 :         const int *verts = mesh.back()->GetElement(i)->GetVertices();
     424            0 :         const int nv = mesh.back()->GetElement(i)->GetNVertices();
     425            0 :         pointmat.SetSize(dim, nv);
     426            0 :         for (int j = 0; j < nv; j++)
     427              :         {
     428            0 :           const double *coord = mesh.back()->GetVertex(verts[j]);
     429            0 :           for (int d = 0; d < dim; d++)
     430              :           {
     431            0 :             pointmat(d, j) = coord[d];
     432              :           }
     433              :         }
     434              :       }
     435            0 :       for (const auto &box : iodata.model.refinement.GetBoxes())
     436              :       {
     437            0 :         if (region_ref_level < box.ref_levels)
     438              :         {
     439            0 :           for (int j = 0; j < pointmat.Width(); j++)
     440              :           {
     441              :             // Check if the point is inside the box.
     442              :             int d = 0;
     443            0 :             for (; d < pointmat.Height(); d++)
     444              :             {
     445            0 :               if (pointmat(d, j) < box.bbmin[d] || pointmat(d, j) > box.bbmax[d])
     446              :               {
     447              :                 break;
     448              :               }
     449              :             }
     450            0 :             if (d == dim)
     451              :             {
     452              :               refine = true;
     453              :               break;
     454              :             }
     455              :           }
     456            0 :           if (refine)
     457              :           {
     458              :             break;
     459              :           }
     460              :         }
     461              :       }
     462            0 :       if (refine)
     463              :       {
     464            0 :         refinements.Append(mfem::Refinement(i));
     465              :         continue;
     466              :       }
     467            0 :       for (const auto &sphere : iodata.model.refinement.GetSpheres())
     468              :       {
     469            0 :         if (region_ref_level < sphere.ref_levels)
     470              :         {
     471            0 :           for (int j = 0; j < pointmat.Width(); j++)
     472              :           {
     473              :             // Check if the point is inside the sphere.
     474              :             double dist = 0.0;
     475            0 :             for (int d = 0; d < pointmat.Height(); d++)
     476              :             {
     477            0 :               double s = pointmat(d, j) - sphere.center[d];
     478            0 :               dist += s * s;
     479              :             }
     480            0 :             if (dist <= sphere.r * sphere.r)
     481              :             {
     482              :               refine = true;
     483              :               break;
     484              :             }
     485              :           }
     486            0 :           if (refine)
     487              :           {
     488              :             break;
     489              :           }
     490              :         }
     491              :       }
     492            0 :       if (refine)
     493              :       {
     494            0 :         refinements.Append(mfem::Refinement(i));
     495              :       }
     496            0 :     }
     497              : 
     498              :     // Do the refinement. For tensor element meshes, this may make the mesh nonconforming
     499              :     // (adds hanging nodes).
     500            0 :     if (mesh.capacity() > 1)
     501              :     {
     502            0 :       mesh.emplace_back(std::make_unique<mfem::ParMesh>(*mesh.back()));
     503              :     }
     504            0 :     mesh.back()->GeneralRefinement(refinements, -1);
     505            0 :     region_ref_level++;
     506              :   }
     507            8 :   if (max_region_ref_levels > 0 && mesh.capacity() == 1)
     508              :   {
     509            0 :     RebalanceMesh(iodata, mesh[0]);
     510              :   }
     511              : 
     512              :   // Prior to MFEM's PR #1046, the tetrahedral mesh required reorientation after all mesh
     513              :   // refinement in order to define higher-order Nedelec spaces on it. This is technically
     514              :   // not required after MFEM's PR #1046, but in case you want to be absolutely sure, we
     515              :   // reorient only the mesh after refinement if there is a single mesh (doesn't work with
     516              :   // h-refinement geometric multigrid).
     517            8 :   if (iodata.model.reorient_tet_mesh && mesh.capacity() == 1)
     518              :   {
     519              :     PalacePragmaDiagnosticPush
     520              :     PalacePragmaDiagnosticDisableDeprecated
     521            0 :     mesh[0]->ReorientTetMesh();
     522              :     PalacePragmaDiagnosticPop
     523              :   }
     524              : 
     525              :   // Print some mesh information.
     526              :   mfem::Vector bbmin, bbmax;
     527            8 :   GetAxisAlignedBoundingBox(*mesh[0], bbmin, bbmax);
     528              :   const double Lc = iodata.units.Dimensionalize<Units::ValueType::LENGTH>(1.0);
     529            8 :   Mpi::Print(mesh[0]->GetComm(), "\nMesh curvature order: {}\nMesh bounding box:\n",
     530              :              mesh[0]->GetNodes()
     531            8 :                  ? std::to_string(mesh[0]->GetNodes()->FESpace()->GetMaxElementOrder())
     532            8 :                  : "None");
     533            8 :   if (mesh[0]->SpaceDimension() == 3)
     534              :   {
     535            8 :     Mpi::Print(mesh[0]->GetComm(),
     536              :                " (Xmin, Ymin, Zmin) = ({:+.3e}, {:+.3e}, {:+.3e}) m\n"
     537              :                " (Xmax, Ymax, Zmax) = ({:+.3e}, {:+.3e}, {:+.3e}) m\n",
     538           16 :                bbmin[0] * Lc, bbmin[1] * Lc, bbmin[2] * Lc, bbmax[0] * Lc, bbmax[1] * Lc,
     539            8 :                bbmax[2] * Lc);
     540              :   }
     541              :   else
     542              :   {
     543            0 :     Mpi::Print(mesh[0]->GetComm(),
     544              :                " (Xmin, Ymin) = ({:+.3e}, {:+.3e}) m\n"
     545              :                " (Xmax, Ymax) = ({:+.3e}, {:+.3e}) m\n",
     546            0 :                bbmin[0] * Lc, bbmin[1] * Lc, bbmax[0] * Lc, bbmax[1] * Lc);
     547              :   }
     548           16 :   Mpi::Print(mesh[0]->GetComm(), "\n{}", (mesh.size() > 1) ? "Coarse " : "");
     549            8 :   mesh[0]->PrintInfo();
     550            8 :   if (mesh.size() > 1)
     551              :   {
     552            0 :     Mpi::Print(mesh[0]->GetComm(), "\nRefined ");
     553            0 :     mesh.back()->PrintInfo();
     554              :   }
     555            8 : }
     556              : 
     557            1 : mfem::Mesh MeshTetToHex(const mfem::Mesh &orig_mesh)
     558              : {
     559              :   // Courtesy of https://gist.github.com/pazner/e9376f77055c0918d7c43e034e9e5888, only
     560              :   // supports tetrahedral elements for now. Eventually should be expanded to support prism
     561              :   // and pyramid elements but this mixed mesh support requires a bit more work.
     562            1 :   MFEM_VERIFY(orig_mesh.Dimension() == 3, "Tet-to-hex conversion only supports 3D meshes!");
     563              :   {
     564              :     // This checks the local mesh on each process, but the assertion failing on any single
     565              :     // process will terminate the program.
     566              :     mfem::Array<mfem::Geometry::Type> geoms;
     567            1 :     orig_mesh.GetGeometries(3, geoms);
     568            1 :     MFEM_VERIFY(geoms.Size() == 1 && geoms[0] == mfem::Geometry::TETRAHEDRON,
     569              :                 "Tet-to-hex conversion only works for pure tetrahedral meshes!");
     570              :   }
     571              : 
     572              :   // Add new vertices in every edge, face, and volume. Each tet is subdivided into 4 hexes,
     573              :   // and each triangular face subdivided into 3 quads.
     574              :   const int nv_tet = orig_mesh.GetNV();
     575              :   const int nedge_tet = orig_mesh.GetNEdges();
     576              :   const int nface_tet = orig_mesh.GetNFaces();
     577              :   const int ne_tet = orig_mesh.GetNE();
     578              :   const int nbe_tet = orig_mesh.GetNBE();
     579            1 :   const int nv = nv_tet + nedge_tet + nface_tet + ne_tet;
     580            1 :   const int ne = 4 * ne_tet;    // 4 hex per tet
     581            1 :   const int nbe = 3 * nbe_tet;  // 3 square per tri
     582            1 :   mfem::Mesh hex_mesh(orig_mesh.Dimension(), nv, ne, nbe, orig_mesh.SpaceDimension());
     583              : 
     584              :   // Add original vertices.
     585            5 :   for (int v = 0; v < nv_tet; v++)
     586              :   {
     587            4 :     hex_mesh.AddVertex(orig_mesh.GetVertex(v));
     588              :   }
     589              : 
     590              :   // Add midpoints of edges, faces, and elements.
     591           11 :   auto AddCentroid = [&orig_mesh, &hex_mesh](const int *verts, int nv)
     592              :   {
     593           11 :     double coord[3] = {0.0, 0.0, 0.0};
     594           39 :     for (int i = 0; i < nv; i++)
     595              :     {
     596          112 :       for (int d = 0; d < orig_mesh.SpaceDimension(); d++)
     597              :       {
     598           84 :         coord[d] += orig_mesh.GetVertex(verts[i])[d] / nv;
     599              :       }
     600              :     }
     601           11 :     hex_mesh.AddVertex(coord);
     602            1 :   };
     603              :   {
     604              :     mfem::Array<int> verts;
     605            7 :     for (int e = 0; e < nedge_tet; ++e)
     606              :     {
     607            6 :       orig_mesh.GetEdgeVertices(e, verts);
     608            6 :       AddCentroid(verts.GetData(), verts.Size());
     609              :     }
     610              :   }
     611            5 :   for (int f = 0; f < nface_tet; ++f)
     612              :   {
     613            8 :     AddCentroid(orig_mesh.GetFace(f)->GetVertices(), orig_mesh.GetFace(f)->GetNVertices());
     614              :   }
     615            2 :   for (int e = 0; e < ne_tet; ++e)
     616              :   {
     617            1 :     AddCentroid(orig_mesh.GetElement(e)->GetVertices(),
     618            1 :                 orig_mesh.GetElement(e)->GetNVertices());
     619              :   }
     620              : 
     621              :   // Connectivity of tetrahedron vertices to the edges. The vertices of the new mesh are
     622              :   // ordered so that the original tet vertices are first, then the vertices splitting each
     623              :   // edge, then the vertices at the center of each triangle face, then the center of the
     624              :   // tet. Thus the edge/face/element numbers can be used to index into the new array of
     625              :   // vertices, and the element local edge/face can be used to extract the global edge/face
     626              :   // index, and thus the corresponding vertex.
     627            1 :   constexpr int tet_vertex_edge_map[4 * 3] = {0, 1, 2, 3, 0, 4, 1, 3, 5, 5, 4, 2};
     628            1 :   constexpr int tet_vertex_face_map[4 * 3] = {3, 2, 1, 3, 0, 2, 3, 1, 0, 0, 1, 2};
     629            1 :   constexpr int tri_vertex_edge_map[3 * 2] = {0, 2, 1, 0, 2, 1};
     630              : 
     631              :   // Add four hexahedra for each tetrahedron.
     632              :   {
     633              :     mfem::Array<int> edges, faces, orients;
     634            2 :     for (int e = 0; e < ne_tet; ++e)
     635              :     {
     636              :       const int *verts = orig_mesh.GetElement(e)->GetVertices();
     637            1 :       orig_mesh.GetElementEdges(e, edges, orients);
     638            1 :       orig_mesh.GetElementFaces(e, faces, orients);
     639              : 
     640              :       // One hex for each vertex of the tet.
     641            5 :       for (int i = 0; i < 4; ++i)
     642              :       {
     643              :         int hex_v[8];
     644            4 :         hex_v[0] = verts[i];
     645            4 :         hex_v[1] = nv_tet + edges[tet_vertex_edge_map[3 * i + 0]];
     646            4 :         hex_v[2] = nv_tet + nedge_tet + faces[tet_vertex_face_map[3 * i + 0]];
     647            4 :         hex_v[3] = nv_tet + edges[tet_vertex_edge_map[3 * i + 1]];
     648            4 :         hex_v[4] = nv_tet + edges[tet_vertex_edge_map[3 * i + 2]];
     649            4 :         hex_v[5] = nv_tet + nedge_tet + faces[tet_vertex_face_map[3 * i + 1]];
     650            4 :         hex_v[6] = nv_tet + nedge_tet + nface_tet + e;
     651            4 :         hex_v[7] = nv_tet + nedge_tet + faces[tet_vertex_face_map[3 * i + 2]];
     652            4 :         hex_mesh.AddHex(hex_v, orig_mesh.GetAttribute(e));
     653              :       }
     654              :     }
     655              :   }
     656              : 
     657              :   // Add the boundary elements.
     658              :   {
     659              :     mfem::Array<int> edges, orients;
     660            5 :     for (int be = 0; be < nbe_tet; ++be)
     661              :     {
     662              :       int f, o;
     663              :       const int *verts = orig_mesh.GetBdrElement(be)->GetVertices();
     664            4 :       orig_mesh.GetBdrElementEdges(be, edges, orients);
     665            4 :       orig_mesh.GetBdrElementFace(be, &f, &o);
     666              : 
     667              :       // One quad for each vertex of the tri.
     668           16 :       for (int i = 0; i < 3; ++i)
     669              :       {
     670              :         int quad_v[4];
     671           12 :         quad_v[0] = verts[i];
     672           12 :         quad_v[1] = nv_tet + edges[tri_vertex_edge_map[2 * i + 0]];
     673           12 :         quad_v[2] = nv_tet + nedge_tet + f;
     674           12 :         quad_v[3] = nv_tet + edges[tri_vertex_edge_map[2 * i + 1]];
     675           12 :         hex_mesh.AddBdrQuad(quad_v, orig_mesh.GetBdrAttribute(be));
     676              :       }
     677              :     }
     678              :   }
     679              : 
     680              :   // Finalize the hex mesh topology. The mesh will be marked for refinement later on.
     681              :   constexpr bool generate_bdr = false;
     682            1 :   hex_mesh.FinalizeTopology(generate_bdr);
     683              : 
     684              :   // All elements have now been added, can construct the higher order field.
     685            1 :   if (orig_mesh.GetNodes())
     686              :   {
     687            1 :     hex_mesh.EnsureNodes();
     688              :     // Higher order associated to vertices are unchanged, and those for
     689              :     // previously existing edges. DOFs associated to new elements need to be set.
     690              :     const int sdim = orig_mesh.SpaceDimension();
     691              :     auto *orig_fespace = orig_mesh.GetNodes()->FESpace();
     692            2 :     hex_mesh.SetCurvature(orig_fespace->GetMaxElementOrder(), orig_fespace->IsDGSpace(),
     693              :                           orig_mesh.SpaceDimension(), orig_fespace->GetOrdering());
     694              : 
     695              :     // Need to convert the hexahedra local coordinate system into the parent tetrahedra
     696              :     // system. Each hexahedra spans a different set of the tet's reference coordinates. To
     697              :     // convert between, define the reference coordinate locations of each of the vertices
     698              :     // the hexahedra will use, then perform trilinear interpolation in the reference space.
     699              : 
     700            1 :     auto [vert_loc, edge_loc, face_loc] = []()
     701              :     {
     702              :       std::array<std::array<double, 3>, 4> vert_loc{};
     703              :       vert_loc[0] = {0.0, 0.0, 0.0};
     704              :       vert_loc[1] = {1.0, 0.0, 0.0};
     705              :       vert_loc[2] = {0.0, 1.0, 0.0};
     706              :       vert_loc[3] = {0.0, 0.0, 1.0};
     707              :       std::array<std::array<double, 3>, 6> edge_loc{};
     708              :       edge_loc[0] = {0.5, 0.0, 0.0};
     709              :       edge_loc[1] = {0.0, 0.5, 0.0};
     710              :       edge_loc[2] = {0.0, 0.0, 0.5};
     711              :       edge_loc[3] = {0.5, 0.5, 0.0};
     712              :       edge_loc[4] = {0.5, 0.0, 0.5};
     713              :       edge_loc[5] = {0.0, 0.5, 0.5};
     714              :       std::array<std::array<double, 3>, 6> face_loc{};
     715              :       face_loc[0] = {1.0 / 3, 1.0 / 3, 1.0 / 3};
     716              :       face_loc[1] = {0.0, 1.0 / 3, 1.0 / 3};
     717              :       face_loc[2] = {1.0 / 3, 0.0, 1.0 / 3};
     718              :       face_loc[3] = {1.0 / 3, 1.0 / 3, 0.0};
     719              :       return std::make_tuple(vert_loc, edge_loc, face_loc);
     720              :     }();
     721              :     std::array<double, 3> centroid{{0.25, 0.25, 0.25}};
     722              : 
     723              :     // We assume the Nodes field is of a single order, and there is a single tet originally.
     724              :     // The nodes within the reference hex and parent tet are always the same, so we use the
     725              :     // typical FE. We then exploit the fact the map between reference spaces is always
     726              :     // linear, and construct the transformation explicitly.
     727            1 :     const auto *orig_FE = orig_mesh.GetNodes()->FESpace()->GetTypicalFE();
     728            1 :     const auto *child_FE = hex_mesh.GetNodes()->FESpace()->GetTypicalFE();
     729              :     // Original shape function (i), at new element nodes (j), for each new element (k).
     730            1 :     mfem::DenseTensor shape(orig_FE->GetDof(), child_FE->GetDof(), 4);
     731              :     mfem::Vector col;  // For slicing into matrices within shape
     732            5 :     for (int i = 0; i < 4; i++)
     733              :     {
     734              :       // Collect the vertices of the new hex within the tet.
     735              :       std::array<std::array<double, 3>, 8> hex_verts;
     736            4 :       hex_verts[0] = vert_loc[i];
     737            4 :       hex_verts[1] = edge_loc[tet_vertex_edge_map[3 * i + 0]];
     738            4 :       hex_verts[2] = face_loc[tet_vertex_face_map[3 * i + 0]];
     739            4 :       hex_verts[3] = edge_loc[tet_vertex_edge_map[3 * i + 1]];
     740            4 :       hex_verts[4] = edge_loc[tet_vertex_edge_map[3 * i + 2]];
     741            4 :       hex_verts[5] = face_loc[tet_vertex_face_map[3 * i + 1]];
     742            4 :       hex_verts[6] = centroid;
     743            4 :       hex_verts[7] = face_loc[tet_vertex_face_map[3 * i + 2]];
     744           36 :       for (int j = 0; j < child_FE->GetNodes().Size(); j++)
     745              :       {
     746              :         const auto &cn = child_FE->GetNodes()[j];
     747              :         mfem::IntegrationPoint cn_in_orig;
     748              : 
     749              :         // Perform trilinear interpolation from (u,v,w) the unit ref coords in the new hex,
     750              :         // and the corresponding nodes in the containing tet.
     751              :         // clang-format off
     752              :         // x component
     753           32 :         cn_in_orig.x =
     754           32 :           hex_verts[0][0] * (1-cn.x) * (1-cn.y) * (1-cn.z) +
     755           32 :           hex_verts[1][0] * cn.x     * (1-cn.y) * (1-cn.z) +
     756           32 :           hex_verts[2][0] * cn.x     * cn.y     * (1-cn.z) +
     757           32 :           hex_verts[3][0] * (1-cn.x) * cn.y     * (1-cn.z) +
     758           32 :           hex_verts[4][0] * (1-cn.x) * (1-cn.y) * cn.z     +
     759           32 :           hex_verts[5][0] * cn.x     * (1-cn.y) * cn.z     +
     760           32 :           hex_verts[6][0] * cn.x     * cn.y     * cn.z     +
     761           32 :           hex_verts[7][0] * (1-cn.x) * cn.y     * cn.z;
     762              : 
     763              :         // y component
     764           32 :         cn_in_orig.y =
     765           32 :           hex_verts[0][1] * (1-cn.x) * (1-cn.y) * (1-cn.z) +
     766           32 :           hex_verts[1][1] * cn.x     * (1-cn.y) * (1-cn.z) +
     767           32 :           hex_verts[2][1] * cn.x     * cn.y     * (1-cn.z) +
     768           32 :           hex_verts[3][1] * (1-cn.x) * cn.y     * (1-cn.z) +
     769           32 :           hex_verts[4][1] * (1-cn.x) * (1-cn.y) * cn.z     +
     770           32 :           hex_verts[5][1] * cn.x     * (1-cn.y) * cn.z     +
     771           32 :           hex_verts[6][1] * cn.x     * cn.y     * cn.z     +
     772           32 :           hex_verts[7][1] * (1-cn.x) * cn.y     * cn.z;
     773              : 
     774              :         // z component
     775           32 :         cn_in_orig.z =
     776           32 :           hex_verts[0][2] * (1-cn.x) * (1-cn.y) * (1-cn.z) +
     777           32 :           hex_verts[1][2] * cn.x     * (1-cn.y) * (1-cn.z) +
     778           32 :           hex_verts[2][2] * cn.x     * cn.y     * (1-cn.z) +
     779           32 :           hex_verts[3][2] * (1-cn.x) * cn.y     * (1-cn.z) +
     780           32 :           hex_verts[4][2] * (1-cn.x) * (1-cn.y) * cn.z     +
     781           32 :           hex_verts[5][2] * cn.x     * (1-cn.y) * cn.z     +
     782           32 :           hex_verts[6][2] * cn.x     * cn.y     * cn.z     +
     783           32 :           hex_verts[7][2] * (1-cn.x) * cn.y     * cn.z;
     784              :         // clang-format on
     785              :         shape(i).GetColumnReference(j, col);
     786           32 :         orig_FE->CalcShape(cn_in_orig, col);
     787              :       }
     788              :     }
     789              : 
     790              :     // Each submatrix of shape tensor now encodes the reference coordinates of each hex
     791              :     // within the containing tet. Extracting the specific element dof values, and applying
     792              :     // to the correct shape slice will now give the requisite higher order dofs evaluated at
     793              :     // the refined elements nodes.
     794              :     mfem::Array<int> hex_dofs;
     795            1 :     mfem::DenseMatrix point_matrix(child_FE->GetDof(), sdim);  // nnode_child x sdim
     796            1 :     mfem::Vector dof_vals(orig_FE->GetDof() * sdim);
     797              :     mfem::DenseMatrix dof_vals_mat(dof_vals.GetData(), orig_FE->GetDof(), sdim);
     798            2 :     for (int e = 0; e < ne_tet; ++e)
     799              :     {
     800              :       // Returns byNODES no matter what, because FiniteElementSpace::GetElementVDofs does.
     801              :       // Matches the GetElementVDofs call below, which similarly always uses byNODES.
     802            1 :       orig_mesh.GetNodes()->GetElementDofValues(e, dof_vals);
     803            5 :       for (int i = 0; i < 4; i++)
     804              :       {
     805              :         // shape(i) : orig_FE->GetDof() x hex_FE->GetDof()
     806              :         // dof_vals_mat : orig_FE->GetDof() x sdim
     807              :         // point_matrix : child_FE->GetDof() x sdim
     808            4 :         MultAtB(shape(i), dof_vals_mat, point_matrix);
     809            4 :         hex_mesh.GetNodes()->FESpace()->GetElementVDofs(4 * e + i, hex_dofs);
     810            4 :         hex_mesh.GetNodes()->SetSubVector(hex_dofs, point_matrix.GetData());
     811              :       }
     812              :     }
     813            2 :   }
     814              : 
     815            1 :   return hex_mesh;
     816            0 : }
     817              : 
     818              : namespace
     819              : {
     820              : 
     821           86 : void ScaleMesh(mfem::Mesh &mesh, double L)
     822              : {
     823           86 :   PalacePragmaOmp(parallel for schedule(static))
     824              :   for (int i = 0; i < mesh.GetNV(); i++)
     825              :   {
     826              :     double *v = mesh.GetVertex(i);
     827              :     std::transform(v, v + mesh.SpaceDimension(), v, [L](double val) { return val * L; });
     828              :   }
     829           86 :   if (auto *pmesh = dynamic_cast<mfem::ParMesh *>(&mesh))
     830              :   {
     831           86 :     PalacePragmaOmp(parallel for schedule(static))
     832              :     for (int i = 0; i < pmesh->face_nbr_vertices.Size(); i++)
     833              :     {
     834              :       double *v = pmesh->face_nbr_vertices[i]();
     835              :       std::transform(v, v + mesh.SpaceDimension(), v, [L](double val) { return val * L; });
     836              :     }
     837              :   }
     838           86 :   if (mesh.GetNodes())
     839              :   {
     840           68 :     *mesh.GetNodes() *= L;
     841           68 :     if (auto *pnodes = dynamic_cast<mfem::ParGridFunction *>(mesh.GetNodes()))
     842              :     {
     843           68 :       pnodes->FaceNbrData() *= L;
     844              :     }
     845              :   }
     846           86 : }
     847              : 
     848              : }  // namespace
     849              : 
     850           30 : void DimensionalizeMesh(mfem::Mesh &mesh, double L)
     851              : {
     852           30 :   ScaleMesh(mesh, L);
     853           30 : }
     854              : 
     855           56 : void NondimensionalizeMesh(mfem::Mesh &mesh, double L)
     856              : {
     857           56 :   ScaleMesh(mesh, 1.0 / L);
     858           56 : }
     859              : 
     860            0 : std::vector<mfem::Geometry::Type> ElementTypeInfo::GetGeomTypes() const
     861              : {
     862              :   std::vector<mfem::Geometry::Type> geom_types;
     863            0 :   if (has_simplices)
     864              :   {
     865            0 :     geom_types.push_back(mfem::Geometry::TETRAHEDRON);
     866              :   }
     867            0 :   if (has_hexahedra)
     868              :   {
     869            0 :     geom_types.push_back(mfem::Geometry::CUBE);
     870              :   }
     871            0 :   if (has_prisms)
     872              :   {
     873            0 :     geom_types.push_back(mfem::Geometry::PRISM);
     874              :   }
     875            0 :   if (has_pyramids)
     876              :   {
     877            0 :     geom_types.push_back(mfem::Geometry::PYRAMID);
     878              :   }
     879            0 :   return geom_types;
     880              : }
     881              : 
     882           16 : ElementTypeInfo CheckElements(const mfem::Mesh &mesh)
     883              : {
     884              :   // MeshGenerator is reduced over the communicator. This checks for geometries on any
     885              :   // processor.
     886              :   auto meshgen = mesh.MeshGenerator();
     887           16 :   return {bool(meshgen & 1), bool(meshgen & 2), bool(meshgen & 4), bool(meshgen & 8)};
     888              : }
     889              : 
     890            0 : bool CheckRefinementFlags(const mfem::Mesh &mesh)
     891              : {
     892            0 :   bool marked = true;
     893            0 :   for (int e = 0; e < mesh.GetNE(); e++)
     894              :   {
     895              :     const mfem::Element *el = mesh.GetElement(e);
     896              :     const int geom = el->GetGeometryType();
     897            0 :     if (geom == mfem::Geometry::TETRAHEDRON)
     898              :     {
     899              :       const mfem::Tetrahedron *tet = static_cast<const mfem::Tetrahedron *>(el);
     900            0 :       if (tet->GetRefinementFlag() == 0)
     901              :       {
     902            0 :         marked = false;
     903              :         break;
     904              :       }
     905              :     }
     906              :   }
     907            0 :   if (const auto *pmesh = dynamic_cast<const mfem::ParMesh *>(&mesh))
     908              :   {
     909              :     Mpi::GlobalAnd(1, &marked, pmesh->GetComm());
     910              :   }
     911            0 :   return marked;
     912              : }
     913              : 
     914          317 : void AttrToMarker(int max_attr, const int *attr_list, int attr_list_size,
     915              :                   mfem::Array<int> &marker, bool skip_invalid)
     916              : {
     917          457 :   MFEM_VERIFY(skip_invalid || attr_list_size == 0 ||
     918              :                   *std::max_element(attr_list, attr_list + attr_list_size) <= max_attr,
     919              :               "Invalid attribute number present ("
     920              :                   << *std::max_element(attr_list, attr_list + attr_list_size) << ")!");
     921              :   marker.SetSize(max_attr);
     922          317 :   if (attr_list_size == 1 && attr_list[0] == -1)
     923              :   {
     924              :     marker = 1;
     925              :   }
     926              :   else
     927              :   {
     928              :     marker = 0;
     929          725 :     for (int i = 0; i < attr_list_size; i++)
     930              :     {
     931          408 :       int attr = attr_list[i];
     932          408 :       if ((attr <= 0 || attr > max_attr) && skip_invalid)
     933              :       {
     934            0 :         continue;
     935              :       }
     936            0 :       MFEM_VERIFY(attr > 0, "Attribute number less than one!");
     937          408 :       MFEM_VERIFY(marker[attr - 1] == 0, "Repeated attribute in attribute list!");
     938          408 :       marker[attr - 1] = 1;
     939              :     }
     940              :   }
     941          317 : }
     942              : 
     943           73 : void GetAxisAlignedBoundingBox(const mfem::ParMesh &mesh, const mfem::Array<int> &marker,
     944              :                                bool bdr, mfem::Vector &min, mfem::Vector &max)
     945              : {
     946           73 :   int dim = mesh.SpaceDimension();
     947           73 :   min.SetSize(dim);
     948           73 :   max.SetSize(dim);
     949          292 :   for (int d = 0; d < dim; d++)
     950              :   {
     951          219 :     min(d) = mfem::infinity();
     952          219 :     max(d) = -mfem::infinity();
     953              :   }
     954           73 :   if (!mesh.GetNodes())
     955              :   {
     956              :     auto BBUpdate =
     957        97668 :         [&mesh, &dim](const int *v, int nv, mfem::Vector &min, mfem::Vector &max)
     958              :     {
     959       488340 :       for (int j = 0; j < nv; j++)
     960              :       {
     961       390672 :         const double *coord = mesh.GetVertex(v[j]);
     962      1562688 :         for (int d = 0; d < dim; d++)
     963              :         {
     964      1172016 :           if (coord[d] < min(d))
     965              :           {
     966          251 :             min(d) = coord[d];
     967              :           }
     968      1172016 :           if (coord[d] > max(d))
     969              :           {
     970          410 :             max(d) = coord[d];
     971              :           }
     972              :         }
     973              :       }
     974        97686 :     };
     975           18 :     PalacePragmaOmp(parallel)
     976              :     {
     977              :       mfem::Vector loc_min(dim), loc_max(dim);
     978              :       for (int d = 0; d < dim; d++)
     979              :       {
     980              :         loc_min(d) = mfem::infinity();
     981              :         loc_max(d) = -mfem::infinity();
     982              :       }
     983              :       if (bdr)
     984              :       {
     985              :         PalacePragmaOmp(for schedule(static))
     986              :         for (int i = 0; i < mesh.GetNBE(); i++)
     987              :         {
     988              :           if (!marker[mesh.GetBdrAttribute(i) - 1])
     989              :           {
     990              :             continue;
     991              :           }
     992              :           const int *verts = mesh.GetBdrElement(i)->GetVertices();
     993              :           BBUpdate(verts, mesh.GetBdrElement(i)->GetNVertices(), loc_min, loc_max);
     994              :         }
     995              :       }
     996              :       else
     997              :       {
     998              :         PalacePragmaOmp(for schedule(static))
     999              :         for (int i = 0; i < mesh.GetNE(); i++)
    1000              :         {
    1001              :           if (!marker[mesh.GetAttribute(i) - 1])
    1002              :           {
    1003              :             continue;
    1004              :           }
    1005              :           const int *verts = mesh.GetElement(i)->GetVertices();
    1006              :           BBUpdate(verts, mesh.GetElement(i)->GetNVertices(), loc_min, loc_max);
    1007              :         }
    1008              :       }
    1009              :       PalacePragmaOmp(critical(BBUpdate))
    1010              :       {
    1011              :         for (int d = 0; d < dim; d++)
    1012              :         {
    1013              :           min(d) = std::min(min(d), loc_min(d));
    1014              :           max(d) = std::max(max(d), loc_max(d));
    1015              :         }
    1016              :       }
    1017              :     }
    1018              :   }
    1019              :   else
    1020              :   {
    1021           55 :     mesh.GetNodes()->HostRead();
    1022           55 :     const int ref = mesh.GetNodes()->FESpace()->GetMaxElementOrder();
    1023        98731 :     auto BBUpdate = [&ref](mfem::GeometryRefiner &refiner, mfem::ElementTransformation &T,
    1024              :                            mfem::DenseMatrix &pointmat, mfem::Vector &min,
    1025              :                            mfem::Vector &max)
    1026              :     {
    1027        98731 :       mfem::RefinedGeometry *RefG = refiner.Refine(T.GetGeometryType(), ref);
    1028        98731 :       T.Transform(RefG->RefPts, pointmat);
    1029       493655 :       for (int j = 0; j < pointmat.Width(); j++)
    1030              :       {
    1031      1579696 :         for (int d = 0; d < pointmat.Height(); d++)
    1032              :         {
    1033      1184772 :           if (pointmat(d, j) < min(d))
    1034              :           {
    1035          711 :             min(d) = pointmat(d, j);
    1036              :           }
    1037      1184772 :           if (pointmat(d, j) > max(d))
    1038              :           {
    1039          715 :             max(d) = pointmat(d, j);
    1040              :           }
    1041              :         }
    1042              :       }
    1043        98786 :     };
    1044           55 :     PalacePragmaOmp(parallel)
    1045              :     {
    1046              :       mfem::Vector loc_min(dim), loc_max(dim);
    1047              :       for (int d = 0; d < dim; d++)
    1048              :       {
    1049              :         loc_min(d) = mfem::infinity();
    1050              :         loc_max(d) = -mfem::infinity();
    1051              :       }
    1052              :       mfem::GeometryRefiner refiner;
    1053              :       mfem::IsoparametricTransformation T;
    1054              :       mfem::DenseMatrix pointmat;
    1055              :       if (bdr)
    1056              :       {
    1057              :         PalacePragmaOmp(for schedule(static))
    1058              :         for (int i = 0; i < mesh.GetNBE(); i++)
    1059              :         {
    1060              :           if (!marker[mesh.GetBdrAttribute(i) - 1])
    1061              :           {
    1062              :             continue;
    1063              :           }
    1064              :           mesh.GetBdrElementTransformation(i, &T);
    1065              :           BBUpdate(refiner, T, pointmat, loc_min, loc_max);
    1066              :         }
    1067              :       }
    1068              :       else
    1069              :       {
    1070              :         PalacePragmaOmp(for schedule(static))
    1071              :         for (int i = 0; i < mesh.GetNE(); i++)
    1072              :         {
    1073              :           if (!marker[mesh.GetAttribute(i) - 1])
    1074              :           {
    1075              :             continue;
    1076              :           }
    1077              :           mesh.GetElementTransformation(i, &T);
    1078              :           BBUpdate(refiner, T, pointmat, loc_min, loc_max);
    1079              :         }
    1080              :       }
    1081              :       PalacePragmaOmp(critical(BBUpdate))
    1082              :       {
    1083              :         for (int d = 0; d < dim; d++)
    1084              :         {
    1085              :           min(d) = std::min(min(d), loc_min(d));
    1086              :           max(d) = std::max(max(d), loc_max(d));
    1087              :         }
    1088              :       }
    1089              :     }
    1090              :   }
    1091           73 :   Mpi::GlobalMin(dim, min.HostReadWrite(), mesh.GetComm());
    1092           73 :   Mpi::GlobalMax(dim, max.HostReadWrite(), mesh.GetComm());
    1093           73 : }
    1094              : 
    1095            0 : double BoundingBox::Area() const
    1096              : {
    1097            0 :   return 4.0 * CVector3dMap(axes[0].data()).cross(CVector3dMap(axes[1].data())).norm();
    1098              : }
    1099              : 
    1100            0 : double BoundingBox::Volume() const
    1101              : {
    1102            0 :   return planar ? 0.0 : 2.0 * CVector3dMap(axes[2].data()).norm() * Area();
    1103              : }
    1104              : 
    1105            1 : std::array<std::array<double, 3>, 3> BoundingBox::Normals() const
    1106              : {
    1107            1 :   std::array<std::array<double, 3>, 3> normals = {axes[0], axes[1], axes[2]};
    1108              :   Vector3dMap(normals[0].data()).normalize();
    1109              :   Vector3dMap(normals[1].data()).normalize();
    1110              :   Vector3dMap(normals[2].data()).normalize();
    1111            1 :   return normals;
    1112              : }
    1113              : 
    1114           31 : std::array<double, 3> BoundingBox::Lengths() const
    1115              : {
    1116           31 :   return {2.0 * CVector3dMap(axes[0].data()).norm(),
    1117           31 :           2.0 * CVector3dMap(axes[1].data()).norm(),
    1118           31 :           2.0 * CVector3dMap(axes[2].data()).norm()};
    1119              : }
    1120              : 
    1121           30 : std::array<double, 3> BoundingBox::Deviations(const std::array<double, 3> &direction) const
    1122              : {
    1123              :   const auto eig_dir = CVector3dMap(direction.data());
    1124              :   std::array<double, 3> deviation_deg;
    1125          120 :   for (std::size_t i = 0; i < 3; i++)
    1126              :   {
    1127           90 :     deviation_deg[i] =
    1128           90 :         std::acos(std::min(1.0, std::abs(eig_dir.normalized().dot(
    1129           90 :                                     CVector3dMap(axes[i].data()).normalized())))) *
    1130              :         (180.0 / M_PI);
    1131              :   }
    1132           30 :   return deviation_deg;
    1133              : }
    1134              : 
    1135           30 : BoundingBox GetBoundingBox(const mfem::ParMesh &mesh, const mfem::Array<int> &marker,
    1136              :                            bool bdr)
    1137              : {
    1138              :   std::vector<Eigen::Vector3d> vertices;
    1139           30 :   int dominant_rank = CollectPointCloudOnRoot(mesh, marker, bdr, vertices);
    1140           60 :   return BoundingBoxFromPointCloud(mesh.GetComm(), vertices, dominant_rank);
    1141              : }
    1142              : 
    1143            0 : BoundingBox GetBoundingBall(const mfem::ParMesh &mesh, const mfem::Array<int> &marker,
    1144              :                             bool bdr)
    1145              : {
    1146              :   std::vector<Eigen::Vector3d> vertices;
    1147            0 :   int dominant_rank = CollectPointCloudOnRoot(mesh, marker, bdr, vertices);
    1148            0 :   return BoundingBallFromPointCloud(mesh.GetComm(), vertices, dominant_rank);
    1149              : }
    1150              : 
    1151           30 : double GetProjectedLength(const mfem::ParMesh &mesh, const mfem::Array<int> &marker,
    1152              :                           bool bdr, const std::array<double, 3> &dir)
    1153              : {
    1154              :   std::vector<Eigen::Vector3d> vertices;
    1155           30 :   int dominant_rank = CollectPointCloudOnRoot(mesh, marker, bdr, vertices);
    1156              :   double length;
    1157           30 :   if (dominant_rank == Mpi::Rank(mesh.GetComm()))
    1158              :   {
    1159              :     CVector3dMap direction(dir.data());
    1160              :     auto Dot = [&](const auto &x, const auto &y)
    1161          472 :     { return direction.dot(x) < direction.dot(y); };
    1162           28 :     auto p_min = std::min_element(vertices.begin(), vertices.end(), Dot);
    1163           28 :     auto p_max = std::max_element(vertices.begin(), vertices.end(), Dot);
    1164           28 :     length = (*p_max - *p_min).dot(direction.normalized());
    1165              :   }
    1166              :   Mpi::Broadcast(1, &length, dominant_rank, mesh.GetComm());
    1167           60 :   return length;
    1168              : }
    1169              : 
    1170            0 : double GetDistanceFromPoint(const mfem::ParMesh &mesh, const mfem::Array<int> &marker,
    1171              :                             bool bdr, const std::array<double, 3> &origin, bool max)
    1172              : {
    1173              :   std::vector<Eigen::Vector3d> vertices;
    1174            0 :   int dominant_rank = CollectPointCloudOnRoot(mesh, marker, bdr, vertices);
    1175              :   double dist;
    1176            0 :   if (dominant_rank == Mpi::Rank(mesh.GetComm()))
    1177              :   {
    1178              :     CVector3dMap x0(origin.data());
    1179              :     auto p =
    1180            0 :         max ? std::max_element(vertices.begin(), vertices.end(),
    1181            0 :                                [&x0](const Eigen::Vector3d &x, const Eigen::Vector3d &y)
    1182            0 :                                { return (x - x0).norm() < (y - x0).norm(); })
    1183            0 :             : std::min_element(vertices.begin(), vertices.end(),
    1184            0 :                                [&x0](const Eigen::Vector3d &x, const Eigen::Vector3d &y)
    1185            0 :                                { return (x - x0).norm() < (y - x0).norm(); });
    1186            0 :     dist = (*p - x0).norm();
    1187              :   }
    1188              :   Mpi::Broadcast(1, &dist, dominant_rank, mesh.GetComm());
    1189            0 :   return dist;
    1190              : }
    1191              : 
    1192              : // Given a mesh and boundary attribute marker array, compute a normal for the surface. If
    1193              : // not averaging, use the first entry.
    1194           78 : mfem::Vector GetSurfaceNormal(const mfem::ParMesh &mesh, const mfem::Array<int> &marker,
    1195              :                               bool average)
    1196              : {
    1197              :   int dim = mesh.SpaceDimension();
    1198           78 :   mfem::IsoparametricTransformation T;
    1199              :   mfem::Vector loc_normal(dim), normal(dim);
    1200           78 :   normal = 0.0;
    1201           78 :   if (mesh.Dimension() == mesh.SpaceDimension())
    1202              :   {
    1203              :     // Loop over boundary elements. Exit early if not averaging and non-zero normal.
    1204         3966 :     for (int i = 0; i < mesh.GetNBE() && !(!average && normal.Norml2() > 0.0); i++)
    1205              :     {
    1206         3888 :       if (!marker[mesh.GetBdrAttribute(i) - 1])
    1207              :       {
    1208         3672 :         continue;
    1209              :       }
    1210          216 :       mesh.GetBdrElementTransformation(i, &T);
    1211          216 :       mesh::Normal(T, loc_normal, &normal);
    1212          216 :       normal += loc_normal;
    1213              :     }
    1214              :   }
    1215              :   else
    1216              :   {
    1217              :     // Loop over domain elements. Exit early if not averaging and non-zero normal.
    1218            0 :     for (int i = 0; i < mesh.GetNE() && !(!average && normal.Norml2() > 0.0); i++)
    1219              :     {
    1220            0 :       if (!marker[mesh.GetAttribute(i) - 1])
    1221              :       {
    1222            0 :         continue;
    1223              :       }
    1224            0 :       mesh.GetElementTransformation(i, &T);
    1225            0 :       mesh::Normal(T, loc_normal, &normal);
    1226            0 :       normal += loc_normal;
    1227              :     }
    1228              :   }
    1229              : 
    1230              :   // If different processors have different normal orientations, take that from the lowest
    1231              :   // rank processor.
    1232              :   MPI_Comm comm = mesh.GetComm();
    1233           78 :   int rank = Mpi::Size(comm);
    1234              :   mfem::Vector glob_normal(dim);
    1235           78 :   if (normal.Norml2() > 0.0)
    1236              :   {
    1237           78 :     rank = Mpi::Rank(comm);
    1238              :   }
    1239              :   Mpi::GlobalMin(1, &rank, comm);
    1240           78 :   if (rank == Mpi::Size(comm))
    1241              :   {
    1242              :     // No boundary elements are marked.
    1243            0 :     normal = 0.0;
    1244              :     return normal;
    1245              :   }
    1246           78 :   if (rank == Mpi::Rank(comm))
    1247              :   {
    1248           76 :     glob_normal = normal;
    1249              :   }
    1250           78 :   Mpi::Broadcast(dim, glob_normal.HostReadWrite(), rank, comm);
    1251           78 :   if (average)
    1252              :   {
    1253           78 :     if (normal * glob_normal < 0.0)
    1254              :     {
    1255            0 :       normal.Neg();
    1256              :     }
    1257              :     Mpi::GlobalSum(dim, normal.HostReadWrite(), comm);
    1258              :   }
    1259              :   else
    1260              :   {
    1261            0 :     normal = glob_normal;
    1262              :   }
    1263           78 :   normal /= normal.Norml2();
    1264              : 
    1265              :   if constexpr (false)
    1266              :   {
    1267              :     Mpi::Print(comm, " Surface normal = ({:+.3e})", fmt::join(normal, ", "));
    1268              :   }
    1269              :   return normal;
    1270           78 : }
    1271              : 
    1272           30 : double GetSurfaceArea(const mfem::ParMesh &mesh, const mfem::Array<int> &marker)
    1273              : {
    1274           30 :   double area = 0.0;
    1275           30 :   PalacePragmaOmp(parallel reduction(+ : area))
    1276              :   {
    1277              :     mfem::IsoparametricTransformation T;
    1278              :     PalacePragmaOmp(for schedule(static))
    1279              :     for (int i = 0; i < mesh.GetNBE(); i++)
    1280              :     {
    1281              :       if (!marker[mesh.GetBdrAttribute(i) - 1])
    1282              :       {
    1283              :         continue;
    1284              :       }
    1285              :       mesh.GetBdrElementTransformation(i, &T);
    1286              :       const mfem::IntegrationRule &ir = mfem::IntRules.Get(T.GetGeometryType(), T.OrderJ());
    1287              :       for (int j = 0; j < ir.GetNPoints(); j++)
    1288              :       {
    1289              :         const mfem::IntegrationPoint &ip = ir.IntPoint(j);
    1290              :         T.SetIntPoint(&ip);
    1291              :         area += ip.weight * T.Weight();
    1292              :       }
    1293              :     }
    1294              :   }
    1295              :   Mpi::GlobalSum(1, &area, mesh.GetComm());
    1296           30 :   return area;
    1297              : }
    1298              : 
    1299            0 : double GetVolume(const mfem::ParMesh &mesh, const mfem::Array<int> &marker)
    1300              : {
    1301            0 :   double volume = 0.0;
    1302            0 :   PalacePragmaOmp(parallel reduction(+ : volume))
    1303              :   {
    1304              :     mfem::IsoparametricTransformation T;
    1305              :     PalacePragmaOmp(for schedule(static))
    1306              :     for (int i = 0; i < mesh.GetNE(); i++)
    1307              :     {
    1308              :       if (!marker[mesh.GetAttribute(i) - 1])
    1309              :       {
    1310              :         continue;
    1311              :       }
    1312              :       mesh.GetElementTransformation(i, &T);
    1313              :       const mfem::IntegrationRule &ir = mfem::IntRules.Get(T.GetGeometryType(), T.OrderJ());
    1314              :       for (int j = 0; j < ir.GetNPoints(); j++)
    1315              :       {
    1316              :         const mfem::IntegrationPoint &ip = ir.IntPoint(j);
    1317              :         T.SetIntPoint(&ip);
    1318              :         volume += ip.weight * T.Weight();
    1319              :       }
    1320              :     }
    1321              :   }
    1322              :   Mpi::GlobalSum(1, &volume, mesh.GetComm());
    1323            0 :   return volume;
    1324              : }
    1325              : 
    1326            0 : double RebalanceMesh(const IoData &iodata, std::unique_ptr<mfem::ParMesh> &mesh)
    1327              : {
    1328            0 :   BlockTimer bt0(Timer::REBALANCE);
    1329            0 :   MPI_Comm comm = mesh->GetComm();
    1330            0 :   if (iodata.model.refinement.save_adapt_mesh)
    1331              :   {
    1332              :     // Create a separate serial mesh to write to disk.
    1333            0 :     auto sfile = fs::path(iodata.problem.output) / fs::path(iodata.model.mesh).stem();
    1334            0 :     sfile += ".mesh";
    1335              : 
    1336            0 :     auto PrintSerial = [&](mfem::Mesh &smesh)
    1337              :     {
    1338            0 :       BlockTimer bt1(Timer::IO);
    1339            0 :       if (Mpi::Root(comm))
    1340              :       {
    1341            0 :         std::ofstream fo(sfile);
    1342              :         // mfem::ofgzstream fo(sfile, true);  // Use zlib compression if available
    1343              :         // fo << std::fixed;
    1344              :         fo << std::scientific;
    1345              :         fo.precision(MSH_FLT_PRECISION);
    1346            0 :         mesh::DimensionalizeMesh(smesh, iodata.units.GetMeshLengthRelativeScale());
    1347            0 :         smesh.Mesh::Print(fo);  // Do not need to nondimensionalize the temporary mesh
    1348            0 :       }
    1349            0 :       Mpi::Barrier(comm);
    1350            0 :     };
    1351              : 
    1352            0 :     if (mesh->Nonconforming())
    1353              :     {
    1354            0 :       mfem::ParMesh smesh(*mesh);
    1355              :       mfem::Array<int> serial_partition(mesh->GetNE());
    1356              :       serial_partition = 0;
    1357            0 :       smesh.Rebalance(serial_partition);
    1358            0 :       PrintSerial(smesh);
    1359            0 :     }
    1360              :     else
    1361              :     {
    1362            0 :       mfem::Mesh smesh = mesh->GetSerialMesh(0);
    1363            0 :       PrintSerial(smesh);
    1364            0 :     }
    1365            0 :   }
    1366              : 
    1367              :   // If there is more than one processor, may perform rebalancing.
    1368            0 :   if (Mpi::Size(comm) == 1)
    1369              :   {
    1370              :     return 1.0;
    1371              :   }
    1372              :   int min_elem, max_elem;
    1373            0 :   min_elem = max_elem = mesh->GetNE();
    1374            0 :   Mpi::GlobalMin(1, &min_elem, comm);
    1375            0 :   Mpi::GlobalMax(1, &max_elem, comm);
    1376            0 :   const double ratio = double(max_elem) / min_elem;
    1377            0 :   const double tol = iodata.model.refinement.maximum_imbalance;
    1378              :   if constexpr (false)
    1379              :   {
    1380              :     Mpi::Print("Rebalancing: max/min elements per processor = {:d}/{:d} (ratio = {:.3e}, "
    1381              :                "tol = {:.3e})\n",
    1382              :                max_elem, min_elem, ratio, tol);
    1383              :   }
    1384            0 :   if (ratio > tol)
    1385              :   {
    1386            0 :     if (mesh->Nonconforming())
    1387              :     {
    1388            0 :       mesh->Rebalance();
    1389              :     }
    1390              :     else
    1391              :     {
    1392              :       // Without access to a refinement tree, partitioning must be done on the root
    1393              :       // processor and then redistributed.
    1394            0 :       RebalanceConformalMesh(mesh);
    1395              :     }
    1396              :   }
    1397              :   return ratio;
    1398            0 : }
    1399              : 
    1400              : }  // namespace mesh
    1401              : 
    1402              : namespace
    1403              : {
    1404              : 
    1405            8 : std::unique_ptr<mfem::Mesh> LoadMesh(const std::string &mesh_file, bool remove_curvature,
    1406              :                                      const config::BoundaryData &boundaries)
    1407              : {
    1408              :   // Read the (serial) mesh from the given mesh file. Handle preparation for refinement and
    1409              :   // orientations here to avoid possible reorientations and reordering later on. MFEM
    1410              :   // supports a native mesh format (.mesh), VTK/VTU, Gmsh, as well as some others. We use
    1411              :   // built-in converters for the types we know, otherwise rely on MFEM to do the conversion
    1412              :   // or error out if not supported.
    1413            8 :   constexpr bool generate_edges = false, refine = false, fix_orientation = true;
    1414            8 :   std::unique_ptr<mfem::Mesh> mesh;
    1415            8 :   fs::path mesh_path(mesh_file);
    1416           40 :   if (mesh_path.extension() == ".mphtxt" || mesh_path.extension() == ".mphbin" ||
    1417           40 :       mesh_path.extension() == ".nas" || mesh_path.extension() == ".bdf")
    1418              :   {
    1419              :     // Put translated mesh in temporary string buffer.
    1420            0 :     std::stringstream fi(std::stringstream::in | std::stringstream::out);
    1421              :     // fi << std::fixed;
    1422              :     fi << std::scientific;
    1423              :     fi.precision(MSH_FLT_PRECISION);
    1424            0 :     if (mesh_path.extension() == ".mphtxt" || mesh_path.extension() == ".mphbin")
    1425              :     {
    1426            0 :       mesh::ConvertMeshComsol(mesh_file, fi, remove_curvature);
    1427              :       // mesh::ConvertMeshComsol(mesh_file, fo, remove_curvature);
    1428              :     }
    1429              :     else
    1430              :     {
    1431            0 :       mesh::ConvertMeshNastran(mesh_file, fi, remove_curvature);
    1432              :       // mesh::ConvertMeshNastran(mesh_file, fo, remove_curvature);
    1433              :     }
    1434            0 :     mesh = std::make_unique<mfem::Mesh>(fi, generate_edges, refine, fix_orientation);
    1435            0 :   }
    1436              :   else
    1437              :   {
    1438              :     // Otherwise, just rely on MFEM load the mesh.
    1439            8 :     std::ifstream fi(mesh_file);
    1440            8 :     if (!fi.good())
    1441              :     {
    1442            0 :       MFEM_ABORT("Unable to open mesh file \"" << mesh_file << "\"!");
    1443              :     }
    1444            8 :     mesh = std::make_unique<mfem::Mesh>(fi, generate_edges, refine, fix_orientation);
    1445            8 :   }
    1446            8 :   if (remove_curvature)
    1447              :   {
    1448            0 :     mesh->SetCurvature(-1);
    1449              :   }
    1450              :   else
    1451              :   {
    1452            8 :     mesh->EnsureNodes();
    1453              :   }
    1454            8 :   if (!boundaries.periodic.boundary_pairs.empty())
    1455              :   {
    1456              :     auto periodic_mesh = std::move(mesh);
    1457              : 
    1458            0 :     for (const auto &data : boundaries.periodic.boundary_pairs)
    1459              :     {
    1460            0 :       auto periodic_mapping = mesh::DeterminePeriodicVertexMapping(periodic_mesh, data);
    1461            0 :       if (!periodic_mapping.empty())
    1462              :       {
    1463              :         auto p_mesh = std::make_unique<mfem::Mesh>(
    1464            0 :             mfem::Mesh::MakePeriodic(*periodic_mesh, periodic_mapping));
    1465              :         periodic_mesh = std::move(p_mesh);
    1466              :       }
    1467              :     }
    1468              :     mesh = std::move(periodic_mesh);
    1469              :   }
    1470              : 
    1471            8 :   return mesh;
    1472            8 : }
    1473              : 
    1474              : template <typename T = std::vector<int>>
    1475            0 : void TransferHighOrderNodes(const mfem::Mesh &orig_mesh, mfem::Mesh &new_mesh,
    1476              :                             const T *elem_delete_map = nullptr)
    1477              : {
    1478              :   // This accounts for new boundary elements too since no new dofs are added. See the MFEM
    1479              :   // trimmer miniapp for reference.
    1480            0 :   MFEM_VERIFY(orig_mesh.GetNodes(), "No high-order nodes information to transfer!");
    1481              :   const mfem::GridFunction *nodes = orig_mesh.GetNodes();
    1482              :   const mfem::FiniteElementSpace *fespace = nodes->FESpace();
    1483              :   mfem::Ordering::Type ordering = fespace->GetOrdering();
    1484            0 :   int order = fespace->GetMaxElementOrder();
    1485              :   int sdim = orig_mesh.SpaceDimension();
    1486            0 :   bool discont =
    1487            0 :       (dynamic_cast<const mfem::L2_FECollection *>(fespace->FEColl()) != nullptr);
    1488            0 :   new_mesh.SetCurvature(order, discont, sdim, ordering);
    1489              :   mfem::GridFunction *new_nodes = new_mesh.GetNodes();
    1490              :   const mfem::FiniteElementSpace *new_fespace = new_nodes->FESpace();
    1491              : 
    1492              :   // Transfer dofs from the old mesh to the new ones. Either consider all elements (works
    1493              :   // for orientation or numbering changes), or use the prescribed old to new element index
    1494              :   // map.
    1495              :   mfem::Array<int> vdofs;
    1496              :   mfem::Vector loc_vec;
    1497            0 :   for (int e = 0; e < orig_mesh.GetNE(); e++)
    1498              :   {
    1499            0 :     if (!elem_delete_map || (*elem_delete_map)[e] >= 0)
    1500              :     {
    1501              :       // No need for DofTransformation here since spaces are H1 or L2.
    1502            0 :       fespace->GetElementVDofs(e, vdofs);
    1503            0 :       nodes->GetSubVector(vdofs, loc_vec);
    1504            0 :       new_fespace->GetElementVDofs(!elem_delete_map ? e : (*elem_delete_map)[e], vdofs);
    1505            0 :       new_nodes->SetSubVector(vdofs, loc_vec);
    1506              :     }
    1507              :   }
    1508            0 : }
    1509              : 
    1510            8 : void CleanMesh(std::unique_ptr<mfem::Mesh> &orig_mesh,
    1511              :                const std::vector<int> &mat_attr_list)
    1512              : {
    1513              :   auto mat_marker = mesh::AttrToMarker(
    1514            8 :       orig_mesh->attributes.Size() ? orig_mesh->attributes.Max() : 0, mat_attr_list, true);
    1515            8 :   std::vector<int> elem_delete_map(orig_mesh->GetNE(), -1),
    1516            8 :       bdr_elem_delete_map(orig_mesh->GetNBE(), -1);
    1517              : 
    1518              :   // Delete domain and boundary elements which have no associated material or BC attribute
    1519              :   // from the mesh.
    1520            8 :   int new_ne = 0;
    1521          344 :   for (int e = 0; e < orig_mesh->GetNE(); e++)
    1522              :   {
    1523          336 :     if (mat_marker[orig_mesh->GetAttribute(e) - 1])
    1524              :     {
    1525          336 :       elem_delete_map[e] = new_ne++;
    1526              :     }
    1527              :   }
    1528              : 
    1529              :   // Make sure to remove any boundary elements which are no longer attached to elements in
    1530              :   // the domain.
    1531            8 :   int new_nbe = 0;
    1532          392 :   for (int be = 0; be < orig_mesh->GetNBE(); be++)
    1533              :   {
    1534              :     int f, o, e1, e2;
    1535          384 :     orig_mesh->GetBdrElementFace(be, &f, &o);
    1536          384 :     orig_mesh->GetFaceElements(f, &e1, &e2);
    1537          384 :     bool no_e1 = (e1 < 0 || elem_delete_map[e1] < 0);
    1538          384 :     bool no_e2 = (e2 < 0 || elem_delete_map[e2] < 0);
    1539          384 :     if (!no_e1 || !no_e2)
    1540              :     {
    1541          384 :       bdr_elem_delete_map[be] = new_nbe++;
    1542              :     }
    1543              :     else if constexpr (false)
    1544              :     {
    1545              :       Mpi::Print("Deleting an unattached boundary element!\n");
    1546              :     }
    1547              :   }
    1548            8 :   if (new_ne < orig_mesh->GetNE())
    1549              :   {
    1550            0 :     Mpi::Print("Removed {:d} unmarked domain elements from the mesh\n",
    1551            0 :                orig_mesh->GetNE() - new_ne);
    1552              :   }
    1553            8 :   if (new_nbe < orig_mesh->GetNBE())
    1554              :   {
    1555            0 :     Mpi::Print("Removed {:d} unattached boundary elements from the mesh\n",
    1556            0 :                orig_mesh->GetNBE() - new_nbe);
    1557              :   }
    1558              : 
    1559              :   // Create the new mesh.
    1560            8 :   if (new_ne == orig_mesh->GetNE() && new_nbe == orig_mesh->GetNBE())
    1561              :   {
    1562              :     return;
    1563              :   }
    1564            0 :   MFEM_VERIFY(!orig_mesh->Nonconforming(),
    1565              :               "Mesh element cleaning is not supported for nonconforming meshes!");
    1566              :   auto new_mesh =
    1567            0 :       std::make_unique<mfem::Mesh>(orig_mesh->Dimension(), orig_mesh->GetNV(), new_ne,
    1568            0 :                                    new_nbe, orig_mesh->SpaceDimension());
    1569              : 
    1570              :   // Copy vertices and non-deleted domain and boundary elements.
    1571            0 :   for (int v = 0; v < orig_mesh->GetNV(); v++)
    1572              :   {
    1573            0 :     new_mesh->AddVertex(orig_mesh->GetVertex(v));
    1574              :   }
    1575            0 :   for (int e = 0; e < orig_mesh->GetNE(); e++)
    1576              :   {
    1577            0 :     if (elem_delete_map[e] >= 0)
    1578              :     {
    1579            0 :       mfem::Element *el = orig_mesh->GetElement(e)->Duplicate(new_mesh.get());
    1580            0 :       new_mesh->AddElement(el);
    1581              :     }
    1582              :   }
    1583            0 :   for (int be = 0; be < orig_mesh->GetNBE(); be++)
    1584              :   {
    1585            0 :     if (bdr_elem_delete_map[be] >= 0)
    1586              :     {
    1587            0 :       mfem::Element *bdr_el = orig_mesh->GetBdrElement(be)->Duplicate(new_mesh.get());
    1588            0 :       new_mesh->AddBdrElement(bdr_el);
    1589              :     }
    1590              :   }
    1591              : 
    1592              :   // Finalize the new mesh topology and replace the old mesh. If a curved mesh, set up the
    1593              :   // new mesh by projecting nodes onto the new mesh for the non-trimmed vdofs. No need to
    1594              :   // mark for refinement or fix orientations, since everything is copied from the previous
    1595              :   // mesh.
    1596              :   constexpr bool generate_bdr = false;
    1597            0 :   new_mesh->FinalizeTopology(generate_bdr);
    1598            0 :   new_mesh->RemoveUnusedVertices();  // Remove vertices from the deleted elements
    1599            0 :   if (orig_mesh->GetNodes())
    1600              :   {
    1601            0 :     TransferHighOrderNodes(*orig_mesh, *new_mesh, &elem_delete_map);
    1602              :   }
    1603              :   orig_mesh = std::move(new_mesh);
    1604              : }
    1605              : 
    1606            0 : void SplitMeshElements(std::unique_ptr<mfem::Mesh> &orig_mesh, bool make_simplex,
    1607              :                        bool make_hex)
    1608              : {
    1609            0 :   if (!make_simplex && !make_hex)
    1610              :   {
    1611            0 :     return;
    1612              :   }
    1613              :   mfem::Mesh *mesh = orig_mesh.get();
    1614            0 :   mfem::Mesh new_mesh;
    1615              : 
    1616              :   // Convert all element types to simplices.
    1617            0 :   if (make_simplex)
    1618              :   {
    1619            0 :     const auto element_types = mesh::CheckElements(*mesh);
    1620            0 :     if (element_types.has_hexahedra || element_types.has_prisms ||
    1621            0 :         element_types.has_pyramids)
    1622              :     {
    1623            0 :       MFEM_VERIFY(!mesh->Nonconforming(),
    1624              :                   "Mesh element splitting is not supported for nonconforming meshes!");
    1625            0 :       MFEM_VERIFY(
    1626              :           !element_types.has_pyramids,
    1627              :           "Splitting mesh elements to simplices does not support pyramid elements yet!");
    1628              :       int ne = mesh->GetNE();
    1629            0 :       new_mesh = mfem::Mesh::MakeSimplicial(*mesh);
    1630            0 :       Mpi::Print("Added {:d} elements to the mesh during conversion to simplices\n",
    1631            0 :                  new_mesh.GetNE() - ne);
    1632              :       mesh = &new_mesh;
    1633              :     }
    1634              :   }
    1635              : 
    1636              :   // Convert all element types to hexahedra (currently only tet-to-hex).
    1637            0 :   if (make_hex)
    1638              :   {
    1639            0 :     const auto element_types = mesh::CheckElements(*mesh);
    1640            0 :     if (element_types.has_simplices || element_types.has_prisms ||
    1641            0 :         element_types.has_pyramids)
    1642              :     {
    1643            0 :       MFEM_VERIFY(!mesh->Nonconforming(),
    1644              :                   "Mesh element splitting is not supported for nonconforming meshes!");
    1645            0 :       MFEM_VERIFY(!element_types.has_prisms && !element_types.has_pyramids,
    1646              :                   "Splitting mesh elements to hexahedra only supports simplex elements "
    1647              :                   "(tetrahedra) for now!");
    1648              :       int ne = mesh->GetNE();
    1649            0 :       new_mesh = mesh::MeshTetToHex(*mesh);
    1650            0 :       Mpi::Print("Added {:d} elements to the mesh during conversion to hexahedra\n",
    1651            0 :                  new_mesh.GetNE() - ne);
    1652              :       mesh = &new_mesh;
    1653              :     }
    1654              :   }
    1655              : 
    1656              :   // Return if no modifications were made.
    1657            0 :   if (mesh == orig_mesh.get())
    1658              :   {
    1659              :     return;
    1660              :   }
    1661            0 :   orig_mesh = std::make_unique<mfem::Mesh>(std::move(new_mesh));  // Call move constructor
    1662            0 :   orig_mesh->FinalizeTopology();
    1663            0 : }
    1664              : 
    1665            0 : void ReorderMeshElements(mfem::Mesh &mesh, bool print)
    1666              : {
    1667              :   mfem::Array<int> ordering;
    1668              :   if constexpr (false)
    1669              :   {
    1670              :     // Gecko reordering.
    1671              :     mfem::Array<int> tentative;
    1672              :     int outer = 3, inner = 3, window = 4, period = 2;
    1673              :     double best_cost = mfem::infinity();
    1674              :     for (int i = 0; i < outer; i++)
    1675              :     {
    1676              :       int seed = i + 1;
    1677              :       double cost =
    1678              :           mesh.GetGeckoElementOrdering(tentative, inner, window, period, seed, true);
    1679              :       if (cost < best_cost)
    1680              :       {
    1681              :         ordering = tentative;
    1682              :         best_cost = cost;
    1683              :       }
    1684              :     }
    1685              :     if (print)
    1686              :     {
    1687              :       Mpi::Print("Final cost: {:e}\n", best_cost);
    1688              :     }
    1689              :   }
    1690              :   else
    1691              :   {
    1692              :     // (Faster) Hilbert reordering.
    1693            0 :     mesh.GetHilbertElementOrdering(ordering);
    1694            0 :     mesh.ReorderElements(ordering);
    1695              :   }
    1696            0 : }
    1697              : 
    1698            8 : std::unordered_map<int, int> GetFaceToBdrElementMap(const mfem::Mesh &mesh,
    1699              :                                                     const config::BoundaryData &boundaries)
    1700              : {
    1701              :   std::unordered_map<int, int> face_to_be;
    1702            8 :   face_to_be.reserve(mesh.GetNBE());
    1703          392 :   for (int be = 0; be < mesh.GetNBE(); be++)
    1704              :   {
    1705          384 :     int f, o, e1 = -1, e2 = -1;
    1706          384 :     mesh.GetBdrElementFace(be, &f, &o);
    1707          384 :     int attr = mesh.GetBdrAttribute(be);
    1708          384 :     if (!boundaries.periodic.boundary_pairs.empty())
    1709              :     {
    1710            0 :       for (const auto &data : boundaries.periodic.boundary_pairs)
    1711              :       {
    1712              :         const auto &da = data.donor_attributes, &ra = data.receiver_attributes;
    1713            0 :         auto donor = std::find(da.begin(), da.end(), attr) != da.end();
    1714            0 :         auto receiver = std::find(ra.begin(), ra.end(), attr) != ra.end();
    1715            0 :         if (donor || receiver)
    1716              :         {
    1717            0 :           mesh.GetFaceElements(f, &e1, &e2);
    1718            0 :           MFEM_VERIFY(e1 >= 0 && e2 >= 0,
    1719              :                       "Mesh is not periodic on attribute " << attr << "!");
    1720              :         }
    1721              :       }
    1722              :     }
    1723          384 :     MFEM_VERIFY((e1 >= 0 && e2 >= 0) || face_to_be.find(f) == face_to_be.end(),
    1724              :                 "A non-periodic face ("
    1725              :                     << f << ") cannot have multiple boundary elements! Attributes: " << attr
    1726              :                     << ' ' << mesh.GetBdrAttribute(face_to_be[f]));
    1727          384 :     face_to_be[f] = be;
    1728              :   }
    1729            8 :   return face_to_be;
    1730              : }
    1731              : 
    1732            8 : std::unordered_map<int, int> CheckMesh(const mfem::Mesh &mesh,
    1733              :                                        const config::BoundaryData &boundaries)
    1734              : {
    1735              :   // Check for:
    1736              :   //   (1) Boundary elements with no prescribed boundary condition, and
    1737              :   //   (2) Boundary faces which have no boundary element.
    1738              :   auto bdr_marker =
    1739            8 :       mesh::AttrToMarker(mesh.bdr_attributes.Size() ? mesh.bdr_attributes.Max() : 0,
    1740           16 :                          boundaries.attributes, true);
    1741            8 :   std::unordered_map<int, int> face_to_be = GetFaceToBdrElementMap(mesh, boundaries);
    1742              :   std::unordered_set<int> bdr_warn_list;
    1743            8 :   int bdr_face_warn = 0;
    1744          872 :   for (int f = 0; f < mesh.GetNumFaces(); f++)
    1745              :   {
    1746              :     int e1, e2;
    1747          864 :     mesh.GetFaceElements(f, &e1, &e2);
    1748          864 :     if (e1 >= 0 && e2 >= 0)
    1749              :     {
    1750          480 :       continue;  // Only consider true exterior faces
    1751              :     }
    1752              :     auto it = face_to_be.find(f);
    1753          384 :     if (it != face_to_be.end())
    1754              :     {
    1755          384 :       int attr = mesh.GetBdrAttribute(it->second);
    1756          384 :       if (!bdr_marker[attr - 1])
    1757              :       {
    1758              :         // Boundary element with no prescribed boundary condition.
    1759              :         bdr_warn_list.insert(attr);
    1760              :       }
    1761              :     }
    1762              :     else
    1763              :     {
    1764              :       // Boundary face with no attached boundary element.
    1765            0 :       bdr_face_warn++;
    1766              :     }
    1767              :   }
    1768            8 :   if (!bdr_warn_list.empty())
    1769              :   {
    1770            8 :     Mpi::Warning("One or more external boundary attributes has no associated boundary "
    1771              :                  "condition!\n\"PMC\"/\"ZeroCharge\" condition is assumed!\n");
    1772           16 :     utils::PrettyPrint(bdr_warn_list, "Boundary attribute list:");
    1773            8 :     Mpi::Print("\n");
    1774              :   }
    1775            8 :   if (bdr_face_warn)
    1776              :   {
    1777            0 :     Mpi::Warning("{:d} mesh faces with no associated boundary element exist on the domain "
    1778              :                  "boundary!\n",
    1779              :                  bdr_face_warn);
    1780              :   }
    1781            8 :   return face_to_be;
    1782              : }
    1783              : 
    1784              : template <typename T>
    1785            0 : class EdgeRefinementMesh : public mfem::Mesh
    1786              : {
    1787              : private:
    1788              :   // Container with keys being pairs of vertex indicies (match row/column indices of v_to_v)
    1789              :   // of edges which we desire to be refined.
    1790              :   const T &refinement_edges;
    1791              : 
    1792            0 :   void MarkTetMeshForRefinement(const mfem::DSTable &v_to_v) override
    1793              :   {
    1794              :     // The standard tetrahedral refinement in MFEM is to mark the longest edge of each tet
    1795              :     // for the first refinement. We hack this marking here in order to prioritize refinement
    1796              :     // of edges which are part of internal boundary faces being marked for refinement. This
    1797              :     // should hopefully limit the amount of extra refinement required to ensure conformity
    1798              :     // after the marked elements are refined. Marking will then discover only the longest
    1799              :     // edges, which are those within the boundary to be cracked.
    1800              :     mfem::Array<mfem::real_t> lengths;
    1801            0 :     GetEdgeLengths2(v_to_v, lengths);
    1802            0 :     const auto min_length = 0.01 * lengths.Min();
    1803            0 :     for (int i = 0; i < v_to_v.NumberOfRows(); i++)
    1804              :     {
    1805            0 :       for (mfem::DSTable::RowIterator it(v_to_v, i); !it; ++it)
    1806              :       {
    1807              :         int j = it.Column();
    1808            0 :         if (refinement_edges.find({i, j}) == refinement_edges.end())
    1809              :         {
    1810              :           // "Zero" the edge lengths which do not connect vertices on the interface. Avoid
    1811              :           // zero-length edges just in case.
    1812            0 :           lengths[it.Index()] = min_length;
    1813              :         }
    1814              :       }
    1815              :     }
    1816              : 
    1817              :     // Finish marking (see mfem::Mesh::MarkTetMeshForRefinement).
    1818            0 :     mfem::Array<int> indices(NumOfEdges);
    1819              :     std::iota(indices.begin(), indices.end(), 0);
    1820            0 :     for (int i = 0; i < NumOfElements; i++)
    1821              :     {
    1822            0 :       if (elements[i]->GetType() == mfem::Element::TETRAHEDRON)
    1823              :       {
    1824              :         MFEM_ASSERT(dynamic_cast<mfem::Tetrahedron *>(elements[i]),
    1825              :                     "Unexpected non-Tetrahedron element type!");
    1826            0 :         static_cast<mfem::Tetrahedron *>(elements[i])->MarkEdge(v_to_v, lengths, indices);
    1827              :       }
    1828              :     }
    1829            0 :     for (int i = 0; i < NumOfBdrElements; i++)
    1830              :     {
    1831            0 :       if (boundary[i]->GetType() == mfem::Element::TRIANGLE)
    1832              :       {
    1833              :         MFEM_ASSERT(dynamic_cast<mfem::Triangle *>(boundary[i]),
    1834              :                     "Unexpected non-Triangle element type!");
    1835            0 :         static_cast<mfem::Triangle *>(boundary[i])->MarkEdge(v_to_v, lengths, indices);
    1836              :       }
    1837              :     }
    1838            0 :   }
    1839              : 
    1840              : public:
    1841            0 :   EdgeRefinementMesh(mfem::Mesh &&mesh, const T &refinement_edges)
    1842            0 :     : mfem::Mesh(std::move(mesh)), refinement_edges(refinement_edges)
    1843              :   {
    1844              :   }
    1845              : };
    1846              : 
    1847              : template <typename T>
    1848              : struct UnorderedPair
    1849              : {
    1850              :   T first, second;
    1851            0 :   UnorderedPair(T first, T second) : first(first), second(second) {}
    1852              :   bool operator==(const UnorderedPair &v) const
    1853              :   {
    1854            0 :     return ((v.first == first && v.second == second) ||
    1855            0 :             (v.first == second && v.second == first));
    1856              :   }
    1857              : };
    1858              : 
    1859              : template <typename T>
    1860              : struct UnorderedPairHasher
    1861              : {
    1862              :   std::size_t operator()(const UnorderedPair<T> &v) const
    1863              :   {
    1864              :     // Simple hash function for a pair, see https://tinyurl.com/2k4phapb.
    1865              :     return std::hash<T>()(std::min(v.first, v.second)) ^
    1866            0 :            std::hash<T>()(std::max(v.first, v.second)) << 1;
    1867              :   }
    1868              : };
    1869              : 
    1870            8 : int AddInterfaceBdrElements(const IoData &iodata, std::unique_ptr<mfem::Mesh> &orig_mesh,
    1871              :                             std::unordered_map<int, int> &face_to_be, MPI_Comm comm)
    1872              : {
    1873              :   // Exclude some internal boundary conditions for which cracking would give invalid
    1874              :   // results: lumpedports in particular.
    1875            8 :   const auto crack_boundary_attributes = [&iodata]()
    1876              :   {
    1877            8 :     auto cba = iodata.boundaries.attributes;
    1878           32 :     for (const auto &[idx, data] : iodata.boundaries.lumpedport)
    1879              :     {
    1880           48 :       for (const auto &e : data.elements)
    1881              :       {
    1882          144 :         auto attr_in_elem = [&](auto x)
    1883              :         {
    1884          144 :           return std::find(e.attributes.begin(), e.attributes.end(), x) !=
    1885          144 :                  e.attributes.end();
    1886              :         };
    1887           24 :         cba.erase(std::remove_if(cba.begin(), cba.end(), attr_in_elem), cba.end());
    1888              :       }
    1889              :     }
    1890            8 :     return cba;
    1891            8 :   }();
    1892              : 
    1893              :   // Return if nothing to do. Otherwise, count vertices and boundary elements to add.
    1894            8 :   if (crack_boundary_attributes.empty() && !iodata.model.add_bdr_elements)
    1895              :   {
    1896              :     return 1;  // Success
    1897              :   }
    1898              : 
    1899            8 :   if (face_to_be.size() != static_cast<std::size_t>(orig_mesh->GetNBE()))
    1900              :   {
    1901            0 :     face_to_be = GetFaceToBdrElementMap(*orig_mesh, iodata.boundaries);
    1902              :   }
    1903            8 :   int new_nv = orig_mesh->GetNV();
    1904            8 :   int new_nbe = orig_mesh->GetNBE();
    1905              : 
    1906              :   // Duplicate internal boundary elements from the given boundary attribute list, cracking
    1907              :   // the mesh such that domain elements on either side are no longer coupled. Correctly
    1908              :   // handles cracks where more than two domains intersect as well as seams where a crack
    1909              :   // ends and the vertices are not duplicated.
    1910              :   std::unordered_set<int> crack_bdr_elem;
    1911              :   std::unordered_map<int, std::vector<std::pair<int, std::unordered_set<int>>>>
    1912              :       crack_vert_duplicates;
    1913            8 :   std::unique_ptr<mfem::Table> vert_to_elem;
    1914            8 :   if (!crack_boundary_attributes.empty() && iodata.model.crack_bdr_elements)
    1915              :   {
    1916              :     auto crack_bdr_marker = mesh::AttrToMarker(
    1917            0 :         orig_mesh->bdr_attributes.Size() ? orig_mesh->bdr_attributes.Max() : 0,
    1918            0 :         crack_boundary_attributes, true);
    1919            0 :     for (int be = 0; be < orig_mesh->GetNBE(); be++)
    1920              :     {
    1921            0 :       if (crack_bdr_marker[orig_mesh->GetBdrAttribute(be) - 1])
    1922              :       {
    1923              :         int f, o, e1, e2;
    1924            0 :         orig_mesh->GetBdrElementFace(be, &f, &o);
    1925            0 :         orig_mesh->GetFaceElements(f, &e1, &e2);
    1926            0 :         if (e1 >= 0 && e2 >= 0)
    1927              :         {
    1928              :           crack_bdr_elem.insert(be);
    1929              :         }
    1930              :       }
    1931              :     }
    1932            0 :     MFEM_VERIFY(crack_bdr_elem.empty() || !orig_mesh->Nonconforming(),
    1933              :                 "Duplicating internal boundary elements for interior boundaries is not "
    1934              :                 "supported for nonconforming meshes!");
    1935              : 
    1936            0 :     vert_to_elem.reset(orig_mesh->GetVertexToElementTable());  // Owned by caller
    1937            0 :     const mfem::Table &elem_to_face = orig_mesh->ElementToFaceTable();
    1938            0 :     int new_nv_dups = 0;
    1939            0 :     for (auto be : crack_bdr_elem)
    1940              :     {
    1941              :       const mfem::Element *bdr_el = orig_mesh->GetBdrElement(be);
    1942              :       const int *verts = bdr_el->GetVertices();
    1943            0 :       for (int i = 0; i < bdr_el->GetNVertices(); i++)
    1944              :       {
    1945              :         // Skip vertices we have already processed.
    1946            0 :         const auto v = verts[i];
    1947            0 :         if (crack_vert_duplicates.find(v) != crack_vert_duplicates.end())
    1948              :         {
    1949            0 :           continue;
    1950              :         }
    1951              : 
    1952              :         // Collect connected components of elements connected to the vertex. Perform BFS
    1953              :         // on graph of all elements connected to this vertex, where adjacencies are
    1954              :         // determined by face connectivity excluding the crack faces.
    1955              :         std::vector<std::unordered_set<int>> components;
    1956              :         const int *elems = vert_to_elem->GetRow(v);
    1957            0 :         std::unordered_set<int> unvisited(elems, elems + vert_to_elem->RowSize(v));
    1958            0 :         while (!unvisited.empty())
    1959              :         {
    1960            0 :           auto &component = components.emplace_back();
    1961              :           component.reserve(unvisited.size());
    1962              :           std::queue<int> que;
    1963              :           que.push(*unvisited.begin());
    1964              :           unvisited.erase(unvisited.begin());
    1965            0 :           while (!que.empty())
    1966              :           {
    1967              :             // Process the current node.
    1968            0 :             int e = que.front();
    1969              :             que.pop();
    1970              :             component.insert(e);
    1971              : 
    1972              :             // Add neighbors.
    1973              :             const int *faces = elem_to_face.GetRow(e);
    1974            0 :             for (int j = 0; j < elem_to_face.RowSize(e); j++)
    1975              :             {
    1976            0 :               const auto f = faces[j];
    1977              :               {
    1978              :                 auto it = face_to_be.find(f);
    1979            0 :                 if (it != face_to_be.end() &&
    1980            0 :                     crack_bdr_elem.find(it->second) != crack_bdr_elem.end())
    1981              :                 {
    1982              :                   // Skip element-element connectivities which cross the crack.
    1983              :                   continue;
    1984              :                 }
    1985              :               }
    1986              :               int e1, e2;
    1987            0 :               orig_mesh->GetFaceElements(f, &e1, &e2);
    1988            0 :               MFEM_VERIFY(
    1989              :                   e == e1 || e == e2,
    1990              :                   "Unexpected face-element connectivity in internal boundary cracking!");
    1991            0 :               int nbr = (e == e1) ? e2 : e1;
    1992            0 :               if (nbr >= 0)
    1993              :               {
    1994              :                 auto it = unvisited.find(nbr);
    1995            0 :                 if (it != unvisited.end())
    1996              :                 {
    1997              :                   que.push(nbr);
    1998              :                   unvisited.erase(it);
    1999              :                 }
    2000              :               }
    2001              :             }
    2002              :           }
    2003              :         }
    2004            0 :         MFEM_VERIFY(
    2005              :             !components.empty(),
    2006              :             "No connected components found for elements adjacent to a crack vertex!");
    2007              : #if defined(MFEM_DEBUG)
    2008              :         {
    2009              :           std::size_t visited_size = 0;
    2010              :           for (const auto &component : components)
    2011              :           {
    2012              :             visited_size += component.size();
    2013              :           }
    2014              :           MFEM_VERIFY(visited_size == static_cast<std::size_t>(vert_to_elem->RowSize(v)),
    2015              :                       "Failed to visit all elements in neighborhood of vertex when "
    2016              :                       "counting connected components!");
    2017              :         }
    2018              : #endif
    2019              : 
    2020              :         // Save mapping from original vertex to duplicate vertices, and the corresponding
    2021              :         // element groupings requiring renumbering. The first group doesn't need
    2022              :         // renumbering so is not saved. We still keep entries for non-duplicated crack
    2023              :         // vertices in the set to track them as processed, however.
    2024            0 :         auto &vert_components = crack_vert_duplicates.try_emplace(v).first->second;
    2025            0 :         for (auto it = components.begin() + 1; it != components.end(); ++it)
    2026              :         {
    2027            0 :           vert_components.emplace_back(-1, std::move(*it));
    2028            0 :           new_nv_dups++;
    2029              :         }
    2030            0 :       }
    2031              :     }
    2032              : 
    2033              :     // After processing all boundary elements, check if there are any elements which need
    2034              :     // refinement in order to successfully decouple both sides. This happens if we have an
    2035              :     // edge interior to the crack which connects to seam vertices (non-duplicated vertices
    2036              :     // attached to crack boundary elements). A previous implementation of refinement
    2037              :     // considered for refinement just all boundary elements with all attached vertices
    2038              :     // lying on the seam, but this doesn't catch all required cases.
    2039            0 :     if (iodata.model.refine_crack_elements)
    2040              :     {
    2041              :       std::unordered_map<UnorderedPair<int>, std::vector<int>, UnorderedPairHasher<int>>
    2042              :           coarse_crack_edge_to_be;
    2043            0 :       for (auto be : crack_bdr_elem)
    2044              :       {
    2045              :         const mfem::Element *bdr_el = orig_mesh->GetBdrElement(be);
    2046              :         const int *verts = bdr_el->GetVertices();
    2047            0 :         for (int i = 0; i < bdr_el->GetNEdges(); i++)
    2048              :         {
    2049            0 :           auto v0 = verts[bdr_el->GetEdgeVertices(i)[0]],
    2050            0 :                v1 = verts[bdr_el->GetEdgeVertices(i)[1]];
    2051              :           MFEM_ASSERT(crack_vert_duplicates.find(v0) != crack_vert_duplicates.end() &&
    2052              :                           crack_vert_duplicates.find(v1) != crack_vert_duplicates.end(),
    2053              :                       "Unable to locate crack vertices for an interior boundary element!");
    2054            0 :           if (crack_vert_duplicates[v0].empty() && crack_vert_duplicates[v1].empty())
    2055              :           {
    2056              :             // This is a seam edge, so add the attached boundary element to a list. The
    2057              :             // check for the edge being interior to the crack is indicated by visiting more
    2058              :             // than once.
    2059            0 :             auto it = coarse_crack_edge_to_be.find({v0, v1});
    2060              :             auto &adjacent_be =
    2061              :                 (it == coarse_crack_edge_to_be.end())
    2062            0 :                     ? coarse_crack_edge_to_be.try_emplace({v0, v1}).first->second
    2063            0 :                     : it->second;
    2064              :             adjacent_be.push_back(be);
    2065              :           }
    2066              :         }
    2067              :       }
    2068            0 :       for (auto it = coarse_crack_edge_to_be.begin(); it != coarse_crack_edge_to_be.end();)
    2069              :       {
    2070              :         // Remove all seam edges which are on the "outside" of the crack (visited only
    2071              :         // once).
    2072            0 :         if (it->second.size() == 1)
    2073              :         {
    2074              :           it = coarse_crack_edge_to_be.erase(it);
    2075              :         }
    2076              :         else
    2077              :         {
    2078              :           ++it;
    2079              :         }
    2080              :       }
    2081              :       // Static reporting variables so can persist across retries.
    2082              :       static int new_ne_ref = 0;
    2083              :       static int new_ref_its = 0;
    2084            0 :       if (!coarse_crack_edge_to_be.empty())
    2085              :       {
    2086              :         // Locally refine the mesh using conformal refinement. If necessary, convert the
    2087              :         // mesh to simplices first to enable conforming refinement (this will do nothing
    2088              :         // if the mesh is already a simplex mesh).
    2089              :         // Note: Eventually we can implement manual conforming face refinement of pairs of
    2090              :         // elements sharing a face for all element types (insert a vertex at the boundary
    2091              :         // element center and connect it to all other element vertices). For now, this adds
    2092              :         // complexity and making use of conformal simplex refinement seems good enough for
    2093              :         // most use cases.
    2094              :         int ne = orig_mesh->GetNE();
    2095            0 :         SplitMeshElements(orig_mesh, true, false);
    2096            0 :         if (ne != orig_mesh->GetNE())
    2097              :         {
    2098              :           face_to_be.clear();
    2099            0 :           return 0;  // Mesh was converted to simplices, start over
    2100              :         }
    2101              :         std::unordered_map<int, int> elem_to_refine;
    2102            0 :         for (const auto &[edge, adjacent_be] : coarse_crack_edge_to_be)
    2103              :         {
    2104            0 :           for (auto be : adjacent_be)
    2105              :           {
    2106              :             int f, o, e1, e2;
    2107            0 :             orig_mesh->GetBdrElementFace(be, &f, &o);
    2108            0 :             orig_mesh->GetFaceElements(f, &e1, &e2);
    2109              :             MFEM_ASSERT(e1 >= 0 && e2 >= 0,
    2110              :                         "Invalid internal boundary element connectivity!");
    2111            0 :             elem_to_refine[e1]++;  // Value-initialized to 0 at first access
    2112            0 :             elem_to_refine[e2]++;
    2113              :           }
    2114              :         }
    2115              :         mfem::Array<mfem::Refinement> refinements;
    2116            0 :         refinements.Reserve(elem_to_refine.size());
    2117            0 :         for (const auto &[e, count] : elem_to_refine)
    2118              :         {
    2119              :           // Tetrahedral bisection (vs. default octasection) will result in fewer added
    2120              :           // elements at the cost of a potential minor mesh quality degredation.
    2121            0 :           refinements.Append(mfem::Refinement(e, mfem::Refinement::X));
    2122              :           // refinements.Append(mfem::Refinement(e, (count > 1) ? mfem::Refinement::XY
    2123              :           //                                                    : mfem::Refinement::X));
    2124              :         }
    2125            0 :         if (mesh::CheckElements(*orig_mesh).has_simplices)
    2126              :         {
    2127              :           // Mark tetrahedral mesh for refinement before doing local refinement. This is a
    2128              :           // bit of a strange pattern to override the standard conforming refinement of the
    2129              :           // mfem::Mesh class. We want to implement our own edge marking of the tetrahedra,
    2130              :           // so we move the mesh to a constructed derived class object, mark it, and then
    2131              :           // move assign it to the original base class object before refining. All of these
    2132              :           // moves should be cheap without any extra memory allocation. Also, we mark the
    2133              :           // mesh every time to ensure multiple rounds of refinement target the interior
    2134              :           // boundary (we don't care about preserving the refinement hierarchy).
    2135              :           constexpr bool refine = true, fix_orientation = false;
    2136              :           EdgeRefinementMesh ref_mesh(std::move(*orig_mesh), coarse_crack_edge_to_be);
    2137            0 :           ref_mesh.Finalize(refine, fix_orientation);
    2138            0 :           *orig_mesh = std::move(ref_mesh);
    2139              :         }
    2140            0 :         orig_mesh->GeneralRefinement(refinements, 0);
    2141            0 :         new_ne_ref += orig_mesh->GetNE() - ne;
    2142            0 :         new_ref_its++;
    2143              :         face_to_be.clear();
    2144              :         return 0;  // Mesh was refined (conformally), start over
    2145              :       }
    2146            0 :       else if (new_ne_ref > 0)
    2147              :       {
    2148            0 :         Mpi::Print(
    2149              :             "Added {:d} elements in {:d} iterations of local bisection for under-resolved "
    2150              :             "interior boundaries\n",
    2151              :             new_ne_ref, new_ref_its);
    2152              :       }
    2153              :     }
    2154              : 
    2155            0 :     new_nv += new_nv_dups;
    2156            0 :     new_nbe += crack_bdr_elem.size();
    2157            0 :     if (crack_bdr_elem.size() > 0)
    2158              :     {
    2159            0 :       Mpi::Print("Added {:d} duplicate vertices for interior boundaries in the mesh\n",
    2160              :                  new_nv_dups);
    2161            0 :       Mpi::Print(
    2162              :           "Added {:d} duplicate boundary elements for interior boundaries in the mesh\n",
    2163            0 :           crack_bdr_elem.size());
    2164              :     }
    2165              :   }
    2166              : 
    2167              :   // Add new boundary elements at material interfaces or on the exterior boundary of the
    2168              :   // simulation domain, if there is not already a boundary element present.
    2169              :   std::unordered_map<int, int> new_face_bdr_elem;
    2170            8 :   if (iodata.model.add_bdr_elements)
    2171              :   {
    2172            8 :     int new_nbe_ext = 0, new_nbe_int = 0;
    2173          872 :     for (int f = 0; f < orig_mesh->GetNumFaces(); f++)
    2174              :     {
    2175              :       // Skip all faces which already have an associated boundary element (this includes
    2176              :       // any boundary elements which were duplicated during cracking in the previous step).
    2177          864 :       if (face_to_be.find(f) != face_to_be.end())
    2178              :       {
    2179          384 :         continue;
    2180              :       }
    2181              :       int e1, e2;
    2182          480 :       orig_mesh->GetFaceElements(f, &e1, &e2);
    2183          480 :       if (e1 < 0 || e2 < 0)
    2184              :       {
    2185              :         if constexpr (false)
    2186              :         {
    2187              :           Mpi::Print("Adding exterior boundary element!\n");
    2188              :         }
    2189            0 :         new_face_bdr_elem[f] = 1;
    2190            0 :         new_nbe_ext++;
    2191              :       }
    2192          480 :       else if (orig_mesh->GetAttribute(e1) != orig_mesh->GetAttribute(e2))
    2193              :       {
    2194              :         if constexpr (false)
    2195              :         {
    2196              :           Mpi::Print("Adding material interface boundary element!\n");
    2197              :         }
    2198            0 :         new_face_bdr_elem[f] = 1;
    2199            0 :         new_nbe_int++;
    2200              :       }
    2201              :     }
    2202            8 :     MFEM_VERIFY(new_nbe_ext + new_nbe_int == 0 || !orig_mesh->Nonconforming(),
    2203              :                 "Adding material interface boundary elements is not supported for "
    2204              :                 "nonconforming meshes!");
    2205              : 
    2206            8 :     new_nbe += (new_nbe_ext + new_nbe_int);
    2207            8 :     if (new_nbe_ext > 0)
    2208              :     {
    2209            0 :       Mpi::Print("Added {:d} boundary elements for exterior boundaries to the mesh\n",
    2210              :                  new_nbe_ext);
    2211              :     }
    2212            8 :     if (new_nbe_int > 0)
    2213              :     {
    2214            0 :       Mpi::Print("Added {:d} boundary elements for material interfaces to the mesh\n",
    2215              :                  new_nbe_int);
    2216              :     }
    2217              :   }
    2218              : 
    2219              :   // Export mesh after pre-processing, before cracking boundary elements.
    2220            8 :   if (iodata.model.export_prerefined_mesh && Mpi::Root(comm))
    2221              :   {
    2222            0 :     auto pos = iodata.model.mesh.find_last_of(".");
    2223            0 :     std::string meshfile = iodata.model.mesh.substr(0, pos) + "_preprocessed.mesh";
    2224            0 :     std::ofstream fo(meshfile);
    2225              :     fo.precision(MSH_FLT_PRECISION);
    2226            0 :     orig_mesh->Print(fo);
    2227            0 :   }
    2228              : 
    2229              :   // Create the new mesh. We can't just add the new vertices and boundary elements to the
    2230              :   // original mesh, because we need to keep it around in order to transfer the high-order
    2231              :   // nodes information to the new mesh.
    2232            8 :   if (new_nv == orig_mesh->GetNV() && new_nbe == orig_mesh->GetNBE())
    2233              :   {
    2234              :     return 1;  // Success
    2235              :   }
    2236              :   auto new_mesh =
    2237            0 :       std::make_unique<mfem::Mesh>(orig_mesh->Dimension(), new_nv, orig_mesh->GetNE(),
    2238            0 :                                    new_nbe, orig_mesh->SpaceDimension());
    2239              : 
    2240              :   // Copy vertices and domain and boundary elements.
    2241            0 :   for (int v = 0; v < orig_mesh->GetNV(); v++)
    2242              :   {
    2243            0 :     new_mesh->AddVertex(orig_mesh->GetVertex(v));
    2244              :   }
    2245            0 :   for (int e = 0; e < orig_mesh->GetNE(); e++)
    2246              :   {
    2247            0 :     mfem::Element *el = orig_mesh->GetElement(e)->Duplicate(new_mesh.get());
    2248            0 :     new_mesh->AddElement(el);
    2249              :   }
    2250            0 :   for (int be = 0; be < orig_mesh->GetNBE(); be++)
    2251              :   {
    2252            0 :     mfem::Element *bdr_el = orig_mesh->GetBdrElement(be)->Duplicate(new_mesh.get());
    2253            0 :     new_mesh->AddBdrElement(bdr_el);
    2254              :   }
    2255              : 
    2256              :   // Add duplicated vertices from interior boundary cracking, renumber the vertices of
    2257              :   // domain and boundary elements to tear the mesh, and add new crack boundary elements.
    2258            0 :   if (!crack_boundary_attributes.empty() && !crack_bdr_elem.empty())
    2259              :   {
    2260              :     // Add duplicate vertices. We assign the vertex number of the duplicated vertex in order
    2261              :     // to update the element connectivities in the next step.
    2262            0 :     for (auto &[orig_v, vert_components] : crack_vert_duplicates)
    2263              :     {
    2264            0 :       for (auto &[dup_v, component] : vert_components)
    2265              :       {
    2266            0 :         dup_v = new_mesh->AddVertex(orig_mesh->GetVertex(orig_v));
    2267              :       }
    2268              :     }
    2269              : 
    2270              :     // Renumber the duplicated vertex in the domain elements.
    2271            0 :     for (const auto &[orig_v, vert_components] : crack_vert_duplicates)
    2272              :     {
    2273            0 :       if (vert_components.empty())
    2274              :       {
    2275            0 :         continue;  // Can skip vertices which were not duplicated
    2276              :       }
    2277            0 :       const int *elems = vert_to_elem->GetRow(orig_v);
    2278            0 :       for (int i = 0; i < vert_to_elem->RowSize(orig_v); i++)
    2279              :       {
    2280              :         // Find vertex in the element.
    2281            0 :         const auto e = elems[i];
    2282              :         mfem::Element *el = new_mesh->GetElement(e);
    2283            0 :         int *verts = el->GetVertices(), j;
    2284            0 :         for (j = 0; j < el->GetNVertices(); j++)
    2285              :         {
    2286            0 :           if (verts[j] == orig_v)
    2287              :           {
    2288              :             break;
    2289              :           }
    2290              :         }
    2291            0 :         MFEM_VERIFY(j < el->GetNVertices(), "Unable to locate vertex in element!");
    2292              : 
    2293              :         // Find the correct duplicate for this vertex. It's OK if the element is not in
    2294              :         // any of the connected components, this indicates that it keeps the original
    2295              :         // vertex and its connectivity is unmodified.
    2296            0 :         for (const auto &[dup_v, component] : vert_components)
    2297              :         {
    2298            0 :           if (component.find(e) != component.end())
    2299              :           {
    2300            0 :             verts[j] = dup_v;
    2301            0 :             break;
    2302              :           }
    2303              :         }
    2304              :       }
    2305              :     }
    2306              : 
    2307              :     // Finally, we insert the new duplicate boundary elements for the crack interface and
    2308              :     // also renumber the original boundary elements. To renumber the original boundary
    2309              :     // elements in the mesh, we use the updated vertex connectivity from the torn elements
    2310              :     // in the new mesh (done above).
    2311            0 :     const mfem::Table &elem_to_face = orig_mesh->ElementToFaceTable();
    2312            0 :     for (int be = 0; be < orig_mesh->GetNBE(); be++)
    2313              :     {
    2314              :       // Whether on the crack or not, we renumber the boundary element vertices as needed
    2315              :       // based on the neighboring element. For non-crack boundary elements, both
    2316              :       // neighboring elements must be part of the same connected component. First we find
    2317              :       // the index of the face in the old element, which should match the new element.
    2318              :       int f, o, e1, e2;
    2319            0 :       orig_mesh->GetBdrElementFace(be, &f, &o);
    2320            0 :       orig_mesh->GetFaceElements(f, &e1, &e2);
    2321            0 :       MFEM_VERIFY(e1 >= 0, "Boundary element with no attached elements!");
    2322              :       const int *faces = elem_to_face.GetRow(e1);
    2323              :       int i;
    2324            0 :       for (i = 0; i < elem_to_face.RowSize(e1); i++)
    2325              :       {
    2326            0 :         if (faces[i] == f)
    2327              :         {
    2328              :           break;
    2329              :         }
    2330              :       }
    2331            0 :       MFEM_VERIFY(i < elem_to_face.RowSize(e1), "Unable to locate face in element!");
    2332              : 
    2333              :       // Update the boundary element vertices.
    2334              :       mfem::Element *bdr_el = new_mesh->GetBdrElement(be);
    2335              :       const mfem::Element *el = new_mesh->GetElement(e1);
    2336            0 :       for (int j = 0; j < bdr_el->GetNVertices(); j++)
    2337              :       {
    2338            0 :         bdr_el->GetVertices()[j] = el->GetVertices()[el->GetFaceVertices(i)[j]];
    2339              :       }
    2340              : 
    2341              :       // Add the duplicate boundary element for boundary elements on the crack.
    2342            0 :       if (crack_bdr_elem.find(be) != crack_bdr_elem.end())
    2343              :       {
    2344            0 :         faces = elem_to_face.GetRow(e2);
    2345            0 :         for (i = 0; i < elem_to_face.RowSize(e2); i++)
    2346              :         {
    2347            0 :           if (faces[i] == f)
    2348              :           {
    2349              :             break;
    2350              :           }
    2351              :         }
    2352            0 :         MFEM_VERIFY(i < elem_to_face.RowSize(e2), "Unable to locate face in element!");
    2353              : 
    2354              :         // Add the interface boundary element attached to element 2 (the other part of the
    2355              :         // pair has been attached to element 1 in the previous step).
    2356            0 :         bdr_el = bdr_el->Duplicate(new_mesh.get());
    2357            0 :         el = new_mesh->GetElement(e2);
    2358            0 :         for (int j = 0; j < bdr_el->GetNVertices(); j++)
    2359              :         {
    2360            0 :           bdr_el->GetVertices()[j] = el->GetVertices()[el->GetFaceVertices(i)[j]];
    2361              :         }
    2362            0 :         new_mesh->AddBdrElement(bdr_el);
    2363              :       }
    2364              :     }
    2365              :   }
    2366              : 
    2367              :   // Add new boundary elements.
    2368            0 :   if (iodata.model.add_bdr_elements && !new_face_bdr_elem.empty())
    2369              :   {
    2370              :     // Some (1-based) boundary attributes may be empty since they were removed from the
    2371              :     // original mesh, but to keep attributes the same as config file we don't compress the
    2372              :     // list.
    2373            0 :     const mfem::Table &elem_to_face = orig_mesh->ElementToFaceTable();
    2374              :     int bdr_attr_max =
    2375            0 :         orig_mesh->bdr_attributes.Size() ? orig_mesh->bdr_attributes.Max() : 0;
    2376            0 :     for (int f = 0; f < orig_mesh->GetNumFaces(); f++)
    2377              :     {
    2378            0 :       if (new_face_bdr_elem[f] > 0)
    2379              :       {
    2380              :         // Assign new unique attribute based on attached elements. Save so that the
    2381              :         // attributes of e1 and e2 can be easily referenced using the new attribute. Since
    2382              :         // attributes are in 1-based indexing, a, b > 0. See also
    2383              :         // https://en.wikipedia.org/wiki/Pairing_function.
    2384              :         int e1, e2, a = 0, b = 0;
    2385            0 :         orig_mesh->GetFaceElements(f, &e1, &e2);
    2386            0 :         if (e1 >= 0 && e2 >= 0)
    2387              :         {
    2388              :           a = std::max(orig_mesh->GetAttribute(e1), orig_mesh->GetAttribute(e2));
    2389            0 :           b = (a == orig_mesh->GetAttribute(e1)) ? orig_mesh->GetAttribute(e2)
    2390              :                                                  : orig_mesh->GetAttribute(e1);
    2391              :         }
    2392              :         else  // e1 >= 0
    2393              :         {
    2394              :           a = orig_mesh->GetAttribute(e1);
    2395              :           b = 0;
    2396              :         }
    2397            0 :         MFEM_VERIFY(a + b > 0, "Invalid new boundary element attribute!");
    2398            0 :         long long int l_new_attr =
    2399            0 :             bdr_attr_max + (((a + b) * (long long int)(a + b + 1)) / 2) + a;
    2400              :         int new_attr = mfem::internal::to_int(l_new_attr);  // At least bdr_attr_max + 1
    2401              : 
    2402              :         // Add the boundary elements with the new boundary attribute. The element vertices
    2403              :         // may have been renumbered in the new mesh, so the new face is not necessarily
    2404              :         // just a duplicate of the old one. First we find the index of the face in the old
    2405              :         // element, which should match the new element.
    2406              :         const int *faces = elem_to_face.GetRow(e1);
    2407              :         int i;
    2408            0 :         for (i = 0; i < elem_to_face.RowSize(e1); i++)
    2409              :         {
    2410            0 :           if (faces[i] == f)
    2411              :           {
    2412              :             break;
    2413              :           }
    2414              :         }
    2415            0 :         MFEM_VERIFY(i < elem_to_face.RowSize(e1), "Unable to locate face in element!");
    2416              : 
    2417              :         // Now add the boundary element(s).
    2418            0 :         mfem::Element *bdr_el = orig_mesh->GetFace(f)->Duplicate(new_mesh.get());
    2419              :         bdr_el->SetAttribute(new_attr);
    2420            0 :         const mfem::Element *el = new_mesh->GetElement(e1);
    2421            0 :         for (int j = 0; j < bdr_el->GetNVertices(); j++)
    2422              :         {
    2423            0 :           bdr_el->GetVertices()[j] = el->GetVertices()[el->GetFaceVertices(i)[j]];
    2424              :         }
    2425            0 :         new_mesh->AddBdrElement(bdr_el);
    2426              :         if constexpr (false)
    2427              :         {
    2428              :           Mpi::Print(
    2429              :               "Adding boundary element with attribute {:d} from elements {:d} and {:d}\n",
    2430              :               new_attr, a, b);
    2431              :         }
    2432            0 :         if (new_face_bdr_elem[f] > 1)
    2433              :         {
    2434              :           // Flip order of vertices to reverse normal direction of the second added element.
    2435            0 :           bdr_el = bdr_el->Duplicate(new_mesh.get());
    2436            0 :           std::reverse(bdr_el->GetVertices(),
    2437            0 :                        bdr_el->GetVertices() + bdr_el->GetNVertices());
    2438            0 :           new_mesh->AddBdrElement(bdr_el);
    2439              :           if constexpr (false)
    2440              :           {
    2441              :             Mpi::Print("Adding second boundary element with attribute {:d} from elements "
    2442              :                        "{:d} and {:d}\n",
    2443              :                        new_attr, a, b);
    2444              :           }
    2445              :         }
    2446              :       }
    2447              :     }
    2448              :   }
    2449              : 
    2450              :   // Finalize the new mesh topology, and copy mesh curvature information if needed. This
    2451              :   // copies the nodes over correctly accounting for the element topology changes (the number
    2452              :   // of elements in the mesh has not changed, just their connectivity has).
    2453              :   constexpr bool generate_bdr = false;
    2454            0 :   new_mesh->FinalizeTopology(generate_bdr);
    2455            0 :   if (orig_mesh->GetNodes())
    2456              :   {
    2457            0 :     TransferHighOrderNodes(*orig_mesh, *new_mesh);
    2458              :   }
    2459              : 
    2460              :   // If we have added cracks for interior boundary elements, apply a very very small
    2461              :   // perturbation to separate the duplicated boundary elements on either side and prevent
    2462              :   // them from lying exactly on top of each other. This is mostly just for visualization
    2463              :   // and can be increased in magnitude for debugging.
    2464            0 :   if (!crack_boundary_attributes.empty() && !crack_bdr_elem.empty() &&
    2465            0 :       iodata.model.crack_displ_factor > 0.0)
    2466              :   {
    2467              :     // mfem::Mesh::MoveNodes expects byNODES ordering when using vertices.
    2468              :     mfem::GridFunction *nodes = new_mesh->GetNodes();
    2469              :     mfem::Ordering::Type ordering =
    2470            0 :         nodes ? nodes->FESpace()->GetOrdering() : mfem::Ordering::byNODES;
    2471            0 :     int sdim = new_mesh->SpaceDimension();
    2472            0 :     int nv = nodes ? nodes->Size() / sdim : new_mesh->GetNV();
    2473              :     auto Index = [ordering, sdim, nv](int v, int d)
    2474            0 :     { return (ordering == mfem::Ordering::byVDIM) ? sdim * v + d : d * nv + v; };
    2475              :     mfem::Vector normal(sdim);
    2476            0 :     mfem::IsoparametricTransformation T;
    2477              :     mfem::Array<int> dofs;
    2478              : 
    2479              :     // Compute the displacement as the average normal of the attached boundary elements.
    2480            0 :     mfem::Vector displacements(nv * sdim);
    2481            0 :     displacements = 0.0;
    2482            0 :     double h_min = mfem::infinity();
    2483            0 :     const mfem::Table &elem_to_face = orig_mesh->ElementToFaceTable();
    2484            0 :     const mfem::Table &new_elem_to_face = new_mesh->ElementToFaceTable();
    2485            0 :     for (auto be : crack_bdr_elem)
    2486              :     {
    2487              :       // Get the neighboring elements (same indices in the old and new mesh).
    2488              :       int f, o, e1, e2;
    2489            0 :       orig_mesh->GetBdrElementFace(be, &f, &o);
    2490            0 :       orig_mesh->GetFaceElements(f, &e1, &e2);
    2491              : 
    2492              :       // Perturb both new boundary elements in opposite directions.
    2493            0 :       for (auto e : {e1, e2})
    2494              :       {
    2495              :         // Find the index of the face in the old element, which matches the new element, so
    2496              :         // we can get the list of all vertices or nodes to perturb.
    2497              :         const int *faces = elem_to_face.GetRow(e);
    2498              :         int i;
    2499            0 :         for (i = 0; i < elem_to_face.RowSize(e); i++)
    2500              :         {
    2501            0 :           if (faces[i] == f)
    2502              :           {
    2503              :             break;
    2504              :           }
    2505              :         }
    2506            0 :         MFEM_VERIFY(i < elem_to_face.RowSize(e), "Unable to locate face in element!");
    2507              : 
    2508              :         // Compute the element normal, oriented to point outward from element 1 initially.
    2509            0 :         int new_f = new_elem_to_face.GetRow(e)[i];
    2510            0 :         if (e == e1)
    2511              :         {
    2512            0 :           new_mesh->GetFaceTransformation(new_f, &T);
    2513              :           const mfem::IntegrationPoint &ip =
    2514              :               mfem::Geometries.GetCenter(T.GetGeometryType());
    2515              :           T.SetIntPoint(&ip);
    2516            0 :           mfem::CalcOrtho(T.Jacobian(), normal);
    2517            0 :           double s = normal.Norml2();
    2518            0 :           h_min = std::min(h_min, std::sqrt(s));
    2519            0 :           normal /= -s;  // We could also area-weight the average normal
    2520              :         }
    2521              :         else  // e == e2
    2522              :         {
    2523            0 :           normal *= -1.0;
    2524              :         }
    2525              : 
    2526              :         // For all "nodes" associated with this crack face, update the direction of their
    2527              :         // displacements.
    2528            0 :         auto NodeUpdate = [&](int v)
    2529              :         {
    2530            0 :           for (int d = 0; d < sdim; d++)
    2531              :           {
    2532            0 :             const int idx = Index(v, d);
    2533            0 :             displacements(idx) += normal(d);
    2534              :           }
    2535            0 :         };
    2536            0 :         if (nodes)
    2537              :         {
    2538            0 :           nodes->FESpace()->GetFaceDofs(new_f, dofs);
    2539            0 :           for (int j = 0; j < dofs.Size(); j++)
    2540              :           {
    2541            0 :             NodeUpdate(dofs[j]);
    2542              :           }
    2543              :         }
    2544              :         else
    2545              :         {
    2546              :           const mfem::Element *el = new_mesh->GetElement(e);
    2547            0 :           for (int j = 0; j < el->GetNFaceVertices(i); j++)
    2548              :           {
    2549            0 :             NodeUpdate(el->GetVertices()[el->GetFaceVertices(i)[j]]);
    2550              :           }
    2551              :         }
    2552              :       }
    2553              :     }
    2554            0 :     for (int v = 0; v < nv; v++)
    2555              :     {
    2556              :       double s = 0.0;
    2557            0 :       for (int d = 0; d < sdim; d++)
    2558              :       {
    2559              :         const int idx = Index(v, d);
    2560            0 :         s += displacements(idx) * displacements(idx);
    2561              :       }
    2562            0 :       if (s > 0.0)
    2563              :       {
    2564            0 :         s = std::sqrt(s);
    2565            0 :         for (int d = 0; d < sdim; d++)
    2566              :         {
    2567              :           const int idx = Index(v, d);
    2568            0 :           displacements(idx) /= s;
    2569              :         }
    2570              :       }
    2571              :     }
    2572              : 
    2573              :     // Scale and apply the displacements. We don't need to do anything special to constrain
    2574              :     // the displacements at seam vertices (and associated high-order nodes on seam edges) to
    2575              :     // to zero, because the normals from both sides will average out to zero.
    2576            0 :     displacements *= (iodata.model.crack_displ_factor * h_min /
    2577            0 :                       (nodes ? nodes->FESpace()->GetMaxElementOrder() : 1));
    2578            0 :     new_mesh->MoveNodes(displacements);
    2579            0 :   }
    2580              : 
    2581              :   orig_mesh = std::move(new_mesh);
    2582              :   return 1;  // Success
    2583            8 : }
    2584              : 
    2585            8 : std::unique_ptr<int[]> GetMeshPartitioning(const mfem::Mesh &mesh, int size,
    2586              :                                            const std::string &part_file, bool print)
    2587              : {
    2588            8 :   MFEM_VERIFY(size <= mesh.GetNE(), "Mesh partitioning must have parts <= mesh elements ("
    2589              :                                         << size << " vs. " << mesh.GetNE() << ")!");
    2590            8 :   if (part_file.length() == 0)
    2591              :   {
    2592              :     const int part_method = 1;
    2593              :     std::unique_ptr<int[]> partitioning(
    2594            8 :         const_cast<mfem::Mesh &>(mesh).GeneratePartitioning(size, part_method));
    2595            8 :     if (print)
    2596              :     {
    2597            8 :       Mpi::Print("Finished partitioning mesh into {:d} subdomain{}\n", size,
    2598           24 :                  (size > 1) ? "s" : "");
    2599              :     }
    2600              :     return partitioning;
    2601              :   }
    2602              :   // User can optionally specify a mesh partitioning file as generated from the MFEM
    2603              :   // mesh-explorer miniapp, for example. It has the format:
    2604              :   //
    2605              :   //   number_of_elements <NE>
    2606              :   //   number_of_processors <NPART>
    2607              :   //   <part[0]>
    2608              :   //     ...
    2609              :   //   <part[NE-1]>
    2610              :   //
    2611              :   int ne, np;
    2612            0 :   std::ifstream part_ifs(part_file);
    2613            0 :   part_ifs.ignore(std::numeric_limits<std::streamsize>::max(), ' ');
    2614            0 :   part_ifs >> ne;
    2615            0 :   if (ne != mesh.GetNE())
    2616              :   {
    2617            0 :     MFEM_ABORT("Invalid partitioning file (number of elements)!");
    2618              :   }
    2619            0 :   part_ifs.ignore(std::numeric_limits<std::streamsize>::max(), ' ');
    2620            0 :   part_ifs >> np;
    2621            0 :   if (np != size)
    2622              :   {
    2623            0 :     MFEM_ABORT("Invalid partitioning file (number of processors)!");
    2624              :   }
    2625            0 :   auto partitioning = std::make_unique<int[]>(mesh.GetNE());
    2626              :   int i = 0;
    2627            0 :   while (i < mesh.GetNE())
    2628              :   {
    2629            0 :     part_ifs >> partitioning[i++];
    2630              :   }
    2631            0 :   if (print)
    2632              :   {
    2633            0 :     Mpi::Print("Read mesh partitioning into {:d} subdomain{} from disk\n", size,
    2634            0 :                (size > 1) ? "s" : "");
    2635              :   }
    2636              :   return partitioning;
    2637            0 : }
    2638              : 
    2639            8 : std::unique_ptr<mfem::ParMesh> DistributeMesh(MPI_Comm comm,
    2640              :                                               std::unique_ptr<mfem::Mesh> &smesh,
    2641              :                                               const int *partitioning,
    2642              :                                               const std::string &output_dir)
    2643              : {
    2644              :   // Take a serial mesh and partitioning on the root process and construct the global
    2645              :   // parallel mesh. For now, prefer the MPI-based version to the file IO one. When
    2646              :   // constructing the ParMesh, we mark for refinement since refinement flags are not copied
    2647              :   // from the serial mesh. Beware that mfem::ParMesh constructor argument order is not the
    2648              :   // same as mfem::Mesh! Each processor's component gets sent as a byte string.
    2649            8 :   constexpr bool generate_edges = false, refine = true, fix_orientation = false;
    2650            8 :   std::unique_ptr<mfem::ParMesh> pmesh;
    2651            8 :   if (Mpi::Root(comm))
    2652              :   {
    2653              :     mfem::MeshPartitioner partitioner(*smesh, Mpi::Size(comm),
    2654            8 :                                       const_cast<int *>(partitioning));
    2655            8 :     std::vector<MPI_Request> send_requests(Mpi::Size(comm) - 1, MPI_REQUEST_NULL);
    2656              :     std::vector<std::string> so;
    2657            8 :     so.reserve(Mpi::Size(comm));
    2658           24 :     for (int i = 0; i < Mpi::Size(comm); i++)
    2659              :     {
    2660            8 :       mfem::MeshPart part;
    2661            8 :       partitioner.ExtractPart(i, part);
    2662            8 :       std::ostringstream fo(std::stringstream::out);
    2663              :       // fo << std::fixed;
    2664              :       fo << std::scientific;
    2665              :       fo.precision(MSH_FLT_PRECISION);
    2666            8 :       part.Print(fo);
    2667            8 :       so.push_back(fo.str());
    2668              :       // so.push_back((i > 0) ? zlib::CompressString(fo.str()) : fo.str());
    2669            8 :       if (i > 0)
    2670              :       {
    2671            0 :         int slen = static_cast<int>(so[i].length());
    2672            0 :         MFEM_VERIFY(so[i].length() == (std::size_t)slen,
    2673              :                     "Overflow error distributing parallel mesh!");
    2674            0 :         MPI_Isend(so[i].data(), slen, MPI_CHAR, i, i, comm, &send_requests[i - 1]);
    2675              :       }
    2676            8 :     }
    2677              :     smesh.reset();
    2678            8 :     std::istringstream fi(so[0]);  // This is never compressed
    2679              :     pmesh =
    2680           16 :         std::make_unique<mfem::ParMesh>(comm, fi, refine, generate_edges, fix_orientation);
    2681            8 :     MPI_Waitall(static_cast<int>(send_requests.size()), send_requests.data(),
    2682              :                 MPI_STATUSES_IGNORE);
    2683           16 :   }
    2684              :   else
    2685              :   {
    2686              :     MPI_Status status;
    2687              :     int rlen;
    2688              :     std::string si;
    2689            0 :     MPI_Probe(0, Mpi::Rank(comm), comm, &status);
    2690            0 :     MPI_Get_count(&status, MPI_CHAR, &rlen);
    2691            0 :     si.resize(rlen);
    2692            0 :     MPI_Recv(si.data(), rlen, MPI_CHAR, 0, Mpi::Rank(comm), comm, MPI_STATUS_IGNORE);
    2693            0 :     std::istringstream fi(si);
    2694              :     // std::istringstream fi(zlib::DecompressString(si));
    2695              :     pmesh =
    2696            0 :         std::make_unique<mfem::ParMesh>(comm, fi, refine, generate_edges, fix_orientation);
    2697            0 :   }
    2698            8 :   return pmesh;
    2699              : }
    2700              : 
    2701            0 : void RebalanceConformalMesh(std::unique_ptr<mfem::ParMesh> &pmesh)
    2702              : {
    2703              :   // Write the parallel mesh to a stream as a serial mesh, then read back in and partition
    2704              :   // using METIS.
    2705              :   MPI_Comm comm = pmesh->GetComm();
    2706              :   constexpr bool generate_edges = false, generate_bdr = false, refine = true,
    2707              :                  fix_orientation = false;
    2708            0 :   std::unique_ptr<mfem::Mesh> smesh;
    2709              :   if constexpr (false)
    2710              :   {
    2711              :     // Write the serial mesh to a stream and read that through the Mesh constructor.
    2712              :     std::ostringstream fo(std::stringstream::out);
    2713              :     // fo << std::fixed;
    2714              :     fo << std::scientific;
    2715              :     fo.precision(MSH_FLT_PRECISION);
    2716              :     pmesh->PrintAsSerial(fo);
    2717              :     if (Mpi::Root(comm))
    2718              :     {
    2719              :       smesh = std::make_unique<mfem::Mesh>(fo, generate_edges, refine, fix_orientation);
    2720              :     }
    2721              :   }
    2722              :   else
    2723              :   {
    2724              :     // Directly ingest the generated Mesh and release the no longer needed memory.
    2725            0 :     smesh = std::make_unique<mfem::Mesh>(pmesh->GetSerialMesh(0));
    2726            0 :     if (Mpi::Root(comm))
    2727              :     {
    2728            0 :       smesh->FinalizeTopology(generate_bdr);
    2729            0 :       smesh->Finalize(refine, fix_orientation);
    2730              :     }
    2731              :     else
    2732              :     {
    2733              :       smesh.reset();
    2734              :     }
    2735              :   }
    2736              : 
    2737              :   // (Re)-construct the parallel mesh.
    2738              :   std::unique_ptr<int[]> partitioning;
    2739            0 :   if (Mpi::Root(comm))
    2740              :   {
    2741            0 :     partitioning = GetMeshPartitioning(*smesh, Mpi::Size(comm), "", false);
    2742              :   }
    2743            0 :   pmesh = DistributeMesh(comm, smesh, partitioning.get());
    2744            0 : }
    2745              : 
    2746              : }  // namespace
    2747              : 
    2748              : }  // namespace palace
        

Generated by: LCOV version 2.0-1