deal.II version GIT relicensing-6960-g70e9c5b7be 2026-10-22 20:10: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 {
529 const std::vector<DynamicSparsityPattern::size_type> &new_indices,
530 const std::vector<std::pair<DynamicSparsityPattern::size_type,
532 &rows_by_coordination,
533 std::size_t &next_candidate)
534 {
535 while (new_indices[rows_by_coordination[next_candidate].second] !=
537 ++next_candidate;
538
539 return rows_by_coordination[next_candidate].second;
540 }
541 } // namespace internal
542
543
544
545 void
547 const DynamicSparsityPattern &sparsity,
548 std::vector<DynamicSparsityPattern::size_type> &new_indices,
549 const std::vector<DynamicSparsityPattern::size_type> &starting_indices)
550 {
551 Assert(sparsity.n_rows() == sparsity.n_cols(),
552 ExcDimensionMismatch(sparsity.n_rows(), sparsity.n_cols()));
553 Assert(sparsity.n_rows() == new_indices.size(),
554 ExcDimensionMismatch(sparsity.n_rows(), new_indices.size()));
555 Assert(starting_indices.size() <= sparsity.n_rows(),
557 "You can't specify more starting indices than there are rows"));
558 Assert(sparsity.row_index_set().size() == 0 ||
559 sparsity.row_index_set().size() == sparsity.n_rows(),
561 "Only valid for sparsity patterns which store all rows."));
562 for (const auto starting_index : starting_indices)
563 {
564 (void)starting_index;
565 Assert(starting_index < sparsity.n_rows(),
566 ExcMessage("Invalid starting index: All starting indices need "
567 "to be between zero and the number of rows in the "
568 "sparsity pattern."));
569 }
570
571 // store the indices of the dofs renumbered in the last round. Default to
572 // starting points
573 std::vector<DynamicSparsityPattern::size_type> last_round_dofs(
574 starting_indices);
575
576 // initialize the new_indices array with invalid values
577 std::fill(new_indices.begin(),
578 new_indices.end(),
580
581 // if no starting indices were given: find dof with lowest coordination
582 // number
583 if (last_round_dofs.empty())
584 {
585 DynamicSparsityPattern::size_type starting_index = 0;
586 for (DynamicSparsityPattern::size_type row = 1; row < sparsity.n_rows();
587 ++row)
588 if (sparsity.row_length(row) < sparsity.row_length(starting_index))
589 starting_index = row;
590 last_round_dofs.push_back(starting_index);
591 }
592
593 // all rows sorted by coordination number (and row index). We only set
594 // up and use this vector when renumbering with sparsity patterns that
595 // have more than one component in the connectivity graph.
596 std::vector<std::pair<DynamicSparsityPattern::size_type,
598 rows_by_coordination;
599 std::size_t next_candidate = 0;
600
601 // store next free dof index
602 DynamicSparsityPattern::size_type next_free_number = 0;
603
604 // enumerate the first round dofs
605 for (const auto &last_round_dof : last_round_dofs)
606 new_indices[last_round_dof] = next_free_number++;
607
608 // store the indices of the dofs to be renumbered in the next round
609 std::vector<DynamicSparsityPattern::size_type> next_round_dofs;
610
611 // store for each coordination number the dofs with these coordination
612 // number
613 std::vector<std::pair<unsigned int, DynamicSparsityPattern::size_type>>
614 dofs_by_coordination;
615
616 // now do as many steps as needed to renumber all dofs
617 while (true)
618 {
619 next_round_dofs.clear();
620
621 // find all neighbors of the dofs numbered in the last round
622 for (const auto dof : last_round_dofs)
623 {
624 const unsigned int row_length = sparsity.row_length(dof);
625 for (unsigned int i = 0; i < row_length; ++i)
626 {
627 // skip dofs which are already numbered
628 const auto column = sparsity.column_number(dof, i);
629 if (new_indices[column] == numbers::invalid_size_type)
630 {
631 next_round_dofs.push_back(column);
632
633 // assign a dummy value to 'new_indices' to avoid adding
634 // the same index again; those will get the right number
635 // at the end of the outer 'while' loop
636 new_indices[column] = 0;
637 }
638 }
639 }
640
641 // check whether there are any new dofs in the list. if there are
642 // none, then we have completely numbered the current component of the
643 // graph. check if there are as yet unnumbered components of the graph
644 // that we would then have to do next
645 if (next_round_dofs.empty())
646 {
647 if (next_free_number == sparsity.n_rows())
648 // no unnumbered indices, so we can leave now
649 break;
650
651 // otherwise find a valid starting point for the next component of
652 // the graph and continue with numbering that one. we only do so
653 // if no starting indices were provided by the user (see the
654 // documentation of this function) so produce an error if we got
655 // here and starting indices were given
656 Assert(starting_indices.empty(),
657 ExcMessage("The input graph appears to have more than one "
658 "component, but as stated in the documentation "
659 "we only want to reorder such graphs if no "
660 "starting indices are given. The function was "
661 "called with starting indices, however."));
662
663 if (rows_by_coordination.empty())
664 {
665 // We have more than one component in the connectivity
666 // graph of the sparsity pattern. Set up rows_by_coordination:
667
668 rows_by_coordination.reserve(sparsity.n_rows());
670 row < sparsity.n_rows();
671 ++row)
672 rows_by_coordination.emplace_back(sparsity.row_length(row),
673 row);
674 std::sort(rows_by_coordination.begin(),
675 rows_by_coordination.end());
676 }
677
678 next_round_dofs.push_back(internal::find_unnumbered_starting_index(
679 new_indices, rows_by_coordination, next_candidate));
680 }
681
682
683 // find coordination number for each of these dofs
684 dofs_by_coordination.clear();
685 for (const types::global_dof_index next_round_dof : next_round_dofs)
686 dofs_by_coordination.emplace_back(sparsity.row_length(next_round_dof),
687 next_round_dof);
688 std::sort(dofs_by_coordination.begin(), dofs_by_coordination.end());
689
690 // assign new DoF numbers to the elements of the present front:
691 for (const auto &i : dofs_by_coordination)
692 new_indices[i.second] = next_free_number++;
693
694 // after that: use this round's dofs for the next round
695 last_round_dofs.swap(next_round_dofs);
696 }
697
698 // test for all indices numbered. this mostly tests whether the
699 // front-marching-algorithm (which Cuthill-McKee actually is) has reached
700 // all points.
701 Assert((std::find(new_indices.begin(),
702 new_indices.end(),
703 numbers::invalid_size_type) == new_indices.end()) &&
704 (next_free_number == sparsity.n_rows()),
706 }
707
708
709
710 namespace internal
711 {
712 void
714 const DynamicSparsityPattern &connectivity,
715 std::vector<DynamicSparsityPattern::size_type> &renumbering)
716 {
717 AssertDimension(connectivity.n_rows(), connectivity.n_cols());
718 AssertDimension(connectivity.n_rows(), renumbering.size());
719 Assert(connectivity.row_index_set().size() == 0 ||
720 connectivity.row_index_set().size() == connectivity.n_rows(),
722 "Only valid for sparsity patterns which store all rows."));
723
724 // The algorithm below works by partitioning the rows in the
725 // connectivity graph, called nodes, into groups. The groups are defined
726 // as those nodes in immediate neighborhood of some pivot node, which we
727 // choose by minimal adjacency below.
728
729 // We define two types of node categories for nodes not yet classified,
730 // one consisting of all nodes we've not seen at all, and one for nodes
731 // identified as neighbors (variable current_neighbors below) but not
732 // yet grouped. We use this classification in combination with an
733 // unsorted vector, which is much faster than keeping a sorted data
734 // structure (e.g. std::set)
735 constexpr types::global_dof_index unseen_node =
737 constexpr types::global_dof_index available_node = unseen_node - 1;
738 const types::global_dof_index n_nodes = connectivity.n_rows();
739 std::vector<types::global_dof_index> touched_nodes(n_nodes, unseen_node);
740
741 std::vector<unsigned int> row_lengths(n_nodes);
742 std::vector<types::global_dof_index> current_neighbors;
743 std::vector<types::global_dof_index> group_starts(1);
744 std::vector<types::global_dof_index> group_indices;
745 group_indices.reserve(n_nodes);
746
747 // First collect the number of neighbors for each node. We use this
748 // field to find the next node with the minimum number of non-touched
749 // neighbors in the field n_remaining_neighbors, so we will count down
750 // on this field. We also cache the row lengths because we need this
751 // data frequently and getting it from the sparsity pattern is more
752 // expensive.
753 for (types::global_dof_index row = 0; row < n_nodes; ++row)
754 {
755 row_lengths[row] = connectivity.row_length(row);
756 Assert(row_lengths[row] > 0, ExcInternalError());
757 }
758 std::vector<unsigned int> n_remaining_neighbors(row_lengths);
759
760 // This outer loop is typically traversed only once, unless the global
761 // graph is not connected
762 while (true)
763 {
764 // Find node with the minimal number of neighbors (typically a
765 // corner node when based on FEM meshes). If no node is left, we are
766 // done. Together with the outer while loop, this loop can possibly
767 // be of quadratic complexity in the number of disconnected
768 // partitions, i.e. up to n_nodes in the worst case,
769 // but that is not the usual use case of this loop and thus not
770 // optimized for.
771 {
772 unsigned int candidate_valence = numbers::invalid_unsigned_int;
773 types::global_dof_index candidate_index =
775 for (types::global_dof_index i = 0; i < n_nodes; ++i)
776 if (touched_nodes[i] == unseen_node)
777 if (row_lengths[i] < candidate_valence)
778 {
779 candidate_index = i;
780 candidate_valence = n_remaining_neighbors[i];
781 if (candidate_valence <= 1)
782 break;
783 }
784 if (candidate_index == numbers::invalid_dof_index)
785 break;
786
787 Assert(candidate_valence > 0, ExcInternalError());
788
789 current_neighbors = {candidate_index};
790 touched_nodes[candidate_index] = available_node;
791 }
792
793 while (true)
794 {
795 // Find node with minimum number of untouched neighbors among
796 // the next set of possible neighbors (= valence), and among the
797 // set of nodes with the minimal number of neighbors, choose the
798 // one with the largest number of touched neighbors (i.e., the
799 // largest row length).
800 //
801 // This loop is typically the most expensive part for large
802 // graphs and thus only run once. We also do some cleanup, i.e.,
803 // the indices added to a group in the previous round need to be
804 // removed at this point.
805 unsigned int candidate_valence = numbers::invalid_unsigned_int;
806 types::global_dof_index candidate_index =
808 unsigned int candidate_row_length = 0;
809 const unsigned int loop_length = current_neighbors.size();
810 unsigned int write_index = 0;
811 for (unsigned int i = 0; i < loop_length; ++i)
812 {
813 const types::global_dof_index node = current_neighbors[i];
814 Assert(touched_nodes[node] != unseen_node,
816 if (touched_nodes[node] == available_node)
817 {
818 current_neighbors[write_index] = node;
819 ++write_index;
820 if (n_remaining_neighbors[node] < candidate_valence ||
821 (n_remaining_neighbors[node] == candidate_valence &&
822 (row_lengths[node] > candidate_row_length ||
823 (row_lengths[node] == candidate_row_length &&
824 node < candidate_index))))
825 {
826 candidate_index = node;
827 candidate_valence = n_remaining_neighbors[node];
828 candidate_row_length = row_lengths[node];
829 }
830 }
831 }
832 current_neighbors.resize(write_index);
833
834 if constexpr (running_in_debug_mode())
835 {
836 for (const types::global_dof_index node : current_neighbors)
837 Assert(touched_nodes[node] == available_node,
839 }
840
841 // No more neighbors left -> terminate loop
842 if (current_neighbors.empty())
843 break;
844
845 // Add the pivot and all direct neighbors of the pivot node not
846 // yet touched to the list of new entries.
847 group_indices.push_back(candidate_index);
848 touched_nodes[candidate_index] = group_starts.size() - 1;
849 const auto end_it = connectivity.end(candidate_index);
850 for (auto it = connectivity.begin(candidate_index); it != end_it;
851 ++it)
852 if (touched_nodes[it->column()] >= available_node)
853 {
854 group_indices.push_back(it->column());
855 touched_nodes[it->column()] = group_starts.size() - 1;
856 }
857 group_starts.push_back(group_indices.size());
858
859 // Add all neighbors of the current list not yet seen to the set
860 // of possible next nodes. The added node is grouped and thus no
861 // longer a valid neighbor (here we assume symmetry of the
862 // connectivity). It will be removed from the list of neighbors
863 // by the code further up in the next iteration of the
864 // surrounding loop.
865 for (types::global_dof_index index =
866 group_starts[group_starts.size() - 2];
867 index < group_starts.back();
868 ++index)
869 {
870 auto it = connectivity.begin(group_indices[index]);
871 const auto end_row = connectivity.end(group_indices[index]);
872 for (; it != end_row; ++it)
873 {
874 if (touched_nodes[it->column()] == unseen_node)
875 {
876 current_neighbors.push_back(it->column());
877 touched_nodes[it->column()] = available_node;
878 }
879 n_remaining_neighbors[it->column()]--;
880 }
881 }
882 }
883 }
884
885 // Sanity check: for all nodes, there should not be any neighbors left
886 for (types::global_dof_index row = 0; row < n_nodes; ++row)
887 Assert(n_remaining_neighbors[row] == 0, ExcInternalError());
888
889 // If the number of groups is smaller than the number of nodes, we
890 // continue by recursively calling this method
891 const unsigned int n_groups = group_starts.size() - 1;
892 if (n_groups < n_nodes)
893 {
894 // Form the connectivity of the groups
895 DynamicSparsityPattern connectivity_next(n_groups, n_groups);
896 for (types::global_dof_index row = 0; row < n_groups; ++row)
897 for (types::global_dof_index index = group_starts[row];
898 index < group_starts[row + 1];
899 ++index)
900 {
901 auto it = connectivity.begin(group_indices[index]);
902 const auto end_it = connectivity.end(group_indices[index]);
903 for (; it != end_it; ++it)
904 connectivity_next.add(row, touched_nodes[it->column()]);
905 }
906
907 // Recursively call the reordering
908 std::vector<types::global_dof_index> renumbering_next(n_groups);
909 reorder_hierarchical(connectivity_next, renumbering_next);
910
911 // Renumber the indices group by group according to the incoming
912 // ordering for the groups
913 for (types::global_dof_index row = 0, c = 0; row < n_groups; ++row)
914 for (types::global_dof_index index =
915 group_starts[renumbering_next[row]];
916 index < group_starts[renumbering_next[row] + 1];
917 ++index, ++c)
918 renumbering[c] = group_indices[index];
919 }
920 else
921 {
922 // All groups should have size one and no more recursion is possible,
923 // so use the numbering of the groups
924 unsigned int c = 0;
925 for (const types::global_dof_index i : group_indices)
926 renumbering[c++] = i;
927 }
928 }
929 } // namespace internal
930
931 void
933 const DynamicSparsityPattern &connectivity,
934 std::vector<DynamicSparsityPattern::size_type> &renumbering)
935 {
936 // the internal renumbering keeps the numbering the wrong way around (but
937 // we cannot invert the numbering inside that method because it is used
938 // recursively), so invert it here
939 internal::reorder_hierarchical(connectivity, renumbering);
940 renumbering = Utilities::invert_permutation(renumbering);
941 }
942
943
944
945#ifdef DEAL_II_WITH_MPI
946
947 void
949 const IndexSet &locally_owned_rows,
950 const MPI_Comm mpi_comm,
951 const IndexSet &locally_relevant_rows)
952 {
953 using map_vec_t =
954 std::map<unsigned int, std::vector<DynamicSparsityPattern::size_type>>;
955
956 // 1. limit rows to non owned:
957 IndexSet requested_rows(locally_relevant_rows);
958 requested_rows.subtract_set(locally_owned_rows);
959
960 std::vector<unsigned int> index_owner =
961 Utilities::MPI::compute_index_owner(locally_owned_rows,
962 requested_rows,
963 mpi_comm);
964
965 // 2. go through requested_rows, figure out the owner and add the row to
966 // request
967 map_vec_t rows_data;
969 i < requested_rows.n_elements();
970 ++i)
971 {
973 requested_rows.nth_index_in_set(i);
974
975 rows_data[index_owner[i]].push_back(row);
976 }
977
978 // 3. get what others ask us to send
979 const auto rows_data_received =
980 Utilities::MPI::some_to_some(mpi_comm, rows_data);
981
982 // 4. now prepare data to be sent in the same format as in
983 // distribute_sparsity_pattern() below, i.e.
984 // rX,num_rX,cols_rX
985 map_vec_t send_data;
986 for (const auto &data : rows_data_received)
987 {
988 for (const auto &row : data.second)
989 {
990 const auto rlen = dsp.row_length(row);
991
992 // skip empty lines
993 if (rlen == 0)
994 continue;
995
996 // save entries
997 send_data[data.first].push_back(row); // row index
998 send_data[data.first].push_back(rlen); // number of entries
999 for (DynamicSparsityPattern::size_type c = 0; c < rlen; ++c)
1000 send_data[data.first].push_back(
1001 dsp.column_number(row, c)); // columns
1002 } // loop over rows
1003 } // loop over received data
1004
1005 // 5. communicate rows
1006 const auto received_data =
1007 Utilities::MPI::some_to_some(mpi_comm, send_data);
1008
1009 // 6. add result to our sparsity
1010 for (const auto &data : received_data)
1011 {
1012 const auto &recv_buf = data.second;
1013 auto ptr = recv_buf.begin();
1014 const auto end = recv_buf.end();
1015 while (ptr != end)
1016 {
1017 const auto row = *(ptr++);
1018 Assert(ptr != end, ExcInternalError());
1019
1020 const auto n_entries = *(ptr++);
1021 Assert(n_entries > 0, ExcInternalError());
1022 Assert(ptr != end, ExcInternalError());
1023
1024 // make sure we clear whatever was previously stored
1025 // in these rows. Otherwise we can't guarantee that the
1026 // data is consistent across MPI communicator.
1027 dsp.clear_row(row);
1028
1029 Assert(ptr + (n_entries - 1) != end, ExcInternalError());
1030 dsp.add_entries(row, ptr, ptr + n_entries, true);
1031 ptr += n_entries;
1032 }
1033 Assert(ptr == end, ExcInternalError());
1034 }
1035 }
1036
1037
1038
1039 void
1042 const std::vector<DynamicSparsityPattern::size_type> &rows_per_cpu,
1043 const MPI_Comm mpi_comm,
1044 const IndexSet &myrange)
1045 {
1046 const unsigned int myid = Utilities::MPI::this_mpi_process(mpi_comm);
1047 std::vector<DynamicSparsityPattern::size_type> start_index(
1048 rows_per_cpu.size() + 1);
1049 start_index[0] = 0;
1050 for (DynamicSparsityPattern::size_type i = 0; i < rows_per_cpu.size(); ++i)
1051 start_index[i + 1] = start_index[i] + rows_per_cpu[i];
1052
1053 IndexSet owned(start_index.back());
1054 owned.add_range(start_index[myid], start_index[myid] + rows_per_cpu[myid]);
1055
1056 distribute_sparsity_pattern(dsp, owned, mpi_comm, myrange);
1057 }
1058
1059
1060
1061 void
1063 const IndexSet &locally_owned_rows,
1064 const MPI_Comm mpi_comm,
1065 const IndexSet &locally_relevant_rows)
1066 {
1068 dsp.row_index_set() == locally_relevant_rows,
1069 ExcMessage(
1070 "The DynamicSparsityPattern must be initialized with an IndexSet that contains locally relevant indices."));
1071
1072 IndexSet requested_rows(locally_relevant_rows);
1073 requested_rows.subtract_set(locally_owned_rows);
1074
1075 std::vector<unsigned int> index_owner =
1076 Utilities::MPI::compute_index_owner(locally_owned_rows,
1077 requested_rows,
1078 mpi_comm);
1079
1080 using map_vec_t =
1081 std::map<unsigned int, std::vector<DynamicSparsityPattern::size_type>>;
1082
1083 map_vec_t send_data;
1084
1086 i < requested_rows.n_elements();
1087 ++i)
1088 {
1090 requested_rows.nth_index_in_set(i);
1091
1092 const auto rlen = dsp.row_length(row);
1093
1094 // skip empty lines
1095 if (rlen == 0)
1096 continue;
1097
1098 // save entries
1099 send_data[index_owner[i]].push_back(row); // row index
1100 send_data[index_owner[i]].push_back(rlen); // number of entries
1101 for (DynamicSparsityPattern::size_type c = 0; c < rlen; ++c)
1102 {
1103 // columns
1104 const auto column = dsp.column_number(row, c);
1105 send_data[index_owner[i]].push_back(column);
1106 }
1107 }
1108
1109 const auto receive_data = Utilities::MPI::some_to_some(mpi_comm, send_data);
1110
1111 // add what we received
1112 for (const auto &data : receive_data)
1113 {
1114 const auto &recv_buf = data.second;
1115 auto ptr = recv_buf.begin();
1116 const auto end = recv_buf.end();
1117 while (ptr != end)
1118 {
1119 const auto row = *(ptr++);
1120 Assert(ptr != end, ExcInternalError());
1121 const auto n_entries = *(ptr++);
1122
1123 Assert(ptr + (n_entries - 1) != end, ExcInternalError());
1124 dsp.add_entries(row, ptr, ptr + n_entries, true);
1125 ptr += n_entries;
1126 }
1127 Assert(ptr == end, ExcInternalError());
1128 }
1129 }
1130
1131
1132
1133 void
1135 const std::vector<IndexSet> &owned_set_per_cpu,
1136 const MPI_Comm mpi_comm,
1137 const IndexSet &myrange)
1138 {
1139 const unsigned int myid = Utilities::MPI::this_mpi_process(mpi_comm);
1141 owned_set_per_cpu[myid],
1142 mpi_comm,
1143 myrange);
1144 }
1145
1146
1147
1148 void
1150 const IndexSet &locally_owned_rows,
1151 const MPI_Comm mpi_comm,
1152 const IndexSet &locally_relevant_rows)
1153 {
1154 using map_vec_t =
1156 std::vector<BlockDynamicSparsityPattern::size_type>>;
1157 map_vec_t send_data;
1158
1159 IndexSet requested_rows(locally_relevant_rows);
1160 requested_rows.subtract_set(locally_owned_rows);
1161
1162 std::vector<unsigned int> index_owner =
1163 Utilities::MPI::compute_index_owner(locally_owned_rows,
1164 requested_rows,
1165 mpi_comm);
1166
1168 i < requested_rows.n_elements();
1169 ++i)
1170 {
1172 requested_rows.nth_index_in_set(i);
1173
1175
1176 // skip empty lines
1177 if (rlen == 0)
1178 continue;
1179
1180 // save entries
1181 std::vector<BlockDynamicSparsityPattern::size_type> &dst =
1182 send_data[index_owner[i]];
1183
1184 dst.push_back(rlen); // number of entries
1185 dst.push_back(row); // row index
1186 for (BlockDynamicSparsityPattern::size_type c = 0; c < rlen; ++c)
1187 {
1188 // columns
1190 dsp.column_number(row, c);
1191 dst.push_back(column);
1192 }
1193 }
1194
1195 unsigned int num_receive = 0;
1196 {
1197 std::vector<unsigned int> send_to;
1198 send_to.reserve(send_data.size());
1199 for (const auto &sparsity_line : send_data)
1200 send_to.push_back(sparsity_line.first);
1201
1202 num_receive =
1204 send_to);
1205 }
1206
1207 std::vector<MPI_Request> requests(send_data.size());
1208
1209
1210 // send data
1211
1213 Utilities::MPI::CollectiveMutex::ScopedLock lock(mutex, mpi_comm);
1214
1215 const int mpi_tag = Utilities::MPI::internal::Tags::
1217
1218 {
1219 unsigned int idx = 0;
1220 for (const auto &sparsity_line : send_data)
1221 {
1222 const int ierr = MPI_Isend(
1223 sparsity_line.second.data(),
1224 sparsity_line.second.size(),
1225 Utilities::MPI::mpi_type_id_for_type<types::global_dof_index>,
1226 sparsity_line.first,
1227 mpi_tag,
1228 mpi_comm,
1229 &requests[idx++]);
1230 AssertThrowMPI(ierr);
1231 }
1232 }
1233
1234 {
1235 // receive
1236 std::vector<BlockDynamicSparsityPattern::size_type> recv_buf;
1237 for (unsigned int index = 0; index < num_receive; ++index)
1238 {
1239 MPI_Status status;
1240 int ierr = MPI_Probe(MPI_ANY_SOURCE, mpi_tag, mpi_comm, &status);
1241 AssertThrowMPI(ierr);
1242
1243 int len;
1244 ierr = MPI_Get_count(
1245 &status,
1246 Utilities::MPI::mpi_type_id_for_type<types::global_dof_index>,
1247 &len);
1248 AssertThrowMPI(ierr);
1249
1250 recv_buf.resize(len);
1251 ierr = MPI_Recv(
1252 recv_buf.data(),
1253 len,
1254 Utilities::MPI::mpi_type_id_for_type<types::global_dof_index>,
1255 status.MPI_SOURCE,
1256 status.MPI_TAG,
1257 mpi_comm,
1258 &status);
1259 AssertThrowMPI(ierr);
1260
1261 std::vector<BlockDynamicSparsityPattern::size_type>::const_iterator
1262 ptr = recv_buf.begin();
1263 std::vector<BlockDynamicSparsityPattern::size_type>::const_iterator
1264 end = recv_buf.end();
1265 while (ptr != end)
1266 {
1268 Assert(ptr != end, ExcInternalError());
1270 for (unsigned int c = 0; c < num; ++c)
1271 {
1272 Assert(ptr != end, ExcInternalError());
1273 dsp.add(row, *ptr);
1274 ++ptr;
1275 }
1276 }
1277 Assert(ptr == end, ExcInternalError());
1278 }
1279 }
1280
1281 // complete all sends, so that we can safely destroy the buffers.
1282 if (requests.size() > 0)
1283 {
1284 const int ierr =
1285 MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE);
1286 AssertThrowMPI(ierr);
1287 }
1288 }
1289#endif
1290} // namespace SparsityTools
1291
*  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)
types::global_dof_index size_type
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
Point< 2 > second
Definition grid_out.cc:4640
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
void reorder_hierarchical(const DynamicSparsityPattern &connectivity, std::vector< DynamicSparsityPattern::size_type > &renumbering)
DynamicSparsityPattern::size_type find_unnumbered_starting_index(const std::vector< DynamicSparsityPattern::size_type > &new_indices, const std::vector< std::pair< DynamicSparsityPattern::size_type, DynamicSparsityPattern::size_type > > &rows_by_coordination, std::size_t &next_candidate)
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 > &)