LCOV - code coverage report
Current view: top level - src/userobjects - PolycrystalVoronoi.C (source / functions) Hit Total Coverage
Test: idaholab/moose phase_field: #32971 (54bef8) with base c6cf66 Lines: 164 170 96.5 %
Date: 2026-05-29 20:38:39 Functions: 10 10 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       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 "PolycrystalVoronoi.h"
      11             : #include "IndirectSort.h"
      12             : #include "MooseRandom.h"
      13             : #include "MooseMesh.h"
      14             : #include "MooseVariable.h"
      15             : #include "NonlinearSystemBase.h"
      16             : #include "DelimitedFileReader.h"
      17             : 
      18             : registerMooseObject("PhaseFieldApp", PolycrystalVoronoi);
      19             : 
      20             : InputParameters
      21         880 : PolycrystalVoronoi::validParams()
      22             : {
      23         880 :   InputParameters params = PolycrystalUserObjectBase::validParams();
      24         880 :   params.addClassDescription(
      25             :       "Random Voronoi tessellation polycrystal (used by PolycrystalVoronoiAction)");
      26        1760 :   params.addParam<unsigned int>(
      27        1760 :       "grain_num", 0, "Number of grains being represented by the order parameters");
      28        1760 :   params.addParam<unsigned int>("rand_seed", 0, "The random seed");
      29        1760 :   params.addParam<bool>(
      30        1760 :       "columnar_3D", false, "3D microstructure will be columnar in the z-direction?");
      31        1760 :   params.addParam<bool>(
      32        1760 :       "use_kdtree", false, "Whether or not to use a KD tree to speedup grain search");
      33        2640 :   params.addRangeCheckedParam<unsigned int>("point_patch_size",
      34        1760 :                                             1,
      35             :                                             "point_patch_size > 0",
      36             :                                             "How many nearest points KDTree should return");
      37        2640 :   params.addRangeCheckedParam<unsigned int>("grain_patch_size",
      38        1760 :                                             10,
      39             :                                             "grain_patch_size > 0",
      40             :                                             "How many nearest grains KDTree should return");
      41        1760 :   params.addParam<FileName>(
      42             :       "file_name",
      43             :       "",
      44             :       "File containing grain centroids, if file_name is provided, the centroids "
      45             :       "from the file will be used.");
      46        1760 :   params.addParam<Real>("int_width", 0.0, "Width of diffuse interfaces");
      47         880 :   return params;
      48           0 : }
      49             : 
      50         440 : PolycrystalVoronoi::PolycrystalVoronoi(const InputParameters & parameters)
      51             :   : PolycrystalUserObjectBase(parameters),
      52         440 :     _grain_num(getParam<unsigned int>("grain_num")),
      53         880 :     _columnar_3D(getParam<bool>("columnar_3D")),
      54         880 :     _rand_seed(getParam<unsigned int>("rand_seed")),
      55         880 :     _int_width(getParam<Real>("int_width")),
      56         880 :     _file_name(getParam<FileName>("file_name")),
      57             :     _kd_tree(nullptr),
      58         880 :     _use_kdtree(getParam<bool>("use_kdtree")),
      59         880 :     _point_patch_size(getParam<unsigned int>("point_patch_size")),
      60        1320 :     _grain_patch_size(getParam<unsigned int>("grain_patch_size"))
      61             : {
      62         440 :   if (_file_name == "" && _grain_num == 0)
      63           0 :     mooseError("grain_num must be provided if the grain centroids are not read from a file");
      64             : 
      65         440 :   if (_file_name != "" && _grain_num > 0)
      66           0 :     mooseWarning("grain_num is ignored and will be determined from the file.");
      67         440 : }
      68             : 
      69             : void
      70     9431780 : PolycrystalVoronoi::getGrainsBasedOnPoint(const Point & point,
      71             :                                           std::vector<unsigned int> & grains) const
      72             : {
      73     9431780 :   grains.resize(0);
      74     9431780 :   Real d_min = _range.norm();
      75             :   Real distance;
      76             :   auto n_grains = _centerpoints.size();
      77             :   auto min_index = n_grains;
      78             : 
      79     9431780 :   if (_use_kdtree)
      80             :   {
      81             :     mooseAssert(_kd_tree, "KD tree is not constructed yet");
      82             :     mooseAssert(_grain_gtl_ids.size() == _new_points.size(),
      83             :                 "The number of grain global IDs does not match that of new center points");
      84             : 
      85      743444 :     std::vector<std::size_t> return_index(_point_patch_size);
      86      743444 :     std::vector<Real> return_dist_sqr(_point_patch_size);
      87             : 
      88      743444 :     _kd_tree->neighborSearch(point, _point_patch_size, return_index, return_dist_sqr);
      89             : 
      90      743444 :     min_index = _grain_gtl_ids[return_index[0]];
      91             : 
      92      743444 :     d_min = return_dist_sqr[0];
      93             : 
      94             :     // By default, _point_patch_size is one. A larger _point_patch_size
      95             :     // may be useful if nearest nodes are not unique, and
      96             :     // we want to select the node that has the smallest ID
      97     1152002 :     for (unsigned int i = 1; i < _point_patch_size; i++)
      98             :     {
      99      408558 :       if (d_min > return_dist_sqr[i])
     100             :       {
     101           0 :         min_index = _grain_gtl_ids[return_index[i]];
     102             :         d_min = return_dist_sqr[i];
     103             :       }
     104      408558 :       else if (d_min == return_dist_sqr[i])
     105             :       {
     106        8000 :         min_index = min_index > _grain_gtl_ids[return_index[i]] ? _grain_gtl_ids[return_index[i]]
     107             :                                                                 : min_index;
     108             :       }
     109             :     }
     110      743444 :   }
     111             :   else
     112             :   {
     113             :     // Find the closest centerpoint to the current point
     114   105737349 :     for (MooseIndex(_centerpoints) grain = 0; grain < n_grains; ++grain)
     115             :     {
     116    97049013 :       distance = _mesh.minPeriodicDistance(*_vars[0], _centerpoints[grain], point);
     117    97049013 :       if (distance < d_min)
     118             :       {
     119             :         d_min = distance;
     120             :         min_index = grain;
     121             :       }
     122             :     }
     123             :   }
     124             : 
     125             :   mooseAssert(min_index < n_grains, "Couldn't find closest Voronoi cell");
     126     9431780 :   Point current_grain = _centerpoints[min_index];
     127     9431780 :   grains.push_back(min_index); // closest centerpoint always gets included
     128             : 
     129     9431780 :   if (_int_width > 0.0)
     130             :   {
     131     2034794 :     if (_use_kdtree)
     132             :     {
     133             :       mooseAssert(_grain_patch_size < _grain_gtl_ids.size(),
     134             :                   "Number of neighboring grains should not exceed number of global grains");
     135             : 
     136      607258 :       std::vector<std::size_t> return_index(_grain_patch_size);
     137      607258 :       _kd_tree->neighborSearch(current_grain, _grain_patch_size, return_index);
     138             : 
     139             :       std::set<dof_id_type> neighbor_grains;
     140     6679838 :       for (unsigned int i = 0; i < _grain_patch_size; i++)
     141     6072580 :         if (_grain_gtl_ids[return_index[i]] != min_index)
     142     5465322 :           neighbor_grains.insert(_grain_gtl_ids[return_index[i]]);
     143             : 
     144     3036290 :       for (auto it = neighbor_grains.begin(); it != neighbor_grains.end(); ++it)
     145     2429032 :         if ((*it) != min_index)
     146             :         {
     147     2429032 :           Point next_grain = _centerpoints[*it];
     148     2429032 :           Point N = findNormalVector(point, current_grain, next_grain);
     149     2429032 :           Point cntr = findCenterPoint(point, current_grain, next_grain);
     150             :           distance = N * (cntr - point);
     151     2429032 :           if (distance < _int_width)
     152      250890 :             grains.push_back(*it); // also include all grains with nearby boundaries
     153             :         }
     154      607258 :     }
     155             :     else
     156             :     {
     157     8309350 :       for (MooseIndex(_centerpoints) grain = 0; grain < n_grains; ++grain)
     158     6881814 :         if (grain != min_index)
     159             :         {
     160     5454278 :           Point next_grain = _centerpoints[grain];
     161     5454278 :           Point N = findNormalVector(point, current_grain, next_grain);
     162     5454278 :           Point cntr = findCenterPoint(point, current_grain, next_grain);
     163             :           distance = N * (cntr - point);
     164     5454278 :           if (distance < _int_width)
     165      592200 :             grains.push_back(grain); // also include all grains with nearby boundaries
     166             :         }
     167             :     }
     168             :   }
     169     9431780 : }
     170             : 
     171             : Real
     172     9090673 : PolycrystalVoronoi::getVariableValue(unsigned int op_index, const Point & p) const
     173             : {
     174             :   std::vector<unsigned int> grain_ids;
     175     9090673 :   getGrainsBasedOnPoint(p, grain_ids);
     176             : 
     177             :   // Now see if any of those grains are represented by the passed in order parameter
     178     9090673 :   unsigned int active_grain_on_op = invalid_id;
     179    16903930 :   for (auto grain_id : grain_ids)
     180     9693953 :     if (op_index == _grain_to_op.at(grain_id))
     181             :     {
     182     1880696 :       active_grain_on_op = grain_id;
     183     1880696 :       break;
     184             :     }
     185             : 
     186             :   Real profile_val = 0.0;
     187     9090673 :   if (active_grain_on_op != invalid_id)
     188     1880696 :     profile_val = computeDiffuseInterface(p, active_grain_on_op, grain_ids);
     189             : 
     190     9090673 :   return profile_val;
     191     9090673 : }
     192             : 
     193             : void
     194         292 : PolycrystalVoronoi::precomputeGrainStructure()
     195             : {
     196             :   // Set up domain bounds with mesh tools
     197        1168 :   for (const auto i : make_range(Moose::dim))
     198             :   {
     199         876 :     _bottom_left(i) = _mesh.getMinInDimension(i);
     200         876 :     _top_right(i) = _mesh.getMaxInDimension(i);
     201             :   }
     202         292 :   _range = _top_right - _bottom_left;
     203             : 
     204         292 :   if (!_file_name.empty())
     205             :   {
     206          10 :     MooseUtils::DelimitedFileReader txt_reader(_file_name, &_communicator);
     207             : 
     208          10 :     txt_reader.read();
     209          10 :     std::vector<std::string> col_names = txt_reader.getNames();
     210          10 :     std::vector<std::vector<Real>> data = txt_reader.getData();
     211          10 :     _grain_num = data[0].size();
     212          10 :     _centerpoints.resize(_grain_num);
     213             : 
     214          30 :     for (unsigned int i = 0; i < col_names.size(); ++i)
     215             :     {
     216             :       // Check vector lengths
     217          20 :       if (data[i].size() != _grain_num)
     218           0 :         paramError("Columns in ", _file_name, " do not have uniform lengths.");
     219             :     }
     220             : 
     221         280 :     for (unsigned int grain = 0; grain < _grain_num; ++grain)
     222             :     {
     223         270 :       _centerpoints[grain](0) = data[0][grain];
     224         270 :       _centerpoints[grain](1) = data[1][grain];
     225         270 :       if (col_names.size() > 2)
     226           0 :         _centerpoints[grain](2) = data[2][grain];
     227             :       else
     228         270 :         _centerpoints[grain](2) = 0.0;
     229             :     }
     230          10 :   }
     231             :   else
     232             :   {
     233         282 :     MooseRandom::seed(_rand_seed);
     234             : 
     235             :     // Randomly generate the centers of the individual grains represented by the Voronoi
     236             :     // tessellation
     237         282 :     _centerpoints.resize(_grain_num);
     238         282 :     std::vector<Real> distances(_grain_num);
     239             : 
     240        2944 :     for (auto grain = decltype(_grain_num)(0); grain < _grain_num; grain++)
     241             :     {
     242       10648 :       for (const auto i : make_range(Moose::dim))
     243        7986 :         _centerpoints[grain](i) = _bottom_left(i) + _range(i) * MooseRandom::rand();
     244        2662 :       if (_columnar_3D)
     245          24 :         _centerpoints[grain](2) = _bottom_left(2) + _range(2) * 0.5;
     246             :     }
     247         282 :   }
     248             : 
     249             :   // Build a KDTree that is used to speedup point search
     250         292 :   buildSearchTree();
     251         292 : }
     252             : 
     253             : void
     254         315 : PolycrystalVoronoi::buildSearchTree()
     255             : {
     256         315 :   if (!_use_kdtree)
     257         304 :     return;
     258             : 
     259             :   auto midplane = _bottom_left + _range / 2.0;
     260          11 :   dof_id_type local_grain_id = 0;
     261          11 :   _grain_gtl_ids.clear();
     262          11 :   _grain_gtl_ids.reserve(_centerpoints.size() * std::pow(2.0, _mesh.dimension()));
     263          11 :   _new_points.clear();
     264             :   // Use more memory. Factor is 2^dim
     265          11 :   _new_points.reserve(_centerpoints.size() * std::pow(2.0, _mesh.dimension()));
     266             :   // Domain will be extended along the periodic directions
     267             :   // For each direction, a half domain is constructed
     268          11 :   std::vector<std::vector<Real>> xyzs(LIBMESH_DIM);
     269          11 :   const auto & periodic_dims = _mesh.queryPeriodicDimensions(*_vars[0]);
     270          60 :   for (auto & point : _centerpoints)
     271             :   {
     272             :     // Cear up
     273         196 :     for (unsigned int i = 0; i < LIBMESH_DIM; i++)
     274         147 :       xyzs[i].clear();
     275             : 
     276             :     // Have at least the original point
     277         196 :     for (unsigned int i = 0; i < LIBMESH_DIM; i++)
     278         147 :       xyzs[i].push_back(point(i));
     279             : 
     280             :     // Add new coords when there exists periodic boundary conditions
     281             :     // We extend half domain
     282         147 :     for (unsigned int i = 0; i < _mesh.dimension(); i++)
     283          98 :       if (periodic_dims[i])
     284          98 :         xyzs[i].push_back(point(i) <= midplane(i) ? point(i) + _range(i) : point(i) - _range(i));
     285             : 
     286             :     // Construct all combinations
     287         147 :     for (auto x : xyzs[0])
     288         294 :       for (auto y : xyzs[1])
     289         392 :         for (auto z : xyzs[2])
     290             :         {
     291         196 :           _new_points.push_back(Point(x, y, z));
     292         196 :           _grain_gtl_ids.push_back(local_grain_id);
     293             :         }
     294             : 
     295          49 :     local_grain_id++;
     296             :   }
     297             : 
     298             :   // Build a KDTree that is used to speedup point search
     299             :   // We should not destroy _new_points after the tree is contributed
     300             :   // KDTree use a reference to "_new_points"
     301          22 :   _kd_tree = std::make_unique<KDTree>(_new_points, _mesh.getMaxLeafSize());
     302          11 : }
     303             : 
     304             : Real
     305     1880696 : PolycrystalVoronoi::computeDiffuseInterface(const Point & point,
     306             :                                             const unsigned int & gr_index,
     307             :                                             const std::vector<unsigned int> & grain_ids) const
     308             : {
     309             :   Real val = 1.0;
     310     1880696 :   Point current_grain = _centerpoints[gr_index];
     311     4166266 :   for (auto i : grain_ids)
     312     2285570 :     if (i != gr_index)
     313             :     {
     314      404874 :       Point next_grain = _centerpoints[i];
     315      404874 :       Point N = findNormalVector(point, current_grain, next_grain);
     316      404874 :       Point cntr = findCenterPoint(point, current_grain, next_grain);
     317      404874 :       for (unsigned int vcomp = 0; vcomp < 3; ++vcomp)
     318      404874 :         if (N(vcomp) != 0.0)
     319             :         {
     320      404874 :           Real L = findLinePoint(point, N, cntr, vcomp);
     321      404874 :           val *= 0.5 * (1.0 - std::tanh(2.0 * (point(vcomp) - L) * N(vcomp) / _int_width));
     322      404874 :           break;
     323             :         }
     324             :     }
     325     1880696 :   return val;
     326             : }
     327             : 
     328             : Point
     329     8288184 : PolycrystalVoronoi::findNormalVector(const Point & point, const Point & p1, const Point & p2) const
     330             : {
     331     8288184 :   Point pa = point + _mesh.minPeriodicVector(*_vars[0], point, p1);
     332     8288184 :   Point pb = point + _mesh.minPeriodicVector(*_vars[0], point, p2);
     333             :   Point N = pb - pa;
     334     8288184 :   return N / N.norm();
     335             : }
     336             : 
     337             : Point
     338     8288184 : PolycrystalVoronoi::findCenterPoint(const Point & point, const Point & p1, const Point & p2) const
     339             : {
     340     8288184 :   Point pa = point + _mesh.minPeriodicVector(*_vars[0], point, p1);
     341     8288184 :   Point pb = point + _mesh.minPeriodicVector(*_vars[0], point, p2);
     342     8288184 :   return 0.5 * (pa + pb);
     343             : }
     344             : 
     345             : Real
     346      404874 : PolycrystalVoronoi::findLinePoint(const Point & point,
     347             :                                   const Point & N,
     348             :                                   const Point & cntr,
     349             :                                   const unsigned int vcomp) const
     350             : {
     351      404874 :   const Real l_sum = N((vcomp + 1) % 3) * (point((vcomp + 1) % 3) - cntr((vcomp + 1) % 3)) +
     352      404874 :                      N((vcomp + 2) % 3) * (point((vcomp + 2) % 3) - cntr((vcomp + 2) % 3));
     353             : 
     354      404874 :   return cntr(vcomp) - l_sum / N(vcomp);
     355             : }

Generated by: LCOV version 1.14