$extrastylesheet
00001 // The libMesh Finite Element Library. 00002 // Copyright (C) 2002-2014 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner 00003 00004 // This library is free software; you can redistribute it and/or 00005 // modify it under the terms of the GNU Lesser General Public 00006 // License as published by the Free Software Foundation; either 00007 // version 2.1 of the License, or (at your option) any later version. 00008 00009 // This library is distributed in the hope that it will be useful, 00010 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00012 // Lesser General Public License for more details. 00013 00014 // You should have received a copy of the GNU Lesser General Public 00015 // License along with this library; if not, write to the Free Software 00016 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 00017 00018 00019 00020 // C++ includes 00021 #include <algorithm> // for std::copy, std::sort 00022 00023 00024 // Local includes 00025 #include "libmesh/mesh_smoother_laplace.h" 00026 #include "libmesh/mesh_tools.h" 00027 #include "libmesh/elem.h" 00028 #include "libmesh/unstructured_mesh.h" 00029 #include "libmesh/parallel.h" 00030 #include "libmesh/parallel_ghost_sync.h" // sync_dofobject_data_by_id() 00031 #include "libmesh/parallel_algebra.h" // StandardType<Point> 00032 00033 namespace libMesh 00034 { 00035 // LaplaceMeshSmoother member functions 00036 LaplaceMeshSmoother::LaplaceMeshSmoother(UnstructuredMesh& mesh) 00037 : MeshSmoother(mesh), 00038 _initialized(false) 00039 { 00040 } 00041 00042 00043 00044 00045 void LaplaceMeshSmoother::smooth(unsigned int n_iterations) 00046 { 00047 if (!_initialized) 00048 this->init(); 00049 00050 // Don't smooth the nodes on the boundary... 00051 // this would change the mesh geometry which 00052 // is probably not something we want! 00053 std::vector<bool> on_boundary; 00054 MeshTools::find_boundary_nodes(_mesh, on_boundary); 00055 00056 // Ensure that the find_boundary_nodes() function returned a properly-sized vector 00057 if (on_boundary.size() != _mesh.n_nodes()) 00058 libmesh_error_msg("MeshTools::find_boundary_nodes() returned incorrect length vector!"); 00059 00060 // We can only update the nodes after all new positions were 00061 // determined. We store the new positions here 00062 std::vector<Point> new_positions; 00063 00064 for (unsigned int n=0; n<n_iterations; n++) 00065 { 00066 new_positions.resize(_mesh.n_nodes()); 00067 00068 { 00069 MeshBase::node_iterator it = _mesh.local_nodes_begin(); 00070 const MeshBase::node_iterator it_end = _mesh.local_nodes_end(); 00071 for (; it != it_end; ++it) 00072 { 00073 Node* node = *it; 00074 00075 if (node == NULL) 00076 libmesh_error_msg("[" << _mesh.processor_id() << "]: Node iterator returned NULL pointer."); 00077 00078 // leave the boundary intact 00079 // Only relocate the nodes which are vertices of an element 00080 // All other entries of _graph (the secondary nodes) are empty 00081 if (!on_boundary[node->id()] && (_graph[node->id()].size() > 0)) 00082 { 00083 Point avg_position(0.,0.,0.); 00084 00085 for (unsigned j=0; j<_graph[node->id()].size(); ++j) 00086 { 00087 // Will these nodal positions always be available 00088 // or will they refer to remote nodes? To be 00089 // careful, we grab a pointer and test it against 00090 // NULL. 00091 Node* connected_node = _mesh.node_ptr(_graph[node->id()][j]); 00092 00093 if (connected_node == NULL) 00094 libmesh_error_msg("Error! Libmesh returned NULL pointer for node " << _graph[connected_node->id()][j]); 00095 00096 avg_position.add( *connected_node ); 00097 } // end for(j) 00098 00099 // Compute the average, store in the new_positions vector 00100 new_positions[node->id()] = avg_position / static_cast<Real>(_graph[node->id()].size()); 00101 } // end if 00102 } // end for 00103 } // end scope 00104 00105 00106 // now update the node positions (local node positions only) 00107 { 00108 MeshBase::node_iterator it = _mesh.local_nodes_begin(); 00109 const MeshBase::node_iterator it_end = _mesh.local_nodes_end(); 00110 for (; it != it_end; ++it) 00111 { 00112 Node* node = *it; 00113 00114 if (!on_boundary[node->id()] && (_graph[node->id()].size() > 0)) 00115 { 00116 // Should call Point::op= 00117 // libMesh::out << "Setting node id " << node->id() << " to position " << new_positions[node->id()]; 00118 _mesh.node(node->id()) = new_positions[node->id()]; 00119 } 00120 } // end for 00121 } // end scope 00122 00123 // Now the nodes which are ghosts on this processor may have been moved on 00124 // the processors which own them. So we need to synchronize with our neighbors 00125 // and get the most up-to-date positions for the ghosts. 00126 SyncNodalPositions sync_object(_mesh); 00127 Parallel::sync_dofobject_data_by_id 00128 (_mesh.comm(), _mesh.nodes_begin(), _mesh.nodes_end(), sync_object); 00129 00130 } // end for n_iterations 00131 00132 // finally adjust the second order nodes (those located between vertices) 00133 // these nodes will be located between their adjacent nodes 00134 // do this element-wise 00135 MeshBase::element_iterator el = _mesh.active_elements_begin(); 00136 const MeshBase::element_iterator end = _mesh.active_elements_end(); 00137 00138 for (; el != end; ++el) 00139 { 00140 // Constant handle for the element 00141 const Elem* elem = *el; 00142 00143 // get the second order nodes (son) 00144 // their element indices start at n_vertices and go to n_nodes 00145 const unsigned int son_begin = elem->n_vertices(); 00146 const unsigned int son_end = elem->n_nodes(); 00147 00148 // loop over all second order nodes (son) 00149 for (unsigned int son=son_begin; son<son_end; son++) 00150 { 00151 // Don't smooth second-order nodes which are on the boundary 00152 if (!on_boundary[elem->node(son)]) 00153 { 00154 const unsigned int n_adjacent_vertices = 00155 elem->n_second_order_adjacent_vertices(son); 00156 00157 // calculate the new position which is the average of the 00158 // position of the adjacent vertices 00159 Point avg_position(0,0,0); 00160 for (unsigned int v=0; v<n_adjacent_vertices; v++) 00161 avg_position += 00162 _mesh.point( elem->node( elem->second_order_adjacent_vertex(son,v) ) ); 00163 00164 _mesh.node(elem->node(son)) = avg_position / n_adjacent_vertices; 00165 } 00166 } 00167 } 00168 } 00169 00170 00171 00172 00173 00174 void LaplaceMeshSmoother::init() 00175 { 00176 switch (_mesh.mesh_dimension()) 00177 { 00178 00179 // TODO:[BSK] Fix this to work for refined meshes... I think 00180 // the implementation was done quickly for Damien, who did not have 00181 // refined grids. Fix it here and in the original Mesh member. 00182 00183 case 2: // Stolen directly from build_L_graph in mesh_base.C 00184 { 00185 // Initialize space in the graph. It is n_nodes long. Each 00186 // node may be connected to an arbitrary number of other nodes 00187 // via edges. 00188 _graph.resize(_mesh.n_nodes()); 00189 00190 MeshBase::element_iterator el = _mesh.active_local_elements_begin(); 00191 const MeshBase::element_iterator end = _mesh.active_local_elements_end(); 00192 00193 for (; el != end; ++el) 00194 { 00195 // Constant handle for the element 00196 const Elem* elem = *el; 00197 00198 for (unsigned int s=0; s<elem->n_neighbors(); s++) 00199 { 00200 // Only operate on sides which are on the 00201 // boundary or for which the current element's 00202 // id is greater than its neighbor's. 00203 // Sides get only built once. 00204 if ((elem->neighbor(s) == NULL) || 00205 (elem->id() > elem->neighbor(s)->id())) 00206 { 00207 UniquePtr<Elem> side(elem->build_side(s)); 00208 _graph[side->node(0)].push_back(side->node(1)); 00209 _graph[side->node(1)].push_back(side->node(0)); 00210 } 00211 } 00212 } 00213 _initialized = true; 00214 break; 00215 } // case 2 00216 00217 case 3: // Stolen blatantly from build_L_graph in mesh_base.C 00218 { 00219 // Initialize space in the graph. 00220 _graph.resize(_mesh.n_nodes()); 00221 00222 MeshBase::element_iterator el = _mesh.active_local_elements_begin(); 00223 const MeshBase::element_iterator end = _mesh.active_local_elements_end(); 00224 00225 for (; el != end; ++el) 00226 { 00227 // Shortcut notation for simplicity 00228 const Elem* elem = *el; 00229 00230 for (unsigned int f=0; f<elem->n_neighbors(); f++) // Loop over faces 00231 if ((elem->neighbor(f) == NULL) || 00232 (elem->id() > elem->neighbor(f)->id())) 00233 { 00234 // We need a full (i.e. non-proxy) element for the face, since we will 00235 // be looking at its sides as well! 00236 UniquePtr<Elem> face = elem->build_side(f, /*proxy=*/false); 00237 00238 for (unsigned int s=0; s<face->n_neighbors(); s++) // Loop over face's edges 00239 { 00240 // Here we can use a proxy 00241 UniquePtr<Elem> side = face->build_side(s); 00242 00243 // At this point, we just insert the node numbers 00244 // again. At the end we'll call sort and unique 00245 // to make sure there are no duplicates 00246 _graph[side->node(0)].push_back(side->node(1)); 00247 _graph[side->node(1)].push_back(side->node(0)); 00248 } 00249 } 00250 } 00251 00252 _initialized = true; 00253 break; 00254 } // case 3 00255 00256 default: 00257 libmesh_error_msg("At this time it is not possible to smooth a dimension " << _mesh.mesh_dimension() << "mesh. Aborting..."); 00258 } 00259 00260 // Done building graph from local elements. Let's now allgather the 00261 // graph so that it is available on all processors for the actual 00262 // smoothing operation? 00263 this->allgather_graph(); 00264 00265 // In 3D, it's possible for > 2 processor partitions to meet 00266 // at a single edge, while in 2D only 2 processor partitions 00267 // share an edge. Therefore the allgather'd graph in 3D may 00268 // now have duplicate entries and we need to remove them so 00269 // they don't foul up the averaging algorithm employed by the 00270 // Laplace smoother. 00271 for (unsigned i=0; i<_graph.size(); ++i) 00272 { 00273 // The std::unique algorithm removes duplicate *consecutive* elements from a range, 00274 // so it only makes sense to call it on a sorted range... 00275 std::sort(_graph[i].begin(), _graph[i].end()); 00276 _graph[i].erase(std::unique(_graph[i].begin(), _graph[i].end()), _graph[i].end()); 00277 } 00278 00279 } // init() 00280 00281 00282 00283 00284 void LaplaceMeshSmoother::print_graph(std::ostream& out_stream) const 00285 { 00286 for (unsigned int i=0; i<_graph.size(); ++i) 00287 { 00288 out_stream << i << ": "; 00289 std::copy(_graph[i].begin(), 00290 _graph[i].end(), 00291 std::ostream_iterator<unsigned>(out_stream, " ")); 00292 out_stream << std::endl; 00293 } 00294 } 00295 00296 00297 00298 void LaplaceMeshSmoother::allgather_graph() 00299 { 00300 // The graph data structure is not well-suited for parallel communication, 00301 // so copy the graph into a single vector defined by: 00302 // NA A_0 A_1 ... A_{NA} | NB B_0 B_1 ... B_{NB} | NC C_0 C_1 ... C_{NC} 00303 // where: 00304 // * NA is the number of graph connections for node A 00305 // * A_0, A_1, etc. are the IDs connected to node A 00306 std::vector<dof_id_type> flat_graph; 00307 00308 // Reserve at least enough space for each node to have zero entries 00309 flat_graph.reserve(_graph.size()); 00310 00311 for (std::size_t i=0; i<_graph.size(); ++i) 00312 { 00313 // First push back the number of entries for this node 00314 flat_graph.push_back (cast_int<dof_id_type>(_graph[i].size())); 00315 00316 // Then push back all the IDs 00317 for (std::size_t j=0; j<_graph[i].size(); ++j) 00318 flat_graph.push_back(_graph[i][j]); 00319 } 00320 00321 // // A copy of the flat graph (for printing only, delete me later) 00322 // std::vector<unsigned> copy_of_flat_graph(flat_graph); 00323 00324 // Use the allgather routine to combine all the flat graphs on all processors 00325 _mesh.comm().allgather(flat_graph); 00326 00327 // Now reconstruct _graph from the allgathered flat_graph. 00328 00329 // // (Delete me later, the copy is just for printing purposes.) 00330 // std::vector<std::vector<unsigned > > copy_of_graph(_graph); 00331 00332 // Make sure the old graph is cleared out 00333 _graph.clear(); 00334 _graph.resize(_mesh.n_nodes()); 00335 00336 // Our current position in the allgather'd flat_graph 00337 std::size_t cursor=0; 00338 00339 // There are n_nodes * n_processors vectors to read in total 00340 for (processor_id_type p=0; p<_mesh.n_processors(); ++p) 00341 for (dof_id_type node_ctr=0; node_ctr<_mesh.n_nodes(); ++node_ctr) 00342 { 00343 // Read the number of entries for this node, move cursor 00344 std::size_t n_entries = flat_graph[cursor++]; 00345 00346 // Reserve space for that many more entries, then push back 00347 _graph[node_ctr].reserve(_graph[node_ctr].size() + n_entries); 00348 00349 // Read all graph connections for this node, move the cursor each time 00350 // Note: there might be zero entries but that's fine 00351 for (std::size_t i=0; i<n_entries; ++i) 00352 _graph[node_ctr].push_back(flat_graph[cursor++]); 00353 } 00354 00355 00356 // // Print local graph to uniquely named file (debugging) 00357 // { 00358 // // Generate unique filename for this processor 00359 // std::ostringstream oss; 00360 // oss << "graph_filename_" << _mesh.processor_id() << ".txt"; 00361 // std::ofstream graph_stream(oss.str().c_str()); 00362 // 00363 // // Print the local non-flat graph 00364 // std::swap(_graph, copy_of_graph); 00365 // print_graph(graph_stream); 00366 // 00367 // // Print the (local) flat graph for verification 00368 // for (std::size_t i=0; i<copy_of_flat_graph.size(); ++i) 00369 //graph_stream << copy_of_flat_graph[i] << " "; 00370 // graph_stream << "\n"; 00371 // 00372 // // Print the allgather'd grap for verification 00373 // for (std::size_t i=0; i<flat_graph.size(); ++i) 00374 //graph_stream << flat_graph[i] << " "; 00375 // graph_stream << "\n"; 00376 // 00377 // // Print the global non-flat graph 00378 // std::swap(_graph, copy_of_graph); 00379 // print_graph(graph_stream); 00380 // } 00381 } // allgather_graph() 00382 00383 } // namespace libMesh