LCOV - code coverage report
Current view: top level - src/userobjects - PolycrystalUserObjectBase.C (source / functions) Hit Total Coverage
Test: idaholab/moose phase_field: #32971 (54bef8) with base c6cf66 Lines: 187 198 94.4 %
Date: 2026-05-29 20:38:39 Functions: 20 20 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             : #include "DenseMatrix.h"
      11             : #include "PolycrystalUserObjectBase.h"
      12             : #include "NonlinearSystemBase.h"
      13             : #include "MooseMesh.h"
      14             : #include "MooseVariable.h"
      15             : 
      16             : #include <vector>
      17             : #include <map>
      18             : #include <algorithm>
      19             : 
      20             : InputParameters
      21        1130 : PolycrystalUserObjectBase::validParams()
      22             : {
      23        1130 :   InputParameters params = FeatureFloodCount::validParams();
      24        1130 :   params.addClassDescription("This object provides the base capability for creating proper reduced "
      25             :                              "order parameter polycrystal initial conditions.");
      26        2260 :   params.addRequiredCoupledVarWithAutoBuild(
      27             :       "variable", "var_name_base", "op_num", "Array of coupled variables");
      28        2260 :   params.addParam<bool>("output_adjacency_matrix",
      29        2260 :                         false,
      30             :                         "Output the Grain Adjacency Matrix used in the coloring algorithms. "
      31             :                         "Additionally, the grain to OP assignments will be printed");
      32        1130 :   params.addParam<MooseEnum>("coloring_algorithm",
      33        2260 :                              PolycrystalUserObjectBase::coloringAlgorithms(),
      34        2260 :                              PolycrystalUserObjectBase::coloringAlgorithmDescriptions());
      35             : 
      36             :   // FeatureFloodCount adds a relationship manager, but we need to extend that for PolycrystalIC
      37             :   params.clearRelationshipManagers();
      38             : 
      39        2260 :   params.addRelationshipManager(
      40             :       "ElementSideNeighborLayers",
      41             :       Moose::RelationshipManagerType::GEOMETRIC,
      42             : 
      43        1695 :       [](const InputParameters & /*obj_params*/, InputParameters & rm_params)
      44        1695 :       { rm_params.set<unsigned short>("layers") = 2; }
      45             : 
      46             :   );
      47             : 
      48        2260 :   params.addRelationshipManager("ElementSideNeighborLayers",
      49             :                                 Moose::RelationshipManagerType::ALGEBRAIC);
      50             : 
      51             :   // Hide the output of the IC objects by default, it doesn't change over time
      52        3390 :   params.set<std::vector<OutputName>>("outputs") = {"none"};
      53             : 
      54             :   /// Run this user object more than once on the initial condition to handle initial adaptivity
      55        1130 :   params.set<bool>("allow_duplicate_execution_on_initial") = true;
      56             : 
      57             :   // This object should only be executed _before_ the initial condition
      58        1130 :   ExecFlagEnum execute_options = MooseUtils::getDefaultExecFlagEnum();
      59        1130 :   execute_options = EXEC_INITIAL;
      60        2260 :   params.set<ExecFlagEnum>("execute_on") = execute_options;
      61             : 
      62        1130 :   return params;
      63           0 : }
      64             : 
      65         565 : PolycrystalUserObjectBase::PolycrystalUserObjectBase(const InputParameters & parameters)
      66             :   : FeatureFloodCount(parameters),
      67         565 :     _dim(_mesh.dimension()),
      68         565 :     _op_num(_vars.size()),
      69        1130 :     _coloring_algorithm(getParam<MooseEnum>("coloring_algorithm")),
      70         565 :     _colors_assigned(false),
      71        1130 :     _output_adjacency_matrix(getParam<bool>("output_adjacency_matrix")),
      72        1130 :     _num_chunks(FeatureFloodCount::invalid_proc_id)
      73             : {
      74             :   mooseAssert(_single_map_mode, "Do not turn off single_map_mode with this class");
      75         565 : }
      76             : 
      77             : void
      78         510 : PolycrystalUserObjectBase::initialSetup()
      79             : {
      80             :   /**
      81             :    * For polycrystal ICs we need to assume that each of the variables has the same periodicity.
      82             :    * Since BCs are handled elsewhere in the system, we'll have to check this case explicitly.
      83             :    */
      84         510 :   if (_op_num < 1)
      85           0 :     mooseError("No coupled variables found");
      86             : 
      87         510 :   const auto first_variable_value = _mesh.queryPeriodicDimensions(*_vars[0]);
      88        3292 :   for (unsigned int i = 1; i < _vars.size(); ++i)
      89        2782 :     if (_mesh.queryPeriodicDimensions(*_vars[i]) != first_variable_value)
      90           0 :       mooseError("Coupled polycrystal variables differ in periodicity");
      91             : 
      92         510 :   FeatureFloodCount::initialSetup();
      93         510 : }
      94             : 
      95             : void
      96         595 : PolycrystalUserObjectBase::initialize()
      97             : {
      98         595 :   if (_colors_assigned && !_fe_problem.hasInitialAdaptivity())
      99             :     return;
     100             : 
     101             :   _entity_to_grain_cache.clear();
     102             : 
     103         424 :   FeatureFloodCount::initialize();
     104             : }
     105             : 
     106             : void
     107         595 : PolycrystalUserObjectBase::execute()
     108             : {
     109         595 :   if (!_colors_assigned)
     110         406 :     precomputeGrainStructure();
     111             :   // No need to rerun the object if the mesh hasn't changed
     112         378 :   else if (!_fe_problem.hasInitialAdaptivity())
     113         171 :     return;
     114             : 
     115         848 :   TIME_SECTION("execute", 2, "Computing Polycrystal Initial Condition");
     116             : 
     117             :   /**
     118             :    * We need one map per grain when creating the initial condition to support overlapping features.
     119             :    * Luckily, this is a fairly sparse structure.
     120             :    */
     121         424 :   _entities_visited.resize(getNumGrains());
     122             : 
     123             :   /**
     124             :    * This loop is similar to the one found in the base class however, there are two key differences
     125             :    * between building up the initial condition and discovering features based on solution variables:
     126             :    *
     127             :    * 1) When building up the initial condition, we aren't inspecting the actual variable values so
     128             :    *    we don't need to loop over all of the coupled variables.
     129             :    * 2) We want to discover all features on a single pass since there may be thousands of features
     130             :    *    in a simulation. However, we can only actively flood a single feature at a time. To make
     131             :    *    sure that we pick up all features that might start on a given entity, we'll keep retrying
     132             :    *    the flood routine on the same entity as long as new discoveries are being made. We know
     133             :    *    this information from the return value of flood.
     134             :    */
     135      478045 :   for (const auto & current_elem : _fe_problem.getNonlinearEvaluableElementRange())
     136             :   {
     137             :     // Loop over elements or nodes
     138      477621 :     if (_is_elemental)
     139      485253 :       while (flood(current_elem, invalid_size_t))
     140             :         ;
     141             :     else
     142             :     {
     143           0 :       auto n_nodes = current_elem->n_vertices();
     144           0 :       for (auto i = decltype(n_nodes)(0); i < n_nodes; ++i)
     145             :       {
     146           0 :         const Node * current_node = current_elem->node_ptr(i);
     147             : 
     148           0 :         while (flood(current_node, invalid_size_t))
     149             :           ;
     150             :       }
     151             :     }
     152             :   }
     153         424 : }
     154             : 
     155             : processor_id_type
     156         424 : PolycrystalUserObjectBase::numberOfDistributedMergeHelpers() const
     157             : {
     158             :   mooseAssert(_num_chunks != FeatureFloodCount::invalid_proc_id,
     159             :               "prepareDataForTransfer() hasn't been called yet");
     160             : 
     161         424 :   return _num_chunks;
     162             : }
     163             : 
     164             : void
     165         424 : PolycrystalUserObjectBase::prepareDataForTransfer()
     166             : {
     167         424 :   FeatureFloodCount::prepareDataForTransfer();
     168             : 
     169             :   /**
     170             :    * With this class, all of the partial features are crammed into a single "outer" entry in our
     171             :    * data structure because we don't know the variable assignments during the initial condition
     172             :    * (that is the whole point of this class!). However, what we do know is _the_ feature number
     173             :    * that each piece belongs to, which is even more useful for merging. However, with extensive
     174             :    * testing on larger problems, even ordering optimally ordering these pieces is far too much
     175             :    * work for a single core to process. In order create a scalable algorithm, we'll first order
     176             :    * our data, then break it into complete chunks for several processors to work on concurrently.
     177             :    *
     178             :    * After sorting the data structure, it'll look something like this on some rank:
     179             :    * _partial_feature_sets (showing the unique_id and fake variable number):
     180             :    * 0     0 0 0 1 1 1 3 4 4 4 4 4 6 6 ...
     181             :    *
     182             :    * We may very well have gaps in our numbering. This is a local view on one rank.
     183             :    * We'd like to transform the data into something like the following
     184             :    * 0     0 0 0 1 1 1         <- First 3 items here (even when we don't have all features)
     185             :    * 1     3 4 4 4 4 4         <- Next 3 items here
     186             :    * 2     6 6 ...             <- Next 3 or remainder (linear partitioning)
     187             :    *
     188             :    * The way to break up this work is to simply break it into min(n_features, n_procs) chunks.
     189             :    * e.g. If we have 70 features and 8 cores, we'll partition the work into 8 chunks. In the
     190             :    * odd case where we have more processors than features (e.g. 100 processors merging 10 features),
     191             :    * we'll use end up using a subset of the available cores.
     192             :    *
     193             :    * To get all this started we just need the number of features, but we don't normally know
     194             :    * that until we've merged everything together... sigh... Wait! We can fall back on our
     195             :    * sorted data structure though and figure out before we sort and merge. We'll need
     196             :    * one more parallel communication, that won't hurt anything, right?!
     197             :    */
     198         424 :   _partial_feature_sets[0].sort();
     199             : 
     200             :   // Get the largest ID seen on any rank
     201         424 :   auto largest_id = _partial_feature_sets[0].back()._id;
     202         424 :   _communicator.max(largest_id);
     203             :   mooseAssert(largest_id != invalid_size_t, "Largest ID should not be invalid");
     204             : 
     205             :   /**
     206             :    * With this class there are no guarentees that our IDs have a contiguous zero-based numbering.
     207             :    * However, for many of the common derived classes they do (generated grain structures).
     208             :    * If we have holes in our numbering, we might not get an even partition, but it shouldn't break.
     209             :    * We just need the best guess at a total number (before we can actually count) which should
     210             :    * be bounded by the largest_id + 1.
     211             :    */
     212         424 :   auto total_items = largest_id + 1;
     213             : 
     214         424 :   _num_chunks = std::min(_app.n_processors(), total_items);
     215             : 
     216             :   /**
     217             :    * Here we are resizing our data structures that we normally size upon construction. This is to
     218             :    * support the parallel merge capability that's in the FeatureFloodCount class. We'll need to undo
     219             :    * this later, there are a few assumptions built on the sizes of these data structures.
     220             :    *
     221             :    * See FeatureFloodCount::consolidateMergedFeatures for the "un-sizing" of these structures.
     222             :    */
     223         424 :   _partial_feature_sets.resize(_num_chunks);
     224         424 :   _feature_counts_per_map.resize(_num_chunks);
     225             : 
     226        8056 :   for (auto it = _partial_feature_sets[0].begin(); it != _partial_feature_sets[0].end();
     227             :        /* No increment on it*/)
     228             :   {
     229        7632 :     auto chunk = MooseUtils::linearPartitionChunk(total_items, _num_chunks, it->_id);
     230             : 
     231        7632 :     if (chunk)
     232             :     {
     233        2501 :       _partial_feature_sets[chunk].emplace_back(std::move(*it));
     234             :       it = _partial_feature_sets[0].erase(it); // it is incremented here!
     235             :     }
     236             :     else
     237             :       ++it;
     238             :   }
     239         424 : }
     240             : 
     241             : void
     242         595 : PolycrystalUserObjectBase::finalize()
     243             : {
     244         595 :   if (_colors_assigned && !_fe_problem.hasInitialAdaptivity())
     245         171 :     return;
     246             : 
     247         848 :   TIME_SECTION("finalize", 2, "Finalizing Polycrystal Initial Condition");
     248             : 
     249             :   // TODO: Possibly retrieve the halo thickness from the active GrainTracker object?
     250             :   constexpr unsigned int halo_thickness = 2;
     251             : 
     252         424 :   expandEdgeHalos(halo_thickness - 1);
     253             : 
     254         424 :   FeatureFloodCount::finalize();
     255             : 
     256         424 :   if (!_colors_assigned)
     257             :   {
     258             :     // Resize the color assignment vector here. All ranks need a copy of this
     259         406 :     _grain_idx_to_op.resize(_feature_count, PolycrystalUserObjectBase::INVALID_COLOR);
     260         406 :     if (_is_primary)
     261             :     {
     262         273 :       buildGrainAdjacencyMatrix();
     263             : 
     264         273 :       assignOpsToGrains();
     265             : 
     266         269 :       if (_output_adjacency_matrix)
     267         103 :         printGrainAdjacencyMatrix();
     268             :     }
     269             : 
     270             :     // Communicate the coloring map with all ranks
     271         402 :     _communicator.broadcast(_grain_to_op);
     272             : 
     273             :     /**
     274             :      * All ranks: Update the variable indices based on the graph coloring algorithm.
     275             :      */
     276        5890 :     for (auto & feature : _feature_sets)
     277        5488 :       feature._var_index = _grain_to_op.at(feature._id);
     278             :   }
     279             : 
     280         420 :   _colors_assigned = true;
     281         420 : }
     282             : 
     283             : void
     284         424 : PolycrystalUserObjectBase::mergeSets()
     285             : {
     286             :   // When working with _distribute_merge_work all of the maps will be empty except for one
     287        1508 :   for (const auto map_num : index_range(_partial_feature_sets))
     288             :   {
     289             :     /**
     290             :      * With initial conditions we know the grain IDs of every grain (even partial grains). We can
     291             :      * use this information to put all mergeable features adjacent to one and other in the list so
     292             :      * that merging is simply O(n).
     293             :      */
     294        1084 :     _partial_feature_sets[map_num].sort();
     295             : 
     296             :     auto it1 = _partial_feature_sets[map_num].begin();
     297             :     auto it_end = _partial_feature_sets[map_num].end();
     298        8292 :     while (it1 != it_end)
     299             :     {
     300             :       auto it2 = it1;
     301        7632 :       if (++it2 == it_end)
     302             :         break;
     303             : 
     304        7208 :       if (areFeaturesMergeable(*it1, *it2))
     305             :       {
     306        4144 :         it1->merge(std::move(*it2));
     307             :         _partial_feature_sets[map_num].erase(it2);
     308             :       }
     309             :       else
     310             :         ++it1; // Only increment if we have a mismatch
     311             :     }
     312             :   }
     313         424 : }
     314             : 
     315             : void
     316         136 : PolycrystalUserObjectBase::restoreOriginalDataStructures(std::vector<std::list<FeatureData>> & orig)
     317             : {
     318             :   // Move all the data back into the first list
     319             :   auto & master_list = orig[0];
     320         660 :   for (MooseIndex(_maps_size) map_num = 1; map_num < orig.size(); ++map_num)
     321             :   {
     322         524 :     master_list.splice(master_list.end(), orig[map_num]);
     323             :     orig[map_num].clear();
     324             :   }
     325             : 
     326         136 :   orig.resize(1);
     327         136 : }
     328             : 
     329             : bool
     330     1252021 : PolycrystalUserObjectBase::isNewFeatureOrConnectedRegion(const DofObject * dof_object,
     331             :                                                          std::size_t & current_index,
     332             :                                                          FeatureData *& feature,
     333             :                                                          Status & status,
     334             :                                                          unsigned int & new_id)
     335             : {
     336             :   mooseAssert(_t_step == 0, "PolyIC only works if we begin in the initial condition");
     337             : 
     338             :   // Retrieve the id of the current entity
     339     1252021 :   auto entity_id = dof_object->id();
     340             :   auto grains_it = _entity_to_grain_cache.lower_bound(entity_id);
     341             : 
     342     1252021 :   if (grains_it == _entity_to_grain_cache.end() || grains_it->first != entity_id)
     343             :   {
     344             :     std::vector<unsigned int> grain_ids;
     345             : 
     346      386897 :     if (_is_elemental)
     347      386897 :       getGrainsBasedOnElem(*static_cast<const Elem *>(dof_object), grain_ids);
     348             :     else
     349           0 :       getGrainsBasedOnPoint(*static_cast<const Node *>(dof_object), grain_ids);
     350             : 
     351      386897 :     grains_it = _entity_to_grain_cache.emplace_hint(grains_it, entity_id, std::move(grain_ids));
     352      386897 :   }
     353             : 
     354             :   /**
     355             :    * When building the IC, we can't use the _entities_visited data structure the same way as we do
     356             :    * for the base class. We need to discover multiple overlapping grains in a single pass. However
     357             :    * we don't know what grain we are working on when we enter the flood routine (when that check is
     358             :    * normally made). Only after we've made the callback to the child class do we know which grains
     359             :    * we are operating on (at least until we've triggered the recursion). We need to see if there
     360             :    * is at least one active grain where we haven't already visited the current entity before
     361             :    * continuing.
     362             :    */
     363     1252021 :   auto saved_grain_id = invalid_id;
     364     1252021 :   if (current_index == invalid_size_t)
     365             :   {
     366      992450 :     for (auto grain_id : grains_it->second)
     367             :     {
     368             :       auto map_it = _grain_to_op.find(grain_id);
     369             :       mooseAssert(!_colors_assigned || map_it != _grain_to_op.end(), "grain_id missing");
     370      514829 :       auto map_num = _colors_assigned ? map_it->second : grain_id;
     371             : 
     372      514829 :       if (_entities_visited[map_num].find(entity_id) == _entities_visited[map_num].end())
     373             :       {
     374             :         saved_grain_id = grain_id;
     375        7632 :         current_index = map_num;
     376             :         break;
     377             :       }
     378             :     }
     379             : 
     380      485253 :     if (current_index == invalid_size_t)
     381             :       return false;
     382             :   }
     383      766768 :   else if (_entities_visited[current_index].find(entity_id) !=
     384             :            _entities_visited[current_index].end())
     385             :     return false;
     386             : 
     387      774400 :   if (!feature)
     388             :   {
     389        7632 :     new_id = saved_grain_id;
     390             :     status &= ~Status::INACTIVE;
     391             : 
     392        7632 :     return true;
     393             :   }
     394             :   else
     395             :   {
     396             :     const auto & grain_ids = grains_it->second;
     397      766768 :     if (std::find(grain_ids.begin(), grain_ids.end(), feature->_id) != grain_ids.end())
     398             :       return true;
     399             : 
     400             :     /**
     401             :      * If we get here the current entity is not part of the active feature, however we now want to
     402             :      * look at neighbors.
     403             :      *
     404             :      */
     405      267917 :     if (_is_elemental)
     406             :     {
     407      267917 :       Elem * elem = _mesh.queryElemPtr(entity_id);
     408             :       mooseAssert(elem, "Element is nullptr");
     409             : 
     410             :       std::vector<const Elem *> all_active_neighbors;
     411      267917 :       MeshBase & mesh = _mesh.getMesh();
     412             : 
     413     1349805 :       for (auto i = decltype(elem->n_neighbors())(0); i < elem->n_neighbors(); ++i)
     414             :       {
     415             :         const Elem * neighbor_ancestor = nullptr;
     416             : 
     417             :         /**
     418             :          * Retrieve only the active neighbors for each side of this element, append them to the list
     419             :          * of active neighbors
     420             :          */
     421             :         neighbor_ancestor = elem->neighbor_ptr(i);
     422             : 
     423     1081888 :         if (neighbor_ancestor)
     424             :         {
     425             :           /**
     426             :            * In general, {evaluable elements} >= {local elements} U {algebraic ghosting elements}.
     427             :            * That is, the number of evaluable elements does NOT necessarily equal to the number of
     428             :            * local and algebraic ghosting elements. The neighbors of evaluable elements can be
     429             :            * remote even though we have two layers of geometric ghosting elements.
     430             :            */
     431     1052672 :           if (neighbor_ancestor->is_remote())
     432           0 :             continue;
     433             : 
     434     1052672 :           neighbor_ancestor->active_family_tree_by_neighbor(all_active_neighbors, elem, false);
     435             :         }
     436             :         else // if (expand_halos_only /*&& feature->_periodic_nodes.empty()*/)
     437             :         {
     438       29216 :           neighbor_ancestor = elem->topological_neighbor(i, mesh, *_point_locator, _pbs);
     439             : 
     440             :           /**
     441             :            * If the current element (passed into this method) doesn't have a connected neighbor but
     442             :            * does have a topological neighbor, this might be a new disjoint region that we'll
     443             :            * need to represent with a separate bounding box. To find out for sure, we'll need
     444             :            * see if the new neighbors are present in any of the halo or disjoint halo sets. If
     445             :            * they are not present, this is a new region.
     446             :            */
     447       29216 :           if (neighbor_ancestor)
     448             :           {
     449             :             /**
     450             :              * In general, {evaluable elements} >= {local elements} U {algebraic ghosting elements}.
     451             :              * That is, the number of evaluable elements does NOT necessarily equal to the number of
     452             :              * local and algebraic ghosting elements. The neighbors of evaluable elements can be
     453             :              * remote even though we have two layers of geometric ghosting elements.
     454             :              */
     455       10682 :             if (neighbor_ancestor->is_remote())
     456           0 :               continue;
     457             : 
     458       10682 :             neighbor_ancestor->active_family_tree_by_topological_neighbor(
     459       10682 :                 all_active_neighbors, elem, mesh, *_point_locator, _pbs, false);
     460             :           }
     461             :         }
     462             :       }
     463             : 
     464     1036685 :       for (const auto neighbor : all_active_neighbors)
     465             :       {
     466             :         // Retrieve the id of the current entity
     467      876251 :         auto neighbor_id = neighbor->id();
     468             :         auto neighbor_it = _entity_to_grain_cache.lower_bound(neighbor_id);
     469             : 
     470      876251 :         if (neighbor_it == _entity_to_grain_cache.end() || neighbor_it->first != neighbor_id)
     471             :         {
     472             :           std::vector<unsigned int> more_grain_ids;
     473             : 
     474       92211 :           getGrainsBasedOnElem(*static_cast<const Elem *>(neighbor), more_grain_ids);
     475             : 
     476       92211 :           neighbor_it = _entity_to_grain_cache.emplace_hint(
     477             :               neighbor_it, neighbor_id, std::move(more_grain_ids));
     478       92211 :         }
     479             : 
     480             :         const auto & more_grain_ids = neighbor_it->second;
     481      876251 :         if (std::find(more_grain_ids.begin(), more_grain_ids.end(), feature->_id) !=
     482             :             more_grain_ids.end())
     483      107483 :           return true;
     484             :       }
     485      267917 :     }
     486             : 
     487      160434 :     return false;
     488             :   }
     489             : }
     490             : 
     491             : bool
     492        7208 : PolycrystalUserObjectBase::areFeaturesMergeable(const FeatureData & f1,
     493             :                                                 const FeatureData & f2) const
     494             : {
     495        7208 :   if (f1._id != f2._id)
     496        3064 :     return false;
     497             : 
     498             :   mooseAssert(f1._var_index == f2._var_index, "Feature should be mergeable but aren't");
     499             :   return true;
     500             : }
     501             : 
     502             : void
     503         273 : PolycrystalUserObjectBase::buildGrainAdjacencyMatrix()
     504             : {
     505             :   mooseAssert(_is_primary, "This routine should only be called on the primary rank");
     506             : 
     507         546 :   _adjacency_matrix = std::make_unique<DenseMatrix<Real>>(_feature_count, _feature_count);
     508        3641 :   for (MooseIndex(_feature_sets) i = 0; i < _feature_sets.size(); ++i)
     509             :   {
     510       38921 :     for (MooseIndex(_feature_sets) j = i + 1; j < _feature_sets.size(); ++j)
     511             :     {
     512       54176 :       if (_feature_sets[i].boundingBoxesIntersect(_feature_sets[j]) &&
     513       18623 :           _feature_sets[i].halosIntersect(_feature_sets[j]))
     514             :       {
     515             :         // Our grain adjacency matrix is symmetrical
     516       12398 :         (*_adjacency_matrix)(i, j) = 1;
     517       12398 :         (*_adjacency_matrix)(j, i) = 1;
     518             :       }
     519             :     }
     520             :   }
     521         273 : }
     522             : 
     523             : void
     524         273 : PolycrystalUserObjectBase::assignOpsToGrains()
     525             : {
     526             :   mooseAssert(_is_primary, "This routine should only be called on the primary rank");
     527             : 
     528         546 :   TIME_SECTION("assignOpsToGrains", 2, "Assigning OPs to grains");
     529             : 
     530             :   // Use a simple backtracking coloring algorithm
     531         273 :   if (_coloring_algorithm == "bt")
     532             :   {
     533         170 :     paramInfo("coloring_algorithm",
     534             :               "The backtracking algorithm has exponential complexity. If you are using very few "
     535             :               "order parameters,\nor you have several hundred grains or more, you should use one "
     536             :               "of the PETSc coloring algorithms such as \"jp\".");
     537             : 
     538         170 :     if (!colorGraph(0))
     539           2 :       paramError("op_num",
     540             :                  "Unable to find a valid grain to op coloring, Make sure you have created enough "
     541             :                  "variables to hold a\nvalid polycrystal initial condition (no grains represented "
     542             :                  "by the same variable should be allowed to\ntouch, ~8 for 2D, ~25 for 3D)?");
     543             :   }
     544             :   else // PETSc Coloring algorithms
     545             :   {
     546             :     const std::string & ca_str = _coloring_algorithm;
     547             :     Real * am_data = _adjacency_matrix->get_values().data();
     548             : 
     549             :     try
     550             :     {
     551         103 :       Moose::PetscSupport::colorAdjacencyMatrix(
     552         103 :           am_data, _feature_count, _vars.size(), _grain_idx_to_op, ca_str.c_str());
     553             :     }
     554           2 :     catch (std::runtime_error & e)
     555             :     {
     556           2 :       paramError("op_num",
     557             :                  "Unable to find a valid grain to op coloring, Make sure you have created enough "
     558             :                  "variables to hold a\nvalid polycrystal initial condition (no grains represented "
     559             :                  "by the same variable should be allowed to\ntouch, ~8 for 2D, ~25 for 3D)?");
     560           0 :     }
     561             :   }
     562             : 
     563             :   /**
     564             :    * Now we have a vector giving us a coloring based on the indices in our features, but we need to
     565             :    * build a map in case our features have non-contiguous IDs.
     566             :    */
     567             :   mooseAssert(_grain_to_op.empty(), "grain_to_op data structure should be empty here");
     568        3497 :   for (MooseIndex(_grain_idx_to_op) i = 0; i < _grain_idx_to_op.size(); ++i)
     569        3228 :     _grain_to_op.emplace_hint(_grain_to_op.end(), _feature_sets[i]._id, _grain_idx_to_op[i]);
     570         269 : }
     571             : 
     572             : bool
     573    58225596 : PolycrystalUserObjectBase::colorGraph(unsigned int vertex)
     574             : {
     575             :   // Base case: All grains are assigned
     576    58225596 :   if (vertex == _feature_count)
     577             :     return true;
     578             : 
     579             :   // Consider this grain and try different ops
     580   582064384 :   for (unsigned int color_idx = 0; color_idx < _op_num; ++color_idx)
     581             :   {
     582             :     // We'll try to spread these colors around a bit rather than
     583             :     // packing them all on the first few colors if we have several colors.
     584   523841156 :     unsigned int color = (vertex + color_idx) % _op_num;
     585             : 
     586   523841156 :     if (isGraphValid(vertex, color))
     587             :     {
     588    58225426 :       _grain_idx_to_op[vertex] = color;
     589             : 
     590    58225426 :       if (colorGraph(vertex + 1))
     591             :         return true;
     592             : 
     593             :       // Backtrack...
     594    58223226 :       _grain_idx_to_op[vertex] = PolycrystalUserObjectBase::INVALID_COLOR;
     595             :     }
     596             :   }
     597             : 
     598             :   return false;
     599             : }
     600             : 
     601             : bool
     602   523841156 : PolycrystalUserObjectBase::isGraphValid(unsigned int vertex, unsigned int color)
     603             : {
     604             :   // See if the proposed color is valid based on the current neighbor colors
     605  8050911539 :   for (unsigned int neighbor = 0; neighbor < _feature_count; ++neighbor)
     606  7992686113 :     if ((*_adjacency_matrix)(vertex, neighbor) && color == _grain_idx_to_op[neighbor])
     607             :       return false;
     608             :   return true;
     609             : }
     610             : 
     611             : void
     612         103 : PolycrystalUserObjectBase::printGrainAdjacencyMatrix() const
     613             : {
     614         103 :   _console << "Grain Adjacency Matrix:\n";
     615        1856 :   for (unsigned int i = 0; i < _adjacency_matrix->m(); i++)
     616             :   {
     617       47808 :     for (unsigned int j = 0; j < _adjacency_matrix->n(); j++)
     618       46055 :       _console << _adjacency_matrix->el(i, j) << "  ";
     619        1753 :     _console << '\n';
     620             :   }
     621             : 
     622         103 :   _console << "Grain to OP assignments:\n";
     623        1856 :   for (auto op : _grain_idx_to_op)
     624        1753 :     _console << op << "  ";
     625         103 :   _console << '\n' << std::endl;
     626         103 : }
     627             : 
     628             : MooseEnum
     629        1130 : PolycrystalUserObjectBase::coloringAlgorithms()
     630             : {
     631        2260 :   return MooseEnum("jp power greedy bt", "jp");
     632             : }
     633             : 
     634             : std::string
     635        1130 : PolycrystalUserObjectBase::coloringAlgorithmDescriptions()
     636             : {
     637             :   return "The grain neighbor graph coloring algorithm to use: \"jp\" (DEFAULT) Jones and "
     638             :          "Plassmann, an efficient coloring algorithm, \"power\" an alternative stochastic "
     639             :          "algorithm, \"greedy\", a greedy assignment algorithm with stochastic updates to "
     640             :          "guarantee a valid coloring, \"bt\", a back tracking algorithm that produces good "
     641             :          "distributions but may experience exponential run time in the worst case scenario "
     642        1130 :          "(works well on medium to large 2D problems)";
     643             : }
     644             : 
     645             : const unsigned int PolycrystalUserObjectBase::INVALID_COLOR =
     646             :     std::numeric_limits<unsigned int>::max();
     647             : const unsigned int PolycrystalUserObjectBase::HALO_THICKNESS = 4;

Generated by: LCOV version 1.14