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  for (unsigned int dim = 0; dim < _dim; ++dim)
88  {
89  bool first_variable_value = _mesh.isTranslatedPeriodic(_vars[0]->number(), dim);
90 
91  for (unsigned int i = 1; i < _vars.size(); ++i)
92  if (_mesh.isTranslatedPeriodic(_vars[i]->number(), dim) != first_variable_value)
93  mooseError("Coupled polycrystal variables differ in periodicity");
94  }
95 
97 }
98 
99 void
101 {
103  return;
104 
105  _entity_to_grain_cache.clear();
106 
108 }
109 
110 void
112 {
113  if (!_colors_assigned)
115  // No need to rerun the object if the mesh hasn't changed
116  else if (!_fe_problem.hasInitialAdaptivity())
117  return;
118 
119  TIME_SECTION("execute", 2, "Computing Polycrystal Initial Condition");
120 
126 
139  for (const auto & current_elem : _fe_problem.getNonlinearEvaluableElementRange())
140  {
141  // Loop over elements or nodes
142  if (_is_elemental)
143  while (flood(current_elem, invalid_size_t))
144  ;
145  else
146  {
147  auto n_nodes = current_elem->n_vertices();
148  for (auto i = decltype(n_nodes)(0); i < n_nodes; ++i)
149  {
150  const Node * current_node = current_elem->node_ptr(i);
151 
152  while (flood(current_node, invalid_size_t))
153  ;
154  }
155  }
156  }
157 }
158 
161 {
163  "prepareDataForTransfer() hasn't been called yet");
164 
165  return _num_chunks;
166 }
167 
168 void
170 {
172 
202  _partial_feature_sets[0].sort();
203 
204  // Get the largest ID seen on any rank
205  auto largest_id = _partial_feature_sets[0].back()._id;
206  _communicator.max(largest_id);
207  mooseAssert(largest_id != invalid_size_t, "Largest ID should not be invalid");
208 
216  auto total_items = largest_id + 1;
217 
218  _num_chunks = std::min(_app.n_processors(), total_items);
219 
229 
230  for (auto it = _partial_feature_sets[0].begin(); it != _partial_feature_sets[0].end();
231  /* No increment on it*/)
232  {
233  auto chunk = MooseUtils::linearPartitionChunk(total_items, _num_chunks, it->_id);
234 
235  if (chunk)
236  {
237  _partial_feature_sets[chunk].emplace_back(std::move(*it));
238  it = _partial_feature_sets[0].erase(it); // it is incremented here!
239  }
240  else
241  ++it;
242  }
243 }
244 
245 void
247 {
249  return;
250 
251  TIME_SECTION("finalize", 2, "Finalizing Polycrystal Initial Condition");
252 
253  // TODO: Possibly retrieve the halo thickness from the active GrainTracker object?
254  constexpr unsigned int halo_thickness = 2;
255 
256  expandEdgeHalos(halo_thickness - 1);
257 
259 
260  if (!_colors_assigned)
261  {
262  // Resize the color assignment vector here. All ranks need a copy of this
264  if (_is_primary)
265  {
267 
269 
272  }
273 
274  // Communicate the coloring map with all ranks
276 
280  for (auto & feature : _feature_sets)
281  feature._var_index = _grain_to_op.at(feature._id);
282  }
283 
284  _colors_assigned = true;
285 }
286 
287 void
289 {
290  // When working with _distribute_merge_work all of the maps will be empty except for one
291  for (const auto map_num : index_range(_partial_feature_sets))
292  {
298  _partial_feature_sets[map_num].sort();
299 
300  auto it1 = _partial_feature_sets[map_num].begin();
301  auto it_end = _partial_feature_sets[map_num].end();
302  while (it1 != it_end)
303  {
304  auto it2 = it1;
305  if (++it2 == it_end)
306  break;
307 
308  if (areFeaturesMergeable(*it1, *it2))
309  {
310  it1->merge(std::move(*it2));
311  _partial_feature_sets[map_num].erase(it2);
312  }
313  else
314  ++it1; // Only increment if we have a mismatch
315  }
316  }
317 }
318 
319 void
320 PolycrystalUserObjectBase::restoreOriginalDataStructures(std::vector<std::list<FeatureData>> & orig)
321 {
322  // Move all the data back into the first list
323  auto & master_list = orig[0];
324  for (MooseIndex(_maps_size) map_num = 1; map_num < orig.size(); ++map_num)
325  {
326  master_list.splice(master_list.end(), orig[map_num]);
327  orig[map_num].clear();
328  }
329 
330  orig.resize(1);
331 }
332 
333 bool
335  std::size_t & current_index,
336  FeatureData *& feature,
337  Status & status,
338  unsigned int & new_id)
339 {
340  mooseAssert(_t_step == 0, "PolyIC only works if we begin in the initial condition");
341 
342  // Retrieve the id of the current entity
343  auto entity_id = dof_object->id();
344  auto grains_it = _entity_to_grain_cache.lower_bound(entity_id);
345 
346  if (grains_it == _entity_to_grain_cache.end() || grains_it->first != entity_id)
347  {
348  std::vector<unsigned int> grain_ids;
349 
350  if (_is_elemental)
351  getGrainsBasedOnElem(*static_cast<const Elem *>(dof_object), grain_ids);
352  else
353  getGrainsBasedOnPoint(*static_cast<const Node *>(dof_object), grain_ids);
354 
355  grains_it = _entity_to_grain_cache.emplace_hint(grains_it, entity_id, std::move(grain_ids));
356  }
357 
367  auto saved_grain_id = invalid_id;
368  if (current_index == invalid_size_t)
369  {
370  for (auto grain_id : grains_it->second)
371  {
372  auto map_it = _grain_to_op.find(grain_id);
373  mooseAssert(!_colors_assigned || map_it != _grain_to_op.end(), "grain_id missing");
374  auto map_num = _colors_assigned ? map_it->second : grain_id;
375 
376  if (_entities_visited[map_num].find(entity_id) == _entities_visited[map_num].end())
377  {
378  saved_grain_id = grain_id;
379  current_index = map_num;
380  break;
381  }
382  }
383 
384  if (current_index == invalid_size_t)
385  return false;
386  }
387  else if (_entities_visited[current_index].find(entity_id) !=
388  _entities_visited[current_index].end())
389  return false;
390 
391  if (!feature)
392  {
393  new_id = saved_grain_id;
394  status &= ~Status::INACTIVE;
395 
396  return true;
397  }
398  else
399  {
400  const auto & grain_ids = grains_it->second;
401  if (std::find(grain_ids.begin(), grain_ids.end(), feature->_id) != grain_ids.end())
402  return true;
403 
409  if (_is_elemental)
410  {
411  Elem * elem = _mesh.queryElemPtr(entity_id);
412  mooseAssert(elem, "Element is nullptr");
413 
414  std::vector<const Elem *> all_active_neighbors;
415  MeshBase & mesh = _mesh.getMesh();
416 
417  for (auto i = decltype(elem->n_neighbors())(0); i < elem->n_neighbors(); ++i)
418  {
419  const Elem * neighbor_ancestor = nullptr;
420 
425  neighbor_ancestor = elem->neighbor_ptr(i);
426 
427  if (neighbor_ancestor)
428  {
435  if (neighbor_ancestor->is_remote())
436  continue;
437 
438  neighbor_ancestor->active_family_tree_by_neighbor(all_active_neighbors, elem, false);
439  }
440  else // if (expand_halos_only /*&& feature->_periodic_nodes.empty()*/)
441  {
442  neighbor_ancestor = elem->topological_neighbor(i, mesh, *_point_locator, _pbs);
443 
451  if (neighbor_ancestor)
452  {
459  if (neighbor_ancestor->is_remote())
460  continue;
461 
462  neighbor_ancestor->active_family_tree_by_topological_neighbor(
463  all_active_neighbors, elem, mesh, *_point_locator, _pbs, false);
464  }
465  }
466  }
467 
468  for (const auto neighbor : all_active_neighbors)
469  {
470  // Retrieve the id of the current entity
471  auto neighbor_id = neighbor->id();
472  auto neighbor_it = _entity_to_grain_cache.lower_bound(neighbor_id);
473 
474  if (neighbor_it == _entity_to_grain_cache.end() || neighbor_it->first != neighbor_id)
475  {
476  std::vector<unsigned int> more_grain_ids;
477 
478  getGrainsBasedOnElem(*static_cast<const Elem *>(neighbor), more_grain_ids);
479 
480  neighbor_it = _entity_to_grain_cache.emplace_hint(
481  neighbor_it, neighbor_id, std::move(more_grain_ids));
482  }
483 
484  const auto & more_grain_ids = neighbor_it->second;
485  if (std::find(more_grain_ids.begin(), more_grain_ids.end(), feature->_id) !=
486  more_grain_ids.end())
487  return true;
488  }
489  }
490 
491  return false;
492  }
493 }
494 
495 bool
497  const FeatureData & f2) const
498 {
499  if (f1._id != f2._id)
500  return false;
501 
502  mooseAssert(f1._var_index == f2._var_index, "Feature should be mergeable but aren't");
503  return true;
504 }
505 
506 void
508 {
509  mooseAssert(_is_primary, "This routine should only be called on the primary rank");
510 
511  _adjacency_matrix = std::make_unique<DenseMatrix<Real>>(_feature_count, _feature_count);
512  for (MooseIndex(_feature_sets) i = 0; i < _feature_sets.size(); ++i)
513  {
514  for (MooseIndex(_feature_sets) j = i + 1; j < _feature_sets.size(); ++j)
515  {
516  if (_feature_sets[i].boundingBoxesIntersect(_feature_sets[j]) &&
517  _feature_sets[i].halosIntersect(_feature_sets[j]))
518  {
519  // Our grain adjacency matrix is symmetrical
520  (*_adjacency_matrix)(i, j) = 1;
521  (*_adjacency_matrix)(j, i) = 1;
522  }
523  }
524  }
525 }
526 
527 void
529 {
530  mooseAssert(_is_primary, "This routine should only be called on the primary rank");
531 
532  TIME_SECTION("assignOpsToGrains", 2, "Assigning OPs to grains");
533 
534  // Use a simple backtracking coloring algorithm
535  if (_coloring_algorithm == "bt")
536  {
537  paramInfo("coloring_algorithm",
538  "The backtracking algorithm has exponential complexity. If you are using very few "
539  "order parameters,\nor you have several hundred grains or more, you should use one "
540  "of the PETSc coloring algorithms such as \"jp\".");
541 
542  if (!colorGraph(0))
543  paramError("op_num",
544  "Unable to find a valid grain to op coloring, Make sure you have created enough "
545  "variables to hold a\nvalid polycrystal initial condition (no grains represented "
546  "by the same variable should be allowed to\ntouch, ~8 for 2D, ~25 for 3D)?");
547  }
548  else // PETSc Coloring algorithms
549  {
550  const std::string & ca_str = _coloring_algorithm;
551  Real * am_data = _adjacency_matrix->get_values().data();
552 
553  try
554  {
556  am_data, _feature_count, _vars.size(), _grain_idx_to_op, ca_str.c_str());
557  }
558  catch (std::runtime_error & e)
559  {
560  paramError("op_num",
561  "Unable to find a valid grain to op coloring, Make sure you have created enough "
562  "variables to hold a\nvalid polycrystal initial condition (no grains represented "
563  "by the same variable should be allowed to\ntouch, ~8 for 2D, ~25 for 3D)?");
564  }
565  }
566 
571  mooseAssert(_grain_to_op.empty(), "grain_to_op data structure should be empty here");
572  for (MooseIndex(_grain_idx_to_op) i = 0; i < _grain_idx_to_op.size(); ++i)
573  _grain_to_op.emplace_hint(_grain_to_op.end(), _feature_sets[i]._id, _grain_idx_to_op[i]);
574 }
575 
576 bool
578 {
579  // Base case: All grains are assigned
580  if (vertex == _feature_count)
581  return true;
582 
583  // Consider this grain and try different ops
584  for (unsigned int color_idx = 0; color_idx < _op_num; ++color_idx)
585  {
586  // We'll try to spread these colors around a bit rather than
587  // packing them all on the first few colors if we have several colors.
588  unsigned int color = (vertex + color_idx) % _op_num;
589 
590  if (isGraphValid(vertex, color))
591  {
592  _grain_idx_to_op[vertex] = color;
593 
594  if (colorGraph(vertex + 1))
595  return true;
596 
597  // Backtrack...
599  }
600  }
601 
602  return false;
603 }
604 
605 bool
606 PolycrystalUserObjectBase::isGraphValid(unsigned int vertex, unsigned int color)
607 {
608  // See if the proposed color is valid based on the current neighbor colors
609  for (unsigned int neighbor = 0; neighbor < _feature_count; ++neighbor)
610  if ((*_adjacency_matrix)(vertex, neighbor) && color == _grain_idx_to_op[neighbor])
611  return false;
612  return true;
613 }
614 
615 void
617 {
618  _console << "Grain Adjacency Matrix:\n";
619  for (unsigned int i = 0; i < _adjacency_matrix->m(); i++)
620  {
621  for (unsigned int j = 0; j < _adjacency_matrix->n(); j++)
622  _console << _adjacency_matrix->el(i, j) << " ";
623  _console << '\n';
624  }
625 
626  _console << "Grain to OP assignments:\n";
627  for (auto op : _grain_idx_to_op)
628  _console << op << " ";
629  _console << '\n' << std::endl;
630 }
631 
632 MooseEnum
634 {
635  return MooseEnum("jp power greedy bt", "jp");
636 }
637 
638 std::string
640 {
641  return "The grain neighbor graph coloring algorithm to use: \"jp\" (DEFAULT) Jones and "
642  "Plassmann, an efficient coloring algorithm, \"power\" an alternative stochastic "
643  "algorithm, \"greedy\", a greedy assignment algorithm with stochastic updates to "
644  "guarantee a valid coloring, \"bt\", a back tracking algorithm that produces good "
645  "distributions but may experience exponential run time in the worst case scenario "
646  "(works well on medium to large 2D problems)";
647 }
648 
649 const unsigned int PolycrystalUserObjectBase::INVALID_COLOR =
650  std::numeric_limits<unsigned int>::max();
651 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 ...
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 addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
PolycrystalUserObjectBase(const InputParameters &parameters)
unsigned int dim
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.
bool isTranslatedPeriodic(unsigned int nonlinear_var_num, unsigned int component) const
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
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.
const unsigned int _dim
mesh dimension
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...
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
void paramError(const std::string &param, Args... args) const
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
FEProblemBase & _fe_problem
void colorAdjacencyMatrix(PetscScalar *adjacency_matrix, unsigned int size, unsigned int colors, std::vector< unsigned int > &vertex_colors, const char *coloring_algorithm)
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...
void mooseError(Args &&... args) const
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...
void paramInfo(const std::string &param, Args... args) const
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()
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