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/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
27namespace
28{
29using namespace libMesh;
30using Utility::pow;
31
32// Distortion function that doesn't distort boundary nodes
33// 2D only, use for LaplaceMeshSmoother
34class 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
60class DistortHyperCube : public FunctionBase<Real>
61{
62public:
63 DistortHyperCube(const unsigned int dim) : _dim(dim) {}
64
65private:
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
112class 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
136class 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
160class 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
189class 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
219using namespace libMesh;
220
221class MeshSmootherTest : public CppUnit::TestCase
222{
227public:
229
230#if LIBMESH_DIM > 2
233#if defined(LIBMESH_ENABLE_VSMOOTHER) && defined(LIBMESH_HAVE_SOLVER)
237
244
248
254
260
267
272
273#if defined(LIBMESH_HAVE_GZSTREAM)
276#endif
277
278#endif // LIBMESH_ENABLE_VSMOOTHER
279#endif
280
282
283public:
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
1365 }
1366
1368 {
1370 mesh.read("meshes/hex8_prism6_smoothed.xda.gz");
1371
1373 }
1374
1375#endif // LIBMESH_ENABLE_VSMOOTHER
1376};
1377
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 testLaplaceSmoother(ReplicatedMesh &mesh, MeshSmoother &smoother, ElemType type)
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:990
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.
virtual const Node & node_ref(const dof_id_type i) const
Definition mesh_base.h:745
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:430
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:1355
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:437
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:40
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.
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