https://mooseframework.inl.gov
Loading...
Searching...
No Matches
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
26template <>
27void
28dataStore(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
36template <>
37void
38dataLoad(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
47
50{
53
54 // FeatureFloodCount adds a relationship manager, but we need to extend that for GrainTracker
56
58 "ElementSideNeighborLayers",
59 Moose::RelationshipManagerType::GEOMETRIC,
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",
67 Moose::RelationshipManagerType::ALGEBRAIC);
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{
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
118Real
119GrainTracker::getEntityValue(dof_id_type entity_id,
120 FieldType field_type,
121 std::size_t var_index) const
122{
124 return 0;
125
126 return FeatureFloodCount::getEntityValue(entity_id, field_type, var_index);
127}
128
129const std::vector<unsigned int> &
130GrainTracker::getVarToFeatureVector(dof_id_type elem_id) const
131{
133}
134
135unsigned int
136GrainTracker::getFeatureVar(unsigned int feature_id) const
137{
138 return FeatureFloodCount::getFeatureVar(feature_id);
139}
140
141std::size_t
143{
144 // Note: This value is parallel consistent, see FeatureFloodCount::communicateAndMerge()
145 return _feature_count;
146}
147
148std::size_t
150{
151 // Note: This value is parallel consistent, see assignGrains()/trackGrains()
153}
154
155Point
156GrainTracker::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
173bool
174GrainTracker::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
189bool
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
206bool
207GrainTracker::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
228void
230{
231 // Don't track grains if the current simulation step is before the specified tracking step
233 return;
234
240 if (!_first_time)
242
244}
245
246void
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
266 }
267
269}
270
271void
273{
274 // Don't track grains if the current simulation step is before the specified tracking step
276 return;
277
279 return;
280
282}
283
284Real
285GrainTracker::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)
292 else
293 return _step_threshold;
294}
295
296void
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
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
328void
330{
331 // Don't track grains if the current simulation step is before the specified 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
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
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
386void
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
449void
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 {
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
497void
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)
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
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
875void
876GrainTracker::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
896std::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
908void
910{
911 // Don't remap grains if the current simulation step is before the specified 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
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();
1168
1169 _sys.system().update();
1170
1171 if (_verbosity_level > 1)
1172 _console << "Swaps complete" << std::endl;
1173 }
1174}
1175
1176void
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
1250bool
1251GrainTracker::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
1425void
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
1464void
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
1544void
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
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
1622void
1624{
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
1707Real
1708GrainTracker::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 =
1725 _mesh.minPeriodicDistance(*_fe_vars[0], centroid_point1, centroid_point2);
1726
1727 if (curr_distance < min_distance)
1728 min_distance = curr_distance;
1729 }
1730 }
1731
1732 return min_distance;
1733}
1734
1735Real
1736GrainTracker::boundingRegionDistance(std::vector<BoundingBox> & bboxes1,
1737 std::vector<BoundingBox> & bboxes2) const
1738{
1745 auto min_distance = std::numeric_limits<Real>::max();
1746 for (const auto & bbox1 : bboxes1)
1747 {
1748 for (const auto & bbox2 : bboxes2)
1749 {
1750 // AABB squared distance
1751 Real curr_distance = 0.0;
1752 bool boxes_overlap = true;
1753 for (unsigned int dim = 0; dim < LIBMESH_DIM; ++dim)
1754 {
1755 const auto & min1 = bbox1.min()(dim);
1756 const auto & max1 = bbox1.max()(dim);
1757 const auto & min2 = bbox2.min()(dim);
1758 const auto & max2 = bbox2.max()(dim);
1759
1760 if (min1 > max2)
1761 {
1762 const auto delta = max2 - min1;
1763 curr_distance += delta * delta;
1764 boxes_overlap = false;
1765 }
1766 else if (min2 > max1)
1767 {
1768 const auto delta = max1 - min2;
1769 curr_distance += delta * delta;
1770 boxes_overlap = false;
1771 }
1772 }
1773
1774 if (boxes_overlap)
1775 return -1.0; /* all overlaps are treated the same */
1776
1777 if (curr_distance < min_distance)
1778 min_distance = curr_distance;
1779 }
1780 }
1781
1782 return min_distance;
1783}
1784
1785unsigned int
1800
1801/*************************************************
1802 ************** Helper Structures ****************
1803 ************************************************/
1804GrainDistance::GrainDistance(Real distance, std::size_t var_index)
1806 var_index,
1807 std::numeric_limits<std::size_t>::max(),
1808 std::numeric_limits<unsigned int>::max())
1809{
1810}
1811
1813 std::size_t var_index,
1814 std::size_t grain_index,
1815 unsigned int grain_id)
1816 : _distance(distance), _var_index(var_index), _grain_index(grain_index), _grain_id(grain_id)
1817{
1818}
1819
1820bool
1821GrainDistance::operator<(const GrainDistance & rhs) const
1822{
1823 return _distance < rhs._distance;
1824}
void storeHelper(std::ostream &stream, P &data, void *context)
void loadHelper(std::istream &stream, P &data, void *context)
void dataLoad(std::istream &stream, GrainTracker::PartialFeatureData &feature, void *context)
registerMooseObject("PhaseFieldApp", GrainTracker)
void dataStore(std::ostream &stream, GrainTracker::PartialFeatureData &feature, void *context)
void dataLoad(std::istream &stream, GrainTracker::PartialFeatureData &feature, void *context)
void dataStore(std::ostream &stream, GrainTracker::PartialFeatureData &feature, void *context)
std::pair< typename M::iterator, bool > moose_try_emplace(M &m, const typename M::key_type &k, Args &&... args)
sideset clear()
unsigned int dim
void ErrorVector unsigned int
const ConsoleStream _console
virtual void reinitElemPhys(const Elem *elem, const std::vector< Point > &phys_points_in_elem, const THREAD_ID tid) override
Status _status
The status of a feature (used mostly in derived classes like the GrainTracker)
std::vector< BoundingBox > _bboxes
The vector of bounding boxes completely enclosing this feature (multiple used with periodic constrain...
container_type _local_ids
Holds the local ids in the interior of a feature.
std::size_t _var_index
The Moose variable where this feature was found (often the "order parameter")
bool halosIntersect(const FeatureData &rhs) const
Determine if one of this FeaturesData's member sets intersects the other FeatureData's corresponding ...
unsigned int _id
An ID for this feature.
This object will mark nodes or elements of continuous regions all with a unique number for the purpos...
const bool _is_elemental
Determines if the flood counter is elements or not (nodes)
const bool _is_primary
Convenience variable for testing primary rank.
const bool _var_index_mode
This variable is used to indicate whether the maps will contain unique region information or just the...
std::vector< MooseVariable * > _vars
The vector of coupled in variables cast to MooseVariable.
static InputParameters validParams()
const processor_id_type _n_procs
Convenience variable holding the number of processors in this simulation.
virtual unsigned int getFeatureVar(unsigned int feature_id) const
Returns the variable representing the passed in feature.
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)
virtual void meshChanged() override
void buildFeatureIdToLocalIndices(unsigned int max_id)
This method builds a lookup map for retrieving the right local feature (by index) given a global inde...
unsigned int _feature_count
The number of features seen by this object (same as summing _feature_counts_per_map)
std::vector< unsigned int > _feature_counts_per_map
The number of features seen by this object per map.
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.
virtual void initialize() override
std::vector< std::map< dof_id_type, int > > _halo_ids
The data structure for looking up halos around features.
Status
This enumeration is used to indicate status of the grains in the _unique_grains data structure.
const std::size_t _maps_size
Convenience variable holding the size of all the datastructures size by the number of maps.
std::vector< std::map< dof_id_type, int > > _var_index_maps
This map keeps track of which variables own which nodes.
virtual void execute() override
const bool _condense_map_info
const bool _single_map_mode
This variable is used to indicate whether or not multiple maps are used during flooding.
static const std::size_t invalid_size_t
static const unsigned int invalid_id
std::vector< MooseVariableFEBase * > _fe_vars
The vector of coupled in variables.
void scatterAndUpdateRanks()
Calls buildLocalToGlobalIndices to build the individual local to global indicies for each rank and sc...
virtual Real getEntityValue(dof_id_type entity_id, FieldType field_type, std::size_t var_index=0) const
std::map< dof_id_type, std::vector< unsigned int > > _entity_var_to_features
std::vector< std::list< FeatureData > > _partial_feature_sets
The data structure used to hold partial and communicated feature data, during the discovery and mergi...
std::vector< FeatureData > & _feature_sets
The data structure used to hold the globally unique features.
void communicateAndMerge()
This routine handles all of the serialization, communication and deserialization of the data structur...
void sortAndLabel()
Sort and assign ids to features based on their position in the container after sorting.
const std::size_t _n_vars
const bool _compute_halo_maps
Indicates whether or not to communicate halo map information with all ranks.
const bool _compute_var_to_feature_map
Indicates whether or not the var to feature map is populated.
MooseMesh & _mesh
A reference to the mesh.
void expandEdgeHalos(unsigned int num_layers_to_expand)
This method expands the existing halo set by some width determined by the passed in value.
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.
const std::vector< FeatureData > & getFeatures() const
Return a constant reference to the vector of all discovered features.
std::map< dof_id_type, int > _ghosted_entity_ids
The map for holding reconstructed ghosted element information.
This class defines the interface for the GrainTracking objects.
static InputParameters validParams()
virtual Real getEntityValue(dof_id_type node_id, FieldType field_type, std::size_t var_index=0) const override
virtual ~GrainTracker()
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.
bool & _first_time
Boolean to indicate the first time this object executes.
void remapGrains()
This method is called after trackGrains to remap grains that are too close to each other.
unsigned int _old_max_grain_id
The previous max grain id (needed to figure out which ids are new in a given step)
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)
static InputParameters validParams()
virtual void execute() override
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.
const bool _error_on_grain_creation
Boolean to terminate with an error if a new grain is created during the simulation.
const PolycrystalUserObjectBase *const _poly_ic_uo
An optional IC UserObject which can provide initial data structures to this object.
virtual void updateFieldInfo() override
This method is used to populate any of the data structures used for storing field data (nodal or elem...
bool attemptGrainRenumber(FeatureData &grain, unsigned int depth, unsigned int max_depth)
This is the recursive part of the remapping algorithm.
const Real _reserve_op_threshold
The threshold above (or below) where a grain may be found on a reserve op field.
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 assignGrains()
When the tracking phase starts (_t_step == _tracking_step) it assigns a unique id to every FeatureDat...
unsigned int & _max_curr_grain_id
Holds the next "regular" grain ID (a grain found or remapped to the standard op vars)
virtual std::vector< unsigned int > getNewGrainIDs() const override
This method returns all of the new ids generated in an invocation of the GrainTracker.
virtual std::size_t getTotalFeatureCount() const override
Returns the total feature count (active and inactive ids, useful for sizing vectors)
const unsigned short _n_reserve_ops
The number of reserved order parameters.
const int _tracking_step
The timestep to begin tracking grains.
unsigned int getNextUniqueID()
Retrieve the next unique grain number if a new grain is detected during trackGrains.
void prepopulateState(const FeatureFloodCount &ffc_object)
This method extracts the necessary state from the passed in object necessary to continue tracking gra...
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.
const bool _remap
Inidicates whether remapping should be done or not (remapping is independent of tracking)
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.
void communicateHaloMap()
std::vector< FeatureData > _feature_sets_old
This data structure holds the map of unique grains from the previous time step.
void trackGrains()
On subsequent time_steps, incoming FeatureData objects are compared to previous time_step information...
virtual void meshChanged() override
virtual unsigned int getFeatureVar(unsigned int feature_id) const override
Returns the variable representing the passed in feature.
void broadcastAndUpdateGrainData()
Broadcast essential Grain information to all processors.
virtual void initialize() override
virtual Point getGrainCentroid(unsigned int grain_id) const override
Returns the centroid for the given grain number.
const Real _bound_value
Absolute value of the lower bound used to represent a region not assigned to this grain,...
const std::size_t _reserve_op_index
The cutoff index where if variable index >= this number, no remapping TO that variable will occur.
virtual std::size_t getNumberActiveGrains() const override
Returns the number of active grains current stored in the GrainTracker.
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...
const bool _is_transient
Boolean to indicate whether this is a Steady or Transient solve.
const short _verbosity_level
Verbosity level controlling the amount of information printed to the console.
const unsigned short _max_remap_recursion_depth
Depth of renumbering recursion (a depth of zero means no recursion)
virtual void finalize() override
virtual bool isFeaturePercolated(unsigned int feature_id) const override
Returns a Boolean indicating whether this feature is percolated (e.g.
virtual bool doesFeatureIntersectBoundary(unsigned int feature_id) const override
Returns a Boolean indicating whether this feature intersects any boundary.
const bool _tolerate_failure
Indicates whether we should continue after a remap failure (will result in non-physical results)
virtual void newGrainCreated(unsigned int new_grain_id)
This method is called when a new grain is detected.
unsigned int _reserve_grain_first_index
Holds the first unique grain index when using _reserve_op (all the remaining indices are sequential)
virtual bool doesFeatureIntersectSpecifiedBoundary(unsigned int feature_id) const override
Returns a Boolean indicating whether this feature intersects boundaries in a user-supplied list.
GrainTracker(const InputParameters &parameters)
const unsigned short _halo_level
The thickness of the halo surrounding each grain.
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 addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
std::vector< std::pair< R1, R2 > > get(const std::string &param1, const std::string &param2) const
void addRelationshipManager(const std::string &name, Moose::RelationshipManagerType rm_type, Moose::RelationshipManagerInputParameterCallback input_parameter_callback=nullptr)
void clearRelationshipManagers()
void addClassDescription(const std::string &doc_string)
T & set(const std::string &name, bool quiet_mode=false)
void paramInfo(const std::string &param, Args... args) const
void mooseError(Args &&... args) const
void mooseWarning(Args &&... args) const
Real minPeriodicDistance(const unsigned int sys_num, const unsigned int var_num, const Point &p, const Point &q) const
virtual Elem * elemPtr(const dof_id_type i)
MeshBase & getMesh()
virtual bool isDistributedMesh() const
virtual Elem * queryElemPtr(const dof_id_type i)
virtual const Node * nodePtr(const dof_id_type i) const
virtual const Node * queryNodePtr(const dof_id_type i) const
This object provides the base capability for creating proper polycrystal ICs.
virtual Real getVariableValue(unsigned int op_index, const Point &p) const =0
Returns the variable value for a given op_index and mesh point.
virtual void reinitNode(const Node *node, const THREAD_ID tid)=0
NumericVector< Number > & solutionOld()
NumericVector< Number > & solutionOlder()
NumericVector< Number > & solution()
virtual libMesh::System & system()=0
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
void scatter(const std::vector< T, A > &data, T &recv, const unsigned int root_id=0) const
void broadcast(T &data, const unsigned int root_id=0, const bool identical_sizes=false) const
void gather(const unsigned int root_id, const T &send_data, std::vector< T, A > &recv) const
SubProblem & _subproblem
FEProblemBase & _fe_problem
virtual void set(const numeric_index_type i, const T value)=0
virtual void close()=0
const Parallel::Communicator & _communicator
processor_id_type processor_id() const
virtual void update()
MeshBase & mesh
std::string stringify(const T &t)
This struct is used to hold distance information to other grains in the simulation.
bool operator<(const GrainDistance &rhs) const
GrainDistance(Real distance, std::size_t var_index)
BoundaryIntersection boundary_intersection
Real distance(const Point &p)