deal.II version GIT relicensing-6834-g5b78e6bcdf 2026-10-01 11:20:01+00:00
\(\newcommand{\dealvcentcolon}{\mathrel{\mathop{:}}}\) \(\newcommand{\dealcoloneq}{\dealvcentcolon\mathrel{\mkern-1.2mu}=}\) \(\newcommand{\jump}[1]{\left[\!\left[ #1 \right]\!\right]}\) \(\newcommand{\average}[1]{\left\{\!\left\{ #1 \right\}\!\right\}}\)
Loading...
Searching...
No Matches
sparsity_tools.cc
Go to the documentation of this file.
1// -----------------------------------------------------------------------------
2//
3// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception OR LGPL-2.1-or-later
4// Copyright (C) 2008 - 2025 by the deal.II authors
5//
6// This file is part of the deal.II library.
7//
8// Detailed license information governing the source code and contributions
9// can be found in LICENSE.md and CONTRIBUTING.md at the top level directory.
10//
11// -----------------------------------------------------------------------------
12
13
15
19
20#include <algorithm>
21#include <functional>
22#include <memory>
23#include <set>
24
25#ifdef DEAL_II_WITH_MPI
26# include <deal.II/base/mpi.h>
28
31#endif
32
33#ifdef DEAL_II_WITH_METIS
34extern "C"
35{
36# include <metis.h>
37}
38#endif
39
40#ifdef DEAL_II_TRILINOS_WITH_ZOLTAN
41# include <zoltan_cpp.h>
42#endif
43
44#include <string>
45
46
48
49namespace SparsityTools
50{
51 namespace
52 {
53 void
54 partition_metis(const SparsityPattern &sparsity_pattern,
55 const std::vector<unsigned int> &cell_weights,
56 const unsigned int n_partitions,
57 std::vector<unsigned int> &partition_indices)
58 {
59 // Make sure that METIS is actually
60 // installed and detected
61#ifndef DEAL_II_WITH_METIS
62 (void)sparsity_pattern;
63 (void)cell_weights;
64 (void)n_partitions;
65 (void)partition_indices;
67#else
68
69 // Generate the data structures for METIS. Note that this is particularly
70 // simple, since METIS wants exactly our compressed row storage format.
71 // We only have to set up a few auxiliary arrays and convert from our
72 // unsigned cell weights to signed ones.
73 idx_t n = static_cast<signed int>(sparsity_pattern.n_rows());
74
75 idx_t ncon = 1; // number of balancing constraints (should be >0)
76
77 // We can not partition n items into more than n parts. METIS will
78 // generate non-sensical output (everything is owned by a single process)
79 // and complain with a message (but won't return an error code!):
80 // ***Cannot bisect a graph with 0 vertices!
81 // ***You are trying to partition a graph into too many parts!
82 idx_t nparts =
83 std::min(n,
84 static_cast<idx_t>(
85 n_partitions)); // number of subdomains to create
86
87 // use default options for METIS
88 idx_t options[METIS_NOPTIONS];
89 METIS_SetDefaultOptions(options);
90
91 // one more nuisance: we have to copy our own data to arrays that store
92 // signed integers :-(
93 std::vector<idx_t> int_rowstart(1);
94 int_rowstart.reserve(sparsity_pattern.n_rows() + 1);
95 std::vector<idx_t> int_colnums;
96 int_colnums.reserve(sparsity_pattern.n_nonzero_elements());
97 for (SparsityPattern::size_type row = 0; row < sparsity_pattern.n_rows();
98 ++row)
99 {
100 for (SparsityPattern::iterator col = sparsity_pattern.begin(row);
101 col < sparsity_pattern.end(row);
102 ++col)
103 int_colnums.push_back(col->column());
104 int_rowstart.push_back(int_colnums.size());
105 }
106
107 std::vector<idx_t> int_partition_indices(sparsity_pattern.n_rows());
108
109 // Set up cell weighting option
110 std::vector<idx_t> int_cell_weights;
111 if (cell_weights.size() > 0)
112 {
113 Assert(cell_weights.size() == sparsity_pattern.n_rows(),
114 ExcDimensionMismatch(cell_weights.size(),
115 sparsity_pattern.n_rows()));
116 int_cell_weights.resize(cell_weights.size());
117 std::copy(cell_weights.begin(),
118 cell_weights.end(),
119 int_cell_weights.begin());
120 }
121 // Set a pointer to the optional cell weighting information.
122 // METIS expects a null pointer if there are no weights to be considered.
123 idx_t *const p_int_cell_weights =
124 (cell_weights.size() > 0 ? int_cell_weights.data() : nullptr);
125
126
127 // Make use of METIS' error code.
128 int ierr;
129
130 // Select which type of partitioning to create
131
132 // Use recursive if the number of partitions is less than or equal to 8
133 idx_t dummy; // output: # of edges cut by the resulting partition
134 if (nparts <= 8)
135 ierr = METIS_PartGraphRecursive(&n,
136 &ncon,
137 int_rowstart.data(),
138 int_colnums.data(),
139 p_int_cell_weights,
140 nullptr,
141 nullptr,
142 &nparts,
143 nullptr,
144 nullptr,
145 options,
146 &dummy,
147 int_partition_indices.data());
148
149 // Otherwise use kway
150 else
151 ierr = METIS_PartGraphKway(&n,
152 &ncon,
153 int_rowstart.data(),
154 int_colnums.data(),
155 p_int_cell_weights,
156 nullptr,
157 nullptr,
158 &nparts,
159 nullptr,
160 nullptr,
161 options,
162 &dummy,
163 int_partition_indices.data());
164
165 // If metis returns normally, an error code METIS_OK=1 is returned from
166 // the above functions (see metish.h)
167 AssertThrow(ierr == 1, ExcMETISError(ierr));
168
169 // now copy back generated indices into the output array
170 std::copy(int_partition_indices.begin(),
171 int_partition_indices.end(),
172 partition_indices.begin());
173#endif
174 }
175
176
177// Query functions unused if zoltan is not installed
178#ifdef DEAL_II_TRILINOS_WITH_ZOLTAN
179 // Query functions for partition_zoltan
180 int
181 get_number_of_objects(void *data, int *ierr)
182 {
183 SparsityPattern *graph = reinterpret_cast<SparsityPattern *>(data);
184
185 *ierr = ZOLTAN_OK;
186
187 return graph->n_rows();
188 }
189
190
191 void
192 get_object_list(void *data,
193 int /*sizeGID*/,
194 int /*sizeLID*/,
195 ZOLTAN_ID_PTR globalID,
196 ZOLTAN_ID_PTR localID,
197 int /*wgt_dim*/,
198 float * /*obj_wgts*/,
199 int *ierr)
200 {
201 SparsityPattern *graph = reinterpret_cast<SparsityPattern *>(data);
202 *ierr = ZOLTAN_OK;
203
204 Assert(globalID != nullptr, ExcInternalError());
205 Assert(localID != nullptr, ExcInternalError());
206
207 // set global degrees of freedom
208 auto n_dofs = graph->n_rows();
209
210 for (unsigned int i = 0; i < n_dofs; ++i)
211 {
212 globalID[i] = i;
213 localID[i] = i; // Same as global ids.
214 }
215 }
216
217
218 void
219 get_num_edges_list(void *data,
220 int /*sizeGID*/,
221 int /*sizeLID*/,
222 int num_obj,
223 ZOLTAN_ID_PTR globalID,
224 ZOLTAN_ID_PTR /*localID*/,
225 int *numEdges,
226 int *ierr)
227 {
228 SparsityPattern *graph = reinterpret_cast<SparsityPattern *>(data);
229
230 *ierr = ZOLTAN_OK;
231
232 Assert(numEdges != nullptr, ExcInternalError());
233
234 for (int i = 0; i < num_obj; ++i)
235 {
236 if (graph->exists(i, i)) // Check if diagonal element is present
237 numEdges[i] = graph->row_length(globalID[i]) - 1;
238 else
239 numEdges[i] = graph->row_length(globalID[i]);
240 }
241 }
242
243
244
245 void
246 get_edge_list(void *data,
247 int /*sizeGID*/,
248 int /*sizeLID*/,
249 int num_obj,
250 ZOLTAN_ID_PTR /*globalID*/,
251 ZOLTAN_ID_PTR /*localID*/,
252 int * /*num_edges*/,
253 ZOLTAN_ID_PTR nborGID,
254 int *nborProc,
255 int /*wgt_dim*/,
256 float * /*ewgts*/,
257 int *ierr)
258 {
259 SparsityPattern *graph = reinterpret_cast<SparsityPattern *>(data);
260 *ierr = ZOLTAN_OK;
261
262 ZOLTAN_ID_PTR nextNborGID = nborGID;
263 int *nextNborProc = nborProc;
264
265 // Loop through rows corresponding to indices in globalID implicitly
267 i < static_cast<SparsityPattern::size_type>(num_obj);
268 ++i)
269 {
270 // Loop through each column to find neighbours
271 for (SparsityPattern::iterator col = graph->begin(i);
272 col < graph->end(i);
273 ++col)
274 // Ignore diagonal entries. Not needed for partitioning.
275 if (i != col->column())
276 {
277 Assert(nextNborGID != nullptr, ExcInternalError());
278 Assert(nextNborProc != nullptr, ExcInternalError());
279
280 *nextNborGID++ = col->column();
281 *nextNborProc++ = 0; // All the vertices on processor 0
282 }
283 }
284 }
285#endif
286
287
288 void
289 partition_zoltan(const SparsityPattern &sparsity_pattern,
290 const std::vector<unsigned int> &cell_weights,
291 const unsigned int n_partitions,
292 std::vector<unsigned int> &partition_indices)
293 {
294 // Make sure that ZOLTAN is actually
295 // installed and detected
296#ifndef DEAL_II_TRILINOS_WITH_ZOLTAN
297 (void)sparsity_pattern;
298 (void)cell_weights;
299 (void)n_partitions;
300 (void)partition_indices;
302#else
303
304 Assert(
305 cell_weights.empty(),
307 "The cell weighting functionality for Zoltan has not yet been implemented."));
308
309 // MPI environment must have been initialized by this point.
310 std::unique_ptr<Zoltan> zz = std::make_unique<Zoltan>(MPI_COMM_SELF);
311
312 // General parameters
313 // DEBUG_LEVEL call must precede the call to LB_METHOD
314 zz->Set_Param("DEBUG_LEVEL", "0"); // set level of debug info
315 zz->Set_Param(
316 "LB_METHOD",
317 "GRAPH"); // graph based partition method (LB-load balancing)
318 zz->Set_Param("NUM_LOCAL_PARTS",
319 std::to_string(n_partitions)); // set number of partitions
320
321 // The PHG partitioner is a hypergraph partitioner that Zoltan could use
322 // for graph partitioning.
323 // If number of vertices in hyperedge divided by total vertices in
324 // hypergraph exceeds PHG_EDGE_SIZE_THRESHOLD,
325 // then the hyperedge will be omitted as such (dense) edges will likely
326 // incur high communication costs regardless of the partition.
327 // PHG_EDGE_SIZE_THRESHOLD value is raised to 0.5 from the default
328 // value of 0.25 so that the PHG partitioner doesn't throw warning saying
329 // "PHG_EDGE_SIZE_THRESHOLD is low ..." after removing all dense edges.
330 // For instance, in two dimensions if the triangulation being partitioned
331 // is two quadrilaterals sharing an edge and if PHG_EDGE_SIZE_THRESHOLD
332 // value is set to 0.25, PHG will remove all the edges throwing the
333 // above warning.
334 zz->Set_Param("PHG_EDGE_SIZE_THRESHOLD", "0.5");
335
336 // Need a non-const object equal to sparsity_pattern
337 SparsityPattern graph;
338 graph.copy_from(sparsity_pattern);
339
340 // Set query functions
341 zz->Set_Num_Obj_Fn(get_number_of_objects, &graph);
342 zz->Set_Obj_List_Fn(get_object_list, &graph);
343 zz->Set_Num_Edges_Multi_Fn(get_num_edges_list, &graph);
344 zz->Set_Edge_List_Multi_Fn(get_edge_list, &graph);
345
346 // Variables needed by partition function
347 int changes = 0;
348 int num_gid_entries = 1;
349 int num_lid_entries = 1;
350 int num_import = 0;
351 ZOLTAN_ID_PTR import_global_ids = nullptr;
352 ZOLTAN_ID_PTR import_local_ids = nullptr;
353 int *import_procs = nullptr;
354 int *import_to_part = nullptr;
355 int num_export = 0;
356 ZOLTAN_ID_PTR export_global_ids = nullptr;
357 ZOLTAN_ID_PTR export_local_ids = nullptr;
358 int *export_procs = nullptr;
359 int *export_to_part = nullptr;
360
361 // call partitioner
362 const int rc = zz->LB_Partition(changes,
363 num_gid_entries,
364 num_lid_entries,
365 num_import,
366 import_global_ids,
367 import_local_ids,
368 import_procs,
369 import_to_part,
370 num_export,
371 export_global_ids,
372 export_local_ids,
373 export_procs,
374 export_to_part);
375
376 // check for error code in partitioner
377 Assert(rc == ZOLTAN_OK, ExcInternalError());
378
379 // By default, all indices belong to part 0. After zoltan partition
380 // some are migrated to different part ID, which is stored in
381 // export_to_part array.
382 std::fill(partition_indices.begin(), partition_indices.end(), 0);
383
384 // copy from export_to_part to partition_indices, whose part_ids != 0.
385 Assert(export_to_part != nullptr, ExcInternalError());
386 for (int i = 0; i < num_export; ++i)
387 partition_indices[export_local_ids[i]] = export_to_part[i];
388#endif
389 }
390 } // namespace
391
392
393 void
394 partition(const SparsityPattern &sparsity_pattern,
395 const unsigned int n_partitions,
396 std::vector<unsigned int> &partition_indices,
397 const Partitioner partitioner)
398 {
399 std::vector<unsigned int> cell_weights;
400
401 // Call the other more general function
402 partition(sparsity_pattern,
403 cell_weights,
404 n_partitions,
405 partition_indices,
406 partitioner);
407 }
408
409
410 void
411 partition(const SparsityPattern &sparsity_pattern,
412 const std::vector<unsigned int> &cell_weights,
413 const unsigned int n_partitions,
414 std::vector<unsigned int> &partition_indices,
415 const Partitioner partitioner)
416 {
417 Assert(sparsity_pattern.n_rows() == sparsity_pattern.n_cols(),
419 Assert(sparsity_pattern.is_compressed(),
421
422 Assert(n_partitions > 0, ExcInvalidNumberOfPartitions(n_partitions));
423 Assert(partition_indices.size() == sparsity_pattern.n_rows(),
424 ExcInvalidArraySize(partition_indices.size(),
425 sparsity_pattern.n_rows()));
426
427 // check for an easy return
428 if (n_partitions == 1 || (sparsity_pattern.n_rows() == 1))
429 {
430 std::fill_n(partition_indices.begin(), partition_indices.size(), 0U);
431 return;
432 }
433
434 if (partitioner == Partitioner::metis)
435 partition_metis(sparsity_pattern,
436 cell_weights,
437 n_partitions,
438 partition_indices);
439 else if (partitioner == Partitioner::zoltan)
440 partition_zoltan(sparsity_pattern,
441 cell_weights,
442 n_partitions,
443 partition_indices);
444 else
446 }
447
448
449 unsigned int
451 std::vector<unsigned int> &color_indices)
452 {
453 // Make sure that ZOLTAN is actually
454 // installed and detected
455#ifndef DEAL_II_TRILINOS_WITH_ZOLTAN
456 (void)sparsity_pattern;
457 (void)color_indices;
459 return 0;
460#else
461 // coloring algorithm is run in serial by each processor.
462 std::unique_ptr<Zoltan> zz = std::make_unique<Zoltan>(MPI_COMM_SELF);
463
464 // Coloring parameters
465 // DEBUG_LEVEL must precede all other calls
466 zz->Set_Param("DEBUG_LEVEL", "0"); // level of debug info
467 zz->Set_Param("COLORING_PROBLEM", "DISTANCE-1"); // Standard coloring
468 zz->Set_Param("NUM_GID_ENTRIES", "1"); // 1 entry represents global ID
469 zz->Set_Param("NUM_LID_ENTRIES", "1"); // 1 entry represents local ID
470 zz->Set_Param("OBJ_WEIGHT_DIM", "0"); // object weights not used
471 zz->Set_Param("RECOLORING_NUM_OF_ITERATIONS", "0");
472
473 // Zoltan::Color function requires a non-const SparsityPattern object
474 SparsityPattern graph;
475 graph.copy_from(sparsity_pattern);
476
477 // Set query functions required by coloring function
478 zz->Set_Num_Obj_Fn(get_number_of_objects, &graph);
479 zz->Set_Obj_List_Fn(get_object_list, &graph);
480 zz->Set_Num_Edges_Multi_Fn(get_num_edges_list, &graph);
481 zz->Set_Edge_List_Multi_Fn(get_edge_list, &graph);
482
483 // Variables needed by coloring function
484 int num_gid_entries = 1;
485 const int num_objects = graph.n_rows();
486
487 // Preallocate input variables. Element type fixed by ZOLTAN.
488 std::vector<ZOLTAN_ID_TYPE> global_ids(num_objects);
489 std::vector<int> color_exp(num_objects);
490
491 // Set ids for which coloring needs to be done
492 for (int i = 0; i < num_objects; ++i)
493 global_ids[i] = i;
494
495 // Call ZOLTAN coloring algorithm
496 int rc = zz->Color(num_gid_entries,
497 num_objects,
498 global_ids.data(),
499 color_exp.data());
500 // Check for error code
501 Assert(rc == ZOLTAN_OK, ExcInternalError());
502
503 // Allocate and assign color indices
504 color_indices.resize(num_objects);
505 Assert(color_exp.size() == color_indices.size(),
506 ExcDimensionMismatch(color_exp.size(), color_indices.size()));
507
508 std::copy(color_exp.begin(), color_exp.end(), color_indices.begin());
509
510 unsigned int n_colors =
511 *(std::max_element(color_indices.begin(), color_indices.end()));
512 return n_colors;
513#endif
514 }
515
516
517 namespace internal
518 {
526 const DynamicSparsityPattern &sparsity,
527 const std::vector<DynamicSparsityPattern::size_type> &new_indices)
528 {
529 DynamicSparsityPattern::size_type starting_point =
531 DynamicSparsityPattern::size_type min_coordination = sparsity.n_rows();
532 for (DynamicSparsityPattern::size_type row = 0; row < sparsity.n_rows();
533 ++row)
534 // look over all as-yet unnumbered indices
535 if (new_indices[row] == numbers::invalid_size_type)
536 {
537 if (sparsity.row_length(row) < min_coordination)
538 {
539 min_coordination = sparsity.row_length(row);
540 starting_point = row;
541 }
542 }
543
544 // now we still have to care for the case that no unnumbered dof has a
545 // coordination number less than sparsity.n_rows(). this rather exotic
546 // case only happens if we only have one cell, as far as I can see,
547 // but there may be others as well.
548 //
549 // if that should be the case, we can chose an arbitrary dof as
550 // starting point, e.g. the first unnumbered one
551 if (starting_point == numbers::invalid_size_type)
552 {
553 for (DynamicSparsityPattern::size_type i = 0; i < new_indices.size();
554 ++i)
555 if (new_indices[i] == numbers::invalid_size_type)
556 {
557 starting_point = i;
558 break;
559 }
560
561 Assert(starting_point != numbers::invalid_size_type,
563 }
564
565 return starting_point;
566 }
567 } // namespace internal
568
569
570
571 void
573 const DynamicSparsityPattern &sparsity,
574 std::vector<DynamicSparsityPattern::size_type> &new_indices,
575 const std::vector<DynamicSparsityPattern::size_type> &starting_indices)
576 {
577 Assert(sparsity.n_rows() == sparsity.n_cols(),
578 ExcDimensionMismatch(sparsity.n_rows(), sparsity.n_cols()));
579 Assert(sparsity.n_rows() == new_indices.size(),
580 ExcDimensionMismatch(sparsity.n_rows(), new_indices.size()));
581 Assert(starting_indices.size() <= sparsity.n_rows(),
583 "You can't specify more starting indices than there are rows"));
584 Assert(sparsity.row_index_set().size() == 0 ||
585 sparsity.row_index_set().size() == sparsity.n_rows(),
587 "Only valid for sparsity patterns which store all rows."));
588 for (const auto starting_index : starting_indices)
589 {
590 (void)starting_index;
591 Assert(starting_index < sparsity.n_rows(),
592 ExcMessage("Invalid starting index: All starting indices need "
593 "to be between zero and the number of rows in the "
594 "sparsity pattern."));
595 }
596
597 // store the indices of the dofs renumbered in the last round. Default to
598 // starting points
599 std::vector<DynamicSparsityPattern::size_type> last_round_dofs(
600 starting_indices);
601
602 // initialize the new_indices array with invalid values
603 std::fill(new_indices.begin(),
604 new_indices.end(),
606
607 // if no starting indices were given: find dof with lowest coordination
608 // number
609 if (last_round_dofs.empty())
610 last_round_dofs.push_back(
611 internal::find_unnumbered_starting_index(sparsity, new_indices));
612
613 // store next free dof index
614 DynamicSparsityPattern::size_type next_free_number = 0;
615
616 // enumerate the first round dofs
617 for (const auto &last_round_dof : last_round_dofs)
618 new_indices[last_round_dof] = next_free_number++;
619
620 // store the indices of the dofs to be renumbered in the next round
621 std::vector<DynamicSparsityPattern::size_type> next_round_dofs;
622
623 // store for each coordination number the dofs with these coordination
624 // number
625 std::vector<std::pair<unsigned int, DynamicSparsityPattern::size_type>>
626 dofs_by_coordination;
627
628 // now do as many steps as needed to renumber all dofs
629 while (true)
630 {
631 next_round_dofs.clear();
632
633 // find all neighbors of the dofs numbered in the last round
634 for (const auto dof : last_round_dofs)
635 {
636 const unsigned int row_length = sparsity.row_length(dof);
637 for (unsigned int i = 0; i < row_length; ++i)
638 {
639 // skip dofs which are already numbered
640 const auto column = sparsity.column_number(dof, i);
641 if (new_indices[column] == numbers::invalid_size_type)
642 {
643 next_round_dofs.push_back(column);
644
645 // assign a dummy value to 'new_indices' to avoid adding
646 // the same index again; those will get the right number
647 // at the end of the outer 'while' loop
648 new_indices[column] = 0;
649 }
650 }
651 }
652
653 // check whether there are any new dofs in the list. if there are
654 // none, then we have completely numbered the current component of the
655 // graph. check if there are as yet unnumbered components of the graph
656 // that we would then have to do next
657 if (next_round_dofs.empty())
658 {
659 if (std::find(new_indices.begin(),
660 new_indices.end(),
661 numbers::invalid_size_type) == new_indices.end())
662 // no unnumbered indices, so we can leave now
663 break;
664
665 // otherwise find a valid starting point for the next component of
666 // the graph and continue with numbering that one. we only do so
667 // if no starting indices were provided by the user (see the
668 // documentation of this function) so produce an error if we got
669 // here and starting indices were given
670 Assert(starting_indices.empty(),
671 ExcMessage("The input graph appears to have more than one "
672 "component, but as stated in the documentation "
673 "we only want to reorder such graphs if no "
674 "starting indices are given. The function was "
675 "called with starting indices, however."));
676
677 next_round_dofs.push_back(
678 internal::find_unnumbered_starting_index(sparsity, new_indices));
679 }
680
681
682 // find coordination number for each of these dofs
683 dofs_by_coordination.clear();
684 for (const types::global_dof_index next_round_dof : next_round_dofs)
685 dofs_by_coordination.emplace_back(sparsity.row_length(next_round_dof),
686 next_round_dof);
687 std::sort(dofs_by_coordination.begin(), dofs_by_coordination.end());
688
689 // assign new DoF numbers to the elements of the present front:
690 for (const auto &i : dofs_by_coordination)
691 new_indices[i.second] = next_free_number++;
692
693 // after that: use this round's dofs for the next round
694 last_round_dofs.swap(next_round_dofs);
695 }
696
697 // test for all indices numbered. this mostly tests whether the
698 // front-marching-algorithm (which Cuthill-McKee actually is) has reached
699 // all points.
700 Assert((std::find(new_indices.begin(),
701 new_indices.end(),
702 numbers::invalid_size_type) == new_indices.end()) &&
703 (next_free_number == sparsity.n_rows()),
705 }
706
707
708
709 namespace internal
710 {
711 void
713 const DynamicSparsityPattern &connectivity,
714 std::vector<DynamicSparsityPattern::size_type> &renumbering)
715 {
716 AssertDimension(connectivity.n_rows(), connectivity.n_cols());
717 AssertDimension(connectivity.n_rows(), renumbering.size());
718 Assert(connectivity.row_index_set().size() == 0 ||
719 connectivity.row_index_set().size() == connectivity.n_rows(),
721 "Only valid for sparsity patterns which store all rows."));
722
723 // The algorithm below works by partitioning the rows in the
724 // connectivity graph, called nodes, into groups. The groups are defined
725 // as those nodes in immediate neighborhood of some pivot node, which we
726 // choose by minimal adjacency below.
727
728 // We define two types of node categories for nodes not yet classified,
729 // one consisting of all nodes we've not seen at all, and one for nodes
730 // identified as neighbors (variable current_neighbors below) but not
731 // yet grouped. We use this classification in combination with an
732 // unsorted vector, which is much faster than keeping a sorted data
733 // structure (e.g. std::set)
734 constexpr types::global_dof_index unseen_node =
736 constexpr types::global_dof_index available_node = unseen_node - 1;
737 const types::global_dof_index n_nodes = connectivity.n_rows();
738 std::vector<types::global_dof_index> touched_nodes(n_nodes, unseen_node);
739
740 std::vector<unsigned int> row_lengths(n_nodes);
741 std::vector<types::global_dof_index> current_neighbors;
742 std::vector<types::global_dof_index> group_starts(1);
743 std::vector<types::global_dof_index> group_indices;
744 group_indices.reserve(n_nodes);
745
746 // First collect the number of neighbors for each node. We use this
747 // field to find the next node with the minimum number of non-touched
748 // neighbors in the field n_remaining_neighbors, so we will count down
749 // on this field. We also cache the row lengths because we need this
750 // data frequently and getting it from the sparsity pattern is more
751 // expensive.
752 for (types::global_dof_index row = 0; row < n_nodes; ++row)
753 {
754 row_lengths[row] = connectivity.row_length(row);
755 Assert(row_lengths[row] > 0, ExcInternalError());
756 }
757 std::vector<unsigned int> n_remaining_neighbors(row_lengths);
758
759 // This outer loop is typically traversed only once, unless the global
760 // graph is not connected
761 while (true)
762 {
763 // Find node with the minimal number of neighbors (typically a
764 // corner node when based on FEM meshes). If no node is left, we are
765 // done. Together with the outer while loop, this loop can possibly
766 // be of quadratic complexity in the number of disconnected
767 // partitions, i.e. up to n_nodes in the worst case,
768 // but that is not the usual use case of this loop and thus not
769 // optimized for.
770 {
771 unsigned int candidate_valence = numbers::invalid_unsigned_int;
772 types::global_dof_index candidate_index =
774 for (types::global_dof_index i = 0; i < n_nodes; ++i)
775 if (touched_nodes[i] == unseen_node)
776 if (row_lengths[i] < candidate_valence)
777 {
778 candidate_index = i;
779 candidate_valence = n_remaining_neighbors[i];
780 if (candidate_valence <= 1)
781 break;
782 }
783 if (candidate_index == numbers::invalid_dof_index)
784 break;
785
786 Assert(candidate_valence > 0, ExcInternalError());
787
788 current_neighbors = {candidate_index};
789 touched_nodes[candidate_index] = available_node;
790 }
791
792 while (true)
793 {
794 // Find node with minimum number of untouched neighbors among
795 // the next set of possible neighbors (= valence), and among the
796 // set of nodes with the minimal number of neighbors, choose the
797 // one with the largest number of touched neighbors (i.e., the
798 // largest row length).
799 //
800 // This loop is typically the most expensive part for large
801 // graphs and thus only run once. We also do some cleanup, i.e.,
802 // the indices added to a group in the previous round need to be
803 // removed at this point.
804 unsigned int candidate_valence = numbers::invalid_unsigned_int;
805 types::global_dof_index candidate_index =
807 unsigned int candidate_row_length = 0;
808 const unsigned int loop_length = current_neighbors.size();
809 unsigned int write_index = 0;
810 for (unsigned int i = 0; i < loop_length; ++i)
811 {
812 const types::global_dof_index node = current_neighbors[i];
813 Assert(touched_nodes[node] != unseen_node,
815 if (touched_nodes[node] == available_node)
816 {
817 current_neighbors[write_index] = node;
818 ++write_index;
819 if (n_remaining_neighbors[node] < candidate_valence ||
820 (n_remaining_neighbors[node] == candidate_valence &&
821 (row_lengths[node] > candidate_row_length ||
822 (row_lengths[node] == candidate_row_length &&
823 node < candidate_index))))
824 {
825 candidate_index = node;
826 candidate_valence = n_remaining_neighbors[node];
827 candidate_row_length = row_lengths[node];
828 }
829 }
830 }
831 current_neighbors.resize(write_index);
832
833 if constexpr (running_in_debug_mode())
834 {
835 for (const types::global_dof_index node : current_neighbors)
836 Assert(touched_nodes[node] == available_node,
838 }
839
840 // No more neighbors left -> terminate loop
841 if (current_neighbors.empty())
842 break;
843
844 // Add the pivot and all direct neighbors of the pivot node not
845 // yet touched to the list of new entries.
846 group_indices.push_back(candidate_index);
847 touched_nodes[candidate_index] = group_starts.size() - 1;
848 const auto end_it = connectivity.end(candidate_index);
849 for (auto it = connectivity.begin(candidate_index); it != end_it;
850 ++it)
851 if (touched_nodes[it->column()] >= available_node)
852 {
853 group_indices.push_back(it->column());
854 touched_nodes[it->column()] = group_starts.size() - 1;
855 }
856 group_starts.push_back(group_indices.size());
857
858 // Add all neighbors of the current list not yet seen to the set
859 // of possible next nodes. The added node is grouped and thus no
860 // longer a valid neighbor (here we assume symmetry of the
861 // connectivity). It will be removed from the list of neighbors
862 // by the code further up in the next iteration of the
863 // surrounding loop.
864 for (types::global_dof_index index =
865 group_starts[group_starts.size() - 2];
866 index < group_starts.back();
867 ++index)
868 {
869 auto it = connectivity.begin(group_indices[index]);
870 const auto end_row = connectivity.end(group_indices[index]);
871 for (; it != end_row; ++it)
872 {
873 if (touched_nodes[it->column()] == unseen_node)
874 {
875 current_neighbors.push_back(it->column());
876 touched_nodes[it->column()] = available_node;
877 }
878 n_remaining_neighbors[it->column()]--;
879 }
880 }
881 }
882 }
883
884 // Sanity check: for all nodes, there should not be any neighbors left
885 for (types::global_dof_index row = 0; row < n_nodes; ++row)
886 Assert(n_remaining_neighbors[row] == 0, ExcInternalError());
887
888 // If the number of groups is smaller than the number of nodes, we
889 // continue by recursively calling this method
890 const unsigned int n_groups = group_starts.size() - 1;
891 if (n_groups < n_nodes)
892 {
893 // Form the connectivity of the groups
894 DynamicSparsityPattern connectivity_next(n_groups, n_groups);
895 for (types::global_dof_index row = 0; row < n_groups; ++row)
896 for (types::global_dof_index index = group_starts[row];
897 index < group_starts[row + 1];
898 ++index)
899 {
900 auto it = connectivity.begin(group_indices[index]);
901 const auto end_it = connectivity.end(group_indices[index]);
902 for (; it != end_it; ++it)
903 connectivity_next.add(row, touched_nodes[it->column()]);
904 }
905
906 // Recursively call the reordering
907 std::vector<types::global_dof_index> renumbering_next(n_groups);
908 reorder_hierarchical(connectivity_next, renumbering_next);
909
910 // Renumber the indices group by group according to the incoming
911 // ordering for the groups
912 for (types::global_dof_index row = 0, c = 0; row < n_groups; ++row)
913 for (types::global_dof_index index =
914 group_starts[renumbering_next[row]];
915 index < group_starts[renumbering_next[row] + 1];
916 ++index, ++c)
917 renumbering[c] = group_indices[index];
918 }
919 else
920 {
921 // All groups should have size one and no more recursion is possible,
922 // so use the numbering of the groups
923 unsigned int c = 0;
924 for (const types::global_dof_index i : group_indices)
925 renumbering[c++] = i;
926 }
927 }
928 } // namespace internal
929
930 void
932 const DynamicSparsityPattern &connectivity,
933 std::vector<DynamicSparsityPattern::size_type> &renumbering)
934 {
935 // the internal renumbering keeps the numbering the wrong way around (but
936 // we cannot invert the numbering inside that method because it is used
937 // recursively), so invert it here
938 internal::reorder_hierarchical(connectivity, renumbering);
939 renumbering = Utilities::invert_permutation(renumbering);
940 }
941
942
943
944#ifdef DEAL_II_WITH_MPI
945
946 void
948 const IndexSet &locally_owned_rows,
949 const MPI_Comm mpi_comm,
950 const IndexSet &locally_relevant_rows)
951 {
952 using map_vec_t =
953 std::map<unsigned int, std::vector<DynamicSparsityPattern::size_type>>;
954
955 // 1. limit rows to non owned:
956 IndexSet requested_rows(locally_relevant_rows);
957 requested_rows.subtract_set(locally_owned_rows);
958
959 std::vector<unsigned int> index_owner =
960 Utilities::MPI::compute_index_owner(locally_owned_rows,
961 requested_rows,
962 mpi_comm);
963
964 // 2. go through requested_rows, figure out the owner and add the row to
965 // request
966 map_vec_t rows_data;
968 i < requested_rows.n_elements();
969 ++i)
970 {
972 requested_rows.nth_index_in_set(i);
973
974 rows_data[index_owner[i]].push_back(row);
975 }
976
977 // 3. get what others ask us to send
978 const auto rows_data_received =
979 Utilities::MPI::some_to_some(mpi_comm, rows_data);
980
981 // 4. now prepare data to be sent in the same format as in
982 // distribute_sparsity_pattern() below, i.e.
983 // rX,num_rX,cols_rX
984 map_vec_t send_data;
985 for (const auto &data : rows_data_received)
986 {
987 for (const auto &row : data.second)
988 {
989 const auto rlen = dsp.row_length(row);
990
991 // skip empty lines
992 if (rlen == 0)
993 continue;
994
995 // save entries
996 send_data[data.first].push_back(row); // row index
997 send_data[data.first].push_back(rlen); // number of entries
998 for (DynamicSparsityPattern::size_type c = 0; c < rlen; ++c)
999 send_data[data.first].push_back(
1000 dsp.column_number(row, c)); // columns
1001 } // loop over rows
1002 } // loop over received data
1003
1004 // 5. communicate rows
1005 const auto received_data =
1006 Utilities::MPI::some_to_some(mpi_comm, send_data);
1007
1008 // 6. add result to our sparsity
1009 for (const auto &data : received_data)
1010 {
1011 const auto &recv_buf = data.second;
1012 auto ptr = recv_buf.begin();
1013 const auto end = recv_buf.end();
1014 while (ptr != end)
1015 {
1016 const auto row = *(ptr++);
1017 Assert(ptr != end, ExcInternalError());
1018
1019 const auto n_entries = *(ptr++);
1020 Assert(n_entries > 0, ExcInternalError());
1021 Assert(ptr != end, ExcInternalError());
1022
1023 // make sure we clear whatever was previously stored
1024 // in these rows. Otherwise we can't guarantee that the
1025 // data is consistent across MPI communicator.
1026 dsp.clear_row(row);
1027
1028 Assert(ptr + (n_entries - 1) != end, ExcInternalError());
1029 dsp.add_entries(row, ptr, ptr + n_entries, true);
1030 ptr += n_entries;
1031 }
1032 Assert(ptr == end, ExcInternalError());
1033 }
1034 }
1035
1036
1037
1038 void
1041 const std::vector<DynamicSparsityPattern::size_type> &rows_per_cpu,
1042 const MPI_Comm mpi_comm,
1043 const IndexSet &myrange)
1044 {
1045 const unsigned int myid = Utilities::MPI::this_mpi_process(mpi_comm);
1046 std::vector<DynamicSparsityPattern::size_type> start_index(
1047 rows_per_cpu.size() + 1);
1048 start_index[0] = 0;
1049 for (DynamicSparsityPattern::size_type i = 0; i < rows_per_cpu.size(); ++i)
1050 start_index[i + 1] = start_index[i] + rows_per_cpu[i];
1051
1052 IndexSet owned(start_index.back());
1053 owned.add_range(start_index[myid], start_index[myid] + rows_per_cpu[myid]);
1054
1055 distribute_sparsity_pattern(dsp, owned, mpi_comm, myrange);
1056 }
1057
1058
1059
1060 void
1062 const IndexSet &locally_owned_rows,
1063 const MPI_Comm mpi_comm,
1064 const IndexSet &locally_relevant_rows)
1065 {
1067 dsp.row_index_set() == locally_relevant_rows,
1068 ExcMessage(
1069 "The DynamicSparsityPattern must be initialized with an IndexSet that contains locally relevant indices."));
1070
1071 IndexSet requested_rows(locally_relevant_rows);
1072 requested_rows.subtract_set(locally_owned_rows);
1073
1074 std::vector<unsigned int> index_owner =
1075 Utilities::MPI::compute_index_owner(locally_owned_rows,
1076 requested_rows,
1077 mpi_comm);
1078
1079 using map_vec_t =
1080 std::map<unsigned int, std::vector<DynamicSparsityPattern::size_type>>;
1081
1082 map_vec_t send_data;
1083
1085 i < requested_rows.n_elements();
1086 ++i)
1087 {
1089 requested_rows.nth_index_in_set(i);
1090
1091 const auto rlen = dsp.row_length(row);
1092
1093 // skip empty lines
1094 if (rlen == 0)
1095 continue;
1096
1097 // save entries
1098 send_data[index_owner[i]].push_back(row); // row index
1099 send_data[index_owner[i]].push_back(rlen); // number of entries
1100 for (DynamicSparsityPattern::size_type c = 0; c < rlen; ++c)
1101 {
1102 // columns
1103 const auto column = dsp.column_number(row, c);
1104 send_data[index_owner[i]].push_back(column);
1105 }
1106 }
1107
1108 const auto receive_data = Utilities::MPI::some_to_some(mpi_comm, send_data);
1109
1110 // add what we received
1111 for (const auto &data : receive_data)
1112 {
1113 const auto &recv_buf = data.second;
1114 auto ptr = recv_buf.begin();
1115 const auto end = recv_buf.end();
1116 while (ptr != end)
1117 {
1118 const auto row = *(ptr++);
1119 Assert(ptr != end, ExcInternalError());
1120 const auto n_entries = *(ptr++);
1121
1122 Assert(ptr + (n_entries - 1) != end, ExcInternalError());
1123 dsp.add_entries(row, ptr, ptr + n_entries, true);
1124 ptr += n_entries;
1125 }
1126 Assert(ptr == end, ExcInternalError());
1127 }
1128 }
1129
1130
1131
1132 void
1134 const std::vector<IndexSet> &owned_set_per_cpu,
1135 const MPI_Comm mpi_comm,
1136 const IndexSet &myrange)
1137 {
1138 const unsigned int myid = Utilities::MPI::this_mpi_process(mpi_comm);
1140 owned_set_per_cpu[myid],
1141 mpi_comm,
1142 myrange);
1143 }
1144
1145
1146
1147 void
1149 const IndexSet &locally_owned_rows,
1150 const MPI_Comm mpi_comm,
1151 const IndexSet &locally_relevant_rows)
1152 {
1153 using map_vec_t =
1155 std::vector<BlockDynamicSparsityPattern::size_type>>;
1156 map_vec_t send_data;
1157
1158 IndexSet requested_rows(locally_relevant_rows);
1159 requested_rows.subtract_set(locally_owned_rows);
1160
1161 std::vector<unsigned int> index_owner =
1162 Utilities::MPI::compute_index_owner(locally_owned_rows,
1163 requested_rows,
1164 mpi_comm);
1165
1167 i < requested_rows.n_elements();
1168 ++i)
1169 {
1171 requested_rows.nth_index_in_set(i);
1172
1174
1175 // skip empty lines
1176 if (rlen == 0)
1177 continue;
1178
1179 // save entries
1180 std::vector<BlockDynamicSparsityPattern::size_type> &dst =
1181 send_data[index_owner[i]];
1182
1183 dst.push_back(rlen); // number of entries
1184 dst.push_back(row); // row index
1185 for (BlockDynamicSparsityPattern::size_type c = 0; c < rlen; ++c)
1186 {
1187 // columns
1189 dsp.column_number(row, c);
1190 dst.push_back(column);
1191 }
1192 }
1193
1194 unsigned int num_receive = 0;
1195 {
1196 std::vector<unsigned int> send_to;
1197 send_to.reserve(send_data.size());
1198 for (const auto &sparsity_line : send_data)
1199 send_to.push_back(sparsity_line.first);
1200
1201 num_receive =
1203 send_to);
1204 }
1205
1206 std::vector<MPI_Request> requests(send_data.size());
1207
1208
1209 // send data
1210
1212 Utilities::MPI::CollectiveMutex::ScopedLock lock(mutex, mpi_comm);
1213
1214 const int mpi_tag = Utilities::MPI::internal::Tags::
1216
1217 {
1218 unsigned int idx = 0;
1219 for (const auto &sparsity_line : send_data)
1220 {
1221 const int ierr = MPI_Isend(
1222 sparsity_line.second.data(),
1223 sparsity_line.second.size(),
1224 Utilities::MPI::mpi_type_id_for_type<types::global_dof_index>,
1225 sparsity_line.first,
1226 mpi_tag,
1227 mpi_comm,
1228 &requests[idx++]);
1229 AssertThrowMPI(ierr);
1230 }
1231 }
1232
1233 {
1234 // receive
1235 std::vector<BlockDynamicSparsityPattern::size_type> recv_buf;
1236 for (unsigned int index = 0; index < num_receive; ++index)
1237 {
1238 MPI_Status status;
1239 int ierr = MPI_Probe(MPI_ANY_SOURCE, mpi_tag, mpi_comm, &status);
1240 AssertThrowMPI(ierr);
1241
1242 int len;
1243 ierr = MPI_Get_count(
1244 &status,
1245 Utilities::MPI::mpi_type_id_for_type<types::global_dof_index>,
1246 &len);
1247 AssertThrowMPI(ierr);
1248
1249 recv_buf.resize(len);
1250 ierr = MPI_Recv(
1251 recv_buf.data(),
1252 len,
1253 Utilities::MPI::mpi_type_id_for_type<types::global_dof_index>,
1254 status.MPI_SOURCE,
1255 status.MPI_TAG,
1256 mpi_comm,
1257 &status);
1258 AssertThrowMPI(ierr);
1259
1260 std::vector<BlockDynamicSparsityPattern::size_type>::const_iterator
1261 ptr = recv_buf.begin();
1262 std::vector<BlockDynamicSparsityPattern::size_type>::const_iterator
1263 end = recv_buf.end();
1264 while (ptr != end)
1265 {
1267 Assert(ptr != end, ExcInternalError());
1269 for (unsigned int c = 0; c < num; ++c)
1270 {
1271 Assert(ptr != end, ExcInternalError());
1272 dsp.add(row, *ptr);
1273 ++ptr;
1274 }
1275 }
1276 Assert(ptr == end, ExcInternalError());
1277 }
1278 }
1279
1280 // complete all sends, so that we can safely destroy the buffers.
1281 if (requests.size() > 0)
1282 {
1283 const int ierr =
1284 MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE);
1285 AssertThrowMPI(ierr);
1286 }
1287 }
1288#endif
1289} // namespace SparsityTools
1290
*  iterator end()
size_type column_number(const size_type row, const unsigned int index) const
unsigned int row_length(const size_type row) const
void add(const size_type i, const size_type j)
void add_entries(const size_type row, ForwardIterator begin, ForwardIterator end, const bool indices_are_unique_and_sorted=false)
const IndexSet & row_index_set() const
size_type row_length(const size_type row) const
size_type column_number(const size_type row, const size_type index) const
void clear_row(const size_type row)
void add(const size_type i, const size_type j)
size_type size() const
Definition index_set.h:1759
size_type n_elements() const
Definition index_set.h:1917
void subtract_set(const IndexSet &other)
Definition index_set.cc:496
void add_range(const size_type begin, const size_type end)
Definition index_set.h:1786
size_type nth_index_in_set(const size_type local_index) const
Definition index_set.h:1958
types::global_dof_index size_type
size_type n_rows() const
size_type n_cols() const
bool is_compressed() const
std::size_t n_nonzero_elements() const
bool exists(const size_type i, const size_type j) const
iterator begin() const
void copy_from(const size_type n_rows, const size_type n_cols, const ForwardIterator begin, const ForwardIterator end)
iterator end() const
unsigned int row_length(const size_type row) const
#define DEAL_II_NAMESPACE_OPEN
Definition config.h:38
constexpr bool running_in_debug_mode()
Definition config.h:76
#define DEAL_II_NAMESPACE_CLOSE
Definition config.h:39
static ::ExceptionBase & ExcMETISNotInstalled()
static ::ExceptionBase & ExcInvalidNumberOfPartitions(int arg1)
static ::ExceptionBase & ExcMETISError(int arg1)
#define Assert(cond, exc)
#define AssertDimension(dim1, dim2)
#define AssertThrowMPI(error_code)
static ::ExceptionBase & ExcInvalidArraySize(int arg1, int arg2)
static ::ExceptionBase & ExcInternalError()
static ::ExceptionBase & ExcDimensionMismatch(std::size_t arg1, std::size_t arg2)
static ::ExceptionBase & ExcNotQuadratic()
static ::ExceptionBase & ExcZOLTANNotInstalled()
static ::ExceptionBase & ExcNotCompressed()
static ::ExceptionBase & ExcMessage(std::string arg1)
#define AssertThrow(cond, exc)
std::vector< index_type > data
Definition mpi.cc:734
DynamicSparsityPattern::size_type find_unnumbered_starting_index(const DynamicSparsityPattern &sparsity, const std::vector< DynamicSparsityPattern::size_type > &new_indices)
void reorder_hierarchical(const DynamicSparsityPattern &connectivity, std::vector< DynamicSparsityPattern::size_type > &renumbering)
unsigned int color_sparsity_pattern(const SparsityPattern &sparsity_pattern, std::vector< unsigned int > &color_indices)
void partition(const SparsityPattern &sparsity_pattern, const unsigned int n_partitions, std::vector< unsigned int > &partition_indices, const Partitioner partitioner=Partitioner::metis)
void reorder_hierarchical(const DynamicSparsityPattern &sparsity, std::vector< DynamicSparsityPattern::size_type > &new_indices)
void distribute_sparsity_pattern(DynamicSparsityPattern &dsp, const IndexSet &locally_owned_rows, const MPI_Comm mpi_comm, const IndexSet &locally_relevant_rows)
void gather_sparsity_pattern(DynamicSparsityPattern &dsp, const IndexSet &locally_owned_rows, const MPI_Comm mpi_comm, const IndexSet &locally_relevant_rows)
void reorder_Cuthill_McKee(const DynamicSparsityPattern &sparsity, std::vector< DynamicSparsityPattern::size_type > &new_indices, const std::vector< DynamicSparsityPattern::size_type > &starting_indices=std::vector< DynamicSparsityPattern::size_type >())
@ sparsity_tools_distribute_sparsity_pattern
SparsityTools::sparsity_tools_distribute_sparsity_pattern()
Definition mpi_tags.h:73
std::map< unsigned int, T > some_to_some(const MPI_Comm comm, const std::map< unsigned int, T > &objects_to_send)
std::vector< unsigned int > compute_index_owner(const IndexSet &owned_indices, const IndexSet &indices_to_look_up, const MPI_Comm comm)
Definition mpi.cc:1820
unsigned int this_mpi_process(const MPI_Comm mpi_communicator)
Definition mpi.cc:118
unsigned int compute_n_point_to_point_communications(const MPI_Comm mpi_comm, const std::vector< unsigned int > &destinations)
Definition mpi.cc:371
std::vector< Integer > invert_permutation(const std::vector< Integer > &permutation)
Definition utilities.h:1670
constexpr types::global_dof_index invalid_dof_index
Definition types.h:259
constexpr types::global_dof_index invalid_size_type
Definition types.h:240
constexpr unsigned int invalid_unsigned_int
Definition types.h:228
::VectorizedArray< Number, width > min(const ::VectorizedArray< Number, width > &, const ::VectorizedArray< Number, width > &)