LCOV - code coverage report
Current view: top level - src/mfem/submeshes - MFEMCutTransitionSubMesh.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: #32971 (54bef8) with base c6cf66 Lines: 136 139 97.8 %
Date: 2026-05-29 20:35:17 Functions: 8 8 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : //* This file is part of the MOOSE framework
       2             : //* https://mooseframework.inl.gov
       3             : //*
       4             : //* All rights reserved, see COPYRIGHT for full restrictions
       5             : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
       6             : //*
       7             : //* Licensed under LGPL 2.1, please see LICENSE for details
       8             : //* https://www.gnu.org/licenses/lgpl-2.1.html
       9             : 
      10             : #ifdef MOOSE_MFEM_ENABLED
      11             : 
      12             : #include "MFEMCutTransitionSubMesh.h"
      13             : #include "MFEMProblem.h"
      14             : 
      15             : registerMooseObject("MooseApp", MFEMCutTransitionSubMesh);
      16             : 
      17             : InputParameters
      18        2224 : MFEMCutTransitionSubMesh::validParams()
      19             : {
      20        2224 :   InputParameters params = MFEMSubMesh::validParams();
      21        2224 :   params += MFEMBlockRestrictable::validParams();
      22        4448 :   params.addClassDescription(
      23             :       "Class to construct an MFEMSubMesh formed from the set of elements that have least one "
      24             :       "vertex on the specified cut plane, that lie on one side of the plane, "
      25             :       "and that are restricted to the set of user-specified subdomains.");
      26        8896 :   params.addRequiredParam<BoundaryName>("cut_boundary",
      27             :                                         "The boundary associated with the mesh cut.");
      28        8896 :   params.addRequiredParam<BoundaryName>(
      29             :       "transition_subdomain_boundary",
      30             :       "Name to assign boundary of transition subdomain not shared with cut surface.");
      31        8896 :   params.addRequiredParam<SubdomainName>(
      32             :       "transition_subdomain",
      33             :       "The name of the subdomain to be created on the mesh comprised of the set of elements "
      34             :       "adjacent to the cut surface on one side.");
      35        6672 :   params.addRequiredParam<SubdomainName>(
      36             :       "closed_subdomain",
      37             :       "The name of the subdomain attribute to be created comprised of the set of all elements "
      38             :       "of the closed geometry, including the new transition region.");
      39        2224 :   return params;
      40           0 : }
      41             : 
      42          63 : MFEMCutTransitionSubMesh::MFEMCutTransitionSubMesh(const InputParameters & parameters)
      43             :   : MFEMSubMesh(parameters),
      44          63 :     MFEMBlockRestrictable(parameters, getMFEMProblem().mesh().getMFEMParMesh()),
      45          63 :     _cut_boundary(getParam<BoundaryName>("cut_boundary")),
      46          63 :     _cut_submesh(std::make_shared<mfem::ParSubMesh>(mfem::ParSubMesh::CreateFromBoundary(
      47          63 :         getMesh(), getMesh().bdr_attribute_sets.GetAttributeSet(_cut_boundary)))),
      48         126 :     _transition_subdomain_boundary(getParam<BoundaryName>("transition_subdomain_boundary")),
      49         126 :     _transition_subdomain(getParam<SubdomainName>("transition_subdomain")),
      50         126 :     _closed_subdomain(getParam<SubdomainName>("closed_subdomain")),
      51         189 :     _cut_normal(3)
      52             : {
      53          63 : }
      54             : 
      55             : void
      56          63 : MFEMCutTransitionSubMesh::buildSubMesh()
      57             : {
      58          63 :   labelMesh(const_cast<mfem::ParMesh &>(getMesh()));
      59         126 :   _submesh = std::make_shared<mfem::ParSubMesh>(mfem::ParSubMesh::CreateFromDomain(
      60         126 :       getMesh(), getMesh().attribute_sets.GetAttributeSet(_transition_subdomain)));
      61          63 :   _submesh->attribute_sets.attr_sets = getMesh().attribute_sets.attr_sets;
      62          63 :   _submesh->bdr_attribute_sets.attr_sets = getMesh().bdr_attribute_sets.attr_sets;
      63             :   // Create boundary attribute set labelling the exterior of the newly created
      64             :   // transition region, excluding the cut
      65          63 :   _submesh->bdr_attribute_sets.SetAttributeSet(
      66          63 :       _transition_subdomain_boundary, mfem::Array<int>({getMesh().bdr_attributes.Max() + 1}));
      67          63 : }
      68             : 
      69             : void
      70          63 : MFEMCutTransitionSubMesh::labelMesh(mfem::ParMesh & parent_mesh)
      71             : {
      72          63 :   int mpi_comm_rank = getMFEMProblem().getProblemData().myid;
      73          63 :   int mpi_comm_size = getMFEMProblem().getProblemData().num_procs;
      74             : 
      75             :   // First determine face normal based on the first boundary element found on the cut
      76             :   // to use when determining orientation relative to the cut
      77          63 :   const mfem::Array<int> & parent_cut_element_id_map = _cut_submesh->GetParentElementIDMap();
      78          63 :   int rank_with_submesh = -1;
      79          63 :   if (parent_cut_element_id_map.Size() > 0)
      80             :   {
      81          55 :     int reference_face = parent_cut_element_id_map[0];
      82          55 :     _cut_normal = findFaceNormal(parent_mesh, parent_mesh.GetBdrElementFaceIndex(reference_face));
      83          55 :     rank_with_submesh = mpi_comm_rank;
      84             :   }
      85          63 :   MPI_Allreduce(MPI_IN_PLACE, &rank_with_submesh, 1, MPI_INT, MPI_MAX, getMFEMProblem().getComm());
      86          63 :   MPI_Bcast(_cut_normal.GetData(),
      87             :             _cut_normal.Size(),
      88             :             MFEM_MPI_REAL_T,
      89             :             rank_with_submesh,
      90             :             getMFEMProblem().getComm());
      91             :   // Iterate over all vertices on cut, find elements with those vertices,
      92             :   // and declare them transition elements if they are on the +ve side of the cut
      93          63 :   mfem::Array<int> transition_els;
      94          63 :   std::vector<HYPRE_BigInt> global_cut_vert_ids;
      95          63 :   mfem::Array<HYPRE_BigInt> gi;
      96          63 :   parent_mesh.GetGlobalVertexIndices(gi);
      97          63 :   std::unique_ptr<mfem::Table> vert_to_elem(parent_mesh.GetVertexToElementTable());
      98          63 :   const mfem::Array<int> & cut_to_parent_vertex_id_map = _cut_submesh->GetParentVertexIDMap();
      99        4857 :   for (int i = 0; i < _cut_submesh->GetNV(); ++i)
     100             :   {
     101        4794 :     int cut_vert = cut_to_parent_vertex_id_map[i];
     102        4794 :     global_cut_vert_ids.push_back(gi[cut_vert]);
     103        4794 :     int ne = vert_to_elem->RowSize(cut_vert); // number of elements touching cut vertex
     104        4794 :     const int * els_adj_to_cut = vert_to_elem->GetRow(cut_vert); // elements touching cut vertex
     105      116858 :     for (int i = 0; i < ne; i++)
     106             :     {
     107      112064 :       const int el_adj_to_cut = els_adj_to_cut[i];
     108      204210 :       if (isInDomain(el_adj_to_cut, getSubdomainAttributes(), parent_mesh) &&
     109       92146 :           isPositiveSideOfCut(el_adj_to_cut, cut_vert, parent_mesh))
     110       46134 :         transition_els.Append(el_adj_to_cut);
     111             :     }
     112             :   }
     113             : 
     114             :   // share cut verts coords or global ids across all procs
     115          63 :   int n_cut_vertices = global_cut_vert_ids.size();
     116          63 :   std::vector<int> cut_vert_sizes(mpi_comm_size);
     117          63 :   MPI_Allgather(
     118             :       &n_cut_vertices, 1, MPI_INT, cut_vert_sizes.data(), 1, MPI_INT, getMFEMProblem().getComm());
     119             :   // Make an offset array and total the sizes.
     120          63 :   std::vector<int> n_vert_offset(mpi_comm_size);
     121          63 :   std::exclusive_scan(cut_vert_sizes.begin(), cut_vert_sizes.end(), n_vert_offset.begin(), 0);
     122          63 :   int global_n_cut_vertices = std::accumulate(cut_vert_sizes.begin(), cut_vert_sizes.end(), 0);
     123             : 
     124             :   // Gather the queries to all ranks.
     125          63 :   std::vector<HYPRE_BigInt> all_cut_verts(global_n_cut_vertices);
     126          63 :   MPI_Allgatherv(global_cut_vert_ids.data(),
     127             :                  n_cut_vertices,
     128             :                  HYPRE_MPI_BIG_INT,
     129             :                  all_cut_verts.data(),
     130             :                  cut_vert_sizes.data(),
     131             :                  n_vert_offset.data(),
     132             :                  HYPRE_MPI_BIG_INT,
     133             :                  getMFEMProblem().getComm());
     134             : 
     135             :   // Detect shared vertices and add corresponding elements
     136         103 :   for (const auto g : make_range(1, parent_mesh.GetNGroups()))
     137       22896 :     for (const auto gv : make_range(parent_mesh.GroupNVertices(g)))
     138             :     {
     139             :       // all elements touching this shared vertex should be updated
     140       22856 :       int cut_vert = parent_mesh.GroupVertex(g, gv);
     141     2447392 :       for (std::size_t i = 0; i < all_cut_verts.size(); i += 1)
     142     2424536 :         if (gi[cut_vert] == all_cut_verts[i]) // check if shared vertex is on the cut plane
     143             :         {
     144         608 :           int ne = vert_to_elem->RowSize(cut_vert); // number of elements touching cut vertex
     145             :           const int * els_adj_to_cut =
     146         608 :               vert_to_elem->GetRow(cut_vert); // elements touching cut vertex
     147        8528 :           for (int i = 0; i < ne; i++)
     148             :           {
     149        7920 :             const int el_adj_to_cut = els_adj_to_cut[i];
     150       15068 :             if (isInDomain(el_adj_to_cut, getSubdomainAttributes(), parent_mesh) &&
     151        7148 :                 isPositiveSideOfCut(el_adj_to_cut, cut_vert, parent_mesh))
     152        3528 :               transition_els.Append(el_adj_to_cut);
     153             :           }
     154             :         }
     155             :     }
     156             : 
     157          63 :   transition_els.Sort();
     158          63 :   transition_els.Unique();
     159             : 
     160          63 :   setAttributes(parent_mesh, transition_els);
     161          63 : }
     162             : 
     163             : mfem::Vector
     164          55 : MFEMCutTransitionSubMesh::findFaceNormal(const mfem::ParMesh & mesh, const int & face)
     165             : {
     166          55 :   if (mesh.SpaceDimension() != 3)
     167           0 :     mooseError("MFEMCutTransitionSubMesh only works in 3-dimensional meshes!");
     168          55 :   mfem::Vector normal;
     169          55 :   mfem::Array<int> face_verts;
     170          55 :   std::vector<mfem::Vector> v;
     171          55 :   mesh.GetFaceVertices(face, face_verts);
     172             : 
     173             :   // First we get the coordinates of 3 vertices on the face
     174         220 :   for (auto vtx : face_verts)
     175             :   {
     176         165 :     mfem::Vector vtx_coords(3);
     177         660 :     for (int j = 0; j < 3; ++j)
     178         495 :       vtx_coords[j] = mesh.GetVertex(vtx)[j];
     179         165 :     v.push_back(vtx_coords);
     180         165 :   }
     181             : 
     182             :   // Now we find the unit vector normal to the face
     183          55 :   v[0] -= v[1];
     184          55 :   v[1] -= v[2];
     185          55 :   v[0].cross3D(v[1], normal);
     186          55 :   normal /= normal.Norml2();
     187         110 :   return normal;
     188          55 : }
     189             : 
     190             : bool
     191       99294 : MFEMCutTransitionSubMesh::isPositiveSideOfCut(const int & el,
     192             :                                               const int & el_vertex_on_cut,
     193             :                                               mfem::ParMesh & parent_mesh)
     194             : {
     195       99294 :   const int sdim = parent_mesh.SpaceDimension();
     196       99294 :   mfem::Vector el_center(3);
     197       99294 :   parent_mesh.GetElementCenter(el, el_center);
     198       99294 :   mfem::Vector vertex_coords(parent_mesh.GetVertex(el_vertex_on_cut), sdim);
     199       99294 :   mfem::Vector relative_center(sdim);
     200      397176 :   for (int j = 0; j < sdim; j++)
     201      297882 :     relative_center[j] = el_center[j] - vertex_coords[j];
     202      198588 :   return _cut_normal * relative_center > 0;
     203       99294 : }
     204             : 
     205             : void
     206          63 : MFEMCutTransitionSubMesh::setAttributes(mfem::ParMesh & parent_mesh,
     207             :                                         mfem::Array<int> & transition_els)
     208             : {
     209             :   // Generate a set of local new attributes for transition region elements
     210          63 :   const int old_max_attr = parent_mesh.attributes.Max();
     211          63 :   mfem::Array<int> new_attrs(old_max_attr);
     212          63 :   new_attrs = -1;
     213       23354 :   for (auto const & transition_el : transition_els)
     214             :   {
     215       23291 :     const int old_attr = parent_mesh.GetAttribute(transition_el);
     216       23291 :     new_attrs[old_attr - 1] = old_max_attr + old_attr;
     217             :     // Set the new attribute IDs for transition region elements
     218       23291 :     parent_mesh.SetAttribute(transition_el, new_attrs[old_attr - 1]);
     219             :   }
     220             : 
     221             :   // Distribute local attribute IDs to other MPI ranks to create set of new
     222             :   // global attribute IDs for the transition region.
     223          63 :   MPI_Allreduce(
     224             :       MPI_IN_PLACE, new_attrs, old_max_attr, MPI_INT, MPI_MAX, getMFEMProblem().getComm());
     225             : 
     226          63 :   mfem::AttributeSets & attr_sets = parent_mesh.attribute_sets;
     227             :   // Create attribute set labelling the newly created transition region on one side of the cut
     228          63 :   attr_sets.CreateAttributeSet(_transition_subdomain);
     229             :   // Create attribute set labelling the entire closed geometry
     230          63 :   attr_sets.SetAttributeSet(_closed_subdomain, getSubdomainAttributes());
     231             :   // Add the new domain attributes to new attribute sets
     232          63 :   const std::set<std::string> attr_set_names = attr_sets.GetAttributeSetNames();
     233         252 :   for (int old_attr = 1; old_attr <= old_max_attr; ++old_attr)
     234             :   {
     235         189 :     int new_attr = new_attrs[old_attr - 1];
     236             :     // Add attributes only if they're transition region attributes
     237         189 :     if (new_attr != -1)
     238             :     {
     239         101 :       attr_sets.AddToAttributeSet(_transition_subdomain, new_attr);
     240         606 :       for (auto const & attr_set_name : attr_set_names)
     241             :       {
     242         505 :         const mfem::Array<int> & attr_set = attr_sets.GetAttributeSet(attr_set_name);
     243             :         // If attribute set has the old attribute of the transition region, add the new one
     244         505 :         if (attr_set.Find(old_attr) != -1)
     245         202 :           attr_sets.AddToAttributeSet(attr_set_name, new_attr);
     246             :       }
     247             :     }
     248             :   }
     249             : 
     250          63 :   parent_mesh.SetAttributes();
     251          63 : }
     252             : 
     253             : bool
     254      119984 : MFEMCutTransitionSubMesh::isInDomain(const int & element,
     255             :                                      const mfem::Array<int> & subdomains,
     256             :                                      const mfem::ParMesh & mesh)
     257             : {
     258             :   // element<0 for ghost elements
     259      119984 :   if (element < 0)
     260           0 :     return true;
     261             : 
     262      166776 :   for (const auto & subdomain : subdomains)
     263      146086 :     if (mesh.GetAttribute(element) == subdomain)
     264       99294 :       return true;
     265       20690 :   return false;
     266             : }
     267             : 
     268             : #endif

Generated by: LCOV version 1.14