libMesh
Loading...
Searching...
No Matches
Public Member Functions | Static Public Member Functions | Protected Member Functions | Protected Attributes | Static Protected Attributes | Private Member Functions | Private Attributes | List of all members
libMesh::ParmetisPartitioner Class Reference

The ParmetisPartitioner uses the Parmetis graph partitioner to partition the elements. More...

#include <parmetis_partitioner.h>

Inheritance diagram for libMesh::ParmetisPartitioner:
[legend]

Public Member Functions

 ParmetisPartitioner ()
 Default and copy ctors.
 
 ParmetisPartitioner (const ParmetisPartitioner &other)
 
ParmetisPartitioner & operator= (const ParmetisPartitioner &)=delete
 This class contains a unique_ptr member, so it can't be default copy assigned.
 
 ParmetisPartitioner (ParmetisPartitioner &&)=default
 Move ctor, move assignment operator, and destructor are all explicitly inline-defaulted for this class.
 
ParmetisPartitioner & operator= (ParmetisPartitioner &&)=default
 
virtual ~ParmetisPartitioner ()
 The destructor is out-of-line-defaulted to play nice with forward declarations.
 
virtual PartitionerType type () const override
 
virtual std::unique_ptr< Partitioner > clone () const override
 
virtual bool partitions_from_scratch () const override
 ParMETIS is called through ParMETIS_V3_AdaptiveRepart, which starts from the partitioning the mesh already carries.
 
virtual void partition (MeshBase &mesh, const unsigned int n)
 Partitions the MeshBase into n parts by setting processor_id() on Nodes and Elems.
 
virtual void partition (MeshBase &mesh)
 Partitions the MeshBase into mesh.n_processors() by setting processor_id() on Nodes and Elems.
 
virtual void partition_range (MeshBase &, MeshBase::element_iterator, MeshBase::element_iterator, const unsigned int)
 Partitions elements in the range (it, end) into n parts.
 
void repartition (MeshBase &mesh, const unsigned int n)
 Repartitions the MeshBase into n parts.
 
void repartition (MeshBase &mesh)
 Repartitions the MeshBase into mesh.n_processors() parts.
 
virtual void attach_weights (ErrorVector *)
 Attach weights that can be used for partitioning.
 

Static Public Member Functions

static std::unique_ptr< Partitioner > build (const PartitionerType solver_package)
 Builds a Partitioner of the type specified by partitioner_type.
 
static void partition_unpartitioned_elements (MeshBase &mesh)
 These functions assign processor IDs to newly-created elements (in parallel) which are currently assigned to processor 0.
 
static void partition_unpartitioned_elements (MeshBase &mesh, const unsigned int n)
 
static void partition_unpartitioned_elements (MeshBase &mesh, const unsigned int n, const bool spatial_indexing)
 As above, but spatial_indexing selects how the elements are mapped onto the partitions.
 
static void set_parent_processor_ids (MeshBase &mesh)
 This function is called after partitioning to set the processor IDs for the inactive parent elements.
 
static void set_node_processor_ids (MeshBase &mesh)
 This function is called after partitioning to set the processor IDs for the nodes.
 
static void processor_pairs_to_interface_nodes (MeshBase &mesh, std::map< std::pair< processor_id_type, processor_id_type >, std::set< dof_id_type > > &processor_pair_to_nodes)
 On the partitioning interface, a surface is shared by two and only two processors.
 
static void set_interface_node_processor_ids_linear (MeshBase &mesh)
 Nodes on the partitioning interface is linearly assigned to each pair of processors.
 
static void set_interface_node_processor_ids_BFS (MeshBase &mesh)
 Nodes on the partitioning interface is clustered into two groups BFS (Breadth First Search)scheme for per pair of processors.
 
static void set_interface_node_processor_ids_petscpartitioner (MeshBase &mesh)
 Nodes on the partitioning interface is partitioned into two groups using a PETSc partitioner for each pair of processors.
 

Protected Member Functions

virtual void _do_repartition (MeshBase &mesh, const unsigned int n) override
 Parmetis can handle dynamically repartitioning a mesh such that the redistribution costs are minimized.
 
virtual void _do_partition (MeshBase &mesh, const unsigned int n) override
 Partition the MeshBase into n subdomains.
 
virtual void build_graph (const MeshBase &mesh) override
 Build the graph.
 
bool single_partition (MeshBase &mesh)
 Trivially "partitions" the mesh for one processor.
 
bool single_partition_range (MeshBase::element_iterator it, MeshBase::element_iterator end)
 Slightly generalized version of single_partition which acts on a range of elements defined by the pair of iterators (it, end).
 
virtual void _find_global_index_by_pid_map (const MeshBase &mesh)
 Construct contiguous global indices for the current partitioning.
 
void assign_partitioning (MeshBase &mesh, const std::vector< dof_id_type > &parts)
 Assign the computed partitioning to the mesh.
 

Protected Attributes

ErrorVector * _weights
 The weights that might be used for partitioning.
 
std::unordered_map< dof_id_type, dof_id_type > _global_index_by_pid_map
 Maps active element ids into a contiguous range, as needed by parallel partitioner.
 
std::vector< dof_id_type > _n_active_elem_on_proc
 The number of active elements on each processor.
 
std::vector< std::vector< dof_id_type > > _dual_graph
 A dual graph corresponds to the mesh, and it is typically used in paritioner.
 
std::vector< Elem * > _local_id_to_elem
 

Static Protected Attributes

static const dof_id_type communication_blocksize
 The blocksize to use when doing blocked parallel communication.
 

Private Member Functions

void initialize (const MeshBase &mesh, const unsigned int n_sbdmns)
 Initialize data structures.
 

Private Attributes

std::unique_ptr< ParmetisHelper > _pmetis
 Pointer to the Parmetis-specific data structures.
 

Detailed Description

The ParmetisPartitioner uses the Parmetis graph partitioner to partition the elements.

Author
Benjamin S. Kirk
Date
2003

Partitioner which provides an interface to ParMETIS.

Definition at line 47 of file parmetis_partitioner.h.

Constructor & Destructor Documentation

◆ ParmetisPartitioner() [1/3]

libMesh::ParmetisPartitioner::ParmetisPartitioner ( )

Default and copy ctors.

◆ ParmetisPartitioner() [2/3]

libMesh::ParmetisPartitioner::ParmetisPartitioner ( const ParmetisPartitioner &  other)

◆ ParmetisPartitioner() [3/3]

libMesh::ParmetisPartitioner::ParmetisPartitioner ( ParmetisPartitioner &&  )
default

Move ctor, move assignment operator, and destructor are all explicitly inline-defaulted for this class.

◆ ~ParmetisPartitioner()

virtual libMesh::ParmetisPartitioner::~ParmetisPartitioner ( )
virtual

The destructor is out-of-line-defaulted to play nice with forward declarations.

Member Function Documentation

◆ _do_partition()

void libMesh::ParmetisPartitioner::_do_partition ( MeshBase &  mesh,
const unsigned int  n 
)
overrideprotectedvirtual

Partition the MeshBase into n subdomains.

Implements libMesh::Partitioner.

Definition at line 105 of file parmetis_partitioner.C.

107{
108 this->_do_repartition (mesh, n_sbdmns);
109}
virtual void _do_repartition(MeshBase &mesh, const unsigned int n) override
Parmetis can handle dynamically repartitioning a mesh such that the redistribution costs are minimize...
MeshBase & mesh

References mesh.

◆ _do_repartition()

void libMesh::ParmetisPartitioner::_do_repartition ( MeshBase &  mesh,
const unsigned int  n 
)
overrideprotectedvirtual

Parmetis can handle dynamically repartitioning a mesh such that the redistribution costs are minimized.

This method takes a previously partitioned mesh (which may have then been adaptively refined) and repartitions it.

Reimplemented from libMesh::Partitioner.

Definition at line 113 of file parmetis_partitioner.C.

115{
116 // This function must be run on all processors at once
117 libmesh_parallel_only(mesh.comm());
118
119 // Check for easy returns
120 if (!mesh.n_elem())
121 return;
122
123 if (n_sbdmns == 1)
124 {
125 this->single_partition(mesh);
126 return;
127 }
128
129 libmesh_assert_greater (n_sbdmns, 0);
130
131 // What to do if the Parmetis library IS NOT present
132#ifndef LIBMESH_HAVE_PARMETIS
133
134 libmesh_do_once(
135 libMesh::out << "ERROR: The library has been built without" << std::endl
136 << "Parmetis support. Using a Metis" << std::endl
137 << "partitioner instead!" << std::endl;);
138
139 MetisPartitioner mp;
140
141 // Metis and other fallbacks only work in serial, and need to get
142 // handed element ranges from an already-serialized mesh.
143 mesh.allgather();
144
145 // Don't just call partition() here; that would end up calling
146 // post-element-partitioning work redundantly (and at the moment
147 // incorrectly)
148 mp.partition_range (mesh, mesh.active_elements_begin(),
149 mesh.active_elements_end(), n_sbdmns);
150
151 // What to do if the Parmetis library IS present
152#else
153
154 // Revert to METIS on one processor.
155 if (mesh.n_processors() == 1)
156 {
157 // Make sure the mesh knows it's serial
158 mesh.allgather();
159
160 MetisPartitioner mp;
161 // Don't just call partition() here; that would end up calling
162 // post-element-partitioning work redundantly (and at the moment
163 // incorrectly)
164 mp.partition_range (mesh, mesh.active_elements_begin(),
165 mesh.active_elements_end(), n_sbdmns);
166 return;
167 }
168
169 LOG_SCOPE("repartition()", "ParmetisPartitioner");
170
171 // Initialize the data structures required by ParMETIS
172 this->initialize (mesh, n_sbdmns);
173
174 // build the graph corresponding to the mesh
175 this->build_graph (mesh);
176
177 // Make sure all processors have enough active local elements and
178 // enough connectivity among them.
179 // Parmetis tends to die when it's given only a couple elements
180 // per partition or when it can't reach elements from each other.
181 {
182 bool ready_for_parmetis = true;
183 for (const auto & nelem : _n_active_elem_on_proc)
184 if (nelem < MIN_ELEM_PER_PROC)
185 ready_for_parmetis = false;
186
187 std::size_t my_adjacency = _pmetis->adjncy.size();
188 mesh.comm().min(my_adjacency);
189 if (!my_adjacency)
190 ready_for_parmetis = false;
191
192 // Parmetis will not work unless each processor has some
193 // elements. Specifically, it will abort when passed a nullptr
194 // partition or adjacency array on *any* of the processors.
195 if (!ready_for_parmetis)
196 {
197 // FIXME: revert to METIS, although this requires a serial mesh
198 MeshSerializer serialize(mesh);
199 MetisPartitioner mp;
200 mp.partition (mesh, n_sbdmns);
201 return;
202 }
203 }
204
205
206 // Partition the graph
207 std::vector<Parmetis::idx_t> vsize(_pmetis->vwgt.size(), 1);
208 Parmetis::real_t itr = 1000000.0;
209 MPI_Comm mpi_comm = mesh.comm().get();
210
211 // Call the ParMETIS adaptive repartitioning method. This respects the
212 // original partitioning when computing the new partitioning so as to
213 // minimize the required data redistribution.
214 Parmetis::ParMETIS_V3_AdaptiveRepart(_pmetis->vtxdist.empty() ? nullptr : _pmetis->vtxdist.data(),
215 _pmetis->xadj.empty() ? nullptr : _pmetis->xadj.data(),
216 _pmetis->adjncy.empty() ? nullptr : _pmetis->adjncy.data(),
217 _pmetis->vwgt.empty() ? nullptr : _pmetis->vwgt.data(),
218 vsize.empty() ? nullptr : vsize.data(),
219 nullptr,
220 &_pmetis->wgtflag,
221 &_pmetis->numflag,
222 &_pmetis->ncon,
223 &_pmetis->nparts,
224 _pmetis->tpwgts.empty() ? nullptr : _pmetis->tpwgts.data(),
225 _pmetis->ubvec.empty() ? nullptr : _pmetis->ubvec.data(),
226 &itr,
227 _pmetis->options.data(),
228 &_pmetis->edgecut,
229 _pmetis->part.empty() ? nullptr : reinterpret_cast<Parmetis::idx_t *>(_pmetis->part.data()),
230 &mpi_comm);
231
232 // Assign the returned processor ids
233 this->assign_partitioning (mesh, _pmetis->part);
234
235#endif // #ifndef LIBMESH_HAVE_PARMETIS ... else ...
236
237}
std::unique_ptr< ParmetisHelper > _pmetis
Pointer to the Parmetis-specific data structures.
virtual void build_graph(const MeshBase &mesh) override
Build the graph.
void initialize(const MeshBase &mesh, const unsigned int n_sbdmns)
Initialize data structures.
bool single_partition(MeshBase &mesh)
Trivially "partitions" the mesh for one processor.
std::vector< dof_id_type > _n_active_elem_on_proc
The number of active elements on each processor.
void assign_partitioning(MeshBase &mesh, const std::vector< dof_id_type > &parts)
Assign the computed partitioning to the mesh.
OStreamProxy out
const unsigned int MIN_ELEM_PER_PROC

References initialize(), mesh, libMesh::MIN_ELEM_PER_PROC, libMesh::out, libMesh::Partitioner::partition(), and libMesh::MetisPartitioner::partition_range().

◆ _find_global_index_by_pid_map()

void libMesh::Partitioner::_find_global_index_by_pid_map ( const MeshBase &  mesh)
protectedvirtualinherited

Construct contiguous global indices for the current partitioning.

The global indices are ordered part-by-part

Definition at line 1092 of file partitioner.C.

1093{
1094 const dof_id_type n_active_local_elem = mesh.n_active_local_elem();
1095
1096 // Find the number of active elements on each processor. We cannot use
1097 // mesh.n_active_elem_on_proc(pid) since that only returns the number of
1098 // elements assigned to pid which are currently stored on the calling
1099 // processor. This will not in general be correct for parallel meshes
1100 // when (pid!=mesh.processor_id()).
1101 auto n_proc = mesh.n_processors();
1102 _n_active_elem_on_proc.resize(n_proc);
1103 mesh.comm().allgather(n_active_local_elem, _n_active_elem_on_proc);
1104
1105 std::vector<dof_id_type> n_active_elem_before_proc(mesh.n_processors());
1106
1107 for (auto i : make_range(n_proc-1))
1108 n_active_elem_before_proc[i+1] =
1109 n_active_elem_before_proc[i] + _n_active_elem_on_proc[i];
1110
1113
1115
1116 // create the mapping which is contiguous by processor
1118 mesh.active_local_elements_begin(),
1119 mesh.active_local_elements_end(),
1121
1123
1125 (mesh.comm(), mesh.active_elements_begin(), mesh.active_elements_end(), sync);
1126
1127 for (const auto & elem : mesh.active_element_ptr_range())
1128 {
1129 const processor_id_type pid = elem->processor_id();
1130 libmesh_assert_less (_global_index_by_pid_map[elem->id()], _n_active_elem_on_proc[pid]);
1131
1132 _global_index_by_pid_map[elem->id()] += n_active_elem_before_proc[pid];
1133 }
1134}
void allgather(const T &send_data, std::vector< T, A > &recv_data) const
Defines a Cartesian bounding box by the two corner extremum.
dof_id_type n_active_local_elem() const
Definition mesh_base.h:711
This is the MeshCommunication class.
void find_local_indices(const libMesh::BoundingBox &, const ForwardIterator &, const ForwardIterator &, std::unordered_map< dof_id_type, dof_id_type > &) const
This method determines a locally unique, contiguous index for each object in the input range.
const Parallel::Communicator & comm() const
processor_id_type n_processors() const
std::unordered_map< dof_id_type, dof_id_type > _global_index_by_pid_map
Maps active element ids into a contiguous range, as needed by parallel partitioner.
libMesh::BoundingBox create_bounding_box(const MeshBase &mesh)
Definition mesh_tools.C:566
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.
uint8_t dof_id_type
Definition id_types.h:67
uint8_t processor_id_type
Definition id_types.h:104
IntRange< T > make_range(T beg, T end)
The 2-parameter make_range() helper function returns an IntRange<T> when both input parameters are of...
Definition int_range.h:176

References libMesh::Partitioner::_global_index_by_pid_map, libMesh::Partitioner::_n_active_elem_on_proc, libMesh::Parallel::Communicator::allgather(), libMesh::ParallelObject::comm(), libMesh::MeshTools::create_bounding_box(), libMesh::MeshCommunication::find_local_indices(), libMesh::make_range(), mesh, libMesh::MeshBase::n_active_local_elem(), libMesh::ParallelObject::n_processors(), and libMesh::Parallel::sync_dofobject_data_by_id().

Referenced by libMesh::Partitioner::build_graph().

◆ assign_partitioning()

void libMesh::Partitioner::assign_partitioning ( MeshBase &  mesh,
const std::vector< dof_id_type > &  parts 
)
protectedinherited

Assign the computed partitioning to the mesh.

Definition at line 1491 of file partitioner.C.

1492{
1493 LOG_SCOPE("assign_partitioning()", "Partitioner");
1494
1495 // This function must be run on all processors at once
1496 libmesh_parallel_only(mesh.comm());
1497
1498 dof_id_type first_local_elem = 0;
1499 for (auto pid : make_range(mesh.processor_id()))
1500 first_local_elem += _n_active_elem_on_proc[pid];
1501
1502#ifndef NDEBUG
1503 const dof_id_type n_active_local_elem = mesh.n_active_local_elem();
1504#endif
1505
1506 std::map<processor_id_type, std::vector<dof_id_type>>
1507 requested_ids;
1508
1509 // Results to gather from each processor - kept in a map so we
1510 // do only one loop over elements after all receives are done.
1511 std::map<processor_id_type, std::vector<processor_id_type>>
1512 filled_request;
1513
1514 for (auto & elem : mesh.active_element_ptr_range())
1515 {
1516 // we need to get the index from the owning processor
1517 // (note we cannot assign it now -- we are iterating
1518 // over elements again and this will be bad!)
1519 requested_ids[elem->processor_id()].push_back(elem->id());
1520 }
1521
1522 auto gather_functor =
1523 [this,
1524 & parts,
1525#ifndef NDEBUG
1526 & mesh,
1527 n_active_local_elem,
1528#endif
1529 first_local_elem]
1530 (processor_id_type, const std::vector<dof_id_type> & ids,
1531 std::vector<processor_id_type> & data)
1532 {
1533 const std::size_t ids_size = ids.size();
1534 data.resize(ids.size());
1535
1536 for (std::size_t i=0; i != ids_size; i++)
1537 {
1538 const dof_id_type requested_elem_index = ids[i];
1539
1540 libmesh_assert(_global_index_by_pid_map.count(requested_elem_index));
1541
1542 const dof_id_type global_index_by_pid =
1543 _global_index_by_pid_map[requested_elem_index];
1544
1545 const dof_id_type local_index =
1546 global_index_by_pid - first_local_elem;
1547
1548 libmesh_assert_less (local_index, parts.size());
1549 libmesh_assert_less (local_index, n_active_local_elem);
1550
1551 const processor_id_type elem_procid =
1552 cast_int<processor_id_type>(parts[local_index]);
1553
1554 libmesh_assert_less (elem_procid, mesh.n_partitions());
1555
1556 data[i] = elem_procid;
1557 }
1558 };
1559
1560 auto action_functor =
1561 [&filled_request]
1562 (processor_id_type pid,
1563 const std::vector<dof_id_type> &,
1564 const std::vector<processor_id_type> & new_procids)
1565 {
1566 filled_request[pid] = new_procids;
1567 };
1568
1569 // Trade requests with other processors
1570 const processor_id_type * ex = nullptr;
1572 (mesh.comm(), requested_ids, gather_functor, action_functor, ex);
1573
1574 // and finally assign the partitioning.
1575 // note we are iterating in exactly the same order
1576 // used to build up the request, so we can expect the
1577 // required entries to be in the proper sequence.
1578 std::vector<unsigned int> counters(mesh.n_processors(), 0);
1579 for (auto & elem : mesh.active_element_ptr_range())
1580 {
1581 const processor_id_type current_pid = elem->processor_id();
1582
1583 libmesh_assert_less (counters[current_pid], requested_ids[current_pid].size());
1584
1585 const processor_id_type elem_procid =
1586 filled_request[current_pid][counters[current_pid]++];
1587
1588 libmesh_assert_less (elem_procid, mesh.n_partitions());
1589 elem->processor_id() = elem_procid;
1590 }
1591}
unsigned int n_partitions() const
Definition mesh_base.h:1530
void pull_parallel_vector_data(const Communicator &comm, const MapToVectors &queries, GatherFunctor &gather_data, const ActionFunctor &act_on_data, const datum *example)
libmesh_assert(ctx)

References libMesh::Partitioner::_global_index_by_pid_map, libMesh::Partitioner::_n_active_elem_on_proc, libMesh::ParallelObject::comm(), libMesh::libmesh_assert(), libMesh::make_range(), mesh, libMesh::MeshBase::n_active_local_elem(), libMesh::MeshBase::n_partitions(), libMesh::ParallelObject::n_processors(), libMesh::ParallelObject::processor_id(), and TIMPI::pull_parallel_vector_data().

◆ attach_weights()

virtual void libMesh::Partitioner::attach_weights ( ErrorVector *  )
inlinevirtualinherited

Attach weights that can be used for partitioning.

This ErrorVector should be exactly the same on every processor and should have mesh->max_elem_id() entries.

Reimplemented in libMesh::MetisPartitioner.

Definition at line 224 of file partitioner.h.

224{ libmesh_not_implemented(); }

◆ build()

std::unique_ptr< Partitioner > libMesh::Partitioner::build ( const PartitionerType  solver_package)
staticinherited

Builds a Partitioner of the type specified by partitioner_type.

Definition at line 159 of file partitioner.C.

160{
161 switch (partitioner_type)
162 {
164 return std::make_unique<CentroidPartitioner>();
166 return std::make_unique<LinearPartitioner>();
168 return std::make_unique<MappedSubdomainPartitioner>();
170 return std::make_unique<MetisPartitioner>();
172 return std::make_unique<ParmetisPartitioner>();
174 return std::make_unique<HilbertSFCPartitioner>();
176 return std::make_unique<MortonSFCPartitioner>();
177 case SFC_PARTITIONER:
178 return std::make_unique<SFCPartitioner>();
180 return std::make_unique<SubdomainPartitioner>();
181 default:
182 libmesh_error_msg("Invalid partitioner type: " <<
183 Utility::enum_to_string(partitioner_type));
184 }
185}
std::string enum_to_string(const T e)

References libMesh::CENTROID_PARTITIONER, libMesh::Utility::enum_to_string(), libMesh::HILBERT_SFC_PARTITIONER, libMesh::LINEAR_PARTITIONER, libMesh::MAPPED_SUBDOMAIN_PARTITIONER, libMesh::METIS_PARTITIONER, libMesh::MORTON_SFC_PARTITIONER, libMesh::PARMETIS_PARTITIONER, libMesh::SFC_PARTITIONER, and libMesh::SUBDOMAIN_PARTITIONER.

Referenced by libMesh::DistributedMesh::DistributedMesh(), libMesh::ReplicatedMesh::ReplicatedMesh(), PartitionerTest< PartitionerSubclass, MeshClass >::testBuild(), and MeshDeletionsTest::testDeleteElem().

◆ build_graph()

void libMesh::ParmetisPartitioner::build_graph ( const MeshBase &  mesh)
overrideprotectedvirtual

Build the graph.

Reimplemented from libMesh::Partitioner.

Definition at line 433 of file parmetis_partitioner.C.

434{
435 LOG_SCOPE("build_graph()", "ParmetisPartitioner");
436
437 // build the graph in distributed CSR format. Note that
438 // the edges in the graph will correspond to
439 // face neighbors
440 const dof_id_type n_active_local_elem = mesh.n_active_local_elem();
441
443
444 dof_id_type graph_size_upper_bound = 0;
445
446 for (auto & row: _dual_graph)
447 graph_size_upper_bound += cast_int<dof_id_type>(row.size());
448
449 // Reserve space in the adjacency array
450 _pmetis->xadj.clear();
451 _pmetis->xadj.reserve (n_active_local_elem + 1);
452 _pmetis->adjncy.clear();
453 _pmetis->adjncy.reserve (graph_size_upper_bound);
454
455 const dof_id_type first_local_elem =
456 _pmetis->vtxdist[mesh.processor_id()];
457 std::unordered_set<dof_id_type> unique_neighbors;
458
459 // ParMETIS requires a simple graph. Preserve first-occurrence order
460 // while filtering so valid graph rows remain otherwise unchanged.
461 for (auto i : index_range(_dual_graph))
462 {
463 _pmetis->xadj.push_back(cast_int<int>(_pmetis->adjncy.size()));
464
465 const dof_id_type global_index =
466 first_local_elem + cast_int<dof_id_type>(i);
467 const auto & graph_row = _dual_graph[i];
468 unique_neighbors.clear();
469
470 for (const auto neighbor : graph_row)
471 if (neighbor != global_index &&
472 unique_neighbors.insert(neighbor).second)
473 _pmetis->adjncy.push_back(neighbor);
474 }
475
476 // The end of the adjacency array for the last elem
477 _pmetis->xadj.push_back(cast_int<int>(_pmetis->adjncy.size()));
478
479 libmesh_assert_equal_to (_pmetis->xadj.size(), n_active_local_elem+1);
480 libmesh_assert_less_equal (_pmetis->adjncy.size(),
481 graph_size_upper_bound);
482}
std::vector< std::vector< dof_id_type > > _dual_graph
A dual graph corresponds to the mesh, and it is typically used in paritioner.
virtual void build_graph(const MeshBase &mesh)
Build a dual graph for partitioner.
Tnew cast_int(Told oldvar)
LIBMESH_DEVICE_INLINE ResultVector row(const TensorLike &T_in, const unsigned int row_index)
auto index_range(const T &sizable)
Helper function that returns an IntRange<std::size_t> representing all the indices of the passed-in v...
Definition int_range.h:153

References libMesh::index_range(), and mesh.

◆ clone()

virtual std::unique_ptr< Partitioner > libMesh::ParmetisPartitioner::clone ( ) const
inlineoverridevirtual
Returns
A copy of this partitioner wrapped in a smart pointer.

Implements libMesh::Partitioner.

Definition at line 81 of file parmetis_partitioner.h.

82 {
83 return std::make_unique<ParmetisPartitioner>(*this);
84 }

◆ initialize()

void libMesh::ParmetisPartitioner::initialize ( const MeshBase &  mesh,
const unsigned int  n_sbdmns 
)
private

Initialize data structures.

Definition at line 244 of file parmetis_partitioner.C.

246{
247 LOG_SCOPE("initialize()", "ParmetisPartitioner");
248
249 const dof_id_type n_active_local_elem = mesh.n_active_local_elem();
250 // Set parameters.
251 _pmetis->wgtflag = 2; // weights on vertices only
252 _pmetis->ncon = 1; // one weight per vertex
253 _pmetis->numflag = 0; // C-style 0-based numbering
254 _pmetis->nparts = static_cast<Parmetis::idx_t>(n_sbdmns); // number of subdomains to create
255 _pmetis->edgecut = 0; // the numbers of edges cut by the
256 // partition
257
258 // Initialize data structures for ParMETIS
259 _pmetis->vtxdist.assign (mesh.n_processors()+1, 0);
260 _pmetis->tpwgts.assign (_pmetis->nparts, 1./_pmetis->nparts);
261 _pmetis->ubvec.assign (_pmetis->ncon, 1.05);
262 _pmetis->part.assign (n_active_local_elem, 0);
263 _pmetis->options.resize (5);
264 _pmetis->vwgt.resize (n_active_local_elem);
265
266 // Set the options
267 _pmetis->options[0] = 1; // don't use default options
268 _pmetis->options[1] = 0; // default (level of timing)
269 _pmetis->options[2] = 15; // random seed (default)
270 _pmetis->options[3] = 2; // processor distribution and subdomain distribution are decoupled
271
272 // ParMetis expects the elements to be numbered in contiguous blocks
273 // by processor, i.e. [0, ne0), [ne0, ne0+ne1), ...
274 // Since we only partition active elements we should have no expectation
275 // that we currently have such a distribution. So we need to create it.
276 // Also, at the same time we are going to map all the active elements into a globally
277 // unique range [0,n_active_elem) which is *independent* of the current partitioning.
278 // This can be fed to ParMetis as the initial partitioning of the subdomains (decoupled
279 // from the partitioning of the objects themselves). This allows us to get the same
280 // resultant partitioning independent of the input partitioning.
283
285
286
287 // count the total number of active elements in the mesh. Note we cannot
288 // use mesh.n_active_elem() in general since this only returns the number
289 // of active elements which are stored on the calling processor.
290 // We should not use n_active_elem for any allocation because that will
291 // be inherently unscalable, but it can be useful for libmesh_assertions.
292 dof_id_type n_active_elem=0;
293
294 // Set up the vtxdist array. This will be the same on each processor.
295 // ***** Consult the Parmetis documentation. *****
296 libmesh_assert_equal_to (_pmetis->vtxdist.size(),
297 cast_int<std::size_t>(mesh.n_processors()+1));
298 libmesh_assert_equal_to (_pmetis->vtxdist[0], 0);
299
300 for (auto pid : make_range(mesh.n_processors()))
301 {
302 _pmetis->vtxdist[pid+1] = _pmetis->vtxdist[pid] + _n_active_elem_on_proc[pid];
303 n_active_elem += _n_active_elem_on_proc[pid];
304 }
305 libmesh_assert_equal_to (_pmetis->vtxdist.back(), static_cast<Parmetis::idx_t>(n_active_elem));
306
307
308 // Maps active element ids into a contiguous range independent of partitioning.
309 // (only needs local scope)
310 std::unordered_map<dof_id_type, dof_id_type> global_index_map;
311
312 {
313 std::vector<dof_id_type> global_index;
314
315 // create the unique mapping for all active elements independent of partitioning
316 {
317 MeshBase::const_element_iterator it = mesh.active_elements_begin();
318 const MeshBase::const_element_iterator end = mesh.active_elements_end();
319
320 // Calling this on all processors a unique range in [0,n_active_elem) is constructed.
321 // Only the indices for the elements we pass in are returned in the array.
322 MeshCommunication().find_global_indices (mesh.comm(),
323 bbox, it, end,
324 global_index);
325
326 for (dof_id_type cnt=0; it != end; ++it)
327 {
328 const Elem * elem = *it;
329 // vectormap::count forces a sort, which is too expensive
330 // in a loop
331 // libmesh_assert (!global_index_map.count(elem->id()));
332 libmesh_assert_less (cnt, global_index.size());
333 libmesh_assert_less (global_index[cnt], n_active_elem);
334
335 global_index_map.emplace(elem->id(), global_index[cnt++]);
336 }
337 }
338 // really, shouldn't be close!
339 libmesh_assert_less_equal (global_index_map.size(), n_active_elem);
340 libmesh_assert_less_equal (_global_index_by_pid_map.size(), n_active_elem);
341
342 // At this point the two maps should be the same size. If they are not
343 // then the number of active elements is not the same as the sum over all
344 // processors of the number of active elements per processor, which means
345 // there must be some unpartitioned objects out there.
346 libmesh_error_msg_if(global_index_map.size() != _global_index_by_pid_map.size(),
347 "ERROR: ParmetisPartitioner cannot handle unpartitioned objects!");
348 }
349
350 // Finally, we need to initialize the vertex (partition) weights and the initial subdomain
351 // mapping. The subdomain mapping will be independent of the processor mapping, and is
352 // defined by a simple mapping of the global indices we just found.
353 {
354 std::vector<dof_id_type> subdomain_bounds(mesh.n_processors());
355
356 const dof_id_type first_local_elem = _pmetis->vtxdist[mesh.processor_id()];
357
358 for (auto pid : make_range(mesh.n_processors()))
359 {
360 dof_id_type tgt_subdomain_size = 0;
361
362 // watch out for the case that n_subdomains < n_processors
363 if (pid < static_cast<unsigned int>(_pmetis->nparts))
364 {
365 tgt_subdomain_size = n_active_elem/std::min
366 (cast_int<Parmetis::idx_t>(mesh.n_processors()), _pmetis->nparts);
367
368 if (pid < n_active_elem%_pmetis->nparts)
369 tgt_subdomain_size++;
370 }
371 if (pid == 0)
372 subdomain_bounds[0] = tgt_subdomain_size;
373 else
374 subdomain_bounds[pid] = subdomain_bounds[pid-1] + tgt_subdomain_size;
375 }
376
377 libmesh_assert_equal_to (subdomain_bounds.back(), n_active_elem);
378
380 (mesh.active_local_element_stored_range(),
381 [first_local_elem, n_active_elem, n_active_local_elem, this,
382 &global_index_map, &subdomain_bounds](const ConstElemRange & range)
383 {
384 for (const Elem * elem : range)
385 {
386 const dof_id_type global_index_by_pid =
387 libmesh_map_find(_global_index_by_pid_map, elem->id());
388 libmesh_assert_less (global_index_by_pid, n_active_elem);
389
390 const dof_id_type local_index =
391 global_index_by_pid - first_local_elem;
392
393 libmesh_assert_less (local_index, n_active_local_elem);
394 libmesh_assert_less (local_index, _pmetis->vwgt.size());
395
396 libmesh_ignore(n_active_elem); // unused outside dbg/devel
397 libmesh_ignore(n_active_local_elem); // unused outside dbg/devel
398
399 // Spline nodes are a special case (storing all the
400 // unconstrained DoFs in an IGA simulation), but in general
401 // we'll try to distribute work by expecting it to be roughly
402 // proportional to DoFs, which are roughly proportional to
403 // nodes.
404 if (elem->type() == NODEELEM &&
405 elem->mapping_type() == RATIONAL_BERNSTEIN_MAP)
406 _pmetis->vwgt[local_index] = 50;
407 else
408 _pmetis->vwgt[local_index] = elem->n_nodes();
409
410 // find the subdomain this element belongs in
411 const dof_id_type global_index =
412 libmesh_map_find(global_index_map, elem->id());
413
414 libmesh_assert_less (global_index, subdomain_bounds.back());
415
416 const unsigned int subdomain_id =
417 cast_int<unsigned int>
418 (std::distance(subdomain_bounds.begin(),
419 std::lower_bound(subdomain_bounds.begin(),
420 subdomain_bounds.end(),
421 global_index)));
422 libmesh_assert_less (subdomain_id, _pmetis->nparts);
423 libmesh_assert_less (local_index, _pmetis->part.size());
424
425 _pmetis->part[local_index] = subdomain_id;
426 }
427 });
428 }
429}
virtual void _find_global_index_by_pid_map(const MeshBase &mesh)
Construct contiguous global indices for the current partitioning.
void parallel_for(const Range &range, const Body &body, unsigned int n_threads=libMesh::n_threads())
Execute the provided function object in parallel on the specified range.
StoredRange< MeshBase::const_element_iterator, const Elem * > ConstElemRange
Definition elem_range.h:34

References libMesh::MeshCommunication::find_global_indices(), libMesh::DofObject::id(), libMesh::make_range(), and mesh.

◆ operator=() [1/2]

ParmetisPartitioner & libMesh::ParmetisPartitioner::operator= ( const ParmetisPartitioner &  )
delete

This class contains a unique_ptr member, so it can't be default copy assigned.

◆ operator=() [2/2]

ParmetisPartitioner & libMesh::ParmetisPartitioner::operator= ( ParmetisPartitioner &&  )
default

◆ partition() [1/2]

void libMesh::Partitioner::partition ( MeshBase &  mesh)
virtualinherited

Partitions the MeshBase into mesh.n_processors() by setting processor_id() on Nodes and Elems.

Note
If you are implementing a new type of Partitioner, you most likely do not want to override the partition() function, see instead the protected virtual _do_partition() method below. The partition() function is responsible for doing a lot of libmesh-internals-specific setup and finalization before and after the _do_partition() function is called. The only responsibility of the _do_partition() function, on the other hand, is to set the processor IDs of the elements according to a specific partitioning algorithm. See, e.g. MetisPartitioner for an example.

Definition at line 189 of file partitioner.C.

190{
191 this->partition(mesh,mesh.n_processors());
192}
virtual void partition(MeshBase &mesh, const unsigned int n)
Partitions the MeshBase into n parts by setting processor_id() on Nodes and Elems.

References mesh, libMesh::ParallelObject::n_processors(), and libMesh::Partitioner::partition().

◆ partition() [2/2]

void libMesh::Partitioner::partition ( MeshBase &  mesh,
const unsigned int  n 
)
virtualinherited

Partitions the MeshBase into n parts by setting processor_id() on Nodes and Elems.

Note
If you are implementing a new type of Partitioner, you most likely do not want to override the partition() function, see instead the protected virtual _do_partition() method below. The partition() function is responsible for doing a lot of libmesh-internals-specific setup and finalization before and after the _do_partition() function is called. The only responsibility of the _do_partition() function, on the other hand, is to set the processor IDs of the elements according to a specific partitioning algorithm. See, e.g. MetisPartitioner for an example.

Definition at line 196 of file partitioner.C.

198{
199 libmesh_parallel_only(mesh.comm());
200
201 // BSK - temporary fix while redistribution is integrated 6/26/2008
202 // Uncomment this to not repartition in parallel
203 // if (!mesh.is_serial())
204 // return;
205
206 // we cannot partition into more pieces than we have
207 // active elements!
208 const unsigned int n_parts =
209 static_cast<unsigned int>
210 (std::min(mesh.n_active_elem(), static_cast<dof_id_type>(n)));
211
212 // Set the number of partitions in the mesh
213 mesh.set_n_partitions()=n_parts;
214
215 if (n_parts == 1)
216 {
217 this->single_partition (mesh);
218 return;
219 }
220
221 // First assign a temporary partitioning to any unpartitioned elements. A spatially
222 // coherent assignment is only worth its parallel sort for a partitioner that refines what
223 // it is handed; the other partitioners just need one that is consistent across processors,
224 // which the cheap traversal-order assignment already is on a replicated mesh.
226 (mesh, n_parts,
227 /*spatial_indexing=*/!(mesh.is_replicated() && this->partitions_from_scratch()));
228
229 // Call the partitioning function
230 this->_do_partition(mesh,n_parts);
231
232 // Set the parent's processor ids
234
235 // Redistribute elements if necessary, before setting node processor
236 // ids, to make sure those will be set consistently
238
239#ifdef DEBUG
241
242 // Messed up elem processor_id()s can leave us without the child
243 // elements we need to restrict vectors on a distributed mesh
244 MeshTools::libmesh_assert_valid_procids<Elem>(mesh);
245#endif
246
247 // Set the node's processor ids
249
250#ifdef DEBUG
251 MeshTools::libmesh_assert_valid_procids<Elem>(mesh);
252#endif
253
254 // Give derived Mesh classes a chance to update any cached data to
255 // reflect the new partitioning
257}
virtual bool is_replicated() const
Definition mesh_base.h:379
virtual void redistribute()
Redistribute elements between processors.
Definition mesh_base.C:1169
virtual void update_post_partitioning()
Recalculate any cached data (or invalidate any caches that are computed on the fly) after elements an...
Definition mesh_base.C:1181
virtual dof_id_type n_active_elem() const =0
unsigned int & set_n_partitions()
Definition mesh_base.h:2176
static void set_parent_processor_ids(MeshBase &mesh)
This function is called after partitioning to set the processor IDs for the inactive parent elements.
static void partition_unpartitioned_elements(MeshBase &mesh)
These functions assign processor IDs to newly-created elements (in parallel) which are currently assi...
virtual void _do_partition(MeshBase &mesh, const unsigned int n)=0
This is the actual partitioning method which must be overridden in derived classes.
static void set_node_processor_ids(MeshBase &mesh)
This function is called after partitioning to set the processor IDs for the nodes.
void libmesh_assert_valid_remote_elems(const MeshBase &mesh)
A function for verifying that active local elements' neighbors are never remote elements.

References libMesh::Partitioner::_do_partition(), libMesh::ParallelObject::comm(), libMesh::MeshBase::is_replicated(), libMesh::MeshTools::libmesh_assert_valid_remote_elems(), mesh, libMesh::MeshBase::n_active_elem(), libMesh::Partitioner::partition_unpartitioned_elements(), libMesh::MeshBase::redistribute(), libMesh::MeshBase::set_n_partitions(), libMesh::Partitioner::set_node_processor_ids(), libMesh::Partitioner::set_parent_processor_ids(), libMesh::Partitioner::single_partition(), and libMesh::MeshBase::update_post_partitioning().

Referenced by _do_repartition(), and libMesh::Partitioner::partition().

◆ partition_range()

virtual void libMesh::Partitioner::partition_range ( MeshBase &  ,
MeshBase::element_iterator  ,
MeshBase::element_iterator  ,
const unsigned int   
)
inlinevirtualinherited

Partitions elements in the range (it, end) into n parts.

The mesh from which the iterators are created must also be passed in, since it is a parallel object and has other useful information in it.

Although partition_range() is part of the public Partitioner interface, it should not generally be called by applications. Its main purpose is to support the SubdomainPartitioner, which uses it internally to individually partition ranges of elements before combining them into the final partitioning. Most of the time, the protected _do_partition() function is implemented in terms of partition_range() by passing a range which includes all the elements of the Mesh.

Reimplemented in libMesh::CentroidPartitioner, libMesh::LinearPartitioner, libMesh::MappedSubdomainPartitioner, libMesh::MetisPartitioner, and libMesh::SFCPartitioner.

Definition at line 137 of file partitioner.h.

141 { libmesh_not_implemented(); }

Referenced by libMesh::SubdomainPartitioner::_do_partition().

◆ partition_unpartitioned_elements() [1/3]

void libMesh::Partitioner::partition_unpartitioned_elements ( MeshBase &  mesh)
staticinherited

◆ partition_unpartitioned_elements() [2/3]

void libMesh::Partitioner::partition_unpartitioned_elements ( MeshBase &  mesh,
const unsigned int  n 
)
staticinherited

Definition at line 360 of file partitioner.C.

362{
364 /*spatial_indexing=*/true);
365}

References mesh, and libMesh::Partitioner::partition_unpartitioned_elements().

◆ partition_unpartitioned_elements() [3/3]

void libMesh::Partitioner::partition_unpartitioned_elements ( MeshBase &  mesh,
const unsigned int  n,
const bool  spatial_indexing 
)
staticinherited

As above, but spatial_indexing selects how the elements are mapped onto the partitions.

With true they are ordered along a space-filling curve, so that each partition gets a spatially compact set; this costs a parallel sort. With false their position in the traversal of the range is used, which is free but is only consistent across processors for a replicated mesh.

Definition at line 369 of file partitioner.C.

372{
373 MeshBase::element_iterator it = mesh.unpartitioned_elements_begin();
374 const MeshBase::element_iterator end = mesh.unpartitioned_elements_end();
375
376 const dof_id_type n_unpartitioned_elements = MeshTools::n_elem (it, end);
377
378 // the unpartitioned elements must exist on all processors. If the range is empty on one
379 // it is empty on all, and we can quit right here.
380 if (!n_unpartitioned_elements)
381 return;
382
383 // find the target subdomain sizes
384 std::vector<dof_id_type> subdomain_bounds(mesh.n_processors());
385
386 for (auto pid : make_range(mesh.n_processors()))
387 {
388 dof_id_type tgt_subdomain_size = 0;
389
390 // watch out for the case that n_subdomains < n_processors
391 if (pid < n_subdomains)
392 {
393 tgt_subdomain_size = n_unpartitioned_elements/n_subdomains;
394
395 if (pid < n_unpartitioned_elements%n_subdomains)
396 tgt_subdomain_size++;
397
398 }
399
400 //libMesh::out << "pid, #= " << pid << ", " << tgt_subdomain_size << std::endl;
401 if (pid == 0)
402 subdomain_bounds[0] = tgt_subdomain_size;
403 else
404 subdomain_bounds[pid] = subdomain_bounds[pid-1] + tgt_subdomain_size;
405 }
406
407 libmesh_assert_equal_to (subdomain_bounds.back(), n_unpartitioned_elements);
408
409 // create the unique mapping for all unpartitioned elements independent of partitioning
410 // determine the global indexing for all the unpartitioned elements
411 std::vector<dof_id_type> global_indices;
412
413 // Calling this on all processors a unique range in [0,n_unpartitioned_elements) is constructed.
414 // Only the indices for the elements we pass in are returned in the array.
415 // Without the spatial indexing an element's position in the traversal of the range is used
416 // instead, which a replicated mesh reaches in the same order on every processor and which
417 // therefore indexes [0, n_unpartitioned_elements) just as uniquely, at no cost.
418 if (spatial_indexing)
421 global_indices);
422 else
424
425 dof_id_type cnt=0;
426 for (auto & elem : as_range(it, end))
427 {
428 libmesh_assert (!spatial_indexing || cnt < global_indices.size());
429 const dof_id_type global_index =
430 spatial_indexing ? global_indices[cnt++] : cnt++;
431
432 libmesh_assert_less (global_index, subdomain_bounds.back());
433 libmesh_assert_less (global_index, n_unpartitioned_elements);
434
435 const processor_id_type subdomain_id =
436 cast_int<processor_id_type>
437 (std::distance(subdomain_bounds.begin(),
438 std::upper_bound(subdomain_bounds.begin(),
439 subdomain_bounds.end(),
440 global_index)));
441 libmesh_assert_less (subdomain_id, n_subdomains);
442
443 elem->processor_id() = subdomain_id;
444 //libMesh::out << "assigning " << global_index << " to " << subdomain_id << std::endl;
445 }
446}
void find_global_indices(const Parallel::Communicator &communicator, const libMesh::BoundingBox &, const ForwardIterator &, const ForwardIterator &, std::vector< dof_id_type > &) const
This method determines a globally unique, partition-agnostic index for each object in the input range...
dof_id_type n_elem(const MeshBase::const_element_iterator &begin, const MeshBase::const_element_iterator &end)
Count up the number of elements of a specific type (as defined by an iterator range).
SimpleRange< IndexType > as_range(const std::pair< IndexType, IndexType > &p)
Helper function that allows us to treat a homogenous pair as a range.
The definition of the element_iterator struct.
Definition mesh_base.h:2557

References libMesh::as_range(), libMesh::ParallelObject::comm(), libMesh::MeshTools::create_bounding_box(), libMesh::MeshCommunication::find_global_indices(), libMesh::MeshBase::is_replicated(), libMesh::libmesh_assert(), libMesh::make_range(), mesh, libMesh::MeshTools::n_elem(), and libMesh::ParallelObject::n_processors().

◆ partitions_from_scratch()

virtual bool libMesh::ParmetisPartitioner::partitions_from_scratch ( ) const
inlineoverridevirtual

ParMETIS is called through ParMETIS_V3_AdaptiveRepart, which starts from the partitioning the mesh already carries.

Reimplemented from libMesh::Partitioner.

Definition at line 91 of file parmetis_partitioner.h.

91{ return false; }

◆ processor_pairs_to_interface_nodes()

void libMesh::Partitioner::processor_pairs_to_interface_nodes ( MeshBase &  mesh,
std::map< std::pair< processor_id_type, processor_id_type >, std::set< dof_id_type > > &  processor_pair_to_nodes 
)
staticinherited

On the partitioning interface, a surface is shared by two and only two processors.

Try to find which pair of processors corresponds to which surfaces, and store their nodes.

Definition at line 603 of file partitioner.C.

605{
606 // This function must be run on all processors at once
607 libmesh_parallel_only(mesh.comm());
608
609 processor_pair_to_nodes.clear();
610
611 std::set<dof_id_type> mynodes;
612 std::set<dof_id_type> neighbor_nodes;
613 std::vector<dof_id_type> common_nodes;
614
615 // Loop over all the active elements
616 for (auto & elem : mesh.active_element_ptr_range())
617 {
618 libmesh_assert(elem);
619
620 libmesh_assert_not_equal_to (elem->processor_id(), DofObject::invalid_processor_id);
621
622 auto n_nodes = elem->n_nodes();
623
624 // prepare data for this element
625 mynodes.clear();
626 neighbor_nodes.clear();
627 common_nodes.clear();
628
629 for (unsigned int inode = 0; inode < n_nodes; inode++)
630 mynodes.insert(elem->node_id(inode));
631
632 for (auto i : elem->side_index_range())
633 {
634 auto neigh = elem->neighbor_ptr(i);
635 if (neigh && !neigh->is_remote() && neigh->processor_id() != elem->processor_id())
636 {
637 neighbor_nodes.clear();
638 common_nodes.clear();
639 auto neigh_n_nodes = neigh->n_nodes();
640 for (unsigned int inode = 0; inode < neigh_n_nodes; inode++)
641 neighbor_nodes.insert(neigh->node_id(inode));
642
643 std::set_intersection(mynodes.begin(), mynodes.end(),
644 neighbor_nodes.begin(), neighbor_nodes.end(),
645 std::back_inserter(common_nodes));
646
647 auto & map_set = processor_pair_to_nodes[std::make_pair(std::min(elem->processor_id(), neigh->processor_id()),
648 std::max(elem->processor_id(), neigh->processor_id()))];
649 for (auto global_node_id : common_nodes)
650 map_set.insert(global_node_id);
651 }
652 }
653 }
654}
static constexpr processor_id_type invalid_processor_id
An invalid processor_id to distinguish DoFs that have not been assigned to a processor.
Definition dof_object.h:484
const dof_id_type n_nodes
Definition tecplot_io.C:67

References libMesh::ParallelObject::comm(), libMesh::DofObject::invalid_processor_id, libMesh::libmesh_assert(), mesh, and n_nodes.

Referenced by libMesh::Partitioner::set_interface_node_processor_ids_BFS(), libMesh::Partitioner::set_interface_node_processor_ids_linear(), and libMesh::Partitioner::set_interface_node_processor_ids_petscpartitioner().

◆ repartition() [1/2]

void libMesh::Partitioner::repartition ( MeshBase &  mesh)
inherited

Repartitions the MeshBase into mesh.n_processors() parts.

This is required since some partitioning algorithms can repartition more efficiently than computing a new partitioning from scratch.

Definition at line 261 of file partitioner.C.

262{
263 this->repartition(mesh,mesh.n_processors());
264}
void repartition(MeshBase &mesh, const unsigned int n)
Repartitions the MeshBase into n parts.

References mesh, libMesh::ParallelObject::n_processors(), and libMesh::Partitioner::repartition().

◆ repartition() [2/2]

void libMesh::Partitioner::repartition ( MeshBase &  mesh,
const unsigned int  n 
)
inherited

Repartitions the MeshBase into n parts.

(Some partitioning algorithms can repartition more efficiently than computing a new partitioning from scratch.) The default behavior is to simply call this->partition(mesh,n).

Definition at line 268 of file partitioner.C.

270{
271 // we cannot partition into more pieces than we have
272 // active elements!
273 const unsigned int n_parts =
274 static_cast<unsigned int>
275 (std::min(mesh.n_active_elem(), static_cast<dof_id_type>(n)));
276
277 // Set the number of partitions in the mesh
278 mesh.set_n_partitions()=n_parts;
279
280 if (n_parts == 1)
281 {
282 this->single_partition (mesh);
283 return;
284 }
285
286 // First assign a temporary partitioning to any unpartitioned elements; see partition()
287 // for why the indexing is chosen this way.
289 (mesh, n_parts,
290 /*spatial_indexing=*/!(mesh.is_replicated() && this->partitions_from_scratch()));
291
292 // Call the partitioning function
293 this->_do_repartition(mesh,n_parts);
294
295 // Set the parent's processor ids
297
298 // Set the node's processor ids
300}
virtual void _do_repartition(MeshBase &mesh, const unsigned int n)
This is the actual re-partitioning method which can be overridden in derived classes.

References libMesh::Partitioner::_do_repartition(), libMesh::MeshBase::is_replicated(), mesh, libMesh::MeshBase::n_active_elem(), libMesh::Partitioner::partition_unpartitioned_elements(), libMesh::MeshBase::set_n_partitions(), libMesh::Partitioner::set_node_processor_ids(), libMesh::Partitioner::set_parent_processor_ids(), and libMesh::Partitioner::single_partition().

Referenced by libMesh::Partitioner::repartition().

◆ set_interface_node_processor_ids_BFS()

void libMesh::Partitioner::set_interface_node_processor_ids_BFS ( MeshBase &  mesh)
staticinherited

Nodes on the partitioning interface is clustered into two groups BFS (Breadth First Search)scheme for per pair of processors.

Definition at line 681 of file partitioner.C.

682{
683 // This function must be run on all processors at once
684 libmesh_parallel_only(mesh.comm());
685
686 // I see occasional consistency failures when using this on a
687 // distributed mesh
688 libmesh_experimental();
689
690 std::map<std::pair<processor_id_type, processor_id_type>, std::set<dof_id_type>> processor_pair_to_nodes;
691
692 processor_pairs_to_interface_nodes(mesh, processor_pair_to_nodes);
693
694 std::unordered_map<dof_id_type, std::vector<const Elem *>> nodes_to_elem_map;
695
696 MeshTools::build_nodes_to_elem_map(mesh, nodes_to_elem_map);
697
698 std::vector<const Node *> neighbors;
699 std::set<dof_id_type> neighbors_order;
700 std::vector<dof_id_type> common_nodes;
701 std::queue<dof_id_type> nodes_queue;
702 std::set<dof_id_type> visted_nodes;
703
704 for (auto & pmap : processor_pair_to_nodes)
705 {
706 std::size_t n_own_nodes = pmap.second.size()/2;
707
708 // Initialize node assignment
709 for (dof_id_type id : pmap.second)
710 mesh.node_ref(id).processor_id() = pmap.first.second;
711
712 visted_nodes.clear();
713 for (dof_id_type id : pmap.second)
714 {
715 mesh.node_ref(id).processor_id() = pmap.first.second;
716
717 if (visted_nodes.count(id))
718 continue;
719 else
720 {
721 nodes_queue.push(id);
722 visted_nodes.insert(id);
723 if (visted_nodes.size() >= n_own_nodes)
724 break;
725 }
726
727 while (!nodes_queue.empty())
728 {
729 auto & node = mesh.node_ref(nodes_queue.front());
730 nodes_queue.pop();
731
732 neighbors.clear();
733 MeshTools::find_nodal_neighbors(mesh, node, nodes_to_elem_map, neighbors);
734 neighbors_order.clear();
735 for (auto & neighbor : neighbors)
736 neighbors_order.insert(neighbor->id());
737
738 common_nodes.clear();
739 std::set_intersection(pmap.second.begin(), pmap.second.end(),
740 neighbors_order.begin(), neighbors_order.end(),
741 std::back_inserter(common_nodes));
742
743 for (auto c_node : common_nodes)
744 if (!visted_nodes.count(c_node))
745 {
746 nodes_queue.push(c_node);
747 visted_nodes.insert(c_node);
748 if (visted_nodes.size() >= n_own_nodes)
749 goto queue_done;
750 }
751
752 if (visted_nodes.size() >= n_own_nodes)
753 goto queue_done;
754 }
755 }
756 queue_done:
757 for (auto node : visted_nodes)
758 mesh.node_ref(node).processor_id() = pmap.first.first;
759 }
760}
processor_id_type processor_id() const
Definition dof_object.h:895
virtual const Node & node_ref(const dof_id_type i) const
Definition mesh_base.h:742
virtual void clear()
Deletes all the element and node data that is currently stored.
Definition mesh_base.C:1036
static void processor_pairs_to_interface_nodes(MeshBase &mesh, std::map< std::pair< processor_id_type, processor_id_type >, std::set< dof_id_type > > &processor_pair_to_nodes)
On the partitioning interface, a surface is shared by two and only two processors.
void build_nodes_to_elem_map(const MeshBase &mesh, std::vector< std::vector< dof_id_type > > &nodes_to_elem_map)
After calling this function the input vector nodes_to_elem_map will contain the node to element conne...
Definition mesh_tools.C:456
void find_nodal_neighbors(const MeshBase &mesh, const Node &n, const std::vector< std::vector< const Elem * > > &nodes_to_elem_map, std::vector< const Node * > &neighbors)
Given a mesh and a node in the mesh, the vector will be filled with every node directly attached to t...

References libMesh::MeshTools::build_nodes_to_elem_map(), libMesh::ParallelObject::comm(), libMesh::MeshTools::find_nodal_neighbors(), mesh, libMesh::MeshBase::node_ref(), libMesh::DofObject::processor_id(), and libMesh::Partitioner::processor_pairs_to_interface_nodes().

Referenced by libMesh::Partitioner::set_node_processor_ids().

◆ set_interface_node_processor_ids_linear()

void libMesh::Partitioner::set_interface_node_processor_ids_linear ( MeshBase &  mesh)
staticinherited

Nodes on the partitioning interface is linearly assigned to each pair of processors.

Definition at line 656 of file partitioner.C.

657{
658 // This function must be run on all processors at once
659 libmesh_parallel_only(mesh.comm());
660
661 std::map<std::pair<processor_id_type, processor_id_type>, std::set<dof_id_type>> processor_pair_to_nodes;
662
663 processor_pairs_to_interface_nodes(mesh, processor_pair_to_nodes);
664
665 for (auto & pmap : processor_pair_to_nodes)
666 {
667 std::size_t n_own_nodes = pmap.second.size()/2, i = 0;
668
669 for (dof_id_type id : pmap.second)
670 {
671 auto & node = mesh.node_ref(id);
672 if (i <= n_own_nodes)
673 node.processor_id() = pmap.first.first;
674 else
675 node.processor_id() = pmap.first.second;
676 i++;
677 }
678 }
679}

References libMesh::ParallelObject::comm(), mesh, libMesh::MeshBase::node_ref(), libMesh::DofObject::processor_id(), and libMesh::Partitioner::processor_pairs_to_interface_nodes().

Referenced by libMesh::Partitioner::set_node_processor_ids().

◆ set_interface_node_processor_ids_petscpartitioner()

void libMesh::Partitioner::set_interface_node_processor_ids_petscpartitioner ( MeshBase &  mesh)
staticinherited

Nodes on the partitioning interface is partitioned into two groups using a PETSc partitioner for each pair of processors.

Definition at line 762 of file partitioner.C.

763{
764 libmesh_ignore(mesh); // Only used if LIBMESH_HAVE_PETSC
765
766 // This function must be run on all processors at once
767 libmesh_parallel_only(mesh.comm());
768
769#ifdef LIBMESH_HAVE_PETSC
770 std::map<std::pair<processor_id_type, processor_id_type>, std::set<dof_id_type>> processor_pair_to_nodes;
771
772 processor_pairs_to_interface_nodes(mesh, processor_pair_to_nodes);
773
774 std::vector<std::vector<const Elem *>> nodes_to_elem_map;
775
776 MeshTools::build_nodes_to_elem_map(mesh, nodes_to_elem_map);
777
778 std::vector<const Node *> neighbors;
779 std::set<dof_id_type> neighbors_order;
780 std::vector<dof_id_type> common_nodes;
781
782 std::vector<dof_id_type> rows;
783 std::vector<dof_id_type> cols;
784
785 std::map<dof_id_type, dof_id_type> global_to_local;
786
787 for (auto & pmap : processor_pair_to_nodes)
788 {
789 unsigned int i = 0;
790
791 rows.clear();
792 rows.resize(pmap.second.size()+1);
793 cols.clear();
794 for (dof_id_type id : pmap.second)
795 global_to_local[id] = i++;
796
797 i = 0;
798 for (auto id : pmap.second)
799 {
800 auto & node = mesh.node_ref(id);
801 neighbors.clear();
802 MeshTools::find_nodal_neighbors(mesh, node, nodes_to_elem_map, neighbors);
803 neighbors_order.clear();
804 for (auto & neighbor : neighbors)
805 neighbors_order.insert(neighbor->id());
806
807 common_nodes.clear();
808 std::set_intersection(pmap.second.begin(), pmap.second.end(),
809 neighbors_order.begin(), neighbors_order.end(),
810 std::back_inserter(common_nodes));
811
812 rows[i+1] = rows[i] + cast_int<dof_id_type>(common_nodes.size());
813
814 for (auto c_node : common_nodes)
815 cols.push_back(global_to_local[c_node]);
816
817 i++;
818 }
819
820 // Next we construct an IS from a MatPartitioning
822 {
823 PetscInt *adj_i, *adj_j;
824 LibmeshPetscCall2(mesh.comm(), PetscCalloc1(rows.size(), &adj_i));
825 LibmeshPetscCall2(mesh.comm(), PetscCalloc1(cols.size(), &adj_j));
826 PetscInt rows_size = cast_int<PetscInt>(rows.size());
827 for (PetscInt ii=0; ii<rows_size; ii++)
828 adj_i[ii] = rows[ii];
829
830 PetscInt cols_size = cast_int<PetscInt>(cols.size());
831 for (PetscInt ii=0; ii<cols_size; ii++)
832 adj_j[ii] = cols[ii];
833
834 const PetscInt sz = cast_int<PetscInt>(pmap.second.size());
835
836 // Create sparse matrix representing an adjacency list
838 LibmeshPetscCall2(mesh.comm(), MatCreateMPIAdj(PETSC_COMM_SELF, sz, sz, adj_i, adj_j, nullptr, adj.get()));
839
840 // Create MatPartitioning object
842 LibmeshPetscCall2(mesh.comm(), MatPartitioningCreate(PETSC_COMM_SELF, part.get()));
843
844 // Apply MatPartitioning, storing results in "is"
845 LibmeshPetscCall2(mesh.comm(), MatPartitioningSetAdjacency(part, adj));
846 LibmeshPetscCall2(mesh.comm(), MatPartitioningSetNParts(part, 2));
847 LibmeshPetscCall2(mesh.comm(), PetscObjectSetOptionsPrefix((PetscObject)(*part), "balance_"));
848 LibmeshPetscCall2(mesh.comm(), MatPartitioningSetFromOptions(part));
849 LibmeshPetscCall2(mesh.comm(), MatPartitioningApply(part, is.get()));
850 }
851
852 PetscInt local_size;
853 const PetscInt *indices;
854 LibmeshPetscCall2(mesh.comm(), ISGetLocalSize(is, &local_size));
855 LibmeshPetscCall2(mesh.comm(), ISGetIndices(is, &indices));
856
857 i = 0;
858 for (auto id : pmap.second)
859 {
860 auto & node = mesh.node_ref(id);
861 if (indices[i])
862 node.processor_id() = pmap.first.second;
863 else
864 node.processor_id() = pmap.first.first;
865
866 i++;
867 }
868 LibmeshPetscCall2(mesh.comm(), ISRestoreIndices(is, &indices));
869 }
870#else
871 libmesh_error_msg("PETSc is required");
872#endif
873}
void libmesh_ignore(const Args &...)
PetscErrorCode PetscInt const PetscInt IS * is

References libMesh::MeshTools::build_nodes_to_elem_map(), libMesh::ParallelObject::comm(), libMesh::MeshTools::find_nodal_neighbors(), libMesh::WrappedPetsc< T >::get(), libMesh::is, libMesh::libmesh_ignore(), mesh, libMesh::MeshBase::node_ref(), libMesh::DofObject::processor_id(), and libMesh::Partitioner::processor_pairs_to_interface_nodes().

Referenced by libMesh::Partitioner::set_node_processor_ids().

◆ set_node_processor_ids()

void libMesh::Partitioner::set_node_processor_ids ( MeshBase &  mesh)
staticinherited

This function is called after partitioning to set the processor IDs for the nodes.

By definition, a Node's processor ID is the minimum processor ID for all of the elements which share the node.

Definition at line 876 of file partitioner.C.

877{
878 LOG_SCOPE("set_node_processor_ids()","Partitioner");
879
880 // This function must be run on all processors at once
881 libmesh_parallel_only(mesh.comm());
882
883 // If we have any unpartitioned elements at this
884 // stage there is a problem
885 libmesh_assert (MeshTools::n_elem(mesh.unpartitioned_elements_begin(),
886 mesh.unpartitioned_elements_end()) == 0);
887
888 // Start from scratch here: nodes we used to own may not be
889 // eligible for us to own any more.
890 for (auto & node : mesh.node_ptr_range())
891 {
892 node->processor_id() = DofObject::invalid_processor_id;
893 }
894
895 // Loop over all the active elements
896 for (auto & elem : mesh.active_element_ptr_range())
897 {
898 libmesh_assert(elem);
899
900 libmesh_assert_not_equal_to (elem->processor_id(), DofObject::invalid_processor_id);
901
902 // Consider updating the processor id on this element's nodes
903 for (Node & node : elem->node_ref_range())
904 {
905 processor_id_type & pid = node.processor_id();
906 pid = node.choose_processor_id(pid, elem->processor_id());
907 }
908 }
909
910 // How we finish off the node partitioning depends on our command
911 // line options.
912
913 const bool load_balanced_nodes_linear =
914 libMesh::on_command_line ("--load-balanced-nodes-linear");
915
916 const bool load_balanced_nodes_bfs =
917 libMesh::on_command_line ("--load-balanced-nodes-bfs");
918
919 const bool load_balanced_nodes_petscpartition =
920 libMesh::on_command_line ("--load-balanced-nodes-petscpartitioner");
921
922 unsigned int n_load_balance_options = load_balanced_nodes_linear;
923 n_load_balance_options += load_balanced_nodes_bfs;
924 n_load_balance_options += load_balanced_nodes_petscpartition;
925 libmesh_error_msg_if(n_load_balance_options > 1,
926 "Cannot perform more than one load balancing type at a time");
927
928 if (load_balanced_nodes_linear)
930 else if (load_balanced_nodes_bfs)
932 else if (load_balanced_nodes_petscpartition)
934
935 // Node balancing algorithm will response to assign owned nodes.
936 // We still need to sync PIDs
937 {
938 // For inactive elements, we will have already gotten most of
939 // these nodes, *except* for the case of a parent with a subset
940 // of active descendants which are remote elements. In that
941 // case some of the parent nodes will not have been properly
942 // handled yet on our processor.
943 //
944 // We don't want to inadvertently give one of them an incorrect
945 // processor id, but if we're not in serial then we have to
946 // assign them temporary pids to make querying work, so we'll
947 // save our *valid* pids before assigning temporaries.
948 //
949 // Even in serial we'll want to check and make sure we're not
950 // overwriting valid active node pids with pids from subactive
951 // elements.
952 std::unordered_set<dof_id_type> bad_pids;
953
954 for (auto & node : mesh.node_ptr_range())
955 if (node->processor_id() == DofObject::invalid_processor_id)
956 bad_pids.insert(node->id());
957
958 // If we assign our temporary ids by looping from finer elements
959 // to coarser elements, we'll always get an id from the finest
960 // ghost element we can see, which will usually be "closer" to
961 // the true processor we want to query and so will reduce query
962 // cycles that don't reach that processor.
963
964 // But we can still end up with a query cycle that dead-ends, so
965 // we need to prepare a "push" communication step here.
966
967 const bool is_serial = mesh.is_serial();
968 std::unordered_map
970 std::unordered_map<dof_id_type, processor_id_type>>
971 potential_pids;
972
973 const unsigned int n_levels = MeshTools::n_levels(mesh);
974 for (unsigned int level = n_levels; level > 0; --level)
975 {
976 for (auto & elem : as_range(mesh.level_elements_begin(level-1),
977 mesh.level_elements_end(level-1)))
978 {
979 libmesh_assert_not_equal_to (elem->processor_id(),
981
982 const processor_id_type elem_pid = elem->processor_id();
983
984 // Consider updating the processor id on this element's nodes
985 for (Node & node : elem->node_ref_range())
986 {
987 processor_id_type & pid = node.processor_id();
988 if (bad_pids.count(node.id()))
989 pid = node.choose_processor_id(pid, elem_pid);
990 else if (!is_serial)
991 potential_pids[elem_pid][node.id()] = pid;
992 }
993 }
994 }
995
996 if (!is_serial)
997 {
998 std::unordered_map
1000 std::vector<std::pair<dof_id_type, processor_id_type>>>
1001 potential_pids_vecs;
1002
1003 for (auto & pair : potential_pids)
1004 potential_pids_vecs[pair.first].assign(pair.second.begin(), pair.second.end());
1005
1006 auto pids_action_functor =
1007 [& mesh, & bad_pids]
1008 (processor_id_type /* src_pid */,
1009 const std::vector<std::pair<dof_id_type, processor_id_type>> & data)
1010 {
1011 for (auto pair : data)
1012 {
1013 Node & node = mesh.node_ref(pair.first);
1014 processor_id_type & pid = node.processor_id();
1015 if (const auto it = bad_pids.find(pair.first);
1016 it != bad_pids.end())
1017 {
1018 pid = pair.second;
1019 bad_pids.erase(it);
1020 }
1021 else
1022 pid = node.choose_processor_id(pid, pair.second);
1023 }
1024 };
1025
1027 (mesh.comm(), potential_pids_vecs, pids_action_functor);
1028
1029 // Using default libMesh options, we'll just need to sync
1030 // between processors now. The catch here is that we can't
1031 // initially trust Node::choose_processor_id() because some
1032 // of those node processor ids are the temporary ones.
1033 CorrectProcIds correct_pids(mesh, bad_pids);
1035 (mesh, mesh.elements_begin(), mesh.elements_end(),
1037 correct_pids);
1038
1039 // But once we've got all the non-temporary pids synced, we
1040 // may need to sync again to get any pids on nodes only
1041 // connected to subactive elements, for which *only*
1042 // "temporary" pids are possible.
1043 bad_pids.clear();
1045 (mesh,
1046 mesh.elements_begin(), mesh.elements_end(),
1048 correct_pids);
1049 }
1050 }
1051
1052 // We can't assert that all nodes are connected to elements, because
1053 // a DistributedMesh with NodeConstraints might have pulled in some
1054 // remote nodes solely for evaluating those constraints.
1055 // MeshTools::libmesh_assert_connected_nodes(mesh);
1056
1057#ifdef DEBUG
1058 MeshTools::libmesh_assert_valid_procids<Node>(mesh);
1059 //MeshTools::libmesh_assert_canonical_node_procids(mesh);
1060#endif
1061}
The DofObject defines an abstract base class for objects that have degrees of freedom associated with...
Definition dof_object.h:55
virtual bool is_serial() const
Definition mesh_base.h:357
A Node is like a Point, but with more information.
Definition node.h:55
processor_id_type choose_processor_id(processor_id_type pid1, processor_id_type pid2) const
Return which of pid1 and pid2 would be preferred by the current load-balancing heuristic applied to t...
Definition node.C:78
static void set_interface_node_processor_ids_petscpartitioner(MeshBase &mesh)
Nodes on the partitioning interface is partitioned into two groups using a PETSc partitioner for each...
static void set_interface_node_processor_ids_BFS(MeshBase &mesh)
Nodes on the partitioning interface is clustered into two groups BFS (Breadth First Search)scheme for...
static void set_interface_node_processor_ids_linear(MeshBase &mesh)
Nodes on the partitioning interface is linearly assigned to each pair of processors.
void push_parallel_vector_data(const Communicator &comm, MapToVectors &&data, const ActionFunctor &act_on_data)
unsigned int n_levels(const MeshBase &mesh)
Definition mesh_tools.C:826
void sync_node_data_by_element_id(MeshBase &mesh, const MeshBase::const_element_iterator &range_begin, const MeshBase::const_element_iterator &range_end, const ElemCheckFunctor &elem_check, const NodeCheckFunctor &node_check, SyncFunctor &sync)
Synchronize data about a range of ghost nodes uniquely identified by an element id and local node id,...
bool on_command_line(std::string arg)
Definition libmesh.C:934

References libMesh::as_range(), libMesh::Node::choose_processor_id(), libMesh::ParallelObject::comm(), libMesh::DofObject::id(), libMesh::DofObject::invalid_processor_id, libMesh::MeshBase::is_serial(), libMesh::libmesh_assert(), mesh, libMesh::MeshTools::n_elem(), libMesh::MeshTools::n_levels(), libMesh::MeshBase::node_ref(), libMesh::on_command_line(), libMesh::DofObject::processor_id(), TIMPI::push_parallel_vector_data(), libMesh::Partitioner::set_interface_node_processor_ids_BFS(), libMesh::Partitioner::set_interface_node_processor_ids_linear(), libMesh::Partitioner::set_interface_node_processor_ids_petscpartitioner(), and libMesh::Parallel::sync_node_data_by_element_id().

Referenced by libMesh::UnstructuredMesh::all_first_order(), libMesh::Partitioner::partition(), libMesh::XdrIO::read(), libMesh::Partitioner::repartition(), and libMesh::BoundaryInfo::sync().

◆ set_parent_processor_ids()

void libMesh::Partitioner::set_parent_processor_ids ( MeshBase &  mesh)
staticinherited

This function is called after partitioning to set the processor IDs for the inactive parent elements.

A parent's processor ID is the same as its first child.

Definition at line 450 of file partitioner.C.

451{
452 // Ignore the parameter when !LIBMESH_ENABLE_AMR
454
455 LOG_SCOPE("set_parent_processor_ids()", "Partitioner");
456
457#ifdef LIBMESH_ENABLE_AMR
458
459 // If the mesh is serial we have access to all the elements,
460 // in particular all the active ones. We can therefore set
461 // the parent processor ids indirectly through their children, and
462 // set the subactive processor ids while examining their active
463 // ancestors.
464 // By convention a parent is assigned to the minimum processor
465 // of all its children, and a subactive is assigned to the processor
466 // of its active ancestor.
467 if (mesh.is_serial())
468 {
469 for (auto & elem : mesh.active_element_ptr_range())
470 {
471 // First set descendents
472 std::vector<Elem *> subactive_family;
473 elem->total_family_tree(subactive_family);
474 for (const auto & f : subactive_family)
475 f->processor_id() = elem->processor_id();
476
477 // Then set ancestors
478 Elem * parent = elem->parent();
479
480 while (parent)
481 {
482 // invalidate the parent id, otherwise the min below
483 // will not work if the current parent id is less
484 // than all the children!
485 parent->invalidate_processor_id();
486
487 for (auto & child : parent->child_ref_range())
488 {
489 libmesh_assert(!child.is_remote());
490 libmesh_assert_not_equal_to (child.processor_id(), DofObject::invalid_processor_id);
491 parent->processor_id() = std::min(parent->processor_id(),
492 child.processor_id());
493 }
494 parent = parent->parent();
495 }
496 }
497 }
498
499 // When the mesh is parallel we cannot guarantee that parents have access to
500 // all their children.
501 else
502 {
503 // Setting subactive processor ids is easy: we can guarantee
504 // that children have access to all their parents.
505
506 // Loop over all the active elements in the mesh
507 for (auto & child : mesh.active_element_ptr_range())
508 {
509 std::vector<Elem *> subactive_family;
510 child->total_family_tree(subactive_family);
511 for (const auto & f : subactive_family)
512 f->processor_id() = child->processor_id();
513 }
514
515 // When the mesh is parallel we cannot guarantee that parents have access to
516 // all their children.
517
518 // We will use a brute-force approach here. Each processor finds its parent
519 // elements and sets the parent pid to the minimum of its
520 // semilocal descendants.
521 // A global reduction is then performed to make sure the true minimum is found.
522 // As noted, this is required because we cannot guarantee that a parent has
523 // access to all its children on any single processor.
524 libmesh_parallel_only(mesh.comm());
525 libmesh_assert(MeshTools::n_elem(mesh.unpartitioned_elements_begin(),
526 mesh.unpartitioned_elements_end()) == 0);
527
528 const dof_id_type max_elem_id = mesh.max_elem_id();
529
530 std::vector<processor_id_type>
531 parent_processor_ids (std::min(communication_blocksize,
532 max_elem_id));
533
534 for (dof_id_type blk=0, last_elem_id=0; last_elem_id<max_elem_id; blk++)
535 {
536 last_elem_id =
537 std::min(static_cast<dof_id_type>((blk+1)*communication_blocksize),
538 max_elem_id);
539 const dof_id_type first_elem_id = blk*communication_blocksize;
540
541 std::fill (parent_processor_ids.begin(),
542 parent_processor_ids.end(),
544
545 // first build up local contributions to parent_processor_ids
546 bool have_parent_in_block = false;
547
548 for (auto & parent : as_range(mesh.ancestor_elements_begin(),
549 mesh.ancestor_elements_end()))
550 {
551 const dof_id_type parent_idx = parent->id();
552 libmesh_assert_less (parent_idx, max_elem_id);
553
554 if ((parent_idx >= first_elem_id) &&
555 (parent_idx < last_elem_id))
556 {
557 have_parent_in_block = true;
559
560 std::vector<const Elem *> active_family;
561 parent->active_family_tree(active_family);
562 for (const auto & f : active_family)
563 parent_pid = std::min (parent_pid, f->processor_id());
564
565 const dof_id_type packed_idx = parent_idx - first_elem_id;
566 libmesh_assert_less (packed_idx, parent_processor_ids.size());
567
568 parent_processor_ids[packed_idx] = parent_pid;
569 }
570 }
571
572 // then find the global minimum
573 mesh.comm().min (parent_processor_ids);
574
575 // and assign the ids, if we have a parent in this block.
576 if (have_parent_in_block)
577 for (auto & parent : as_range(mesh.ancestor_elements_begin(),
578 mesh.ancestor_elements_end()))
579 {
580 const dof_id_type parent_idx = parent->id();
581
582 if ((parent_idx >= first_elem_id) &&
583 (parent_idx < last_elem_id))
584 {
585 const dof_id_type packed_idx = parent_idx - first_elem_id;
586 libmesh_assert_less (packed_idx, parent_processor_ids.size());
587
588 const processor_id_type parent_pid =
589 parent_processor_ids[packed_idx];
590
591 libmesh_assert_not_equal_to (parent_pid, DofObject::invalid_processor_id);
592
593 parent->processor_id() = parent_pid;
594 }
595 }
596 }
597 }
598
599#endif // LIBMESH_ENABLE_AMR
600}
void min(const T &r, T &o, Request &req) const
void invalidate_processor_id()
Sets the processor id to invalid_processor_id.
Definition dof_object.h:785
dof_id_type id() const
Definition dof_object.h:833
This is the base class from which all geometric element types are derived.
Definition elem.h:96
const Elem * parent() const
Definition elem.h:3168
void total_family_tree(std::vector< const Elem * > &family, bool reset=true) const
Same as the family_tree() member, but also adds any subactive descendants.
Definition elem.C:2228
void active_family_tree(std::vector< const Elem * > &active_family, bool reset=true) const
Same as the family_tree() member, but only adds the active children.
Definition elem.C:2244
virtual dof_id_type max_elem_id() const =0
static const dof_id_type communication_blocksize
The blocksize to use when doing blocked parallel communication.

References libMesh::as_range(), libMesh::Elem::child_ref_range(), libMesh::ParallelObject::comm(), libMesh::Partitioner::communication_blocksize, libMesh::DofObject::invalid_processor_id, libMesh::DofObject::invalidate_processor_id(), libMesh::MeshBase::is_serial(), libMesh::libmesh_assert(), libMesh::libmesh_ignore(), libMesh::MeshBase::max_elem_id(), mesh, libMesh::Parallel::Communicator::min(), libMesh::MeshTools::n_elem(), libMesh::Elem::parent(), libMesh::DofObject::processor_id(), and libMesh::Elem::total_family_tree().

Referenced by libMesh::Partitioner::partition(), and libMesh::Partitioner::repartition().

◆ single_partition()

bool libMesh::Partitioner::single_partition ( MeshBase &  mesh)
protectedinherited

Trivially "partitions" the mesh for one processor.

Simply loops through the elements and assigns all of them to processor 0. Is is provided as a separate function so that derived classes may use it without reimplementing it.

Returns true iff any processor id was changed.

Definition at line 306 of file partitioner.C.

307{
308 bool changed_pid =
309 this->single_partition_range(mesh.elements_begin(),
310 mesh.elements_end());
311
312 // If we have a distributed mesh with an empty rank (or where rank
313 // 0 has only its own component of a disconnected mesh, I guess),
314 // that rank might need to be informed of a change.
315 mesh.comm().max(changed_pid);
316
317 // We may need to redistribute, in case someone (like our unit
318 // tests) is doing something silly (like moving a whole
319 // already-distributed mesh back onto rank 0).
320 if (changed_pid)
322
323 return changed_pid;
324}
void max(const T &r, T &o, Request &req) const
bool single_partition_range(MeshBase::element_iterator it, MeshBase::element_iterator end)
Slightly generalized version of single_partition which acts on a range of elements defined by the pai...

References libMesh::ParallelObject::comm(), libMesh::Parallel::Communicator::max(), mesh, libMesh::MeshBase::redistribute(), and libMesh::Partitioner::single_partition_range().

Referenced by libMesh::SubdomainPartitioner::_do_partition(), libMesh::Partitioner::partition(), and libMesh::Partitioner::repartition().

◆ single_partition_range()

bool libMesh::Partitioner::single_partition_range ( MeshBase::element_iterator  it,
MeshBase::element_iterator  end 
)
protectedinherited

Slightly generalized version of single_partition which acts on a range of elements defined by the pair of iterators (it, end).

Returns true iff any processor id was changed.

Definition at line 328 of file partitioner.C.

330{
331 LOG_SCOPE("single_partition_range()", "Partitioner");
332
333 bool changed_pid = false;
334
335 for (auto & elem : as_range(it, end))
336 {
337 if (elem->processor_id())
338 changed_pid = true;
339 elem->processor_id() = 0;
340
341 // Assign all this element's nodes to processor 0 as well.
342 for (Node & node : elem->node_ref_range())
343 {
344 if (node.processor_id())
345 changed_pid = true;
346 node.processor_id() = 0;
347 }
348 }
349
350 return changed_pid;
351}

References libMesh::as_range(), and libMesh::DofObject::processor_id().

Referenced by NonManifoldTestPartitioner::_do_partition(), OverlappingTestPartitioner::_do_partition(), libMesh::CentroidPartitioner::partition_range(), libMesh::LinearPartitioner::partition_range(), libMesh::MappedSubdomainPartitioner::partition_range(), libMesh::MetisPartitioner::partition_range(), libMesh::SFCPartitioner::partition_range(), and libMesh::Partitioner::single_partition().

◆ type()

virtual PartitionerType libMesh::ParmetisPartitioner::type ( ) const
overridevirtual

Reimplemented from libMesh::Partitioner.

Member Data Documentation

◆ _dual_graph

std::vector<std::vector<dof_id_type> > libMesh::Partitioner::_dual_graph
protectedinherited

A dual graph corresponds to the mesh, and it is typically used in paritioner.

A vertex represents an element, and its neighbors are the element neighbors.

Definition at line 332 of file partitioner.h.

Referenced by libMesh::Partitioner::build_graph().

◆ _global_index_by_pid_map

std::unordered_map<dof_id_type, dof_id_type> libMesh::Partitioner::_global_index_by_pid_map
protectedinherited

Maps active element ids into a contiguous range, as needed by parallel partitioner.

Definition at line 316 of file partitioner.h.

Referenced by libMesh::Partitioner::_find_global_index_by_pid_map(), libMesh::Partitioner::assign_partitioning(), and libMesh::Partitioner::build_graph().

◆ _local_id_to_elem

std::vector<Elem *> libMesh::Partitioner::_local_id_to_elem
protectedinherited

Definition at line 335 of file partitioner.h.

Referenced by libMesh::Partitioner::build_graph().

◆ _n_active_elem_on_proc

std::vector<dof_id_type> libMesh::Partitioner::_n_active_elem_on_proc
protectedinherited

The number of active elements on each processor.

Note
ParMETIS requires that each processor have some active elements; it will abort if any processor passes a nullptr _part array.

Definition at line 325 of file partitioner.h.

Referenced by libMesh::Partitioner::_find_global_index_by_pid_map(), libMesh::Partitioner::assign_partitioning(), and libMesh::Partitioner::build_graph().

◆ _pmetis

std::unique_ptr<ParmetisHelper> libMesh::ParmetisPartitioner::_pmetis
private

Pointer to the Parmetis-specific data structures.

Lets us avoid including parmetis.h here.

Definition at line 130 of file parmetis_partitioner.h.

◆ _weights

ErrorVector* libMesh::Partitioner::_weights
protectedinherited

The weights that might be used for partitioning.

Definition at line 311 of file partitioner.h.

Referenced by libMesh::MetisPartitioner::attach_weights(), and libMesh::MetisPartitioner::partition_range().

◆ communication_blocksize

const dof_id_type libMesh::Partitioner::communication_blocksize
staticprotectedinherited
Initial value:
=
dof_id_type(1000000)

The blocksize to use when doing blocked parallel communication.

This limits the maximum vector size which can be used in a single communication step.

Definition at line 288 of file partitioner.h.

Referenced by libMesh::Partitioner::set_parent_processor_ids().


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