LCOV - code coverage report
Current view: top level - src/utils - PolycrystalICTools.C (source / functions) Hit Total Coverage
Test: idaholab/moose phase_field: #31405 (292dce) with base fef103 Lines: 0 127 0.0 %
Date: 2025-09-04 07:55:36 Functions: 0 11 0.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             : #include "PolycrystalICTools.h"
      11             : 
      12             : // MOOSE includes
      13             : #include "MooseMesh.h"
      14             : #include "MooseVariable.h"
      15             : #include "PetscSupport.h"
      16             : 
      17             : #include "libmesh/mesh_tools.h"
      18             : #include "libmesh/periodic_boundaries.h"
      19             : #include "libmesh/point_locator_base.h"
      20             : 
      21             : using namespace libMesh;
      22             : 
      23             : namespace GraphColoring
      24             : {
      25             : const unsigned int INVALID_COLOR = std::numeric_limits<unsigned int>::max();
      26             : }
      27             : 
      28             : namespace PolycrystalICTools
      29             : {
      30             : const unsigned int HALO_THICKNESS = 4;
      31             : }
      32             : 
      33             : // Forward declarations
      34             : bool colorGraph(const PolycrystalICTools::AdjacencyMatrix<Real> & adjacency_matrix,
      35             :                 std::vector<unsigned int> & colors,
      36             :                 unsigned int n_vertices,
      37             :                 unsigned int n_ops,
      38             :                 unsigned int vertex);
      39             : bool isGraphValid(const PolycrystalICTools::AdjacencyMatrix<Real> & adjacency_matrix,
      40             :                   std::vector<unsigned int> & colors,
      41             :                   unsigned int n_vertices,
      42             :                   unsigned int vertex,
      43             :                   unsigned int color);
      44             : void visitElementalNeighbors(const Elem * elem,
      45             :                              const MeshBase & mesh,
      46             :                              const PointLocatorBase & point_locator,
      47             :                              const PeriodicBoundaries * pb,
      48             :                              std::set<dof_id_type> & halo_ids);
      49             : 
      50             : std::vector<unsigned int>
      51           0 : PolycrystalICTools::assignPointsToVariables(const std::vector<Point> & centerpoints,
      52             :                                             const Real op_num,
      53             :                                             const MooseMesh & mesh,
      54             :                                             const MooseVariable & var)
      55             : {
      56           0 :   Real grain_num = centerpoints.size();
      57             : 
      58           0 :   std::vector<unsigned int> assigned_op(grain_num);
      59           0 :   std::vector<int> min_op_ind(op_num);
      60           0 :   std::vector<Real> min_op_dist(op_num);
      61             : 
      62             :   // Assign grains to specific order parameters in a way that maximizes the distance
      63           0 :   for (unsigned int grain = 0; grain < grain_num; grain++)
      64             :   {
      65             :     // Determine the distance to the closest center assigned to each order parameter
      66           0 :     if (grain >= op_num)
      67             :     {
      68             :       // We can set the array to the distances to the grains 0..op_num-1 (see assignment in the else
      69             :       // case)
      70           0 :       for (unsigned int i = 0; i < op_num; ++i)
      71             :       {
      72           0 :         min_op_dist[i] =
      73           0 :             mesh.minPeriodicDistance(var.number(), centerpoints[grain], centerpoints[i]);
      74           0 :         min_op_ind[assigned_op[i]] = i;
      75             :       }
      76             : 
      77             :       // Now check if any of the extra grains are even closer
      78           0 :       for (unsigned int i = op_num; i < grain; ++i)
      79             :       {
      80           0 :         Real dist = mesh.minPeriodicDistance(var.number(), centerpoints[grain], centerpoints[i]);
      81           0 :         if (min_op_dist[assigned_op[i]] > dist)
      82             :         {
      83           0 :           min_op_dist[assigned_op[i]] = dist;
      84           0 :           min_op_ind[assigned_op[i]] = i;
      85             :         }
      86             :       }
      87             :     }
      88             :     else
      89             :     {
      90           0 :       assigned_op[grain] = grain;
      91           0 :       continue;
      92             :     }
      93             : 
      94             :     // Assign the current center point to the order parameter that is furthest away.
      95             :     unsigned int mx_ind = 0;
      96           0 :     for (unsigned int i = 1; i < op_num; ++i) // Find index of max
      97           0 :       if (min_op_dist[mx_ind] < min_op_dist[i])
      98             :         mx_ind = i;
      99             : 
     100           0 :     assigned_op[grain] = mx_ind;
     101             :   }
     102             : 
     103           0 :   return assigned_op;
     104           0 : }
     105             : 
     106             : unsigned int
     107           0 : PolycrystalICTools::assignPointToGrain(const Point & p,
     108             :                                        const std::vector<Point> & centerpoints,
     109             :                                        const MooseMesh & mesh,
     110             :                                        const MooseVariable & var,
     111             :                                        const Real maxsize)
     112             : {
     113           0 :   unsigned int grain_num = centerpoints.size();
     114             : 
     115             :   Real min_distance = maxsize;
     116             :   unsigned int min_index = grain_num;
     117             :   // Loops through all of the grain centers and finds the center that is closest to the point p
     118           0 :   for (unsigned int grain = 0; grain < grain_num; grain++)
     119             :   {
     120           0 :     Real distance = mesh.minPeriodicDistance(var.number(), centerpoints[grain], p);
     121             : 
     122           0 :     if (min_distance > distance)
     123             :     {
     124             :       min_distance = distance;
     125             :       min_index = grain;
     126             :     }
     127             :   }
     128             : 
     129           0 :   if (min_index >= grain_num)
     130           0 :     mooseError("ERROR in PolycrystalVoronoiVoidIC: didn't find minimum values in grain_value_calc");
     131             : 
     132           0 :   return min_index;
     133             : }
     134             : 
     135             : PolycrystalICTools::AdjacencyMatrix<Real>
     136           0 : PolycrystalICTools::buildGrainAdjacencyMatrix(
     137             :     const std::map<dof_id_type, unsigned int> & entity_to_grain,
     138             :     MooseMesh & mesh,
     139             :     const PeriodicBoundaries * pb,
     140             :     unsigned int n_grains,
     141             :     bool is_elemental)
     142             : {
     143           0 :   if (is_elemental)
     144           0 :     return buildElementalGrainAdjacencyMatrix(entity_to_grain, mesh, pb, n_grains);
     145             :   else
     146           0 :     return buildNodalGrainAdjacencyMatrix(entity_to_grain, mesh, pb, n_grains);
     147             : }
     148             : 
     149             : PolycrystalICTools::AdjacencyMatrix<Real>
     150           0 : PolycrystalICTools::buildElementalGrainAdjacencyMatrix(
     151             :     const std::map<dof_id_type, unsigned int> & element_to_grain,
     152             :     MooseMesh & mesh,
     153             :     const PeriodicBoundaries * pb,
     154             :     unsigned int n_grains)
     155             : {
     156             :   AdjacencyMatrix<Real> adjacency_matrix(n_grains);
     157             : 
     158             :   // We can't call this in the constructor, it appears that _mesh_ptr is always NULL there.
     159           0 :   mesh.errorIfDistributedMesh("advanced_op_assignment = true");
     160             : 
     161             :   std::vector<const Elem *> all_active_neighbors;
     162             : 
     163           0 :   std::vector<std::set<dof_id_type>> local_ids(n_grains);
     164           0 :   std::vector<std::set<dof_id_type>> halo_ids(n_grains);
     165             : 
     166           0 :   std::unique_ptr<PointLocatorBase> point_locator = mesh.getMesh().sub_point_locator();
     167           0 :   for (const auto & elem : mesh.getMesh().active_element_ptr_range())
     168             :   {
     169             :     std::map<dof_id_type, unsigned int>::const_iterator grain_it =
     170           0 :         element_to_grain.find(elem->id());
     171             :     mooseAssert(grain_it != element_to_grain.end(), "Element not found in map");
     172           0 :     unsigned int my_grain = grain_it->second;
     173             : 
     174           0 :     all_active_neighbors.clear();
     175             :     // Loop over all neighbors (at the the same level as the current element)
     176           0 :     for (unsigned int i = 0; i < elem->n_neighbors(); ++i)
     177             :     {
     178           0 :       const Elem * neighbor_ancestor = elem->topological_neighbor(i, mesh, *point_locator, pb);
     179           0 :       if (neighbor_ancestor)
     180             :         // Retrieve only the active neighbors for each side of this element, append them to the list
     181             :         // of active neighbors
     182           0 :         neighbor_ancestor->active_family_tree_by_topological_neighbor(
     183             :             all_active_neighbors, elem, mesh, *point_locator, pb, false);
     184             :     }
     185             : 
     186             :     // Loop over all active element neighbors
     187           0 :     for (std::vector<const Elem *>::const_iterator neighbor_it = all_active_neighbors.begin();
     188           0 :          neighbor_it != all_active_neighbors.end();
     189             :          ++neighbor_it)
     190             :     {
     191           0 :       const Elem * neighbor = *neighbor_it;
     192             :       std::map<dof_id_type, unsigned int>::const_iterator grain_it2 =
     193           0 :           element_to_grain.find(neighbor->id());
     194             :       mooseAssert(grain_it2 != element_to_grain.end(), "Element not found in map");
     195           0 :       unsigned int their_grain = grain_it2->second;
     196             : 
     197           0 :       if (my_grain != their_grain)
     198             :       {
     199             :         /**
     200             :          * We've found a grain neighbor interface. In order to assign order parameters though, we
     201             :          * need to make sure that we build out a small buffer region to avoid literal "corner cases"
     202             :          * where nodes on opposite corners of a QUAD end up with the same OP because those nodes are
     203             :          * not nodal neighbors. To do that we'll build a halo region based on these interface nodes.
     204             :          * For now, we need to record the nodes inside of the grain and those outside of the grain.
     205             :          */
     206             : 
     207             :         // First add corresponding element and grain information
     208           0 :         local_ids[my_grain].insert(elem->id());
     209           0 :         local_ids[their_grain].insert(neighbor->id());
     210             : 
     211             :         // Now add opposing element and grain information
     212           0 :         halo_ids[my_grain].insert(neighbor->id());
     213           0 :         halo_ids[their_grain].insert(elem->id());
     214             :       }
     215             :       //  adjacency_matrix[my_grain][their_grain] = 1;
     216             :     }
     217           0 :   }
     218             : 
     219             :   // Build up the halos
     220             :   std::set<dof_id_type> set_difference;
     221           0 :   for (unsigned int i = 0; i < n_grains; ++i)
     222             :   {
     223           0 :     std::set<dof_id_type> orig_halo_ids(halo_ids[i]);
     224             : 
     225           0 :     for (unsigned int halo_level = 0; halo_level < PolycrystalICTools::HALO_THICKNESS; ++halo_level)
     226             :     {
     227           0 :       for (std::set<dof_id_type>::iterator entity_it = orig_halo_ids.begin();
     228           0 :            entity_it != orig_halo_ids.end();
     229             :            ++entity_it)
     230             :       {
     231             :         if (true)
     232           0 :           visitElementalNeighbors(
     233           0 :               mesh.elemPtr(*entity_it), mesh.getMesh(), *point_locator, pb, halo_ids[i]);
     234             :         else
     235             :           mooseError("Unimplemented");
     236             :       }
     237             : 
     238             :       set_difference.clear();
     239           0 :       std::set_difference(
     240             :           halo_ids[i].begin(),
     241             :           halo_ids[i].end(),
     242             :           local_ids[i].begin(),
     243             :           local_ids[i].end(),
     244             :           std::insert_iterator<std::set<dof_id_type>>(set_difference, set_difference.begin()));
     245             : 
     246             :       halo_ids[i].swap(set_difference);
     247             :     }
     248             :   }
     249             : 
     250             :   // Finally look at the halo intersections to build the connectivity graph
     251             :   std::set<dof_id_type> set_intersection;
     252           0 :   for (unsigned int i = 0; i < n_grains; ++i)
     253           0 :     for (unsigned int j = i + 1; j < n_grains; ++j)
     254             :     {
     255             :       set_intersection.clear();
     256           0 :       std::set_intersection(
     257             :           halo_ids[i].begin(),
     258           0 :           halo_ids[i].end(),
     259             :           halo_ids[j].begin(),
     260           0 :           halo_ids[j].end(),
     261             :           std::insert_iterator<std::set<dof_id_type>>(set_intersection, set_intersection.begin()));
     262             : 
     263           0 :       if (!set_intersection.empty())
     264             :       {
     265           0 :         adjacency_matrix(i, j) = 1.;
     266           0 :         adjacency_matrix(j, i) = 1.;
     267             :       }
     268             :     }
     269             : 
     270           0 :   return adjacency_matrix;
     271           0 : }
     272             : 
     273             : PolycrystalICTools::AdjacencyMatrix<Real>
     274           0 : PolycrystalICTools::buildNodalGrainAdjacencyMatrix(
     275             :     const std::map<dof_id_type, unsigned int> & node_to_grain,
     276             :     MooseMesh & mesh,
     277             :     const PeriodicBoundaries * /*pb*/,
     278             :     unsigned int n_grains)
     279             : {
     280             :   // Build node to elem map
     281             :   std::vector<std::vector<const Elem *>> nodes_to_elem_map;
     282           0 :   MeshTools::build_nodes_to_elem_map(mesh.getMesh(), nodes_to_elem_map);
     283             : 
     284             :   AdjacencyMatrix<Real> adjacency_matrix(n_grains);
     285             : 
     286           0 :   const auto end = mesh.getMesh().active_nodes_end();
     287           0 :   for (auto nl = mesh.getMesh().active_nodes_begin(); nl != end; ++nl)
     288             :   {
     289           0 :     const Node * node = *nl;
     290           0 :     std::map<dof_id_type, unsigned int>::const_iterator grain_it = node_to_grain.find(node->id());
     291             :     mooseAssert(grain_it != node_to_grain.end(), "Node not found in map");
     292           0 :     unsigned int my_grain = grain_it->second;
     293             : 
     294             :     std::vector<const Node *> nodal_neighbors;
     295           0 :     MeshTools::find_nodal_neighbors(mesh.getMesh(), *node, nodes_to_elem_map, nodal_neighbors);
     296             : 
     297             :     // Loop over all nodal neighbors
     298           0 :     for (unsigned int i = 0; i < nodal_neighbors.size(); ++i)
     299             :     {
     300           0 :       const Node * neighbor_node = nodal_neighbors[i];
     301             :       std::map<dof_id_type, unsigned int>::const_iterator grain_it2 =
     302           0 :           node_to_grain.find(neighbor_node->id());
     303             :       mooseAssert(grain_it2 != node_to_grain.end(), "Node not found in map");
     304           0 :       unsigned int their_grain = grain_it2->second;
     305             : 
     306           0 :       if (my_grain != their_grain)
     307             :         /**
     308             :          * We've found a grain neighbor interface. In order to assign order parameters though, we
     309             :          * need to make sure that we build out a small buffer region to avoid literal "corner cases"
     310             :          * where nodes on opposite corners of a QUAD end up with the same OP because those nodes are
     311             :          * not nodal neighbors. To do that we'll build a halo region based on these interface nodes.
     312             :          * For now, we need to record the nodes inside of the grain and those outside of the grain.
     313             :          */
     314           0 :         adjacency_matrix(my_grain, their_grain) = 1.;
     315             :     }
     316           0 :   }
     317             : 
     318           0 :   return adjacency_matrix;
     319           0 : }
     320             : 
     321             : std::vector<unsigned int>
     322           0 : PolycrystalICTools::assignOpsToGrains(AdjacencyMatrix<Real> & adjacency_matrix,
     323             :                                       unsigned int n_grains,
     324             :                                       unsigned int n_ops,
     325             :                                       const MooseEnum & coloring_algorithm)
     326             : {
     327           0 :   std::vector<unsigned int> grain_to_op(n_grains, GraphColoring::INVALID_COLOR);
     328             : 
     329             :   // Use a simple backtracking coloring algorithm
     330           0 :   if (coloring_algorithm == "bt")
     331             :   {
     332           0 :     if (!colorGraph(adjacency_matrix, grain_to_op, n_grains, n_ops, 0))
     333           0 :       mooseError(
     334             :           "Unable to find a valid Grain to op configuration, do you have enough op variables?");
     335             :   }
     336             :   else // PETSc Coloring algorithms
     337             :   {
     338             :     const std::string & ca_str = coloring_algorithm;
     339             :     Real * am_data = adjacency_matrix.rawDataPtr();
     340           0 :     Moose::PetscSupport::colorAdjacencyMatrix(
     341             :         am_data, n_grains, n_ops, grain_to_op, ca_str.c_str());
     342             :   }
     343             : 
     344           0 :   return grain_to_op;
     345           0 : }
     346             : 
     347             : MooseEnum
     348           0 : PolycrystalICTools::coloringAlgorithms()
     349             : {
     350           0 :   return MooseEnum("legacy bt jp power greedy", "legacy");
     351             : }
     352             : 
     353             : std::string
     354           0 : PolycrystalICTools::coloringAlgorithmDescriptions()
     355             : {
     356             :   return "The grain neighbor graph coloring algorithm to use. \"legacy\" is the original "
     357             :          "algorithm "
     358             :          "which does not guarantee a valid coloring. \"bt\" is a simple backtracking algorithm "
     359             :          "which will produce a valid coloring but has potential exponential run time. The "
     360             :          "remaining algorithms require PETSc but are recommended for larger problems (See "
     361           0 :          "MatColoringType)";
     362             : }
     363             : 
     364             : /**
     365             :  * Utility routines
     366             :  */
     367             : void
     368           0 : visitElementalNeighbors(const Elem * elem,
     369             :                         const MeshBase & mesh,
     370             :                         const PointLocatorBase & point_locator,
     371             :                         const PeriodicBoundaries * pb,
     372             :                         std::set<dof_id_type> & halo_ids)
     373             : {
     374             :   mooseAssert(elem, "Elem is NULL");
     375             : 
     376             :   std::vector<const Elem *> all_active_neighbors;
     377             : 
     378             :   // Loop over all neighbors (at the the same level as the current element)
     379           0 :   for (unsigned int i = 0; i < elem->n_neighbors(); ++i)
     380             :   {
     381           0 :     const Elem * neighbor_ancestor = elem->topological_neighbor(i, mesh, point_locator, pb);
     382           0 :     if (neighbor_ancestor)
     383             :       // Retrieve only the active neighbors for each side of this element, append them to the list
     384             :       // of active neighbors
     385           0 :       neighbor_ancestor->active_family_tree_by_topological_neighbor(
     386             :           all_active_neighbors, elem, mesh, point_locator, pb, false);
     387             :   }
     388             : 
     389             :   // Loop over all active element neighbors
     390           0 :   for (std::vector<const Elem *>::const_iterator neighbor_it = all_active_neighbors.begin();
     391           0 :        neighbor_it != all_active_neighbors.end();
     392             :        ++neighbor_it)
     393           0 :     if (*neighbor_it)
     394           0 :       halo_ids.insert((*neighbor_it)->id());
     395           0 : }
     396             : 
     397             : /**
     398             :  * Backtracking graph coloring routines
     399             :  */
     400             : bool
     401           0 : colorGraph(const PolycrystalICTools::AdjacencyMatrix<Real> & adjacency_matrix,
     402             :            std::vector<unsigned int> & colors,
     403             :            unsigned int n_vertices,
     404             :            unsigned int n_colors,
     405             :            unsigned int vertex)
     406             : {
     407             :   // Base case: All grains are assigned
     408           0 :   if (vertex == n_vertices)
     409             :     return true;
     410             : 
     411             :   // Consider this grain and try different ops
     412           0 :   for (unsigned int color_idx = 0; color_idx < n_colors; ++color_idx)
     413             :   {
     414             :     // We'll try to spread these colors around a bit rather than
     415             :     // packing them all on the first few colors if we have several colors.
     416           0 :     unsigned int color = (vertex + color_idx) % n_colors;
     417             : 
     418           0 :     if (isGraphValid(adjacency_matrix, colors, n_vertices, vertex, color))
     419             :     {
     420           0 :       colors[vertex] = color;
     421             : 
     422           0 :       if (colorGraph(adjacency_matrix, colors, n_vertices, n_colors, vertex + 1))
     423             :         return true;
     424             : 
     425             :       // Backtrack...
     426           0 :       colors[vertex] = GraphColoring::INVALID_COLOR;
     427             :     }
     428             :   }
     429             : 
     430             :   return false;
     431             : }
     432             : 
     433             : bool
     434           0 : isGraphValid(const PolycrystalICTools::AdjacencyMatrix<Real> & adjacency_matrix,
     435             :              std::vector<unsigned int> & colors,
     436             :              unsigned int n_vertices,
     437             :              unsigned int vertex,
     438             :              unsigned int color)
     439             : {
     440             :   // See if the proposed color is valid based on the current neighbor colors
     441           0 :   for (unsigned int neighbor = 0; neighbor < n_vertices; ++neighbor)
     442           0 :     if (adjacency_matrix(vertex, neighbor) && color == colors[neighbor])
     443             :       return false;
     444             :   return true;
     445             : }

Generated by: LCOV version 1.14