https://mooseframework.inl.gov
Loading...
Searching...
No Matches
PetscExternalPartitioner.C
Go to the documentation of this file.
1//* This file is part of the MOOSE framework
2//* https://mooseframework.inl.gov
3//*
4//* All rights reserved, see COPYRIGHT for full restrictions
5//* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6//*
7//* Licensed under LGPL 2.1, please see LICENSE for details
8//* https://www.gnu.org/licenses/lgpl-2.1.html
9
11
12#include "GeneratedMesh.h"
13#include "MooseApp.h"
14
15#include "libmesh/mesh_tools.h"
16#include "libmesh/linear_partitioner.h"
17#include "libmesh/elem.h"
18#include "libmesh/mesh_base.h"
19#include "libmesh/petsc_solver_exception.h"
20#include <algorithm>
21#include <memory>
22
24
27{
29
30 MooseEnum partPackage("parmetis ptscotch chaco party hierarch", "parmetis", false);
31
32 params.addParam<MooseEnum>("part_package",
33 partPackage,
34 "The external package is used for partitioning the mesh via PETSc");
35
36 params.addParam<processor_id_type>(
37 "num_cores_per_compute_node",
38 1,
39 "Number of cores per compute node for hierarchical partitioning");
40
41 params.addParam<bool>("apply_element_weight",
42 false,
43 "Indicate if we are going to apply element weights to partitioners");
44
45 params.addParam<bool>(
46 "apply_side_weight", false, "Indicate if we are going to apply side weights to partitioners");
47
49 "Partition mesh using external packages via PETSc MatPartitioning interface");
50
51 return params;
52}
53
55 : MoosePartitioner(params),
56 _part_package(params.get<MooseEnum>("part_package")),
57 _apply_element_weight(params.get<bool>("apply_element_weight")),
58 _apply_side_weight(params.get<bool>("apply_side_weight")),
59 _num_parts_per_compute_node(params.get<processor_id_type>("num_cores_per_compute_node"))
60{
62 (_part_package == "chaco" || _part_package == "party"))
63 mooseError(_part_package, " does not support weighted graph");
64}
65
66std::unique_ptr<Partitioner>
68{
69 return _app.getFactory().clone(*this);
70}
71
72void
74{
75 // Temporarily cache the old partition method
76 auto old_partitioner = std::move(mesh.partitioner());
77 // Create a linear partitioner
78 mesh.partitioner() = std::make_unique<libMesh::LinearPartitioner>();
79 // Partition mesh
80 mesh.partition(n_processors());
81 // Restore the old partition
82 mesh.partitioner() = std::move(old_partitioner);
83}
84
85void
86PetscExternalPartitioner::partition(MeshBase & mesh, const unsigned int n_parts)
87{
88 // We want to use a parallel partitioner that requires a distributed graph
89 // Simply calling a linear partitioner provides us the distributed graph
90 // We should not do anything when using a distributed mesh since the mesh itself
91 // is already distributed
92 // When n_parts=1, we do not need to run any partitioner, instead, let libmesh
93 // handle this
94 if (mesh.is_replicated() && n_parts > 1)
96
97 if (!isParamSetByUser("part_package") && (mesh.n_elem() / n_parts < 28) &&
98 _part_package == "parmetis")
99 {
100 Moose::out
101 << "Average number of elements per partition (" << mesh.n_elem() / n_parts
102 << ") is less than 28. We are switching from ParMETIS to PTScotch for the partitioning."
103 << std::endl;
104 _part_package = "ptscotch";
105 }
106
107 Partitioner::partition(mesh, n_parts);
108}
109
110void
111PetscExternalPartitioner::_do_partition(MeshBase & mesh, const unsigned int n_parts)
112{
114
115 dof_id_type num_edges, num_local_elems, local_elem_id, nj, side;
116 std::vector<dof_id_type> side_weights;
117 std::vector<dof_id_type> elem_weights;
118
119 // Call libmesh to build the dual graph of mesh
121 num_local_elems = _dual_graph.size();
122 // PETSc requires that each row be sorted
123 for (auto & row : _dual_graph)
124 std::sort(row.begin(), row.end());
125
126 elem_weights.clear();
128 elem_weights.resize(num_local_elems);
129
130 num_edges = 0;
131 // compute element weight
132 for (dof_id_type k = 0; k < num_local_elems; k++)
133 {
134 num_edges += _dual_graph[k].size();
136 {
137 // Get the original element
138 mooseAssert(k < static_cast<dof_id_type>(_local_id_to_elem.size()),
139 "Local element id " << k << " is not smaller than " << _local_id_to_elem.size());
140 auto elem = _local_id_to_elem[k];
141
142 elem_weights[k] = computeElementWeight(*elem);
143 }
144 }
145
146 side_weights.clear();
147 // Edge weights represent the communication
149 side_weights.resize(num_edges);
150
151 // Besides face neighbors, the graph links lower-dimensional elements to their interior parents,
152 // elements to the NodeElems constraining their spline nodes, and elements to the active
153 // children of a refined neighbor. Such a link may join a local element to a remote one, so all
154 // ranks must agree on whether these links are expected
155 bool has_non_face_links = !mesh.get_constraint_rows().empty();
156 for (const auto * const elem : mesh.active_local_element_ptr_range())
157 {
158 if (elem->interior_parent())
159 has_non_face_links = true;
160 for (const auto * const neighbor : elem->neighbor_ptr_range())
161 if (neighbor && !neighbor->active())
162 has_non_face_links = true;
163 if (has_non_face_links)
164 break;
165 }
166 comm().max(has_non_face_links);
167
168 local_elem_id = 0;
169 nj = 0;
170 std::map<dof_id_type, dof_id_type> global_index_to_weight;
171 for (auto & row : _dual_graph)
172 {
173 mooseAssert(local_elem_id < static_cast<dof_id_type>(_local_id_to_elem.size()),
174 "Local element id " << local_elem_id << " is not smaller than "
175 << _local_id_to_elem.size());
176 global_index_to_weight.clear();
177 auto elem = _local_id_to_elem[local_elem_id];
178 unsigned int n_neighbors = 0;
179
180 side = 0;
181 for (auto neighbor : elem->neighbor_ptr_range())
182 {
183 // Skip boundary sides since they do not connect to
184 // anything.
185 if (neighbor != nullptr && neighbor->active())
186 {
187 global_index_to_weight.emplace(libmesh_map_find(_global_index_by_pid_map, neighbor->id()),
188 _apply_side_weight ? computeSideWeight(*elem, side) : 1);
189 n_neighbors++;
190 }
191
192 side++;
193 }
194
195 // Graph indices missing from the map are the links that do not cross a side
196 unsigned int n_face_neighbors = 0;
197 for (const auto global_index : row)
198 {
199 const auto it = global_index_to_weight.find(global_index);
200 const bool is_face_neighbor = it != global_index_to_weight.end();
201 if (is_face_neighbor)
202 n_face_neighbors++;
203 // Links that do not cross a side have no side to pass to computeSideWeight(), so they get
204 // its default weight of 1
206 side_weights[nj++] = is_face_neighbor ? it->second : 1;
207 }
208 if (n_face_neighbors != n_neighbors || (!has_non_face_links && row.size() != n_neighbors))
210 "Cannot construct dual graph correctly since the number of neighbors is inconsistent");
211
212 local_elem_id++;
213 }
214
215 std::vector<dof_id_type> partition;
216 // Partition graph
219 elem_weights,
220 side_weights,
221 n_parts,
224 partition);
225 // Assign partition to mesh
227}
228
229void
231 const std::vector<std::vector<dof_id_type>> & graph,
232 const std::vector<dof_id_type> & elem_weights,
233 const std::vector<dof_id_type> & side_weights,
234 const dof_id_type num_parts,
235 const dof_id_type num_parts_per_compute_node,
236 const std::string & part_package,
237 std::vector<dof_id_type> & partition)
238{
239 Mat dual;
240 PetscInt num_local_elems, num_elems, *xadj = nullptr, *adjncy = nullptr, i, *values = nullptr,
241 *petsc_elem_weights = nullptr;
242 const PetscInt * parts;
243 MatPartitioning part;
244 IS is;
245
246 // Number of local elements
247 num_elems = num_local_elems = graph.size();
248 // Figure out the total of elements
249 comm.sum(num_elems);
250
251 LibmeshPetscCallA(comm.get(), PetscCalloc1(num_local_elems + 1, &xadj));
252
253 num_local_elems = 0;
254 xadj[0] = 0;
255 for (auto & row : graph)
256 {
257 num_local_elems++;
258 xadj[num_local_elems] = xadj[num_local_elems - 1] + row.size();
259 }
260
261 LibmeshPetscCallA(comm.get(), PetscCalloc1(xadj[num_local_elems], &adjncy));
262
263 // Fill up adjacency
264 i = 0;
265 for (auto & row : graph)
266 for (const auto elem : row)
267 adjncy[i++] = elem;
268
269 // If there are no neighbors at all, no side weights should be proivded
270 if (!i)
271 {
272 mooseAssert(!side_weights.size(),
273 "No side weights should be provided since there are no neighbors at all");
274 }
275
276 /*
277 * Whether side (edge) weights are in use has to be decided collectively, not from this rank's
278 * own side_weights vector: a rank with no local elements has an empty side_weights vector
279 * regardless of whether side weighting is enabled, so its local size cannot distinguish
280 * "weighting is off" from "weighting is on, but this rank has nothing to weight."
281 */
282 bool use_side_weights = !side_weights.empty();
283 comm.max(use_side_weights);
284
285 /*
286 * Copy over weights. A rank with nothing to weight must still supply a non-null array once
287 * side weighting is enabled anywhere: PT-Scotch's SCOTCH_dgraphBuild requires every rank to
288 * agree on whether each optional array is null, and a zero-size allocation is not guaranteed
289 * to return a non-null pointer.
290 */
291 if (use_side_weights)
292 {
293 mooseAssert((PetscInt)side_weights.size() == i,
294 "Side weight size " << side_weights.size()
295 << " does not match with adjacency matrix size " << i);
296 LibmeshPetscCallA(comm.get(),
297 PetscCalloc1(std::max<std::size_t>(side_weights.size(), 1), &values));
298 i = 0;
299 for (auto weight : side_weights)
300 values[i++] = weight;
301 }
302
303 LibmeshPetscCallA(
304 comm.get(),
305 MatCreateMPIAdj(comm.get(), num_local_elems, num_elems, xadj, adjncy, values, &dual));
306
307 LibmeshPetscCallA(comm.get(), MatPartitioningCreate(comm.get(), &part));
308 if (use_side_weights)
309 // Set based on whether side weighting is enabled anywhere, not on whether this rank's
310 // 'values' pointer happens to be null, for the same collective-agreement reason as above
311 LibmeshPetscCallA(comm.get(), MatPartitioningSetUseEdgeWeights(part, PETSC_TRUE));
312 LibmeshPetscCallA(comm.get(), MatPartitioningSetAdjacency(part, dual));
313
314 if (!num_local_elems)
315 mooseAssert(!elem_weights.size(),
316 "No element weights should be provided since there are no elements at all");
317
318 // Element weights are subject to the same collective-agreement requirement as side weights
319 bool use_elem_weights = !elem_weights.empty();
320 comm.max(use_elem_weights);
321
322 // Handle element weights
323 if (use_elem_weights)
324 {
325 mooseAssert((PetscInt)elem_weights.size() == num_local_elems,
326 "Element weight size " << elem_weights.size()
327 << " does not match with the number of local elements"
328 << num_local_elems);
329
330 LibmeshPetscCallA(
331 comm.get(),
332 PetscCalloc1(std::max<std::size_t>(elem_weights.size(), 1), &petsc_elem_weights));
333 i = 0;
334 for (auto weight : elem_weights)
335 petsc_elem_weights[i++] = weight;
336
337 LibmeshPetscCallA(comm.get(), MatPartitioningSetVertexWeights(part, petsc_elem_weights));
338 }
339
340 LibmeshPetscCallA(comm.get(), MatPartitioningSetNParts(part, num_parts));
341 mooseAssert(part_package != "party", "PETSc-3.9.3 or higher is required for using party");
342 mooseAssert(part_package != "chaco", "PETSc-3.9.0 or higher is required for using chaco");
343 LibmeshPetscCallA(comm.get(), MatPartitioningSetType(part, part_package.c_str()));
344 if (part_package == "hierarch")
345 LibmeshPetscCallA(comm.get(),
346 MatPartitioningHierarchicalSetNfineparts(part, num_parts_per_compute_node));
347
348 LibmeshPetscCallA(comm.get(), MatPartitioningSetFromOptions(part));
349 LibmeshPetscCallA(comm.get(), MatPartitioningApply(part, &is));
350
351 LibmeshPetscCallA(comm.get(), ISGetIndices(is, &parts));
352
353 partition.resize(num_local_elems);
354 for (i = 0; i < num_local_elems; i++)
355 partition[i] = parts[i];
356
357 LibmeshPetscCallA(comm.get(), ISRestoreIndices(is, &parts));
358 LibmeshPetscCallA(comm.get(), MatPartitioningDestroy(&part));
359 LibmeshPetscCallA(comm.get(), MatDestroy(&dual));
360 LibmeshPetscCallA(comm.get(), ISDestroy(&is));
361}
362
363dof_id_type
365{
366 return 1;
367}
368
369dof_id_type
370PetscExternalPartitioner::computeSideWeight(Elem & /*elem*/, unsigned int /*side*/) const
371{
372 return 1;
373}
std::array< Real, 2 > values
Definition MortarUtils.C:52
registerMooseObject("MooseApp", PetscExternalPartitioner)
std::unique_ptr< T > clone(const T &object)
Clones the object object.
Definition Factory.h:323
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system.
void addParam(const std::string &name, const S &value, const std::string &doc_string)
These methods add an optional parameter and a documentation string to the InputParameters object.
void addClassDescription(const std::string &doc_string)
This method adds a description of the class that will be displayed in the input file syntax dump.
Factory & getFactory()
Retrieve a writable reference to the Factory associated with this App.
Definition MooseApp.h:404
bool isParamSetByUser(const std::string &name) const
Test if the supplied parameter is set by a user, as opposed to not set or set to default.
Definition MooseBase.h:205
void mooseError(Args &&... args) const
Emits an error prefixed with object name and type and optionally a file path to the top-level block p...
Definition MooseBase.h:271
This is a "smart" enum class intended to replace many of the shortcomings in the C++ enum type It sho...
Definition MooseEnum.h:55
MooseApp & _app
The MOOSE application this is associated with.
Definition MooseBase.h:375
Base class for MOOSE partitioner.
static InputParameters validParams()
Partitions a mesh using external petsc partitioners such as parmetis, ptscotch, chaco,...
virtual dof_id_type computeElementWeight(Elem &elm) const
void preLinearPartition(MeshBase &mesh)
PetscExternalPartitioner(const InputParameters &params)
virtual dof_id_type computeSideWeight(Elem &elem, unsigned int side) const
virtual void initialize(MeshBase &)
Called immediately before partitioning.
virtual void _do_partition(MeshBase &mesh, const unsigned int n) override
virtual void partition(MeshBase &mesh, const unsigned int n) override
static void partitionGraph(const Parallel::Communicator &comm, const std::vector< std::vector< dof_id_type > > &graph, const std::vector< dof_id_type > &elem_weights, const std::vector< dof_id_type > &side_weights, const dof_id_type num_parts, const dof_id_type num_parts_per_compute_node, const std::string &part_package, std::vector< dof_id_type > &partition)
processor_id_type _num_parts_per_compute_node
static InputParameters validParams()
virtual std::unique_ptr< Partitioner > clone() const override
void max(const T &r, T &o, Request &req) const
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
std::vector< std::vector< dof_id_type > > _dual_graph
virtual void build_graph(const MeshBase &mesh)
void assign_partitioning(MeshBase &mesh, const std::vector< dof_id_type > &parts)
std::vector< Elem * > _local_id_to_elem
MeshBase & mesh