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