$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 00022 // Local Includes ----------------------------------- 00023 #include "libmesh/elem.h" 00024 #include "libmesh/mesh_base.h" 00025 #include "libmesh/parallel.h" 00026 #include "libmesh/partitioner.h" 00027 #include "libmesh/mesh_tools.h" 00028 #include "libmesh/mesh_communication.h" 00029 #include "libmesh/libmesh_logging.h" 00030 00031 //FIXME 00032 #include "libmesh/parallel_mesh.h" 00033 #include "libmesh/mesh_tools.h" 00034 00035 namespace libMesh 00036 { 00037 00038 00039 00040 // ------------------------------------------------------------ 00041 // Partitioner static data 00042 const dof_id_type Partitioner::communication_blocksize = 1000000; 00043 00044 00045 00046 // ------------------------------------------------------------ 00047 // Partitioner implementation 00048 void Partitioner::partition (MeshBase& mesh) 00049 { 00050 this->partition(mesh,mesh.n_processors()); 00051 } 00052 00053 00054 00055 void Partitioner::partition (MeshBase& mesh, 00056 const unsigned int n) 00057 { 00058 libmesh_parallel_only(mesh.comm()); 00059 00060 // BSK - temporary fix while redistribution is integrated 6/26/2008 00061 // Uncomment this to not repartition in parallel 00062 // if (!mesh.is_serial()) 00063 // return; 00064 00065 // we cannot partition into more pieces than we have 00066 // active elements! 00067 const unsigned int n_parts = 00068 static_cast<unsigned int> 00069 (std::min(mesh.n_active_elem(), static_cast<dof_id_type>(n))); 00070 00071 // Set the number of partitions in the mesh 00072 mesh.set_n_partitions()=n_parts; 00073 00074 if (n_parts == 1) 00075 { 00076 this->single_partition (mesh); 00077 return; 00078 } 00079 00080 // First assign a temporary partitioning to any unpartitioned elements 00081 Partitioner::partition_unpartitioned_elements(mesh, n_parts); 00082 00083 // Call the partitioning function 00084 this->_do_partition(mesh,n_parts); 00085 00086 // Set the parent's processor ids 00087 Partitioner::set_parent_processor_ids(mesh); 00088 00089 // Redistribute elements if necessary, before setting node processor 00090 // ids, to make sure those will be set consistently 00091 mesh.redistribute(); 00092 00093 #ifdef DEBUG 00094 MeshTools::libmesh_assert_valid_remote_elems(mesh); 00095 00096 // Messed up elem processor_id()s can leave us without the child 00097 // elements we need to restrict vectors on a distributed mesh 00098 MeshTools::libmesh_assert_valid_procids<Elem>(mesh); 00099 #endif 00100 00101 // Set the node's processor ids 00102 Partitioner::set_node_processor_ids(mesh); 00103 00104 #ifdef DEBUG 00105 MeshTools::libmesh_assert_valid_procids<Elem>(mesh); 00106 #endif 00107 00108 // Give derived Mesh classes a chance to update any cached data to 00109 // reflect the new partitioning 00110 mesh.update_post_partitioning(); 00111 } 00112 00113 00114 00115 void Partitioner::repartition (MeshBase& mesh) 00116 { 00117 this->repartition(mesh,mesh.n_processors()); 00118 } 00119 00120 00121 00122 void Partitioner::repartition (MeshBase& mesh, 00123 const unsigned int n) 00124 { 00125 // we cannot partition into more pieces than we have 00126 // active elements! 00127 const unsigned int n_parts = 00128 static_cast<unsigned int> 00129 (std::min(mesh.n_active_elem(), static_cast<dof_id_type>(n))); 00130 00131 // Set the number of partitions in the mesh 00132 mesh.set_n_partitions()=n_parts; 00133 00134 if (n_parts == 1) 00135 { 00136 this->single_partition (mesh); 00137 return; 00138 } 00139 00140 // First assign a temporary partitioning to any unpartitioned elements 00141 Partitioner::partition_unpartitioned_elements(mesh, n_parts); 00142 00143 // Call the partitioning function 00144 this->_do_repartition(mesh,n_parts); 00145 00146 // Set the parent's processor ids 00147 Partitioner::set_parent_processor_ids(mesh); 00148 00149 // Set the node's processor ids 00150 Partitioner::set_node_processor_ids(mesh); 00151 } 00152 00153 00154 00155 00156 00157 void Partitioner::single_partition (MeshBase& mesh) 00158 { 00159 START_LOG("single_partition()","Partitioner"); 00160 00161 // Loop over all the elements and assign them to processor 0. 00162 MeshBase::element_iterator elem_it = mesh.elements_begin(); 00163 const MeshBase::element_iterator elem_end = mesh.elements_end(); 00164 00165 for ( ; elem_it != elem_end; ++elem_it) 00166 (*elem_it)->processor_id() = 0; 00167 00168 // For a single partition, all the nodes are on processor 0 00169 MeshBase::node_iterator node_it = mesh.nodes_begin(); 00170 const MeshBase::node_iterator node_end = mesh.nodes_end(); 00171 00172 for ( ; node_it != node_end; ++node_it) 00173 (*node_it)->processor_id() = 0; 00174 00175 STOP_LOG("single_partition()","Partitioner"); 00176 } 00177 00178 00179 00180 void Partitioner::partition_unpartitioned_elements (MeshBase &mesh) 00181 { 00182 Partitioner::partition_unpartitioned_elements(mesh, mesh.n_processors()); 00183 } 00184 00185 00186 00187 void Partitioner::partition_unpartitioned_elements (MeshBase &mesh, 00188 const unsigned int n_subdomains) 00189 { 00190 MeshBase::element_iterator it = mesh.unpartitioned_elements_begin(); 00191 const MeshBase::element_iterator end = mesh.unpartitioned_elements_end(); 00192 00193 const dof_id_type n_unpartitioned_elements = MeshTools::n_elem (it, end); 00194 00195 // the unpartitioned elements must exist on all processors. If the range is empty on one 00196 // it is empty on all, and we can quit right here. 00197 if (!n_unpartitioned_elements) return; 00198 00199 // find the target subdomain sizes 00200 std::vector<dof_id_type> subdomain_bounds(mesh.n_processors()); 00201 00202 for (processor_id_type pid=0; pid<mesh.n_processors(); pid++) 00203 { 00204 dof_id_type tgt_subdomain_size = 0; 00205 00206 // watch out for the case that n_subdomains < n_processors 00207 if (pid < n_subdomains) 00208 { 00209 tgt_subdomain_size = n_unpartitioned_elements/n_subdomains; 00210 00211 if (pid < n_unpartitioned_elements%n_subdomains) 00212 tgt_subdomain_size++; 00213 00214 } 00215 00216 //libMesh::out << "pid, #= " << pid << ", " << tgt_subdomain_size << std::endl; 00217 if (pid == 0) 00218 subdomain_bounds[0] = tgt_subdomain_size; 00219 else 00220 subdomain_bounds[pid] = subdomain_bounds[pid-1] + tgt_subdomain_size; 00221 } 00222 00223 libmesh_assert_equal_to (subdomain_bounds.back(), n_unpartitioned_elements); 00224 00225 // create the unique mapping for all unpartitioned elements independent of partitioning 00226 // determine the global indexing for all the unpartitoned elements 00227 std::vector<dof_id_type> global_indices; 00228 00229 // Calling this on all processors a unique range in [0,n_unpartitioned_elements) is constructed. 00230 // Only the indices for the elements we pass in are returned in the array. 00231 MeshCommunication().find_global_indices (mesh.comm(), 00232 MeshTools::bounding_box(mesh), it, end, 00233 global_indices); 00234 00235 for (dof_id_type cnt=0; it != end; ++it) 00236 { 00237 Elem *elem = *it; 00238 00239 libmesh_assert_less (cnt, global_indices.size()); 00240 const dof_id_type global_index = 00241 global_indices[cnt++]; 00242 00243 libmesh_assert_less (global_index, subdomain_bounds.back()); 00244 libmesh_assert_less (global_index, n_unpartitioned_elements); 00245 00246 const processor_id_type subdomain_id = 00247 cast_int<processor_id_type> 00248 (std::distance(subdomain_bounds.begin(), 00249 std::upper_bound(subdomain_bounds.begin(), 00250 subdomain_bounds.end(), 00251 global_index))); 00252 libmesh_assert_less (subdomain_id, n_subdomains); 00253 00254 elem->processor_id() = subdomain_id; 00255 //libMesh::out << "assigning " << global_index << " to " << subdomain_id << std::endl; 00256 } 00257 } 00258 00259 00260 00261 void Partitioner::set_parent_processor_ids(MeshBase& 00262 #ifdef LIBMESH_ENABLE_AMR 00263 mesh 00264 #endif 00265 ) 00266 { 00267 START_LOG("set_parent_processor_ids()","Partitioner"); 00268 00269 #ifdef LIBMESH_ENABLE_AMR 00270 00271 // If the mesh is serial we have access to all the elements, 00272 // in particular all the active ones. We can therefore set 00273 // the parent processor ids indirecly through their children, and 00274 // set the subactive processor ids while examining their active 00275 // ancestors. 00276 // By convention a parent is assigned to the minimum processor 00277 // of all its children, and a subactive is assigned to the processor 00278 // of its active ancestor. 00279 if (mesh.is_serial()) 00280 { 00281 // Loop over all the active elements in the mesh 00282 MeshBase::element_iterator it = mesh.active_elements_begin(); 00283 const MeshBase::element_iterator end = mesh.active_elements_end(); 00284 00285 for ( ; it!=end; ++it) 00286 { 00287 Elem *child = *it; 00288 00289 // First set descendents 00290 00291 std::vector<const Elem*> subactive_family; 00292 child->total_family_tree(subactive_family); 00293 for (unsigned int i = 0; i != subactive_family.size(); ++i) 00294 const_cast<Elem*>(subactive_family[i])->processor_id() = child->processor_id(); 00295 00296 // Then set ancestors 00297 00298 Elem *parent = child->parent(); 00299 00300 while (parent) 00301 { 00302 // invalidate the parent id, otherwise the min below 00303 // will not work if the current parent id is less 00304 // than all the children! 00305 parent->invalidate_processor_id(); 00306 00307 for(unsigned int c=0; c<parent->n_children(); c++) 00308 { 00309 child = parent->child(c); 00310 libmesh_assert(child); 00311 libmesh_assert(!child->is_remote()); 00312 libmesh_assert_not_equal_to (child->processor_id(), DofObject::invalid_processor_id); 00313 parent->processor_id() = std::min(parent->processor_id(), 00314 child->processor_id()); 00315 } 00316 parent = parent->parent(); 00317 } 00318 } 00319 } 00320 00321 // When the mesh is parallel we cannot guarantee that parents have access to 00322 // all their children. 00323 else 00324 { 00325 // Setting subactive processor ids is easy: we can guarantee 00326 // that children have access to all their parents. 00327 00328 // Loop over all the active elements in the mesh 00329 MeshBase::element_iterator it = mesh.active_elements_begin(); 00330 const MeshBase::element_iterator end = mesh.active_elements_end(); 00331 00332 for ( ; it!=end; ++it) 00333 { 00334 Elem *child = *it; 00335 00336 std::vector<const Elem*> subactive_family; 00337 child->total_family_tree(subactive_family); 00338 for (unsigned int i = 0; i != subactive_family.size(); ++i) 00339 const_cast<Elem*>(subactive_family[i])->processor_id() = child->processor_id(); 00340 } 00341 00342 // When the mesh is parallel we cannot guarantee that parents have access to 00343 // all their children. 00344 00345 // We will use a brute-force approach here. Each processor finds its parent 00346 // elements and sets the parent pid to the minimum of its 00347 // semilocal descendants. 00348 // A global reduction is then performed to make sure the true minimum is found. 00349 // As noted, this is required because we cannot guarantee that a parent has 00350 // access to all its children on any single processor. 00351 libmesh_parallel_only(mesh.comm()); 00352 libmesh_assert(MeshTools::n_elem(mesh.unpartitioned_elements_begin(), 00353 mesh.unpartitioned_elements_end()) == 0); 00354 00355 const dof_id_type max_elem_id = mesh.max_elem_id(); 00356 00357 std::vector<processor_id_type> 00358 parent_processor_ids (std::min(communication_blocksize, 00359 max_elem_id)); 00360 00361 for (dof_id_type blk=0, last_elem_id=0; last_elem_id<max_elem_id; blk++) 00362 { 00363 last_elem_id = 00364 std::min(static_cast<dof_id_type>((blk+1)*communication_blocksize), 00365 max_elem_id); 00366 const dof_id_type first_elem_id = blk*communication_blocksize; 00367 00368 std::fill (parent_processor_ids.begin(), 00369 parent_processor_ids.end(), 00370 DofObject::invalid_processor_id); 00371 00372 // first build up local contributions to parent_processor_ids 00373 MeshBase::element_iterator not_it = mesh.ancestor_elements_begin(); 00374 const MeshBase::element_iterator not_end = mesh.ancestor_elements_end(); 00375 00376 bool have_parent_in_block = false; 00377 00378 for ( ; not_it != not_end; ++not_it) 00379 { 00380 Elem *parent = *not_it; 00381 00382 const dof_id_type parent_idx = parent->id(); 00383 libmesh_assert_less (parent_idx, max_elem_id); 00384 00385 if ((parent_idx >= first_elem_id) && 00386 (parent_idx < last_elem_id)) 00387 { 00388 have_parent_in_block = true; 00389 processor_id_type parent_pid = DofObject::invalid_processor_id; 00390 00391 std::vector<const Elem*> active_family; 00392 parent->active_family_tree(active_family); 00393 for (unsigned int i = 0; i != active_family.size(); ++i) 00394 parent_pid = std::min (parent_pid, active_family[i]->processor_id()); 00395 00396 const dof_id_type packed_idx = parent_idx - first_elem_id; 00397 libmesh_assert_less (packed_idx, parent_processor_ids.size()); 00398 00399 parent_processor_ids[packed_idx] = parent_pid; 00400 } 00401 } 00402 00403 // then find the global minimum 00404 mesh.comm().min (parent_processor_ids); 00405 00406 // and assign the ids, if we have a parent in this block. 00407 if (have_parent_in_block) 00408 for (not_it = mesh.ancestor_elements_begin(); 00409 not_it != not_end; ++not_it) 00410 { 00411 Elem *parent = *not_it; 00412 00413 const dof_id_type parent_idx = parent->id(); 00414 00415 if ((parent_idx >= first_elem_id) && 00416 (parent_idx < last_elem_id)) 00417 { 00418 const dof_id_type packed_idx = parent_idx - first_elem_id; 00419 libmesh_assert_less (packed_idx, parent_processor_ids.size()); 00420 00421 const processor_id_type parent_pid = 00422 parent_processor_ids[packed_idx]; 00423 00424 libmesh_assert_not_equal_to (parent_pid, DofObject::invalid_processor_id); 00425 00426 parent->processor_id() = parent_pid; 00427 } 00428 } 00429 } 00430 } 00431 00432 #endif // LIBMESH_ENABLE_AMR 00433 00434 STOP_LOG("set_parent_processor_ids()","Partitioner"); 00435 } 00436 00437 00438 00439 void Partitioner::set_node_processor_ids(MeshBase& mesh) 00440 { 00441 START_LOG("set_node_processor_ids()","Partitioner"); 00442 00443 // This function must be run on all processors at once 00444 libmesh_parallel_only(mesh.comm()); 00445 00446 // If we have any unpartitioned elements at this 00447 // stage there is a problem 00448 libmesh_assert (MeshTools::n_elem(mesh.unpartitioned_elements_begin(), 00449 mesh.unpartitioned_elements_end()) == 0); 00450 00451 00452 // const dof_id_type orig_n_local_nodes = mesh.n_local_nodes(); 00453 00454 // libMesh::err << "[" << mesh.processor_id() << "]: orig_n_local_nodes=" 00455 // << orig_n_local_nodes << std::endl; 00456 00457 // Build up request sets. Each node is currently owned by a processor because 00458 // it is connected to an element owned by that processor. However, during the 00459 // repartitioning phase that element may have been assigned a new processor id, but 00460 // it is still resident on the original processor. We need to know where to look 00461 // for new ids before assigning new ids, otherwise we may be asking the wrong processors 00462 // for the wrong information. 00463 // 00464 // The only remaining issue is what to do with unpartitioned nodes. Since they are required 00465 // to live on all processors we can simply rely on ourselves to number them properly. 00466 std::vector<std::vector<dof_id_type> > 00467 requested_node_ids(mesh.n_processors()); 00468 00469 // Loop over all the nodes, count the ones on each processor. We can skip ourself 00470 std::vector<dof_id_type> ghost_nodes_from_proc(mesh.n_processors(), 0); 00471 00472 MeshBase::node_iterator node_it = mesh.nodes_begin(); 00473 const MeshBase::node_iterator node_end = mesh.nodes_end(); 00474 00475 for (; node_it != node_end; ++node_it) 00476 { 00477 Node *node = *node_it; 00478 libmesh_assert(node); 00479 const processor_id_type current_pid = node->processor_id(); 00480 if (current_pid != mesh.processor_id() && 00481 current_pid != DofObject::invalid_processor_id) 00482 { 00483 libmesh_assert_less (current_pid, ghost_nodes_from_proc.size()); 00484 ghost_nodes_from_proc[current_pid]++; 00485 } 00486 } 00487 00488 // We know how many objects live on each processor, so reserve() 00489 // space for each. 00490 for (processor_id_type pid=0; pid != mesh.n_processors(); ++pid) 00491 requested_node_ids[pid].reserve(ghost_nodes_from_proc[pid]); 00492 00493 // We need to get the new pid for each node from the processor 00494 // which *currently* owns the node. We can safely skip ourself 00495 for (node_it = mesh.nodes_begin(); node_it != node_end; ++node_it) 00496 { 00497 Node *node = *node_it; 00498 libmesh_assert(node); 00499 const processor_id_type current_pid = node->processor_id(); 00500 if (current_pid != mesh.processor_id() && 00501 current_pid != DofObject::invalid_processor_id) 00502 { 00503 libmesh_assert_less (current_pid, requested_node_ids.size()); 00504 libmesh_assert_less (requested_node_ids[current_pid].size(), 00505 ghost_nodes_from_proc[current_pid]); 00506 requested_node_ids[current_pid].push_back(node->id()); 00507 } 00508 00509 // Unset any previously-set node processor ids 00510 node->invalidate_processor_id(); 00511 } 00512 00513 // Loop over all the active elements 00514 MeshBase::element_iterator elem_it = mesh.active_elements_begin(); 00515 const MeshBase::element_iterator elem_end = mesh.active_elements_end(); 00516 00517 for ( ; elem_it != elem_end; ++elem_it) 00518 { 00519 Elem* elem = *elem_it; 00520 libmesh_assert(elem); 00521 00522 libmesh_assert_not_equal_to (elem->processor_id(), DofObject::invalid_processor_id); 00523 00524 // For each node, set the processor ID to the min of 00525 // its current value and this Element's processor id. 00526 // 00527 // TODO: we would probably get better parallel partitioning if 00528 // we did something like "min for even numbered nodes, max for 00529 // odd numbered". We'd need to be careful about how that would 00530 // affect solution ordering for I/O, though. 00531 for (unsigned int n=0; n<elem->n_nodes(); ++n) 00532 elem->get_node(n)->processor_id() = std::min(elem->get_node(n)->processor_id(), 00533 elem->processor_id()); 00534 } 00535 00536 // And loop over the subactive elements, but don't reassign 00537 // nodes that are already active on another processor. 00538 MeshBase::element_iterator sub_it = mesh.subactive_elements_begin(); 00539 const MeshBase::element_iterator sub_end = mesh.subactive_elements_end(); 00540 00541 for ( ; sub_it != sub_end; ++sub_it) 00542 { 00543 Elem* elem = *sub_it; 00544 libmesh_assert(elem); 00545 00546 libmesh_assert_not_equal_to (elem->processor_id(), DofObject::invalid_processor_id); 00547 00548 for (unsigned int n=0; n<elem->n_nodes(); ++n) 00549 if (elem->get_node(n)->processor_id() == DofObject::invalid_processor_id) 00550 elem->get_node(n)->processor_id() = elem->processor_id(); 00551 } 00552 00553 // Same for the inactive elements -- we will have already gotten most of these 00554 // nodes, *except* for the case of a parent with a subset of children which are 00555 // ghost elements. In that case some of the parent nodes will not have been 00556 // properly handled yet 00557 MeshBase::element_iterator not_it = mesh.not_active_elements_begin(); 00558 const MeshBase::element_iterator not_end = mesh.not_active_elements_end(); 00559 00560 for ( ; not_it != not_end; ++not_it) 00561 { 00562 Elem* elem = *not_it; 00563 libmesh_assert(elem); 00564 00565 libmesh_assert_not_equal_to (elem->processor_id(), DofObject::invalid_processor_id); 00566 00567 for (unsigned int n=0; n<elem->n_nodes(); ++n) 00568 if (elem->get_node(n)->processor_id() == DofObject::invalid_processor_id) 00569 elem->get_node(n)->processor_id() = elem->processor_id(); 00570 } 00571 00572 // We can't assert that all nodes are connected to elements, because 00573 // a ParallelMesh with NodeConstraints might have pulled in some 00574 // remote nodes solely for evaluating those constraints. 00575 // MeshTools::libmesh_assert_connected_nodes(mesh); 00576 00577 // For such nodes, we'll do a sanity check later when making sure 00578 // that we successfully reset their processor ids to something 00579 // valid. 00580 00581 // Next set node ids from other processors, excluding self 00582 for (processor_id_type p=1; p != mesh.n_processors(); ++p) 00583 { 00584 // Trade my requests with processor procup and procdown 00585 processor_id_type procup = cast_int<processor_id_type> 00586 ((mesh.processor_id() + p) % mesh.n_processors()); 00587 processor_id_type procdown = cast_int<processor_id_type> 00588 ((mesh.n_processors() + mesh.processor_id() - p) % 00589 mesh.n_processors()); 00590 std::vector<dof_id_type> request_to_fill; 00591 mesh.comm().send_receive(procup, requested_node_ids[procup], 00592 procdown, request_to_fill); 00593 00594 // Fill those requests in-place 00595 for (std::size_t i=0; i != request_to_fill.size(); ++i) 00596 { 00597 Node *node = mesh.node_ptr(request_to_fill[i]); 00598 libmesh_assert(node); 00599 const processor_id_type new_pid = node->processor_id(); 00600 00601 // We may have an invalid processor_id() on nodes that have been 00602 // "detatched" from coarsened-away elements but that have not yet 00603 // themselves been removed. 00604 // libmesh_assert_not_equal_to (new_pid, DofObject::invalid_processor_id); 00605 // libmesh_assert_less (new_pid, mesh.n_partitions()); // this is the correct test -- 00606 00607 request_to_fill[i] = new_pid; // the number of partitions may 00608 } // not equal the number of processors 00609 00610 // Trade back the results 00611 std::vector<dof_id_type> filled_request; 00612 mesh.comm().send_receive(procdown, request_to_fill, 00613 procup, filled_request); 00614 libmesh_assert_equal_to (filled_request.size(), requested_node_ids[procup].size()); 00615 00616 // And copy the id changes we've now been informed of 00617 for (std::size_t i=0; i != filled_request.size(); ++i) 00618 { 00619 Node *node = mesh.node_ptr(requested_node_ids[procup][i]); 00620 libmesh_assert(node); 00621 00622 // this is the correct test -- the number of partitions may 00623 // not equal the number of processors 00624 00625 // But: we may have an invalid processor_id() on nodes that 00626 // have been "detatched" from coarsened-away elements but 00627 // that have not yet themselves been removed. 00628 // libmesh_assert_less (filled_request[i], mesh.n_partitions()); 00629 00630 node->processor_id(cast_int<processor_id_type>(filled_request[i])); 00631 } 00632 } 00633 00634 #ifdef DEBUG 00635 MeshTools::libmesh_assert_valid_procids<Node>(mesh); 00636 #endif 00637 00638 STOP_LOG("set_node_processor_ids()","Partitioner"); 00639 } 00640 00641 } // namespace libMesh