libMesh::LaplaceMeshSmoother Class Reference

#include <mesh_smoother_laplace.h>

Inheritance diagram for libMesh::LaplaceMeshSmoother:

Public Member Functions

 LaplaceMeshSmoother (UnstructuredMesh &mesh)
 
virtual ~LaplaceMeshSmoother ()
 
virtual void smooth () override
 
void smooth (unsigned int n_iterations)
 
void init ()
 
void print_graph (std::ostream &out=libMesh::out) const
 

Protected Attributes

UnstructuredMesh_mesh
 

Private Member Functions

void allgather_graph ()
 

Private Attributes

bool _initialized
 
std::vector< std::vector< dof_id_type > > _graph
 

Detailed Description

This class defines the data structures necessary for Laplace smoothing.

Note
This is a simple averaging smoother, which does not guarantee that points will be smoothed to valid locations, e.g. locations inside the boundary! This aspect could use work.
Author
John W. Peterson
Date
2002-2007

Definition at line 44 of file mesh_smoother_laplace.h.

Constructor & Destructor Documentation

◆ LaplaceMeshSmoother()

libMesh::LaplaceMeshSmoother::LaplaceMeshSmoother ( UnstructuredMesh mesh)
explicit

Constructor. Sets the constant mesh reference in the protected data section of the class.

Definition at line 36 of file mesh_smoother_laplace.C.

37  : MeshSmoother(mesh),
38  _initialized(false)
39 {
40 }
MeshBase & mesh
MeshSmoother(UnstructuredMesh &mesh)
Definition: mesh_smoother.h:46

◆ ~LaplaceMeshSmoother()

virtual libMesh::LaplaceMeshSmoother::~LaplaceMeshSmoother ( )
inlinevirtual

Destructor.

Definition at line 57 of file mesh_smoother_laplace.h.

57 {}

Member Function Documentation

◆ allgather_graph()

void libMesh::LaplaceMeshSmoother::allgather_graph ( )
private

This function allgather's the (local) graph after it is computed on each processor by the init() function.

Definition at line 261 of file mesh_smoother_laplace.C.

References _graph, libMesh::MeshSmoother::_mesh, libMesh::Parallel::Communicator::allgather(), libMesh::ParallelObject::comm(), libMesh::MeshBase::max_node_id(), and libMesh::ParallelObject::n_processors().

Referenced by init().

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 (std::size_t i=0; i<_graph.size(); ++i)
275  {
276  // First push back the number of entries for this node
277  flat_graph.push_back (cast_int<dof_id_type>(_graph[i].size()));
278 
279  // Then push back all the IDs
280  for (std::size_t j=0; j<_graph[i].size(); ++j)
281  flat_graph.push_back(_graph[i][j]);
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 
319  // // Print local graph to uniquely named file (debugging)
320  // {
321  // // Generate unique filename for this processor
322  // std::ostringstream oss;
323  // oss << "graph_filename_" << _mesh.processor_id() << ".txt";
324  // std::ofstream graph_stream(oss.str().c_str());
325  //
326  // // Print the local non-flat graph
327  // std::swap(_graph, copy_of_graph);
328  // print_graph(graph_stream);
329  //
330  // // Print the (local) flat graph for verification
331  // for (std::size_t i=0; i<copy_of_flat_graph.size(); ++i)
332  //graph_stream << copy_of_flat_graph[i] << " ";
333  // graph_stream << "\n";
334  //
335  // // Print the allgather'd grap for verification
336  // for (std::size_t i=0; i<flat_graph.size(); ++i)
337  //graph_stream << flat_graph[i] << " ";
338  // graph_stream << "\n";
339  //
340  // // Print the global non-flat graph
341  // std::swap(_graph, copy_of_graph);
342  // print_graph(graph_stream);
343  // }
344 } // allgather_graph()
std::vector< std::vector< dof_id_type > > _graph
void allgather(const T &send, std::vector< T, A > &recv) const
uint8_t processor_id_type
Definition: id_types.h:99
const Parallel::Communicator & comm() const
UnstructuredMesh & _mesh
Definition: mesh_smoother.h:61
processor_id_type n_processors() const
virtual dof_id_type max_node_id() const =0
uint8_t dof_id_type
Definition: id_types.h:64

◆ init()

void libMesh::LaplaceMeshSmoother::init ( )

Initialization for the Laplace smoothing routine is basically identical to building an "L-graph" which is expensive. It's provided separately from the constructor since you may or may not want to build the L-graph on construction.

Definition at line 153 of file mesh_smoother_laplace.C.

References _graph, _initialized, libMesh::MeshSmoother::_mesh, libMesh::MeshBase::active_local_element_ptr_range(), allgather_graph(), end, libMesh::MeshBase::max_node_id(), libMesh::MeshBase::mesh_dimension(), and side.

Referenced by smooth().

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 (std::size_t i=0; i<_graph.size(); ++i)
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(_graph[i].begin(), _graph[i].end());
239  _graph[i].erase(std::unique(_graph[i].begin(), _graph[i].end()), _graph[i].end());
240  }
241 
242 } // init()
std::vector< std::vector< dof_id_type > > _graph
unsigned short int side
Definition: xdr_io.C:50
IterBase * end
UnstructuredMesh & _mesh
Definition: mesh_smoother.h:61
virtual SimpleRange< element_iterator > active_local_element_ptr_range()=0
unsigned int mesh_dimension() const
Definition: mesh_base.C:126
virtual dof_id_type max_node_id() const =0

◆ print_graph()

void libMesh::LaplaceMeshSmoother::print_graph ( std::ostream &  out = libMesh::out) const

Mainly for debugging, this function will print out the connectivity graph which has been created.

Definition at line 247 of file mesh_smoother_laplace.C.

References _graph, and end.

248 {
249  for (std::size_t i=0; i<_graph.size(); ++i)
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 }
std::vector< std::vector< dof_id_type > > _graph
IterBase * end

◆ smooth() [1/2]

virtual void libMesh::LaplaceMeshSmoother::smooth ( )
inlineoverridevirtual

Redefinition of the smooth function from the base class. All this does is call the smooth function in this class which takes an int, using a default value of 1.

Implements libMesh::MeshSmoother.

Definition at line 65 of file mesh_smoother_laplace.h.

References smooth().

Referenced by smooth(), and libMesh::TriangleInterface::triangulate().

65 { this->smooth(1); }
virtual void smooth() override

◆ smooth() [2/2]

void libMesh::LaplaceMeshSmoother::smooth ( unsigned int  n_iterations)

The actual smoothing function, gets called whenever the user specifies an actual number of smoothing iterations.

Definition at line 45 of file mesh_smoother_laplace.C.

References _graph, _initialized, libMesh::MeshSmoother::_mesh, libMesh::MeshBase::active_element_ptr_range(), libMesh::TypeVector< T >::add(), libMesh::ParallelObject::comm(), libMesh::MeshTools::find_block_boundary_nodes(), libMesh::MeshTools::find_boundary_nodes(), init(), libMesh::MeshBase::local_node_ptr_range(), libMesh::MeshBase::max_node_id(), libMesh::MeshBase::node_ref(), libMesh::MeshBase::nodes_begin(), libMesh::MeshBase::nodes_end(), libMesh::MeshBase::point(), libMesh::ParallelObject::processor_id(), libMesh::Real, and libMesh::Parallel::sync_dofobject_data_by_id().

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 (std::size_t j=0; j<_graph[node->id()].size(); ++j)
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(_graph[node->id()][j]);
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 }
std::vector< std::vector< dof_id_type > > _graph
virtual SimpleRange< node_iterator > local_node_ptr_range()=0
std::unordered_set< dof_id_type > find_block_boundary_nodes(const MeshBase &mesh)
Definition: mesh_tools.C:351
const Parallel::Communicator & comm() const
virtual SimpleRange< element_iterator > active_element_ptr_range()=0
UnstructuredMesh & _mesh
Definition: mesh_smoother.h:61
void add(const TypeVector< T2 > &)
Definition: type_vector.h:603
virtual node_iterator nodes_begin()=0
void sync_dofobject_data_by_id(const Communicator &comm, const Iterator &range_begin, const Iterator &range_end, SyncFunctor &sync)
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
virtual node_iterator nodes_end()=0
virtual const Node & node_ref(const dof_id_type i) const
Definition: mesh_base.h:434
virtual const Point & point(const dof_id_type i) const =0
virtual dof_id_type max_node_id() const =0
processor_id_type processor_id() const
void find_boundary_nodes(const MeshBase &mesh, std::vector< bool > &on_boundary)
Definition: mesh_tools.C:303

Member Data Documentation

◆ _graph

std::vector<std::vector<dof_id_type> > libMesh::LaplaceMeshSmoother::_graph
private

Data structure for holding the L-graph

Definition at line 104 of file mesh_smoother_laplace.h.

Referenced by allgather_graph(), init(), print_graph(), and smooth().

◆ _initialized

bool libMesh::LaplaceMeshSmoother::_initialized
private

True if the L-graph has been created, false otherwise.

Definition at line 99 of file mesh_smoother_laplace.h.

Referenced by init(), and smooth().

◆ _mesh


The documentation for this class was generated from the following files: