https://mooseframework.inl.gov
Loading...
Searching...
No Matches
PolycrystalUserObjectBase.C
Go to the documentation of this file.
1//* This file is part of the MOOSE framework
2//* https://mooseframework.inl.gov
3//*
4//* All rights reserved, see COPYRIGHT for full restrictions
5//* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6//*
7//* Licensed under LGPL 2.1, please see LICENSE for details
8//* https://www.gnu.org/licenses/lgpl-2.1.html
9
10#include "DenseMatrix.h"
12#include "NonlinearSystemBase.h"
13#include "MooseMesh.h"
14#include "MooseVariable.h"
15
16#include <vector>
17#include <map>
18#include <algorithm>
19
22{
24 params.addClassDescription("This object provides the base capability for creating proper reduced "
25 "order parameter polycrystal initial conditions.");
27 "variable", "var_name_base", "op_num", "Array of coupled variables");
28 params.addParam<bool>("output_adjacency_matrix",
29 false,
30 "Output the Grain Adjacency Matrix used in the coloring algorithms. "
31 "Additionally, the grain to OP assignments will be printed");
32 params.addParam<MooseEnum>("coloring_algorithm",
35
36 // FeatureFloodCount adds a relationship manager, but we need to extend that for PolycrystalIC
38
40 "ElementSideNeighborLayers",
41 Moose::RelationshipManagerType::GEOMETRIC,
42
43 [](const InputParameters & /*obj_params*/, InputParameters & rm_params)
44 { rm_params.set<unsigned short>("layers") = 2; }
45
46 );
47
48 params.addRelationshipManager("ElementSideNeighborLayers",
49 Moose::RelationshipManagerType::ALGEBRAIC);
50
51 // Hide the output of the IC objects by default, it doesn't change over time
52 params.set<std::vector<OutputName>>("outputs") = {"none"};
53
55 params.set<bool>("allow_duplicate_execution_on_initial") = true;
56
57 // This object should only be executed _before_ the initial condition
59 execute_options = EXEC_INITIAL;
60 params.set<ExecFlagEnum>("execute_on") = execute_options;
61
62 return params;
63}
64
66 : FeatureFloodCount(parameters),
67 _dim(_mesh.dimension()),
68 _op_num(_vars.size()),
69 _coloring_algorithm(getParam<MooseEnum>("coloring_algorithm")),
70 _colors_assigned(false),
71 _output_adjacency_matrix(getParam<bool>("output_adjacency_matrix")),
72 _num_chunks(FeatureFloodCount::invalid_proc_id)
73{
74 mooseAssert(_single_map_mode, "Do not turn off single_map_mode with this class");
75}
76
77void
79{
84 if (_op_num < 1)
85 mooseError("No coupled variables found");
86
87 const auto first_variable_value = _mesh.queryPeriodicDimensions(*_vars[0]);
88 for (unsigned int i = 1; i < _vars.size(); ++i)
89 if (_mesh.queryPeriodicDimensions(*_vars[i]) != first_variable_value)
90 mooseError("Coupled polycrystal variables differ in periodicity");
91
93}
94
95void
105
106void
108{
109 if (!_colors_assigned)
111 // No need to rerun the object if the mesh hasn't changed
113 return;
114
115 TIME_SECTION("execute", 2, "Computing Polycrystal Initial Condition");
116
122
135 for (const auto & current_elem : _fe_problem.getNonlinearEvaluableElementRange())
136 {
137 // Loop over elements or nodes
138 if (_is_elemental)
139 while (flood(current_elem, invalid_size_t))
140 ;
141 else
142 {
143 auto n_nodes = current_elem->n_vertices();
144 for (auto i = decltype(n_nodes)(0); i < n_nodes; ++i)
145 {
146 const Node * current_node = current_elem->node_ptr(i);
147
148 while (flood(current_node, invalid_size_t))
149 ;
150 }
151 }
152 }
153}
154
155processor_id_type
157{
159 "prepareDataForTransfer() hasn't been called yet");
160
161 return _num_chunks;
162}
163
164void
166{
168
198 _partial_feature_sets[0].sort();
199
200 // Get the largest ID seen on any rank
201 auto largest_id = _partial_feature_sets[0].back()._id;
202 _communicator.max(largest_id);
203 mooseAssert(largest_id != invalid_size_t, "Largest ID should not be invalid");
204
212 auto total_items = largest_id + 1;
213
214 _num_chunks = std::min(_app.n_processors(), total_items);
215
225
226 for (auto it = _partial_feature_sets[0].begin(); it != _partial_feature_sets[0].end();
227 /* No increment on it*/)
228 {
229 auto chunk = MooseUtils::linearPartitionChunk(total_items, _num_chunks, it->_id);
230
231 if (chunk)
232 {
233 _partial_feature_sets[chunk].emplace_back(std::move(*it));
234 it = _partial_feature_sets[0].erase(it); // it is incremented here!
235 }
236 else
237 ++it;
238 }
239}
240
241void
243{
245 return;
246
247 TIME_SECTION("finalize", 2, "Finalizing Polycrystal Initial Condition");
248
249 // TODO: Possibly retrieve the halo thickness from the active GrainTracker object?
250 constexpr unsigned int halo_thickness = 2;
251
252 expandEdgeHalos(halo_thickness - 1);
253
255
256 if (!_colors_assigned)
257 {
258 // Resize the color assignment vector here. All ranks need a copy of this
260 if (_is_primary)
261 {
263
265
268 }
269
270 // Communicate the coloring map with all ranks
272
276 for (auto & feature : _feature_sets)
277 feature._var_index = _grain_to_op.at(feature._id);
278 }
279
280 _colors_assigned = true;
281}
282
283void
285{
286 // When working with _distribute_merge_work all of the maps will be empty except for one
287 for (const auto map_num : index_range(_partial_feature_sets))
288 {
294 _partial_feature_sets[map_num].sort();
295
296 auto it1 = _partial_feature_sets[map_num].begin();
297 auto it_end = _partial_feature_sets[map_num].end();
298 while (it1 != it_end)
299 {
300 auto it2 = it1;
301 if (++it2 == it_end)
302 break;
303
304 if (areFeaturesMergeable(*it1, *it2))
305 {
306 it1->merge(std::move(*it2));
307 _partial_feature_sets[map_num].erase(it2);
308 }
309 else
310 ++it1; // Only increment if we have a mismatch
311 }
312 }
313}
314
315void
316PolycrystalUserObjectBase::restoreOriginalDataStructures(std::vector<std::list<FeatureData>> & orig)
317{
318 // Move all the data back into the first list
319 auto & master_list = orig[0];
320 for (MooseIndex(_maps_size) map_num = 1; map_num < orig.size(); ++map_num)
321 {
322 master_list.splice(master_list.end(), orig[map_num]);
323 orig[map_num].clear();
324 }
325
326 orig.resize(1);
327}
328
329bool
331 std::size_t & current_index,
332 FeatureData *& feature,
333 Status & status,
334 unsigned int & new_id)
335{
336 mooseAssert(_t_step == 0, "PolyIC only works if we begin in the initial condition");
337
338 // Retrieve the id of the current entity
339 auto entity_id = dof_object->id();
340 auto grains_it = _entity_to_grain_cache.lower_bound(entity_id);
341
342 if (grains_it == _entity_to_grain_cache.end() || grains_it->first != entity_id)
343 {
344 std::vector<unsigned int> grain_ids;
345
346 if (_is_elemental)
347 getGrainsBasedOnElem(*static_cast<const Elem *>(dof_object), grain_ids);
348 else
349 getGrainsBasedOnPoint(*static_cast<const Node *>(dof_object), grain_ids);
350
351 grains_it = _entity_to_grain_cache.emplace_hint(grains_it, entity_id, std::move(grain_ids));
352 }
353
363 auto saved_grain_id = invalid_id;
364 if (current_index == invalid_size_t)
365 {
366 for (auto grain_id : grains_it->second)
367 {
368 auto map_it = _grain_to_op.find(grain_id);
369 mooseAssert(!_colors_assigned || map_it != _grain_to_op.end(), "grain_id missing");
370 auto map_num = _colors_assigned ? map_it->second : grain_id;
371
372 if (_entities_visited[map_num].find(entity_id) == _entities_visited[map_num].end())
373 {
374 saved_grain_id = grain_id;
375 current_index = map_num;
376 break;
377 }
378 }
379
380 if (current_index == invalid_size_t)
381 return false;
382 }
383 else if (_entities_visited[current_index].find(entity_id) !=
384 _entities_visited[current_index].end())
385 return false;
386
387 if (!feature)
388 {
389 new_id = saved_grain_id;
390 status &= ~Status::INACTIVE;
391
392 return true;
393 }
394 else
395 {
396 const auto & grain_ids = grains_it->second;
397 if (std::find(grain_ids.begin(), grain_ids.end(), feature->_id) != grain_ids.end())
398 return true;
399
405 if (_is_elemental)
406 {
407 Elem * elem = _mesh.queryElemPtr(entity_id);
408 mooseAssert(elem, "Element is nullptr");
409
410 std::vector<const Elem *> all_active_neighbors;
411 MeshBase & mesh = _mesh.getMesh();
412
413 for (auto i = decltype(elem->n_neighbors())(0); i < elem->n_neighbors(); ++i)
414 {
415 const Elem * neighbor_ancestor = nullptr;
416
421 neighbor_ancestor = elem->neighbor_ptr(i);
422
423 if (neighbor_ancestor)
424 {
431 if (neighbor_ancestor->is_remote())
432 continue;
433
434 neighbor_ancestor->active_family_tree_by_neighbor(all_active_neighbors, elem, false);
435 }
436 else // if (expand_halos_only /*&& feature->_periodic_nodes.empty()*/)
437 {
438 neighbor_ancestor = elem->topological_neighbor(i, mesh, *_point_locator, _pbs);
439
447 if (neighbor_ancestor)
448 {
455 if (neighbor_ancestor->is_remote())
456 continue;
457
458 neighbor_ancestor->active_family_tree_by_topological_neighbor(
459 all_active_neighbors, elem, mesh, *_point_locator, _pbs, false);
460 }
461 }
462 }
463
464 for (const auto neighbor : all_active_neighbors)
465 {
466 // Retrieve the id of the current entity
467 auto neighbor_id = neighbor->id();
468 auto neighbor_it = _entity_to_grain_cache.lower_bound(neighbor_id);
469
470 if (neighbor_it == _entity_to_grain_cache.end() || neighbor_it->first != neighbor_id)
471 {
472 std::vector<unsigned int> more_grain_ids;
473
474 getGrainsBasedOnElem(*static_cast<const Elem *>(neighbor), more_grain_ids);
475
476 neighbor_it = _entity_to_grain_cache.emplace_hint(
477 neighbor_it, neighbor_id, std::move(more_grain_ids));
478 }
479
480 const auto & more_grain_ids = neighbor_it->second;
481 if (std::find(more_grain_ids.begin(), more_grain_ids.end(), feature->_id) !=
482 more_grain_ids.end())
483 return true;
484 }
485 }
486
487 return false;
488 }
489}
490
491bool
493 const FeatureData & f2) const
494{
495 if (f1._id != f2._id)
496 return false;
497
498 mooseAssert(f1._var_index == f2._var_index, "Feature should be mergeable but aren't");
499 return true;
500}
501
502void
504{
505 mooseAssert(_is_primary, "This routine should only be called on the primary rank");
506
507 _adjacency_matrix = std::make_unique<DenseMatrix<Real>>(_feature_count, _feature_count);
508 for (MooseIndex(_feature_sets) i = 0; i < _feature_sets.size(); ++i)
509 {
510 for (MooseIndex(_feature_sets) j = i + 1; j < _feature_sets.size(); ++j)
511 {
512 if (_feature_sets[i].boundingBoxesIntersect(_feature_sets[j]) &&
513 _feature_sets[i].halosIntersect(_feature_sets[j]))
514 {
515 // Our grain adjacency matrix is symmetrical
516 (*_adjacency_matrix)(i, j) = 1;
517 (*_adjacency_matrix)(j, i) = 1;
518 }
519 }
520 }
521}
522
523void
525{
526 mooseAssert(_is_primary, "This routine should only be called on the primary rank");
527
528 TIME_SECTION("assignOpsToGrains", 2, "Assigning OPs to grains");
529
530 // Use a simple backtracking coloring algorithm
531 if (_coloring_algorithm == "bt")
532 {
533 paramInfo("coloring_algorithm",
534 "The backtracking algorithm has exponential complexity. If you are using very few "
535 "order parameters,\nor you have several hundred grains or more, you should use one "
536 "of the PETSc coloring algorithms such as \"jp\".");
537
538 if (!colorGraph(0))
539 paramError("op_num",
540 "Unable to find a valid grain to op coloring, Make sure you have created enough "
541 "variables to hold a\nvalid polycrystal initial condition (no grains represented "
542 "by the same variable should be allowed to\ntouch, ~8 for 2D, ~25 for 3D)?");
543 }
544 else // PETSc Coloring algorithms
545 {
546 const std::string & ca_str = _coloring_algorithm;
547 Real * am_data = _adjacency_matrix->get_values().data();
548
549 try
550 {
552 am_data, _feature_count, _vars.size(), _grain_idx_to_op, ca_str.c_str());
553 }
554 catch (std::runtime_error & e)
555 {
556 paramError("op_num",
557 "Unable to find a valid grain to op coloring, Make sure you have created enough "
558 "variables to hold a\nvalid polycrystal initial condition (no grains represented "
559 "by the same variable should be allowed to\ntouch, ~8 for 2D, ~25 for 3D)?");
560 }
561 }
562
567 mooseAssert(_grain_to_op.empty(), "grain_to_op data structure should be empty here");
568 for (MooseIndex(_grain_idx_to_op) i = 0; i < _grain_idx_to_op.size(); ++i)
569 _grain_to_op.emplace_hint(_grain_to_op.end(), _feature_sets[i]._id, _grain_idx_to_op[i]);
570}
571
572bool
574{
575 // Base case: All grains are assigned
576 if (vertex == _feature_count)
577 return true;
578
579 // Consider this grain and try different ops
580 for (unsigned int color_idx = 0; color_idx < _op_num; ++color_idx)
581 {
582 // We'll try to spread these colors around a bit rather than
583 // packing them all on the first few colors if we have several colors.
584 unsigned int color = (vertex + color_idx) % _op_num;
585
586 if (isGraphValid(vertex, color))
587 {
588 _grain_idx_to_op[vertex] = color;
589
590 if (colorGraph(vertex + 1))
591 return true;
592
593 // Backtrack...
595 }
596 }
597
598 return false;
599}
600
601bool
602PolycrystalUserObjectBase::isGraphValid(unsigned int vertex, unsigned int color)
603{
604 // See if the proposed color is valid based on the current neighbor colors
605 for (unsigned int neighbor = 0; neighbor < _feature_count; ++neighbor)
606 if ((*_adjacency_matrix)(vertex, neighbor) && color == _grain_idx_to_op[neighbor])
607 return false;
608 return true;
609}
610
611void
613{
614 _console << "Grain Adjacency Matrix:\n";
615 for (unsigned int i = 0; i < _adjacency_matrix->m(); i++)
616 {
617 for (unsigned int j = 0; j < _adjacency_matrix->n(); j++)
618 _console << _adjacency_matrix->el(i, j) << " ";
619 _console << '\n';
620 }
621
622 _console << "Grain to OP assignments:\n";
623 for (auto op : _grain_idx_to_op)
624 _console << op << " ";
625 _console << '\n' << std::endl;
626}
627
630{
631 return MooseEnum("jp power greedy bt", "jp");
632}
633
634std::string
636{
637 return "The grain neighbor graph coloring algorithm to use: \"jp\" (DEFAULT) Jones and "
638 "Plassmann, an efficient coloring algorithm, \"power\" an alternative stochastic "
639 "algorithm, \"greedy\", a greedy assignment algorithm with stochastic updates to "
640 "guarantee a valid coloring, \"bt\", a back tracking algorithm that produces good "
641 "distributions but may experience exponential run time in the worst case scenario "
642 "(works well on medium to large 2D problems)";
643}
644
646 std::numeric_limits<unsigned int>::max();
647const unsigned int PolycrystalUserObjectBase::HALO_THICKNESS = 4;
const ExecFlagType EXEC_INITIAL
const ConsoleStream _console
const libMesh::ConstElemRange & getNonlinearEvaluableElementRange()
bool hasInitialAdaptivity() const
std::size_t _var_index
The Moose variable where this feature was found (often the "order parameter")
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...
std::vector< std::set< dof_id_type > > _entities_visited
This variable keeps track of which nodes have been visited during execution.
const bool _is_elemental
Determines if the flood counter is elements or not (nodes)
const bool _is_primary
Convenience variable for testing primary rank.
std::vector< MooseVariable * > _vars
The vector of coupled in variables cast to MooseVariable.
static InputParameters validParams()
virtual void prepareDataForTransfer()
This routine uses the local flooded data to build up the local feature data structures (_partial feat...
virtual void initialSetup() override
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.
virtual void initialize() override
bool flood(const DofObject *dof_object, std::size_t current_index)
This method will check if the current entity is above the supplied threshold and "mark" it.
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.
libMesh::PeriodicBoundaries * _pbs
A pointer to the periodic boundary constraints object.
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< 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.
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.
static const processor_id_type invalid_proc_id
std::unique_ptr< libMesh::PointLocatorBase > _point_locator
virtual void finalize() override
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
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 addRequiredCoupledVarWithAutoBuild(const std::string &name, const std::string &base_name, const std::string &num_name, const std::string &doc_string)
void paramInfo(const std::string &param, Args... args) const
void paramError(const std::string &param, Args... args) const
void mooseError(Args &&... args) const
MeshBase & getMesh()
virtual Elem * queryElemPtr(const dof_id_type i)
const std::array< bool, 3 > & queryPeriodicDimensions(const unsigned int sys_num, const unsigned int var_num) const
const unsigned int _op_num
The maximum number of order parameters (colors) available to assign to the grain structure.
virtual void initialize() override
virtual void getGrainsBasedOnElem(const Elem &elem, std::vector< unsigned int > &grains) const
This method may be defined in addition to the point based initialization to speed up lookups.
std::unique_ptr< DenseMatrix< Real > > _adjacency_matrix
The dense adjacency matrix.
static const unsigned int HALO_THICKNESS
Used to hold the thickness of the halo that should be constructed for detecting adjacency.
static std::string coloringAlgorithmDescriptions()
Returns corresponding descriptions of available coloring algorithms.
virtual bool areFeaturesMergeable(const FeatureData &f1, const FeatureData &f2) const override
Method for determining whether two features are mergeable.
void buildGrainAdjacencyMatrix()
Builds a dense adjacency matrix based on the discovery of grain neighbors and halos surrounding each ...
void assignOpsToGrains()
Method that runs a coloring algorithm to assign OPs to grains.
virtual unsigned int getNumGrains() const =0
Must be overridden by the deriving class to provide the number of grains in the polycrystal structure...
processor_id_type _num_chunks
The number of chunks (for merging the features together)
virtual void getGrainsBasedOnPoint(const Point &point, std::vector< unsigned int > &grains) const =0
Method for retrieving active grain IDs based on some point in the mesh.
std::map< dof_id_type, std::vector< unsigned int > > _entity_to_grain_cache
const bool _output_adjacency_matrix
A user controllable Boolean which can be used to print the adjacency matrix to the console.
std::vector< unsigned int > _grain_idx_to_op
A vector indicating which op is assigned to each grain (by index of the grain)
bool colorGraph(unsigned int vertex)
Built-in simple "back-tracking" algorithm to assign colors to a graph.
virtual void mergeSets() override
This routine is called on the primary rank only and stitches together the partial feature pieces seen...
std::map< unsigned int, unsigned int > _grain_to_op
A map of the grain_id to op.
bool isGraphValid(unsigned int vertex, unsigned int color)
Helper method for the back-tracking graph coloring algorithm.
virtual bool isNewFeatureOrConnectedRegion(const DofObject *dof_object, std::size_t &current_index, FeatureData *&feature, Status &status, unsigned int &new_id) override
Method called during the recursive flood routine that should return whether or not the current entity...
static InputParameters validParams()
virtual void prepareDataForTransfer() override
This routine uses the local flooded data to build up the local feature data structures (_partial feat...
virtual void precomputeGrainStructure()
This callback is triggered after the object is initialized and may be optionally overridden to do pre...
void printGrainAdjacencyMatrix() const
Prints out the adjacency matrix in a nicely spaced integer format.
virtual void initialSetup() override
UserObject interface overrides.
PolycrystalUserObjectBase(const InputParameters &parameters)
const MooseEnum _coloring_algorithm
The selected graph coloring algorithm used by this object.
bool _colors_assigned
A Boolean indicating whether the object has assigned colors to grains (internal use)
virtual void restoreOriginalDataStructures(std::vector< std::list< FeatureData > > &orig) override
virtual processor_id_type numberOfDistributedMergeHelpers() const override
Returns a number indicating the number of merge helpers when running in parallel based on certain imp...
static const unsigned int INVALID_COLOR
Used to indicate an invalid coloring for the built-in back-tracking algorithm.
void max(const T &r, T &o, Request &req) const
void broadcast(T &data, const unsigned int root_id=0, const bool identical_sizes=false) const
FEProblemBase & _fe_problem
const Parallel::Communicator & _communicator
processor_id_type n_processors() const
MeshBase & mesh
processor_id_type linearPartitionChunk(dof_id_type num_items, dof_id_type num_chunks, dof_id_type item_id)
ExecFlagEnum getDefaultExecFlagEnum()
void colorAdjacencyMatrix(PetscScalar *adjacency_matrix, unsigned int size, unsigned int colors, std::vector< unsigned int > &vertex_colors, const char *coloring_algorithm)
const dof_id_type n_nodes