libMesh
mesh_smoother_test.C
Go to the documentation of this file.
1 #include <libmesh/libmesh.h>
2 #include <libmesh/enum_elem_type.h>
3 #include <libmesh/function_base.h>
4 #include <libmesh/mesh_generation.h>
5 #include <libmesh/mesh_modification.h>
6 #include <libmesh/mesh_smoother_laplace.h>
7 #include <libmesh/mesh_smoother_vsmoother.h>
8 #include <libmesh/mesh_tools.h>
9 #include <libmesh/node.h>
10 #include <libmesh/elem.h>
11 #include <libmesh/replicated_mesh.h>
12 #include <libmesh/boundary_info.h>
13 #include <libmesh/system.h> // LIBMESH_HAVE_SOLVER define
14 
15 #include "test_comm.h"
16 #include "libmesh_cppunit.h"
17 
18 namespace {
19 using namespace libMesh;
20 
21 class DistortSquare : public FunctionBase<Real>
22 {
23  std::unique_ptr<FunctionBase<Real>> clone () const override
24  { return std::make_unique<DistortSquare>(); }
25 
26  Real operator() (const Point &,
27  const Real = 0.) override
28  { libmesh_not_implemented(); } // scalar-only API
29 
30  // Skew inward based on a cubic function
31  void operator() (const Point & p,
32  const Real,
33  DenseVector<Real> & output)
34  {
35  output.resize(3);
36  const Real eta = 2*p(0)-1;
37  const Real zeta = 2*p(1)-1;
38  output(0) = p(0) + (std::pow(eta,3)-eta)*p(1)*(1-p(1));
39  output(1) = p(1) + (std::pow(zeta,3)-zeta)*p(0)*(1-p(0));
40  output(2) = 0;
41  }
42 };
43 
44 class SquareToParallelogram : public FunctionBase<Real>
45 {
46  std::unique_ptr<FunctionBase<Real>> clone () const override
47  { return std::make_unique<SquareToParallelogram>(); }
48 
49  Real operator() (const Point &,
50  const Real = 0.) override
51  { libmesh_not_implemented(); } // scalar-only API
52 
53  // Has the effect that a square, meshed into right triangles with diagonals
54  // rising from lower-left to upper-right, is transformed into a left-leaning
55  // parallelogram of eqilateral triangles.
56  void operator() (const Point & p,
57  const Real,
58  DenseVector<Real> & output)
59  {
60  output.resize(3);
61  output(0) = p(0) - 0.5 * p(1);
62  output(1) = 0.5 * std::sqrt(3) * p(1);
63  output(2) = 0;
64  }
65 };
66 
67 class ParallelogramToSquare : public FunctionBase<Real>
68 {
69  std::unique_ptr<FunctionBase<Real>> clone () const override
70  { return std::make_unique<ParallelogramToSquare>(); }
71 
72  Real operator() (const Point &,
73  const Real = 0.) override
74  { libmesh_not_implemented(); } // scalar-only API
75 
76  // Has the effect that a left-leaning parallelogram of equilateral triangles is transformed
77  // into a square of right triangle with diagonals rising from lower-left to upper-right.
78  // This is the inversion of the SquareToParallelogram mapping.
79  void operator() (const Point & p,
80  const Real,
81  DenseVector<Real> & output)
82  {
83  output.resize(3);
84  output(0) = p(0) + p(1) / std::sqrt(3.);
85  output(1) = (2. / std::sqrt(3)) * p(1);
86  output(2) = 0;
87  }
88 };
89 
90 }
91 
92 using namespace libMesh;
93 
94 class MeshSmootherTest : public CppUnit::TestCase
95 {
100 public:
101  LIBMESH_CPPUNIT_TEST_SUITE( MeshSmootherTest );
102 
103 #if LIBMESH_DIM > 2
104  CPPUNIT_TEST( testLaplaceQuad );
105  CPPUNIT_TEST( testLaplaceTri );
106 #if defined(LIBMESH_ENABLE_VSMOOTHER) && defined(LIBMESH_HAVE_SOLVER)
107  CPPUNIT_TEST( testVariationalQuad );
108  CPPUNIT_TEST( testVariationalTri );
109  CPPUNIT_TEST( testVariationalQuadMultipleSubdomains );
110 # endif // LIBMESH_ENABLE_VSMOOTHER
111 #endif
112 
113  CPPUNIT_TEST_SUITE_END();
114 
115 public:
116  void setUp() {}
117 
118  void tearDown() {}
119 
120  void testSmoother(ReplicatedMesh & mesh, MeshSmoother & smoother, const ElemType type, const bool multiple_subdomains=false)
121  {
122  LOG_UNIT_TEST;
123 
124  unsigned int n_elems_per_side = 4;
125 
126  MeshTools::Generation::build_square(mesh, n_elems_per_side, n_elems_per_side,
127  0.,1.,0.,1., type);
128 
129  // Move it around so we have something that needs smoothing
130  DistortSquare ds;
132 
133  std::unordered_map<dof_id_type, Point> subdomain_boundary_node_id_to_point;
134  if (multiple_subdomains)
135  {
136  // Increment the subdomain id on the right half by 1
137  for (auto * elem : mesh.active_element_ptr_range())
138  if (elem->vertex_average()(0) > 0.5)
139  ++elem->subdomain_id();
140 
141  // This loop should NOT be combined with the one above because we need to
142  // finish checking and updating subdomain ids for all elements before
143  // recording the final subdomain boundary.
144  for (auto * elem : mesh.active_element_ptr_range())
145  for (const auto & s : elem->side_index_range())
146  {
147  const auto* neighbor = elem->neighbor_ptr(s);
148  if (neighbor == nullptr)
149  continue;
150 
151  if (elem->subdomain_id() != neighbor->subdomain_id())
152  // This side is part of a subdomain boundary, record the
153  // corresponding node locations
154  for (const auto & n : elem->nodes_on_side(s))
155  subdomain_boundary_node_id_to_point[elem->node_id(n)] = Point(*(elem->get_nodes()[n]));
156  }
157  }
158 
159  const auto & boundary_info = mesh.get_boundary_info();
160 
161  // Function to assert the distortion is as expected
162  auto center_distortion_is = [&boundary_info, n_elems_per_side]
163  (const Node & node, int d, bool distortion,
164  Real distortion_tol=TOLERANCE) {
165  const Real r = node(d);
166  const Real R = r * n_elems_per_side;
167  CPPUNIT_ASSERT_GREATER(-distortion_tol*distortion_tol, r);
168  CPPUNIT_ASSERT_GREATER(-distortion_tol*distortion_tol, 1-r);
169 
170  // If we're at the center we're fine
171  if (std::abs(r-0.5) < distortion_tol*distortion_tol)
172  return true;
173 
174  // Boundary nodes are allowed to slide along the boundary.
175  // However, nodes that are part of more than one boundary (i.e., corners) should remain fixed.
176 
177  // Get boundary ids associated with the node
178  std::vector<boundary_id_type> boundary_ids;
179  boundary_info.boundary_ids(&node, boundary_ids);
180 
181  switch (boundary_ids.size())
182  {
183  // Internal node
184  case 0:
185  return ((std::abs(R-std::round(R)) > distortion_tol) == distortion);
186  break;
187  // Sliding boundary node
188  case 1:
189  // Since sliding boundary nodes may or may not already be in the optimal
190  // position, they may or may not be different from the originally distorted
191  // mesh. Return true here to avoid issues.
192  return true;
193  break;
194  // Fixed boundary node, should not have moved
195  case 2:
196  if (std::abs(node(0)) < distortion_tol*distortion_tol ||
197  std::abs(node(0)-1) < distortion_tol*distortion_tol)
198  {
199  const Real R1 = node(1) * n_elems_per_side;
200  CPPUNIT_ASSERT_LESS(distortion_tol*distortion_tol, std::abs(R1-std::round(R1)));
201  return true;
202  }
203 
204  if (std::abs(node(1)) < distortion_tol*distortion_tol ||
205  std::abs(node(1)-1) < distortion_tol*distortion_tol)
206  {
207  const Real R0 = node(0) * n_elems_per_side;
208  CPPUNIT_ASSERT_LESS(distortion_tol*distortion_tol, std::abs(R0-std::round(R0)));
209 
210  return true;
211  }
212  return false;
213  break;
214  default:
215  libmesh_error_msg("Node has unsupported number of boundary ids = " << boundary_ids.size());
216  }
217  };
218 
219  // Function to check if a given node has changed based on previous mapping
220  auto is_internal_subdomain_boundary_node_the_same = [&subdomain_boundary_node_id_to_point]
221  (const Node & node) {
222  auto it = subdomain_boundary_node_id_to_point.find(node.id());
223  if (it != subdomain_boundary_node_id_to_point.end())
224  return (Point(node) == subdomain_boundary_node_id_to_point[node.id()]);
225  else
226  // node is not an internal subdomain boundary node, just return true
227  return true;
228  };
229 
230  // Make sure our DistortSquare transformation has distorted the mesh
231  for (auto node : mesh.node_ptr_range())
232  {
233  CPPUNIT_ASSERT(center_distortion_is(*node, 0, true));
234  CPPUNIT_ASSERT(center_distortion_is(*node, 1, true));
235  }
236 
237  // Transform the square mesh of triangles to a parallelogram mesh of triangles.
238  // This will allow the Variational Smoother to smooth the mesh to the optimal case
239  // of equilateral triangles
240  const bool is_variational_smoother_type = (dynamic_cast<VariationalMeshSmoother*>(&smoother) != nullptr);
241  if (type == TRI3 && is_variational_smoother_type)
242  {
243  SquareToParallelogram stp;
245  }
246 
247  // Enough iterations to mostly fix us up. Laplace seems to be at 1e-3
248  // tolerance by iteration 6, so hopefully everything is there on any
249  // system by 8.
250  const unsigned int num_iterations = is_variational_smoother_type ? 1 : 8;
251  for (unsigned int i=0; i != num_iterations; ++i)
252  smoother.smooth();
253 
254  // Transform the parallelogram mesh back to a square mesh. In the case of the
255  // Variational Smoother, equilateral triangular elements will be transformed
256  // into right triangular elements that align with the original undistorted mesh.
257  if (type == TRI3 && is_variational_smoother_type)
258  {
259  ParallelogramToSquare pts;
261  }
262 
263  // Make sure we're not too distorted anymore OR that interval subdomain boundary nodes did not change.
264  for (auto node : mesh.node_ptr_range())
265  {
266  if (multiple_subdomains)
267  {
268  CPPUNIT_ASSERT(is_internal_subdomain_boundary_node_the_same(*node));
269  }
270  else
271  {
272  CPPUNIT_ASSERT(center_distortion_is(*node, 0, false, 1e-3));
273  CPPUNIT_ASSERT(center_distortion_is(*node, 1, false, 1e-3));
274  }
275  }
276  }
277 
278 
280  {
282  LaplaceMeshSmoother laplace(mesh);
283 
284  testSmoother(mesh, laplace, QUAD4);
285  }
286 
287 
289  {
291  LaplaceMeshSmoother laplace(mesh);
292 
293  testSmoother(mesh, laplace, TRI3);
294  }
295 
296 
297 #ifdef LIBMESH_ENABLE_VSMOOTHER
299  {
301  VariationalMeshSmoother variational(mesh);
302 
303  testSmoother(mesh, variational, QUAD4);
304  }
305 
306 
308  {
310  VariationalMeshSmoother variational(mesh);
311 
312  testSmoother(mesh, variational, TRI3);
313  }
314 
316  {
318  VariationalMeshSmoother variational(mesh);
319 
320  testSmoother(mesh, variational, QUAD4, true);
321  }
322 #endif // LIBMESH_ENABLE_VSMOOTHER
323 };
324 
325 
ElemType
Defines an enum for geometric element types.
The ReplicatedMesh class is derived from the MeshBase class, and is used to store identical copies of...
A Node is like a Point, but with more information.
Definition: node.h:52
libMesh::Parallel::Communicator * TestCommWorld
Definition: driver.C:171
static constexpr Real TOLERANCE
void resize(const unsigned int n)
Resize the vector.
Definition: dense_vector.h:396
MeshBase & mesh
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.
The libMesh namespace provides an interface to certain functionality in the library.
const BoundaryInfo & get_boundary_info() const
The information about boundary ids on the mesh.
Definition: mesh_base.h:165
virtual void smooth()=0
Function which actually performs the smoothing operations.
This class defines the data structures necessary for Laplace smoothing.
T pow(const T &x)
Definition: utility.h:328
CPPUNIT_TEST_SUITE_REGISTRATION(MeshSmootherTest)
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
This is an implementation of Larisa Branets&#39; smoothing algorithms.
void testVariationalQuadMultipleSubdomains()
void redistribute(MeshBase &mesh, const FunctionBase< Real > &mapfunc)
Deterministically perturb the nodal locations.
void testSmoother(ReplicatedMesh &mesh, MeshSmoother &smoother, const ElemType type, const bool multiple_subdomains=false)
This class provides the necessary interface for mesh smoothing.
Definition: mesh_smoother.h:38
Defines a dense vector for use in Finite Element-type computations.
Definition: dof_map.h:74
Base class for functors that can be evaluated at a point and (optionally) time.
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition: point.h:39