1 // ---------------------------------------------------------------------
2 //
3 // Copyright (C) 2010 - 2020 by the deal.II authors
4 //
5 // This file is part of the deal.II library.
6 //
7 // The deal.II library is free software; you can use it, redistribute
8 // it, and/or modify it under the terms of the GNU Lesser General
9 // Public License as published by the Free Software Foundation; either
10 // version 2.1 of the License, or (at your option) any later version.
11 // The full text of the license can be found in the file LICENSE.md at
12 // the top level directory of deal.II.
13 //
14 // ---------------------------------------------------------------------
15
16 #ifndef dealii_block_linear_operator_h
17 #define dealii_block_linear_operator_h
18
19 #include <deal.II/base/config.h>
20
21 #include <deal.II/base/exceptions.h>
22
23 #include <deal.II/lac/linear_operator.h>
24
25
26 DEAL_II_NAMESPACE_OPEN
27
28 // Forward declarations:
29 #ifndef DOXYGEN
30 namespace internal
31 {
32 namespace BlockLinearOperatorImplementation
33 {
34 template <typename PayloadBlockType =
35 internal::LinearOperatorImplementation::EmptyPayload>
36 class EmptyBlockPayload;
37 }
38 } // namespace internal
39
40 template <typename Number>
41 class BlockVector;
42
43 template <typename Range = BlockVector<double>,
44 typename Domain = Range,
45 typename BlockPayload =
46 internal::BlockLinearOperatorImplementation::EmptyBlockPayload<>>
47 class BlockLinearOperator;
48 #endif
49
50 template <typename Range = BlockVector<double>,
51 typename Domain = Range,
52 typename BlockPayload =
53 internal::BlockLinearOperatorImplementation::EmptyBlockPayload<>,
54 typename BlockMatrixType>
55 BlockLinearOperator<Range, Domain, BlockPayload>
56 block_operator(const BlockMatrixType &matrix);
57
58 template <std::size_t m,
59 std::size_t n,
60 typename Range = BlockVector<double>,
61 typename Domain = Range,
62 typename BlockPayload =
63 internal::BlockLinearOperatorImplementation::EmptyBlockPayload<>>
64 BlockLinearOperator<Range, Domain, BlockPayload>
65 block_operator(
66 const std::array<std::array<LinearOperator<typename Range::BlockType,
67 typename Domain::BlockType,
68 typename BlockPayload::BlockType>,
69 n>,
70 m> &);
71
72 template <std::size_t m,
73 typename Range = BlockVector<double>,
74 typename Domain = Range,
75 typename BlockPayload =
76 internal::BlockLinearOperatorImplementation::EmptyBlockPayload<>>
77 BlockLinearOperator<Range, Domain, BlockPayload>
78 block_diagonal_operator(
79 const std::array<LinearOperator<typename Range::BlockType,
80 typename Domain::BlockType,
81 typename BlockPayload::BlockType>,
82 m> &);
83
84 template <std::size_t m,
85 typename Range = BlockVector<double>,
86 typename Domain = Range,
87 typename BlockPayload =
88 internal::BlockLinearOperatorImplementation::EmptyBlockPayload<>>
89 BlockLinearOperator<Range, Domain, BlockPayload>
90 block_diagonal_operator(
91 const LinearOperator<typename Range::BlockType,
92 typename Domain::BlockType,
93 typename BlockPayload::BlockType> &op);
94
95
96
97 /**
98 * A class to store the concept of a block linear operator.
99 *
100 * This class increases the interface of LinearOperator (which encapsulates
101 * the @p Matrix interface) by three additional functions:
102 * @code
103 * std::function<unsigned int()> n_block_rows;
104 * std::function<unsigned int()> n_block_cols;
105 * std::function<BlockType(unsigned int, unsigned int)> block;
106 * @endcode
107 * that describe the underlying block structure (of an otherwise opaque)
108 * linear operator.
109 *
110 * Objects of type BlockLinearOperator can be created similarly to
111 * LinearOperator with a wrapper function:
112 * @code
113 * dealii::BlockSparseMatrix<double> A;
114 * const auto block_op_a = block_operator(A);
115 * @endcode
116 *
117 * Alternatively, there are several helper functions available for creating
118 * instances from multiple independent matrices of possibly different types.
119 * Here is an example of a block diagonal matrix created from a FullMatrix and
120 * a SparseMatrixEZ:
121 *
122 * @code
123 * FullMatrix<double> top_left(2, 2);
124 * top_left(0, 0) = 2.0;
125 * top_left(0, 1) = -1.0;
126 * top_left(1, 0) = -1.0;
127 * top_left(1, 1) = 2.0;
128 *
129 * SparseMatrixEZ<double> bottom_right(4, 4, 4);
130 * for (std::size_t row_n = 0; row_n < 4; ++row_n)
131 * {
132 * bottom_right.add(row_n, row_n, 1.0);
133 * if (row_n < 3)
134 * bottom_right.add(row_n, row_n + 1, -1.0);
135 * }
136 *
137 * auto top_left_op = linear_operator(top_left);
138 * auto bottom_right_op = linear_operator(bottom_right);
139 * std::array<decltype(top_left_op), 2> operators {{top_left_op,
140 * bottom_right_op}};
141 * auto block_op = block_diagonal_operator (operators);
142 *
143 * std::vector<BlockVector<double>::size_type> block_sizes {2, 4};
144 * BlockVector<double> src(block_sizes);
145 * src = 2.0;
146 * BlockVector<double> dst(block_sizes);
147 * block_op.vmult(dst, src); // now equal to 2, 2, 0, 0, 0, 2
148 * @endcode
149 *
150 *
151 * A BlockLinearOperator can be sliced to a LinearOperator at any time. This
152 * removes all information about the underlying block structure (because above
153 * <code>std::function</code> objects are no longer available) - the linear
154 * operator interface, however, remains intact.
155 *
156 * @note This class makes heavy use of <code>std::function</code> objects and
157 * lambda functions. This flexibility comes with a run-time penalty. Only use
158 * this object to encapsulate object with medium to large individual block
159 * sizes, and small block structure (as a rule of thumb, matrix blocks greater
160 * than $1000\times1000$).
161 *
162 *
163 * @ingroup LAOperators
164 */
165 template <typename Range, typename Domain, typename BlockPayload>
166 class BlockLinearOperator
167 : public LinearOperator<Range, Domain, typename BlockPayload::BlockType>
168 {
169 public:
170 using BlockType = LinearOperator<typename Range::BlockType,
171 typename Domain::BlockType,
172 typename BlockPayload::BlockType>;
173
174 /**
175 * Create an empty BlockLinearOperator object.
176 *
177 * All<code>std::function</code> member objects of this class and its base
178 * class LinearOperator are initialized with default variants that throw an
179 * exception upon invocation.
180 */
BlockLinearOperator(const BlockPayload & payload)181 BlockLinearOperator(const BlockPayload &payload)
182 : LinearOperator<Range, Domain, typename BlockPayload::BlockType>(
183 typename BlockPayload::BlockType(payload, payload))
184 {
185 n_block_rows = []() -> unsigned int {
186 Assert(
187 false,
188 ExcMessage(
189 "Uninitialized BlockLinearOperator<Range, Domain>::n_block_rows called"));
190 return 0;
191 };
192
193 n_block_cols = []() -> unsigned int {
194 Assert(
195 false,
196 ExcMessage(
197 "Uninitialized BlockLinearOperator<Range, Domain>::n_block_cols called"));
198 return 0;
199 };
200
201 block = [](unsigned int, unsigned int) -> BlockType {
202 Assert(
203 false,
204 ExcMessage(
205 "Uninitialized BlockLinearOperator<Range, Domain>::block called"));
206 return BlockType();
207 };
208 }
209
210 /**
211 * Default copy constructor.
212 */
213 BlockLinearOperator(
214 const BlockLinearOperator<Range, Domain, BlockPayload> &) = default;
215
216 /**
217 * Templated copy constructor that creates a BlockLinearOperator object from
218 * an object @p op for which the conversion function
219 * <code>block_operator</code> is defined.
220 */
221 template <typename Op>
BlockLinearOperator(const Op & op)222 BlockLinearOperator(const Op &op)
223 {
224 *this = block_operator<Range, Domain, BlockPayload, Op>(op);
225 }
226
227 /**
228 * Create a BlockLinearOperator from a two-dimensional array @p ops of
229 * LinearOperator. This constructor calls the corresponding block_operator()
230 * specialization.
231 */
232 template <std::size_t m, std::size_t n>
BlockLinearOperator(const std::array<std::array<BlockType,n>,m> & ops)233 BlockLinearOperator(const std::array<std::array<BlockType, n>, m> &ops)
234 {
235 *this = block_operator<m, n, Range, Domain, BlockPayload>(ops);
236 }
237
238 /**
239 * Create a block-diagonal BlockLinearOperator from a one-dimensional array
240 * @p ops of LinearOperator. This constructor calls the corresponding
241 * block_operator() specialization.
242 */
243 template <std::size_t m>
BlockLinearOperator(const std::array<BlockType,m> & ops)244 BlockLinearOperator(const std::array<BlockType, m> &ops)
245 {
246 *this = block_diagonal_operator<m, Range, Domain, BlockPayload>(ops);
247 }
248
249 /**
250 * Default copy assignment operator.
251 */
252 BlockLinearOperator<Range, Domain, BlockPayload> &
253 operator=(const BlockLinearOperator<Range, Domain, BlockPayload> &) = default;
254
255 /**
256 * Templated copy assignment operator for an object @p op for which the
257 * conversion function <code>block_operator</code> is defined.
258 */
259 template <typename Op>
260 BlockLinearOperator<Range, Domain, BlockPayload> &
261 operator=(const Op &op)
262 {
263 *this = block_operator<Range, Domain, BlockPayload, Op>(op);
264 return *this;
265 }
266
267 /**
268 * Copy assignment from a two-dimensional array @p ops of LinearOperator.
269 * This assignment operator calls the corresponding block_operator()
270 * specialization.
271 */
272 template <std::size_t m, std::size_t n>
273 BlockLinearOperator<Range, Domain, BlockPayload> &
274 operator=(const std::array<std::array<BlockType, n>, m> &ops)
275 {
276 *this = block_operator<m, n, Range, Domain, BlockPayload>(ops);
277 return *this;
278 }
279
280 /**
281 * Copy assignment from a one-dimensional array @p ops of LinearOperator
282 * that creates a block-diagonal BlockLinearOperator. This assignment
283 * operator calls the corresponding block_operator() specialization.
284 */
285 template <std::size_t m>
286 BlockLinearOperator<Range, Domain, BlockPayload> &
287 operator=(const std::array<BlockType, m> &ops)
288 {
289 *this = block_diagonal_operator<m, Range, Domain, BlockPayload>(ops);
290 return *this;
291 }
292
293 /**
294 * Return the number of blocks in a column (i.e, the number of "block rows",
295 * or the number $m$, if interpreted as a $m\times n$ block system).
296 */
297 std::function<unsigned int()> n_block_rows;
298
299 /**
300 * Return the number of blocks in a row (i.e, the number of "block columns",
301 * or the number $n$, if interpreted as a $m\times n$ block system).
302 */
303 std::function<unsigned int()> n_block_cols;
304
305 /**
306 * Access the block with the given coordinates. This
307 * <code>std::function</code> object returns a LinearOperator representing
308 * the $(i,j)$-th block of the BlockLinearOperator.
309 */
310 std::function<BlockType(unsigned int, unsigned int)> block;
311 };
312
313
314 namespace internal
315 {
316 namespace BlockLinearOperatorImplementation
317 {
318 // A helper function to apply a given vmult, or Tvmult to a vector with
319 // intermediate storage, similar to the corresponding helper
320 // function for LinearOperator. Here, two operators are used.
321 // The first one takes care of the first "column" and typically doesn't add.
322 // On the other hand, the second operator is normally an adding one.
323 template <typename Function1,
324 typename Function2,
325 typename Range,
326 typename Domain>
327 void
apply_with_intermediate_storage(const Function1 & first_op,const Function2 & loop_op,Range & v,const Domain & u,bool add)328 apply_with_intermediate_storage(const Function1 &first_op,
329 const Function2 &loop_op,
330 Range & v,
331 const Domain & u,
332 bool add)
333 {
334 GrowingVectorMemory<Range> vector_memory;
335
336 typename VectorMemory<Range>::Pointer tmp(vector_memory);
337 tmp->reinit(v, /*bool omit_zeroing_entries =*/true);
338
339 const unsigned int n = u.n_blocks();
340 const unsigned int m = v.n_blocks();
341
342 for (unsigned int i = 0; i < m; ++i)
343 {
344 first_op(*tmp, u, i, 0);
345 for (unsigned int j = 1; j < n; ++j)
346 loop_op(*tmp, u, i, j);
347 }
348
349 if (add)
350 v += *tmp;
351 else
352 v = *tmp;
353 }
354
355 // Populate the LinearOperator interfaces with the help of the
356 // BlockLinearOperator functions
357 template <typename Range, typename Domain, typename BlockPayload>
358 inline void
populate_linear_operator_functions(dealii::BlockLinearOperator<Range,Domain,BlockPayload> & op)359 populate_linear_operator_functions(
360 dealii::BlockLinearOperator<Range, Domain, BlockPayload> &op)
361 {
362 op.reinit_range_vector = [=](Range &v, bool omit_zeroing_entries) {
363 const unsigned int m = op.n_block_rows();
364
365 // Reinitialize the block vector to m blocks:
366 v.reinit(m);
367
368 // And reinitialize every individual block with reinit_range_vectors:
369 for (unsigned int i = 0; i < m; ++i)
370 op.block(i, 0).reinit_range_vector(v.block(i), omit_zeroing_entries);
371
372 v.collect_sizes();
373 };
374
375 op.reinit_domain_vector = [=](Domain &v, bool omit_zeroing_entries) {
376 const unsigned int n = op.n_block_cols();
377
378 // Reinitialize the block vector to n blocks:
379 v.reinit(n);
380
381 // And reinitialize every individual block with reinit_domain_vectors:
382 for (unsigned int i = 0; i < n; ++i)
383 op.block(0, i).reinit_domain_vector(v.block(i), omit_zeroing_entries);
384
385 v.collect_sizes();
386 };
387
388 op.vmult = [&op](Range &v, const Domain &u) {
389 const unsigned int m = op.n_block_rows();
390 const unsigned int n = op.n_block_cols();
391 Assert(v.n_blocks() == m, ExcDimensionMismatch(v.n_blocks(), m));
392 Assert(u.n_blocks() == n, ExcDimensionMismatch(u.n_blocks(), n));
393
394 if (PointerComparison::equal(&v, &u))
395 {
396 const auto first_op = [&op](Range & v,
397 const Domain & u,
398 const unsigned int i,
399 const unsigned int j) {
400 op.block(i, j).vmult(v.block(i), u.block(j));
401 };
402
403 const auto loop_op = [&op](Range & v,
404 const Domain & u,
405 const unsigned int i,
406 const unsigned int j) {
407 op.block(i, j).vmult_add(v.block(i), u.block(j));
408 };
409
410 apply_with_intermediate_storage(first_op, loop_op, v, u, false);
411 }
412 else
413 {
414 for (unsigned int i = 0; i < m; ++i)
415 {
416 op.block(i, 0).vmult(v.block(i), u.block(0));
417 for (unsigned int j = 1; j < n; ++j)
418 op.block(i, j).vmult_add(v.block(i), u.block(j));
419 }
420 }
421 };
422
423 op.vmult_add = [&op](Range &v, const Domain &u) {
424 const unsigned int m = op.n_block_rows();
425 const unsigned int n = op.n_block_cols();
426 Assert(v.n_blocks() == m, ExcDimensionMismatch(v.n_blocks(), m));
427 Assert(u.n_blocks() == n, ExcDimensionMismatch(u.n_blocks(), n));
428
429 if (PointerComparison::equal(&v, &u))
430 {
431 const auto first_op = [&op](Range & v,
432 const Domain & u,
433 const unsigned int i,
434 const unsigned int j) {
435 op.block(i, j).vmult(v.block(i), u.block(j));
436 };
437
438 const auto loop_op = [&op](Range & v,
439 const Domain & u,
440 const unsigned int i,
441 const unsigned int j) {
442 op.block(i, j).vmult_add(v.block(i), u.block(j));
443 };
444
445 apply_with_intermediate_storage(first_op, loop_op, v, u, true);
446 }
447 else
448 {
449 for (unsigned int i = 0; i < m; ++i)
450 for (unsigned int j = 0; j < n; ++j)
451 op.block(i, j).vmult_add(v.block(i), u.block(j));
452 }
453 };
454
455 op.Tvmult = [&op](Domain &v, const Range &u) {
456 const unsigned int n = op.n_block_cols();
457 const unsigned int m = op.n_block_rows();
458 Assert(v.n_blocks() == n, ExcDimensionMismatch(v.n_blocks(), n));
459 Assert(u.n_blocks() == m, ExcDimensionMismatch(u.n_blocks(), m));
460
461 if (PointerComparison::equal(&v, &u))
462 {
463 const auto first_op = [&op](Range & v,
464 const Domain & u,
465 const unsigned int i,
466 const unsigned int j) {
467 op.block(j, i).Tvmult(v.block(i), u.block(j));
468 };
469
470 const auto loop_op = [&op](Range & v,
471 const Domain & u,
472 const unsigned int i,
473 const unsigned int j) {
474 op.block(j, i).Tvmult_add(v.block(i), u.block(j));
475 };
476
477 apply_with_intermediate_storage(first_op, loop_op, v, u, false);
478 }
479 else
480 {
481 for (unsigned int i = 0; i < n; ++i)
482 {
483 op.block(0, i).Tvmult(v.block(i), u.block(0));
484 for (unsigned int j = 1; j < m; ++j)
485 op.block(j, i).Tvmult_add(v.block(i), u.block(j));
486 }
487 }
488 };
489
490 op.Tvmult_add = [&op](Domain &v, const Range &u) {
491 const unsigned int n = op.n_block_cols();
492 const unsigned int m = op.n_block_rows();
493 Assert(v.n_blocks() == n, ExcDimensionMismatch(v.n_blocks(), n));
494 Assert(u.n_blocks() == m, ExcDimensionMismatch(u.n_blocks(), m));
495
496 if (PointerComparison::equal(&v, &u))
497 {
498 const auto first_op = [&op](Range & v,
499 const Domain & u,
500 const unsigned int i,
501 const unsigned int j) {
502 op.block(j, i).Tvmult(v.block(i), u.block(j));
503 };
504
505 const auto loop_op = [&op](Range & v,
506 const Domain & u,
507 const unsigned int i,
508 const unsigned int j) {
509 op.block(j, i).Tvmult_add(v.block(i), u.block(j));
510 };
511
512 apply_with_intermediate_storage(first_op, loop_op, v, u, true);
513 }
514 else
515 {
516 for (unsigned int i = 0; i < n; ++i)
517 for (unsigned int j = 0; j < m; ++j)
518 op.block(j, i).Tvmult_add(v.block(i), u.block(j));
519 }
520 };
521 }
522
523
524
525 /**
526 * A dummy class for BlockLinearOperators that do not require any
527 * extensions to facilitate the operations of the block matrix or its
528 * subblocks.
529 *
530 * This is the Payload class typically associated with deal.II's native
531 * BlockSparseMatrix. To use either TrilinosWrappers::BlockSparseMatrix or
532 * PETScWrappers::BlockSparseMatrix one must initialize a
533 * BlockLinearOperator with their associated BlockPayload.
534 *
535 *
536 * @ingroup LAOperators
537 */
538 template <typename PayloadBlockType>
539 class EmptyBlockPayload
540 {
541 public:
542 /**
543 * Type of payload held by each subblock
544 */
545 using BlockType = PayloadBlockType;
546
547 /**
548 * Default constructor
549 *
550 * Since this class does not do anything in particular and needs no
551 * special configuration, we have only one generic constructor that can
552 * be called under any conditions.
553 */
554 template <typename... Args>
EmptyBlockPayload(const Args &...)555 EmptyBlockPayload(const Args &...)
556 {}
557 };
558
559 } // namespace BlockLinearOperatorImplementation
560 } // namespace internal
561
562
563
564 /**
565 * @name Creation of a BlockLinearOperator
566 */
567 //@{
568
569 /**
570 * @relatesalso BlockLinearOperator
571 *
572 * A function that encapsulates a @p block_matrix into a BlockLinearOperator.
573 *
574 * All changes made on the block structure and individual blocks of @p
575 * block_matrix after the creation of the BlockLinearOperator object are
576 * reflected by the operator object.
577 *
578 * @ingroup LAOperators
579 */
580 template <typename Range,
581 typename Domain,
582 typename BlockPayload,
583 typename BlockMatrixType>
584 BlockLinearOperator<Range, Domain, BlockPayload>
block_operator(const BlockMatrixType & block_matrix)585 block_operator(const BlockMatrixType &block_matrix)
586 {
587 using BlockType =
588 typename BlockLinearOperator<Range, Domain, BlockPayload>::BlockType;
589
590 BlockLinearOperator<Range, Domain, BlockPayload> return_op{
591 BlockPayload(block_matrix, block_matrix)};
592
593 return_op.n_block_rows = [&block_matrix]() -> unsigned int {
594 return block_matrix.n_block_rows();
595 };
596
597 return_op.n_block_cols = [&block_matrix]() -> unsigned int {
598 return block_matrix.n_block_cols();
599 };
600
601 return_op.block = [&block_matrix](unsigned int i,
602 unsigned int j) -> BlockType {
603 #ifdef DEBUG
604 const unsigned int m = block_matrix.n_block_rows();
605 const unsigned int n = block_matrix.n_block_cols();
606 AssertIndexRange(i, m);
607 AssertIndexRange(j, n);
608 #endif
609
610 return BlockType(block_matrix.block(i, j));
611 };
612
613 populate_linear_operator_functions(return_op);
614 return return_op;
615 }
616
617
618
619 /**
620 * @relatesalso BlockLinearOperator
621 *
622 * A variant of above function that encapsulates a given collection @p ops of
623 * LinearOperators into a block structure. Here, it is assumed that Range and
624 * Domain are block vectors, i.e., derived from
625 * @ref BlockVectorBase.
626 * The individual linear operators in @p ops must act on the underlying vector
627 * type of the block vectors, i.e., on Domain::BlockType yielding a result in
628 * Range::BlockType.
629 *
630 * The list @p ops is best passed as an initializer list. Consider for example
631 * a linear operator block (acting on Vector<double>)
632 * @code
633 * op_a00 | op_a01
634 * |
635 * ---------------
636 * |
637 * op_a10 | op_a11
638 * @endcode
639 * The corresponding block_operator invocation takes the form
640 * @code
641 * block_operator<2, 2, BlockVector<double>>({op_a00, op_a01, op_a10, op_a11});
642 * @endcode
643 *
644 * @ingroup LAOperators
645 */
646 template <std::size_t m,
647 std::size_t n,
648 typename Range,
649 typename Domain,
650 typename BlockPayload>
651 BlockLinearOperator<Range, Domain, BlockPayload>
block_operator(const std::array<std::array<LinearOperator<typename Range::BlockType,typename Domain::BlockType,typename BlockPayload::BlockType>,n>,m> & ops)652 block_operator(
653 const std::array<std::array<LinearOperator<typename Range::BlockType,
654 typename Domain::BlockType,
655 typename BlockPayload::BlockType>,
656 n>,
657 m> &ops)
658 {
659 static_assert(m > 0 && n > 0,
660 "a blocked LinearOperator must consist of at least one block");
661
662 using BlockType =
663 typename BlockLinearOperator<Range, Domain, BlockPayload>::BlockType;
664
665 // TODO: Create block payload so that this can be initialized correctly
666 BlockLinearOperator<Range, Domain, BlockPayload> return_op{BlockPayload()};
667
668 return_op.n_block_rows = []() -> unsigned int { return m; };
669
670 return_op.n_block_cols = []() -> unsigned int { return n; };
671
672 return_op.block = [ops](unsigned int i, unsigned int j) -> BlockType {
673 AssertIndexRange(i, m);
674 AssertIndexRange(j, n);
675
676 return ops[i][j];
677 };
678
679 populate_linear_operator_functions(return_op);
680 return return_op;
681 }
682
683
684
685 /**
686 * @relatesalso BlockLinearOperator
687 *
688 * This function extracts the diagonal blocks of @p block_matrix (either a
689 * block matrix type or a BlockLinearOperator) and creates a
690 * BlockLinearOperator with the diagonal. Off-diagonal elements are
691 * initialized as null_operator (with correct reinit_range_vector and
692 * reinit_domain_vector methods).
693 *
694 * All changes made on the individual diagonal blocks of @p block_matrix after
695 * the creation of the BlockLinearOperator object are reflected by the
696 * operator object.
697 *
698 * @ingroup LAOperators
699 */
700 template <typename Range = BlockVector<double>,
701 typename Domain = Range,
702 typename BlockPayload =
703 internal::BlockLinearOperatorImplementation::EmptyBlockPayload<>,
704 typename BlockMatrixType>
705 BlockLinearOperator<Range, Domain, BlockPayload>
block_diagonal_operator(const BlockMatrixType & block_matrix)706 block_diagonal_operator(const BlockMatrixType &block_matrix)
707 {
708 using BlockType =
709 typename BlockLinearOperator<Range, Domain, BlockPayload>::BlockType;
710
711 BlockLinearOperator<Range, Domain, BlockPayload> return_op{
712 BlockPayload(block_matrix, block_matrix)};
713
714 return_op.n_block_rows = [&block_matrix]() -> unsigned int {
715 return block_matrix.n_block_rows();
716 };
717
718 return_op.n_block_cols = [&block_matrix]() -> unsigned int {
719 return block_matrix.n_block_cols();
720 };
721
722 return_op.block = [&block_matrix](unsigned int i,
723 unsigned int j) -> BlockType {
724 #ifdef DEBUG
725 const unsigned int m = block_matrix.n_block_rows();
726 const unsigned int n = block_matrix.n_block_cols();
727 Assert(m == n, ExcDimensionMismatch(m, n));
728 AssertIndexRange(i, m);
729 AssertIndexRange(j, n);
730 #endif
731 if (i == j)
732 return BlockType(block_matrix.block(i, j));
733 else
734 return null_operator(BlockType(block_matrix.block(i, j)));
735 };
736
737 populate_linear_operator_functions(return_op);
738 return return_op;
739 }
740
741
742
743 /**
744 * @relatesalso BlockLinearOperator
745 *
746 * A variant of above function that builds up a block diagonal linear operator
747 * from an array @p ops of diagonal elements (off-diagonal blocks are assumed
748 * to be 0).
749 *
750 * The list @p ops is best passed as an initializer list. Consider for example
751 * a linear operator block (acting on Vector<double>) <code>diag(op_a0, op_a1,
752 * ..., op_am)</code>. The corresponding block_operator invocation takes the
753 * form
754 * @code
755 * block_diagonal_operator<m, BlockVector<double>>({op_00, op_a1, ..., op_am});
756 * @endcode
757 *
758 * @ingroup LAOperators
759 */
760 template <std::size_t m, typename Range, typename Domain, typename BlockPayload>
761 BlockLinearOperator<Range, Domain, BlockPayload>
block_diagonal_operator(const std::array<LinearOperator<typename Range::BlockType,typename Domain::BlockType,typename BlockPayload::BlockType>,m> & ops)762 block_diagonal_operator(
763 const std::array<LinearOperator<typename Range::BlockType,
764 typename Domain::BlockType,
765 typename BlockPayload::BlockType>,
766 m> &ops)
767 {
768 static_assert(
769 m > 0, "a blockdiagonal LinearOperator must consist of at least one block");
770
771 using BlockType =
772 typename BlockLinearOperator<Range, Domain, BlockPayload>::BlockType;
773
774 std::array<std::array<BlockType, m>, m> new_ops;
775
776 // This is a bit tricky. We have to make sure that the off-diagonal
777 // elements of return_op.ops are populated correctly. They must be
778 // null_operators, but with correct reinit_domain_vector and
779 // reinit_range_vector functions.
780 for (unsigned int i = 0; i < m; ++i)
781 for (unsigned int j = 0; j < m; ++j)
782 if (i == j)
783 {
784 // diagonal elements are easy:
785 new_ops[i][j] = ops[i];
786 }
787 else
788 {
789 // create a null-operator...
790 new_ops[i][j] = null_operator(ops[i]);
791 // ... and fix up reinit_domain_vector:
792 new_ops[i][j].reinit_domain_vector = ops[j].reinit_domain_vector;
793 }
794
795 return block_operator<m, m, Range, Domain>(new_ops);
796 }
797
798
799
800 /**
801 * @relatesalso BlockLinearOperator
802 *
803 * A variant of above function that only takes a single LinearOperator
804 * argument @p op and creates a blockdiagonal linear operator with @p m copies
805 * of it.
806 *
807 * @ingroup LAOperators
808 */
809 template <std::size_t m, typename Range, typename Domain, typename BlockPayload>
810 BlockLinearOperator<Range, Domain, BlockPayload>
block_diagonal_operator(const LinearOperator<typename Range::BlockType,typename Domain::BlockType,typename BlockPayload::BlockType> & op)811 block_diagonal_operator(
812 const LinearOperator<typename Range::BlockType,
813 typename Domain::BlockType,
814 typename BlockPayload::BlockType> &op)
815 {
816 static_assert(m > 0,
817 "a blockdiagonal LinearOperator must consist of at least "
818 "one block");
819
820 using BlockType =
821 typename BlockLinearOperator<Range, Domain, BlockPayload>::BlockType;
822 std::array<BlockType, m> new_ops;
823 new_ops.fill(op);
824
825 return block_diagonal_operator(new_ops);
826 }
827
828
829
830 //@}
831 /**
832 * @name Manipulation of a BlockLinearOperator
833 */
834 //@{
835
836 /**
837 * @relatesalso LinearOperator
838 * @relatesalso BlockLinearOperator
839 *
840 * This function implements forward substitution to invert a lower block
841 * triangular matrix. As arguments, it takes a BlockLinearOperator @p
842 * block_operator representing a block lower triangular matrix, as well as a
843 * BlockLinearOperator @p diagonal_inverse representing inverses of diagonal
844 * blocks of @p block_operator.
845 *
846 * Let us assume we have a linear system with the following block structure:
847 *
848 * @code
849 * A00 x0 + ... = y0
850 * A01 x0 + A11 x1 + ... = y1
851 * ... ...
852 * A0n x0 + A1n x1 + ... + Ann xn = yn
853 * @endcode
854 *
855 * First of all, <code>x0 = A00^-1 y0</code>. Then, we can use x0 to recover
856 * x1:
857 * @code
858 * x1 = A11^-1 ( y1 - A01 x0 )
859 * @endcode
860 * and therefore:
861 * @code
862 * xn = Ann^-1 ( yn - A0n x0 - ... - A(n-1)n x(n-1) )
863 * @endcode
864 *
865 * @note We are not using all blocks of the BlockLinearOperator arguments:
866 * Just the lower triangular block matrix of @p block_operator is used as well
867 * as the diagonal of @p diagonal_inverse.
868 *
869 * @ingroup LAOperators
870 */
871 template <typename Range = BlockVector<double>,
872 typename Domain = Range,
873 typename BlockPayload =
874 internal::BlockLinearOperatorImplementation::EmptyBlockPayload<>>
875 LinearOperator<Domain, Range, typename BlockPayload::BlockType>
block_forward_substitution(const BlockLinearOperator<Range,Domain,BlockPayload> & block_operator,const BlockLinearOperator<Domain,Range,BlockPayload> & diagonal_inverse)876 block_forward_substitution(
877 const BlockLinearOperator<Range, Domain, BlockPayload> &block_operator,
878 const BlockLinearOperator<Domain, Range, BlockPayload> &diagonal_inverse)
879 {
880 LinearOperator<Range, Range, typename BlockPayload::BlockType> return_op{
881 typename BlockPayload::BlockType(diagonal_inverse)};
882
883 return_op.reinit_range_vector = diagonal_inverse.reinit_range_vector;
884 return_op.reinit_domain_vector = diagonal_inverse.reinit_domain_vector;
885
886 return_op.vmult = [block_operator, diagonal_inverse](Range & v,
887 const Range &u) {
888 const unsigned int m = block_operator.n_block_rows();
889 Assert(block_operator.n_block_cols() == m,
890 ExcDimensionMismatch(block_operator.n_block_cols(), m));
891 Assert(diagonal_inverse.n_block_rows() == m,
892 ExcDimensionMismatch(diagonal_inverse.n_block_rows(), m));
893 Assert(diagonal_inverse.n_block_cols() == m,
894 ExcDimensionMismatch(diagonal_inverse.n_block_cols(), m));
895 Assert(v.n_blocks() == m, ExcDimensionMismatch(v.n_blocks(), m));
896 Assert(u.n_blocks() == m, ExcDimensionMismatch(u.n_blocks(), m));
897
898 if (m == 0)
899 return;
900
901 diagonal_inverse.block(0, 0).vmult(v.block(0), u.block(0));
902 for (unsigned int i = 1; i < m; ++i)
903 {
904 auto &dst = v.block(i);
905 dst = u.block(i);
906 dst *= -1.;
907 for (unsigned int j = 0; j < i; ++j)
908 block_operator.block(i, j).vmult_add(dst, v.block(j));
909 dst *= -1.;
910 diagonal_inverse.block(i, i).vmult(dst,
911 dst); // uses intermediate storage
912 }
913 };
914
915 return_op.vmult_add = [block_operator, diagonal_inverse](Range & v,
916 const Range &u) {
917 const unsigned int m = block_operator.n_block_rows();
918 Assert(block_operator.n_block_cols() == m,
919 ExcDimensionMismatch(block_operator.n_block_cols(), m));
920 Assert(diagonal_inverse.n_block_rows() == m,
921 ExcDimensionMismatch(diagonal_inverse.n_block_rows(), m));
922 Assert(diagonal_inverse.n_block_cols() == m,
923 ExcDimensionMismatch(diagonal_inverse.n_block_cols(), m));
924 Assert(v.n_blocks() == m, ExcDimensionMismatch(v.n_blocks(), m));
925 Assert(u.n_blocks() == m, ExcDimensionMismatch(u.n_blocks(), m));
926
927 if (m == 0)
928 return;
929
930 GrowingVectorMemory<typename Range::BlockType> vector_memory;
931 typename VectorMemory<typename Range::BlockType>::Pointer tmp(
932 vector_memory);
933
934 diagonal_inverse.block(0, 0).vmult_add(v.block(0), u.block(0));
935
936 for (unsigned int i = 1; i < m; ++i)
937 {
938 diagonal_inverse.block(i, i).reinit_range_vector(
939 *tmp, /*bool omit_zeroing_entries=*/true);
940 *tmp = u.block(i);
941 *tmp *= -1.;
942 for (unsigned int j = 0; j < i; ++j)
943 block_operator.block(i, j).vmult_add(*tmp, v.block(j));
944 *tmp *= -1.;
945 diagonal_inverse.block(i, i).vmult_add(v.block(i), *tmp);
946 }
947 };
948
949 return return_op;
950 }
951
952
953
954 /**
955 * @relatesalso LinearOperator
956 * @relatesalso BlockLinearOperator
957 *
958 * This function implements back substitution to invert an upper block
959 * triangular matrix. As arguments, it takes a BlockLinearOperator @p
960 * block_operator representing an upper block triangular matrix, as well as a
961 * BlockLinearOperator @p diagonal_inverse representing inverses of diagonal
962 * blocks of @p block_operator.
963 *
964 * Let us assume we have a linear system with the following block structure:
965 *
966 * @code
967 * A00 x0 + A01 x1 + ... + A0n xn = yn
968 * A11 x1 + ... = y1
969 * ... ..
970 * Ann xn = yn
971 * @endcode
972 *
973 * First of all, <code>xn = Ann^-1 yn</code>. Then, we can use xn to recover
974 * x(n-1):
975 * @code
976 * x(n-1) = A(n-1)(n-1)^-1 ( y(n-1) - A(n-1)n x(n-1) )
977 * @endcode
978 * and therefore:
979 * @code
980 * x0 = A00^-1 ( y0 - A0n xn - ... - A01 x1 )
981 * @endcode
982 *
983 * @note We are not using all blocks of the BlockLinearOperator arguments:
984 * Just the upper triangular block matrix of @p block_operator is used as well
985 * as the diagonal of @p diagonal_inverse.
986 *
987 * @ingroup LAOperators
988 */
989 template <typename Range = BlockVector<double>,
990 typename Domain = Range,
991 typename BlockPayload =
992 internal::BlockLinearOperatorImplementation::EmptyBlockPayload<>>
993 LinearOperator<Domain, Range, typename BlockPayload::BlockType>
block_back_substitution(const BlockLinearOperator<Range,Domain,BlockPayload> & block_operator,const BlockLinearOperator<Domain,Range,BlockPayload> & diagonal_inverse)994 block_back_substitution(
995 const BlockLinearOperator<Range, Domain, BlockPayload> &block_operator,
996 const BlockLinearOperator<Domain, Range, BlockPayload> &diagonal_inverse)
997 {
998 LinearOperator<Range, Range, typename BlockPayload::BlockType> return_op{
999 typename BlockPayload::BlockType(diagonal_inverse)};
1000
1001 return_op.reinit_range_vector = diagonal_inverse.reinit_range_vector;
1002 return_op.reinit_domain_vector = diagonal_inverse.reinit_domain_vector;
1003
1004 return_op.vmult = [block_operator, diagonal_inverse](Range & v,
1005 const Range &u) {
1006 const unsigned int m = block_operator.n_block_rows();
1007 Assert(block_operator.n_block_cols() == m,
1008 ExcDimensionMismatch(block_operator.n_block_cols(), m));
1009 Assert(diagonal_inverse.n_block_rows() == m,
1010 ExcDimensionMismatch(diagonal_inverse.n_block_rows(), m));
1011 Assert(diagonal_inverse.n_block_cols() == m,
1012 ExcDimensionMismatch(diagonal_inverse.n_block_cols(), m));
1013 Assert(v.n_blocks() == m, ExcDimensionMismatch(v.n_blocks(), m));
1014 Assert(u.n_blocks() == m, ExcDimensionMismatch(u.n_blocks(), m));
1015
1016 if (m == 0)
1017 return;
1018
1019 diagonal_inverse.block(m - 1, m - 1).vmult(v.block(m - 1), u.block(m - 1));
1020
1021 for (int i = m - 2; i >= 0; --i)
1022 {
1023 auto &dst = v.block(i);
1024 dst = u.block(i);
1025 dst *= -1.;
1026 for (unsigned int j = i + 1; j < m; ++j)
1027 block_operator.block(i, j).vmult_add(dst, v.block(j));
1028 dst *= -1.;
1029 diagonal_inverse.block(i, i).vmult(dst,
1030 dst); // uses intermediate storage
1031 }
1032 };
1033
1034 return_op.vmult_add = [block_operator, diagonal_inverse](Range & v,
1035 const Range &u) {
1036 const unsigned int m = block_operator.n_block_rows();
1037 Assert(block_operator.n_block_cols() == m,
1038 ExcDimensionMismatch(block_operator.n_block_cols(), m));
1039 Assert(diagonal_inverse.n_block_rows() == m,
1040 ExcDimensionMismatch(diagonal_inverse.n_block_rows(), m));
1041 Assert(diagonal_inverse.n_block_cols() == m,
1042 ExcDimensionMismatch(diagonal_inverse.n_block_cols(), m));
1043 Assert(v.n_blocks() == m, ExcDimensionMismatch(v.n_blocks(), m));
1044 Assert(u.n_blocks() == m, ExcDimensionMismatch(u.n_blocks(), m));
1045 GrowingVectorMemory<typename Range::BlockType> vector_memory;
1046 typename VectorMemory<typename Range::BlockType>::Pointer tmp(
1047 vector_memory);
1048
1049 if (m == 0)
1050 return;
1051
1052 diagonal_inverse.block(m - 1, m - 1)
1053 .vmult_add(v.block(m - 1), u.block(m - 1));
1054
1055 for (int i = m - 2; i >= 0; --i)
1056 {
1057 diagonal_inverse.block(i, i).reinit_range_vector(
1058 *tmp, /*bool omit_zeroing_entries=*/true);
1059 *tmp = u.block(i);
1060 *tmp *= -1.;
1061 for (unsigned int j = i + 1; j < m; ++j)
1062 block_operator.block(i, j).vmult_add(*tmp, v.block(j));
1063 *tmp *= -1.;
1064 diagonal_inverse.block(i, i).vmult_add(v.block(i), *tmp);
1065 }
1066 };
1067
1068 return return_op;
1069 }
1070
1071 //@}
1072
1073 DEAL_II_NAMESPACE_CLOSE
1074
1075 #endif
1076