https://mooseframework.inl.gov
MeshCoarseningUtils.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 "MeshCoarseningUtils.h"
11 #include "Conversion.h"
12 #include "MooseError.h"
13 
14 #include "libmesh/enum_elem_type.h"
15 #include "libmesh/remote_elem.h"
16 
17 using namespace libMesh;
18 
19 namespace MeshCoarseningUtils
20 {
21 bool
23  const libMesh::Node & reference_node,
24  const libMesh::Elem & fine_elem,
25  std::vector<const libMesh::Node *> & tentative_coarse_nodes,
26  std::set<const libMesh::Elem *> & fine_elements)
27 {
28  const auto elem_type = fine_elem.type();
29 
30  // Add point neighbors of interior node to list of potentially refined elements
31  // NOTE: we could potentially replace this with a simple call to point_neighbors
32  // on a fine element with the interior node. It's not clear which approach is more
33  // resilient to meshes with slits from discarded adaptivity information
34  fine_elements.insert(&fine_elem);
35  for (const auto neigh : fine_elem.neighbor_ptr_range())
36  {
37  if (!neigh || neigh == libMesh::remote_elem)
38  continue;
39  const auto node_index = neigh->get_node_index(&interior_node);
40  if (node_index != libMesh::invalid_uint && neigh->is_vertex(node_index))
41  {
42  // Get the neighbor's neighbors, to catch point non-side neighbors
43  // This is needed in 2D to get all quad neighbors
44  fine_elements.insert(neigh);
45  for (const auto neigh_two : neigh->neighbor_ptr_range())
46  {
47  if (!neigh_two || neigh_two == libMesh::remote_elem)
48  continue;
49  const auto node_index_2 = neigh_two->get_node_index(&interior_node);
50  if (node_index_2 != libMesh::invalid_uint && neigh_two->is_vertex(node_index_2))
51  {
52  // Get the neighbor's neighbors
53  fine_elements.insert(neigh_two);
54 
55  // Get the neighbor's neighbors' neighbors, to catch point non-side neighbors
56  // This is needed for 3D to get all hex neighbors
57  for (const auto neigh_three : neigh_two->neighbor_ptr_range())
58  {
59  if (!neigh_three || neigh_three == libMesh::remote_elem)
60  continue;
61  const auto node_index_3 = neigh_three->get_node_index(&interior_node);
62  if (node_index_3 != libMesh::invalid_uint && neigh_three->is_vertex(node_index_3))
63  fine_elements.insert(neigh_three);
64  }
65  }
66  }
67  }
68  }
69 
70  // If the fine elements are not all of the same type, we do not know how to get the opposite node
71  // of the interior node in the fine elements
72  for (auto elem : fine_elements)
73  if (elem && fine_elem.type() != elem_type)
74  return false;
75 
76  if (elem_type == libMesh::QUAD4 || elem_type == libMesh::QUAD8 || elem_type == libMesh::QUAD9)
77  {
78  // We need 4 elements around the interior node
79  if (fine_elements.size() != 4)
80  return false;
81 
82  // We need to order the fine elements so when we get the coarse element nodes they form
83  // a non-twisted element
84  tentative_coarse_nodes.resize(4);
85 
86  // The exterior nodes are the opposite nodes of the interior_node!
87  unsigned int neighbor_i = 0;
88  for (auto neighbor : fine_elements)
89  {
90  const auto interior_node_number = neighbor->get_node_index(&interior_node);
91  unsigned int opposite_node_index = (interior_node_number + 2) % 4;
92 
93  tentative_coarse_nodes[neighbor_i++] = neighbor->node_ptr(opposite_node_index);
94  }
95 
96  // Re-order nodes so that they will form a decent quad
97  libMesh::Point axis =
98  (fine_elem.vertex_average() - interior_node).cross(interior_node - reference_node);
99  reorderNodes(tentative_coarse_nodes, interior_node, reference_node, axis);
100  return true;
101  }
102  // For hexes, similar strategy but we need to pick 4 nodes to form a side, then 4 other nodes
103  // facing those initial nodes
104  else if (elem_type == libMesh::HEX8)
105  {
106  // We need 8 elements around the interior node
107  if (fine_elements.size() != 8)
108  return false;
109 
110  tentative_coarse_nodes.resize(4);
111 
112  // Pick a node (mid-face for the coarse element) to form the base
113  // We must use the same element reproducibly, despite the pointers being ordered in the set
114  // We use the element id to choose the same element consistently
115  const Elem * one_fine_elem = nullptr;
116  unsigned int max_id = 0;
117  for (const auto elem_ptr : fine_elements)
118  if (elem_ptr->id() > max_id)
119  {
120  max_id = elem_ptr->id();
121  one_fine_elem = elem_ptr;
122  }
123  const auto interior_node_index = one_fine_elem->get_node_index(&interior_node);
124 
125  // Find any side which contains the interior node
126  unsigned int an_interior_node_side = 0;
127  for (const auto s : make_range(one_fine_elem->n_sides()))
128  if (one_fine_elem->is_node_on_side(interior_node_index, s))
129  {
130  an_interior_node_side = s;
131  break;
132  }
133  // A node near a face of the coarse element seek is on the same side, but opposite from the
134  // interior node
135  const auto center_face_node_index =
136  one_fine_elem->opposite_node(interior_node_index, an_interior_node_side);
137  const auto center_face_node = one_fine_elem->node_ptr(center_face_node_index);
138 
139  // We gather the coarse element nodes from the fine elements that share the center face node we
140  // just selected
141  unsigned int neighbor_i = 0;
142  std::vector<const libMesh::Elem *> other_fine_elems;
143  for (auto neighbor : fine_elements)
144  {
145  if (neighbor->get_node_index(center_face_node) == libMesh::invalid_uint)
146  {
147  other_fine_elems.push_back(neighbor);
148  continue;
149  }
150  // The coarse element node is the opposite nodes of the interior_node in a fine element
151  const auto interior_node_number = neighbor->get_node_index(&interior_node);
152  unsigned int opposite_node_index =
153  getOppositeNodeIndex(neighbor->type(), interior_node_number);
154 
155  tentative_coarse_nodes[neighbor_i++] = neighbor->node_ptr(opposite_node_index);
156  }
157 
158  // Center face node was not shared with 4 elements
159  // We could try again on any of the 5 other coarse element faces but we don't insist for now
160  if (neighbor_i != 4 || other_fine_elems.size() != 4)
161  return false;
162 
163  // Sort the coarse nodes so we are reproducibly picking the same ordering of nodes
164  auto cmp_node = [](const Node * a, const Node * b) { return a->id() < b->id(); };
165  std::sort(tentative_coarse_nodes.begin(), tentative_coarse_nodes.end(), cmp_node);
166 
167  // Re-order nodes so that they will form a decent quad
168  // Pick the reference node for the rotation frame as the face center
169  const libMesh::Point clock_start = *tentative_coarse_nodes[0];
170  libMesh::Point axis = interior_node - *center_face_node;
171  reorderNodes(tentative_coarse_nodes, *center_face_node, clock_start, axis);
172 
173  // Look through the 4 other fine elements to finish the coarse hex element nodes
174  for (const auto coarse_node_index : make_range(4))
175  {
176  // Find the fine element containing each coarse node already found & ordered
177  const Elem * fine_elem = nullptr;
178  for (auto elem : fine_elements)
179  if (elem->get_node_index(tentative_coarse_nodes[coarse_node_index]) !=
181  {
182  fine_elem = elem;
183  break;
184  }
185  mooseAssert(fine_elem, "Search for fine element should have worked");
186 
187  // Find the other fine element opposite the element containing the node (they share a side)
188  const Elem * fine_neighbor = nullptr;
189  for (auto neighbor : other_fine_elems)
190  // Side neighbor. This requires the mesh to have correct neighbors
191  // For meshes with lost AMR information, this wont work
192  if (neighbor->which_neighbor_am_i(fine_elem) != libMesh::invalid_uint)
193  {
194  if (fine_neighbor)
195  mooseError("Found two neighbors");
196  fine_neighbor = neighbor;
197  }
198  // the fine element in the base of the coarse hex is not a neighbor to any element
199  // in the top part. The mesh is probably slit in the middle of the potential coarse hex
200  // element. We wont support this for now.
201  if (!fine_neighbor)
202  return false;
203 
204  // Get the coarse node, opposite the interior node in that fine element
205  const auto interior_node_index_neighbor = fine_neighbor->get_node_index(&interior_node);
206  tentative_coarse_nodes.push_back(fine_neighbor->node_ptr(
207  getOppositeNodeIndex(fine_neighbor->type(), interior_node_index_neighbor)));
208  }
209 
210  // Found 8 fine elements and 8 coarse element nodes as expected
211  if (tentative_coarse_nodes.size() == 8)
212  return true;
213  else
214  return false;
215  }
216  else
217  mooseError("Not implemented for element type " + Moose::stringify(elem_type));
218 }
219 
220 void
221 reorderNodes(std::vector<const libMesh::Node *> & nodes,
222  const libMesh::Point & origin,
223  const libMesh::Point & clock_start,
224  libMesh::Point & axis)
225 {
226  mooseAssert(axis.norm() != 0, "Invalid rotation axis when ordering nodes");
227  mooseAssert(origin != clock_start, "Invalid starting direction when ordering nodes");
228 
229  // We'll need to order the coarse nodes based on the clock-wise order of the elements
230  // Define a frame in which to compute the angles of the fine elements centers
231  // angle 0 is the [interior node, non-conformal node] vertex
232  auto start_clock = origin - clock_start;
233  start_clock /= start_clock.norm();
234  axis /= axis.norm();
235 
236  std::vector<std::pair<unsigned int, libMesh::Real>> nodes_angles(nodes.size());
237  for (const auto angle_i : index_range(nodes))
238  {
239  mooseAssert(nodes[angle_i], "Nodes cant be nullptr");
240  auto vec = *nodes[angle_i] - origin;
241  vec /= vec.norm();
242  const auto angle = atan2(vec.cross(start_clock) * axis, vec * start_clock);
243  nodes_angles[angle_i] = std::make_pair(angle_i, angle);
244  }
245 
246  // sort by angle, so it goes around the interior node
247  std::sort(nodes_angles.begin(),
248  nodes_angles.end(),
249  [](auto & left, auto & right) { return left.second < right.second; });
250 
251  // Re-sort the nodes based on their angle
252  std::vector<const libMesh::Node *> new_nodes(nodes.size());
253  for (const auto & old_index : index_range(nodes))
254  new_nodes[old_index] = nodes[nodes_angles[old_index].first];
255  for (const auto & index : index_range(nodes))
256  nodes[index] = new_nodes[index];
257 }
258 
259 unsigned int
260 getOppositeNodeIndex(libMesh::ElemType elem_type, unsigned int node_index)
261 {
262  switch (elem_type)
263  {
264  case QUAD4:
265  return (node_index + 2) % 4;
266  case HEX8:
267  {
268  mooseAssert(node_index < 8, "Node index too high: " + std::to_string(node_index));
269  return std::vector<unsigned int>({6, 7, 4, 5, 2, 3, 0, 1})[node_index];
270  }
271  default:
272  mooseError("Unsupported element type for retrieving the opposite node");
273  }
274 }
275 }
auto norm() const -> decltype(std::norm(Real()))
const unsigned int invalid_uint
unsigned int get_node_index(const Node *node_ptr) const
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application...
Definition: MooseError.h:302
virtual bool is_node_on_side(const unsigned int n, const unsigned int s) const=0
The following methods are specializations for using the libMesh::Parallel::packed_range_* routines fo...
dof_id_type id() const
virtual unsigned int opposite_node(const unsigned int n, const unsigned int s) const
std::string stringify(const T &t)
conversion to string
Definition: Conversion.h:64
virtual unsigned int n_sides() const=0
const Node * node_ptr(const unsigned int i) const
IntRange< T > make_range(T beg, T end)
SimpleRange< NeighborPtrIter > neighbor_ptr_range()
void reorderNodes(std::vector< const libMesh::Node *> &nodes, const libMesh::Point &origin, const libMesh::Point &clock_start, libMesh::Point &axis)
Utility routine to re-order a vector of nodes so that they can form a valid quad element.
virtual ElemType type() const=0
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...
Point vertex_average() const
bool getFineElementsFromInteriorNode(const libMesh::Node &interior_node, const libMesh::Node &reference_node, const libMesh::Elem &fine_elem, std::vector< const libMesh::Node *> &tentative_coarse_nodes, std::set< const libMesh::Elem *> &fine_elements)
const RemoteElem * remote_elem