libMesh
mesh_smoother_test.C
Go to the documentation of this file.
1 #include <libmesh/boundary_info.h>
2 #include <libmesh/elem.h>
3 #include <libmesh/enum_elem_type.h>
4 #include <libmesh/function_base.h>
5 #include <libmesh/libmesh.h>
6 #include <libmesh/mesh_generation.h>
7 #include <libmesh/mesh_modification.h>
8 #include <libmesh/mesh_smoother_laplace.h>
9 #include <libmesh/mesh_smoother_vsmoother.h>
10 #include <libmesh/mesh_tools.h>
11 #include <libmesh/node.h>
12 #include <libmesh/reference_elem.h>
13 #include <libmesh/replicated_mesh.h>
14 #include <libmesh/mesh.h>
15 #include <libmesh/system.h> // LIBMESH_HAVE_SOLVER define
16 #include "libmesh/face_tri.h"
17 #include "libmesh/cell_prism.h"
18 #include "libmesh/utility.h"
19 #include "libmesh/enum_to_string.h"
20 #include "libmesh/parallel_ghost_sync.h"
21 
22 #include "test_comm.h"
23 #include "libmesh_cppunit.h"
24 
25 #include <random>
26 
27 namespace
28 {
29 using namespace libMesh;
30 using Utility::pow;
31 
32 // Distortion function that doesn't distort boundary nodes
33 // 2D only, use for LaplaceMeshSmoother
34 class DistortSquare : public FunctionBase<Real>
35 {
36  std::unique_ptr<FunctionBase<Real>> clone() const override
37  {
38  return std::make_unique<DistortSquare>();
39  }
40 
41  Real operator()(const Point &, const Real = 0.) override
42  {
43  libmesh_not_implemented();
44  } // scalar-only API
45 
46  // Skew inward based on a cubic function
47  void operator()(const Point & p, const Real, DenseVector<Real> & output)
48  {
49  output.resize(3);
50  const Real eta = 2 * p(0) - 1;
51  const Real zeta = 2 * p(1) - 1;
52  output(0) = p(0) + (pow<3>(eta) - eta) * p(1) * (1 - p(1));
53  output(1) = p(1) + (pow<3>(zeta) - zeta) * p(0) * (1 - p(0));
54  output(2) = 0;
55  }
56 };
57 
58 // Distortion function supporting 1D, 2D, and 3D
59 // Use for VariationalMeshSmoother
60 class DistortHyperCube : public FunctionBase<Real>
61 {
62 public:
63  DistortHyperCube(const unsigned int dim) : _dim(dim) {}
64 
65 private:
66  std::unique_ptr<FunctionBase<Real>> clone() const override
67  {
68  return std::make_unique<DistortHyperCube>(_dim);
69  }
70 
71  Real operator()(const Point &, const Real = 0.) override { libmesh_not_implemented(); }
72 
73  void operator()(const Point & p, const Real, DenseVector<Real> & output) override
74  {
75  output.resize(3);
76  output.zero();
77 
78  // Count how many coordinates are exactly on the boundary (0 or 1)
79  std::array<bool, 3> is_on_boundary = {false, false, false};
80  for (unsigned int i = 0; i < _dim; ++i)
81  if (std::abs(p(i)) < TOLERANCE || std::abs(p(i) - 1.) < TOLERANCE)
82  is_on_boundary[i] = true;
83 
84  // Distort only those directions not fixed on the boundary
85  for (unsigned int i = 0; i < _dim; ++i)
86  {
87  if (!is_on_boundary[i]) // only distort free dimensions
88  {
89  // This value controls the strength of the distortion
90  Real modulation = 0.3;
91  Real xi = 2. * p(i) - 1.;
92  for (unsigned int j = 0; j < _dim; ++j)
93  {
94  if (j != i)
95  {
96  Real pj = std::clamp(p(j), 0., 1.); // ensure numeric safety
97  modulation *= (pj - 0.5) * (pj - 0.5) * 4.; // quadratic bump centered at 0.5
98  }
99  }
100  const auto delta = (pow<3>(xi) - xi) * modulation;
101  // Check for delta = 0 to make sure we perturb every point
102  output(i) = (delta > TOLERANCE) ? p(i) + delta : 1.03 * p(i);
103  }
104  else
105  output(i) = p(i); // dimension on boundary remains unchanged
106  }
107  }
108 
109  const unsigned int _dim;
110 };
111 
112 class SquareToParallelogram : public FunctionBase<Real>
113 {
114  std::unique_ptr<FunctionBase<Real>> clone() const override
115  {
116  return std::make_unique<SquareToParallelogram>();
117  }
118 
119  Real operator()(const Point &, const Real = 0.) override
120  {
121  libmesh_not_implemented();
122  } // scalar-only API
123 
124  // Has the effect that a square, meshed into right triangles with diagonals
125  // rising from lower-left to upper-right, is transformed into a left-leaning
126  // parallelogram of eqilateral triangles.
127  void operator()(const Point & p, const Real, DenseVector<Real> & output)
128  {
129  output.resize(3);
130  output(0) = p(0) - 0.5 * p(1);
131  output(1) = 0.5 * std::sqrt(Real(3)) * p(1);
132  output(2) = p(2);
133  }
134 };
135 
136 class ParallelogramToSquare : public FunctionBase<Real>
137 {
138  std::unique_ptr<FunctionBase<Real>> clone() const override
139  {
140  return std::make_unique<ParallelogramToSquare>();
141  }
142 
143  Real operator()(const Point &, const Real = 0.) override
144  {
145  libmesh_not_implemented();
146  } // scalar-only API
147 
148  // Has the effect that a left-leaning parallelogram of equilateral triangles is transformed
149  // into a square of right triangle with diagonals rising from lower-left to upper-right.
150  // This is the inversion of the SquareToParallelogram mapping.
151  void operator()(const Point & p, const Real, DenseVector<Real> & output)
152  {
153  output.resize(3);
154  output(0) = p(0) + p(1) / std::sqrt(Real(3));
155  output(1) = (2. / std::sqrt(Real(3))) * p(1);
156  output(2) = p(2);
157  }
158 };
159 
160 class CubeToParallelepiped : public FunctionBase<Real>
161 {
162  std::unique_ptr<FunctionBase<Real>> clone() const override
163  {
164  return std::make_unique<CubeToParallelepiped>();
165  }
166 
167  Real operator()(const Point &, const Real = 0.) override
168  {
169  libmesh_not_implemented();
170  } // scalar-only API
171 
172  // Has the effect that a cube, meshed into right triangular prisms with diagonals
173  // rising from lower-right to upper-left, is transformed into a right-leaning
174  // parallelepiped of equilateral triangular prisms. Additionally, the z direction
175  // is scaled to ensure element height to base aspect ratios match the target element.
176  // Without the correct aspect ratio, the smoothed mesh nodes are off and the
177  // distortion_is assertions fail.
178  void operator()(const Point & p, const Real, DenseVector<Real> & output)
179  {
180  output.resize(3);
181  output(0) = p(0) + 0.5 * p(1);
182  output(1) = 0.5 * std::sqrt(3) * p(1);
183  // Adjusting z to get the triangular area to element height aspect ratio To
184  // match the target element
185  output(2) = p(2) * 0.25 * std::sqrt(3.);
186  }
187 };
188 
189 class ParallelepipedToCube : public FunctionBase<Real>
190 {
191  std::unique_ptr<FunctionBase<Real>> clone() const override
192  {
193  return std::make_unique<ParallelepipedToCube>();
194  }
195 
196  Real operator()(const Point &, const Real = 0.) override
197  {
198  libmesh_not_implemented();
199  } // scalar-only API
200 
201  // Has the effect that a right-leaning parallelepiped of equilaterial triangular
202  // prisms with is transformed into a cube of right triangular prisms with
203  // diagonals rising from lower-right to upper-left. Additionally, the z direction
204  // is scaled to ensure element heights align with the values expected by the
205  // distortion_is function.
206  // This is the inversion of the CubeToParallelepiped mapping.
207  void operator()(const Point & p, const Real, DenseVector<Real> & output)
208  {
209  output.resize(3);
210  output(0) = p(0) - p(1) / std::sqrt(3.);
211  output(1) = (2. / std::sqrt(3)) * p(1);
212  // Undoing the aspect ratio adjustment to get the z divisions to work with
213  // distortion_is
214  output(2) = p(2) * 4. / std::sqrt(3.);
215  }
216 };
217 }
218 
219 using namespace libMesh;
220 
221 class MeshSmootherTest : public CppUnit::TestCase
222 {
227 public:
228  LIBMESH_CPPUNIT_TEST_SUITE(MeshSmootherTest);
229 
230 #if LIBMESH_DIM > 2
231  CPPUNIT_TEST(testLaplaceQuad);
232  CPPUNIT_TEST(testLaplaceTri);
233 #if defined(LIBMESH_ENABLE_VSMOOTHER) && defined(LIBMESH_HAVE_SOLVER)
234  CPPUNIT_TEST(testVariationalEdge2);
235  CPPUNIT_TEST(testVariationalEdge3);
236  CPPUNIT_TEST(testVariationalEdge3MultipleSubdomains);
237 
238  CPPUNIT_TEST(testVariationalQuad4);
239  CPPUNIT_TEST(testVariationalQuad4MultipleSubdomains);
240  CPPUNIT_TEST(testVariationalQuad8);
241  CPPUNIT_TEST(testVariationalQuad9);
242  CPPUNIT_TEST(testVariationalQuad9MultipleSubdomains);
243  CPPUNIT_TEST(testVariationalQuad4Tangled);
244 
245  CPPUNIT_TEST(testVariationalTri3);
246  CPPUNIT_TEST(testVariationalTri6);
247  CPPUNIT_TEST(testVariationalTri6MultipleSubdomains);
248 
249  CPPUNIT_TEST(testVariationalHex8);
250  CPPUNIT_TEST(testVariationalHex20);
251  CPPUNIT_TEST(testVariationalHex27);
252  CPPUNIT_TEST(testVariationalHex27Tangled);
253  CPPUNIT_TEST(testVariationalHex27MultipleSubdomains);
254 
255  CPPUNIT_TEST(testVariationalPyramid5);
256  CPPUNIT_TEST(testVariationalPyramid13);
257  CPPUNIT_TEST(testVariationalPyramid14);
258  CPPUNIT_TEST(testVariationalPyramid18);
259  CPPUNIT_TEST(testVariationalPyramid18MultipleSubdomains);
260 
261  CPPUNIT_TEST(testVariationalPrism6);
262  CPPUNIT_TEST(testVariationalPrism15);
263  CPPUNIT_TEST(testVariationalPrism18);
264  CPPUNIT_TEST(testVariationalPrism20);
265  CPPUNIT_TEST(testVariationalPrism21);
266  CPPUNIT_TEST(testVariationalPrism21MultipleSubdomains);
267 
268  CPPUNIT_TEST(testVariationalTet4);
269  CPPUNIT_TEST(testVariationalTet10);
270  CPPUNIT_TEST(testVariationalTet14);
271  CPPUNIT_TEST(testVariationalTet14MultipleSubdomains);
272 
273 #if defined(LIBMESH_HAVE_GZSTREAM)
274  CPPUNIT_TEST(testVariationalMixed2D);
275  CPPUNIT_TEST(testVariationalMixed3D);
276 #endif
277 
278 #endif // LIBMESH_ENABLE_VSMOOTHER
279 #endif
280 
281  CPPUNIT_TEST_SUITE_END();
282 
283 public:
284  void setUp() {}
285 
286  void tearDown() {}
287 
289  {
290  LOG_UNIT_TEST;
291 
292  constexpr unsigned int n_elems_per_side = 4;
293 
295  mesh, n_elems_per_side, n_elems_per_side, 0., 1., 0., 1., type);
296 
297  // Move it around so we have something that needs smoothing
298  DistortSquare ds;
300 
301  // Assert the distortion is as expected
302  auto center_distortion_is =
303  [](const Node & node, int d, bool distortion, Real distortion_tol = TOLERANCE) {
304  const Real r = node(d);
305  const Real R = r * n_elems_per_side;
306  CPPUNIT_ASSERT_GREATER(-TOLERANCE * TOLERANCE, r);
307  CPPUNIT_ASSERT_GREATER(-TOLERANCE * TOLERANCE, 1 - r);
308 
309  // If we're at the boundaries we should *never* be distorted
310  if (std::abs(node(0)) < TOLERANCE * TOLERANCE ||
311  std::abs(node(0) - 1) < TOLERANCE * TOLERANCE)
312  {
313  const Real R1 = node(1) * n_elems_per_side;
314  CPPUNIT_ASSERT_LESS(TOLERANCE * TOLERANCE, std::abs(R1 - std::round(R1)));
315  return true;
316  }
317 
318  if (std::abs(node(1)) < TOLERANCE * TOLERANCE ||
319  std::abs(node(1) - 1) < TOLERANCE * TOLERANCE)
320  {
321  const Real R0 = node(0) * n_elems_per_side;
322  CPPUNIT_ASSERT_LESS(TOLERANCE * TOLERANCE, std::abs(R0 - std::round(R0)));
323 
324  return true;
325  }
326 
327  // If we're at the center we're fine
328  if (std::abs(r - 0.5) < TOLERANCE * TOLERANCE)
329  return true;
330 
331  return ((std::abs(R - std::round(R)) > distortion_tol) == distortion);
332  };
333 
334  for (auto node : mesh.node_ptr_range())
335  {
336  CPPUNIT_ASSERT(center_distortion_is(*node, 0, true));
337  CPPUNIT_ASSERT(center_distortion_is(*node, 1, true));
338  }
339 
340  // Enough iterations to mostly fix us up. Laplace seems to be at 1e-3
341  // tolerance by iteration 6, so hopefully everything is there on any
342  // system by 8.
343  for (unsigned int i = 0; i != 8; ++i)
344  smoother.smooth();
345 
346  // Make sure we're not too distorted anymore.
347  for (auto node : mesh.node_ptr_range())
348  {
349  CPPUNIT_ASSERT(center_distortion_is(*node, 0, false, 1e-3));
350  CPPUNIT_ASSERT(center_distortion_is(*node, 1, false, 1e-3));
351  }
352  }
353 
354  // Helper function to determine how many dimensions of a given point lie at
355  // the center and faces of a sub-cube
356  std::pair<unsigned int, unsigned int>
357  numCenteredAndFacedDimensions(const Point & point, const Real & side_length, const Real & tol)
358  {
359  const auto half_length = 0.5 * side_length;
360  unsigned int num_centered = 0;
361  unsigned int num_face = 0;
362  for (const auto d : make_range(3))
363  {
364  const auto num_half_lengths = point(d) / half_length;
365  if (std::abs(num_half_lengths - std::round(num_half_lengths)) > tol)
366  // This coordinante does not occur at a sub-cube face or center
367  continue;
368 
369  if (std::lround(num_half_lengths) % 2)
370  // An odd number of half lengths means point(d) is at a sub-cube center
371  ++num_centered;
372  else
373  // An odd number of half lengths means point(d) is on a sub-cube face
374  ++num_face;
375  }
376 
377  return std::make_pair(num_centered, num_face);
378  }
379 
380  // Helper function to determine whether a given point lies at the center of
381  // a sub-cube
382  bool pointIsCubeCenter(const Point & point, const Real & side_length, const Real & tol = TOLERANCE)
383  {
384  return numCenteredAndFacedDimensions(point, side_length, tol).first == 3;
385  }
386 
387  // Helper function to determine whether a given point lies at the vertex of
388  // a sub-cube
389  bool pointIsCubeVertex(const Point & point, const Real & side_length, const Real & tol = TOLERANCE)
390  {
391  return numCenteredAndFacedDimensions(point, side_length, tol).second == 3;
392  }
393 
394  // Helper function to determine whether a given point lies at the center of
395  // a sub-cube face
396  bool pointIsCubeFaceCenter(const Point & point, const Real & side_length, const Real & tol = TOLERANCE)
397  {
398  const auto result = numCenteredAndFacedDimensions(point, side_length, tol);
399  return (result.first == 2) && (result.second == 1);
400  }
401 
403  VariationalMeshSmoother &smoother,
404  const ElemType type,
405  const bool multiple_subdomains = false,
406  const bool tangle_mesh = false,
407  const Real tangle_damping_factor = 1.0)
408  {
409  LOG_UNIT_TEST;
410 
411  if (multiple_subdomains && tangle_mesh)
412  libmesh_not_implemented_msg(
413  "Arbitrary mesh tangling with multiple subdomains is not supported.");
414 
415  // Get mesh dimension, determine whether type is triangular
416  const auto * ref_elem = &(ReferenceElem::get(type));
417  const auto dim = ref_elem->dim();
418  const bool type_is_tri = Utility::enum_to_string(type).compare(0, 3, "TRI") == 0;
419  const bool type_is_pyramid = Utility::enum_to_string(type).compare(0, 7, "PYRAMID") == 0;
420  const bool type_is_prism = Utility::enum_to_string(type).compare(0, 5, "PRISM") == 0;
421  const bool type_is_tet = Utility::enum_to_string(type).compare(0, 3, "TET") == 0;
422 
423  // Used fewer elems for higher order types, as extra midpoint nodes will add
424  // enough complexity
425  unsigned int n_elems_per_side = 4 / Elem::type_to_default_order_map[type];
426  const auto side_length = 1.0 / n_elems_per_side;
427 
428  switch (dim)
429  {
430  case 1:
431  MeshTools::Generation::build_line(mesh, n_elems_per_side, 0., 1., type);
432  break;
433  case 2:
435  mesh, n_elems_per_side, n_elems_per_side, 0., 1., 0., 1., type);
436  break;
437 
438  case 3:
440  n_elems_per_side,
441  n_elems_per_side,
442  n_elems_per_side,
443  0.,
444  1.,
445  0.,
446  1.,
447  0.,
448  1.,
449  type);
450  break;
451 
452  default:
453  libmesh_error_msg("Unsupported dimension " << dim);
454  }
455 
456  // Move it around so we have something that needs smoothing
457  DistortHyperCube dh(dim);
459 
460  const auto & boundary_info = mesh.get_boundary_info();
461  const auto & proc_id = mesh.processor_id();
462 
463  if (tangle_mesh)
464  {
465  // We tangle the mesh by (partially) swapping the locations of 2 nodes.
466  // If the n-dimentional hypercube is represented as a grid with
467  // (undistorted) divisions occuring at integer numbers, we will
468  // (partially) swap the nodes nearest p1 = (1,1,1) and p2 = (2,1,2).
469 
470  // Define p1 and p2 given the integer indices
471  // Undistorted elem side length
472  const Real dr = 1. / (n_elems_per_side * Elem::type_to_default_order_map[type]);
473  const Point p1 = Point(dr, dim > 1 ? dr : 0, dim > 2 ? dr : 0);
474  const Point p2 = Point(2. * dr, dim > 1 ? dr : 0, dim > 2 ? 2. * dr : 0);
475 
476  // We need to find the nodes of the mesh that are closest to p1 and p2.
477  // This will require some parallel communication for distributed meshes.
478 
479  // Distances between local mesh nodes and p1
480  std::map<dof_id_type, Real> dist1_map;
481  // Distances between local mesh nodes and p2
482  std::map<dof_id_type, Real> dist2_map;
483 
484  for (const auto * node : mesh.local_node_ptr_range())
485  {
486  dist1_map[node->id()] = (*node - p1).norm();
487  dist2_map[node->id()] = (*node - p2).norm();
488  }
489 
490  // Helper function to analyze a dist_map accross all processors and
491  // return the node id and spatial coordinates for the node with the
492  // smallest distance
493  auto get_closet_point_accross_all_procs =
494  [&mesh, &proc_id](const std::map<dof_id_type, Real> &dist_map) {
495 
496  processor_id_type broadcasting_proc = 0;
497  Real d_min_local = std::numeric_limits<Real>::max();
499 
500  // Only execute this if this proc owns any nodes. Otherwise,
501  // use default values defined above
502  if (dist_map.size())
503  {
504  // Iterator to the map entry with the smallest distance
505  auto min_it =
506  std::min_element(dist_map.begin(), dist_map.end(),
507  [](const auto &a, const auto &b) {
508  return a.second < b.second;
509  });
510 
511  node_id = min_it->first;
512  d_min_local = min_it->second;
513  }
514 
515  // Get the smallest distance accross all procs
516  auto d_min_global = d_min_local;
517  mesh.comm().min(d_min_global);
518 
519  // Find the proc id, node_id, and spatial coordinantes for the
520  // node with the smallest distance
521  Point node;
522  if (d_min_local == d_min_global) {
523  broadcasting_proc = proc_id;
524  node = mesh.node_ref(node_id);
525  }
526 
527  mesh.comm().max(broadcasting_proc);
528  // Broadcast information about this node to all procs
529  mesh.comm().broadcast(node_id, broadcasting_proc);
530  mesh.comm().broadcast(node, broadcasting_proc);
531 
532  return std::pair(node_id, node);
533  };
534 
535  const auto [node_id1, node1] =
536  get_closet_point_accross_all_procs(dist1_map);
537  const auto [node_id2, node2] =
538  get_closet_point_accross_all_procs(dist2_map);
539 
540  // modify the nodes if they are on this proc
541  const auto displacement = tangle_damping_factor * (node1 - node2);
542  if (mesh.query_node_ptr(node_id1))
543  mesh.node_ref(node_id1) -= displacement;
544  if (mesh.query_node_ptr(node_id2))
545  mesh.node_ref(node_id2) += displacement;
546 
547  SyncNodalPositions sync_object(mesh);
549  mesh.nodes_end(), sync_object);
550 
551  // Make sure the mesh is tangled
552  // Need this to create the system we are about to access
553  smoother.setup();
554  const auto &unsmoothed_info = smoother.get_mesh_info();
555  CPPUNIT_ASSERT(unsmoothed_info.mesh_is_tangled);
556  }
557 
558  // Add multiple subdomains if requested
559  std::unordered_map<subdomain_id_type, Real> distorted_subdomain_volumes;
560  subdomain_id_type highest_subdomain_id = 0;
561  if (multiple_subdomains)
562  {
563  // Modify the subdomain ids in an interesting way
564  for (auto * elem : mesh.active_element_ptr_range())
565  {
566  unsigned int subdomain_id = 0;
567  for (const auto d : make_range(dim))
568  if (elem->vertex_average()(d) > 0.5)
569  ++subdomain_id;
570  elem->subdomain_id() += subdomain_id;
571  }
572 
573  // This loop should NOT be combined with the one above because we need to
574  // finish checking and updating subdomain ids for all elements before
575  // recording the final subdomain volumes.
576  for (auto *elem : mesh.active_element_ptr_range()) {
577  const auto sub_id = elem->subdomain_id();
578  // Don't double count an element's volume on multiple procs
579  if (elem->processor_id() != proc_id)
580  continue;
581  distorted_subdomain_volumes[sub_id] += elem->volume();
582  highest_subdomain_id = std::max(sub_id, highest_subdomain_id);
583  }
584 
585  // Parallel communication
586  mesh.comm().max(highest_subdomain_id);
587  for (const auto sub_id : make_range(highest_subdomain_id + 1)) {
588  // Make sure sub_id exists in the map on this proc
589  if (distorted_subdomain_volumes.find(sub_id) ==
590  distorted_subdomain_volumes.end())
591  distorted_subdomain_volumes[sub_id] = 0.;
592 
593  mesh.comm().sum(distorted_subdomain_volumes[sub_id]);
594  }
595 
596  // We've just invalidated the get_mesh_subdomains() cache by
597  // adding a new one; fix it.
599  }
600 
601  // Get the mesh order
602  const auto & elem_orders = mesh.elem_default_orders();
603  libmesh_error_msg_if(elem_orders.size() != 1,
604  "The variational smoother cannot be used for mixed-order meshes!");
605  // For pyramids, the factor 2 accounts for the account that cubes of side
606  // length s are divided into pyramids of base side length s and height s/2.
607  // The factor 4 lets us catch triangular face midpoint nodes for PYRAMID18 elements.
608  // Similar reasoning for tets.
609  const auto scale_factor = *elem_orders.begin() * ((type_is_pyramid || type_is_tet) ? 2 * 4 : 1);
610 
611  // Function to assert the node distortion is as expected
612  auto node_distortion_is = [&n_elems_per_side, &dim, &boundary_info, &scale_factor, &type_is_prism](
613  const Node & node, bool distortion, Real distortion_tol = TOLERANCE) {
614  // Get boundary ids associated with the node
615  std::vector<boundary_id_type> boundary_ids;
616  boundary_info.boundary_ids(&node, boundary_ids);
617 
618  // This tells us what type of node we are: internal, sliding, or fixed
619  const auto num_dofs = dim - boundary_ids.size();
620  /*
621  * The following cases of num_dofs are possible, ASSUMING all boundaries
622  * are non-overlapping
623  * 3D: 3-0, 3-1, 3-2, 3-3
624  * = 3 2 1 0
625  * internal sliding, sliding, fixed
626  * 2D: 2-0, 2-1, 2-2
627  * = 2 1 0
628  * internal sliding, fixed
629  * 1D: 1-0, 1-1
630  * = 1 0
631  * internal fixed
632  *
633  * We expect that R is an integer in [0, n_elems_per_side] for
634  * num_dofs of the node's cooridinantes, while the remaining coordinates
635  * are fixed to the boundary with value 0 or 1. In other words, at LEAST
636  * dim - num_dofs coordinantes should be 0 or 1.
637  */
638 
639  std::size_t num_zero_or_one = 0;
640 
641  bool distorted = false;
642  for (const auto d : make_range(dim))
643  {
644  const Real r = node(d);
645  const Real R = r * n_elems_per_side * scale_factor;
646  CPPUNIT_ASSERT_GREATER(-distortion_tol * distortion_tol, r);
647  CPPUNIT_ASSERT_GREATER(-distortion_tol * distortion_tol, 1 - r);
648 
649  bool d_distorted = std::abs(R - std::round(R)) > distortion_tol;
650  if (type_is_prism && (scale_factor == 3))
651  {
652  // Adjustment required for triangular face nodes of PRISM20/21 elements.
653  // These elements have fe_order 3. This takes care of nodes occuring at
654  // thirds, but not halves.
655  const Real R_prism = R / scale_factor * 2;
656  const bool d_distorted_prism =
657  std::abs(R_prism - std::round(R_prism)) > distortion_tol;
658  d_distorted &= d_distorted_prism;
659  }
660  distorted |= d_distorted;
661  num_zero_or_one += (absolute_fuzzy_equals(r, 0.) || absolute_fuzzy_equals(r, 1.));
662  }
663 
664  CPPUNIT_ASSERT_GREATEREQUAL(dim - num_dofs, num_zero_or_one);
665 
666  // We can never expect a fixed node to be distorted
667  if (num_dofs == 0)
668  // if (num_dofs < dim)
669  return true;
670  return distorted == distortion;
671  };
672 
673  // Make sure our DistortHyperCube transformation has distorted the mesh
674  for (auto node : mesh.node_ptr_range())
675  CPPUNIT_ASSERT(node_distortion_is(*node, true));
676 
677  // Transform the square mesh of triangles to a parallelogram mesh of
678  // triangles. This will allow the Variational Smoother to smooth the mesh
679  // to the optimal case of equilateral triangles
680  if (type_is_tri)
681  {
682  // Transform the square mesh of triangles to a parallelogram mesh of
683  // triangles. This will allow the Variational Smoother to smooth the mesh
684  // to the optimal case of equilateral triangles
685  SquareToParallelogram stp;
687  }
688  else if (type_is_prism)
689  {
690  // Transform the cube mesh of prisms to a parallelepiped mesh of
691  // prisms. This will allow the Variational Smoother to smooth the mesh
692  // to the optimal case of equilateral triangular prisms
693  CubeToParallelepiped ctp;
695  }
696 
697  smoother.smooth();
698 
699  if (type_is_tri)
700  {
701  // Transform the parallelogram mesh back to a square mesh. In the case of
702  // the Variational Smoother, equilateral triangules will be transformed
703  // into right triangules that align with the original undistorted mesh.
704  ParallelogramToSquare pts;
706  }
707  else if (type_is_prism)
708  {
709  // Transform the parallelepiped mesh back to a cube mesh. In the case of
710  // the Variational Smoother, equilateral triangular prisms will be transformed
711  // into right triangular prisms that align with the original undistorted mesh.
712  ParallelepipedToCube ptc;
714  }
715 
716  if (multiple_subdomains)
717  {
718  // Make sure the subdomain volumes didn't change. Although this doesn't
719  // guarantee that the subdomain didn't change, it is a good indicator
720  // and is friedly to sliding subdomain boundary nodes.
721  std::unordered_map<subdomain_id_type, Real> smoothed_subdomain_volumes;
722  for (auto *elem : mesh.active_element_ptr_range()) {
723  // Don't double count an element's volume on multiple procs
724  if (elem->processor_id() != proc_id)
725  continue;
726  smoothed_subdomain_volumes[elem->subdomain_id()] += elem->volume();
727  }
728 
729  // Parallel communication
730  for (const auto sub_id : make_range(highest_subdomain_id + 1)) {
731  // Make sure sub_id exists in the map on this proc
732  if (smoothed_subdomain_volumes.find(sub_id) ==
733  smoothed_subdomain_volumes.end())
734  smoothed_subdomain_volumes[sub_id] = 0.;
735 
736  mesh.comm().sum(smoothed_subdomain_volumes[sub_id]);
737  }
738 
739  for (const auto sub_id : make_range(highest_subdomain_id + 1))
740  CPPUNIT_ASSERT(relative_fuzzy_equals(
741  libmesh_map_find(distorted_subdomain_volumes, sub_id),
742  libmesh_map_find(smoothed_subdomain_volumes, sub_id), TOLERANCE));
743  }
744  else
745  {
746  std::unordered_map<dof_id_type, std::vector<const Elem *>> nodes_to_elem_map;
747  MeshTools::build_nodes_to_elem_map(mesh, nodes_to_elem_map);
748 
749  // Make sure we're not too distorted anymore, using the given
750  // tolerance.
751  std::set<dof_id_type> nodes_checked;
752  const Real tol = TOLERANCE;
753 
754  for (const auto * elem : mesh.active_element_ptr_range())
755  {
756  for (const auto local_node_id : make_range(elem->n_nodes()))
757  {
758  const auto & node = elem->node_ref(local_node_id);
759  if (nodes_checked.find(node.id()) != nodes_checked.end())
760  continue;
761 
762  nodes_checked.insert(node.id());
763 
764  // Check for special case where pyramidal base-to-apex midpoint
765  // nodes are not smoothed to the actual midpoints.
766  if (type_is_pyramid)
767  {
768  if (local_node_id > 8 && local_node_id < 13)
769  {
770  // Base-Apex midpoint nodes
771  //
772  // Due to the nature of the pyramidal target element and the
773  // cubic nature of the test mesh, for a dilation weight o
774  // 0.5, smoothed base-apex midpoint nodes are smoothed to
775  // the point base + x * (apex - base) instead of
776  // base + 0.5 (apex - base), where x is in (0,1) and
777  // depends on the number of nodes in the element.
778  // We hard-code this check below.
779 
780  const auto & base = elem->node_ref(local_node_id - 9);
781  const auto & apex = elem->node_ref(4);
782  const Real x = (type == PYRAMID18) ? 0.56646084 : 0.54985875;
783 
784  CPPUNIT_ASSERT(node.absolute_fuzzy_equals(base + x * (apex - base), tol));
785  continue;
786  }
787  else if (local_node_id > 13)
788  {
789  // Triangular face midpoint nodes
790  //
791  // Due to the nature of the pyramidal target element and the
792  // cubic nature of the test mesh, for a dilation weight o
793  // 0.5, smoothed triangular face midpoint nodes are
794  // smoothed a weighted average of the constituent
795  // vertices instead of an unweighted average.
796  // We hard-code this check below.
797  const auto & base1 = elem->node_ref(local_node_id - 14);
798  const auto & base2 = elem->node_ref((local_node_id - 13) % 4);
799  const auto & apex = elem->node_ref(4);
800 
801  const auto node_approx =
802  (0.31401599 * base1 + 0.31401599 * base2 + 0.37196802 * apex);
803  CPPUNIT_ASSERT(node.absolute_fuzzy_equals(node_approx, tol));
804  continue;
805  }
806  }
807 
808  // Check for special case where some tet midpoint nodes are not
809  // smoothed to the actual midpoints.
810  else if (type_is_tet && !elem->is_vertex(local_node_id))
811  {
812  // We have a non-vertex node. Determine what "type" of
813  // midpoint node with respect to the mesh geometry.
814  // First, get the nodes that neighbor this node
815  std::vector<const Node *> neighbors;
817  mesh, node, nodes_to_elem_map, neighbors);
818 
819  switch (neighbors.size())
820  {
821  case 2: {
822  // Midpoint node
823  // Determine what "type" of midpoint node
824 
825  // Is one of the neighbors at the center of a sub-cube?
826  const auto is_0_cube_center =
827  pointIsCubeCenter(*neighbors[0], side_length);
828  const auto is_1_cube_center =
829  pointIsCubeCenter(*neighbors[1], side_length);
830  libmesh_assert(!(is_0_cube_center && is_1_cube_center));
831 
832  if (is_0_cube_center || is_1_cube_center)
833  {
834  const auto & cube_center =
835  is_0_cube_center ? *neighbors[0] : *neighbors[1];
836  const auto & other =
837  is_0_cube_center ? *neighbors[1] : *neighbors[0];
838 
839  // Since one neighbor is a sub-cube center, the
840  // other will either be a sub-cube face center or
841  // a sub-cube vertex. Determine which one.
842  if (pointIsCubeFaceCenter(other, side_length))
843  {
844  const Real x = (type == TET10) ? 0.42895041 : 0.41486385;
845  CPPUNIT_ASSERT(node.absolute_fuzzy_equals(
846  other + x * (cube_center - other), tol));
847  }
848 
849  else if (pointIsCubeVertex(other, side_length))
850  {
851  const Real x = (type == TET10) ? 0.55388920 : 0.58093516;
852  CPPUNIT_ASSERT(node.absolute_fuzzy_equals(
853  other + x * (cube_center - other), tol));
854  }
855  }
856 
857  else
858  {
859  // The remaining possibilities are
860  // cube-vertex-to-cube-vertex midpoints and
861  // cube-vertex-to-cube-face-center midpoints.
862  const auto is_0_cube_vertex =
863  pointIsCubeVertex(*neighbors[0], side_length);
864  const auto is_1_cube_vertex =
865  pointIsCubeVertex(*neighbors[1], side_length);
866  const auto is_0_cube_face_center =
867  pointIsCubeFaceCenter(*neighbors[0], side_length);
868  const auto is_1_cube_face_center =
869  pointIsCubeFaceCenter(*neighbors[1], side_length);
870 
871  if (is_0_cube_vertex && is_1_cube_vertex)
872  // Due to symmetry, this type of midpoint is
873  // the geometric midpoint. Let the
874  // node_distortion_is function check it
875  CPPUNIT_ASSERT(node_distortion_is(node, false));
876 
877  else
878  {
879  libmesh_error_msg_if(
880  (is_0_cube_center || is_0_cube_face_center) &&
881  (is_1_cube_center || is_1_cube_face_center),
882  "We should never get here!");
883  const auto & cube_vertex =
884  is_0_cube_vertex ? *neighbors[0] : *neighbors[1];
885  const auto & cube_face_center =
886  is_0_cube_face_center ? *neighbors[0] : *neighbors[1];
887  const Real x = (type == TET10) ? 0.61299101 : 0.65125580;
888  CPPUNIT_ASSERT(node.absolute_fuzzy_equals(
889  cube_vertex + x * (cube_face_center - cube_vertex), tol));
890  }
891  }
892 
893  continue;
894  break;
895  }
896 
897  case 6: {
898  // Face node
899  // Only keep the face vertex nodes
900  neighbors.erase(std::remove_if(neighbors.begin(),
901  neighbors.end(),
902  [&elem](const Node * n) {
903  return !elem->is_vertex(
904  elem->local_node(n->id()));
905  }),
906  neighbors.end());
907 
908  // There are three types of faces:
909  //
910  // 1) Isoscelese triangle with vertices at two
911  // sub-cube vertices and the sub-cube center,
912  // 2) Isosceles triangle with vertices at two
913  // sub-cube vertices and a sub-cube face center,
914  // 3) Scalene triangle with vertices at a sub-cube
915  // center, a sub-cube vertex, and a sub-cube face
916  // center.
917  //
918  // Determine which kind of face this node belongs to based on the vertex
919  // neighbors
920 
921  unsigned int num_vertices_at_cube_center = 0;
922  unsigned int num_vertices_at_cube_vertices = 0;
923  unsigned int num_vertices_at_cube_face_centers = 0;
924  for (const auto * neighbor : neighbors)
925  {
926  if (pointIsCubeCenter(*neighbor, side_length))
927  num_vertices_at_cube_center += 1;
928  else if (pointIsCubeVertex(*neighbor, side_length))
929  num_vertices_at_cube_vertices += 1;
930  else if (pointIsCubeFaceCenter(*neighbor, side_length))
931  num_vertices_at_cube_face_centers += 1;
932  }
933 
934  // We will express the face node at a linear
935  // combination of the face vertices
936  Node node_approx = Node(0, 0, 0);
937 
938  // Isosceles triangular face
939  if (num_vertices_at_cube_vertices == 2)
940  {
941  // Isosceles triangular face (type 1)
942  if (num_vertices_at_cube_center)
943  for (const auto * neighbor : neighbors)
944  {
945  Real weight;
946  if (pointIsCubeVertex(*neighbor, side_length))
947  weight = 0.30600747;
948  else if (pointIsCubeCenter(*neighbor, side_length))
949  weight = 0.38798506;
950  else
951  libmesh_error_msg("We should never get here!");
952 
953  node_approx += weight * (*neighbor);
954  }
955 
956  // Isosceles triangular face (type 2)
957  else if (num_vertices_at_cube_face_centers)
958  for (const auto * neighbor : neighbors)
959  {
960  Real weight;
961  if (pointIsCubeVertex(*neighbor, side_length))
962  weight = 0.28078090;
963  else if (pointIsCubeFaceCenter(*neighbor, side_length))
964  weight = 0.43843820;
965  else
966  libmesh_error_msg("We should never get here!");
967 
968  node_approx += weight * (*neighbor);
969  }
970 
971  else
972  libmesh_error_msg("We should never get here!");
973  }
974 
975  // Scalene triangular face (type 3)
976  else if (num_vertices_at_cube_center && num_vertices_at_cube_vertices &&
977  num_vertices_at_cube_face_centers)
978  for (const auto * neighbor : neighbors)
979  {
980  Real weight;
981  if (pointIsCubeCenter(*neighbor, side_length))
982  weight = 0.33102438;
983  else if (pointIsCubeVertex(*neighbor, side_length))
984  weight = 0.27147230;
985  else if (pointIsCubeFaceCenter(*neighbor, side_length))
986  weight = 0.39750332;
987  else
988  libmesh_error_msg("We should never get here!");
989 
990  node_approx += weight * (*neighbor);
991  }
992 
993  else
994  libmesh_error_msg("We should never get here!");
995 
996  CPPUNIT_ASSERT(node.absolute_fuzzy_equals(node_approx, tol));
997 
998  continue;
999  break;
1000  }
1001  default: {
1002  libmesh_error_msg(node << " has unexpected number of neighbors ("
1003  << neighbors.size() << ")");
1004  break;
1005  }
1006  } // switch (neighbors.size())
1007  } // if (type_is_tet)
1008 
1009  CPPUNIT_ASSERT(node_distortion_is(node, false));
1010 
1011  }
1012  }
1013  }
1014  }
1015 
1016  // Function that distorts and smooths a mesh and checks that the result is
1017  // equal to the original "gold" mesh
1019  {
1020  // Make a copy of the gold mesh to distort and then smooth
1021  ReplicatedMesh mesh(gold_mesh);
1022 
1023  // Move it around so we have something that needs smoothing
1024  DistortHyperCube dh(mesh.mesh_dimension());
1026 
1027  // Function to assert the distortion is as expected
1028  auto mesh_distortion_is = [](const ReplicatedMesh & gold_mesh,
1029  const ReplicatedMesh & mesh,
1030  const bool distortion,
1031  Real distortion_tol = TOLERANCE) {
1032 
1033  CPPUNIT_ASSERT(gold_mesh.n_nodes() == mesh.n_nodes());
1034  for (const auto node_id : make_range(gold_mesh.n_nodes()))
1035  {
1036  const auto & gold_node = gold_mesh.node_ref(node_id);
1037  const auto & node = mesh.node_ref(node_id);
1038  for (const auto d : make_range(gold_mesh.mesh_dimension()))
1039  {
1040  const bool d_distorted = std::abs(gold_node(d) - node(d)) > distortion_tol;
1041  if (d_distorted)
1042  // mesh is distorted from gold_mesh
1043  return distortion;
1044  }
1045  }
1046 
1047  // mesh is not distorted from gold_mesh
1048  return !distortion;
1049  };
1050 
1051  // Make sure the mesh has been distorted
1052  CPPUNIT_ASSERT(mesh_distortion_is(gold_mesh, mesh, true));
1053 
1054  // Turn off subdomain boundary preservation because DistortHyperCube
1055  // does not preserve subdomain boundaries
1056  // Also set dilation coefficient to 0 because the reference volume of the
1057  // distorted mesh won't be equal to the reference volume of the smoothed
1058  // mesh and will smooth to a slightly different solution.
1059  VariationalMeshSmoother smoother(mesh, 0.0, false);
1060  smoother.smooth();
1061 
1062  // Make sure the mesh has been smoothed to the gold mesh
1063  CPPUNIT_ASSERT(mesh_distortion_is(gold_mesh, mesh, false));
1064  }
1065 
1067  {
1069  LaplaceMeshSmoother laplace(mesh, 8);
1070 
1071  testLaplaceSmoother(mesh, laplace, QUAD4);
1072  }
1073 
1075  {
1077  LaplaceMeshSmoother laplace(mesh, 8);
1078 
1079  testLaplaceSmoother(mesh, laplace, TRI3);
1080  }
1081 
1082 #ifdef LIBMESH_ENABLE_VSMOOTHER
1084  {
1086  VariationalMeshSmoother variational(mesh);
1087 
1088  testVariationalSmoother(mesh, variational, EDGE2);
1089  }
1090 
1092  {
1094  VariationalMeshSmoother variational(mesh);
1095 
1096  testVariationalSmoother(mesh, variational, EDGE3);
1097  }
1098 
1100  {
1102  VariationalMeshSmoother variational(mesh);
1103 
1104  testVariationalSmoother(mesh, variational, EDGE3, true);
1105  }
1106 
1108  {
1110  VariationalMeshSmoother variational(mesh, 0.5, true, TOLERANCE * TOLERANCE, TOLERANCE * TOLERANCE, 100);
1111 
1112  // High verbosity for a 3D mesh to increase code coverage, but silence the output
1113  // If we want to save the output for processing later, send it somewhere
1114  // besides nullptr
1115  std::streambuf * out_buf = libMesh::out.rdbuf(nullptr);
1116 
1117  testVariationalSmoother(mesh, variational, QUAD4);
1118 
1119  // Reset stdout
1120  libMesh::out.rdbuf(out_buf);
1121  }
1122 
1124  {
1126  VariationalMeshSmoother variational(mesh);
1127 
1128  testVariationalSmoother(mesh, variational, QUAD4, true);
1129  }
1130 
1132  {
1134  VariationalMeshSmoother variational(mesh);
1135 
1136  testVariationalSmoother(mesh, variational, QUAD8);
1137  }
1138 
1140  {
1142  VariationalMeshSmoother variational(mesh);
1143 
1144  testVariationalSmoother(mesh, variational, QUAD9);
1145  }
1146 
1148  {
1150  VariationalMeshSmoother variational(mesh);
1151 
1152  testVariationalSmoother(mesh, variational, QUAD9, true);
1153  }
1154 
1156  {
1158  VariationalMeshSmoother variational(mesh);
1159 
1160  testVariationalSmoother(mesh, variational, QUAD4, false, true, 0.65);
1161  }
1162 
1164  {
1166  VariationalMeshSmoother variational(mesh);
1167 
1168  testVariationalSmoother(mesh, variational, TRI3);
1169  }
1170 
1172  {
1174  VariationalMeshSmoother variational(mesh);
1175 
1176  testVariationalSmoother(mesh, variational, TRI6);
1177  }
1178 
1180  {
1182  VariationalMeshSmoother variational(mesh);
1183 
1184  testVariationalSmoother(mesh, variational, TRI3, true);
1185  }
1186 
1188  {
1190  VariationalMeshSmoother variational(mesh);
1191 
1192  testVariationalSmoother(mesh, variational, HEX8);
1193  }
1194 
1196  {
1198  VariationalMeshSmoother variational(mesh);
1199 
1200  testVariationalSmoother(mesh, variational, HEX20);
1201  }
1202 
1204  {
1206  VariationalMeshSmoother variational(mesh);
1207 
1208  testVariationalSmoother(mesh, variational, HEX27);
1209  }
1210 
1212  {
1214  VariationalMeshSmoother variational(mesh);
1215 
1216  testVariationalSmoother(mesh, variational, HEX27, true);
1217  }
1218 
1220  {
1222  VariationalMeshSmoother variational(mesh);
1223 
1224  testVariationalSmoother(mesh, variational, HEX27, false, true, 0.55);
1225  }
1226 
1228  {
1230  VariationalMeshSmoother variational(mesh);
1231 
1232  testVariationalSmoother(mesh, variational, PYRAMID5);
1233  }
1234 
1236  {
1238  VariationalMeshSmoother variational(mesh);
1239 
1240  testVariationalSmoother(mesh, variational, PYRAMID13);
1241  }
1242 
1244  {
1246  VariationalMeshSmoother variational(mesh);
1247 
1248  testVariationalSmoother(mesh, variational, PYRAMID14);
1249  }
1250 
1252  {
1254  VariationalMeshSmoother variational(mesh);
1255 
1256  testVariationalSmoother(mesh, variational, PYRAMID18);
1257  }
1258 
1260  {
1262  VariationalMeshSmoother variational(mesh, 0.0);
1263 
1264  testVariationalSmoother(mesh, variational, PYRAMID18, true);
1265  }
1266 
1268  {
1270  VariationalMeshSmoother variational(mesh);
1271 
1272  testVariationalSmoother(mesh, variational, PRISM6);
1273  }
1274 
1276  {
1278  VariationalMeshSmoother variational(mesh);
1279 
1280  testVariationalSmoother(mesh, variational, PRISM15);
1281  }
1282 
1284  {
1286  VariationalMeshSmoother variational(
1287  mesh, 0.5, true, TOLERANCE * TOLERANCE, 10 * TOLERANCE * TOLERANCE);
1288 
1289  testVariationalSmoother(mesh, variational, PRISM18);
1290  }
1291 
1293  {
1295  VariationalMeshSmoother variational(
1296  mesh, 0.5, true, TOLERANCE * TOLERANCE, 10 * TOLERANCE * TOLERANCE);
1297 
1298  testVariationalSmoother(mesh, variational, PRISM20);
1299  }
1300 
1302  {
1304  VariationalMeshSmoother variational(
1305  mesh, 0.5, true, TOLERANCE * TOLERANCE, 10 * TOLERANCE * TOLERANCE);
1306 
1307  testVariationalSmoother(mesh, variational, PRISM21);
1308  }
1309 
1311  {
1313  mesh.allow_renumbering(false);
1314  VariationalMeshSmoother variational(mesh);
1315 
1316  testVariationalSmoother(mesh, variational, PRISM21, true);
1317  }
1318 
1320  {
1322  VariationalMeshSmoother variational(mesh, 0.5, true, TOLERANCE * TOLERANCE, TOLERANCE * TOLERANCE, 100);
1323 
1324  // High verbosity for a 3D mesh to increase code coverage, but silence the output
1325  // If we want to save the output for processing later, send it somewhere
1326  // besides nullptr
1327  std::streambuf * out_buf = libMesh::out.rdbuf(nullptr);
1328 
1329  testVariationalSmoother(mesh, variational, TET4);
1330 
1331  // Reset stdout
1332  libMesh::out.rdbuf(out_buf);
1333  }
1334 
1336  {
1338  VariationalMeshSmoother variational(mesh);
1339 
1340  testVariationalSmoother(mesh, variational, TET10);
1341  }
1342 
1344  {
1346  VariationalMeshSmoother variational(mesh);
1347 
1348  testVariationalSmoother(mesh, variational, TET14);
1349  }
1350 
1352  {
1354  VariationalMeshSmoother variational(mesh);
1355 
1356  testVariationalSmoother(mesh, variational, TET14, true);
1357  }
1358 
1360  {
1362  mesh.read("meshes/quad4_tri3_smoothed.xda.gz");
1363 
1364  testVariationalSmootherRegression(mesh);
1365  }
1366 
1368  {
1370  mesh.read("meshes/hex8_prism6_smoothed.xda.gz");
1371 
1372  testVariationalSmootherRegression(mesh);
1373  }
1374 
1375 #endif // LIBMESH_ENABLE_VSMOOTHER
1376 };
1377 
virtual dof_id_type n_nodes() const override final
static const Order type_to_default_order_map[INVALID_ELEM]
This array maps the integer representation of the ElemType enum to the default approximation order of...
Definition: elem.h:990
std::pair< unsigned int, unsigned int > numCenteredAndFacedDimensions(const Point &point, const Real &side_length, const Real &tol)
ElemType
Defines an enum for geometric element types.
const std::set< Order > & elem_default_orders() const
Definition: mesh_base.h:427
The ReplicatedMesh class is derived from the MeshBase class, and is used to store identical copies of...
A Node is like a Point, but with more information.
Definition: node.h:52
virtual void zero() override final
Set every element in the vector to 0.
Definition: dense_vector.h:420
virtual void read(const std::string &name, void *mesh_data=nullptr, bool skip_renumber_nodes_and_elements=false, bool skip_find_neighbors=false, bool skip_detect_interior_parents=false)=0
Interfaces for reading/writing a mesh to/from a file.
void testLaplaceSmoother(ReplicatedMesh &mesh, MeshSmoother &smoother, ElemType type)
void allow_renumbering(bool allow)
If false is passed in then this mesh will no longer be renumbered when being prepared for use...
Definition: mesh_base.h:1345
libMesh::Parallel::Communicator * TestCommWorld
Definition: driver.C:218
static constexpr Real TOLERANCE
unsigned int dim
virtual void smooth() override
Redefinition of the smooth function from the base class.
void resize(const unsigned int n)
Resize the vector.
Definition: dense_vector.h:396
void sum(T &r) const
MeshBase & mesh
const MeshQualityInfo & get_mesh_info() const
Getter for the _system&#39;s _mesh_info attribute.
bool pointIsCubeVertex(const Point &point, const Real &side_length, const Real &tol=TOLERANCE)
void testVariationalPyramid18MultipleSubdomains()
const Parallel::Communicator & comm() const
void testVariationalPrism21MultipleSubdomains()
void build_square(UnstructuredMesh &mesh, const unsigned int nx, const unsigned int ny, const Real xmin=0., const Real xmax=1., const Real ymin=0., const Real ymax=1., const ElemType type=INVALID_ELEM, const bool gauss_lobatto_grid=false)
A specialized build_cube() for 2D meshes.
void build_nodes_to_elem_map(const MeshBase &mesh, std::vector< std::vector< dof_id_type >> &nodes_to_elem_map)
After calling this function the input vector nodes_to_elem_map will contain the node to element conne...
Definition: mesh_tools.C:456
void testVariationalSmoother(Mesh &mesh, VariationalMeshSmoother &smoother, const ElemType type, const bool multiple_subdomains=false, const bool tangle_mesh=false, const Real tangle_damping_factor=1.0)
The libMesh namespace provides an interface to certain functionality in the library.
const BoundaryInfo & get_boundary_info() const
The information about boundary ids on the mesh.
Definition: mesh_base.h:170
virtual void smooth()=0
Function which actually performs the smoothing operations.
uint8_t processor_id_type
This class defines the data structures necessary for Laplace smoothing.
T pow(const T &x)
Definition: utility.h:296
void min(const T &r, T &o, Request &req) const
static constexpr dof_id_type invalid_id
An invalid id to distinguish an uninitialized DofObject.
Definition: dof_object.h:473
dof_id_type weight(const MeshBase &mesh, const processor_id_type pid)
Definition: mesh_tools.C:444
bool pointIsCubeFaceCenter(const Point &point, const Real &side_length, const Real &tol=TOLERANCE)
void cache_elem_data()
Definition: mesh_base.C:1959
virtual const Node * query_node_ptr(const dof_id_type i) const =0
libmesh_assert(ctx)
void testVariationalQuad9MultipleSubdomains()
CPPUNIT_TEST_SUITE_REGISTRATION(MeshSmootherTest)
void sync_dofobject_data_by_id(const Communicator &comm, const Iterator &range_begin, const Iterator &range_end, SyncFunctor &sync)
Request data about a range of ghost dofobjects uniquely identified by their id.
streambufT * rdbuf() const
Get the associated stream buffer.
void broadcast(T &data, const unsigned int root_id=0, const bool identical_sizes=false) const
std::string enum_to_string(const T e)
void testVariationalTet14MultipleSubdomains()
bool absolute_fuzzy_equals(const T &var1, const T2 &var2, const Real tol=TOLERANCE *TOLERANCE)
Function to check whether two variables are equal within an absolute tolerance.
Definition: fuzzy_equals.h:64
void testVariationalHex27MultipleSubdomains()
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
void testVariationalSmootherRegression(const ReplicatedMesh &gold_mesh)
This is an implementation of Larisa Branets&#39; smoothing algorithms.
void max(const T &r, T &o, Request &req) const
auto norm(const T &a)
Definition: tensor_tools.h:74
static const Real b
OStreamProxy out
void testVariationalTri6MultipleSubdomains()
IntRange< T > make_range(T beg, T end)
The 2-parameter make_range() helper function returns an IntRange<T> when both input parameters are of...
Definition: int_range.h:176
unsigned int mesh_dimension() const
Definition: mesh_base.C:430
void build_line(UnstructuredMesh &mesh, const unsigned int nx, const Real xmin=0., const Real xmax=1., const ElemType type=INVALID_ELEM, const bool gauss_lobatto_grid=false)
A specialized build_cube() for 1D meshes.
void redistribute(MeshBase &mesh, const FunctionBase< Real > &mapfunc)
Deterministically perturb the nodal locations.
virtual const Node & node_ref(const dof_id_type i) const
Definition: mesh_base.h:735
bool pointIsCubeCenter(const Point &point, const Real &side_length, const Real &tol=TOLERANCE)
This class provides the necessary interface for mesh smoothing.
Definition: mesh_smoother.h:38
bool relative_fuzzy_equals(const T &var1, const T2 &var2, const Real tol=TOLERANCE *TOLERANCE)
Function to check whether two variables are equal within a relative tolerance.
Definition: fuzzy_equals.h:78
Defines a dense vector for use in Finite Element-type computations.
Definition: dof_map.h:74
void find_nodal_or_face_neighbors(const MeshBase &mesh, const Node &node, const std::unordered_map< dof_id_type, std::vector< const Elem *>> &nodes_to_elem_map, std::vector< const Node *> &neighbors)
Given a mesh and a node in the mesh, the vector will be filled with every node directly attached to t...
Definition: mesh_tools.C:1092
virtual void setup()
Setup method that creates equation systems, system, and constraints, to be called just prior to smoot...
void testVariationalQuad4MultipleSubdomains()
Base class for functors that can be evaluated at a point and (optionally) time.
The Mesh class is a thin wrapper, around the ReplicatedMesh class by default.
Definition: mesh.h:50
processor_id_type processor_id() const
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition: point.h:39
const Elem & get(const ElemType type_in)
void build_cube(UnstructuredMesh &mesh, const unsigned int nx=0, const unsigned int ny=0, const unsigned int nz=0, const Real xmin=0., const Real xmax=1., const Real ymin=0., const Real ymax=1., const Real zmin=0., const Real zmax=1., const ElemType type=INVALID_ELEM, const bool gauss_lobatto_grid=false)
Builds a (elements) cube.
virtual dof_id_type n_nodes() const =0
uint8_t dof_id_type
Definition: id_types.h:67
void testVariationalEdge3MultipleSubdomains()