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