deal.II version GIT relicensing-2291-g668cd86249 2024-12-24 11:30:00+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
partitioner.cc
Go to the documentation of this file.
1// ------------------------------------------------------------------------
2//
3// SPDX-License-Identifier: LGPL-2.1-or-later
4// Copyright (C) 2012 - 2023 by the deal.II authors
5//
6// This file is part of the deal.II library.
7//
8// Part of the source code is dual licensed under Apache-2.0 WITH
9// LLVM-exception OR LGPL-2.1-or-later. Detailed license information
10// governing the source code and code contributions can be found in
11// LICENSE.md and CONTRIBUTING.md at the top level directory of deal.II.
12//
13// ------------------------------------------------------------------------
14
16#include <deal.II/base/mpi.h>
18#include <deal.II/base/partitioner.templates.h>
19
20#include <boost/serialization/utility.hpp>
21
22#include <limits>
23
25
26#ifndef DOXYGEN
27namespace Utilities
28{
29 namespace MPI
30 {
32 : global_size(0)
33 , local_range_data(
35 , n_ghost_indices_data(0)
36 , n_import_indices_data(0)
37 , n_ghost_indices_in_larger_set(0)
38 , my_pid(0)
39 , n_procs(1)
40 , communicator(MPI_COMM_SELF)
41 , have_ghost_indices(false)
42 {}
43
44
45
46 Partitioner::Partitioner(const unsigned int size)
47 : global_size(size)
48 , locally_owned_range_data(size)
49 , local_range_data(
51 , n_ghost_indices_data(0)
52 , n_import_indices_data(0)
53 , n_ghost_indices_in_larger_set(0)
54 , my_pid(0)
55 , n_procs(1)
56 , communicator(MPI_COMM_SELF)
57 , have_ghost_indices(false)
58 {
59 locally_owned_range_data.add_range(0, size);
60 locally_owned_range_data.compress();
61 ghost_indices_data.set_size(size);
62 }
63
64
65
66 Partitioner::Partitioner(const types::global_dof_index local_size,
67 const types::global_dof_index ghost_size,
68 const MPI_Comm communicator)
69 : global_size(Utilities::MPI::sum<types::global_dof_index>(local_size,
70 communicator))
71 , locally_owned_range_data(global_size)
72 , local_range_data{0, local_size}
73 , n_ghost_indices_data(ghost_size)
74 , n_import_indices_data(0)
75 , n_ghost_indices_in_larger_set(0)
76 , my_pid(Utilities::MPI::this_mpi_process(communicator))
77 , n_procs(Utilities::MPI::n_mpi_processes(communicator))
78 , communicator(communicator)
79 , have_ghost_indices(true)
80 {
81 types::global_dof_index prefix_sum = 0;
82
83# ifdef DEAL_II_WITH_MPI
84 const int ierr =
85 MPI_Exscan(&local_size,
86 &prefix_sum,
87 1,
88 Utilities::MPI::mpi_type_id_for_type<decltype(prefix_sum)>,
89 MPI_SUM,
90 communicator);
91 AssertThrowMPI(ierr);
92# endif
93
94 local_range_data = {prefix_sum, prefix_sum + local_size};
95
96 locally_owned_range_data.add_range(prefix_sum, prefix_sum + local_size);
97 locally_owned_range_data.compress();
98 }
99
100
101
102 Partitioner::Partitioner(const IndexSet &locally_owned_indices,
103 const IndexSet &ghost_indices_in,
104 const MPI_Comm communicator_in)
105 : global_size(
106 static_cast<types::global_dof_index>(locally_owned_indices.size()))
107 , n_ghost_indices_data(0)
108 , n_import_indices_data(0)
109 , n_ghost_indices_in_larger_set(0)
110 , my_pid(0)
111 , n_procs(1)
112 , communicator(communicator_in)
113 , have_ghost_indices(false)
114 {
115 set_owned_indices(locally_owned_indices);
116 set_ghost_indices(ghost_indices_in);
117 }
118
119
120
121 Partitioner::Partitioner(const IndexSet &locally_owned_indices,
122 const MPI_Comm communicator_in)
123 : global_size(
124 static_cast<types::global_dof_index>(locally_owned_indices.size()))
125 , n_ghost_indices_data(0)
126 , n_import_indices_data(0)
127 , n_ghost_indices_in_larger_set(0)
128 , my_pid(0)
129 , n_procs(1)
130 , communicator(communicator_in)
131 , have_ghost_indices(false)
132 {
133 set_owned_indices(locally_owned_indices);
134 }
135
136
137
138 void
139 Partitioner::reinit(const IndexSet &locally_owned_indices,
140 const IndexSet &ghost_indices,
141 const MPI_Comm communicator_in)
142 {
143 have_ghost_indices = false;
144 communicator = communicator_in;
145 set_owned_indices(locally_owned_indices);
146 set_ghost_indices(ghost_indices);
147 }
148
149
150
151 void
152 Partitioner::set_owned_indices(const IndexSet &locally_owned_indices)
153 {
154 my_pid = Utilities::MPI::this_mpi_process(communicator);
156
157 // set the local range
158 Assert(locally_owned_indices.is_contiguous() == true,
159 ExcMessage("The index set specified in locally_owned_indices "
160 "is not contiguous."));
161 locally_owned_indices.compress();
162 if (locally_owned_indices.n_elements() > 0)
163 local_range_data =
164 std::pair<types::global_dof_index, types::global_dof_index>(
165 locally_owned_indices.nth_index_in_set(0),
166 locally_owned_indices.nth_index_in_set(0) +
167 locally_owned_indices.n_elements());
169 local_range_data.second - local_range_data.first <
170 static_cast<types::global_dof_index>(
171 std::numeric_limits<unsigned int>::max()),
173 "Index overflow: This class supports at most 2^32-1 locally owned vector entries"));
174 locally_owned_range_data.set_size(locally_owned_indices.size());
175 locally_owned_range_data.add_range(local_range_data.first,
176 local_range_data.second);
177 locally_owned_range_data.compress();
178
179 ghost_indices_data.set_size(locally_owned_indices.size());
180 }
181
182
183
184 void
185 Partitioner::set_ghost_indices(const IndexSet &ghost_indices_in,
186 const IndexSet &larger_ghost_index_set)
187 {
188 // Set ghost indices from input. To be sure that no entries from the
189 // locally owned range are present, subtract the locally owned indices
190 // in any case.
191 Assert(ghost_indices_in.n_elements() == 0 ||
192 ghost_indices_in.size() == locally_owned_range_data.size(),
193 ExcDimensionMismatch(ghost_indices_in.size(),
194 locally_owned_range_data.size()));
195
196 ghost_indices_data = ghost_indices_in;
197 if (ghost_indices_data.size() != locally_owned_range_data.size())
198 ghost_indices_data.set_size(locally_owned_range_data.size());
199 ghost_indices_data.subtract_set(locally_owned_range_data);
200 ghost_indices_data.compress();
202 ghost_indices_data.n_elements() <
203 static_cast<types::global_dof_index>(
204 std::numeric_limits<unsigned int>::max()),
206 "Index overflow: This class supports at most 2^32-1 ghost elements"));
207 n_ghost_indices_data = ghost_indices_data.n_elements();
208
209 have_ghost_indices =
210 Utilities::MPI::max(n_ghost_indices_data, communicator) > 0;
211
212 // In the rest of this function, we determine the point-to-point
213 // communication pattern of the partitioner. We make up a list with both
214 // the processors the ghost indices actually belong to, and the indices
215 // that are locally held but ghost indices of other processors. This
216 // allows then to import and export data very easily.
217
218 // find out the end index for each processor and communicate it (this
219 // implies the start index for the next processor)
220# ifdef DEAL_II_WITH_MPI
221 if (n_procs < 2)
222 {
223 Assert(ghost_indices_data.n_elements() == 0, ExcInternalError());
224 Assert(n_import_indices_data == 0, ExcInternalError());
225 Assert(n_ghost_indices_data == 0, ExcInternalError());
226 return;
227 }
228
230
231 // Allow non-zero start index for the vector. Part 1:
232 // Assume for now that the index set of rank 0 starts with 0
233 // and therefore has an increased size.
234 if (my_pid == 0)
235 my_size += local_range_data.first;
236
237 types::global_dof_index my_shift = 0;
238 {
239 const int ierr = MPI_Exscan(&my_size,
240 &my_shift,
241 1,
243 MPI_SUM,
244 communicator);
245 AssertThrowMPI(ierr);
246 }
247
248 // Allow non-zero start index for the vector. Part 2:
249 // We correct the assumption made above and let the
250 // index set of rank 0 actually start from the
251 // correct value, i.e. we correct the shift to
252 // its start.
253 if (my_pid == 0)
254 my_shift = local_range_data.first;
255
256 // Fix the index start in case the index set could not give us that
257 // information.
258 if (local_range_data.first == 0 && my_shift != 0)
259 {
260 const types::global_dof_index old_locally_owned_size =
262 local_range_data.first = my_shift;
263 local_range_data.second = my_shift + old_locally_owned_size;
264 }
265
266 const auto [owning_ranks_of_ghosts, import_data] =
268 locally_owned_range_data, ghost_indices_data, communicator);
269
270
271 {
272 ghost_targets_data = {};
273
274 if (owning_ranks_of_ghosts.size() > 0)
275 {
276 ghost_targets_data.emplace_back(owning_ranks_of_ghosts[0], 0);
277 for (auto i : owning_ranks_of_ghosts)
278 {
279 Assert(i >= ghost_targets_data.back().first,
281 "Expect result of ConsensusAlgorithms::Process to be "
282 "sorted."));
283 if (i == ghost_targets_data.back().first)
284 ghost_targets_data.back().second++;
285 else
286 ghost_targets_data.emplace_back(i, 1);
287 }
288 }
289 }
290
291 // count import requests and set up the compressed indices
292 n_import_indices_data = 0;
293 import_targets_data = {};
294 import_targets_data.reserve(import_data.size());
295 import_indices_chunks_by_rank_data = {};
296 import_indices_chunks_by_rank_data.reserve(import_data.size());
297 import_indices_chunks_by_rank_data.resize(1);
298 for (const auto &i : import_data)
299 if (i.second.n_elements() > 0)
300 {
301 import_targets_data.emplace_back(i.first, i.second.n_elements());
302 n_import_indices_data += i.second.n_elements();
303 import_indices_chunks_by_rank_data.push_back(
304 import_indices_chunks_by_rank_data.back() +
305 i.second.n_intervals());
306 }
307
308 // transform import indices to local index space
309 import_indices_data = {};
310 import_indices_data.reserve(import_indices_chunks_by_rank_data.back());
311 for (const auto &i : import_data)
312 {
313 Assert((i.second & locally_owned_range_data) == i.second,
314 ExcInternalError("Requested indices must be in local range"));
315 for (auto interval = i.second.begin_intervals();
316 interval != i.second.end_intervals();
317 ++interval)
318 import_indices_data.emplace_back(*interval->begin() -
319 local_range_data.first,
320 interval->last() + 1 -
321 local_range_data.first);
322 }
323
324# ifdef DEBUG
325
326 // simple check: the number of processors to which we want to send
327 // ghosts and the processors to which ghosts reference should be the
328 // same
330 Utilities::MPI::sum(import_targets_data.size(), communicator),
331 Utilities::MPI::sum(ghost_targets_data.size(), communicator));
332
333 // simple check: the number of indices to exchange should match from the
334 // ghost indices side and the import indices side
335 AssertDimension(Utilities::MPI::sum(n_import_indices_data, communicator),
336 Utilities::MPI::sum(n_ghost_indices_data, communicator));
337
338 // expensive check that the communication channel is sane -> do a ghost
339 // exchange step and see whether the ghost indices sent to us by other
340 // processes (ghost_indices) are the same as we hold locally
341 // (ghost_indices_ref).
342 const std::vector<types::global_dof_index> ghost_indices_ref =
343 ghost_indices_data.get_index_vector();
344 AssertDimension(ghost_indices_ref.size(), n_ghost_indices());
345 std::vector<types::global_dof_index> indices_to_send(n_import_indices());
346 std::vector<types::global_dof_index> ghost_indices(n_ghost_indices());
347
348 const std::vector<types::global_dof_index> my_indices =
349 locally_owned_range_data.get_index_vector();
350 std::vector<MPI_Request> requests;
351 n_ghost_indices_in_larger_set = n_ghost_indices_data;
352 export_to_ghosted_array_start(127,
354 my_indices.data(), my_indices.size()),
355 make_array_view(indices_to_send),
356 make_array_view(ghost_indices),
357 requests);
358 export_to_ghosted_array_finish(make_array_view(ghost_indices), requests);
359 int flag = 0;
360 const int ierr = MPI_Testall(requests.size(),
361 requests.data(),
362 &flag,
363 MPI_STATUSES_IGNORE);
364 AssertThrowMPI(ierr);
365 Assert(flag == 1,
367 "MPI found unfinished requests. Check communication setup"));
368
369 for (unsigned int i = 0; i < ghost_indices.size(); ++i)
370 AssertDimension(ghost_indices[i], ghost_indices_ref[i]);
371
372# endif
373
374# endif // #ifdef DEAL_II_WITH_MPI
375
376 if (larger_ghost_index_set.size() == 0)
377 {
378 ghost_indices_subset_chunks_by_rank_data.clear();
379 ghost_indices_subset_data.emplace_back(0, n_ghost_indices());
380 n_ghost_indices_in_larger_set = n_ghost_indices_data;
381 }
382 else
383 {
384 AssertDimension(larger_ghost_index_set.size(),
385 ghost_indices_data.size());
386 Assert(
387 (larger_ghost_index_set & locally_owned_range_data).n_elements() ==
388 0,
389 ExcMessage("Ghost index set should not overlap with owned set."));
390 Assert((larger_ghost_index_set & ghost_indices_data) ==
391 ghost_indices_data,
392 ExcMessage("Larger ghost index set must contain the tight "
393 "ghost index set."));
394
395 n_ghost_indices_in_larger_set = larger_ghost_index_set.n_elements();
396
397 // first translate tight ghost indices into indices within the large
398 // set:
399 std::vector<unsigned int> expanded_numbering;
400 for (const ::IndexSet::size_type index : ghost_indices_data)
401 {
402 Assert(larger_ghost_index_set.is_element(index),
403 ExcMessage("The given larger ghost index set must contain "
404 "all indices in the actual index set."));
405 Assert(
406 larger_ghost_index_set.index_within_set(index) <
407 static_cast<types::global_dof_index>(
408 std::numeric_limits<unsigned int>::max()),
410 "Index overflow: This class supports at most 2^32-1 ghost elements"));
411 expanded_numbering.push_back(
412 larger_ghost_index_set.index_within_set(index));
413 }
414
415 // now rework expanded_numbering into ranges and store in:
416 std::vector<std::pair<unsigned int, unsigned int>>
417 ghost_indices_subset;
418 ghost_indices_subset_chunks_by_rank_data.resize(
419 ghost_targets_data.size() + 1);
420 // also populate ghost_indices_subset_chunks_by_rank_data
421 ghost_indices_subset_chunks_by_rank_data[0] = 0;
422 unsigned int shift = 0;
423 for (unsigned int p = 0; p < ghost_targets_data.size(); ++p)
424 {
425 unsigned int last_index = numbers::invalid_unsigned_int - 1;
426 for (unsigned int ii = 0; ii < ghost_targets_data[p].second; ++ii)
427 {
428 const unsigned int i = shift + ii;
429 if (expanded_numbering[i] == last_index + 1)
430 // if contiguous, increment the end of last range:
431 ghost_indices_subset.back().second++;
432 else
433 // otherwise start a new range
434 ghost_indices_subset.emplace_back(expanded_numbering[i],
435 expanded_numbering[i] +
436 1);
437 last_index = expanded_numbering[i];
438 }
439 shift += ghost_targets_data[p].second;
440 ghost_indices_subset_chunks_by_rank_data[p + 1] =
441 ghost_indices_subset.size();
442 }
443 ghost_indices_subset_data = ghost_indices_subset;
444 }
445 }
446
447
448
449 bool
450 Partitioner::is_compatible(const Partitioner &part) const
451 {
452 // if the partitioner points to the same memory location as the calling
453 // processor
454 if (&part == this)
455 return true;
456# ifdef DEAL_II_WITH_MPI
458 {
459 int communicators_same = 0;
460 const int ierr = MPI_Comm_compare(part.communicator,
461 communicator,
462 &communicators_same);
463 AssertThrowMPI(ierr);
464 if (!(communicators_same == MPI_IDENT ||
465 communicators_same == MPI_CONGRUENT))
466 return false;
467 }
468# endif
469 return (global_size == part.global_size &&
470 local_range_data == part.local_range_data &&
471 ghost_indices_data == part.ghost_indices_data);
472 }
473
474
475
476 bool
477 Partitioner::is_globally_compatible(const Partitioner &part) const
478 {
479 return Utilities::MPI::min(static_cast<int>(is_compatible(part)),
480 communicator) == 1;
481 }
482
483
484
485 std::size_t
486 Partitioner::memory_consumption() const
487 {
488 std::size_t memory = MemoryConsumption::memory_consumption(global_size);
489 memory += locally_owned_range_data.memory_consumption();
490 memory += MemoryConsumption::memory_consumption(local_range_data);
491 memory += ghost_indices_data.memory_consumption();
492 memory += sizeof(n_ghost_indices_data);
493 memory += MemoryConsumption::memory_consumption(ghost_targets_data);
494 memory += MemoryConsumption::memory_consumption(import_indices_data);
495 memory += sizeof(import_indices_plain_dev) +
496 sizeof(*import_indices_plain_dev.begin()) *
497 import_indices_plain_dev.capacity();
498 memory += MemoryConsumption::memory_consumption(n_import_indices_data);
499 memory += MemoryConsumption::memory_consumption(import_targets_data);
501 import_indices_chunks_by_rank_data);
502 memory +=
503 MemoryConsumption::memory_consumption(n_ghost_indices_in_larger_set);
505 ghost_indices_subset_chunks_by_rank_data);
506 memory +=
507 MemoryConsumption::memory_consumption(ghost_indices_subset_data);
510 memory += MemoryConsumption::memory_consumption(communicator);
511 memory += MemoryConsumption::memory_consumption(have_ghost_indices);
512 return memory;
513 }
514
515
516
517 void
518 Partitioner::initialize_import_indices_plain_dev() const
519 {
520 const unsigned int n_import_targets = import_targets_data.size();
521 import_indices_plain_dev.reserve(n_import_targets);
522 for (unsigned int i = 0; i < n_import_targets; ++i)
523 {
524 // Expand the indices on the host
525 std::vector<std::pair<unsigned int, unsigned int>>::const_iterator
526 my_imports = import_indices_data.begin() +
527 import_indices_chunks_by_rank_data[i],
528 end_my_imports = import_indices_data.begin() +
529 import_indices_chunks_by_rank_data[i + 1];
530 std::vector<unsigned int> import_indices_plain_host;
531 for (; my_imports != end_my_imports; ++my_imports)
532 {
533 const unsigned int chunk_size =
534 my_imports->second - my_imports->first;
535 for (unsigned int j = 0; j < chunk_size; ++j)
536 import_indices_plain_host.push_back(my_imports->first + j);
537 }
538
539 // Move the indices to the device
540 const auto chunk_size = import_indices_plain_host.size();
541 import_indices_plain_dev.emplace_back("import_indices_plain_dev" +
542 std::to_string(i),
543 chunk_size);
544 Kokkos::deep_copy(import_indices_plain_dev.back(),
545 Kokkos::View<unsigned int *, Kokkos::HostSpace>(
546 import_indices_plain_host.data(), chunk_size));
547 }
548 }
549
550 } // namespace MPI
551
552} // end of namespace Utilities
553
554#endif
555
556// explicit instantiations from .templates.h file
557#include "partitioner.inst"
558
ArrayView< std::remove_reference_t< typename std::iterator_traits< Iterator >::reference >, MemorySpaceType > make_array_view(const Iterator begin, const Iterator end)
Definition array_view.h:949
bool is_contiguous() const
Definition index_set.h:1917
size_type size() const
Definition index_set.h:1776
size_type index_within_set(const size_type global_index) const
Definition index_set.h:2001
size_type n_elements() const
Definition index_set.h:1934
bool is_element(const size_type index) const
Definition index_set.h:1894
void set_size(const size_type size)
Definition index_set.h:1764
size_type nth_index_in_set(const size_type local_index) const
Definition index_set.h:1982
void compress() const
Definition index_set.h:1784
#define DEAL_II_NAMESPACE_OPEN
Definition config.h:498
#define DEAL_II_NAMESPACE_CLOSE
Definition config.h:499
Point< 2 > second
Definition grid_out.cc:4630
#define Assert(cond, exc)
#define AssertDimension(dim1, dim2)
#define AssertThrowMPI(error_code)
static ::ExceptionBase & ExcInternalError()
static ::ExceptionBase & ExcDimensionMismatch(std::size_t arg1, std::size_t arg2)
static ::ExceptionBase & ExcMessage(std::string arg1)
#define AssertThrow(cond, exc)
std::size_t size
Definition mpi.cc:734
types::global_dof_index locally_owned_size
Definition mpi.cc:822
const unsigned int n_procs
Definition mpi.cc:924
void shift(const Tensor< 1, spacedim > &shift_vector, Triangulation< dim, spacedim > &triangulation)
std::enable_if_t< std::is_fundamental_v< T >, std::size_t > memory_consumption(const T &t)
std::pair< std::vector< unsigned int >, std::map< unsigned int, IndexSet > > compute_index_owner_and_requesters(const IndexSet &owned_indices, const IndexSet &indices_to_look_up, const MPI_Comm &comm)
Definition mpi.cc:1860
T sum(const T &t, const MPI_Comm mpi_communicator)
unsigned int n_mpi_processes(const MPI_Comm mpi_communicator)
Definition mpi.cc:92
T max(const T &t, const MPI_Comm mpi_communicator)
T min(const T &t, const MPI_Comm mpi_communicator)
unsigned int this_mpi_process(const MPI_Comm mpi_communicator)
Definition mpi.cc:107
bool job_supports_mpi()
Definition mpi.cc:681
const MPI_Datatype mpi_type_id_for_type
Definition mpi.h:1626
static const unsigned int invalid_unsigned_int
Definition types.h:220
STL namespace.
Definition types.h:32
unsigned int global_dof_index
Definition types.h:81
#define DEAL_II_DOF_INDEX_MPI_TYPE
Definition types.h:102