https://mooseframework.inl.gov
CoarsenBlockGenerator.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 "CoarsenBlockGenerator.h"
11 #include "MooseMeshUtils.h"
12 #include "MeshCoarseningUtils.h"
14 
15 #include "libmesh/elem.h"
16 #include "libmesh/mesh_modification.h"
17 #include "CastUniquePointer.h"
18 
20 
23 {
25 
26  params.addClassDescription("Mesh generator which coarsens one or more blocks in an existing "
27  "mesh. The coarsening algorithm works best for regular meshes.");
28  params.addRequiredParam<MeshGeneratorName>("input", "Input mesh to coarsen");
29  params.addRequiredParam<std::vector<SubdomainName>>("block",
30  "The list of blocks to be coarsened");
31  params.addRequiredParam<std::vector<unsigned int>>(
32  "coarsening",
33  "Maximum amount of times to coarsen elements in each block. See 'block' for indexing");
34  params.addRequiredParam<Point>("starting_point",
35  "A point inside the element to start the coarsening from");
36 
37  // This is a heuristic to be able to coarsen inside blocks that are not uniformly refined
38  params.addRangeCheckedParam<Real>(
39  "maximum_volume_ratio",
40  2,
41  "maximum_volume_ratio > 0",
42  "Maximum allowed volume ratio between two fine elements to propagate "
43  "the coarsening front through a side");
44  params.addParam<bool>(
45  "verbose",
46  false,
47  "Whether to make the mesh generator output details of its actions on the console");
48  params.addParam<bool>("check_for_non_conformal_output_mesh",
49  true,
50  "Whether to check the entire mesh for non-conformal nodes indicating that "
51  "the coarsening operation has failed to produce a conformal mesh");
52  return params;
53 }
54 
56  : MeshGenerator(parameters),
57  _input(getMesh("input")),
58  _block(getParam<std::vector<SubdomainName>>("block")),
59  _coarsening(getParam<std::vector<unsigned int>>("coarsening")),
60  _starting_point(getParam<Point>("starting_point")),
61  _max_vol_ratio(getParam<Real>("maximum_volume_ratio")),
62  _verbose(getParam<bool>("verbose")),
63  _check_output_mesh_for_nonconformality(getParam<bool>("check_for_non_conformal_output_mesh"))
64 {
65  if (_block.size() != _coarsening.size())
66  paramError("coarsening", "The blocks and coarsening parameter vectors should be the same size");
67 }
68 
69 std::unique_ptr<MeshBase>
71 {
72  // Get the list of block ids from the block names
73  const auto block_ids =
74  MooseMeshUtils::getSubdomainIDs(*_input, getParam<std::vector<SubdomainName>>("block"));
75  const std::set<SubdomainID> block_ids_set(block_ids.begin(), block_ids.end());
76 
77  // Check that the block ids/names exist in the mesh
78  std::set<SubdomainID> mesh_blocks;
79  _input->subdomain_ids(mesh_blocks);
80 
81  for (std::size_t i = 0; i < block_ids.size(); ++i)
82  if (!MooseMeshUtils::hasSubdomainID(*_input, block_ids[i]))
83  paramError("block",
84  "The block '",
85  getParam<std::vector<SubdomainName>>("block")[i],
86  "' was not found within the mesh");
87 
88  // Error if it has not been implemented for this element type
89  for (const auto & elem : _input->active_subdomain_set_elements_ptr_range(block_ids_set))
90  // Only types implemented
91  if (elem->type() != QUAD4 && elem->type() != HEX8)
92  paramError("block",
93  "The input mesh contains an unsupported element type '" +
94  Moose::stringify(elem->type()) + "' for coarsening in block " +
95  std::to_string(elem->subdomain_id()));
96 
97  // Take ownership of the mesh
98  std::unique_ptr<MeshBase> mesh = std::move(_input);
99  if (!mesh->is_serial())
100  paramError("input", "Input mesh must not be distributed");
101 
102  // Find the element to start from
103  auto start_elem = (*mesh->sub_point_locator())(_starting_point);
104 
105  // Check that the starting element choice: in the block with the most coarsening requested
106  if (!start_elem)
107  paramError("starting_point", "No element was found at that point");
108  unsigned int max_c = *std::max_element(_coarsening.begin(), _coarsening.end());
109  for (const auto i : index_range(block_ids))
110  if (block_ids[i] == start_elem->subdomain_id() && _coarsening[i] != max_c)
111  mooseError("The starting element must be in the block set to be coarsened the most.\n"
112  "Starting element is in block ",
113  start_elem->subdomain_id(),
114  " set to be coarsened ",
115  _coarsening[i],
116  " times but the max coarsening required is ",
117  max_c);
118 
119  // Determine how many times the coarsening will be used
120  if (max_c > 0 && !mesh->is_prepared())
121  // we prepare for use to make sure the neighbors have been found
122  mesh->prepare_for_use();
123 
124  auto mesh_ptr = recursiveCoarsen(block_ids, mesh, _coarsening, max_c, /*step=*/0);
125 
126  // element neighbors are not valid
127  if (max_c > 0)
128  mesh_ptr->set_isnt_prepared();
129 
130  // flip elements as we were not careful to build them with a positive volume
131  MeshTools::Modification::orient_elements(*mesh_ptr);
132 
133  // check that we are not returning a non-conformal mesh
135  {
136  mesh_ptr->prepare_for_use();
137  unsigned int num_nonconformal_nodes = 0;
139  mesh_ptr, _console, 10, TOLERANCE, num_nonconformal_nodes);
140  if (num_nonconformal_nodes)
141  mooseError("Coarsened mesh has non-conformal nodes. The coarsening process likely failed to "
142  "form a uniform paving of coarsened elements. Number of non-conformal nodes: " +
143  Moose::stringify(num_nonconformal_nodes));
144  }
145  return mesh_ptr;
146 }
147 
148 std::unique_ptr<MeshBase>
149 CoarsenBlockGenerator::recursiveCoarsen(const std::vector<subdomain_id_type> & block_ids,
150  std::unique_ptr<MeshBase> & mesh,
151  const std::vector<unsigned int> & coarsening,
152  const unsigned int max,
153  unsigned int coarse_step)
154 {
155  if (coarse_step == max)
156  return dynamic_pointer_cast<MeshBase>(mesh);
157 
158  // Elements should know their neighbors
159  if (!mesh->is_prepared())
160  mesh->prepare_for_use();
161 
162  // We wont be modifying the starting mesh for simplicity, we will make a copy and return that
163  std::unique_ptr<MeshBase> mesh_return;
164  int max_num_coarsened = -1;
165 
166  const auto base_start_elem = (*mesh->sub_point_locator())(_starting_point);
167 
168  // Try every node as the 'center' point of a coarsened element
169  for (const auto & start_node_index : base_start_elem->node_index_range())
170  {
171  if (_verbose)
172  _console << "Step " << coarse_step + 1 << " coarsening attempt #" << start_node_index
173  << "\nUsing node " << *base_start_elem->node_ptr(start_node_index)
174  << " as the interior node of the coarse element." << std::endl;
175 
176  // Make a copy of the mesh in case the initial node choice was bad
177  auto mesh_copy = mesh->clone();
178 
179  // We will only have a single starting element for now. If there are non-connected components,
180  // we will need to have a starting element-node pair in every component.
181  auto start_elem = mesh_copy->elem_ptr(base_start_elem->id());
182  mooseAssert(start_elem, "Should have a real elem pointer");
183  mooseAssert(start_elem->active(), "Starting element must be active");
184 
185  auto start_node = start_elem->node_ptr(start_node_index);
186  mooseAssert(start_node, "Starting node should exist");
187 
188  // Create comparator for ordering of candidates
189  auto cmp = [](std::pair<Elem *, Node *> a, std::pair<Elem *, Node *> b)
190  {
191  // Sweep direction
192  // Potentially a user selectable parameter in the future
193  Point sorting_direction(1, 1, 1);
194  const auto sorting =
195  (a.first->vertex_average() - b.first->vertex_average()) * sorting_direction;
196  if (MooseUtils::absoluteFuzzyGreaterThan(sorting, 0))
197  return true;
198  else if (MooseUtils::absoluteFuzzyEqual(sorting, 0) &&
199  MooseUtils::absoluteFuzzyGreaterThan((*a.second - *b.second) * sorting_direction, 0))
200  return true;
201  else
202  // Sorting direction is orthogonal to the two pairs, rely on element ids
203  return a.first->id() > b.first->id();
204  };
205 
206  // This set will keep track of all the 'fine elem' + 'coarse element interior node' pairs
207  // we should attempt to form coarse element from
208  // TODO: think about the implications of set vs vector. Set might grow to the entire mesh
209  // due to sorting. Vector we could insert at the beginning and treat new candidates immediately
210  std::set<std::pair<Elem *, Node *>, decltype(cmp)> candidate_pairs(cmp);
211  candidate_pairs.insert(std::make_pair(start_elem, start_node));
212 
213  // Keep track of the coarse elements created
214  std::set<Elem *> coarse_elems;
215 
216  while (candidate_pairs.size() > 0)
217  {
218  Elem * current_elem = candidate_pairs.begin()->first;
219  Node * interior_node = candidate_pairs.begin()->second;
220  mooseAssert(current_elem, "Null candidate element pointer");
221  mooseAssert(interior_node, "Null candidate node pointer");
222  const auto current_node_index = current_elem->get_node_index(interior_node);
223  // just take any another node for now
224  const auto ref_node =
225  current_elem->node_ptr(current_node_index == 0 ? 1 : current_node_index - 1);
226  mooseAssert(ref_node, "Should have a real node pointer");
227  candidate_pairs.erase(candidate_pairs.begin());
228 
229  const auto elem_type = current_elem->type();
230 
231  // Mid-edge nodes could be an option too for making coarse elements.
232  // For a first implementation, we wont try to use them as near the edge we would need
233  // a special treatment.
234  if (!current_elem->is_vertex(current_node_index))
235  continue;
236 
237  // We dont support coarsening libMesh h-refined meshes
238  if (current_elem->level() > 0)
239  mooseError("H-refined meshes cannot be coarsened with this mesh generator. Use the "
240  "[Adaptivity] block to coarsen them.");
241 
242  // Get the nodes to build a coarse element
243  std::vector<const Node *> tentative_coarse_nodes;
244  std::set<const Elem *> fine_elements_const;
246  *interior_node, *ref_node, *current_elem, tentative_coarse_nodes, fine_elements_const);
247 
248  // For example, not enough fine elements around the node to build a coarse element
249  if (!success)
250  continue;
251 
252  bool go_to_next_candidate = false;
253  // If the fine elements are not all of the same type, we currently cannot coarsen
254  for (auto elem : fine_elements_const)
255  if (elem && elem->type() != elem_type)
256  go_to_next_candidate = true;
257 
258  // We do not coarsen across subdomains for now
259  const auto common_subdomain_id = current_elem->subdomain_id();
260  for (auto elem : fine_elements_const)
261  if (elem && elem->subdomain_id() != common_subdomain_id)
262  go_to_next_candidate = true;
263 
264  // Check the coarse element nodes gathered
265  for (const auto & check_node : tentative_coarse_nodes)
266  if (check_node == nullptr)
267  go_to_next_candidate = true;
268 
269  if (go_to_next_candidate)
270  continue;
271 
272  // We will likely delete the fine elements so we have to drop the const
273  auto cmp_elem = [](Elem * a, Elem * b) { return a->id() - b->id(); };
274  std::set<Elem *, decltype(cmp_elem)> fine_elements(cmp_elem);
275  for (const auto elem_ptr : fine_elements_const)
276  fine_elements.insert(mesh_copy->elem_ptr(elem_ptr->id()));
277 
278  // Form a parent, of a low order type as we only have the extreme vertex nodes
279  std::unique_ptr<Elem> parent = Elem::build(Elem::first_order_equivalent_type(elem_type));
280  parent->subdomain_id() = common_subdomain_id;
281  auto parent_ptr = mesh_copy->add_elem(parent.release());
282  coarse_elems.insert(parent_ptr);
283 
284  // Set the nodes to the coarse element
285  // They were sorted previously in getFineElementFromInteriorNode
286  for (auto i : index_range(tentative_coarse_nodes))
287  parent_ptr->set_node(i, mesh_copy->node_ptr(tentative_coarse_nodes[i]->id()));
288 
289  // Gather targets / next candidates for the next element coarsening
290  // Find the face neighbors, then look for the center node
291  for (const auto side_index : make_range(parent_ptr->n_sides()))
292  {
293  // Pick one of the coarse element nodes by that face
294  // it should not matter which one, they are all vertex nodes of a fine element
295  // that has a neighbor on the other side of the coarse element face
296  const auto coarse_node = parent_ptr->side_ptr(side_index)->node_ptr(0);
297  mooseAssert(coarse_node,
298  "We should have a node on coarse side " + std::to_string(side_index));
299 
300  // Find one of the fine elements next to the face, its neighbor on the other side
301  // of the coarse face is the face neighbor we want
302  Elem * fine_el = nullptr;
303  for (const auto & fine_elem : fine_elements)
304  {
305  bool found = false;
306  for (const auto & fine_elem_node : fine_elem->node_ref_range())
307  if (MooseUtils::absoluteFuzzyEqual((*coarse_node - fine_elem_node).norm_sq(), 0))
308  {
309  fine_el = fine_elem;
310  found = true;
311  break;
312  }
313  if (found)
314  break;
315  }
316  mooseAssert(fine_el, "We should have found a fine element for the next candidate");
317  const Real fine_el_volume = fine_el->volume();
318 
319  // Get the element(s) on the other side of the coarse face
320  // We can tentatively support three cases:
321  // - 1 element on the other side, coarse as well (towards less refinement).
322  // In that case, do not do anything. Two coarse elements sitting next to each other is
323  // perfect. We can detect this case by looking at the element volumes, with a heuristic
324  // on the ratio of volumes
325  // - same number of elements on the other side than the fine elements touching the face
326  // (refinement was uniform on both sides of the face, we have coarsened one side so far)
327  // - more elements on the other side than the fine elements touching the face
328  // (more refinement on that side of the face initially, we are now two levels of
329  // refinement away)
330  // TODO: That last case
331  unsigned int fine_side_index = 0;
332  const auto coarse_side_center = parent_ptr->side_ptr(side_index)->vertex_average();
333  Real min_distance = std::numeric_limits<Real>::max();
334  // There might be a better way to find this index. Smallest distance should work
335  for (const auto side_index : make_range(fine_el->n_sides()))
336  {
337  // only two sides (quad), three sides (hex) also own the coarse node
338  if (fine_el->side_ptr(side_index)->get_node_index(coarse_node) == libMesh::invalid_uint)
339  continue;
340  const auto dist =
341  (fine_el->side_ptr(side_index)->vertex_average() - coarse_side_center).norm_sq();
342  if (min_distance > dist)
343  {
344  min_distance = dist;
345  fine_side_index = side_index;
346  }
347  }
348  mooseAssert(min_distance != std::numeric_limits<Real>::max(),
349  "We should have found a side");
350 
351  // We cannot use the neighbor pointer from the fine element, or else wont be able to
352  // deal with non-conformal meshes that are disjoint at this location
353  // Instead we offset a little and use a point locator
354  Point offset_point =
355  fine_el->side_ptr(fine_side_index)->vertex_average() +
356  100 * TOLERANCE *
357  (fine_el->side_ptr(fine_side_index)->vertex_average() - fine_el->vertex_average());
358  auto pl = mesh_copy->sub_point_locator();
359  pl->enable_out_of_mesh_mode();
360  auto const_neighbor = (*pl)(offset_point);
361  pl->disable_out_of_mesh_mode();
362 
363  // We're at a boundary
364  if (!const_neighbor)
365  continue;
366 
367  // Get a non-const element since it will be a candidate for deletion
368  auto neighbor_fine_elem = mesh_copy->elem_ptr(const_neighbor->id());
369 
370  // Point locator finding a fine element inside
371  if (fine_elements.find(neighbor_fine_elem) != fine_elements.end())
372  continue;
373 
374  // Get the interior node for the next tentative coarse element
375  // We can just use the index to get it from the next tentative fine element
376  const auto neighbor_coarse_node_index = neighbor_fine_elem->get_node_index(coarse_node);
377  // Node is not shared between the coarse element and its fine neighbors.
378  // The mesh should probably be stitched before attempting coarsening
379  if (neighbor_coarse_node_index == libMesh::invalid_uint)
380  {
381  mooseInfoRepeated("Coarse element node " + Moose::stringify(*coarse_node) +
382  " does not seem shared with any element other than the coarse element. "
383  "Is the mesh will stitched? Or are there non-conformalities?");
384  continue;
385  }
386  const auto opposite_node_index = MeshCoarseningUtils::getOppositeNodeIndex(
387  neighbor_fine_elem->type(), neighbor_coarse_node_index);
388  auto neighbor_interior_node = neighbor_fine_elem->node_ptr(opposite_node_index);
389 
390  // avoid attempting to coarsen again an element we've already coarsened
391  if (coarse_elems.find(neighbor_fine_elem) == coarse_elems.end())
392  {
393  // dont add a candidate if it's too early to coarsen it (will be coarsened on next step)
394  // dont add a candidate if they are close to the size of a coarsened element already
395  for (const auto i : index_range(block_ids))
396  if (block_ids[i] == neighbor_fine_elem->subdomain_id() &&
397  coarsening[i] > max - coarse_step - 1 &&
398  std::abs(neighbor_fine_elem->volume()) < std::abs(_max_vol_ratio * fine_el_volume))
399  {
400  candidate_pairs.insert(std::make_pair(neighbor_fine_elem, neighbor_interior_node));
401  break;
402  }
403  }
404  }
405 
406  // Delete the elements used to build the coarse element
407  for (auto & fine_elem : fine_elements)
408  {
409  if (!fine_elem)
410  continue;
411 
412  // We dont delete nodes in the fine elements as they get removed during renumbering/
413  // remove_orphaned_nodes calls in preparing for use
414  mesh_copy->delete_elem(fine_elem);
415 
416  // Clean up the list of candidates from any deleted elements
417  for (auto iter = candidate_pairs.begin(); iter != candidate_pairs.end();)
418  {
419  if (iter->first == fine_elem)
420  {
421  iter = candidate_pairs.erase(iter);
422  continue;
423  }
424  ++iter;
425  }
426  }
427 
428  // Contract to remove the elements that were marked for deletion
429  mesh_copy->contract();
430  // Prepare for use to refresh the element neighbors
431  mesh_copy->prepare_for_use();
432  }
433 
434  // We pick the configuration (eg starting node) for which we managed to coarsen the most
435  // This isn't the best idea, as some coarsening could be invalid (non-conformalities)
436  // Maybe we should examine for non-conformality here to make a decision?
437  // It's expensive to do so in a global mesh-wide check though, maybe if we baked that check
438  // into the coarsening work it would be more reasonable.
439  if (_verbose)
440  _console << "Step " << coarse_step + 1 << " attempt #" << start_node_index << " created "
441  << coarse_elems.size() << " coarse elements." << std::endl;
442  if (int(coarse_elems.size()) > max_num_coarsened)
443  {
444  mesh_return = std::move(mesh_copy);
445  max_num_coarsened = coarse_elems.size();
446  }
447  }
448  if (_verbose)
449  _console << "Step " << coarse_step + 1 << " created " << max_num_coarsened
450  << " coarsened elements in its most successful attempt." << std::endl;
451  coarse_step++;
452  return recursiveCoarsen(block_ids, mesh_return, coarsening, max, coarse_step);
453 }
MetaPhysicL::DualNumber< V, D, asd > abs(const MetaPhysicL::DualNumber< V, D, asd > &a)
Definition: EigenADReal.h:42
const bool _verbose
Whether the mesh generator should be verbose to the console.
const bool _check_output_mesh_for_nonconformality
Whether to check the output mesh for non-conformality.
bool absoluteFuzzyEqual(const T &var1, const T2 &var2, const T3 &tol=libMesh::TOLERANCE *libMesh::TOLERANCE)
Function to check whether two variables are equal within an absolute tolerance.
Definition: MooseUtils.h:380
const unsigned int invalid_uint
HEX8
const std::vector< unsigned int > _coarsening
The amount of times to coarsen each block, corresponding to their index in &#39;block&#39;.
CoarsenBlockGenerator(const InputParameters &parameters)
MeshBase & mesh
void mooseInfoRepeated(Args &&... args)
Emit an informational message with the given stringified, concatenated args.
Definition: MooseError.h:377
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system...
std::unique_ptr< T_DEST, T_DELETER > dynamic_pointer_cast(std::unique_ptr< T_SRC, T_DELETER > &src)
These are reworked from https://stackoverflow.com/a/11003103.
std::vector< subdomain_id_type > getSubdomainIDs(const libMesh::MeshBase &mesh, const std::vector< SubdomainName > &subdomain_name)
Get the associated subdomainIDs for the subdomain names that are passed in.
bool getFineElementsFromInteriorNode(const libMesh::Node &interior_node, const libMesh::Node &reference_node, const libMesh::Elem &elem, std::vector< const libMesh::Node *> &tentative_coarse_nodes, std::set< const libMesh::Elem *> &fine_elements)
Utility routine to gather vertex nodes for, and elements contained in, for a coarse QUAD or HEX eleme...
virtual std::unique_ptr< MeshBase > recursiveCoarsen(const std::vector< subdomain_id_type > &block_ids, std::unique_ptr< MeshBase > &mesh, const std::vector< unsigned int > &coarsening, const unsigned int max, unsigned int coarse_step)
The actual function coarsening the blocks.
void addRequiredParam(const std::string &name, const std::string &doc_string)
This method adds a parameter and documentation string to the InputParameters object that will be extr...
auto max(const L &left, const R &right)
const Point _starting_point
The location on the mesh to start the coarsening from.
QUAD4
bool hasSubdomainID(const MeshBase &input_mesh, const SubdomainID &id)
Whether a particular subdomain ID exists in the mesh.
MeshGenerator for coarsening one or more blocks.
const T & getParam(const std::string &name) const
Retrieve a parameter for the object.
void paramError(const std::string &param, Args... args) const
Emits an error prefixed with the file and line number of the given param (from the input file) along ...
static InputParameters validParams()
Definition: MeshGenerator.C:23
std::string stringify(const T &t)
conversion to string
Definition: Conversion.h:64
const Real _max_vol_ratio
Maximum volume ratio between a neighbor and an element to consider the neighbor as a candidate for co...
registerMooseObject("MooseApp", CoarsenBlockGenerator)
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
auto norm_sq(const T &a) -> decltype(std::norm(a))
std::unique_ptr< MeshBase > & _input
Input mesh to coarsen.
IntRange< T > make_range(T beg, T end)
void mooseError(Args &&... args) const
Emits an error prefixed with object name and type.
virtual std::unique_ptr< MeshBase > generate() override
Generate / modify the mesh.
void addClassDescription(const std::string &doc_string)
This method adds a description of the class that will be displayed in the input file syntax dump...
void addParam(const std::string &name, const S &value, const std::string &doc_string)
These methods add an optional parameter and a documentation string to the InputParameters object...
void addRangeCheckedParam(const std::string &name, const T &value, const std::string &parsed_function, const std::string &doc_string)
const ConsoleStream _console
An instance of helper class to write streams to the Console objects.
void checkNonConformalMesh(const std::unique_ptr< libMesh::MeshBase > &mesh, const ConsoleStream &console, const unsigned int num_outputs, const Real conformality_tol, unsigned int &num_nonconformal_nodes)
MeshGenerators are objects that can modify or add to an existing mesh.
Definition: MeshGenerator.h:32
void ErrorVector unsigned int
auto index_range(const T &sizable)
unsigned int getOppositeNodeIndex(libMesh::ElemType elem_type, unsigned int node_index)
Utility routine to get the index of the node opposite, in the element, to the node of interest...
bool absoluteFuzzyGreaterThan(const T &var1, const T2 &var2, const T3 &tol=libMesh::TOLERANCE *libMesh::TOLERANCE)
Function to check whether a variable is greater than another variable within an absolute tolerance...
Definition: MooseUtils.h:428
static InputParameters validParams()
const std::vector< SubdomainName > _block
List of block(s) to coarsen.