libMesh
mesh_smoother_laplace.C
Go to the documentation of this file.
1 // The libMesh Finite Element Library.
2 // Copyright (C) 2002-2019 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
3 
4 // This library is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU Lesser General Public
6 // License as published by the Free Software Foundation; either
7 // version 2.1 of the License, or (at your option) any later version.
8 
9 // This library is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 // Lesser General Public License for more details.
13 
14 // You should have received a copy of the GNU Lesser General Public
15 // License along with this library; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 
18 
19 
20 // C++ includes
21 #include <algorithm> // for std::copy, std::sort
22 
23 // Local includes
24 #include "libmesh/mesh_smoother_laplace.h"
25 #include "libmesh/mesh_tools.h"
26 #include "libmesh/elem.h"
27 #include "libmesh/unstructured_mesh.h"
28 #include "libmesh/parallel.h"
29 #include "libmesh/parallel_ghost_sync.h" // sync_dofobject_data_by_id()
30 #include "libmesh/parallel_algebra.h" // StandardType<Point>
31 #include "libmesh/int_range.h"
32 
33 namespace libMesh
34 {
35 // LaplaceMeshSmoother member functions
37  : MeshSmoother(mesh),
38  _initialized(false)
39 {
40 }
41 
42 
43 
44 
45 void LaplaceMeshSmoother::smooth(unsigned int n_iterations)
46 {
47  if (!_initialized)
48  this->init();
49 
50  // Don't smooth the nodes on the boundary...
51  // this would change the mesh geometry which
52  // is probably not something we want!
53  auto on_boundary = MeshTools::find_boundary_nodes(_mesh);
54 
55  // Also: don't smooth block bondary nodes
56  auto on_block_boundary = MeshTools::find_block_boundary_nodes(_mesh);
57 
58  // Merge them
59  on_boundary.insert(on_block_boundary.begin(), on_block_boundary.end());
60 
61  // We can only update the nodes after all new positions were
62  // determined. We store the new positions here
63  std::vector<Point> new_positions;
64 
65  for (unsigned int n=0; n<n_iterations; n++)
66  {
67  new_positions.resize(_mesh.max_node_id());
68 
69  for (auto & node : _mesh.local_node_ptr_range())
70  {
71  if (node == nullptr)
72  libmesh_error_msg("[" << _mesh.processor_id() << "]: Node iterator returned nullptr.");
73 
74  // leave the boundary intact
75  // Only relocate the nodes which are vertices of an element
76  // All other entries of _graph (the secondary nodes) are empty
77  if (!on_boundary.count(node->id()) && (_graph[node->id()].size() > 0))
78  {
79  Point avg_position(0.,0.,0.);
80 
81  for (const auto & connected_id : _graph[node->id()])
82  {
83  // Will these nodal positions always be available
84  // or will they refer to remote nodes? This will
85  // fail an assertion in the latter case, which
86  // shouldn't occur if DistributedMesh is working
87  // correctly.
88  const Point & connected_node = _mesh.point(connected_id);
89 
90  avg_position.add( connected_node );
91  } // end for (j)
92 
93  // Compute the average, store in the new_positions vector
94  new_positions[node->id()] = avg_position / static_cast<Real>(_graph[node->id()].size());
95  } // end if
96  } // end for
97 
98 
99  // now update the node positions (local node positions only)
100  for (auto & node : _mesh.local_node_ptr_range())
101  if (!on_boundary.count(node->id()) && (_graph[node->id()].size() > 0))
102  {
103  // Should call Point::op=
104  // libMesh::out << "Setting node id " << node->id() << " to position " << new_positions[node->id()];
105  _mesh.node_ref(node->id()) = new_positions[node->id()];
106  }
107 
108  // Now the nodes which are ghosts on this processor may have been moved on
109  // the processors which own them. So we need to synchronize with our neighbors
110  // and get the most up-to-date positions for the ghosts.
111  SyncNodalPositions sync_object(_mesh);
113  (_mesh.comm(), _mesh.nodes_begin(), _mesh.nodes_end(), sync_object);
114 
115  } // end for n_iterations
116 
117  // finally adjust the second order nodes (those located between vertices)
118  // these nodes will be located between their adjacent nodes
119  // do this element-wise
120  for (auto & elem : _mesh.active_element_ptr_range())
121  {
122  // get the second order nodes (son)
123  // their element indices start at n_vertices and go to n_nodes
124  const unsigned int son_begin = elem->n_vertices();
125  const unsigned int son_end = elem->n_nodes();
126 
127  // loop over all second order nodes (son)
128  for (unsigned int son=son_begin; son<son_end; son++)
129  {
130  // Don't smooth second-order nodes which are on the boundary
131  if (!on_boundary.count(elem->node_id(son)))
132  {
133  const unsigned int n_adjacent_vertices =
134  elem->n_second_order_adjacent_vertices(son);
135 
136  // calculate the new position which is the average of the
137  // position of the adjacent vertices
138  Point avg_position(0,0,0);
139  for (unsigned int v=0; v<n_adjacent_vertices; v++)
140  avg_position +=
141  _mesh.point( elem->node_id( elem->second_order_adjacent_vertex(son,v) ) );
142 
143  _mesh.node_ref(elem->node_id(son)) = avg_position / n_adjacent_vertices;
144  }
145  }
146  }
147 }
148 
149 
150 
151 
152 
154 {
155  switch (_mesh.mesh_dimension())
156  {
157 
158  // TODO:[BSK] Fix this to work for refined meshes... I think
159  // the implementation was done quickly for Damien, who did not have
160  // refined grids. Fix it here and in the original Mesh member.
161 
162  case 2: // Stolen directly from build_L_graph in mesh_base.C
163  {
164  // Initialize space in the graph. It is indexed by node id.
165  // Each node may be connected to an arbitrary number of other
166  // nodes via edges.
167  _graph.resize(_mesh.max_node_id());
168 
169  for (auto & elem : _mesh.active_local_element_ptr_range())
170  for (auto s : elem->side_index_range())
171  {
172  // Only operate on sides which are on the
173  // boundary or for which the current element's
174  // id is greater than its neighbor's.
175  // Sides get only built once.
176  if ((elem->neighbor_ptr(s) == nullptr) ||
177  (elem->id() > elem->neighbor_ptr(s)->id()))
178  {
179  std::unique_ptr<const Elem> side(elem->build_side_ptr(s));
180  _graph[side->node_id(0)].push_back(side->node_id(1));
181  _graph[side->node_id(1)].push_back(side->node_id(0));
182  }
183  }
184  _initialized = true;
185  break;
186  } // case 2
187 
188  case 3: // Stolen blatantly from build_L_graph in mesh_base.C
189  {
190  // Initialize space in the graph.
191  _graph.resize(_mesh.max_node_id());
192 
193  for (auto & elem : _mesh.active_local_element_ptr_range())
194  for (auto f : elem->side_index_range()) // Loop over faces
195  if ((elem->neighbor_ptr(f) == nullptr) ||
196  (elem->id() > elem->neighbor_ptr(f)->id()))
197  {
198  // We need a full (i.e. non-proxy) element for the face, since we will
199  // be looking at its sides as well!
200  std::unique_ptr<const Elem> face = elem->build_side_ptr(f, /*proxy=*/false);
201 
202  for (auto s : face->side_index_range()) // Loop over face's edges
203  {
204  // Here we can use a proxy
205  std::unique_ptr<const Elem> side = face->build_side_ptr(s);
206 
207  // At this point, we just insert the node numbers
208  // again. At the end we'll call sort and unique
209  // to make sure there are no duplicates
210  _graph[side->node_id(0)].push_back(side->node_id(1));
211  _graph[side->node_id(1)].push_back(side->node_id(0));
212  }
213  }
214 
215  _initialized = true;
216  break;
217  } // case 3
218 
219  default:
220  libmesh_error_msg("At this time it is not possible to smooth a dimension " << _mesh.mesh_dimension() << "mesh. Aborting...");
221  }
222 
223  // Done building graph from local elements. Let's now allgather the
224  // graph so that it is available on all processors for the actual
225  // smoothing operation?
226  this->allgather_graph();
227 
228  // In 3D, it's possible for > 2 processor partitions to meet
229  // at a single edge, while in 2D only 2 processor partitions
230  // share an edge. Therefore the allgather'd graph in 3D may
231  // now have duplicate entries and we need to remove them so
232  // they don't foul up the averaging algorithm employed by the
233  // Laplace smoother.
234  for (auto & id_vec : _graph)
235  {
236  // The std::unique algorithm removes duplicate *consecutive* elements from a range,
237  // so it only makes sense to call it on a sorted range...
238  std::sort(id_vec.begin(), id_vec.end());
239  id_vec.erase(std::unique(id_vec.begin(), id_vec.end()), id_vec.end());
240  }
241 
242 } // init()
243 
244 
245 
246 
247 void LaplaceMeshSmoother::print_graph(std::ostream & out_stream) const
248 {
249  for (auto i : index_range(_graph))
250  {
251  out_stream << i << ": ";
252  std::copy(_graph[i].begin(),
253  _graph[i].end(),
254  std::ostream_iterator<unsigned>(out_stream, " "));
255  out_stream << std::endl;
256  }
257 }
258 
259 
260 
262 {
263  // The graph data structure is not well-suited for parallel communication,
264  // so copy the graph into a single vector defined by:
265  // NA A_0 A_1 ... A_{NA} | NB B_0 B_1 ... B_{NB} | NC C_0 C_1 ... C_{NC}
266  // where:
267  // * NA is the number of graph connections for node A
268  // * A_0, A_1, etc. are the IDs connected to node A
269  std::vector<dof_id_type> flat_graph;
270 
271  // Reserve at least enough space for each node to have zero entries
272  flat_graph.reserve(_graph.size());
273 
274  for (const auto & id_vec : _graph)
275  {
276  // First push back the number of entries for this node
277  flat_graph.push_back (cast_int<dof_id_type>(id_vec.size()));
278 
279  // Then push back all the IDs
280  for (const auto & dof : id_vec)
281  flat_graph.push_back(dof);
282  }
283 
284  // // A copy of the flat graph (for printing only, delete me later)
285  // std::vector<unsigned> copy_of_flat_graph(flat_graph);
286 
287  // Use the allgather routine to combine all the flat graphs on all processors
288  _mesh.comm().allgather(flat_graph);
289 
290  // Now reconstruct _graph from the allgathered flat_graph.
291 
292  // // (Delete me later, the copy is just for printing purposes.)
293  // std::vector<std::vector<unsigned >> copy_of_graph(_graph);
294 
295  // Make sure the old graph is cleared out
296  _graph.clear();
297  _graph.resize(_mesh.max_node_id());
298 
299  // Our current position in the allgather'd flat_graph
300  std::size_t cursor=0;
301 
302  // There are max_node_id * n_processors entries to read in total
303  for (processor_id_type p=0; p<_mesh.n_processors(); ++p)
304  for (dof_id_type node_ctr=0; node_ctr<_mesh.max_node_id(); ++node_ctr)
305  {
306  // Read the number of entries for this node, move cursor
307  std::size_t n_entries = flat_graph[cursor++];
308 
309  // Reserve space for that many more entries, then push back
310  _graph[node_ctr].reserve(_graph[node_ctr].size() + n_entries);
311 
312  // Read all graph connections for this node, move the cursor each time
313  // Note: there might be zero entries but that's fine
314  for (std::size_t i=0; i<n_entries; ++i)
315  _graph[node_ctr].push_back(flat_graph[cursor++]);
316  }
317 
318  // // Print local graph to uniquely named file (debugging)
319  // {
320  // // Generate unique filename for this processor
321  // std::ostringstream oss;
322  // oss << "graph_filename_" << _mesh.processor_id() << ".txt";
323  // std::ofstream graph_stream(oss.str().c_str());
324  //
325  // // Print the local non-flat graph
326  // std::swap(_graph, copy_of_graph);
327  // print_graph(graph_stream);
328  //
329  // // Print the (local) flat graph for verification
330  // for (const auto & dof : copy_of_flat_graph)
331  // graph_stream << dof << " ";
332  // graph_stream << "\n";
333  //
334  // // Print the allgather'd grap for verification
335  // for (const auto & dof : flat_graph)
336  // graph_stream << dof << " ";
337  // graph_stream << "\n";
338  //
339  // // Print the global non-flat graph
340  // std::swap(_graph, copy_of_graph);
341  // print_graph(graph_stream);
342  // }
343 } // allgather_graph()
344 
345 } // namespace libMesh
libMesh::dof_id_type
uint8_t dof_id_type
Definition: id_types.h:67
libMesh::MeshSmoother::_mesh
UnstructuredMesh & _mesh
Definition: mesh_smoother.h:61
libMesh::MeshBase::point
virtual const Point & point(const dof_id_type i) const =0
libMesh::LaplaceMeshSmoother::allgather_graph
void allgather_graph()
This function allgather's the (local) graph after it is computed on each processor by the init() func...
Definition: mesh_smoother_laplace.C:261
libMesh::LaplaceMeshSmoother::_graph
std::vector< std::vector< dof_id_type > > _graph
Data structure for holding the L-graph.
Definition: mesh_smoother_laplace.h:104
libMesh::MeshBase::active_local_element_ptr_range
virtual SimpleRange< element_iterator > active_local_element_ptr_range()=0
libMesh::MeshBase::active_element_ptr_range
virtual SimpleRange< element_iterator > active_element_ptr_range()=0
libMesh::TypeVector::add
void add(const TypeVector< T2 > &)
Add to this vector without creating a temporary.
Definition: type_vector.h:641
libMesh::index_range
IntRange< std::size_t > index_range(const std::vector< T > &vec)
Helper function that returns an IntRange<std::size_t> representing all the indices of the passed-in v...
Definition: int_range.h:106
libMesh
The libMesh namespace provides an interface to certain functionality in the library.
Definition: factoryfunction.C:55
end
IterBase * end
Also have a polymorphic pointer to the end object, this prevents iterating past the end.
Definition: variant_filter_iterator.h:343
libMesh::ParallelObject::comm
const Parallel::Communicator & comm() const
Definition: parallel_object.h:94
mesh
MeshBase & mesh
Definition: mesh_communication.C:1257
libMesh::MeshBase::mesh_dimension
unsigned int mesh_dimension() const
Definition: mesh_base.C:135
libMesh::MeshBase::max_node_id
virtual dof_id_type max_node_id() const =0
libMesh::MeshTools::find_block_boundary_nodes
std::unordered_set< dof_id_type > find_block_boundary_nodes(const MeshBase &mesh)
Returns a std::set containing Node IDs for all of the block boundary nodes.
Definition: mesh_tools.C:354
libMesh::LaplaceMeshSmoother::smooth
virtual void smooth() override
Redefinition of the smooth function from the base class.
Definition: mesh_smoother_laplace.h:65
libMesh::ParallelObject::n_processors
processor_id_type n_processors() const
Definition: parallel_object.h:100
libMesh::ParallelObject::processor_id
processor_id_type processor_id() const
Definition: parallel_object.h:106
libMesh::MeshBase::local_node_ptr_range
virtual SimpleRange< node_iterator > local_node_ptr_range()=0
libMesh::Point
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition: point.h:38
libMesh::processor_id_type
uint8_t processor_id_type
Definition: id_types.h:104
libMesh::LaplaceMeshSmoother::init
void init()
Initialization for the Laplace smoothing routine is basically identical to building an "L-graph" whic...
Definition: mesh_smoother_laplace.C:153
libMesh::SyncNodalPositions
Definition: parallel_ghost_sync.h:782
libMesh::MeshBase::nodes_end
virtual node_iterator nodes_end()=0
libMesh::LaplaceMeshSmoother::LaplaceMeshSmoother
LaplaceMeshSmoother(UnstructuredMesh &mesh)
Constructor.
Definition: mesh_smoother_laplace.C:36
libMesh::MeshBase::node_ref
virtual const Node & node_ref(const dof_id_type i) const
Definition: mesh_base.h:451
libMesh::UnstructuredMesh
The UnstructuredMesh class is derived from the MeshBase class.
Definition: unstructured_mesh.h:48
libMesh::LaplaceMeshSmoother::print_graph
void print_graph(std::ostream &out=libMesh::out) const
Mainly for debugging, this function will print out the connectivity graph which has been created.
Definition: mesh_smoother_laplace.C:247
libMesh::Parallel::sync_dofobject_data_by_id
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.
Definition: parallel_ghost_sync.h:338
libMesh::MeshBase::nodes_begin
virtual node_iterator nodes_begin()=0
Iterate over all the nodes in the Mesh.
libMesh::LaplaceMeshSmoother::_initialized
bool _initialized
True if the L-graph has been created, false otherwise.
Definition: mesh_smoother_laplace.h:99
libMesh::MeshTools::find_boundary_nodes
void find_boundary_nodes(const MeshBase &mesh, std::vector< bool > &on_boundary)
Definition: mesh_tools.C:306
libMesh::MeshSmoother
This class provides the necessary interface for mesh smoothing.
Definition: mesh_smoother.h:38