https://mooseframework.inl.gov
PolycrystalUserObjectBase.C
Go to the documentation of this file.
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"
12 #include "NonlinearSystemBase.h"
13 #include "MooseMesh.h"
14 #include "MooseVariable.h"
15 
16 #include <vector>
17 #include <map>
18 #include <algorithm>
19 
22 {
24  params.addClassDescription("This object provides the base capability for creating proper reduced "
25  "order parameter polycrystal initial conditions.");
27  "variable", "var_name_base", "op_num", "Array of coupled variables");
28  params.addParam<bool>("output_adjacency_matrix",
29  false,
30  "Output the Grain Adjacency Matrix used in the coloring algorithms. "
31  "Additionally, the grain to OP assignments will be printed");
32  params.addParam<MooseEnum>("coloring_algorithm",
35 
36  // FeatureFloodCount adds a relationship manager, but we need to extend that for PolycrystalIC
38 
40  "ElementSideNeighborLayers",
42 
43  [](const InputParameters & /*obj_params*/, InputParameters & rm_params)
44  { rm_params.set<unsigned short>("layers") = 2; }
45 
46  );
47 
48  params.addRelationshipManager("ElementSideNeighborLayers",
50 
51  // Hide the output of the IC objects by default, it doesn't change over time
52  params.set<std::vector<OutputName>>("outputs") = {"none"};
53 
55  params.set<bool>("allow_duplicate_execution_on_initial") = true;
56 
57  // This object should only be executed _before_ the initial condition
59  execute_options = EXEC_INITIAL;
60  params.set<ExecFlagEnum>("execute_on") = execute_options;
61 
62  return params;
63 }
64 
66  : FeatureFloodCount(parameters),
67  _dim(_mesh.dimension()),
68  _op_num(_vars.size()),
69  _coloring_algorithm(getParam<MooseEnum>("coloring_algorithm")),
70  _colors_assigned(false),
71  _output_adjacency_matrix(getParam<bool>("output_adjacency_matrix")),
72  _num_chunks(FeatureFloodCount::invalid_proc_id)
73 {
74  mooseAssert(_single_map_mode, "Do not turn off single_map_mode with this class");
75 }
76 
77 void
79 {
84  if (_op_num < 1)
85  mooseError("No coupled variables found");
86 
87  const auto first_variable_value = _mesh.queryPeriodicDimensions(*_vars[0]);
88  for (unsigned int i = 1; i < _vars.size(); ++i)
89  if (_mesh.queryPeriodicDimensions(*_vars[i]) != first_variable_value)
90  mooseError("Coupled polycrystal variables differ in periodicity");
91 
93 }
94 
95 void
97 {
99  return;
100 
101  _entity_to_grain_cache.clear();
102 
104 }
105 
106 void
108 {
109  if (!_colors_assigned)
111  // No need to rerun the object if the mesh hasn't changed
112  else if (!_fe_problem.hasInitialAdaptivity())
113  return;
114 
115  TIME_SECTION("execute", 2, "Computing Polycrystal Initial Condition");
116 
122 
135  for (const auto & current_elem : _fe_problem.getNonlinearEvaluableElementRange())
136  {
137  // Loop over elements or nodes
138  if (_is_elemental)
139  while (flood(current_elem, invalid_size_t))
140  ;
141  else
142  {
143  auto n_nodes = current_elem->n_vertices();
144  for (auto i = decltype(n_nodes)(0); i < n_nodes; ++i)
145  {
146  const Node * current_node = current_elem->node_ptr(i);
147 
148  while (flood(current_node, invalid_size_t))
149  ;
150  }
151  }
152  }
153 }
154 
157 {
159  "prepareDataForTransfer() hasn't been called yet");
160 
161  return _num_chunks;
162 }
163 
164 void
166 {
168 
198  _partial_feature_sets[0].sort();
199 
200  // Get the largest ID seen on any rank
201  auto largest_id = _partial_feature_sets[0].back()._id;
202  _communicator.max(largest_id);
203  mooseAssert(largest_id != invalid_size_t, "Largest ID should not be invalid");
204 
212  auto total_items = largest_id + 1;
213 
214  _num_chunks = std::min(_app.n_processors(), total_items);
215 
225 
226  for (auto it = _partial_feature_sets[0].begin(); it != _partial_feature_sets[0].end();
227  /* No increment on it*/)
228  {
229  auto chunk = MooseUtils::linearPartitionChunk(total_items, _num_chunks, it->_id);
230 
231  if (chunk)
232  {
233  _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 }
240 
241 void
243 {
245  return;
246 
247  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  expandEdgeHalos(halo_thickness - 1);
253 
255 
256  if (!_colors_assigned)
257  {
258  // Resize the color assignment vector here. All ranks need a copy of this
260  if (_is_primary)
261  {
263 
265 
268  }
269 
270  // Communicate the coloring map with all ranks
272 
276  for (auto & feature : _feature_sets)
277  feature._var_index = _grain_to_op.at(feature._id);
278  }
279 
280  _colors_assigned = true;
281 }
282 
283 void
285 {
286  // When working with _distribute_merge_work all of the maps will be empty except for one
287  for (const auto map_num : index_range(_partial_feature_sets))
288  {
294  _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  while (it1 != it_end)
299  {
300  auto it2 = it1;
301  if (++it2 == it_end)
302  break;
303 
304  if (areFeaturesMergeable(*it1, *it2))
305  {
306  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 }
314 
315 void
316 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  for (MooseIndex(_maps_size) map_num = 1; map_num < orig.size(); ++map_num)
321  {
322  master_list.splice(master_list.end(), orig[map_num]);
323  orig[map_num].clear();
324  }
325 
326  orig.resize(1);
327 }
328 
329 bool
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  auto entity_id = dof_object->id();
340  auto grains_it = _entity_to_grain_cache.lower_bound(entity_id);
341 
342  if (grains_it == _entity_to_grain_cache.end() || grains_it->first != entity_id)
343  {
344  std::vector<unsigned int> grain_ids;
345 
346  if (_is_elemental)
347  getGrainsBasedOnElem(*static_cast<const Elem *>(dof_object), grain_ids);
348  else
349  getGrainsBasedOnPoint(*static_cast<const Node *>(dof_object), grain_ids);
350 
351  grains_it = _entity_to_grain_cache.emplace_hint(grains_it, entity_id, std::move(grain_ids));
352  }
353 
363  auto saved_grain_id = invalid_id;
364  if (current_index == invalid_size_t)
365  {
366  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  auto map_num = _colors_assigned ? map_it->second : grain_id;
371 
372  if (_entities_visited[map_num].find(entity_id) == _entities_visited[map_num].end())
373  {
374  saved_grain_id = grain_id;
375  current_index = map_num;
376  break;
377  }
378  }
379 
380  if (current_index == invalid_size_t)
381  return false;
382  }
383  else if (_entities_visited[current_index].find(entity_id) !=
384  _entities_visited[current_index].end())
385  return false;
386 
387  if (!feature)
388  {
389  new_id = saved_grain_id;
390  status &= ~Status::INACTIVE;
391 
392  return true;
393  }
394  else
395  {
396  const auto & grain_ids = grains_it->second;
397  if (std::find(grain_ids.begin(), grain_ids.end(), feature->_id) != grain_ids.end())
398  return true;
399 
405  if (_is_elemental)
406  {
407  Elem * elem = _mesh.queryElemPtr(entity_id);
408  mooseAssert(elem, "Element is nullptr");
409 
410  std::vector<const Elem *> all_active_neighbors;
411  MeshBase & mesh = _mesh.getMesh();
412 
413  for (auto i = decltype(elem->n_neighbors())(0); i < elem->n_neighbors(); ++i)
414  {
415  const Elem * neighbor_ancestor = nullptr;
416 
421  neighbor_ancestor = elem->neighbor_ptr(i);
422 
423  if (neighbor_ancestor)
424  {
431  if (neighbor_ancestor->is_remote())
432  continue;
433 
434  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  neighbor_ancestor = elem->topological_neighbor(i, mesh, *_point_locator, _pbs);
439 
447  if (neighbor_ancestor)
448  {
455  if (neighbor_ancestor->is_remote())
456  continue;
457 
458  neighbor_ancestor->active_family_tree_by_topological_neighbor(
459  all_active_neighbors, elem, mesh, *_point_locator, _pbs, false);
460  }
461  }
462  }
463 
464  for (const auto neighbor : all_active_neighbors)
465  {
466  // Retrieve the id of the current entity
467  auto neighbor_id = neighbor->id();
468  auto neighbor_it = _entity_to_grain_cache.lower_bound(neighbor_id);
469 
470  if (neighbor_it == _entity_to_grain_cache.end() || neighbor_it->first != neighbor_id)
471  {
472  std::vector<unsigned int> more_grain_ids;
473 
474  getGrainsBasedOnElem(*static_cast<const Elem *>(neighbor), more_grain_ids);
475 
476  neighbor_it = _entity_to_grain_cache.emplace_hint(
477  neighbor_it, neighbor_id, std::move(more_grain_ids));
478  }
479 
480  const auto & more_grain_ids = neighbor_it->second;
481  if (std::find(more_grain_ids.begin(), more_grain_ids.end(), feature->_id) !=
482  more_grain_ids.end())
483  return true;
484  }
485  }
486 
487  return false;
488  }
489 }
490 
491 bool
493  const FeatureData & f2) const
494 {
495  if (f1._id != f2._id)
496  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
504 {
505  mooseAssert(_is_primary, "This routine should only be called on the primary rank");
506 
507  _adjacency_matrix = std::make_unique<DenseMatrix<Real>>(_feature_count, _feature_count);
508  for (MooseIndex(_feature_sets) i = 0; i < _feature_sets.size(); ++i)
509  {
510  for (MooseIndex(_feature_sets) j = i + 1; j < _feature_sets.size(); ++j)
511  {
512  if (_feature_sets[i].boundingBoxesIntersect(_feature_sets[j]) &&
513  _feature_sets[i].halosIntersect(_feature_sets[j]))
514  {
515  // Our grain adjacency matrix is symmetrical
516  (*_adjacency_matrix)(i, j) = 1;
517  (*_adjacency_matrix)(j, i) = 1;
518  }
519  }
520  }
521 }
522 
523 void
525 {
526  mooseAssert(_is_primary, "This routine should only be called on the primary rank");
527 
528  TIME_SECTION("assignOpsToGrains", 2, "Assigning OPs to grains");
529 
530  // Use a simple backtracking coloring algorithm
531  if (_coloring_algorithm == "bt")
532  {
533  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  if (!colorGraph(0))
539  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  {
552  am_data, _feature_count, _vars.size(), _grain_idx_to_op, ca_str.c_str());
553  }
554  catch (std::runtime_error & e)
555  {
556  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  }
561  }
562 
567  mooseAssert(_grain_to_op.empty(), "grain_to_op data structure should be empty here");
568  for (MooseIndex(_grain_idx_to_op) i = 0; i < _grain_idx_to_op.size(); ++i)
569  _grain_to_op.emplace_hint(_grain_to_op.end(), _feature_sets[i]._id, _grain_idx_to_op[i]);
570 }
571 
572 bool
574 {
575  // Base case: All grains are assigned
576  if (vertex == _feature_count)
577  return true;
578 
579  // Consider this grain and try different ops
580  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  unsigned int color = (vertex + color_idx) % _op_num;
585 
586  if (isGraphValid(vertex, color))
587  {
588  _grain_idx_to_op[vertex] = color;
589 
590  if (colorGraph(vertex + 1))
591  return true;
592 
593  // Backtrack...
595  }
596  }
597 
598  return false;
599 }
600 
601 bool
602 PolycrystalUserObjectBase::isGraphValid(unsigned int vertex, unsigned int color)
603 {
604  // See if the proposed color is valid based on the current neighbor colors
605  for (unsigned int neighbor = 0; neighbor < _feature_count; ++neighbor)
606  if ((*_adjacency_matrix)(vertex, neighbor) && color == _grain_idx_to_op[neighbor])
607  return false;
608  return true;
609 }
610 
611 void
613 {
614  _console << "Grain Adjacency Matrix:\n";
615  for (unsigned int i = 0; i < _adjacency_matrix->m(); i++)
616  {
617  for (unsigned int j = 0; j < _adjacency_matrix->n(); j++)
618  _console << _adjacency_matrix->el(i, j) << " ";
619  _console << '\n';
620  }
621 
622  _console << "Grain to OP assignments:\n";
623  for (auto op : _grain_idx_to_op)
624  _console << op << " ";
625  _console << '\n' << std::endl;
626 }
627 
628 MooseEnum
630 {
631  return MooseEnum("jp power greedy bt", "jp");
632 }
633 
634 std::string
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  "(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;
virtual void finalize() override
std::unique_ptr< DenseMatrix< Real > > _adjacency_matrix
The dense adjacency matrix.
void clearRelationshipManagers()
void buildGrainAdjacencyMatrix()
Builds a dense adjacency matrix based on the discovery of grain neighbors and halos surrounding each ...
KOKKOS_INLINE_FUNCTION const T * find(const T &target, const T *const begin, const T *const end)
void expandEdgeHalos(unsigned int num_layers_to_expand)
This method expands the existing halo set by some width determined by the passed in value...
static std::string coloringAlgorithmDescriptions()
Returns corresponding descriptions of available coloring algorithms.
std::map< unsigned int, unsigned int > _grain_to_op
A map of the grain_id to op.
static const std::size_t invalid_size_t
static InputParameters validParams()
void paramError(const std::string &param, Args... args) const
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
PolycrystalUserObjectBase(const InputParameters &parameters)
Status
This enumeration is used to indicate status of the grains in the _unique_grains data structure...
std::vector< std::set< dof_id_type > > _entities_visited
This variable keeps track of which nodes have been visited during execution.
virtual bool areFeaturesMergeable(const FeatureData &f1, const FeatureData &f2) const override
Method for determining whether two features are mergeable.
T & set(const std::string &name, bool quiet_mode=false)
libMesh::PeriodicBoundaries * _pbs
A pointer to the periodic boundary constraints object.
virtual void finalize() override
std::vector< FeatureData > & _feature_sets
The data structure used to hold the globally unique features.
MeshBase & mesh
virtual void getGrainsBasedOnPoint(const Point &point, std::vector< unsigned int > &grains) const =0
Method for retrieving active grain IDs based on some point in the mesh.
std::map< dof_id_type, std::vector< unsigned int > > _entity_to_grain_cache
void assignOpsToGrains()
Method that runs a coloring algorithm to assign OPs to grains.
const bool _is_primary
Convenience variable for testing primary rank.
virtual void initialize() override
const Parallel::Communicator & _communicator
void addRelationshipManager(const std::string &name, Moose::RelationshipManagerType rm_type, Moose::RelationshipManagerInputParameterCallback input_parameter_callback=nullptr)
void printGrainAdjacencyMatrix() const
Prints out the adjacency matrix in a nicely spaced integer format.
virtual Elem * queryElemPtr(const dof_id_type i)
ExecFlagEnum getDefaultExecFlagEnum()
virtual void getGrainsBasedOnElem(const Elem &elem, std::vector< unsigned int > &grains) const
This method may be defined in addition to the point based initialization to speed up lookups...
MPI_Status status
FEProblemBase & _fe_problem
uint8_t processor_id_type
processor_id_type n_processors() const
const dof_id_type n_nodes
const unsigned int _op_num
The maximum number of order parameters (colors) available to assign to the grain structure.
processor_id_type _num_chunks
The number of chunks (for merging the features together)
std::vector< MooseVariable * > _vars
The vector of coupled in variables cast to MooseVariable.
static const unsigned int HALO_THICKNESS
Used to hold the thickness of the halo that should be constructed for detecting adjacency.
MeshBase & getMesh()
bool isGraphValid(unsigned int vertex, unsigned int color)
Helper method for the back-tracking graph coloring algorithm.
std::size_t _var_index
The Moose variable where this feature was found (often the "order parameter")
virtual void precomputeGrainStructure()
This callback is triggered after the object is initialized and may be optionally overridden to do pre...
const std::array< bool, 3 > & queryPeriodicDimensions(const unsigned int sys_num, const unsigned int var_num) const
bool _colors_assigned
A Boolean indicating whether the object has assigned colors to grains (internal use) ...
This object will mark nodes or elements of continuous regions all with a unique number for the purpos...
bool hasInitialAdaptivity() const
const std::size_t _maps_size
Convenience variable holding the size of all the datastructures size by the number of maps...
static const unsigned int invalid_id
const bool _single_map_mode
This variable is used to indicate whether or not multiple maps are used during flooding.
unsigned int _feature_count
The number of features seen by this object (same as summing _feature_counts_per_map) ...
void broadcast(T &data, const unsigned int root_id=0, const bool identical_sizes=false) const
std::vector< unsigned int > _grain_idx_to_op
A vector indicating which op is assigned to each grain (by index of the grain)
bool colorGraph(unsigned int vertex)
Built-in simple "back-tracking" algorithm to assign colors to a graph.
processor_id_type linearPartitionChunk(dof_id_type num_items, dof_id_type num_chunks, dof_id_type item_id)
std::vector< std::list< FeatureData > > _partial_feature_sets
The data structure used to hold partial and communicated feature data, during the discovery and mergi...
virtual bool isNewFeatureOrConnectedRegion(const DofObject *dof_object, std::size_t &current_index, FeatureData *&feature, Status &status, unsigned int &new_id) override
Method called during the recursive flood routine that should return whether or not the current entity...
virtual void prepareDataForTransfer() override
This routine uses the local flooded data to build up the local feature data structures (_partial feat...
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
const bool _is_elemental
Determines if the flood counter is elements or not (nodes)
void addRequiredCoupledVarWithAutoBuild(const std::string &name, const std::string &base_name, const std::string &num_name, const std::string &doc_string)
bool flood(const DofObject *dof_object, std::size_t current_index)
This method will check if the current entity is above the supplied threshold and "mark" it...
void max(const T &r, T &o, Request &req) const
void colorAdjacencyMatrix(PetscScalar *adjacency_matrix, unsigned int size, unsigned int colors, std::vector< unsigned int > &vertex_colors, const char *coloring_algorithm)
void mooseError(Args &&... args) const
std::vector< unsigned int > _feature_counts_per_map
The number of features seen by this object per map.
virtual void mergeSets() override
This routine is called on the primary rank only and stitches together the partial feature pieces seen...
static const processor_id_type invalid_proc_id
void addClassDescription(const std::string &doc_string)
static const std::complex< double > j(0, 1)
Complex number "j" (also known as "i")
virtual void initialSetup() override
virtual unsigned int getNumGrains() const =0
Must be overridden by the deriving class to provide the number of grains in the polycrystal structure...
virtual void initialize() override
const bool _output_adjacency_matrix
A user controllable Boolean which can be used to print the adjacency matrix to the console...
const ConsoleStream _console
static const unsigned int INVALID_COLOR
Used to indicate an invalid coloring for the built-in back-tracking algorithm.
MooseMesh & _mesh
A reference to the mesh.
const libMesh::ConstElemRange & getNonlinearEvaluableElementRange()
virtual processor_id_type numberOfDistributedMergeHelpers() const override
Returns a number indicating the number of merge helpers when running in parallel based on certain imp...
auto index_range(const T &sizable)
std::unique_ptr< libMesh::PointLocatorBase > _point_locator
virtual void restoreOriginalDataStructures(std::vector< std::list< FeatureData >> &orig) override
unsigned int _id
An ID for this feature.
const MooseEnum _coloring_algorithm
The selected graph coloring algorithm used by this object.
static InputParameters validParams()
void paramInfo(const std::string &param, Args... args) const
virtual void initialSetup() override
UserObject interface overrides.
virtual void prepareDataForTransfer()
This routine uses the local flooded data to build up the local feature data structures (_partial feat...
const ExecFlagType EXEC_INITIAL