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
dynamic_sparsity_pattern.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 - 2026 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
15
18
19#include <algorithm>
20#include <cmath>
21#include <functional>
22#include <numeric>
23#include <set>
24
26
27
28
29template <typename ForwardIterator>
30void
32 ForwardIterator end,
33 const bool indices_are_sorted,
34 ScratchData &scratch_data)
35{
36 const auto n_elements = end - begin;
37 if (n_elements <= 0)
38 return;
39
40 // Given some current size, find the next power of 2 (or number of the form
41 // 2^{k + 1} + 2^k) not less than size.
42 auto compute_next_size = [](const std::size_t size) {
43 std::size_t current_size = 1;
44 while (current_size < size)
45 {
46 // try to slot in a not-quite power of 2 if it is a better fit:
47 const auto next = 2 * current_size;
48 const auto next_and_half = 3 * current_size;
49 if (next < size && size <= next_and_half)
50 return next_and_half;
51 else
52 current_size *= 2;
53 }
54 return current_size;
55 };
56
57 auto reserve_next_size = [&](const std::size_t size,
58 std::vector<size_type> &vec) {
59 if (vec.capacity() >= size)
60 return;
61
62 // Try to minimize the number of allocations via some empirical measurements
63 // to map values of n_elements to row sizes:
64 // 1. 1 -> one constraint
65 // 2. 8 -> FE_Q<3>(1), use 32 to cover the full row
66 // 3. 10 -> FE_SimplexP<3>(2), experiments show 75% of rows have 27 entries
67 // or fewer, so use 32
68 // 4. 27 -> FE_Q<3>(2), half of all rows have <= 45 entries, so use 64
69 //
70 // A common lower bound is 2 * dofs_per_cell, so use either a hard-coded
71 // case or that estimate with an upper bound (e.g., we shouldn't allocate 2
72 // * n_elements if someone wants to fill a row of a constrained matrix with
73 // all 1s).
74 std::size_t next_size = 0u;
75 if (entries.size() == 0)
76 switch (n_elements)
77 {
78 case 1:
79 next_size = 1;
80 break;
81 case 8:
82 next_size = 32;
83 break;
84 default:
85 next_size = n_elements < 256 ? compute_next_size(2 * n_elements) :
86 compute_next_size(n_elements);
87 }
88 else
89 next_size = compute_next_size(size);
90 Assert(next_size >= size, ExcInternalError());
91 vec.reserve(next_size);
92 };
93
94 if (indices_are_sorted)
95 {
96 Assert(std::is_sorted(begin, end), ExcInternalError());
97 Assert(std::adjacent_find(begin, end) == end, ExcInternalError());
98
99 std::vector<size_type> &scratch_indices = scratch_data.indices;
100 reserve_next_size(n_elements + entries.size(), scratch_indices);
101 scratch_indices.resize(n_elements + entries.size());
102 scratch_indices.erase(std::set_union(begin,
103 end,
104 entries.begin(),
105 entries.end(),
106 scratch_indices.begin()),
107 scratch_indices.end());
108 scratch_indices.swap(entries);
109 }
110 else
111 {
112 reserve_next_size(n_elements + entries.size(), entries);
113
114 auto lower = entries.begin();
115 auto upper = entries.end();
116 for (auto new_it = begin; new_it < end; ++new_it)
117 {
118 auto it = Utilities::lower_bound(lower, upper, *new_it);
119 if (it == upper || *it != *new_it)
120 {
121 entries.insert(it, *new_it);
122 lower = entries.begin();
123 upper = entries.end();
124 }
125 }
126 }
127
128 Assert(std::is_sorted(entries.begin(), entries.end()), ExcInternalError());
129 Assert(std::adjacent_find(entries.begin(), entries.end()) == entries.end(),
131}
132
133
134
137{
138 return entries.capacity() * sizeof(size_type) + sizeof(Line);
139}
140
141
147
148
149
152 , have_entries(false)
153 , rowset(0)
154{
155 Assert(s.rows == 0 && s.cols == 0,
157 "This constructor can only be called if the provided argument "
158 "is the sparsity pattern for an empty matrix. This constructor can "
159 "not be used to copy-construct a non-empty sparsity pattern."));
160}
161
162
163
165 const size_type n,
166 const IndexSet &rowset_)
168 , have_entries(false)
169 , rowset(0)
170{
171 reinit(m, n, rowset_);
172}
173
174
176 : DynamicSparsityPattern(rowset_.size(), rowset_.size(), rowset_)
177{}
178
179
182 , have_entries(false)
183 , rowset(0)
184{
185 reinit(n, n);
186}
187
188
189
192{
193 Assert(s.n_rows() == 0 && s.n_cols() == 0,
195 "This operator can only be called if the provided argument "
196 "is the sparsity pattern for an empty matrix. This operator can "
197 "not be used to copy a non-empty sparsity pattern."));
198
199 Assert(n_rows() == 0 && n_cols() == 0,
200 ExcMessage("This operator can only be called if the current object is "
201 "empty."));
202
203 return *this;
204}
205
206
207
208void
210 const size_type n,
211 const IndexSet &rowset_)
212{
213 resize(m, n);
214 have_entries = false;
215 rowset = rowset_;
216
217 Assert(rowset.size() == 0 || rowset.size() == m,
219 "The IndexSet argument to this function needs to either "
220 "be empty (indicating the complete set of rows), or have size "
221 "equal to the desired number of rows as specified by the "
222 "first argument to this function. (Of course, the number "
223 "of indices in this IndexSet may be less than the number "
224 "of rows, but the *size* of the IndexSet must be equal.)"));
225
226 std::vector<Line> new_lines(rowset.size() == 0 ? n_rows() :
228 lines.swap(new_lines);
229}
230
231
232
233void
236
237
238
239bool
241{
242 return ((rows == 0) && (cols == 0));
243}
244
245
246
249{
250 if (!have_entries)
251 return 0;
252
253 size_type m = 0;
254 for (const auto &line : lines)
255 {
256 m = std::max(m, static_cast<size_type>(line.entries.size()));
257 }
258
259 return m;
260}
261
262
263
264void
266 const size_type &row,
267 const ArrayView<const size_type> &columns,
268 const bool indices_are_sorted)
269{
270 add_entries(row, columns.begin(), columns.end(), indices_are_sorted);
271}
272
273
274
275bool
277{
280 Assert(
281 rowset.size() == 0 || rowset.is_element(i),
283 "The row IndexSet does not contain the index i. This sparsity pattern "
284 "object cannot know whether the entry (i, j) exists or not."));
285
286 // Avoid a segmentation fault in below code if the row index happens to
287 // not be present in the IndexSet rowset:
288 if (!(rowset.size() == 0 || rowset.is_element(i)))
289 return false;
290
291 if (!have_entries)
292 return false;
293
294 const size_type rowindex =
295 rowset.size() == 0 ? i : rowset.index_within_set(i);
296
297 return std::binary_search(lines[rowindex].entries.begin(),
298 lines[rowindex].entries.end(),
299 j);
300}
301
302
303
304void
306{
308
309 // loop over all elements presently in the sparsity pattern and add the
310 // transpose element. note:
311 //
312 // 1. that the sparsity pattern changes which we work on, but not the present
313 // row
314 //
315 // 2. that the @p{add} function can be called on elements that already exist
316 // without any harm
317 for (size_type row = 0; row < lines.size(); ++row)
318 {
319 const size_type rowindex =
320 rowset.size() == 0 ? row : rowset.nth_index_in_set(row);
321
322 for (const size_type row_entry : lines[row].entries)
323 // add the transpose entry if this is not the diagonal
324 if (rowindex != row_entry)
325 add(row_entry, rowindex);
326 }
327}
328
329
330
331void
333{
334 AssertIndexRange(row, n_rows());
335 if (!have_entries)
336 return;
337
338 if (rowset.size() > 0 && !rowset.is_element(row))
339 return;
340
341 const size_type rowindex =
342 rowset.size() == 0 ? row : rowset.index_within_set(row);
343
344 AssertIndexRange(rowindex, lines.size());
345 lines[rowindex].entries = std::vector<size_type>();
346}
347
348
349
352{
354 view.reinit(rows.n_elements(), this->n_cols());
355 AssertDimension(rows.size(), this->n_rows());
356
357 const auto end = rows.end();
359 for (auto it = rows.begin(); it != end; ++it, ++view_row)
360 {
361 const size_type rowindex =
362 rowset.size() == 0 ? *it : rowset.index_within_set(*it);
363
364 view.lines[view_row].entries = lines[rowindex].entries;
365 view.have_entries |= (lines[rowindex].entries.size() > 0);
366 }
367 return view;
368}
369
370
371
372template <typename SparsityPatternTypeLeft, typename SparsityPatternTypeRight>
373void
375 const SparsityPatternTypeLeft &sp_A,
376 const SparsityPatternTypeRight &sp_B)
377{
378 Assert(sp_A.n_rows() == sp_B.n_rows(),
379 ExcDimensionMismatch(sp_A.n_rows(), sp_B.n_rows()));
380
381 this->reinit(sp_A.n_cols(), sp_B.n_cols());
382 // we will go through all the
383 // rows in the matrix A, and for each column in a row we add the whole
384 // row of matrix B with that row number. This means that we will insert
385 // a lot of entries to each row, which is best handled by the
386 // DynamicSparsityPattern class.
387
388 std::vector<size_type> new_cols;
389 new_cols.reserve(sp_B.max_entries_per_row());
390
391 // C_{kl} = A_{ik} B_{il}
392 for (size_type i = 0; i < sp_A.n_rows(); ++i)
393 {
394 // get all column numbers from sp_B in a temporary vector:
395 new_cols.resize(sp_B.row_length(i));
396 {
397 const auto last_il = sp_B.end(i);
398 auto *col_ptr = new_cols.data();
399 for (auto il = sp_B.begin(i); il != last_il; ++il)
400 *col_ptr++ = il->column();
401 }
402 std::sort(new_cols.begin(), new_cols.end());
403
404 // now for each k, add new_cols to the target sparsity
405 const auto last_ik = sp_A.end(i);
406 for (auto ik = sp_A.begin(i); ik != last_ik; ++ik)
407 this->add_entries(ik->column(), new_cols.begin(), new_cols.end(), true);
408 }
409}
410
411
412
413template <typename SparsityPatternTypeLeft, typename SparsityPatternTypeRight>
414void
416 const SparsityPatternTypeLeft &left,
417 const SparsityPatternTypeRight &right)
418{
419 Assert(left.n_cols() == right.n_rows(),
420 ExcDimensionMismatch(left.n_cols(), right.n_rows()));
421
422 this->reinit(left.n_rows(), right.n_cols());
423
424 typename SparsityPatternTypeLeft::iterator it_left = left.begin(),
425 end_left = left.end();
426 for (; it_left != end_left; ++it_left)
427 {
428 const unsigned int j = it_left->column();
429
430 // We are sitting on entry (i,j) of the left sparsity pattern. We then
431 // need to add all entries (i,k) to the final sparsity pattern where (j,k)
432 // exists in the right sparsity pattern -- i.e., we need to iterate over
433 // row j.
434 typename SparsityPatternTypeRight::iterator it_right = right.begin(j),
435 end_right = right.end(j);
436 for (; it_right != end_right; ++it_right)
437 this->add(it_left->row(), it_right->column());
438 }
439}
440
441
442
443void
444DynamicSparsityPattern::print(std::ostream &out) const
445{
446 for (size_type row = 0; row < lines.size(); ++row)
447 {
448 out << '[' << (rowset.size() == 0 ? row : rowset.nth_index_in_set(row));
449
450 for (const auto entry : lines[row].entries)
451 out << ',' << entry;
452
453 out << ']' << std::endl;
454 }
455
456 AssertThrow(out.fail() == false, ExcIO());
457}
458
459
460
461void
463{
464 for (size_type row = 0; row < lines.size(); ++row)
465 {
466 const size_type rowindex =
467 rowset.size() == 0 ? row : rowset.nth_index_in_set(row);
468
469 for (const auto entry : lines[row].entries)
470 // while matrix entries are usually
471 // written (i,j), with i vertical and
472 // j horizontal, gnuplot output is
473 // x-y, that is we have to exchange
474 // the order of output
475 out << entry << " " << -static_cast<signed int>(rowindex) << std::endl;
476 }
477
478
479 AssertThrow(out.fail() == false, ExcIO());
480}
481
482
483
486{
487 size_type b = 0;
488 for (size_type row = 0; row < lines.size(); ++row)
489 {
490 const size_type rowindex =
491 rowset.size() == 0 ? row : rowset.nth_index_in_set(row);
492
493 for (const auto entry : lines[row].entries)
494 if (static_cast<size_type>(
495 std::abs(static_cast<int>(rowindex - entry))) > b)
496 b = std::abs(static_cast<signed int>(rowindex - entry));
497 }
498
499 return b;
500}
501
502
503
506{
507 if (!have_entries)
508 return 0;
509
510 size_type n = 0;
511 for (const auto &line : lines)
512 {
513 n += line.entries.size();
514 }
515
516 return n;
517}
518
519
520
523{
524 std::set<types::global_dof_index> cols;
525 for (const auto &line : lines)
526 cols.insert(line.entries.begin(), line.entries.end());
527
528 IndexSet res(this->n_cols());
529 res.add_indices(cols.begin(), cols.end());
530 return res;
531}
532
533
534
537{
538 const IndexSet all_rows = complete_index_set(this->n_rows());
539 const IndexSet &locally_stored_rows = rowset.size() == 0 ? all_rows : rowset;
540
541 std::vector<types::global_dof_index> rows;
542 auto line = lines.begin();
543 AssertDimension(locally_stored_rows.n_elements(), lines.size());
544 for (const auto row : locally_stored_rows)
545 {
546 if (line->entries.size() > 0)
547 rows.push_back(row);
548
549 ++line;
550 }
551
552 IndexSet res(this->n_rows());
553 res.add_indices(rows.begin(), rows.end());
554 return res;
555}
556
557
558
561{
562 size_type mem = sizeof(DynamicSparsityPattern) +
564 sizeof(rowset);
565
566 for (const auto &line : lines)
568
569 return mem;
570}
571
572
573
577 const DynamicSparsityPattern::size_type col) const
578{
579 AssertIndexRange(row, n_rows());
580 AssertIndexRange(col, n_cols());
582
583 const DynamicSparsityPattern::size_type local_row =
584 rowset.size() != 0u ? rowset.index_within_set(row) : row;
585
586 // now we need to do a binary search. Note that col indices are assumed to
587 // be sorted.
588 const auto &cols = lines[local_row].entries;
589 auto it = Utilities::lower_bound(cols.begin(), cols.end(), col);
590
591 if ((it != cols.end()) && (*it == col))
592 return (it - cols.begin());
593 else
595}
596
597
598
599// explicit instantiations
600template void
602 size_type *,
603 const bool,
604 ScratchData &);
605template void
607 const size_type *,
608 const bool,
609 ScratchData &);
610#ifndef DEAL_II_VECTOR_ITERATOR_IS_POINTER
611template void
612DynamicSparsityPattern::Line::add_entries(std::vector<size_type>::iterator,
613 std::vector<size_type>::iterator,
614 const bool,
615 ScratchData &);
616template void
618 std::vector<size_type>::const_iterator,
619 std::vector<size_type>::const_iterator,
620 const bool,
621 ScratchData &);
622#endif
623
624template void
626 const DynamicSparsityPattern &);
627template void
629 const SparsityPattern &);
630template void
632 const DynamicSparsityPattern &);
633template void
635 const SparsityPattern &);
636
637template void
639 const SparsityPattern &);
640template void
642 const SparsityPattern &);
643template void
645 const DynamicSparsityPattern &);
646template void
648 const DynamicSparsityPattern &);
649
*  iterator end()
*  *  iterator begin()
iterator begin() const
Definition array_view.h:755
iterator end() const
Definition array_view.h:764
void add_entries(const size_type row, ForwardIterator begin, ForwardIterator end, const bool indices_are_unique_and_sorted=false)
DynamicSparsityPattern get_view(const IndexSet &rows) const
types::global_dof_index size_type
void compute_mmult_pattern(const SparsityPatternTypeLeft &left, const SparsityPatternTypeRight &right)
virtual void add_row_entries(const size_type &row, const ArrayView< const size_type > &columns, const bool indices_are_sorted=false) override
size_type column_index(const size_type row, const size_type col) const
DynamicSparsityPattern & operator=(const DynamicSparsityPattern &)
Threads::ThreadLocalStorage< ScratchData > scratch_data
void print(std::ostream &out) const
void reinit(const size_type m, const size_type n, const IndexSet &rowset=IndexSet())
void clear_row(const size_type row)
void print_gnuplot(std::ostream &out) const
void compute_Tmmult_pattern(const SparsityPatternTypeLeft &left, const SparsityPatternTypeRight &right)
bool exists(const size_type i, const size_type j) const
void add(const size_type i, const size_type j)
size_type size() const
Definition index_set.h:1759
size_type index_within_set(const size_type global_index) const
Definition index_set.h:1977
size_type n_elements() const
Definition index_set.h:1917
bool is_element(const size_type index) const
Definition index_set.h:1877
size_type nth_index_in_set(const size_type local_index) const
Definition index_set.h:1958
void add_indices(const ForwardIterator &begin, const ForwardIterator &end)
Definition index_set.h:1814
virtual void resize(const size_type rows, const size_type cols)
size_type n_rows() const
size_type n_cols() const
#define DEAL_II_NAMESPACE_OPEN
Definition config.h:38
#define DEAL_II_NAMESPACE_CLOSE
Definition config.h:39
static ::ExceptionBase & ExcIO()
#define Assert(cond, exc)
#define AssertDimension(dim1, dim2)
#define AssertIndexRange(index, range)
static ::ExceptionBase & ExcInternalError()
static ::ExceptionBase & ExcDimensionMismatch(std::size_t arg1, std::size_t arg2)
static ::ExceptionBase & ExcNotQuadratic()
static ::ExceptionBase & ExcMessage(std::string arg1)
#define AssertThrow(cond, exc)
IndexSet complete_index_set(const IndexSet::size_type N)
Definition index_set.h:1187
std::size_t size
Definition mpi.cc:733
std::enable_if_t< std::is_fundamental_v< T >, std::size_t > memory_consumption(const T &t)
Iterator lower_bound(Iterator first, Iterator last, const T &val)
Definition utilities.h:1003
constexpr types::global_dof_index invalid_size_type
Definition types.h:240
::VectorizedArray< Number, width > max(const ::VectorizedArray< Number, width > &, const ::VectorizedArray< Number, width > &)
::VectorizedArray< Number, width > abs(const ::VectorizedArray< Number, width > &)
void add_entries(ForwardIterator begin, ForwardIterator end, const bool indices_are_sorted, ScratchData &scratch_data)