https://mooseframework.inl.gov
GrainTracker.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 "GrainTracker.h"
11 
12 // MOOSE includes
14 #include "GeneratedMesh.h"
15 #include "MooseMesh.h"
16 #include "MooseVariable.h"
17 #include "NonlinearSystem.h"
18 
19 #include "libmesh/periodic_boundary_base.h"
20 
21 // C++ includes
22 #include <algorithm>
23 #include <limits>
24 #include <numeric>
25 
26 template <>
27 void
28 dataStore(std::ostream & stream, GrainTracker::PartialFeatureData & feature, void * context)
29 {
30  storeHelper(stream, feature.boundary_intersection, context);
31  storeHelper(stream, feature.id, context);
32  storeHelper(stream, feature.centroid, context);
33  storeHelper(stream, feature.status, context);
34 }
35 
36 template <>
37 void
38 dataLoad(std::istream & stream, GrainTracker::PartialFeatureData & feature, void * context)
39 {
40  loadHelper(stream, feature.boundary_intersection, context);
41  loadHelper(stream, feature.id, context);
42  loadHelper(stream, feature.centroid, context);
43  loadHelper(stream, feature.status, context);
44 }
45 
46 registerMooseObject("PhaseFieldApp", GrainTracker);
47 
50 {
53 
54  // FeatureFloodCount adds a relationship manager, but we need to extend that for GrainTracker
56 
58  "ElementSideNeighborLayers",
60 
61  [](const InputParameters & obj_params, InputParameters & rm_params)
62  { rm_params.set<unsigned short>("layers") = obj_params.get<unsigned short>("halo_level"); }
63 
64  );
65 
66  params.addRelationshipManager("ElementSideNeighborLayers",
68 
69  // The GrainTracker requires non-volatile storage for tracking grains across invocations.
70  params.set<bool>("restartable_required") = true;
71 
72  params.addParam<Real>("bound_value",
73  0.0,
74  "Absolute value of the lower bound for the variable value that represents "
75  "a region not assigned to the grain. Must be positive, but the actual "
76  "value used is -bound_value.");
77 
78  params.addClassDescription("Grain Tracker object for running reduced order parameter simulations "
79  "without grain coalescence.");
80 
81  return params;
82 }
83 
85  : FeatureFloodCount(parameters),
87  _tracking_step(getParam<int>("tracking_step")),
88  _halo_level(getParam<unsigned short>("halo_level")),
89  _max_remap_recursion_depth(getParam<unsigned short>("max_remap_recursion_depth")),
90  _n_reserve_ops(getParam<unsigned short>("reserve_op")),
91  _reserve_op_index(_n_reserve_ops <= _n_vars ? _n_vars - _n_reserve_ops : 0),
92  _reserve_op_threshold(getParam<Real>("reserve_op_threshold")),
93  _bound_value(getParam<Real>("bound_value")),
94  _remap(getParam<bool>("remap_grains")),
95  _tolerate_failure(getParam<bool>("tolerate_failure")),
96  _poly_ic_uo(parameters.isParamValid("polycrystal_ic_uo")
97  ? &getUserObject<PolycrystalUserObjectBase>("polycrystal_ic_uo")
98  : nullptr),
99  _verbosity_level(getParam<short>("verbosity_level")),
100  _first_time(declareRestartableData<bool>("first_time", true)),
101  _error_on_grain_creation(getParam<bool>("error_on_grain_creation")),
102  _reserve_grain_first_index(0),
103  _old_max_grain_id(0),
104  _max_curr_grain_id(declareRestartableData<unsigned int>("max_curr_grain_id", invalid_id)),
105  _is_transient(_subproblem.isTransient())
106 {
107  if (_tolerate_failure)
108  paramInfo("tolerate_failure",
109  "Tolerate failure has been set to true. Non-physical simulation results "
110  "are possible, you will be notified in the event of a failed remapping operation.");
111 
112  if (_tracking_step > 0 && _poly_ic_uo)
113  mooseError("Can't start tracking after the initial condition when using a polycrystal_ic_uo");
114 }
115 
117 
118 Real
120  FieldType field_type,
121  std::size_t var_index) const
122 {
123  if (_t_step < _tracking_step)
124  return 0;
125 
126  return FeatureFloodCount::getEntityValue(entity_id, field_type, var_index);
127 }
128 
129 const std::vector<unsigned int> &
131 {
133 }
134 
135 unsigned int
136 GrainTracker::getFeatureVar(unsigned int feature_id) const
137 {
138  return FeatureFloodCount::getFeatureVar(feature_id);
139 }
140 
141 std::size_t
143 {
144  // Note: This value is parallel consistent, see FeatureFloodCount::communicateAndMerge()
145  return _feature_count;
146 }
147 
148 std::size_t
150 {
151  // Note: This value is parallel consistent, see assignGrains()/trackGrains()
153 }
154 
155 Point
156 GrainTracker::getGrainCentroid(unsigned int grain_id) const
157 {
158  mooseAssert(grain_id < _feature_id_to_local_index.size(), "Grain ID out of bounds");
159  auto grain_index = _feature_id_to_local_index[grain_id];
160 
161  if (grain_index != invalid_size_t)
162  {
163  mooseAssert(_feature_id_to_local_index[grain_id] < _feature_sets.size(),
164  "Grain index out of bounds");
165  // Note: This value is parallel consistent, see GrainTracker::broadcastAndUpdateGrainData()
166  return _feature_sets[_feature_id_to_local_index[grain_id]]._centroid;
167  }
168 
169  // Inactive grain
170  return Point();
171 }
172 
173 bool
174 GrainTracker::doesFeatureIntersectBoundary(unsigned int feature_id) const
175 {
176  // TODO: This data structure may need to be turned into a Multimap
177  mooseAssert(feature_id < _feature_id_to_local_index.size(), "Grain ID out of bounds");
178 
179  auto feature_index = _feature_id_to_local_index[feature_id];
180  if (feature_index != invalid_size_t)
181  {
182  mooseAssert(feature_index < _feature_sets.size(), "Grain index out of bounds");
183  return _feature_sets[feature_index]._boundary_intersection != BoundaryIntersection::NONE;
184  }
185 
186  return false;
187 }
188 
189 bool
191 {
192  // TODO: This data structure may need to be turned into a Multimap
193  mooseAssert(feature_id < _feature_id_to_local_index.size(), "Grain ID out of bounds");
194 
195  auto feature_index = _feature_id_to_local_index[feature_id];
196  if (feature_index != invalid_size_t)
197  {
198  mooseAssert(feature_index < _feature_sets.size(), "Grain index out of bounds");
199  return ((_feature_sets[feature_index]._boundary_intersection &
201  }
202 
203  return false;
204 }
205 
206 bool
207 GrainTracker::isFeaturePercolated(unsigned int feature_id) const
208 {
209  // TODO: This data structure may need to be turned into a Multimap
210  mooseAssert(feature_id < _feature_id_to_local_index.size(), "Grain ID out of bounds");
211 
212  auto feature_index = _feature_id_to_local_index[feature_id];
213  if (feature_index != invalid_size_t)
214  {
215  mooseAssert(feature_index < _feature_sets.size(), "Grain index out of bounds");
216  bool primary = ((_feature_sets[feature_index]._boundary_intersection &
219  bool secondary = ((_feature_sets[feature_index]._boundary_intersection &
222  return (primary && secondary);
223  }
224 
225  return false;
226 }
227 
228 void
230 {
231  // Don't track grains if the current simulation step is before the specified tracking step
232  if (_t_step < _tracking_step)
233  return;
234 
240  if (!_first_time)
242 
244 }
245 
246 void
248 {
249  // Update the element ID ranges for use when computing halo maps
251  {
252  _all_ranges.clear();
253 
254  auto range = std::make_pair(std::numeric_limits<dof_id_type>::max(),
255  std::numeric_limits<dof_id_type>::min());
256  for (const auto & current_elem : _mesh.getMesh().active_local_element_ptr_range())
257  {
258  auto id = current_elem->id();
259  if (id < range.first)
260  range.first = id;
261  else if (id > range.second)
262  range.second = id;
263  }
264 
265  _communicator.gather(0, range, _all_ranges);
266  }
267 
269 }
270 
271 void
273 {
274  // Don't track grains if the current simulation step is before the specified tracking step
275  if (_t_step < _tracking_step)
276  return;
277 
278  if (_poly_ic_uo && _first_time)
279  return;
280 
282 }
283 
284 Real
285 GrainTracker::getThreshold(std::size_t var_index) const
286 {
287  // If we are inspecting a reserve op parameter, we need to make sure
288  // that there is an entity above the reserve_op threshold before
289  // starting the flood of the feature.
290  if (var_index >= _reserve_op_index)
291  return _reserve_op_threshold;
292  else
293  return _step_threshold;
294 }
295 
296 void
298 {
299  mooseAssert(_first_time, "This method should only be called on the first invocation");
300 
301  _feature_sets.clear();
302 
308  if (_is_primary)
309  {
310  const auto & features = ffc_object.getFeatures();
311  for (auto & feature : features)
312  _feature_sets.emplace_back(feature.duplicate());
313 
314  _feature_count = _feature_sets.size();
315  }
316  else
317  {
318  const auto & features = ffc_object.getFeatures();
319  _partial_feature_sets[0].clear();
320  for (auto & feature : features)
321  _partial_feature_sets[0].emplace_back(feature.duplicate());
322  }
323 
324  // Make sure that feature count is communicated to all ranks
326 }
327 
328 void
330 {
331  // Don't track grains if the current simulation step is before the specified tracking step
332  if (_t_step < _tracking_step)
333  return;
334 
335  TIME_SECTION("finalize", 3, "Finalizing GrainTracker");
336 
337  // Expand the depth of the halos around all grains
338  auto num_halo_layers = _halo_level >= 1
339  ? _halo_level - 1
340  : 0; // The first level of halos already exists so subtract one
341 
342  if (_poly_ic_uo && _first_time)
344  else
345  {
346  expandEdgeHalos(num_halo_layers);
347 
348  // Build up the grain map on the root processor
350  }
351 
355  if (_first_time)
356  assignGrains();
357  else
358  trackGrains();
359 
360  if (_verbosity_level > 1)
361  _console << "Finished inside of trackGrains" << std::endl;
362 
367 
371  if (_remap)
372  remapGrains();
373 
374  updateFieldInfo();
375  if (_verbosity_level > 1)
376  _console << "Finished inside of updateFieldInfo" << std::endl;
377 
378  // Set the first time flag false here (after all methods of finalize() have completed)
379  _first_time = false;
380 
381  // TODO: Release non essential memory
382  if (_verbosity_level > 0)
383  _console << "Finished inside of GrainTracker\n" << std::endl;
384 }
385 
386 void
388 {
389  TIME_SECTION("broadcastAndUpdateGrainData", 3, "Broadcasting and Updating Grain Data");
390 
391  std::vector<PartialFeatureData> root_feature_data;
392  std::vector<std::string> send_buffer(1), recv_buffer;
393 
394  if (_is_primary)
395  {
396  root_feature_data.reserve(_feature_sets.size());
397 
398  // Populate a subset of the information in a small data structure
399  std::transform(_feature_sets.begin(),
400  _feature_sets.end(),
401  std::back_inserter(root_feature_data),
402  [](FeatureData & feature)
403  {
404  PartialFeatureData partial_feature;
405  partial_feature.boundary_intersection = feature._boundary_intersection;
406  partial_feature.id = feature._id;
407  partial_feature.centroid = feature._centroid;
408  partial_feature.status = feature._status;
409  return partial_feature;
410  });
411 
412  std::ostringstream oss;
413  dataStore(oss, root_feature_data, this);
414  send_buffer[0].assign(oss.str());
415  }
416 
417  // Broadcast the data to all ranks
418  _communicator.broadcast_packed_range((void *)(nullptr),
419  send_buffer.begin(),
420  send_buffer.end(),
421  (void *)(nullptr),
422  std::back_inserter(recv_buffer));
423 
424  // Unpack and update
425  if (!_is_primary)
426  {
427  std::istringstream iss;
428  iss.str(recv_buffer[0]);
429  iss.clear();
430 
431  dataLoad(iss, root_feature_data, this);
432 
433  for (const auto & partial_data : root_feature_data)
434  {
435  // See if this processor has a record of this grain
436  if (partial_data.id < _feature_id_to_local_index.size() &&
437  _feature_id_to_local_index[partial_data.id] != invalid_size_t)
438  {
439  auto & grain = _feature_sets[_feature_id_to_local_index[partial_data.id]];
440  grain._boundary_intersection = partial_data.boundary_intersection;
441  grain._centroid = partial_data.centroid;
442  if (partial_data.status == Status::INACTIVE)
443  grain._status = Status::INACTIVE;
444  }
445  }
446  }
447 }
448 
449 void
451 {
452  mooseAssert(_first_time, "assignGrains may only be called on the first tracking step");
453 
459  if (_is_primary)
460  {
461  // Find the largest grain ID, this requires sorting if the ID is not already set
462  sortAndLabel();
463 
464  if (_feature_sets.empty())
465  {
468  }
469  else
470  {
471  _max_curr_grain_id = _feature_sets.back()._id;
473  }
474 
475  for (auto & grain : _feature_sets)
476  grain._status = Status::MARKED; // Mark the grain
477 
478  } // is_primary
479 
480  /*************************************************************
481  ****************** COLLECTIVE WORK SECTION ******************
482  *************************************************************/
483 
484  // Make IDs on all non-primary ranks consistent
486 
487  // Build up an id to index map
490 
491  // Now trigger the newGrainCreated() callback on all ranks
493  for (unsigned int new_id = 0; new_id <= _max_curr_grain_id; ++new_id)
494  newGrainCreated(new_id);
495 }
496 
497 void
499 {
500  TIME_SECTION("trackGrains", 3, "Tracking Grains");
501 
502  mooseAssert(!_first_time, "Track grains may only be called when _tracking_step > _t_step");
503 
504  // Used to track indices for which to trigger the new grain callback on (used on all ranks)
506 
511  if (_is_primary)
512  {
513  // Reset Status on active unique grains
514  std::vector<unsigned int> map_sizes(_maps_size);
515  for (auto & grain : _feature_sets_old)
516  {
517  if (grain._status != Status::INACTIVE)
518  {
519  grain._status = Status::CLEAR;
520  map_sizes[grain._var_index]++;
521  }
522  }
523 
524  // Print out stats on overall tracking changes per var_index
525  if (_verbosity_level > 0)
526  {
527  _console << "\nGrain Tracker Status:";
528  for (const auto map_num : make_range(_maps_size))
529  {
530  _console << "\nGrains active index " << map_num << ": " << map_sizes[map_num] << " -> "
531  << _feature_counts_per_map[map_num];
532  if (map_sizes[map_num] > _feature_counts_per_map[map_num])
533  _console << "--";
534  else if (map_sizes[map_num] < _feature_counts_per_map[map_num])
535  _console << "++";
536  }
537  _console << '\n' << std::endl;
538  }
539 
540  // Before we track grains, lets sort them so that we get parallel consistent answers
541  std::sort(_feature_sets.begin(), _feature_sets.end());
542 
549  std::vector<std::size_t> new_grain_index_to_existing_grain_index(_feature_sets.size(),
551 
552  for (const auto old_grain_index : index_range(_feature_sets_old))
553  {
554  auto & old_grain = _feature_sets_old[old_grain_index];
555 
556  if (old_grain._status == Status::INACTIVE) // Don't try to find matches for inactive grains
557  continue;
558 
559  std::size_t closest_match_index = invalid_size_t;
560  Real min_centroid_diff = std::numeric_limits<Real>::max();
561 
567  // clang-format off
568  auto start_it =
569  std::lower_bound(_feature_sets.begin(), _feature_sets.end(), old_grain._var_index,
570  [](const FeatureData & item, std::size_t var_index)
571  {
572  return item._var_index < var_index;
573  });
574  // clang-format on
575 
576  // We only need to examine grains that have matching variable indices
577  bool any_boxes_intersect = false;
578  for (MooseIndex(_feature_sets)
579  new_grain_index = std::distance(_feature_sets.begin(), start_it);
580  new_grain_index < _feature_sets.size() &&
581  _feature_sets[new_grain_index]._var_index == old_grain._var_index;
582  ++new_grain_index)
583  {
584  auto & new_grain = _feature_sets[new_grain_index];
585 
590  if (new_grain.boundingBoxesIntersect(old_grain))
591  {
592  any_boxes_intersect = true;
593  Real curr_centroid_diff = centroidRegionDistance(old_grain._bboxes, new_grain._bboxes);
594  if (curr_centroid_diff <= min_centroid_diff)
595  {
596  closest_match_index = new_grain_index;
597  min_centroid_diff = curr_centroid_diff;
598  }
599  }
600  }
601 
602  if (_verbosity_level > 2 && !any_boxes_intersect)
603  _console << "\nNo intersecting bounding boxes found while trying to match grain "
604  << old_grain;
605 
606  // found a match
607  if (closest_match_index != invalid_size_t)
608  {
615  auto curr_index = new_grain_index_to_existing_grain_index[closest_match_index];
616  if (curr_index != invalid_size_t)
617  {
618  // The new feature being competed for
619  auto & new_grain = _feature_sets[closest_match_index];
620 
621  // The other old grain competing to match up to the same new grain
622  auto & other_old_grain = _feature_sets_old[curr_index];
623 
624  auto centroid_diff1 = centroidRegionDistance(new_grain._bboxes, old_grain._bboxes);
625  auto centroid_diff2 = centroidRegionDistance(new_grain._bboxes, other_old_grain._bboxes);
626 
627  auto & inactive_grain = (centroid_diff1 < centroid_diff2) ? other_old_grain : old_grain;
628 
629  inactive_grain._status = Status::INACTIVE;
630  if (_verbosity_level > 0)
631  {
632  _console << COLOR_GREEN << "Marking Grain " << inactive_grain._id
633  << " as INACTIVE (variable index: " << inactive_grain._var_index << ")\n"
634  << COLOR_DEFAULT;
635  if (_verbosity_level > 1)
636  _console << inactive_grain;
637  }
638 
644  if (&inactive_grain == &other_old_grain)
645  new_grain_index_to_existing_grain_index[closest_match_index] = old_grain_index;
646  }
647  else
648  new_grain_index_to_existing_grain_index[closest_match_index] = old_grain_index;
649  }
650  }
651 
652  // Mark all resolved grain matches
653  for (const auto new_index : index_range(new_grain_index_to_existing_grain_index))
654  {
655  auto curr_index = new_grain_index_to_existing_grain_index[new_index];
656 
657  // This may be a new grain, we'll handle that case below
658  if (curr_index == invalid_size_t)
659  continue;
660 
661  mooseAssert(_feature_sets_old[curr_index]._id != invalid_id,
662  "Invalid ID in old grain structure");
663 
664  _feature_sets[new_index]._id = _feature_sets_old[curr_index]._id; // Transfer ID
665  _feature_sets[new_index]._status = Status::MARKED; // Mark the status in the new set
666  _feature_sets_old[curr_index]._status = Status::MARKED; // Mark the status in the old set
667  }
668 
683  // Case 1 (new grains in _feature_sets):
684  for (const auto grain_num : index_range(_feature_sets))
685  {
686  auto & grain = _feature_sets[grain_num];
687 
688  // New Grain
689  if (grain._status == Status::CLEAR)
690  {
709  // clang-format off
710  auto start_it =
711  std::lower_bound(_feature_sets.begin(), _feature_sets.end(), grain._var_index,
712  [](const FeatureData & item, std::size_t var_index)
713  {
714  return item._var_index < var_index;
715  });
716  // clang-format on
717 
718  // Loop over matching variable indices
719  for (MooseIndex(_feature_sets)
720  new_grain_index = std::distance(_feature_sets.begin(), start_it);
721  new_grain_index < _feature_sets.size() &&
722  _feature_sets[new_grain_index]._var_index == grain._var_index;
723  ++new_grain_index)
724  {
725  auto & other_grain = _feature_sets[new_grain_index];
726 
727  // Splitting grain?
728  if (grain_num != new_grain_index && // Make sure indices aren't pointing at the same grain
729  other_grain._status == Status::MARKED && // and that the other grain is indeed marked
730  other_grain.boundingBoxesIntersect(grain) && // and the bboxes intersect
731  other_grain.halosIntersect(grain)) // and the halos also intersect
732  // TODO: Inspect combined volume and see if it's "close" to the expected value
733  {
734  grain._id = other_grain._id; // Set the duplicate ID
735  grain._status = Status::MARKED; // Mark it
736 
737  if (_verbosity_level > 0)
738  _console << COLOR_YELLOW << "Split Grain Detected #" << grain._id
739  << " (variable index: " << grain._var_index << ")\n"
740  << COLOR_DEFAULT;
741  if (_verbosity_level > 1)
742  _console << grain << other_grain;
743  }
744  }
745 
746  if (grain._var_index < _reserve_op_index)
747  {
766  if (_verbosity_level > 1)
767  _console << COLOR_YELLOW
768  << "Trying harder to detect a split grain while examining grain on variable "
769  "index "
770  << grain._var_index << '\n'
771  << COLOR_DEFAULT;
772 
773  std::vector<std::size_t> old_grain_indices;
774  for (const auto old_grain_index : index_range(_feature_sets_old))
775  {
776  auto & old_grain = _feature_sets_old[old_grain_index];
777 
778  if (old_grain._status == Status::INACTIVE)
779  continue;
780 
786  if (grain._var_index == old_grain._var_index &&
787  grain.boundingBoxesIntersect(old_grain) && grain.halosIntersect(old_grain))
788  old_grain_indices.push_back(old_grain_index);
789  }
790 
791  if (old_grain_indices.size() == 1)
792  {
793  grain._id = _feature_sets_old[old_grain_indices[0]]._id;
794  grain._status = Status::MARKED;
795 
796  if (_verbosity_level > 0)
797  _console << COLOR_YELLOW << "Split Grain Detected #" << grain._id
798  << " (variable index: " << grain._var_index << ")\n"
799  << COLOR_DEFAULT;
800  }
801  else if (old_grain_indices.size() > 1)
802  _console
803  << COLOR_RED << "Split Grain Likely Detected #" << grain._id
804  << " Need more information to find correct candidate - contact a developer!\n\n"
805  << COLOR_DEFAULT;
806  }
807 
808  // Must be a nucleating grain (status is still not set)
809  if (grain._status == Status::CLEAR)
810  {
811  auto new_index = getNextUniqueID();
812  grain._id = new_index; // Set the ID
813  grain._status = Status::MARKED; // Mark it
814 
815  if (_verbosity_level > 0)
816  _console << COLOR_YELLOW << "Nucleating Grain Detected "
817  << " (variable index: " << grain._var_index << ")\n"
818  << COLOR_DEFAULT;
819  if (_verbosity_level > 1)
820  _console << grain;
821  }
822  }
823  }
824 
825  // Case 2 (inactive grains in _feature_sets_old)
826  for (auto & grain : _feature_sets_old)
827  {
828  if (grain._status == Status::CLEAR)
829  {
830  grain._status = Status::INACTIVE;
831  if (_verbosity_level > 0)
832  {
833  _console << COLOR_GREEN << "Marking Grain " << grain._id
834  << " as INACTIVE (variable index: " << grain._var_index << ")\n"
835  << COLOR_DEFAULT;
836  if (_verbosity_level > 1)
837  _console << grain;
838  }
839  }
840  }
841  } // is_primary
842 
843  /*************************************************************
844  ****************** COLLECTIVE WORK SECTION ******************
845  *************************************************************/
846 
847  // Make IDs on all non-primary ranks consistent
849 
850  // Build up an id to index map
853 
858  {
859  for (auto new_id = _old_max_grain_id + 1; new_id <= _max_curr_grain_id; ++new_id)
860  {
861  // Don't trigger the callback on the reserve IDs
863  {
864  // See if we've been instructed to terminate with an error
866  mooseError(
867  "Error: New grain detected and \"error_on_new_grain_creation\" is set to true");
868  else
869  newGrainCreated(new_id);
870  }
871  }
872  }
873 }
874 
875 void
876 GrainTracker::newGrainCreated(unsigned int new_grain_id)
877 {
878  if (!_first_time && _is_primary)
879  {
880  mooseAssert(new_grain_id < _feature_id_to_local_index.size(), "new_grain_id is out of bounds");
881  auto grain_index = _feature_id_to_local_index[new_grain_id];
882  mooseAssert(grain_index != invalid_size_t && grain_index < _feature_sets.size(),
883  "new_grain_id appears to be invalid");
884 
885  const auto & grain = _feature_sets[grain_index];
886  _console << COLOR_YELLOW
887  << "\n*****************************************************************************"
888  << "\nCouldn't find a matching grain while working on variable index: "
889  << grain._var_index << "\nCreating new unique grain: " << new_grain_id << '\n'
890  << grain
891  << "\n*****************************************************************************\n"
892  << COLOR_DEFAULT;
893  }
894 }
895 
896 std::vector<unsigned int>
898 {
899  std::vector<unsigned int> new_ids(_max_curr_grain_id - _old_max_grain_id);
900  auto new_id = _old_max_grain_id + 1;
901 
902  // Generate the new ids
903  std::iota(new_ids.begin(), new_ids.end(), new_id);
904 
905  return new_ids;
906 }
907 
908 void
910 {
911  // Don't remap grains if the current simulation step is before the specified tracking step
912  if (_t_step < _tracking_step)
913  return;
914 
915  TIME_SECTION("remapGrains", 3, "Remapping Grains");
916 
917  if (_verbosity_level > 1)
918  _console << "Running remap Grains\n" << std::endl;
919 
926  std::map<unsigned int, std::size_t> grain_id_to_new_var;
927 
928  // Items are added to this list when split grains are found
929  std::list<std::pair<std::size_t, std::size_t>> split_pairs;
930 
939  if (_is_primary)
940  {
941  // Build the map to detect difference in _var_index mappings after the remap operation
942  std::map<unsigned int, std::size_t> grain_id_to_existing_var_index;
943  for (auto & grain : _feature_sets)
944  {
945  // Unmark the grain so it can be used in the remap loop
946  grain._status = Status::CLEAR;
947 
948  grain_id_to_existing_var_index[grain._id] = grain._var_index;
949  }
950 
951  // Make sure that all split pieces of any grain are on the same OP
952  for (const auto i : index_range(_feature_sets))
953  {
954  auto & grain1 = _feature_sets[i];
955 
956  for (const auto j : index_range(_feature_sets))
957  {
958  auto & grain2 = _feature_sets[j];
959  if (i == j)
960  continue;
961 
962  // The first condition below is there to prevent symmetric checks (duplicate values)
963  if (i < j && grain1._id == grain2._id)
964  {
965  split_pairs.push_front(std::make_pair(i, j));
966  if (grain1._var_index != grain2._var_index)
967  {
968  if (_verbosity_level > 0)
969  _console << COLOR_YELLOW << "Split Grain (#" << grain1._id
970  << ") detected on unmatched OPs (" << grain1._var_index << ", "
971  << grain2._var_index << ") attempting to remap to " << grain1._var_index
972  << ".\n"
973  << COLOR_DEFAULT;
974 
979  grain1._var_index = grain2._var_index;
980  grain1._status |= Status::DIRTY;
981  }
982  }
983  }
984  }
985 
989  bool grains_remapped;
990 
991  std::set<unsigned int> notify_ids;
992  do
993  {
994  grains_remapped = false;
995  notify_ids.clear();
996 
997  for (auto & grain1 : _feature_sets)
998  {
999  // We need to remap any grains represented on any variable index above the cuttoff
1000  if (grain1._var_index >= _reserve_op_index)
1001  {
1002  if (_verbosity_level > 0)
1003  _console << COLOR_YELLOW << "\nGrain #" << grain1._id
1004  << " detected on a reserved order parameter #" << grain1._var_index
1005  << ", remapping to another variable\n"
1006  << COLOR_DEFAULT;
1007 
1008  for (const auto max : make_range(0, _max_remap_recursion_depth + 1))
1010  {
1011  if (attemptGrainRenumber(grain1, 0, max))
1012  break;
1013  }
1014  else if (!attemptGrainRenumber(grain1, 0, max))
1015  {
1016  _console << std::flush;
1017  std::stringstream oss;
1018  oss << "Unable to find any suitable order parameters for remapping while working "
1019  << "with Grain #" << grain1._id << ", which is on a reserve order parameter.\n"
1020  << "\n\nPossible Resolutions:\n"
1021  << "\t- Add more order parameters to your simulation (8 for 2D, 28 for 3D)\n"
1022  << "\t- Increase adaptivity or reduce your grain boundary widths\n"
1023  << "\t- Make sure you are not starting with too many grains for the mesh size\n";
1024  mooseError(oss.str());
1025  }
1026 
1027  grains_remapped = true;
1028  }
1029 
1030  for (auto & grain2 : _feature_sets)
1031  {
1032  // Don't compare a grain with itself and don't try to remap inactive grains
1033  if (&grain1 == &grain2)
1034  continue;
1035 
1036  if (grain1._var_index == grain2._var_index && // grains represented by same variable?
1037  grain1._id != grain2._id && // are they part of different grains?
1038  grain1.boundingBoxesIntersect(grain2) && // do bboxes intersect (coarse level)?
1039  grain1.halosIntersect(grain2)) // do they actually overlap (fine level)?
1040  {
1041  if (_verbosity_level > 0)
1042  _console << COLOR_YELLOW << "Grain #" << grain1._id << " intersects Grain #"
1043  << grain2._id << " (variable index: " << grain1._var_index << ")\n"
1044  << COLOR_DEFAULT;
1045 
1046  for (const auto max : make_range(0, _max_remap_recursion_depth + 1))
1047  {
1049  {
1050  if (attemptGrainRenumber(grain1, 0, max))
1051  {
1052  grains_remapped = true;
1053  break;
1054  }
1055  }
1056  else if (!attemptGrainRenumber(grain1, 0, max) &&
1057  !attemptGrainRenumber(grain2, 0, max))
1058  {
1059  notify_ids.insert(grain1._id);
1060  notify_ids.insert(grain2._id);
1061  }
1062  }
1063  }
1064  }
1065  }
1066  } while (grains_remapped);
1067 
1068  if (!notify_ids.empty())
1069  {
1070  _console << std::flush;
1071  std::stringstream oss;
1072  oss << "Unable to find any suitable order parameters for remapping while working "
1073  << "with the following grain IDs:\n"
1074  << Moose::stringify(notify_ids, ", ", "", true) << "\n\nPossible Resolutions:\n"
1075  << "\t- Add more order parameters to your simulation (8 for 2D, 28 for 3D)\n"
1076  << "\t- Increase adaptivity or reduce your grain boundary widths\n"
1077  << "\t- Make sure you are not starting with too many grains for the mesh size\n";
1078 
1079  if (_tolerate_failure)
1080  mooseWarning(oss.str());
1081  else
1082  mooseError(oss.str());
1083  }
1084 
1085  // Verify that split grains are still intact
1086  for (auto & split_pair : split_pairs)
1087  if (_feature_sets[split_pair.first]._var_index != _feature_sets[split_pair.first]._var_index)
1088  mooseError("Split grain remapped - This case is currently not handled");
1089 
1095  for (auto & grain : _feature_sets)
1096  {
1097  mooseAssert(grain_id_to_existing_var_index.find(grain._id) !=
1098  grain_id_to_existing_var_index.end(),
1099  "Missing unique ID");
1100 
1101  auto old_var_index = grain_id_to_existing_var_index[grain._id];
1102 
1103  if (old_var_index != grain._var_index)
1104  {
1105  mooseAssert(static_cast<bool>(grain._status & Status::DIRTY), "grain status is incorrect");
1106 
1107  grain_id_to_new_var.emplace_hint(
1108  grain_id_to_new_var.end(),
1109  std::pair<unsigned int, std::size_t>(grain._id, grain._var_index));
1110 
1119  grain._var_index = old_var_index;
1120  // Clear the DIRTY status as well for consistency
1121  grain._status &= ~Status::DIRTY;
1122  }
1123  }
1124 
1125  if (!grain_id_to_new_var.empty())
1126  {
1127  if (_verbosity_level > 1)
1128  {
1129  _console << "Final remapping tally:\n";
1130  for (const auto & remap_pair : grain_id_to_new_var)
1131  _console << "Grain #" << remap_pair.first << " var_index "
1132  << grain_id_to_existing_var_index[remap_pair.first] << " -> "
1133  << remap_pair.second << '\n';
1134  _console << "Communicating swaps with remaining processors..." << std::endl;
1135  }
1136  }
1137  } // root processor
1138 
1139  // Communicate the std::map to all ranks
1140  _communicator.broadcast(grain_id_to_new_var);
1141 
1142  // Perform swaps if any occurred
1143  if (!grain_id_to_new_var.empty())
1144  {
1145  // Cache for holding values during swaps
1146  std::vector<std::map<Node *, CacheValues>> cache(_n_vars);
1147 
1148  // Perform the actual swaps on all processors
1149  for (auto & grain : _feature_sets)
1150  {
1151  // See if this grain was remapped
1152  auto new_var_it = grain_id_to_new_var.find(grain._id);
1153  if (new_var_it != grain_id_to_new_var.end())
1154  swapSolutionValues(grain, new_var_it->second, cache, RemapCacheMode::FILL);
1155  }
1156 
1157  for (auto & grain : _feature_sets)
1158  {
1159  // See if this grain was remapped
1160  auto new_var_it = grain_id_to_new_var.find(grain._id);
1161  if (new_var_it != grain_id_to_new_var.end())
1162  swapSolutionValues(grain, new_var_it->second, cache, RemapCacheMode::USE);
1163  }
1164 
1165  _sys.solution().close();
1166  _sys.solutionOld().close();
1167  _sys.solutionOlder().close();
1168 
1169  _sys.system().update();
1170 
1171  if (_verbosity_level > 1)
1172  _console << "Swaps complete" << std::endl;
1173  }
1174 }
1175 
1176 void
1178  std::vector<std::list<GrainDistance>> & min_distances)
1179 {
1203  for (const auto i : index_range(_feature_sets))
1204  {
1205  auto & other_grain = _feature_sets[i];
1206 
1207  if (other_grain._var_index == grain._var_index || other_grain._var_index >= _reserve_op_index)
1208  continue;
1209 
1210  auto target_var_index = other_grain._var_index;
1211  auto target_grain_index = i;
1212  auto target_grain_id = other_grain._id;
1213 
1214  Real curr_bbox_diff = boundingRegionDistance(grain._bboxes, other_grain._bboxes);
1215 
1216  GrainDistance grain_distance_obj(
1217  curr_bbox_diff, target_var_index, target_grain_index, target_grain_id);
1218 
1219  // To handle touching halos we penalize the top pick each time we see another
1220  if (curr_bbox_diff == -1.0 && !min_distances[target_var_index].empty())
1221  {
1222  Real last_distance = min_distances[target_var_index].begin()->_distance;
1223  if (last_distance < 0)
1224  grain_distance_obj._distance += last_distance;
1225  }
1226 
1227  // Insertion sort into a list
1228  auto insert_it = min_distances[target_var_index].begin();
1229  while (insert_it != min_distances[target_var_index].end() && !(grain_distance_obj < *insert_it))
1230  ++insert_it;
1231  min_distances[target_var_index].insert(insert_it, grain_distance_obj);
1232  }
1233 
1239  for (const auto var_index : make_range(_reserve_op_index))
1240  {
1241  // Don't put an entry in for matching variable indices (i.e. we can't remap to ourselves)
1242  if (grain._var_index == var_index)
1243  continue;
1244 
1245  if (min_distances[var_index].empty())
1246  min_distances[var_index].emplace_front(std::numeric_limits<Real>::max(), var_index);
1247  }
1248 }
1249 
1250 bool
1251 GrainTracker::attemptGrainRenumber(FeatureData & grain, unsigned int depth, unsigned int max_depth)
1252 {
1253  // End the recursion of our breadth first search
1254  if (depth > max_depth)
1255  return false;
1256 
1257  std::size_t curr_var_index = grain._var_index;
1258 
1259  std::vector<std::map<Node *, CacheValues>> cache;
1260 
1261  std::vector<std::list<GrainDistance>> min_distances(_vars.size());
1262 
1268  computeMinDistancesFromGrain(grain, min_distances);
1269 
1280  // clang-format off
1281  std::sort(min_distances.begin(), min_distances.end(),
1282  [](const std::list<GrainDistance> & lhs, const std::list<GrainDistance> & rhs)
1283  {
1284  // Sort lists in reverse order (largest distance first)
1285  // These empty cases are here to make this comparison stable
1286  if (lhs.empty())
1287  return false;
1288  else if (rhs.empty())
1289  return true;
1290  else
1291  return lhs.begin()->_distance > rhs.begin()->_distance;
1292  });
1293  // clang-format on
1294 
1295  for (auto & list_ref : min_distances)
1296  {
1297  const auto target_it = list_ref.begin();
1298  if (target_it == list_ref.end())
1299  continue;
1300 
1301  // If the distance is positive we can just remap and be done
1302  if (target_it->_distance > 0)
1303  {
1304  if (_verbosity_level > 0)
1305  {
1306  _console << COLOR_GREEN << "- Depth " << depth << ": Remapping grain #" << grain._id
1307  << " from variable index " << curr_var_index << " to " << target_it->_var_index;
1308  if (target_it->_distance == std::numeric_limits<Real>::max())
1309  _console << " which currently contains zero grains.\n\n" << COLOR_DEFAULT;
1310  else
1311  _console << " whose closest grain (#" << target_it->_grain_id << ") is at a distance of "
1312  << std::sqrt(target_it->_distance) << "\n\n"
1313  << COLOR_DEFAULT;
1314  }
1315 
1316  grain._status |= Status::DIRTY;
1317  grain._var_index = target_it->_var_index;
1318  return true;
1319  }
1320 
1321  // If the distance isn't positive we just need to make sure that none of the grains represented
1322  // by the target variable index would intersect this one if we were to remap
1323  {
1324  auto next_target_it = target_it;
1325  bool intersection_hit = false;
1326  unsigned short num_close_targets = 0;
1327  std::ostringstream oss;
1328  while (!intersection_hit && next_target_it != list_ref.end())
1329  {
1330  if (next_target_it->_distance > 0)
1331  break;
1332 
1333  mooseAssert(next_target_it->_grain_index < _feature_sets.size(),
1334  "Error in indexing target grain in attemptGrainRenumber");
1335  FeatureData & next_target_grain = _feature_sets[next_target_it->_grain_index];
1336 
1337  // If any grains touch we're done here
1338  if (grain.halosIntersect(next_target_grain))
1339  intersection_hit = true;
1340  else
1341  {
1342  if (num_close_targets > 0)
1343  oss << ", "; // delimiter
1344  oss << "#" << next_target_it->_grain_id;
1345  }
1346 
1347  ++next_target_it;
1348  ++num_close_targets;
1349  }
1350 
1351  if (!intersection_hit)
1352  {
1353  if (_verbosity_level > 0)
1354  {
1355  _console << COLOR_GREEN << "- Depth " << depth << ": Remapping grain #" << grain._id
1356  << " from variable index " << curr_var_index << " to " << target_it->_var_index;
1357 
1358  if (num_close_targets == 1)
1359  _console << " whose closest grain (" << oss.str()
1360  << ") is inside our bounding box but whose halo is not touching.\n\n"
1361  << COLOR_DEFAULT;
1362  else
1363  _console << " whose closest grains (" << oss.str()
1364  << ") are inside our bounding box but whose halos are not touching.\n\n"
1365  << COLOR_DEFAULT;
1366  }
1367 
1368  grain._status |= Status::DIRTY;
1369  grain._var_index = target_it->_var_index;
1370  return true;
1371  }
1372  }
1373 
1374  // If we reach this part of the loop, there is no simple renumbering that can be done.
1375  mooseAssert(target_it->_grain_index < _feature_sets.size(),
1376  "Error in indexing target grain in attemptGrainRenumber");
1377  FeatureData & target_grain = _feature_sets[target_it->_grain_index];
1378 
1386  if (target_it->_distance < -1)
1387  return false;
1388 
1389  // Make sure this grain isn't marked. If it is, we can't recurse here
1390  if ((target_grain._status & Status::MARKED) == Status::MARKED)
1391  return false;
1392 
1398  grain._var_index = target_it->_var_index;
1399  grain._status |= Status::MARKED;
1400  if (attemptGrainRenumber(target_grain, depth + 1, max_depth))
1401  {
1402  // SUCCESS!
1403  if (_verbosity_level > 0)
1404  _console << COLOR_GREEN << "- Depth " << depth << ": Remapping grain #" << grain._id
1405  << " from variable index " << curr_var_index << " to " << target_it->_var_index
1406  << "\n\n"
1407  << COLOR_DEFAULT;
1408 
1409  // Now we need to mark the grain as DIRTY since the recursion succeeded.
1410  grain._status |= Status::DIRTY;
1411  return true;
1412  }
1413  else
1414  // FAILURE, We need to set our var index back after failed recursive step
1415  grain._var_index = curr_var_index;
1416 
1417  // ALWAYS "unmark" (or clear the MARKED status) after recursion so it can be used by other remap
1418  // operations
1419  grain._status &= ~Status::MARKED;
1420  }
1421 
1422  return false;
1423 }
1424 
1425 void
1427  std::size_t new_var_index,
1428  std::vector<std::map<Node *, CacheValues>> & cache,
1429  RemapCacheMode cache_mode)
1430 {
1431  MeshBase & mesh = _mesh.getMesh();
1432 
1433  // Remap the grain
1434  std::set<Node *> updated_nodes_tmp; // Used only in the elemental case
1435  for (auto entity : grain._local_ids)
1436  {
1437  if (_is_elemental)
1438  {
1439  Elem * elem = mesh.query_elem_ptr(entity);
1440  if (!elem)
1441  continue;
1442 
1443  for (unsigned int i = 0; i < elem->n_nodes(); ++i)
1444  {
1445  Node * curr_node = elem->node_ptr(i);
1446  if (updated_nodes_tmp.find(curr_node) == updated_nodes_tmp.end())
1447  {
1448  // cache this node so we don't attempt to remap it again within this loop
1449  updated_nodes_tmp.insert(curr_node);
1450  swapSolutionValuesHelper(curr_node, grain._var_index, new_var_index, cache, cache_mode);
1451  }
1452  }
1453  }
1454  else
1456  mesh.query_node_ptr(entity), grain._var_index, new_var_index, cache, cache_mode);
1457  }
1458 
1459  // Update the variable index in the unique grain datastructure after swaps are complete
1460  if (cache_mode == RemapCacheMode::USE || cache_mode == RemapCacheMode::BYPASS)
1461  grain._var_index = new_var_index;
1462 }
1463 
1464 void
1466  std::size_t curr_var_index,
1467  std::size_t new_var_index,
1468  std::vector<std::map<Node *, CacheValues>> & cache,
1469  RemapCacheMode cache_mode)
1470 {
1471  if (curr_node && curr_node->processor_id() == processor_id())
1472  {
1473  // Reinit the node so we can get and set values of the solution here
1474  _subproblem.reinitNode(curr_node, 0);
1475 
1476  // Local variables to hold values being transferred
1477  Real current, old = 0, older = 0;
1478  // Retrieve the value either from the old variable or cache
1479  if (cache_mode == RemapCacheMode::FILL || cache_mode == RemapCacheMode::BYPASS)
1480  {
1481  current = _vars[curr_var_index]->dofValues()[0];
1482  if (_is_transient)
1483  {
1484  old = _vars[curr_var_index]->dofValuesOld()[0];
1485  older = _vars[curr_var_index]->dofValuesOlder()[0];
1486  }
1487  }
1488  else // USE
1489  {
1490  const auto cache_it = cache[curr_var_index].find(curr_node);
1491  mooseAssert(cache_it != cache[curr_var_index].end(), "Error in cache");
1492  current = cache_it->second.current;
1493  old = cache_it->second.old;
1494  older = cache_it->second.older;
1495  }
1496 
1497  // Cache the value or use it!
1498  if (cache_mode == RemapCacheMode::FILL)
1499  {
1500  cache[curr_var_index][curr_node].current = current;
1501  cache[curr_var_index][curr_node].old = old;
1502  cache[curr_var_index][curr_node].older = older;
1503  }
1504  else // USE or BYPASS
1505  {
1506  const auto & dof_index = _vars[new_var_index]->nodalDofIndex();
1507 
1508  // Transfer this solution from the old to the current
1509  _sys.solution().set(dof_index, current);
1510  if (_is_transient)
1511  {
1512  _sys.solutionOld().set(dof_index, old);
1513  _sys.solutionOlder().set(dof_index, older);
1514  }
1515  }
1516 
1529  if (cache_mode == RemapCacheMode::FILL || cache_mode == RemapCacheMode::BYPASS)
1530  {
1531  const auto & dof_index = _vars[curr_var_index]->nodalDofIndex();
1532 
1533  // Set the DOF for the current variable to zero
1534  _sys.solution().set(dof_index, -_bound_value);
1535  if (_is_transient)
1536  {
1537  _sys.solutionOld().set(dof_index, -_bound_value);
1538  _sys.solutionOlder().set(dof_index, -_bound_value);
1539  }
1540  }
1541  }
1542 }
1543 
1544 void
1546 {
1547  TIME_SECTION("updateFieldInfo", 3, "Updating Field Info");
1548 
1549  for (const auto map_num : make_range(_maps_size))
1550  _feature_maps[map_num].clear();
1551 
1552  std::map<dof_id_type, Real> tmp_map;
1553 
1554  for (const auto & grain : _feature_sets)
1555  {
1556  std::size_t curr_var = grain._var_index;
1557  std::size_t map_index = (_single_map_mode || _condense_map_info) ? 0 : curr_var;
1558 
1559  for (auto entity : grain._local_ids)
1560  {
1561  // Highest variable value at this entity wins
1562  Real entity_value = std::numeric_limits<Real>::lowest();
1563  if (_is_elemental)
1564  {
1565  const Elem * elem = _mesh.elemPtr(entity);
1566  std::vector<Point> centroid(1, elem->vertex_average());
1567  if (_poly_ic_uo && _first_time)
1568  {
1569  entity_value = _poly_ic_uo->getVariableValue(grain._var_index, centroid[0]);
1570  }
1571  else
1572  {
1573  _fe_problem.reinitElemPhys(elem, centroid, 0);
1574  entity_value = _vars[curr_var]->sln()[0];
1575  }
1576  }
1577  else
1578  {
1579  auto node_ptr = _mesh.nodePtr(entity);
1580  entity_value = _vars[curr_var]->getNodalValue(*node_ptr);
1581  }
1582 
1583  if (entity_value != std::numeric_limits<Real>::lowest() &&
1584  (tmp_map.find(entity) == tmp_map.end() || entity_value > tmp_map[entity]))
1585  {
1586  mooseAssert(grain._id != invalid_id, "Missing Grain ID");
1587  _feature_maps[map_index][entity] = grain._id;
1588 
1589  if (_var_index_mode)
1590  _var_index_maps[map_index][entity] = grain._var_index;
1591 
1592  tmp_map[entity] = entity_value;
1593  }
1594 
1596  {
1597  auto insert_pair = moose_try_emplace(
1598  _entity_var_to_features, entity, std::vector<unsigned int>(_n_vars, invalid_id));
1599  auto & vec_ref = insert_pair.first->second;
1600 
1601  if (insert_pair.second)
1602  {
1603  // insert the reserve op numbers (if appropriate)
1604  for (const auto reserve_index : make_range(_n_reserve_ops))
1605  vec_ref[reserve_index] = _reserve_grain_first_index + reserve_index;
1606  }
1607  vec_ref[grain._var_index] = grain._id;
1608  }
1609  }
1610 
1611  if (_compute_halo_maps)
1612  for (auto entity : grain._halo_ids)
1613  _halo_ids[grain._var_index][entity] = grain._var_index;
1614 
1615  for (auto entity : grain._ghosted_ids)
1616  _ghosted_entity_ids[entity] = 1;
1617  }
1618 
1620 }
1621 
1622 void
1624 {
1625  if (_compute_halo_maps)
1626  {
1627  // rank var_index entity_id
1628  std::vector<std::pair<std::size_t, dof_id_type>> halo_ids_all;
1629 
1630  std::vector<int> counts;
1631  std::vector<std::pair<std::size_t, dof_id_type>> local_halo_ids;
1632  std::size_t counter = 0;
1633 
1634  const bool isDistributedMesh = _mesh.isDistributedMesh();
1635 
1636  if (_is_primary)
1637  {
1638  std::vector<std::vector<std::pair<std::size_t, dof_id_type>>> root_halo_ids(_n_procs);
1639  counts.resize(_n_procs);
1640 
1641  // Loop over the _halo_ids "field" and build minimal lists for all of the other ranks
1642  for (const auto var_index : index_range(_halo_ids))
1643  {
1644  for (const auto & entity_pair : _halo_ids[var_index])
1645  {
1646  auto entity_id = entity_pair.first;
1647  if (isDistributedMesh)
1648  {
1649  // Check to see which contiguous range this entity ID falls into
1650  auto range_it =
1651  std::lower_bound(_all_ranges.begin(),
1652  _all_ranges.end(),
1653  entity_id,
1654  [](const std::pair<dof_id_type, dof_id_type> range,
1655  dof_id_type entity_id) { return range.second < entity_id; });
1656 
1657  mooseAssert(range_it != _all_ranges.end(), "No range round?");
1658 
1659  // Recover the index from the iterator
1660  auto proc_id = std::distance(_all_ranges.begin(), range_it);
1661 
1662  // Now add this halo entity to the map for the corresponding proc to scatter latter
1663  root_halo_ids[proc_id].push_back(std::make_pair(var_index, entity_id));
1664  }
1665  else
1666  {
1667  DofObject * halo_entity;
1668  if (_is_elemental)
1669  halo_entity = _mesh.queryElemPtr(entity_id);
1670  else
1671  halo_entity = _mesh.queryNodePtr(entity_id);
1672 
1673  if (halo_entity)
1674  root_halo_ids[halo_entity->processor_id()].push_back(
1675  std::make_pair(var_index, entity_id));
1676  }
1677  }
1678  }
1679 
1680  // Build up the counts vector for MPI scatter
1681  for (const auto & vector_ref : root_halo_ids)
1682  {
1683  std::copy(vector_ref.begin(), vector_ref.end(), std::back_inserter(halo_ids_all));
1684  counts[counter] = vector_ref.size();
1685  counter++;
1686  }
1687  }
1688 
1689  _communicator.scatter(halo_ids_all, counts, local_halo_ids);
1690 
1691  // Now add the contributions from the root process to the processor local maps
1692  for (const auto & halo_pair : local_halo_ids)
1693  _halo_ids[halo_pair.first].emplace(std::make_pair(halo_pair.second, halo_pair.first));
1694 
1701  for (const auto & grain : _feature_sets)
1702  for (auto local_id : grain._local_ids)
1703  _halo_ids[grain._var_index].erase(local_id);
1704  }
1705 }
1706 
1707 Real
1708 GrainTracker::centroidRegionDistance(std::vector<BoundingBox> & bboxes1,
1709  std::vector<BoundingBox> & bboxes2) const
1710 {
1714  auto min_distance = std::numeric_limits<Real>::max();
1715  for (const auto & bbox1 : bboxes1)
1716  {
1717  const auto centroid_point1 = (bbox1.max() + bbox1.min()) / 2.0;
1718 
1719  for (const auto & bbox2 : bboxes2)
1720  {
1721  const auto centroid_point2 = (bbox2.max() + bbox2.min()) / 2.0;
1722 
1723  // Here we'll calculate a distance between the centroids
1724  auto curr_distance = _mesh.minPeriodicDistance(_var_number, centroid_point1, centroid_point2);
1725 
1726  if (curr_distance < min_distance)
1727  min_distance = curr_distance;
1728  }
1729  }
1730 
1731  return min_distance;
1732 }
1733 
1734 Real
1735 GrainTracker::boundingRegionDistance(std::vector<BoundingBox> & bboxes1,
1736  std::vector<BoundingBox> & bboxes2) const
1737 {
1744  auto min_distance = std::numeric_limits<Real>::max();
1745  for (const auto & bbox1 : bboxes1)
1746  {
1747  for (const auto & bbox2 : bboxes2)
1748  {
1749  // AABB squared distance
1750  Real curr_distance = 0.0;
1751  bool boxes_overlap = true;
1752  for (unsigned int dim = 0; dim < LIBMESH_DIM; ++dim)
1753  {
1754  const auto & min1 = bbox1.min()(dim);
1755  const auto & max1 = bbox1.max()(dim);
1756  const auto & min2 = bbox2.min()(dim);
1757  const auto & max2 = bbox2.max()(dim);
1758 
1759  if (min1 > max2)
1760  {
1761  const auto delta = max2 - min1;
1762  curr_distance += delta * delta;
1763  boxes_overlap = false;
1764  }
1765  else if (min2 > max1)
1766  {
1767  const auto delta = max1 - min2;
1768  curr_distance += delta * delta;
1769  boxes_overlap = false;
1770  }
1771  }
1772 
1773  if (boxes_overlap)
1774  return -1.0; /* all overlaps are treated the same */
1775 
1776  if (curr_distance < min_distance)
1777  min_distance = curr_distance;
1778  }
1779  }
1780 
1781  return min_distance;
1782 }
1783 
1784 unsigned int
1786 {
1795  _reserve_grain_first_index + _n_reserve_ops /* no +1 here!*/);
1796 
1797  return _max_curr_grain_id;
1798 }
1799 
1800 /*************************************************
1801  ************** Helper Structures ****************
1802  ************************************************/
1803 GrainDistance::GrainDistance(Real distance, std::size_t var_index)
1805  var_index,
1806  std::numeric_limits<std::size_t>::max(),
1807  std::numeric_limits<unsigned int>::max())
1808 {
1809 }
1810 
1812  std::size_t var_index,
1813  std::size_t grain_index,
1814  unsigned int grain_id)
1815  : _distance(distance), _var_index(var_index), _grain_index(grain_index), _grain_id(grain_id)
1816 {
1817 }
1818 
1819 bool
1821 {
1822  return _distance < rhs._distance;
1823 }
void remapGrains()
This method is called after trackGrains to remap grains that are too close to each other...
Definition: GrainTracker.C:909
void clearRelationshipManagers()
bool halosIntersect(const FeatureData &rhs) const
Determine if one of this FeaturesData&#39;s member sets intersects the other FeatureData&#39;s corresponding ...
virtual bool doesFeatureIntersectBoundary(unsigned int feature_id) const override
Returns a Boolean indicating whether this feature intersects any boundary.
Definition: GrainTracker.C:174
std::vector< BoundingBox > _bboxes
The vector of bounding boxes completely enclosing this feature (multiple used with periodic constrain...
void expandEdgeHalos(unsigned int num_layers_to_expand)
This method expands the existing halo set by some width determined by the passed in value...
void broadcast_packed_range(const Context *context1, Iter range_begin, const Iter range_end, OutputContext *context2, OutputIter out, const unsigned int root_id=0, std::size_t approx_buffer_size=1000000) const
virtual std::vector< unsigned int > getNewGrainIDs() const override
This method returns all of the new ids generated in an invocation of the GrainTracker.
Definition: GrainTracker.C:897
This class defines the interface for the GrainTracking objects.
void scatter(const std::vector< T, A > &data, T &recv, const unsigned int root_id=0) const
const std::size_t _n_vars
static const std::size_t invalid_size_t
static InputParameters validParams()
virtual Elem * elemPtr(const dof_id_type i)
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
NumericVector< Number > & solution()
virtual const std::vector< unsigned int > & getVarToFeatureVector(dof_id_type elem_id) const
Returns a list of active unique feature ids for a particular element.
virtual void execute() override
Definition: GrainTracker.C:272
virtual bool isFeaturePercolated(unsigned int feature_id) const override
Returns a Boolean indicating whether this feature is percolated (e.g.
Definition: GrainTracker.C:207
std::vector< std::pair< R1, R2 > > get(const std::string &param1, const std::string &param2) const
virtual void meshChanged() override
Definition: GrainTracker.C:247
const bool _condense_map_info
unsigned int dim
void gather(const unsigned int root_id, const T &send_data, std::vector< T, A > &recv) const
Status
This enumeration is used to indicate status of the grains in the _unique_grains data structure...
virtual Real getVariableValue(unsigned int op_index, const Point &p) const =0
Returns the variable value for a given op_index and mesh point.
bool & _first_time
Boolean to indicate the first time this object executes.
Definition: GrainTracker.h:227
T & set(const std::string &name, bool quiet_mode=false)
static InputParameters validParams()
virtual libMesh::System & system()=0
std::vector< FeatureData > & _feature_sets
The data structure used to hold the globally unique features.
std::vector< FeatureData > _feature_sets_old
This data structure holds the map of unique grains from the previous time step.
Definition: GrainTracker.h:213
MeshBase & mesh
int delta(unsigned int i, unsigned int j)
Delta function, which returns zero if $i j$ and unity if $i=j$.
virtual const Node * queryNodePtr(const dof_id_type i) const
const bool _error_on_grain_creation
Boolean to terminate with an error if a new grain is created during the simulation.
Definition: GrainTracker.h:234
const bool _is_primary
Convenience variable for testing primary rank.
void trackGrains()
On subsequent time_steps, incoming FeatureData objects are compared to previous time_step information...
Definition: GrainTracker.C:498
This object provides the base capability for creating proper polycrystal ICs.
std::map< dof_id_type, std::vector< unsigned int > > _entity_var_to_features
void communicateAndMerge()
This routine handles all of the serialization, communication and deserialization of the data structur...
virtual void initialize() override
NumericVector< Number > & solutionOlder()
const Parallel::Communicator & _communicator
unsigned int _old_max_grain_id
The previous max grain id (needed to figure out which ids are new in a given step) ...
Definition: GrainTracker.h:241
void swapSolutionValuesHelper(Node *curr_node, std::size_t curr_var_index, std::size_t new_var_index, std::vector< std::map< Node *, CacheValues >> &cache, RemapCacheMode cache_mode)
Helper method for actually performing the swaps.
virtual void finalize() override
Definition: GrainTracker.C:329
void addRelationshipManager(const std::string &name, Moose::RelationshipManagerType rm_type, Moose::RelationshipManagerInputParameterCallback input_parameter_callback=nullptr)
unsigned int & _max_curr_grain_id
Holds the next "regular" grain ID (a grain found or remapped to the standard op vars) ...
Definition: GrainTracker.h:244
std::pair< typename M::iterator, bool > moose_try_emplace(M &m, const typename M::key_type &k, Args &&... args)
const std::vector< FeatureData > & getFeatures() const
Return a constant reference to the vector of all discovered features.
registerMooseObject("PhaseFieldApp", GrainTracker)
virtual std::size_t getTotalFeatureCount() const override
Returns the total feature count (active and inactive ids, useful for sizing vectors) ...
Definition: GrainTracker.C:149
Real distance(const Point &p)
const PolycrystalUserObjectBase *const _poly_ic_uo
An optional IC UserObject which can provide initial data structures to this object.
Definition: GrainTracker.h:216
Real centroidRegionDistance(std::vector< BoundingBox > &bboxes1, std::vector< BoundingBox > &bboxes2) const
This method returns the minimum periodic distance between the centroids of two vectors of bounding bo...
void mooseWarning(Args &&... args) const
std::map< dof_id_type, int > _ghosted_entity_ids
The map for holding reconstructed ghosted element information.
This struct is used to hold distance information to other grains in the simulation.
Definition: GrainTracker.h:257
auto max(const L &left, const R &right)
virtual Elem * queryElemPtr(const dof_id_type i)
SubProblem & _subproblem
void storeHelper(std::ostream &stream, P &data, void *context)
Real boundingRegionDistance(std::vector< BoundingBox > &bboxes1, std::vector< BoundingBox > &bboxes2) const
This method returns the minimum periodic distance between two vectors of bounding boxes...
void dataLoad(std::istream &stream, GrainTracker::PartialFeatureData &feature, void *context)
Definition: GrainTracker.C:38
void sortAndLabel()
Sort and assign ids to features based on their position in the container after sorting.
virtual void updateFieldInfo() override
This method is used to populate any of the data structures used for storing field data (nodal or elem...
const Real _bound_value
Absolute value of the lower bound used to represent a region not assigned to this grain...
Definition: GrainTracker.h:201
virtual const std::vector< unsigned int > & getVarToFeatureVector(dof_id_type elem_id) const override
Returns a list of active unique feature ids for a particular element.
Definition: GrainTracker.C:130
void communicateHaloMap()
virtual unsigned int getFeatureVar(unsigned int feature_id) const
Returns the variable representing the passed in feature.
GrainTracker(const InputParameters &parameters)
Definition: GrainTracker.C:84
std::vector< std::map< dof_id_type, int > > _halo_ids
The data structure for looking up halos around features.
virtual void reinitNode(const Node *node, const THREAD_ID tid)=0
BoundaryIntersection boundary_intersection
Definition: GrainTracker.h:38
std::vector< MooseVariable * > _vars
The vector of coupled in variables cast to MooseVariable.
virtual void execute() override
MeshBase & getMesh()
std::size_t _var_index
The Moose variable where this feature was found (often the "order parameter")
void prepopulateState(const FeatureFloodCount &ffc_object)
This method extracts the necessary state from the passed in object necessary to continue tracking gra...
Definition: GrainTracker.C:297
void broadcastAndUpdateGrainData()
Broadcast essential Grain information to all processors.
Definition: GrainTracker.C:387
GrainDistance(Real distance, std::size_t var_index)
virtual const Node * nodePtr(const dof_id_type i) const
unsigned long _var_number
This variable is used to build the periodic node map.
This object will mark nodes or elements of continuous regions all with a unique number for the purpos...
const Real _reserve_op_threshold
The threshold above (or below) where a grain may be found on a reserve op field.
Definition: GrainTracker.h:195
const std::size_t _maps_size
Convenience variable holding the size of all the datastructures size by the number of maps...
virtual void initialize() override
Definition: GrainTracker.C:229
void computeMinDistancesFromGrain(FeatureData &grain, std::vector< std::list< GrainDistance >> &min_distances)
Populates and sorts a min_distances vector with the minimum distances to all grains in the simulation...
void swapSolutionValues(FeatureData &grain, std::size_t new_var_index, std::vector< std::map< Node *, CacheValues >> &cache, RemapCacheMode cache_mode)
A routine for moving all of the solution values from a given grain to a new variable number...
const short _verbosity_level
Verbosity level controlling the amount of information printed to the console.
Definition: GrainTracker.h:221
static const unsigned int invalid_id
std::string stringify(const T &t)
Real minPeriodicDistance(unsigned int nonlinear_var_num, Point p, Point q) const
std::vector< std::map< dof_id_type, int > > _feature_maps
The feature maps contain the raw flooded node information and eventually the unique grain numbers...
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) ...
virtual std::size_t getNumberActiveGrains() const override
Returns the number of active grains current stored in the GrainTracker.
Definition: GrainTracker.C:142
void broadcast(T &data, const unsigned int root_id=0, const bool identical_sizes=false) const
virtual void close()=0
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 void update()
const bool _compute_halo_maps
Indicates whether or not to communicate halo map information with all ranks.
std::vector< std::pair< dof_id_type, dof_id_type > > _all_ranges
Data structure to hold element ID ranges when using Distributed Mesh (populated on rank 0 only) ...
Definition: GrainTracker.h:250
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
const bool _remap
Inidicates whether remapping should be done or not (remapping is independent of tracking) ...
Definition: GrainTracker.h:204
const bool _is_elemental
Determines if the flood counter is elements or not (nodes)
container_type _local_ids
Holds the local ids in the interior of a feature.
FEProblemBase & _fe_problem
bool attemptGrainRenumber(FeatureData &grain, unsigned int depth, unsigned int max_depth)
This is the recursive part of the remapping algorithm.
const processor_id_type _n_procs
Convenience variable holding the number of processors in this simulation.
IntRange< T > make_range(T beg, T end)
static InputParameters validParams()
Definition: GrainTracker.C:49
virtual ~GrainTracker()
Definition: GrainTracker.C:116
const bool _is_transient
Boolean to indicate whether this is a Steady or Transient solve.
Definition: GrainTracker.h:247
virtual bool doesFeatureIntersectSpecifiedBoundary(unsigned int feature_id) const override
Returns a Boolean indicating whether this feature intersects boundaries in a user-supplied list...
Definition: GrainTracker.C:190
virtual void meshChanged() override
std::vector< unsigned int > _feature_counts_per_map
The number of features seen by this object per map.
void mooseError(Args &&... args) const
std::vector< std::size_t > _feature_id_to_local_index
The vector recording the grain_id to local index (several indices will contain invalid_size_t) ...
void addClassDescription(const std::string &doc_string)
void dataStore(std::ostream &stream, GrainTracker::PartialFeatureData &feature, void *context)
Definition: GrainTracker.C:28
static const std::complex< double > j(0, 1)
Complex number "j" (also known as "i")
void assignGrains()
When the tracking phase starts (_t_step == _tracking_step) it assigns a unique id to every FeatureDat...
Definition: GrainTracker.C:450
virtual void set(const numeric_index_type i, const Number value)=0
Status _status
The status of a feature (used mostly in derived classes like the GrainTracker)
const ConsoleStream _console
bool operator<(const GrainDistance &rhs) const
const int _tracking_step
The timestep to begin tracking grains.
Definition: GrainTracker.h:179
unsigned int _reserve_grain_first_index
Holds the first unique grain index when using _reserve_op (all the remaining indices are sequential) ...
Definition: GrainTracker.h:238
virtual Real getEntityValue(dof_id_type node_id, FieldType field_type, std::size_t var_index=0) const override
Definition: GrainTracker.C:119
const unsigned short _halo_level
The thickness of the halo surrounding each grain.
Definition: GrainTracker.h:182
const std::size_t _reserve_op_index
The cutoff index where if variable index >= this number, no remapping TO that variable will occur...
Definition: GrainTracker.h:192
NumericVector< Number > & solutionOld()
const unsigned short _max_remap_recursion_depth
Depth of renumbering recursion (a depth of zero means no recursion)
Definition: GrainTracker.h:185
MooseMesh & _mesh
A reference to the mesh.
processor_id_type processor_id() const
virtual bool isDistributedMesh() const
virtual Point getGrainCentroid(unsigned int grain_id) const override
Returns the centroid for the given grain number.
Definition: GrainTracker.C:156
virtual Real getEntityValue(dof_id_type entity_id, FieldType field_type, std::size_t var_index=0) const
void buildFeatureIdToLocalIndices(unsigned int max_id)
This method builds a lookup map for retrieving the right local feature (by index) given a global inde...
void scatterAndUpdateRanks()
Calls buildLocalToGlobalIndices to build the individual local to global indicies for each rank and sc...
virtual void reinitElemPhys(const Elem *elem, const std::vector< Point > &phys_points_in_elem, const THREAD_ID tid) override
virtual unsigned int getFeatureVar(unsigned int feature_id) const override
Returns the variable representing the passed in feature.
Definition: GrainTracker.C:136
void paramInfo(const std::string &param, Args... args) const
void loadHelper(std::istream &stream, P &data, void *context)
virtual void newGrainCreated(unsigned int new_grain_id)
This method is called when a new grain is detected.
Definition: GrainTracker.C:876
const bool _compute_var_to_feature_map
Indicates whether or not the var to feature map is populated.
void ErrorVector unsigned int
auto index_range(const T &sizable)
virtual Real getThreshold(std::size_t current_index) const override
Return the starting comparison threshold to use when inspecting an entity during the flood stage...
Definition: GrainTracker.C:285
const bool _tolerate_failure
Indicates whether we should continue after a remap failure (will result in non-physical results) ...
Definition: GrainTracker.h:207
const bool _var_index_mode
This variable is used to indicate whether the maps will contain unique region information or just the...
unsigned int getNextUniqueID()
Retrieve the next unique grain number if a new grain is detected during trackGrains.
unsigned int _id
An ID for this feature.
uint8_t dof_id_type
std::vector< std::map< dof_id_type, int > > _var_index_maps
This map keeps track of which variables own which nodes.
const unsigned short _n_reserve_ops
The number of reserved order parameters.
Definition: GrainTracker.h:188